novelWriter 2.7.5__py3-none-any.whl → 2.8b1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (196) hide show
  1. novelwriter/__init__.py +8 -7
  2. novelwriter/assets/icons/font_awesome.icons +22 -4
  3. novelwriter/assets/icons/material_filled_normal.icons +20 -2
  4. novelwriter/assets/icons/material_filled_thin.icons +20 -2
  5. novelwriter/assets/icons/material_rounded_normal.icons +20 -2
  6. novelwriter/assets/icons/material_rounded_thin.icons +20 -2
  7. novelwriter/assets/icons/material_sharp_normal.icons +20 -2
  8. novelwriter/assets/icons/material_sharp_thin.icons +20 -2
  9. novelwriter/assets/icons/remix_filled.icons +20 -2
  10. novelwriter/assets/icons/remix_outline.icons +20 -2
  11. novelwriter/assets/images/welcome.webp +0 -0
  12. novelwriter/assets/manual.pdf +0 -0
  13. novelwriter/assets/manual_fr.pdf +0 -0
  14. novelwriter/assets/sample.zip +0 -0
  15. novelwriter/assets/text/credits_en.htm +61 -11
  16. novelwriter/assets/themes/aura.conf +97 -0
  17. novelwriter/assets/themes/aura_bright.conf +95 -0
  18. novelwriter/assets/themes/aura_soft.conf +97 -0
  19. novelwriter/assets/themes/b2t_garden_dark.conf +97 -0
  20. novelwriter/assets/themes/b2t_garden_light.conf +97 -0
  21. novelwriter/assets/themes/b2t_suburb_dark.conf +97 -0
  22. novelwriter/assets/themes/b2t_suburb_light.conf +97 -0
  23. novelwriter/assets/themes/b4t_classic_o_dark.conf +97 -0
  24. novelwriter/assets/themes/b4t_classic_o_light.conf +97 -0
  25. novelwriter/assets/themes/b4t_modern_c_dark.conf +97 -0
  26. novelwriter/assets/themes/b4t_modern_c_light.conf +97 -0
  27. novelwriter/assets/themes/blue_streak_dark.conf +97 -0
  28. novelwriter/assets/themes/blue_streak_light.conf +97 -0
  29. novelwriter/assets/themes/castle_day.conf +95 -0
  30. novelwriter/assets/themes/castle_night.conf +95 -0
  31. novelwriter/assets/themes/catppuccin_latte.conf +97 -0
  32. novelwriter/assets/themes/catppuccin_mocha.conf +97 -0
  33. novelwriter/assets/themes/chalky_soil.conf +95 -0
  34. novelwriter/assets/themes/chernozem.conf +95 -0
  35. novelwriter/assets/themes/cyberpunk_night.conf +88 -40
  36. novelwriter/assets/themes/default_dark.conf +89 -41
  37. novelwriter/assets/themes/default_light.conf +89 -41
  38. novelwriter/assets/themes/dracula.conf +91 -42
  39. novelwriter/assets/themes/espresso.conf +97 -0
  40. novelwriter/assets/themes/everforest_dark.conf +97 -0
  41. novelwriter/assets/themes/everforest_light.conf +97 -0
  42. novelwriter/assets/themes/floral_daydream.conf +95 -0
  43. novelwriter/assets/themes/floral_midnight.conf +95 -0
  44. novelwriter/assets/themes/full_moon.conf +95 -0
  45. novelwriter/assets/themes/grey_dark.conf +97 -0
  46. novelwriter/assets/themes/grey_light.conf +97 -0
  47. novelwriter/assets/themes/horizon_dark.conf +97 -0
  48. novelwriter/assets/themes/horizon_light.conf +97 -0
  49. novelwriter/assets/themes/jewel_case_dark.conf +95 -0
  50. novelwriter/assets/themes/jewel_case_light.conf +95 -0
  51. novelwriter/assets/themes/lcars.conf +97 -0
  52. novelwriter/assets/themes/light_owl.conf +117 -0
  53. novelwriter/assets/themes/new_moon.conf +97 -0
  54. novelwriter/assets/themes/night_owl.conf +117 -0
  55. novelwriter/assets/themes/noctis.conf +129 -0
  56. novelwriter/assets/themes/noctis_lux.conf +129 -0
  57. novelwriter/assets/themes/nord.conf +97 -0
  58. novelwriter/assets/themes/nordlicht.conf +95 -0
  59. novelwriter/assets/themes/otium_dark.conf +95 -0
  60. novelwriter/assets/themes/otium_light.conf +95 -0
  61. novelwriter/assets/themes/paragon.conf +96 -0
  62. novelwriter/assets/themes/primer_light.conf +97 -0
  63. novelwriter/assets/themes/primer_night.conf +97 -0
  64. novelwriter/assets/themes/rose_pine.conf +97 -0
  65. novelwriter/assets/themes/rose_pine_dawn.conf +97 -0
  66. novelwriter/assets/themes/ruby_day.conf +95 -0
  67. novelwriter/assets/themes/ruby_night.conf +95 -0
  68. novelwriter/assets/themes/selenium_dark.conf +95 -0
  69. novelwriter/assets/themes/selenium_light.conf +95 -0
  70. novelwriter/assets/themes/sepia_dark.conf +95 -0
  71. novelwriter/assets/themes/sepia_light.conf +95 -0
  72. novelwriter/assets/themes/snazzy.conf +102 -40
  73. novelwriter/assets/themes/solarized_dark.conf +108 -40
  74. novelwriter/assets/themes/solarized_light.conf +108 -40
  75. novelwriter/assets/themes/sultana_light.conf +95 -0
  76. novelwriter/assets/themes/sultana_night.conf +95 -0
  77. novelwriter/assets/themes/tango_dark.conf +111 -0
  78. novelwriter/assets/themes/tango_light.conf +111 -0
  79. novelwriter/assets/themes/tomorrow.conf +117 -0
  80. novelwriter/assets/themes/tomorrow_night.conf +117 -0
  81. novelwriter/assets/themes/tomorrow_night_blue.conf +117 -0
  82. novelwriter/assets/themes/tomorrow_night_bright.conf +117 -0
  83. novelwriter/assets/themes/tomorrow_night_eighties.conf +117 -0
  84. novelwriter/assets/themes/vivid_black_green.conf +97 -0
  85. novelwriter/assets/themes/vivid_black_red.conf +97 -0
  86. novelwriter/assets/themes/vivid_white_green.conf +97 -0
  87. novelwriter/assets/themes/vivid_white_red.conf +97 -0
  88. novelwriter/assets/themes/warpgate.conf +96 -0
  89. novelwriter/assets/themes/waterlily_dark.conf +95 -0
  90. novelwriter/assets/themes/waterlily_light.conf +95 -0
  91. novelwriter/common.py +47 -17
  92. novelwriter/config.py +57 -62
  93. novelwriter/constants.py +32 -6
  94. novelwriter/core/buildsettings.py +3 -23
  95. novelwriter/core/coretools.py +21 -25
  96. novelwriter/core/docbuild.py +4 -9
  97. novelwriter/core/document.py +2 -6
  98. novelwriter/core/index.py +33 -53
  99. novelwriter/core/indexdata.py +17 -22
  100. novelwriter/core/item.py +11 -35
  101. novelwriter/core/itemmodel.py +5 -21
  102. novelwriter/core/novelmodel.py +3 -7
  103. novelwriter/core/options.py +3 -4
  104. novelwriter/core/project.py +31 -21
  105. novelwriter/core/projectdata.py +2 -21
  106. novelwriter/core/projectxml.py +13 -21
  107. novelwriter/core/sessions.py +2 -4
  108. novelwriter/core/spellcheck.py +12 -13
  109. novelwriter/core/status.py +27 -20
  110. novelwriter/core/storage.py +5 -10
  111. novelwriter/core/tree.py +6 -15
  112. novelwriter/dialogs/about.py +9 -10
  113. novelwriter/dialogs/docmerge.py +17 -14
  114. novelwriter/dialogs/docsplit.py +18 -14
  115. novelwriter/dialogs/editlabel.py +15 -9
  116. novelwriter/dialogs/preferences.py +69 -68
  117. novelwriter/dialogs/projectsettings.py +88 -67
  118. novelwriter/dialogs/quotes.py +15 -10
  119. novelwriter/dialogs/wordlist.py +18 -21
  120. novelwriter/enum.py +75 -30
  121. novelwriter/error.py +6 -11
  122. novelwriter/extensions/configlayout.py +8 -34
  123. novelwriter/extensions/eventfilters.py +3 -3
  124. novelwriter/extensions/modified.py +87 -32
  125. novelwriter/extensions/novelselector.py +13 -12
  126. novelwriter/extensions/pagedsidebar.py +10 -18
  127. novelwriter/extensions/progressbars.py +5 -11
  128. novelwriter/extensions/statusled.py +3 -6
  129. novelwriter/extensions/switch.py +8 -11
  130. novelwriter/extensions/switchbox.py +2 -11
  131. novelwriter/extensions/versioninfo.py +6 -7
  132. novelwriter/formats/shared.py +10 -2
  133. novelwriter/formats/todocx.py +15 -37
  134. novelwriter/formats/tohtml.py +52 -61
  135. novelwriter/formats/tokenizer.py +33 -64
  136. novelwriter/formats/tomarkdown.py +4 -11
  137. novelwriter/formats/toodt.py +12 -71
  138. novelwriter/formats/toqdoc.py +11 -21
  139. novelwriter/formats/toraw.py +2 -6
  140. novelwriter/gui/doceditor.py +160 -225
  141. novelwriter/gui/dochighlight.py +142 -101
  142. novelwriter/gui/docviewer.py +53 -84
  143. novelwriter/gui/docviewerpanel.py +18 -41
  144. novelwriter/gui/editordocument.py +12 -17
  145. novelwriter/gui/itemdetails.py +5 -14
  146. novelwriter/gui/mainmenu.py +24 -32
  147. novelwriter/gui/noveltree.py +13 -51
  148. novelwriter/gui/outline.py +20 -61
  149. novelwriter/gui/projtree.py +40 -96
  150. novelwriter/gui/search.py +9 -24
  151. novelwriter/gui/sidebar.py +54 -22
  152. novelwriter/gui/statusbar.py +7 -22
  153. novelwriter/gui/theme.py +482 -368
  154. novelwriter/guimain.py +87 -101
  155. novelwriter/shared.py +79 -48
  156. novelwriter/splash.py +9 -5
  157. novelwriter/text/comments.py +1 -1
  158. novelwriter/text/counting.py +9 -5
  159. novelwriter/text/patterns.py +20 -15
  160. novelwriter/tools/dictionaries.py +18 -16
  161. novelwriter/tools/lipsum.py +15 -17
  162. novelwriter/tools/manusbuild.py +25 -45
  163. novelwriter/tools/manuscript.py +94 -95
  164. novelwriter/tools/manussettings.py +149 -104
  165. novelwriter/tools/noveldetails.py +10 -24
  166. novelwriter/tools/welcome.py +24 -72
  167. novelwriter/tools/writingstats.py +17 -26
  168. novelwriter/types.py +23 -13
  169. {novelwriter-2.7.5.dist-info → novelwriter-2.8b1.dist-info}/METADATA +7 -7
  170. novelwriter-2.8b1.dist-info/RECORD +212 -0
  171. novelwriter/assets/images/welcome-dark.jpg +0 -0
  172. novelwriter/assets/images/welcome-light.jpg +0 -0
  173. novelwriter/assets/syntax/cyberpunk_night.conf +0 -28
  174. novelwriter/assets/syntax/default_dark.conf +0 -42
  175. novelwriter/assets/syntax/default_light.conf +0 -42
  176. novelwriter/assets/syntax/dracula.conf +0 -44
  177. novelwriter/assets/syntax/grey_dark.conf +0 -29
  178. novelwriter/assets/syntax/grey_light.conf +0 -29
  179. novelwriter/assets/syntax/light_owl.conf +0 -49
  180. novelwriter/assets/syntax/night_owl.conf +0 -49
  181. novelwriter/assets/syntax/snazzy.conf +0 -42
  182. novelwriter/assets/syntax/solarized_dark.conf +0 -29
  183. novelwriter/assets/syntax/solarized_light.conf +0 -29
  184. novelwriter/assets/syntax/tango.conf +0 -39
  185. novelwriter/assets/syntax/tomorrow.conf +0 -49
  186. novelwriter/assets/syntax/tomorrow_night.conf +0 -49
  187. novelwriter/assets/syntax/tomorrow_night_blue.conf +0 -49
  188. novelwriter/assets/syntax/tomorrow_night_bright.conf +0 -49
  189. novelwriter/assets/syntax/tomorrow_night_eighties.conf +0 -49
  190. novelwriter/assets/themes/default.conf +0 -3
  191. novelwriter-2.7.5.dist-info/RECORD +0 -163
  192. {novelwriter-2.7.5.dist-info → novelwriter-2.8b1.dist-info}/WHEEL +0 -0
  193. {novelwriter-2.7.5.dist-info → novelwriter-2.8b1.dist-info}/entry_points.txt +0 -0
  194. {novelwriter-2.7.5.dist-info → novelwriter-2.8b1.dist-info}/licenses/LICENSE.md +0 -0
  195. {novelwriter-2.7.5.dist-info → novelwriter-2.8b1.dist-info}/licenses/setup/LICENSE-Apache-2.0.txt +0 -0
  196. {novelwriter-2.7.5.dist-info → novelwriter-2.8b1.dist-info}/top_level.txt +0 -0
novelwriter/guimain.py CHANGED
@@ -20,7 +20,7 @@ General Public License for more details.
20
20
 
21
21
  You should have received a copy of the GNU General Public License
22
22
  along with this program. If not, see <https://www.gnu.org/licenses/>.
23
- """
23
+ """ # noqa
24
24
  from __future__ import annotations
25
25
 
26
26
  import logging
@@ -30,7 +30,7 @@ from datetime import datetime
30
30
  from pathlib import Path
31
31
  from time import time
32
32
 
33
- from PyQt6.QtCore import Qt, QTimer, pyqtSlot
33
+ from PyQt6.QtCore import QEvent, Qt, QTimer, pyqtSlot
34
34
  from PyQt6.QtGui import QCloseEvent, QCursor, QIcon, QShortcut
35
35
  from PyQt6.QtWidgets import (
36
36
  QApplication, QFileDialog, QHBoxLayout, QMainWindow, QMessageBox,
@@ -38,13 +38,14 @@ from PyQt6.QtWidgets import (
38
38
  )
39
39
 
40
40
  from novelwriter import CONFIG, SHARED, __hexversion__, __version__
41
- from novelwriter.common import formatFileFilter, formatVersion, hexToInt
41
+ from novelwriter.common import formatFileFilter, formatVersion, hexToInt, minmax
42
42
  from novelwriter.constants import nwConst
43
43
  from novelwriter.dialogs.about import GuiAbout
44
44
  from novelwriter.dialogs.preferences import GuiPreferences
45
45
  from novelwriter.dialogs.projectsettings import GuiProjectSettings
46
46
  from novelwriter.dialogs.wordlist import GuiWordList
47
47
  from novelwriter.enum import nwDocAction, nwDocInsert, nwDocMode, nwFocus, nwItemType, nwView
48
+ from novelwriter.extensions.progressbars import NProgressSimple
48
49
  from novelwriter.gui.doceditor import GuiDocEditor
49
50
  from novelwriter.gui.docviewer import GuiDocViewer
50
51
  from novelwriter.gui.docviewerpanel import GuiDocViewerPanel
@@ -67,16 +68,12 @@ logger = logging.getLogger(__name__)
67
68
 
68
69
 
69
70
  class GuiMain(QMainWindow):
70
- """Main GUI Window
71
-
72
- The Main GUI window class. It is the entry point of the
73
- application, and holds all runtime objects aside from the main
74
- Config instance, which is created before the Main GUI.
71
+ """Main GUI Window.
75
72
 
76
- The Main GUI is split up into GUI components, assembled in the init
77
- function. Also, the project instance and theme instance are created
78
- here. These should be passed around to all other objects who need
79
- them and new instances of them should generally not be created.
73
+ The Main GUI window class is the entry point of the application. It
74
+ is split up into GUI components, assembled in the init function.
75
+ Tools and dialog windows are created on demand, but may be cached by
76
+ the Qt library and reused unless explicitly freed after use.
80
77
  """
81
78
 
82
79
  def __init__(self) -> None:
@@ -103,7 +100,7 @@ class GuiMain(QMainWindow):
103
100
  SHARED.initSharedData(self)
104
101
 
105
102
  # Prepare Main Window
106
- self.resize(*CONFIG.mainWinSize)
103
+ self._setWindowSize(CONFIG.mainWinSize)
107
104
  self._updateWindowTitle()
108
105
 
109
106
  nwIcon = CONFIG.assetPath("icons") / "novelwriter.svg"
@@ -169,9 +166,9 @@ class GuiMain(QMainWindow):
169
166
  self.splitMain.addWidget(self.splitDocs)
170
167
  self.splitMain.setOpaqueResize(False)
171
168
  self.splitMain.setHandleWidth(4)
172
- self.splitMain.setSizes(CONFIG.mainPanePos)
173
- self.splitMain.setCollapsible(0, False)
169
+ self.splitMain.setSizes([max(s, 100) for s in CONFIG.mainPanePos])
174
170
  self.splitMain.setCollapsible(0, False)
171
+ self.splitMain.setCollapsible(1, False)
175
172
  self.splitMain.setStretchFactor(1, 0)
176
173
  self.splitMain.setStretchFactor(1, 1)
177
174
 
@@ -185,6 +182,10 @@ class GuiMain(QMainWindow):
185
182
  self.splitView.setVisible(False)
186
183
  self.docEditor.closeSearch()
187
184
 
185
+ # Progress Bar
186
+ self.mainProgress = NProgressSimple(self)
187
+ self.mainProgress.setFixedHeight(2)
188
+
188
189
  # Assemble Main Window Elements
189
190
  self.mainBox = QHBoxLayout()
190
191
  self.mainBox.addWidget(self.sideBar)
@@ -192,8 +193,14 @@ class GuiMain(QMainWindow):
192
193
  self.mainBox.setContentsMargins(0, 0, 0, 0)
193
194
  self.mainBox.setSpacing(0)
194
195
 
196
+ self.outerBox = QVBoxLayout()
197
+ self.outerBox.addLayout(self.mainBox)
198
+ self.outerBox.addWidget(self.mainProgress)
199
+ self.outerBox.setContentsMargins(0, 0, 0, 0)
200
+ self.outerBox.setSpacing(0)
201
+
195
202
  self.mainWidget = QWidget(self)
196
- self.mainWidget.setLayout(self.mainBox)
203
+ self.mainWidget.setLayout(self.outerBox)
197
204
 
198
205
  self.setMenuBar(self.mainMenu)
199
206
  self.setCentralWidget(self.mainWidget)
@@ -316,13 +323,10 @@ class GuiMain(QMainWindow):
316
323
  self.mainStatus.setStatusMessage(self.tr("novelWriter is ready ..."))
317
324
  CONFIG.splashMessage("novelWriter is ready ...")
318
325
 
319
- return
320
-
321
326
  def initMain(self) -> None:
322
327
  """Initialise elements that depend on user settings."""
323
328
  self.asProjTimer.setInterval(int(CONFIG.autoSaveProj*1000))
324
329
  self.asDocTimer.setInterval(int(CONFIG.autoSaveDoc*1000))
325
- return
326
330
 
327
331
  def postLaunchTasks(self, cmdOpen: str | None) -> None:
328
332
  """Process tasks after the main window has been created."""
@@ -344,8 +348,6 @@ class GuiMain(QMainWindow):
344
348
  # before showing any dialogs
345
349
  QTimer.singleShot(50, self.showPostLaunchDialogs)
346
350
 
347
- return
348
-
349
351
  @pyqtSlot()
350
352
  def showPostLaunchDialogs(self) -> None:
351
353
  """Show post launch dialogs."""
@@ -363,8 +365,6 @@ class GuiMain(QMainWindow):
363
365
  ).format(f"<a href='{nwConst.URL_RELEASES}'>", "</a>")
364
366
  SHARED.info(f"{trVersion}<br>{trRelease}")
365
367
 
366
- return
367
-
368
368
  ##
369
369
  # Project Actions
370
370
  ##
@@ -497,7 +497,8 @@ class GuiMain(QMainWindow):
497
497
 
498
498
  # Check if we need to rebuild the index
499
499
  if SHARED.project.index.indexBroken:
500
- SHARED.info(self.tr("The project index is outdated or broken. Rebuilding index."))
500
+ if not SHARED.project.index.indexUpgrade:
501
+ SHARED.warn(self.tr("The project index is broken. Rebuilding index."))
501
502
  self.rebuildIndex()
502
503
 
503
504
  # Make sure the changed status is set to false on things opened
@@ -527,7 +528,6 @@ class GuiMain(QMainWindow):
527
528
  SHARED.setFocusMode(False)
528
529
  self.saveDocument()
529
530
  self.docEditor.clearEditor()
530
- return
531
531
 
532
532
  def openDocument(
533
533
  self,
@@ -584,7 +584,6 @@ class GuiMain(QMainWindow):
584
584
  self.openDocument(nHandle, tLine=1, doScroll=True)
585
585
  elif wrapAround:
586
586
  self.openDocument(fHandle, tLine=1, doScroll=True)
587
- return
588
587
 
589
588
  def saveDocument(self, force: bool = False) -> None:
590
589
  """Save the current documents."""
@@ -592,13 +591,11 @@ class GuiMain(QMainWindow):
592
591
  self.docEditor.saveCursorPosition()
593
592
  if force or self.docEditor.docChanged:
594
593
  self.docEditor.saveText()
595
- return
596
594
 
597
595
  @pyqtSlot()
598
596
  def forceSaveDocument(self) -> None:
599
597
  """Save document even of it has not changed."""
600
598
  self.saveDocument(force=True)
601
- return
602
599
 
603
600
  def viewDocument(self, tHandle: str | None = None, sTitle: str | None = None) -> bool:
604
601
  """Load a document for viewing in the view panel."""
@@ -733,7 +730,7 @@ class GuiMain(QMainWindow):
733
730
 
734
731
  return
735
732
 
736
- def rebuildIndex(self, beQuiet: bool = False) -> None:
733
+ def rebuildIndex(self) -> None:
737
734
  """Rebuild the entire index."""
738
735
  if SHARED.hasProject:
739
736
  logger.info("Rebuilding index ...")
@@ -750,10 +747,7 @@ class GuiMain(QMainWindow):
750
747
  self._updateStatusWordCount()
751
748
  QApplication.restoreOverrideCursor()
752
749
 
753
- if not beQuiet:
754
- SHARED.info(self.tr("The project index has been successfully rebuilt."))
755
-
756
- return
750
+ SHARED.info(self.tr("The project index has been successfully rebuilt."))
757
751
 
758
752
  ##
759
753
  # Main Dialogs
@@ -765,7 +759,6 @@ class GuiMain(QMainWindow):
765
759
  dialog = GuiWelcome(self)
766
760
  dialog.openProjectRequest.connect(self._openProjectFromWelcome)
767
761
  dialog.exec()
768
- return
769
762
 
770
763
  @pyqtSlot()
771
764
  def showPreferencesDialog(self) -> None:
@@ -773,7 +766,6 @@ class GuiMain(QMainWindow):
773
766
  dialog = GuiPreferences(self)
774
767
  dialog.newPreferencesReady.connect(self._processConfigChanges)
775
768
  dialog.exec()
776
- return
777
769
 
778
770
  @pyqtSlot()
779
771
  @pyqtSlot(int)
@@ -783,7 +775,6 @@ class GuiMain(QMainWindow):
783
775
  dialog = GuiProjectSettings(self, gotoPage=focusTab)
784
776
  dialog.newProjectSettingsReady.connect(self._processProjectSettingsChanges)
785
777
  dialog.exec()
786
- return
787
778
 
788
779
  @pyqtSlot()
789
780
  def showNovelDetailsDialog(self) -> None:
@@ -792,7 +783,6 @@ class GuiMain(QMainWindow):
792
783
  dialog = GuiNovelDetails(self)
793
784
  dialog.activateDialog()
794
785
  dialog.updateValues()
795
- return
796
786
 
797
787
  @pyqtSlot()
798
788
  def showBuildManuscriptDialog(self) -> None:
@@ -802,7 +792,6 @@ class GuiMain(QMainWindow):
802
792
  dialog = GuiManuscript(self)
803
793
  dialog.activateDialog()
804
794
  dialog.loadContent()
805
- return
806
795
 
807
796
  @pyqtSlot()
808
797
  def showProjectWordListDialog(self) -> None:
@@ -811,7 +800,6 @@ class GuiMain(QMainWindow):
811
800
  dialog = GuiWordList(self)
812
801
  dialog.newWordListReady.connect(self._processWordListChanges)
813
802
  dialog.exec()
814
- return
815
803
 
816
804
  @pyqtSlot()
817
805
  def showWritingStatsDialog(self) -> None:
@@ -821,31 +809,32 @@ class GuiMain(QMainWindow):
821
809
  dialog = GuiWritingStats(self)
822
810
  dialog.activateDialog()
823
811
  dialog.populateGUI()
824
- return
825
812
 
826
813
  @pyqtSlot()
827
814
  def showAboutNWDialog(self) -> None:
828
815
  """Show the novelWriter about dialog."""
829
816
  dialog = GuiAbout(self)
830
817
  dialog.exec()
831
- return
832
818
 
833
819
  @pyqtSlot()
834
820
  def showAboutQtDialog(self) -> None:
835
821
  """Show the Qt about dialog."""
836
822
  msgBox = QMessageBox(self)
837
823
  msgBox.aboutQt(self, "About Qt")
838
- return
839
824
 
840
825
  @pyqtSlot()
841
826
  def showDictionariesDialog(self) -> None:
842
- """Show the download dictionaries dialog."""
827
+ """Show the download dictionaries dialog and update language
828
+ list when closed.
829
+ """
843
830
  dialog = GuiDictionaries(self)
844
831
  dialog.activateDialog()
845
832
  if not dialog.initDialog():
846
833
  dialog.close()
847
834
  SHARED.error(self.tr("Could not initialise the dialog."))
848
- return
835
+ return
836
+
837
+ dialog.finished.connect(self.mainMenu.updateSpellCheckLanguages)
849
838
 
850
839
  ##
851
840
  # Main Window Actions
@@ -902,16 +891,50 @@ class GuiMain(QMainWindow):
902
891
 
903
892
  return not self.splitView.isVisible()
904
893
 
894
+ def checkThemeUpdate(self) -> None:
895
+ """Load theme if mode changed."""
896
+ if SHARED.theme.loadTheme():
897
+ self.refreshThemeColors(syntax=True)
898
+ self.docEditor.initEditor()
899
+ self.docViewer.initViewer()
900
+
901
+ def refreshThemeColors(self, syntax: bool = False, force: bool = False) -> None:
902
+ """Refresh the GUI theme."""
903
+ SHARED.theme.loadTheme(force=force)
904
+ SHARED.project.updateTheme()
905
+ self.setPalette(QApplication.palette())
906
+ self.docEditor.updateTheme()
907
+ self.docViewer.updateTheme()
908
+ self.docViewerPanel.updateTheme()
909
+ self.sideBar.updateTheme()
910
+ self.projView.updateTheme()
911
+ self.novelView.updateTheme()
912
+ self.projSearch.updateTheme()
913
+ self.outlineView.updateTheme()
914
+ self.itemDetails.updateTheme()
915
+ self.mainStatus.updateTheme()
916
+ SHARED.project.tree.refreshAllItems()
917
+
918
+ if dialog := SHARED.findTopLevelWidget(GuiManuscript):
919
+ dialog.updateTheme()
920
+
921
+ if syntax:
922
+ self.docEditor.updateSyntaxColors()
923
+
905
924
  ##
906
925
  # Events
907
926
  ##
908
927
 
928
+ def changeEvent(self, event: QEvent) -> None:
929
+ """Capture application change events."""
930
+ if int(event.type()) == 210: # ThemeChange
931
+ self.checkThemeUpdate()
932
+
909
933
  def closeEvent(self, event: QCloseEvent) -> None:
910
934
  """Capture the closing event of the GUI and call the close
911
935
  function to handle all the close process steps.
912
936
  """
913
937
  event.accept() if self.closeMain() else event.ignore()
914
- return
915
938
 
916
939
  ##
917
940
  # Public Slots
@@ -919,30 +942,26 @@ class GuiMain(QMainWindow):
919
942
 
920
943
  @pyqtSlot()
921
944
  def toggleFullScreenMode(self) -> None:
922
- """Toggle full screen mode"""
945
+ """Toggle full screen mode."""
923
946
  self.setWindowState(self.windowState() ^ Qt.WindowState.WindowFullScreen)
924
- return
925
947
 
926
948
  @pyqtSlot()
927
949
  def closeDocEditor(self) -> None:
928
950
  """Close the document editor. This does not hide the editor."""
929
951
  self.closeDocument()
930
952
  SHARED.project.data.setLastHandle(None, "editor")
931
- return
932
953
 
933
954
  @pyqtSlot()
934
955
  def closeDocViewer(self) -> None:
935
956
  """Close the document viewer."""
936
957
  self.closeViewerPanel()
937
958
  SHARED.project.data.setLastHandle(None, "viewer")
938
- return
939
959
 
940
960
  @pyqtSlot()
941
961
  def toggleFocusMode(self) -> None:
942
962
  """Toggle focus mode."""
943
963
  if self.docEditor.docHandle:
944
964
  SHARED.setFocusMode(not SHARED.focusMode)
945
- return
946
965
 
947
966
  ##
948
967
  # Private Slots
@@ -960,7 +979,6 @@ class GuiMain(QMainWindow):
960
979
  docViewer = True
961
980
  self.docEditor.changeFocusState(docEditor)
962
981
  self.docViewer.changeFocusState(docViewer)
963
- return
964
982
 
965
983
  @pyqtSlot(bool)
966
984
  def _focusModeChanged(self, focusMode: bool) -> None:
@@ -991,7 +1009,6 @@ class GuiMain(QMainWindow):
991
1009
 
992
1010
  if cursorVisible:
993
1011
  self.docEditor.ensureCursorVisibleNoCentre()
994
- return
995
1012
 
996
1013
  @pyqtSlot(nwFocus)
997
1014
  def _switchFocus(self, paneNo: nwFocus) -> None:
@@ -1017,16 +1034,16 @@ class GuiMain(QMainWindow):
1017
1034
  fP = self.projView.treeHasFocus()
1018
1035
  fN = self.novelView.treeHasFocus()
1019
1036
 
1020
- self._changeView(nwView.EDITOR)
1037
+ self._changeView(nwView.EDITOR, exitFocus=True)
1021
1038
  if (vM and ((vP and fP) or (vN and not fN))) or (not vM and vN):
1022
- self._changeView(nwView.NOVEL)
1039
+ self._changeView(nwView.NOVEL, exitFocus=True)
1023
1040
  self.novelView.setTreeFocus()
1024
1041
  else:
1025
- self._changeView(nwView.PROJECT)
1042
+ self._changeView(nwView.PROJECT, exitFocus=True)
1026
1043
  self.projView.setTreeFocus()
1027
1044
 
1028
1045
  elif paneNo == nwFocus.DOCUMENT:
1029
- self._changeView(nwView.EDITOR)
1046
+ self._changeView(nwView.EDITOR, exitFocus=True)
1030
1047
  hasViewer = self.splitView.isVisible()
1031
1048
  if hasViewer and self.docEditor.anyFocus():
1032
1049
  self.docViewer.setFocus()
@@ -1036,11 +1053,9 @@ class GuiMain(QMainWindow):
1036
1053
  self.docEditor.setFocus()
1037
1054
 
1038
1055
  elif paneNo == nwFocus.OUTLINE:
1039
- self._changeView(nwView.OUTLINE)
1056
+ self._changeView(nwView.OUTLINE, exitFocus=True)
1040
1057
  self.outlineView.setTreeFocus()
1041
1058
 
1042
- return
1043
-
1044
1059
  @pyqtSlot(bool, bool, bool, bool)
1045
1060
  def _processConfigChanges(self, restart: bool, tree: bool, theme: bool, syntax: bool) -> None:
1046
1061
  """Refresh GUI based on flags from the Preferences dialog."""
@@ -1054,23 +1069,7 @@ class GuiMain(QMainWindow):
1054
1069
  self.novelView.refreshCurrentTree()
1055
1070
 
1056
1071
  if theme:
1057
- SHARED.theme.loadTheme()
1058
- self.setPalette(QApplication.palette())
1059
- self.docEditor.updateTheme()
1060
- self.docViewer.updateTheme()
1061
- self.docViewerPanel.updateTheme()
1062
- self.sideBar.updateTheme()
1063
- self.projView.updateTheme()
1064
- self.novelView.updateTheme()
1065
- self.projSearch.updateTheme()
1066
- self.outlineView.updateTheme()
1067
- self.itemDetails.updateTheme()
1068
- self.mainStatus.updateTheme()
1069
- SHARED.project.tree.refreshAllItems()
1070
-
1071
- if syntax:
1072
- SHARED.theme.loadSyntax()
1073
- self.docEditor.updateSyntaxColors()
1072
+ self.refreshThemeColors(syntax=syntax, force=True)
1074
1073
 
1075
1074
  self.docEditor.initEditor()
1076
1075
  self.docViewer.initViewer()
@@ -1088,8 +1087,6 @@ class GuiMain(QMainWindow):
1088
1087
  "Some changes will not be applied until novelWriter has been restarted."
1089
1088
  ))
1090
1089
 
1091
- return
1092
-
1093
1090
  @pyqtSlot()
1094
1091
  def _processProjectSettingsChanges(self) -> None:
1095
1092
  """Refresh data dependent on project settings."""
@@ -1097,7 +1094,6 @@ class GuiMain(QMainWindow):
1097
1094
  SHARED.updateSpellCheckLanguage()
1098
1095
  self.itemDetails.refreshDetails()
1099
1096
  self._updateWindowTitle(SHARED.project.data.name)
1100
- return
1101
1097
 
1102
1098
  @pyqtSlot()
1103
1099
  def _processWordListChanges(self) -> None:
@@ -1105,7 +1101,6 @@ class GuiMain(QMainWindow):
1105
1101
  logger.debug("Reloading word list")
1106
1102
  SHARED.updateSpellCheckLanguage(reload=True)
1107
1103
  self.docEditor.spellCheckDocument()
1108
- return
1109
1104
 
1110
1105
  @pyqtSlot(str, nwDocMode)
1111
1106
  def _followTag(self, tag: str, mode: nwDocMode) -> None:
@@ -1124,7 +1119,6 @@ class GuiMain(QMainWindow):
1124
1119
  self.openDocument(tHandle, sTitle=sTitle)
1125
1120
  elif mode == nwDocMode.VIEW:
1126
1121
  self.viewDocument(tHandle=tHandle, sTitle=sTitle)
1127
- return
1128
1122
 
1129
1123
  @pyqtSlot(Path)
1130
1124
  def _openProjectFromWelcome(self, path: Path) -> None:
@@ -1133,7 +1127,6 @@ class GuiMain(QMainWindow):
1133
1127
  self.openProject(path)
1134
1128
  if not SHARED.hasProject:
1135
1129
  self.showWelcomeDialog()
1136
- return
1137
1130
 
1138
1131
  @pyqtSlot(str, nwDocMode, str, bool)
1139
1132
  def _openDocument(self, tHandle: str, mode: nwDocMode, sTitle: str, setFocus: bool) -> None:
@@ -1143,7 +1136,6 @@ class GuiMain(QMainWindow):
1143
1136
  self.openDocument(tHandle, sTitle=sTitle, changeFocus=setFocus)
1144
1137
  elif mode == nwDocMode.VIEW:
1145
1138
  self.viewDocument(tHandle=tHandle, sTitle=sTitle)
1146
- return
1147
1139
 
1148
1140
  @pyqtSlot(str, int, int, bool)
1149
1141
  def _openDocumentSelection(
@@ -1152,7 +1144,6 @@ class GuiMain(QMainWindow):
1152
1144
  """Open a document and select a section of the text."""
1153
1145
  if self.openDocument(tHandle, changeFocus=changeFocus):
1154
1146
  self.docEditor.setCursorSelection(selStart, selLength)
1155
- return
1156
1147
 
1157
1148
  @pyqtSlot()
1158
1149
  def _reloadViewer(self) -> None:
@@ -1161,11 +1152,13 @@ class GuiMain(QMainWindow):
1161
1152
  # If the two panels have the same document, save any changes in the editor
1162
1153
  self.saveDocument()
1163
1154
  self.docViewer.reloadText()
1164
- return
1165
1155
 
1166
1156
  @pyqtSlot(nwView)
1167
- def _changeView(self, view: nwView) -> None:
1157
+ def _changeView(self, view: nwView, exitFocus: bool = False) -> None:
1168
1158
  """Handle the requested change of view from the GuiViewBar."""
1159
+ if exitFocus:
1160
+ SHARED.setFocusMode(False)
1161
+
1169
1162
  if view == nwView.EDITOR:
1170
1163
  # Only change the main stack, but not the project stack
1171
1164
  self.mainStack.setCurrentWidget(self.splitMain)
@@ -1189,8 +1182,6 @@ class GuiMain(QMainWindow):
1189
1182
  isNovel = self.projStack.currentWidget() == self.novelView
1190
1183
  self.novelView.setActive(isMain and isNovel)
1191
1184
 
1192
- return
1193
-
1194
1185
  @pyqtSlot(nwDocAction)
1195
1186
  def _passDocumentAction(self, action: nwDocAction) -> None:
1196
1187
  """Pass on a document action to the editor or viewer based on
@@ -1200,7 +1191,6 @@ class GuiMain(QMainWindow):
1200
1191
  self.docEditor.docAction(action)
1201
1192
  elif self.docViewer.hasFocus():
1202
1193
  self.docViewer.docAction(action)
1203
- return
1204
1194
 
1205
1195
  @pyqtSlot(str)
1206
1196
  @pyqtSlot(nwDocInsert)
@@ -1210,14 +1200,12 @@ class GuiMain(QMainWindow):
1210
1200
  """
1211
1201
  if self.docEditor.hasFocus():
1212
1202
  self.docEditor.insertText(content)
1213
- return
1214
1203
 
1215
1204
  @pyqtSlot()
1216
1205
  def _toggleViewerPanelVisibility(self) -> None:
1217
1206
  """Toggle the visibility of the document viewer panel."""
1218
1207
  CONFIG.showViewerPanel = not CONFIG.showViewerPanel
1219
1208
  self.docViewerPanel.setVisible(CONFIG.showViewerPanel)
1220
- return
1221
1209
 
1222
1210
  @pyqtSlot()
1223
1211
  def _timeTick(self) -> None:
@@ -1233,7 +1221,6 @@ class GuiMain(QMainWindow):
1233
1221
  self._updateStatusWordCount()
1234
1222
  if CONFIG.memInfo: # pragma: no cover
1235
1223
  self.mainStatus.memInfo()
1236
- return
1237
1224
 
1238
1225
  @pyqtSlot()
1239
1226
  def _autoSaveProject(self) -> None:
@@ -1244,7 +1231,6 @@ class GuiMain(QMainWindow):
1244
1231
  if doSave:
1245
1232
  logger.debug("Auto-saving project")
1246
1233
  self.saveProject(autoSave=True)
1247
- return
1248
1234
 
1249
1235
  @pyqtSlot()
1250
1236
  def _autoSaveDocument(self) -> None:
@@ -1252,7 +1238,6 @@ class GuiMain(QMainWindow):
1252
1238
  if SHARED.hasProject and self.docEditor.docChanged:
1253
1239
  logger.debug("Auto-saving document")
1254
1240
  self.saveDocument()
1255
- return
1256
1241
 
1257
1242
  @pyqtSlot()
1258
1243
  def _updateStatusWordCount(self) -> None:
@@ -1282,8 +1267,6 @@ class GuiMain(QMainWindow):
1282
1267
 
1283
1268
  self.mainStatus.setProjectStats(cTotal, cTotal - iTotal)
1284
1269
 
1285
- return
1286
-
1287
1270
  @pyqtSlot()
1288
1271
  def _keyPressReturn(self) -> None:
1289
1272
  """Process a return or enter keypress in the main window."""
@@ -1291,7 +1274,6 @@ class GuiMain(QMainWindow):
1291
1274
  self.projSearch.processReturn()
1292
1275
  else:
1293
1276
  self.openSelectedItem()
1294
- return
1295
1277
 
1296
1278
  @pyqtSlot()
1297
1279
  def _keyPressEscape(self) -> None:
@@ -1300,7 +1282,6 @@ class GuiMain(QMainWindow):
1300
1282
  self.docEditor.closeSearch()
1301
1283
  elif SHARED.focusMode:
1302
1284
  SHARED.setFocusMode(False)
1303
- return
1304
1285
 
1305
1286
  @pyqtSlot(int)
1306
1287
  def _mainStackChanged(self, index: int) -> None:
@@ -1308,7 +1289,6 @@ class GuiMain(QMainWindow):
1308
1289
  if self.mainStack.widget(index) == self.outlineView:
1309
1290
  if SHARED.hasProject:
1310
1291
  self.outlineView.refreshTree()
1311
- return
1312
1292
 
1313
1293
  @pyqtSlot(int)
1314
1294
  def _projStackChanged(self, index: int) -> None:
@@ -1320,13 +1300,19 @@ class GuiMain(QMainWindow):
1320
1300
  elif widget == self.novelView:
1321
1301
  sHandle, _ = self.novelView.getSelectedHandle()
1322
1302
  self.itemDetails.updateViewBox(sHandle)
1323
- return
1324
1303
 
1325
1304
  ##
1326
1305
  # Internal Functions
1327
1306
  ##
1328
1307
 
1308
+ def _setWindowSize(self, size: list[int]) -> None:
1309
+ """Set the main window size."""
1310
+ if len(size) == 2 and (screen := SHARED.mainScreen):
1311
+ availSize = screen.availableSize()
1312
+ width = minmax(size[0], 900, availSize.width())
1313
+ height = minmax(size[1], 500, availSize.height())
1314
+ self.resize(width, height)
1315
+
1329
1316
  def _updateWindowTitle(self, projName: str | None = None) -> None:
1330
1317
  """Set the window title and add the project's name."""
1331
1318
  self.setWindowTitle(" - ".join(filter(None, [projName, CONFIG.appName])))
1332
- return