novelWriter 2.2b1__py3-none-any.whl → 2.2.1__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 (134) hide show
  1. {novelWriter-2.2b1.dist-info → novelWriter-2.2.1.dist-info}/METADATA +3 -3
  2. {novelWriter-2.2b1.dist-info → novelWriter-2.2.1.dist-info}/RECORD +128 -114
  3. {novelWriter-2.2b1.dist-info → novelWriter-2.2.1.dist-info}/WHEEL +1 -1
  4. novelwriter/__init__.py +10 -5
  5. novelwriter/assets/i18n/nw_de_DE.qm +0 -0
  6. novelwriter/assets/i18n/nw_en_US.qm +0 -0
  7. novelwriter/assets/i18n/nw_es_419.qm +0 -0
  8. novelwriter/assets/i18n/nw_fr_FR.qm +0 -0
  9. novelwriter/assets/i18n/nw_it_IT.qm +0 -0
  10. novelwriter/assets/i18n/nw_ja_JP.qm +0 -0
  11. novelwriter/assets/i18n/nw_nb_NO.qm +0 -0
  12. novelwriter/assets/i18n/nw_zh_CN.qm +0 -0
  13. novelwriter/assets/i18n/project_de_DE.json +1 -0
  14. novelwriter/assets/i18n/project_en_GB.json +1 -0
  15. novelwriter/assets/i18n/project_en_US.json +1 -0
  16. novelwriter/assets/i18n/project_es_419.json +11 -0
  17. novelwriter/assets/i18n/project_fr_FR.json +11 -0
  18. novelwriter/assets/i18n/project_it_IT.json +11 -0
  19. novelwriter/assets/i18n/project_ja_JP.json +2 -1
  20. novelwriter/assets/i18n/project_nb_NO.json +1 -0
  21. novelwriter/assets/i18n/project_zh_CN.json +11 -0
  22. novelwriter/assets/icons/novelwriter.ico +0 -0
  23. novelwriter/assets/icons/typicons_dark/icons.conf +11 -3
  24. novelwriter/assets/icons/typicons_dark/nw_deco-h2-narrow.svg +4 -0
  25. novelwriter/assets/icons/typicons_dark/nw_deco-h3-narrow.svg +4 -0
  26. novelwriter/assets/icons/typicons_dark/nw_deco-h4-narrow.svg +4 -0
  27. novelwriter/assets/icons/typicons_dark/nw_deco-note.svg +4 -0
  28. novelwriter/assets/icons/typicons_dark/nw_panel.svg +4 -0
  29. novelwriter/assets/icons/typicons_dark/nw_tb-bold-md.svg +4 -0
  30. novelwriter/assets/icons/typicons_dark/nw_tb-bold.svg +3 -1
  31. novelwriter/assets/icons/typicons_dark/nw_tb-italic-md.svg +4 -0
  32. novelwriter/assets/icons/typicons_dark/nw_tb-italic.svg +3 -1
  33. novelwriter/assets/icons/typicons_dark/nw_tb-strike-md.svg +4 -0
  34. novelwriter/assets/icons/typicons_dark/nw_tb-strike.svg +3 -1
  35. novelwriter/assets/icons/typicons_dark/nw_tb-subscript.svg +4 -2
  36. novelwriter/assets/icons/typicons_dark/nw_tb-superscript.svg +4 -2
  37. novelwriter/assets/icons/typicons_dark/nw_tb-underline.svg +4 -2
  38. novelwriter/assets/icons/typicons_dark/typ_eye.svg +4 -0
  39. novelwriter/assets/icons/typicons_light/icons.conf +11 -3
  40. novelwriter/assets/icons/typicons_light/nw_deco-h2-narrow.svg +4 -0
  41. novelwriter/assets/icons/typicons_light/nw_deco-h3-narrow.svg +4 -0
  42. novelwriter/assets/icons/typicons_light/nw_deco-h4-narrow.svg +4 -0
  43. novelwriter/assets/icons/typicons_light/nw_deco-note.svg +4 -0
  44. novelwriter/assets/icons/typicons_light/nw_panel.svg +4 -0
  45. novelwriter/assets/icons/typicons_light/nw_tb-bold-md.svg +4 -0
  46. novelwriter/assets/icons/typicons_light/nw_tb-bold.svg +3 -1
  47. novelwriter/assets/icons/typicons_light/nw_tb-italic-md.svg +4 -0
  48. novelwriter/assets/icons/typicons_light/nw_tb-italic.svg +3 -1
  49. novelwriter/assets/icons/typicons_light/nw_tb-strike-md.svg +4 -0
  50. novelwriter/assets/icons/typicons_light/nw_tb-strike.svg +3 -1
  51. novelwriter/assets/icons/typicons_light/nw_tb-subscript.svg +4 -2
  52. novelwriter/assets/icons/typicons_light/nw_tb-superscript.svg +4 -2
  53. novelwriter/assets/icons/typicons_light/nw_tb-underline.svg +4 -2
  54. novelwriter/assets/icons/typicons_light/typ_eye.svg +4 -0
  55. novelwriter/assets/icons/x-novelwriter-project.ico +0 -0
  56. novelwriter/assets/manual.pdf +0 -0
  57. novelwriter/assets/sample.zip +0 -0
  58. novelwriter/assets/text/release_notes.htm +50 -7
  59. novelwriter/common.py +35 -27
  60. novelwriter/config.py +13 -28
  61. novelwriter/constants.py +21 -4
  62. novelwriter/core/buildsettings.py +2 -2
  63. novelwriter/core/coretools.py +8 -2
  64. novelwriter/core/docbuild.py +1 -1
  65. novelwriter/core/document.py +1 -1
  66. novelwriter/core/index.py +102 -36
  67. novelwriter/core/item.py +2 -2
  68. novelwriter/core/options.py +6 -3
  69. novelwriter/core/project.py +5 -5
  70. novelwriter/core/projectdata.py +3 -3
  71. novelwriter/core/projectxml.py +1 -1
  72. novelwriter/core/sessions.py +2 -2
  73. novelwriter/core/spellcheck.py +4 -3
  74. novelwriter/core/status.py +3 -3
  75. novelwriter/core/storage.py +1 -1
  76. novelwriter/core/tohtml.py +11 -5
  77. novelwriter/core/tokenizer.py +28 -21
  78. novelwriter/core/tomd.py +6 -2
  79. novelwriter/core/toodt.py +12 -5
  80. novelwriter/core/tree.py +2 -2
  81. novelwriter/dialogs/about.py +30 -31
  82. novelwriter/dialogs/docmerge.py +24 -15
  83. novelwriter/dialogs/docsplit.py +27 -16
  84. novelwriter/dialogs/editlabel.py +19 -7
  85. novelwriter/dialogs/preferences.py +116 -131
  86. novelwriter/dialogs/projdetails.py +29 -36
  87. novelwriter/dialogs/projload.py +32 -36
  88. novelwriter/dialogs/projsettings.py +20 -15
  89. novelwriter/dialogs/quotes.py +32 -25
  90. novelwriter/dialogs/updates.py +17 -16
  91. novelwriter/dialogs/wordlist.py +34 -21
  92. novelwriter/enum.py +19 -8
  93. novelwriter/error.py +1 -1
  94. novelwriter/extensions/circularprogress.py +1 -1
  95. novelwriter/extensions/configlayout.py +3 -15
  96. novelwriter/extensions/{wheeleventfilter.py → eventfilters.py} +15 -5
  97. novelwriter/extensions/novelselector.py +1 -1
  98. novelwriter/extensions/pageddialog.py +1 -1
  99. novelwriter/extensions/pagedsidebar.py +2 -5
  100. novelwriter/extensions/simpleprogress.py +8 -9
  101. novelwriter/extensions/statusled.py +1 -1
  102. novelwriter/extensions/switch.py +4 -4
  103. novelwriter/extensions/switchbox.py +1 -6
  104. novelwriter/gui/doceditor.py +349 -236
  105. novelwriter/gui/dochighlight.py +10 -11
  106. novelwriter/gui/docviewer.py +158 -360
  107. novelwriter/gui/docviewerpanel.py +502 -0
  108. novelwriter/gui/editordocument.py +4 -4
  109. novelwriter/gui/itemdetails.py +2 -2
  110. novelwriter/gui/mainmenu.py +50 -36
  111. novelwriter/gui/noveltree.py +44 -53
  112. novelwriter/gui/outline.py +12 -7
  113. novelwriter/gui/projtree.py +465 -381
  114. novelwriter/gui/sidebar.py +9 -7
  115. novelwriter/gui/statusbar.py +48 -5
  116. novelwriter/gui/theme.py +26 -8
  117. novelwriter/guimain.py +212 -208
  118. novelwriter/shared.py +76 -30
  119. novelwriter/tools/dictionaries.py +268 -0
  120. novelwriter/tools/lipsum.py +34 -28
  121. novelwriter/tools/manusbuild.py +20 -10
  122. novelwriter/tools/manuscript.py +20 -27
  123. novelwriter/tools/manussettings.py +2 -4
  124. novelwriter/tools/projwizard.py +3 -3
  125. novelwriter/tools/writingstats.py +18 -5
  126. novelwriter/assets/icons/typicons_dark/nw_tb-markdown.svg +0 -8
  127. novelwriter/assets/icons/typicons_dark/nw_tb-shortcode.svg +0 -8
  128. novelwriter/assets/icons/typicons_dark/typ_at.svg +0 -4
  129. novelwriter/assets/icons/typicons_light/nw_tb-markdown.svg +0 -8
  130. novelwriter/assets/icons/typicons_light/nw_tb-shortcode.svg +0 -8
  131. novelwriter/assets/icons/typicons_light/typ_at.svg +0 -4
  132. {novelWriter-2.2b1.dist-info → novelWriter-2.2.1.dist-info}/LICENSE.md +0 -0
  133. {novelWriter-2.2b1.dist-info → novelWriter-2.2.1.dist-info}/entry_points.txt +0 -0
  134. {novelWriter-2.2b1.dist-info → novelWriter-2.2.1.dist-info}/top_level.txt +0 -0
novelwriter/guimain.py CHANGED
@@ -6,7 +6,7 @@ File History:
6
6
  Created: 2018-09-22 [0.0.1] GuiMain
7
7
 
8
8
  This file is a part of novelWriter
9
- Copyright 2018–2023, Veronica Berglyd Olsen
9
+ Copyright 2018–2024, Veronica Berglyd Olsen
10
10
 
11
11
  This program is free software: you can redistribute it and/or modify
12
12
  it under the terms of the GNU General Public License as published by
@@ -44,10 +44,11 @@ from novelwriter.gui.outline import GuiOutlineView
44
44
  from novelwriter.gui.mainmenu import GuiMainMenu
45
45
  from novelwriter.gui.projtree import GuiProjectView
46
46
  from novelwriter.gui.doceditor import GuiDocEditor
47
- from novelwriter.gui.docviewer import GuiDocViewDetails, GuiDocViewer
47
+ from novelwriter.gui.docviewer import GuiDocViewer
48
48
  from novelwriter.gui.noveltree import GuiNovelView
49
49
  from novelwriter.gui.statusbar import GuiMainStatus
50
50
  from novelwriter.gui.itemdetails import GuiItemDetails
51
+ from novelwriter.gui.docviewerpanel import GuiDocViewerPanel
51
52
  from novelwriter.dialogs.about import GuiAbout
52
53
  from novelwriter.dialogs.updates import GuiUpdates
53
54
  from novelwriter.dialogs.projload import GuiProjectLoad
@@ -55,16 +56,16 @@ from novelwriter.dialogs.wordlist import GuiWordList
55
56
  from novelwriter.dialogs.preferences import GuiPreferences
56
57
  from novelwriter.dialogs.projdetails import GuiProjectDetails
57
58
  from novelwriter.dialogs.projsettings import GuiProjectSettings
58
- from novelwriter.tools.lipsum import GuiLipsum
59
59
  from novelwriter.tools.manuscript import GuiManuscript
60
60
  from novelwriter.tools.projwizard import GuiProjectWizard
61
+ from novelwriter.tools.dictionaries import GuiDictionaries
61
62
  from novelwriter.tools.writingstats import GuiWritingStats
62
63
  from novelwriter.core.coretools import ProjectBuilder
63
64
 
64
65
  from novelwriter.enum import (
65
66
  nwDocAction, nwDocInsert, nwDocMode, nwItemType, nwItemClass, nwWidget, nwView
66
67
  )
67
- from novelwriter.common import getGuiItem, hexToInt
68
+ from novelwriter.common import hexToInt
68
69
  from novelwriter.constants import nwFiles
69
70
 
70
71
  logger = logging.getLogger(__name__)
@@ -133,16 +134,16 @@ class GuiMain(QMainWindow):
133
134
  hWd = CONFIG.pxInt(4)
134
135
 
135
136
  # Main GUI Elements
136
- self.mainStatus = GuiMainStatus(self)
137
- self.projView = GuiProjectView(self)
138
- self.novelView = GuiNovelView(self)
139
- self.docEditor = GuiDocEditor(self)
140
- self.viewMeta = GuiDocViewDetails(self)
141
- self.docViewer = GuiDocViewer(self)
142
- self.itemDetails = GuiItemDetails(self)
143
- self.outlineView = GuiOutlineView(self)
144
- self.mainMenu = GuiMainMenu(self)
145
- self.sideBar = GuiSideBar(self)
137
+ self.mainStatus = GuiMainStatus(self)
138
+ self.projView = GuiProjectView(self)
139
+ self.novelView = GuiNovelView(self)
140
+ self.docEditor = GuiDocEditor(self)
141
+ self.docViewer = GuiDocViewer(self)
142
+ self.docViewerPanel = GuiDocViewerPanel(self)
143
+ self.itemDetails = GuiItemDetails(self)
144
+ self.outlineView = GuiOutlineView(self)
145
+ self.mainMenu = GuiMainMenu(self)
146
+ self.sideBar = GuiSideBar(self)
146
147
 
147
148
  # Project Tree Stack
148
149
  self.projStack = QStackedWidget(self)
@@ -162,7 +163,7 @@ class GuiMain(QMainWindow):
162
163
  # Splitter : Document Viewer / Document Meta
163
164
  self.splitView = QSplitter(Qt.Vertical, self)
164
165
  self.splitView.addWidget(self.docViewer)
165
- self.splitView.addWidget(self.viewMeta)
166
+ self.splitView.addWidget(self.docViewerPanel)
166
167
  self.splitView.setHandleWidth(hWd)
167
168
  self.splitView.setOpaqueResize(False)
168
169
  self.splitView.setSizes(CONFIG.viewPanePos)
@@ -190,12 +191,12 @@ class GuiMain(QMainWindow):
190
191
  self.mainStack.currentChanged.connect(self._mainStackChanged)
191
192
 
192
193
  # Indices of Splitter Widgets
193
- self.idxTree = self.splitMain.indexOf(self.treePane)
194
- self.idxMain = self.splitMain.indexOf(self.splitDocs)
195
- self.idxEditor = self.splitDocs.indexOf(self.docEditor)
196
- self.idxViewer = self.splitDocs.indexOf(self.splitView)
197
- self.idxViewDoc = self.splitView.indexOf(self.docViewer)
198
- self.idxViewMeta = self.splitView.indexOf(self.viewMeta)
194
+ self.idxTree = self.splitMain.indexOf(self.treePane)
195
+ self.idxMain = self.splitMain.indexOf(self.splitDocs)
196
+ self.idxEditor = self.splitDocs.indexOf(self.docEditor)
197
+ self.idxViewer = self.splitDocs.indexOf(self.splitView)
198
+ self.idxViewDoc = self.splitView.indexOf(self.docViewer)
199
+ self.idxViewDocPanel = self.splitView.indexOf(self.docViewerPanel)
199
200
 
200
201
  # Indices of Stack Widgets
201
202
  self.idxEditorView = self.mainStack.indexOf(self.splitMain)
@@ -209,7 +210,7 @@ class GuiMain(QMainWindow):
209
210
  self.splitDocs.setCollapsible(self.idxEditor, False)
210
211
  self.splitDocs.setCollapsible(self.idxViewer, False)
211
212
  self.splitView.setCollapsible(self.idxViewDoc, False)
212
- self.splitView.setCollapsible(self.idxViewMeta, False)
213
+ self.splitView.setCollapsible(self.idxViewDocPanel, False)
213
214
 
214
215
  self.splitMain.setStretchFactor(self.idxTree, 0)
215
216
  self.splitMain.setStretchFactor(self.idxMain, 1)
@@ -242,6 +243,12 @@ class GuiMain(QMainWindow):
242
243
  SHARED.projectStatusChanged.connect(self.mainStatus.updateProjectStatus)
243
244
  SHARED.projectStatusMessage.connect(self.mainStatus.setStatusMessage)
244
245
  SHARED.spellLanguageChanged.connect(self.mainStatus.setLanguage)
246
+ SHARED.indexChangedTags.connect(self.docViewerPanel.updateChangedTags)
247
+ SHARED.indexScannedText.connect(self.docViewerPanel.projectItemChanged)
248
+ SHARED.indexScannedText.connect(self.projView.updateItemValues)
249
+ SHARED.indexScannedText.connect(self.itemDetails.updateViewBox)
250
+ SHARED.indexCleared.connect(self.docViewerPanel.indexWasCleared)
251
+ SHARED.indexAvailable.connect(self.docViewerPanel.indexHasAppeared)
245
252
 
246
253
  self.mainMenu.requestDocAction.connect(self._passDocumentAction)
247
254
  self.mainMenu.requestDocInsert.connect(self._passDocumentInsert)
@@ -257,6 +264,7 @@ class GuiMain(QMainWindow):
257
264
  self.projView.treeItemChanged.connect(self.docEditor.updateDocInfo)
258
265
  self.projView.treeItemChanged.connect(self.docViewer.updateDocInfo)
259
266
  self.projView.treeItemChanged.connect(self.itemDetails.updateViewBox)
267
+ self.projView.treeItemChanged.connect(self.docViewerPanel.projectItemChanged)
260
268
  self.projView.rootFolderChanged.connect(self.outlineView.updateRootItem)
261
269
  self.projView.rootFolderChanged.connect(self.novelView.updateRootItem)
262
270
  self.projView.rootFolderChanged.connect(self.projView.updateRootItem)
@@ -275,8 +283,16 @@ class GuiMain(QMainWindow):
275
283
  self.docEditor.spellCheckStateChanged.connect(self.mainMenu.setSpellCheckState)
276
284
  self.docEditor.closeDocumentRequest.connect(self.closeDocEditor)
277
285
  self.docEditor.toggleFocusModeRequest.connect(self.toggleFocusMode)
286
+ self.docEditor.requestProjectItemSelected.connect(self.projView.setSelectedHandle)
287
+ self.docEditor.requestProjectItemRenamed.connect(self.projView.renameTreeItem)
278
288
 
289
+ self.docViewer.documentLoaded.connect(self.docViewerPanel.updateHandle)
279
290
  self.docViewer.loadDocumentTagRequest.connect(self._followTag)
291
+ self.docViewer.togglePanelVisibility.connect(self._toggleViewerPanelVisibility)
292
+ self.docViewer.requestProjectItemSelected.connect(self.projView.setSelectedHandle)
293
+
294
+ self.docViewerPanel.loadDocumentTagRequest.connect(self._followTag)
295
+ self.docViewerPanel.openDocumentRequest.connect(self._openDocument)
280
296
 
281
297
  self.outlineView.loadDocumentTagRequest.connect(self._followTag)
282
298
  self.outlineView.openDocumentRequest.connect(self._openDocument)
@@ -321,19 +337,9 @@ class GuiMain(QMainWindow):
321
337
  self.asProjTimer.start()
322
338
  self.asDocTimer.start()
323
339
  self.mainStatus.clearStatus()
324
-
325
- # Handle Windows Mode
326
340
  self.showNormal()
327
341
 
328
342
  logger.debug("Ready: GUI")
329
-
330
- if __hexversion__[-2] == "a" and logger.getEffectiveLevel() > logging.DEBUG:
331
- SHARED.warn(self.tr(
332
- "You are running an untested development version of novelWriter. "
333
- "Please be careful when working on a live project "
334
- "and make sure you take regular backups."
335
- ))
336
-
337
343
  logger.info("novelWriter is ready ...")
338
344
  self.mainStatus.setStatusMessage(self.tr("novelWriter is ready ..."))
339
345
 
@@ -437,6 +443,7 @@ class GuiMain(QMainWindow):
437
443
  self.docViewer.clearNavHistory()
438
444
  self.closeDocViewer(byUser=False)
439
445
 
446
+ self.docViewerPanel.closeProjectTasks()
440
447
  self.outlineView.closeProjectTasks()
441
448
  self.novelView.closeProjectTasks()
442
449
  self.projView.clearProjectView()
@@ -510,6 +517,7 @@ class GuiMain(QMainWindow):
510
517
  self.projView.openProjectTasks()
511
518
  self.novelView.openProjectTasks()
512
519
  self.outlineView.openProjectTasks()
520
+ self.docViewerPanel.openProjectTasks()
513
521
  self._updateStatusWordCount()
514
522
 
515
523
  # Restore previously open documents, if any
@@ -598,7 +606,7 @@ class GuiMain(QMainWindow):
598
606
  if self.docEditor.loadText(tHandle, tLine):
599
607
  SHARED.project.data.setLastHandle(tHandle, "editor")
600
608
  self.projView.setSelectedHandle(tHandle, doScroll=doScroll)
601
- self.novelView.setActiveHandle(tHandle)
609
+ self.novelView.setActiveHandle(tHandle, doScroll=doScroll)
602
610
  if changeFocus:
603
611
  self.docEditor.setFocus()
604
612
  else:
@@ -675,13 +683,19 @@ class GuiMain(QMainWindow):
675
683
  logger.debug("Viewing document with handle '%s'", tHandle)
676
684
  if self.docViewer.loadText(tHandle):
677
685
  if not self.splitView.isVisible():
686
+ cursorVisible = self.docEditor.cursorIsVisible()
678
687
  bPos = self.splitMain.sizes()
679
688
  self.splitView.setVisible(True)
680
689
  vPos = [0, 0]
681
690
  vPos[0] = int(bPos[1]/2)
682
691
  vPos[1] = bPos[1] - vPos[0]
683
692
  self.splitDocs.setSizes(vPos)
684
- self.viewMeta.setVisible(CONFIG.showRefPanel)
693
+ self.docViewerPanel.setVisible(CONFIG.showViewerPanel)
694
+
695
+ # Since editor width changes, we need to make sure we
696
+ # restore cursor visibility in the editor. See #1302
697
+ if cursorVisible:
698
+ self.docEditor.ensureCursorVisibleNoCentre()
685
699
 
686
700
  if sTitle:
687
701
  self.docViewer.navigateTo(f"#{sTitle}")
@@ -782,11 +796,9 @@ class GuiMain(QMainWindow):
782
796
  if not SHARED.hasProject:
783
797
  logger.error("No project open")
784
798
  return False
785
-
786
799
  if tHandle is None and (self.docEditor.anyFocus() or self.isFocusMode):
787
800
  tHandle = self.docEditor.docHandle
788
801
  self.projView.renameTreeItem(tHandle)
789
-
790
802
  return True
791
803
 
792
804
  def rebuildTrees(self) -> None:
@@ -853,198 +865,113 @@ class GuiMain(QMainWindow):
853
865
 
854
866
  return None
855
867
 
868
+ @pyqtSlot()
856
869
  def showPreferencesDialog(self) -> None:
857
870
  """Open the preferences dialog."""
858
- dlgConf = GuiPreferences(self)
859
- dlgConf.exec_()
860
-
861
- if dlgConf.result() == QDialog.Accepted:
862
- logger.debug("Applying new preferences")
863
- self.initMain()
864
- self.saveDocument()
865
-
866
- if dlgConf.needsRestart:
867
- SHARED.info(self.tr(
868
- "Some changes will not be applied until novelWriter has been restarted."
869
- ))
870
-
871
- if dlgConf.refreshTree:
872
- self.projView.populateTree()
873
-
874
- if dlgConf.updateTheme:
875
- # We are doing this manually instead of connecting to
876
- # qApp.paletteChanged since the processing order matters
877
- SHARED.theme.loadTheme()
878
- self.docEditor.updateTheme()
879
- self.docViewer.updateTheme()
880
- self.sideBar.updateTheme()
881
- self.projView.updateTheme()
882
- self.novelView.updateTheme()
883
- self.outlineView.updateTheme()
884
- self.itemDetails.updateTheme()
885
- self.mainStatus.updateTheme()
886
-
887
- if dlgConf.updateSyntax:
888
- SHARED.theme.loadSyntax()
889
- self.docEditor.updateSyntaxColours()
890
-
891
- self.docEditor.initEditor()
892
- self.docViewer.initViewer()
893
- self.projView.initSettings()
894
- self.novelView.initSettings()
895
- self.outlineView.initSettings()
896
-
897
- self._updateStatusWordCount()
898
-
871
+ dialog = GuiPreferences(self)
872
+ dialog.newPreferencesReady.connect(self._processConfigChanges)
873
+ dialog.exec_()
899
874
  return
900
875
 
876
+ @pyqtSlot()
901
877
  @pyqtSlot(int)
902
- def showProjectSettingsDialog(self, focusTab: int = GuiProjectSettings.TAB_MAIN) -> bool:
878
+ def showProjectSettingsDialog(self, focusTab: int = GuiProjectSettings.TAB_MAIN) -> None:
903
879
  """Open the project settings dialog."""
904
- if not SHARED.hasProject:
905
- logger.error("No project open")
906
- return False
907
-
908
- dlgProj = GuiProjectSettings(self, focusTab=focusTab)
909
- dlgProj.exec_()
910
-
911
- if dlgProj.result() == QDialog.Accepted:
912
- logger.debug("Applying new project settings")
913
- SHARED.updateSpellCheckLanguage()
914
- self.itemDetails.refreshDetails()
915
- self._updateWindowTitle(SHARED.project.data.name)
916
-
917
- return True
880
+ if SHARED.hasProject:
881
+ dialog = GuiProjectSettings(self, focusTab=focusTab)
882
+ dialog.newProjectSettingsReady.connect(self._processProjectSettingsChanges)
883
+ dialog.exec_()
884
+ return
918
885
 
919
- def showProjectDetailsDialog(self) -> bool:
886
+ @pyqtSlot()
887
+ def showProjectDetailsDialog(self) -> None:
920
888
  """Open the project details dialog."""
921
- if not SHARED.hasProject:
922
- logger.error("No project open")
923
- return False
924
-
925
- dlgDetails = getGuiItem("GuiProjectDetails")
926
- if dlgDetails is None:
927
- dlgDetails = GuiProjectDetails(self)
928
- assert isinstance(dlgDetails, GuiProjectDetails)
929
-
930
- dlgDetails.setModal(True)
931
- dlgDetails.show()
932
- dlgDetails.raise_()
933
- dlgDetails.updateValues()
934
-
935
- return True
889
+ if SHARED.hasProject:
890
+ dialog = GuiProjectDetails(self)
891
+ dialog.setModal(True)
892
+ dialog.show()
893
+ dialog.raise_()
894
+ qApp.processEvents()
895
+ dialog.updateValues()
896
+ return
936
897
 
937
898
  @pyqtSlot()
938
- def showBuildManuscriptDialog(self) -> bool:
899
+ def showBuildManuscriptDialog(self) -> None:
939
900
  """Open the build manuscript dialog."""
940
- if not SHARED.hasProject:
941
- logger.error("No project open")
942
- return False
943
-
944
- dlgBuild = getGuiItem("GuiManuscript")
945
- if dlgBuild is None:
946
- dlgBuild = GuiManuscript(self)
947
- assert isinstance(dlgBuild, GuiManuscript)
948
-
949
- dlgBuild.setModal(False)
950
- dlgBuild.show()
951
- dlgBuild.raise_()
952
- qApp.processEvents()
953
-
954
- dlgBuild.loadContent()
955
-
956
- return True
957
-
958
- def showLoremIpsumDialog(self) -> bool:
959
- """Open the insert lorem ipsum text dialog."""
960
- if not SHARED.hasProject:
961
- logger.error("No project open")
962
- return False
963
-
964
- dlgLipsum = getGuiItem("GuiLipsum")
965
- if dlgLipsum is None:
966
- dlgLipsum = GuiLipsum(self)
967
- assert isinstance(dlgLipsum, GuiLipsum)
968
-
969
- dlgLipsum.setModal(False)
970
- dlgLipsum.show()
971
- dlgLipsum.raise_()
972
- qApp.processEvents()
973
-
974
- return True
901
+ if SHARED.hasProject:
902
+ if (dialog := SHARED.findTopLevelWidget(GuiManuscript)) is None:
903
+ dialog = GuiManuscript(self)
904
+ dialog.setModal(False)
905
+ dialog.show()
906
+ dialog.raise_()
907
+ qApp.processEvents()
908
+ dialog.loadContent()
909
+ return
975
910
 
976
- def showProjectWordListDialog(self) -> bool:
911
+ @pyqtSlot()
912
+ def showProjectWordListDialog(self) -> None:
977
913
  """Open the project word list dialog."""
978
- if not SHARED.hasProject:
979
- logger.error("No project open")
980
- return False
981
-
982
- dlgWords = GuiWordList(self)
983
- dlgWords.exec_()
984
-
985
- if dlgWords.result() == QDialog.Accepted:
986
- logger.debug("Reloading word list")
987
- SHARED.updateSpellCheckLanguage(reload=True)
988
- self.docEditor.spellCheckDocument()
989
-
990
- return True
914
+ if SHARED.hasProject:
915
+ dialog = GuiWordList(self)
916
+ dialog.newWordListReady.connect(self._processWordListChanges)
917
+ dialog.exec_()
918
+ return
991
919
 
992
- def showWritingStatsDialog(self) -> bool:
920
+ @pyqtSlot()
921
+ def showWritingStatsDialog(self) -> None:
993
922
  """Open the session stats dialog."""
994
- if not SHARED.hasProject:
995
- logger.error("No project open")
996
- return False
997
-
998
- dlgStats = getGuiItem("GuiWritingStats")
999
- if dlgStats is None:
1000
- dlgStats = GuiWritingStats(self)
1001
- assert isinstance(dlgStats, GuiWritingStats)
1002
-
1003
- dlgStats.setModal(False)
1004
- dlgStats.show()
1005
- dlgStats.raise_()
1006
- qApp.processEvents()
1007
- dlgStats.populateGUI()
1008
-
1009
- return True
1010
-
1011
- def showAboutNWDialog(self, showNotes: bool = False) -> bool:
1012
- """Show the about dialog for novelWriter."""
1013
- dlgAbout = getGuiItem("GuiAbout")
1014
- if dlgAbout is None:
1015
- dlgAbout = GuiAbout(self)
1016
- assert isinstance(dlgAbout, GuiAbout)
923
+ if SHARED.hasProject:
924
+ if (dialog := SHARED.findTopLevelWidget(GuiWritingStats)) is None:
925
+ dialog = GuiWritingStats(self)
926
+ dialog.setModal(False)
927
+ dialog.show()
928
+ dialog.raise_()
929
+ qApp.processEvents()
930
+ dialog.populateGUI()
931
+ return
1017
932
 
1018
- dlgAbout.setModal(True)
1019
- dlgAbout.show()
1020
- dlgAbout.raise_()
933
+ @pyqtSlot()
934
+ def showAboutNWDialog(self, showNotes: bool = False) -> None:
935
+ """Show the novelWriter about dialog."""
936
+ dialog = GuiAbout(self)
937
+ dialog.setModal(True)
938
+ dialog.show()
939
+ dialog.raise_()
1021
940
  qApp.processEvents()
1022
- dlgAbout.populateGUI()
1023
-
941
+ dialog.populateGUI()
1024
942
  if showNotes:
1025
- dlgAbout.showReleaseNotes()
1026
-
1027
- return True
943
+ dialog.showReleaseNotes()
944
+ return
1028
945
 
946
+ @pyqtSlot()
1029
947
  def showAboutQtDialog(self) -> None:
1030
- """Show the about dialog for Qt."""
948
+ """Show the Qt about dialog."""
1031
949
  msgBox = QMessageBox(self)
1032
950
  msgBox.aboutQt(self, "About Qt")
1033
951
  return
1034
952
 
953
+ @pyqtSlot()
1035
954
  def showUpdatesDialog(self) -> None:
1036
955
  """Show the check for updates dialog."""
1037
- dlgUpdate = getGuiItem("GuiUpdates")
1038
- if dlgUpdate is None:
1039
- dlgUpdate = GuiUpdates(self)
1040
- assert isinstance(dlgUpdate, GuiUpdates)
1041
-
1042
- dlgUpdate.setModal(True)
1043
- dlgUpdate.show()
1044
- dlgUpdate.raise_()
956
+ dialog = GuiUpdates(self)
957
+ dialog.setModal(True)
958
+ dialog.show()
959
+ dialog.raise_()
1045
960
  qApp.processEvents()
1046
- dlgUpdate.checkLatest()
961
+ dialog.checkLatest()
962
+ return
1047
963
 
964
+ @pyqtSlot()
965
+ def showDictionariesDialog(self) -> None:
966
+ """Show the download dictionaries dialog."""
967
+ dialog = GuiDictionaries(self)
968
+ dialog.setModal(True)
969
+ dialog.show()
970
+ dialog.raise_()
971
+ qApp.processEvents()
972
+ if not dialog.initDialog():
973
+ dialog.close()
974
+ SHARED.error(self.tr("Could not initialise the dialog."))
1048
975
  return
1049
976
 
1050
977
  def reportConfErr(self) -> bool:
@@ -1076,10 +1003,10 @@ class GuiMain(QMainWindow):
1076
1003
  if not self.isFocusMode:
1077
1004
  CONFIG.setMainPanePos(self.splitMain.sizes())
1078
1005
  CONFIG.setOutlinePanePos(self.outlineView.splitSizes())
1079
- if self.viewMeta.isVisible():
1006
+ if self.docViewerPanel.isVisible():
1080
1007
  CONFIG.setViewPanePos(self.splitView.sizes())
1081
1008
 
1082
- CONFIG.showRefPanel = self.viewMeta.isVisible()
1009
+ CONFIG.showViewerPanel = self.docViewerPanel.isVisible()
1083
1010
  if self.windowState() & Qt.WindowFullScreen != Qt.WindowFullScreen:
1084
1011
  # Ignore window size if in full screen mode
1085
1012
  CONFIG.setMainWinSize(self.width(), self.height())
@@ -1101,11 +1028,18 @@ class GuiMain(QMainWindow):
1101
1028
  # Only reset the last handle if the user called this
1102
1029
  SHARED.project.data.setLastHandle(None, "viewer")
1103
1030
 
1031
+ cursorVisible = self.docEditor.cursorIsVisible()
1032
+
1104
1033
  # Hide the panel
1105
1034
  bPos = self.splitMain.sizes()
1106
1035
  self.splitView.setVisible(False)
1107
1036
  self.splitDocs.setSizes([bPos[1], 0])
1108
1037
 
1038
+ # Since editor width changes, we need to make sure we restore
1039
+ # cursor visibility in the editor. See #1302
1040
+ if cursorVisible:
1041
+ self.docEditor.ensureCursorVisibleNoCentre()
1042
+
1109
1043
  return not self.splitView.isVisible()
1110
1044
 
1111
1045
  def toggleFullScreenMode(self) -> None:
@@ -1121,10 +1055,7 @@ class GuiMain(QMainWindow):
1121
1055
  """Capture the closing event of the GUI and call the close
1122
1056
  function to handle all the close process steps.
1123
1057
  """
1124
- if self.closeMain():
1125
- event.accept()
1126
- else:
1127
- event.ignore()
1058
+ event.accept() if self.closeMain() else event.ignore()
1128
1059
  return
1129
1060
 
1130
1061
  ##
@@ -1154,6 +1085,7 @@ class GuiMain(QMainWindow):
1154
1085
  else:
1155
1086
  logger.debug("Deactivating Focus Mode")
1156
1087
 
1088
+ cursorVisible = self.docEditor.cursorIsVisible()
1157
1089
  isVisible = not self.isFocusMode
1158
1090
  self.treePane.setVisible(isVisible)
1159
1091
  self.mainStatus.setVisible(isVisible)
@@ -1169,6 +1101,9 @@ class GuiMain(QMainWindow):
1169
1101
  elif self.docViewer.docHandle is not None:
1170
1102
  self.splitView.setVisible(True)
1171
1103
 
1104
+ if cursorVisible:
1105
+ self.docEditor.ensureCursorVisibleNoCentre()
1106
+
1172
1107
  return
1173
1108
 
1174
1109
  @pyqtSlot(nwWidget)
@@ -1202,6 +1137,65 @@ class GuiMain(QMainWindow):
1202
1137
  # Private Slots
1203
1138
  ##
1204
1139
 
1140
+ @pyqtSlot(bool, bool, bool, bool)
1141
+ def _processConfigChanges(self, restart: bool, tree: bool, theme: bool, syntax: bool) -> None:
1142
+ """Refresh GUI based on flags from the Preferences dialog."""
1143
+ logger.debug("Applying new preferences")
1144
+ self.initMain()
1145
+ self.saveDocument()
1146
+
1147
+ if restart:
1148
+ SHARED.info(self.tr(
1149
+ "Some changes will not be applied until novelWriter has been restarted."
1150
+ ))
1151
+
1152
+ if tree:
1153
+ self.projView.populateTree()
1154
+
1155
+ if theme:
1156
+ # We are doing this manually instead of connecting to
1157
+ # qApp.paletteChanged since the processing order matters
1158
+ SHARED.theme.loadTheme()
1159
+ self.docEditor.updateTheme()
1160
+ self.docViewer.updateTheme()
1161
+ self.docViewerPanel.updateTheme()
1162
+ self.sideBar.updateTheme()
1163
+ self.projView.updateTheme()
1164
+ self.novelView.updateTheme()
1165
+ self.outlineView.updateTheme()
1166
+ self.itemDetails.updateTheme()
1167
+ self.mainStatus.updateTheme()
1168
+
1169
+ if syntax:
1170
+ SHARED.theme.loadSyntax()
1171
+ self.docEditor.updateSyntaxColours()
1172
+
1173
+ self.docEditor.initEditor()
1174
+ self.docViewer.initViewer()
1175
+ self.projView.initSettings()
1176
+ self.novelView.initSettings()
1177
+ self.outlineView.initSettings()
1178
+ self._updateStatusWordCount()
1179
+
1180
+ return
1181
+
1182
+ @pyqtSlot()
1183
+ def _processProjectSettingsChanges(self) -> None:
1184
+ """Refresh data dependent on project settings."""
1185
+ logger.debug("Applying new project settings")
1186
+ SHARED.updateSpellCheckLanguage()
1187
+ self.itemDetails.refreshDetails()
1188
+ self._updateWindowTitle(SHARED.project.data.name)
1189
+ return
1190
+
1191
+ @pyqtSlot()
1192
+ def _processWordListChanges(self) -> None:
1193
+ """Reload project word list."""
1194
+ logger.debug("Reloading word list")
1195
+ SHARED.updateSpellCheckLanguage(reload=True)
1196
+ self.docEditor.spellCheckDocument()
1197
+ return
1198
+
1205
1199
  @pyqtSlot(str, nwDocMode)
1206
1200
  def _followTag(self, tag: str, mode: nwDocMode) -> None:
1207
1201
  """Follow a tag after user interaction with a link."""
@@ -1267,6 +1261,13 @@ class GuiMain(QMainWindow):
1267
1261
  self.docEditor.insertText(content)
1268
1262
  return
1269
1263
 
1264
+ @pyqtSlot()
1265
+ def _toggleViewerPanelVisibility(self):
1266
+ """Toggle the visibility of the document viewer panel."""
1267
+ CONFIG.showViewerPanel = not CONFIG.showViewerPanel
1268
+ self.docViewerPanel.setVisible(CONFIG.showViewerPanel)
1269
+ return
1270
+
1270
1271
  @pyqtSlot()
1271
1272
  def _timeTick(self) -> None:
1272
1273
  """Process time tick of the main timer."""
@@ -1278,6 +1279,8 @@ class GuiMain(QMainWindow):
1278
1279
  self.mainStatus.setUserIdle(editIdle or userIdle)
1279
1280
  SHARED.updateIdleTime(currTime, editIdle or userIdle)
1280
1281
  self.mainStatus.updateTime(idleTime=SHARED.projectIdleTime)
1282
+ if CONFIG.memInfo and int(currTime) % 5 == 0: # pragma: no cover
1283
+ self.mainStatus.memInfo()
1281
1284
  return
1282
1285
 
1283
1286
  @pyqtSlot()
@@ -1408,6 +1411,7 @@ class GuiMain(QMainWindow):
1408
1411
  self.addAction(self.mainMenu.aInsTimes)
1409
1412
  self.addAction(self.mainMenu.aInsDivide)
1410
1413
  self.addAction(self.mainMenu.aInsSynopsis)
1414
+ self.addAction(self.mainMenu.aInsShort)
1411
1415
 
1412
1416
  for mAction, _ in self.mainMenu.mInsKWItems.values():
1413
1417
  self.addAction(mAction)
@@ -1420,8 +1424,8 @@ class GuiMain(QMainWindow):
1420
1424
  self.addAction(self.mainMenu.aReplaceNext)
1421
1425
 
1422
1426
  # Format
1423
- self.addAction(self.mainMenu.aFmtEmph)
1424
- self.addAction(self.mainMenu.aFmtStrong)
1427
+ self.addAction(self.mainMenu.aFmtItalic)
1428
+ self.addAction(self.mainMenu.aFmtBold)
1425
1429
  self.addAction(self.mainMenu.aFmtStrike)
1426
1430
  self.addAction(self.mainMenu.aFmtDQuote)
1427
1431
  self.addAction(self.mainMenu.aFmtSQuote)