novelWriter 2.5b1__py3-none-any.whl → 2.5.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 (78) hide show
  1. {novelWriter-2.5b1.dist-info → novelWriter-2.5.1.dist-info}/METADATA +1 -1
  2. {novelWriter-2.5b1.dist-info → novelWriter-2.5.1.dist-info}/RECORD +77 -75
  3. {novelWriter-2.5b1.dist-info → novelWriter-2.5.1.dist-info}/WHEEL +1 -1
  4. novelwriter/__init__.py +3 -3
  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_nl_NL.qm +0 -0
  13. novelwriter/assets/i18n/nw_pl_PL.qm +0 -0
  14. novelwriter/assets/i18n/nw_pt_BR.qm +0 -0
  15. novelwriter/assets/i18n/nw_zh_CN.qm +0 -0
  16. novelwriter/assets/i18n/project_pl_PL.json +116 -0
  17. novelwriter/assets/i18n/project_pt_BR.json +74 -74
  18. novelwriter/assets/manual.pdf +0 -0
  19. novelwriter/assets/sample.zip +0 -0
  20. novelwriter/assets/text/credits_en.htm +52 -44
  21. novelwriter/assets/themes/cyberpunk_night.conf +1 -0
  22. novelwriter/assets/themes/default_dark.conf +1 -0
  23. novelwriter/assets/themes/default_light.conf +1 -0
  24. novelwriter/assets/themes/dracula.conf +1 -0
  25. novelwriter/assets/themes/solarized_dark.conf +1 -0
  26. novelwriter/assets/themes/solarized_light.conf +1 -0
  27. novelwriter/common.py +12 -3
  28. novelwriter/config.py +67 -15
  29. novelwriter/constants.py +8 -10
  30. novelwriter/core/buildsettings.py +5 -3
  31. novelwriter/core/coretools.py +3 -1
  32. novelwriter/core/docbuild.py +1 -0
  33. novelwriter/core/project.py +15 -4
  34. novelwriter/core/status.py +4 -1
  35. novelwriter/core/storage.py +6 -1
  36. novelwriter/core/tohtml.py +69 -29
  37. novelwriter/core/tokenizer.py +83 -14
  38. novelwriter/core/toodt.py +48 -21
  39. novelwriter/core/toqdoc.py +37 -21
  40. novelwriter/dialogs/about.py +10 -15
  41. novelwriter/dialogs/docmerge.py +16 -16
  42. novelwriter/dialogs/docsplit.py +16 -16
  43. novelwriter/dialogs/editlabel.py +6 -8
  44. novelwriter/dialogs/preferences.py +106 -93
  45. novelwriter/dialogs/projectsettings.py +16 -20
  46. novelwriter/dialogs/quotes.py +9 -5
  47. novelwriter/dialogs/wordlist.py +6 -6
  48. novelwriter/enum.py +4 -5
  49. novelwriter/extensions/configlayout.py +38 -4
  50. novelwriter/extensions/modified.py +22 -3
  51. novelwriter/extensions/{circularprogress.py → progressbars.py} +26 -3
  52. novelwriter/extensions/statusled.py +39 -23
  53. novelwriter/gui/doceditor.py +22 -13
  54. novelwriter/gui/dochighlight.py +30 -39
  55. novelwriter/gui/docviewer.py +24 -15
  56. novelwriter/gui/docviewerpanel.py +7 -0
  57. novelwriter/gui/mainmenu.py +11 -11
  58. novelwriter/gui/outline.py +4 -3
  59. novelwriter/gui/projtree.py +85 -77
  60. novelwriter/gui/search.py +10 -1
  61. novelwriter/gui/statusbar.py +25 -29
  62. novelwriter/gui/theme.py +3 -0
  63. novelwriter/guimain.py +139 -124
  64. novelwriter/shared.py +19 -8
  65. novelwriter/text/patterns.py +113 -0
  66. novelwriter/tools/dictionaries.py +2 -8
  67. novelwriter/tools/lipsum.py +8 -12
  68. novelwriter/tools/manusbuild.py +9 -9
  69. novelwriter/tools/manuscript.py +10 -5
  70. novelwriter/tools/manussettings.py +7 -3
  71. novelwriter/tools/noveldetails.py +10 -10
  72. novelwriter/tools/welcome.py +19 -10
  73. novelwriter/tools/writingstats.py +3 -3
  74. novelwriter/types.py +5 -2
  75. novelwriter/extensions/simpleprogress.py +0 -53
  76. {novelWriter-2.5b1.dist-info → novelWriter-2.5.1.dist-info}/LICENSE.md +0 -0
  77. {novelWriter-2.5b1.dist-info → novelWriter-2.5.1.dist-info}/entry_points.txt +0 -0
  78. {novelWriter-2.5b1.dist-info → novelWriter-2.5.1.dist-info}/top_level.txt +0 -0
novelwriter/guimain.py CHANGED
@@ -44,7 +44,7 @@ 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
- from novelwriter.enum import nwDocAction, nwDocInsert, nwDocMode, nwItemType, nwView, nwWidget
47
+ from novelwriter.enum import nwDocAction, nwDocInsert, nwDocMode, nwFocus, nwItemType, nwView
48
48
  from novelwriter.gui.doceditor import GuiDocEditor
49
49
  from novelwriter.gui.docviewer import GuiDocViewer
50
50
  from novelwriter.gui.docviewerpanel import GuiDocViewerPanel
@@ -189,9 +189,6 @@ class GuiMain(QMainWindow):
189
189
  self.splitView.setVisible(False)
190
190
  self.docEditor.closeSearch()
191
191
 
192
- # Initialise the Project Tree
193
- self.rebuildTrees()
194
-
195
192
  # Assemble Main Window Elements
196
193
  self.mainBox = QHBoxLayout()
197
194
  self.mainBox.addWidget(self.sideBar)
@@ -210,67 +207,70 @@ class GuiMain(QMainWindow):
210
207
  # Connect Signals
211
208
  # ===============
212
209
 
213
- SHARED.projectStatusChanged.connect(self.mainStatus.updateProjectStatus)
214
- SHARED.projectStatusMessage.connect(self.mainStatus.setStatusMessage)
215
- SHARED.spellLanguageChanged.connect(self.mainStatus.setLanguage)
216
210
  SHARED.focusModeChanged.connect(self._focusModeChanged)
211
+ SHARED.indexAvailable.connect(self.docViewerPanel.indexHasAppeared)
212
+ SHARED.indexChangedTags.connect(self.docEditor.updateChangedTags)
217
213
  SHARED.indexChangedTags.connect(self.docViewerPanel.updateChangedTags)
214
+ SHARED.indexCleared.connect(self.docViewerPanel.indexWasCleared)
218
215
  SHARED.indexScannedText.connect(self.docViewerPanel.projectItemChanged)
219
- SHARED.indexScannedText.connect(self.projView.updateItemValues)
220
216
  SHARED.indexScannedText.connect(self.itemDetails.updateViewBox)
221
- SHARED.indexCleared.connect(self.docViewerPanel.indexWasCleared)
222
- SHARED.indexAvailable.connect(self.docViewerPanel.indexHasAppeared)
217
+ SHARED.indexScannedText.connect(self.projView.updateItemValues)
223
218
  SHARED.mainClockTick.connect(self._timeTick)
219
+ SHARED.projectStatusChanged.connect(self.mainStatus.updateProjectStatus)
220
+ SHARED.projectStatusMessage.connect(self.mainStatus.setStatusMessage)
221
+ SHARED.spellLanguageChanged.connect(self.mainStatus.setLanguage)
222
+ SHARED.statusLabelsChanged.connect(self.docViewerPanel.updateStatusLabels)
223
+ SHARED.statusLabelsChanged.connect(self.projView.refreshUserLabels)
224
224
 
225
225
  self.mainMenu.requestDocAction.connect(self._passDocumentAction)
226
226
  self.mainMenu.requestDocInsert.connect(self._passDocumentInsert)
227
227
  self.mainMenu.requestDocInsertText.connect(self._passDocumentInsert)
228
228
  self.mainMenu.requestDocKeyWordInsert.connect(self.docEditor.insertKeyWord)
229
- self.mainMenu.requestFocusChange.connect(self.switchFocus)
229
+ self.mainMenu.requestFocusChange.connect(self._switchFocus)
230
230
  self.mainMenu.requestViewChange.connect(self._changeView)
231
231
 
232
232
  self.sideBar.requestViewChange.connect(self._changeView)
233
233
 
234
- self.projView.selectedItemChanged.connect(self.itemDetails.updateViewBox)
235
234
  self.projView.openDocumentRequest.connect(self._openDocument)
236
- self.projView.wordCountsChanged.connect(self._updateStatusWordCount)
235
+ self.projView.projectSettingsRequest.connect(self.showProjectSettingsDialog)
236
+ self.projView.rootFolderChanged.connect(self.novelView.updateRootItem)
237
+ self.projView.rootFolderChanged.connect(self.outlineView.updateRootItem)
238
+ self.projView.rootFolderChanged.connect(self.projView.updateRootItem)
239
+ self.projView.selectedItemChanged.connect(self.itemDetails.updateViewBox)
237
240
  self.projView.treeItemChanged.connect(self.docEditor.updateDocInfo)
238
241
  self.projView.treeItemChanged.connect(self.docViewer.updateDocInfo)
239
- self.projView.treeItemChanged.connect(self.itemDetails.updateViewBox)
240
242
  self.projView.treeItemChanged.connect(self.docViewerPanel.projectItemChanged)
241
- self.projView.rootFolderChanged.connect(self.outlineView.updateRootItem)
242
- self.projView.rootFolderChanged.connect(self.novelView.updateRootItem)
243
- self.projView.rootFolderChanged.connect(self.projView.updateRootItem)
244
- self.projView.projectSettingsRequest.connect(self.showProjectSettingsDialog)
243
+ self.projView.treeItemChanged.connect(self.itemDetails.updateViewBox)
244
+ self.projView.wordCountsChanged.connect(self._updateStatusWordCount)
245
245
 
246
- self.novelView.selectedItemChanged.connect(self.itemDetails.updateViewBox)
247
246
  self.novelView.openDocumentRequest.connect(self._openDocument)
247
+ self.novelView.selectedItemChanged.connect(self.itemDetails.updateViewBox)
248
248
 
249
249
  self.projSearch.openDocumentSelectRequest.connect(self._openDocumentSelection)
250
250
  self.projSearch.selectedItemChanged.connect(self.itemDetails.updateViewBox)
251
251
 
252
- self.docEditor.editedStatusChanged.connect(self.mainStatus.updateDocumentStatus)
252
+ self.docEditor.closeDocumentRequest.connect(self.closeDocEditor)
253
253
  self.docEditor.docCountsChanged.connect(self.itemDetails.updateCounts)
254
254
  self.docEditor.docCountsChanged.connect(self.projView.updateCounts)
255
+ self.docEditor.docTextChanged.connect(self.projSearch.textChanged)
256
+ self.docEditor.editedStatusChanged.connect(self.mainStatus.updateDocumentStatus)
255
257
  self.docEditor.loadDocumentTagRequest.connect(self._followTag)
256
- self.docEditor.novelStructureChanged.connect(self.novelView.refreshTree)
257
258
  self.docEditor.novelItemMetaChanged.connect(self.novelView.updateNovelItemMeta)
258
- self.docEditor.statusMessage.connect(self.mainStatus.setStatusMessage)
259
- self.docEditor.spellCheckStateChanged.connect(self.mainMenu.setSpellCheckState)
260
- self.docEditor.closeDocumentRequest.connect(self.closeDocEditor)
261
- self.docEditor.toggleFocusModeRequest.connect(self.toggleFocusMode)
262
- self.docEditor.requestProjectItemSelected.connect(self.projView.setSelectedHandle)
263
- self.docEditor.requestProjectItemRenamed.connect(self.projView.renameTreeItem)
259
+ self.docEditor.novelStructureChanged.connect(self.novelView.refreshTree)
264
260
  self.docEditor.requestNewNoteCreation.connect(self.projView.createNewNote)
265
- self.docEditor.docTextChanged.connect(self.projSearch.textChanged)
266
261
  self.docEditor.requestNextDocument.connect(self.openNextDocument)
262
+ self.docEditor.requestProjectItemRenamed.connect(self.projView.renameTreeItem)
263
+ self.docEditor.requestProjectItemSelected.connect(self.projView.setSelectedHandle)
264
+ self.docEditor.spellCheckStateChanged.connect(self.mainMenu.setSpellCheckState)
265
+ self.docEditor.statusMessage.connect(self.mainStatus.setStatusMessage)
266
+ self.docEditor.toggleFocusModeRequest.connect(self.toggleFocusMode)
267
267
 
268
+ self.docViewer.closeDocumentRequest.connect(self.closeDocViewer)
268
269
  self.docViewer.documentLoaded.connect(self.docViewerPanel.updateHandle)
269
270
  self.docViewer.loadDocumentTagRequest.connect(self._followTag)
270
- self.docViewer.closeDocumentRequest.connect(self.closeDocViewer)
271
271
  self.docViewer.reloadDocumentRequest.connect(self._reloadViewer)
272
- self.docViewer.togglePanelVisibility.connect(self._toggleViewerPanelVisibility)
273
272
  self.docViewer.requestProjectItemSelected.connect(self.projView.setSelectedHandle)
273
+ self.docViewer.togglePanelVisibility.connect(self._toggleViewerPanelVisibility)
274
274
 
275
275
  self.docViewerPanel.loadDocumentTagRequest.connect(self._followTag)
276
276
  self.docViewerPanel.openDocumentRequest.connect(self._openDocument)
@@ -304,9 +304,6 @@ class GuiMain(QMainWindow):
304
304
  self.keyEscape.setKey(Qt.Key.Key_Escape)
305
305
  self.keyEscape.activated.connect(self._keyPressEscape)
306
306
 
307
- # Check that config loaded fine
308
- self.reportConfErr()
309
-
310
307
  # Initialise Main GUI
311
308
  self.initMain()
312
309
  self.asProjTimer.start()
@@ -328,6 +325,15 @@ class GuiMain(QMainWindow):
328
325
 
329
326
  def postLaunchTasks(self, cmdOpen: str | None) -> None:
330
327
  """Process tasks after the main window has been created."""
328
+ QApplication.processEvents()
329
+ app = QApplication.instance()
330
+ if isinstance(app, QApplication):
331
+ app.focusChanged.connect(self._appFocusChanged)
332
+
333
+ # Check that config loaded fine
334
+ if CONFIG.hasError:
335
+ SHARED.error(CONFIG.errorText())
336
+
331
337
  if cmdOpen:
332
338
  QApplication.processEvents()
333
339
  logger.info("Command line path: %s", cmdOpen)
@@ -454,7 +460,6 @@ class GuiMain(QMainWindow):
454
460
 
455
461
  # Update GUI
456
462
  self._updateWindowTitle(SHARED.project.data.name)
457
- self.rebuildTrees()
458
463
  self.docEditor.toggleSpellCheck(SHARED.project.data.spellCheck)
459
464
  self.mainStatus.setRefTime(SHARED.project.projOpened)
460
465
  self.projView.openProjectTasks()
@@ -519,8 +524,14 @@ class GuiMain(QMainWindow):
519
524
  self.novelView.setActiveHandle(None)
520
525
  return
521
526
 
522
- def openDocument(self, tHandle: str | None, tLine: int | None = None,
523
- changeFocus: bool = True, doScroll: bool = False) -> bool:
527
+ def openDocument(
528
+ self,
529
+ tHandle: str | None,
530
+ tLine: int | None = None,
531
+ sTitle: str | None = None,
532
+ changeFocus: bool = True,
533
+ doScroll: bool = False
534
+ ) -> bool:
524
535
  """Open a specific document, optionally at a given line."""
525
536
  if not SHARED.hasProject:
526
537
  logger.error("No project open")
@@ -530,9 +541,12 @@ class GuiMain(QMainWindow):
530
541
  logger.debug("Requested item '%s' is not a document", tHandle)
531
542
  return False
532
543
 
544
+ if sTitle and tLine is None:
545
+ if hItem := SHARED.project.index.getItemHeading(tHandle, sTitle):
546
+ tLine = hItem.line
547
+
533
548
  self._changeView(nwView.EDITOR)
534
- cHandle = self.docEditor.docHandle
535
- if cHandle == tHandle:
549
+ if tHandle == self.docEditor.docHandle:
536
550
  self.docEditor.setCursorLine(tLine)
537
551
  if changeFocus:
538
552
  self.docEditor.setFocus()
@@ -616,6 +630,10 @@ class GuiMain(QMainWindow):
616
630
  # Make sure main tab is in Editor view
617
631
  self._changeView(nwView.EDITOR)
618
632
 
633
+ # If we're loading the document in the editor, it may need to be saved
634
+ if tHandle == self.docEditor.docHandle and self.docEditor.docChanged:
635
+ self.saveDocument()
636
+
619
637
  logger.debug("Viewing document with handle '%s'", tHandle)
620
638
  updateHistory = tHandle != self.docViewer.docHandle
621
639
  if self.docViewer.loadText(tHandle, updateHistory=updateHistory):
@@ -647,7 +665,7 @@ class GuiMain(QMainWindow):
647
665
  logger.error("No project open")
648
666
  return False
649
667
 
650
- lastPath = CONFIG.lastPath()
668
+ lastPath = CONFIG.lastPath("import")
651
669
  ffilter = formatFileFilter(["*.txt", "*.md", "*.nwd", "*"])
652
670
  loadFile, _ = QFileDialog.getOpenFileName(
653
671
  self, self.tr("Import File"), str(lastPath), filter=ffilter
@@ -662,7 +680,7 @@ class GuiMain(QMainWindow):
662
680
  try:
663
681
  with open(loadFile, mode="rt", encoding="utf-8") as inFile:
664
682
  text = inFile.read()
665
- CONFIG.setLastPath(loadFile)
683
+ CONFIG.setLastPath("import", loadFile)
666
684
  except Exception as exc:
667
685
  SHARED.error(self.tr(
668
686
  "Could not read file. The file must be an existing text file."
@@ -700,7 +718,6 @@ class GuiMain(QMainWindow):
700
718
  if SHARED.hasProject:
701
719
  tHandle = None
702
720
  sTitle = None
703
- tLine = None
704
721
  if self.projView.treeHasFocus():
705
722
  tHandle = self.projView.getSelectedHandle()
706
723
  elif self.novelView.treeHasFocus():
@@ -711,25 +728,9 @@ class GuiMain(QMainWindow):
711
728
  logger.warning("No item selected")
712
729
  return
713
730
 
714
- if tHandle and sTitle:
715
- if hItem := SHARED.project.index.getItemHeading(tHandle, sTitle):
716
- tLine = hItem.line
717
731
  if tHandle:
718
- self.openDocument(tHandle, tLine=tLine, changeFocus=False, doScroll=False)
719
-
720
- return
721
-
722
- def editItemLabel(self, tHandle: str | None = None) -> None:
723
- """Open the edit item dialog."""
724
- if SHARED.hasProject:
725
- if tHandle is None and (self.docEditor.anyFocus() or SHARED.focusMode):
726
- tHandle = self.docEditor.docHandle
727
- self.projView.renameTreeItem(tHandle)
728
- return
732
+ self.openDocument(tHandle, sTitle=sTitle, changeFocus=False, doScroll=False)
729
733
 
730
- def rebuildTrees(self) -> None:
731
- """Rebuild the project tree."""
732
- self.projView.populateTree()
733
734
  return
734
735
 
735
736
  def rebuildIndex(self, beQuiet: bool = False) -> None:
@@ -748,7 +749,6 @@ class GuiMain(QMainWindow):
748
749
  self.mainStatus.setStatusMessage(
749
750
  self.tr("Indexing completed in {0} ms").format(f"{(tEnd - tStart)*1000.0:.1f}")
750
751
  )
751
- self.docEditor.updateTagHighLighting()
752
752
  self._updateStatusWordCount()
753
753
  QApplication.restoreOverrideCursor()
754
754
 
@@ -800,7 +800,8 @@ class GuiMain(QMainWindow):
800
800
  def showBuildManuscriptDialog(self) -> None:
801
801
  """Open the build manuscript dialog."""
802
802
  if SHARED.hasProject:
803
- dialog = GuiManuscript(self)
803
+ if not (dialog := SHARED.findTopLevelWidget(GuiManuscript)):
804
+ dialog = GuiManuscript(self)
804
805
  dialog.activateDialog()
805
806
  dialog.loadContent()
806
807
  return
@@ -818,7 +819,8 @@ class GuiMain(QMainWindow):
818
819
  def showWritingStatsDialog(self) -> None:
819
820
  """Open the session stats dialog."""
820
821
  if SHARED.hasProject:
821
- dialog = GuiWritingStats(self)
822
+ if not (dialog := SHARED.findTopLevelWidget(GuiWritingStats)):
823
+ dialog = GuiWritingStats(self)
822
824
  dialog.activateDialog()
823
825
  dialog.populateGUI()
824
826
  return
@@ -827,8 +829,7 @@ class GuiMain(QMainWindow):
827
829
  def showAboutNWDialog(self) -> None:
828
830
  """Show the novelWriter about dialog."""
829
831
  dialog = GuiAbout(self)
830
- dialog.activateDialog()
831
- dialog.populateGUI()
832
+ dialog.exec()
832
833
  return
833
834
 
834
835
  @pyqtSlot()
@@ -848,15 +849,6 @@ class GuiMain(QMainWindow):
848
849
  SHARED.error(self.tr("Could not initialise the dialog."))
849
850
  return
850
851
 
851
- def reportConfErr(self) -> None:
852
- """Checks if the Config module has any errors to report, and let
853
- the user know if this is the case. The Config module caches
854
- errors since it is initialised before the GUI itself.
855
- """
856
- if CONFIG.hasError:
857
- SHARED.error(CONFIG.errorText())
858
- return
859
-
860
852
  ##
861
853
  # Main Window Actions
862
854
  ##
@@ -887,9 +879,7 @@ class GuiMain(QMainWindow):
887
879
 
888
880
  if SHARED.hasProject:
889
881
  self.closeProject(True)
890
-
891
882
  CONFIG.saveConfig()
892
- self.reportConfErr()
893
883
 
894
884
  QApplication.quit()
895
885
 
@@ -957,14 +947,37 @@ class GuiMain(QMainWindow):
957
947
  SHARED.setFocusMode(not SHARED.focusMode)
958
948
  return
959
949
 
950
+ ##
951
+ # Private Slots
952
+ ##
953
+
954
+ @pyqtSlot("QWidget*", "QWidget*")
955
+ def _appFocusChanged(self, old: QWidget, new: QWidget) -> None:
956
+ """Alert main widgets that they have received or lost focus."""
957
+ if isinstance(new, QWidget):
958
+ docEditor = False
959
+ docViewer = False
960
+ if self.docEditor.isAncestorOf(new):
961
+ docEditor = True
962
+ elif self.docViewer.isAncestorOf(new):
963
+ docViewer = True
964
+
965
+ self.docEditor.changeFocusState(docEditor)
966
+ self.docViewer.changeFocusState(docViewer)
967
+
968
+ logger.debug("Main focus switched to: %s", type(new).__name__)
969
+
970
+ return
971
+
960
972
  @pyqtSlot(bool)
961
973
  def _focusModeChanged(self, focusMode: bool) -> None:
962
- """Handle change of focus mode. The Main GUI Focus Mode hides tree,
963
- view, statusbar and menu.
974
+ """Handle change of focus mode. The Main GUI Focus Mode hides
975
+ tree, view, statusbar and menu.
964
976
  """
965
977
  if focusMode:
966
978
  logger.debug("Activating Focus Mode")
967
- self.switchFocus(nwWidget.EDITOR)
979
+ self._changeView(nwView.EDITOR)
980
+ self.docEditor.setFocus()
968
981
  else:
969
982
  logger.debug("Deactivating Focus Mode")
970
983
 
@@ -987,39 +1000,53 @@ class GuiMain(QMainWindow):
987
1000
  self.docEditor.ensureCursorVisibleNoCentre()
988
1001
  return
989
1002
 
990
- @pyqtSlot(nwWidget)
991
- def switchFocus(self, paneNo: nwWidget) -> None:
1003
+ @pyqtSlot(nwFocus)
1004
+ def _switchFocus(self, paneNo: nwFocus) -> None:
992
1005
  """Switch focus between main GUI views."""
993
- if paneNo == nwWidget.TREE:
994
- if self.projStack.currentWidget() is self.projView:
995
- if self.projView.treeHasFocus():
996
- self._changeView(nwView.NOVEL)
997
- self.novelView.setTreeFocus()
998
- else:
999
- self.projView.setTreeFocus()
1000
- elif self.projStack.currentWidget() is self.novelView:
1001
- if self.novelView.treeHasFocus():
1002
- self._changeView(nwView.PROJECT)
1003
- self.projView.setTreeFocus()
1004
- else:
1005
- self.novelView.setTreeFocus()
1006
+ if paneNo == nwFocus.TREE:
1007
+ # Decision Matrix
1008
+ # vM | vP | fP | vN | fN | Focus
1009
+ # ----|----|----|----|----|---------
1010
+ # T | T | T | F | F | Novel
1011
+ # T | T | F | F | F | Project
1012
+ # T | F | F | T | T | Project
1013
+ # T | F | F | T | F | Novel
1014
+ # T | F | F | F | F | Project
1015
+ # F | T | T | F | F | Project
1016
+ # F | T | F | F | F | Project
1017
+ # F | F | F | T | T | Novel
1018
+ # F | F | F | T | F | Novel
1019
+ # F | F | F | F | F | Project
1020
+
1021
+ vM = self.mainStack.currentWidget() is self.splitMain
1022
+ vP = self.projStack.currentWidget() is self.projView
1023
+ vN = self.projStack.currentWidget() is self.novelView
1024
+ fP = self.projView.treeHasFocus()
1025
+ fN = self.novelView.treeHasFocus()
1026
+
1027
+ self._changeView(nwView.EDITOR)
1028
+ if (vM and (vP and fP or vN and not fN)) or (not vM and vN):
1029
+ self._changeView(nwView.NOVEL)
1030
+ self.novelView.setTreeFocus()
1006
1031
  else:
1007
1032
  self._changeView(nwView.PROJECT)
1008
1033
  self.projView.setTreeFocus()
1009
- elif paneNo == nwWidget.EDITOR:
1010
- self._changeView(nwView.EDITOR)
1011
- self.docEditor.setFocus()
1012
- elif paneNo == nwWidget.VIEWER:
1034
+
1035
+ elif paneNo == nwFocus.DOCUMENT:
1013
1036
  self._changeView(nwView.EDITOR)
1014
- self.docViewer.setFocus()
1015
- elif paneNo == nwWidget.OUTLINE:
1037
+ hasViewer = self.splitView.isVisible()
1038
+ if hasViewer and self.docEditor.anyFocus():
1039
+ self.docViewer.setFocus()
1040
+ elif hasViewer and self.docViewer.anyFocus():
1041
+ self.docEditor.setFocus()
1042
+ else:
1043
+ self.docEditor.setFocus()
1044
+
1045
+ elif paneNo == nwFocus.OUTLINE:
1016
1046
  self._changeView(nwView.OUTLINE)
1017
1047
  self.outlineView.setTreeFocus()
1018
- return
1019
1048
 
1020
- ##
1021
- # Private Slots
1022
- ##
1049
+ return
1023
1050
 
1024
1051
  @pyqtSlot(bool, bool, bool, bool)
1025
1052
  def _processConfigChanges(self, restart: bool, tree: bool, theme: bool, syntax: bool) -> None:
@@ -1064,15 +1091,13 @@ class GuiMain(QMainWindow):
1064
1091
 
1065
1092
  return
1066
1093
 
1067
- @pyqtSlot(bool)
1068
- def _processProjectSettingsChanges(self, rebuildTrees: bool) -> None:
1094
+ @pyqtSlot()
1095
+ def _processProjectSettingsChanges(self) -> None:
1069
1096
  """Refresh data dependent on project settings."""
1070
1097
  logger.debug("Applying new project settings")
1071
1098
  SHARED.updateSpellCheckLanguage()
1072
1099
  self.itemDetails.refreshDetails()
1073
1100
  self._updateWindowTitle(SHARED.project.data.name)
1074
- if rebuildTrees:
1075
- self.rebuildTrees()
1076
1101
  return
1077
1102
 
1078
1103
  @pyqtSlot()
@@ -1089,7 +1114,7 @@ class GuiMain(QMainWindow):
1089
1114
  tHandle, sTitle = self._getTagSource(tag)
1090
1115
  if tHandle is not None:
1091
1116
  if mode == nwDocMode.EDIT:
1092
- self.openDocument(tHandle)
1117
+ self.openDocument(tHandle, sTitle=sTitle)
1093
1118
  elif mode == nwDocMode.VIEW:
1094
1119
  self.viewDocument(tHandle=tHandle, sTitle=sTitle)
1095
1120
  return
@@ -1108,11 +1133,7 @@ class GuiMain(QMainWindow):
1108
1133
  """Handle an open document request."""
1109
1134
  if tHandle is not None:
1110
1135
  if mode == nwDocMode.EDIT:
1111
- tLine = None
1112
- hItem = SHARED.project.index.getItemHeading(tHandle, sTitle)
1113
- if hItem is not None:
1114
- tLine = hItem.line
1115
- self.openDocument(tHandle, tLine=tLine, changeFocus=setFocus)
1136
+ self.openDocument(tHandle, sTitle=sTitle, changeFocus=setFocus)
1116
1137
  elif mode == nwDocMode.VIEW:
1117
1138
  self.viewDocument(tHandle=tHandle, sTitle=sTitle)
1118
1139
  return
@@ -1159,16 +1180,13 @@ class GuiMain(QMainWindow):
1159
1180
 
1160
1181
  @pyqtSlot(nwDocAction)
1161
1182
  def _passDocumentAction(self, action: nwDocAction) -> None:
1162
- """Pass on a document action to the document viewer if it has
1163
- focus, or pass it to the document editor if it or any of its
1164
- child widgets have focus. If neither has focus, ignore it.
1183
+ """Pass on a document action to the editor or viewer based on
1184
+ which one has focus, or if neither has focus, ignore it.
1165
1185
  """
1166
- if self.docViewer.hasFocus():
1167
- self.docViewer.docAction(action)
1168
- elif self.docEditor.hasFocus():
1186
+ if self.docEditor.hasFocus():
1169
1187
  self.docEditor.docAction(action)
1170
- else:
1171
- logger.debug("Action cancelled as neither editor nor viewer has focus")
1188
+ elif self.docViewer.hasFocus():
1189
+ self.docViewer.docAction(action)
1172
1190
  return
1173
1191
 
1174
1192
  @pyqtSlot(str)
@@ -1376,10 +1394,7 @@ class GuiMain(QMainWindow):
1376
1394
 
1377
1395
  def _updateWindowTitle(self, projName: str | None = None) -> None:
1378
1396
  """Set the window title and add the project's name."""
1379
- winTitle = CONFIG.appName
1380
- if projName is not None:
1381
- winTitle += " - %s" % projName
1382
- self.setWindowTitle(winTitle)
1397
+ self.setWindowTitle(" - ".join(filter(None, [projName, CONFIG.appName])))
1383
1398
  return
1384
1399
 
1385
1400
  def _getTagSource(self, tag: str) -> tuple[str | None, str | None]:
novelwriter/shared.py CHANGED
@@ -64,6 +64,7 @@ class SharedData(QObject):
64
64
  indexCleared = pyqtSignal()
65
65
  indexAvailable = pyqtSignal()
66
66
  mainClockTick = pyqtSignal()
67
+ statusLabelsChanged = pyqtSignal(str)
67
68
 
68
69
  def __init__(self) -> None:
69
70
  super().__init__()
@@ -172,15 +173,21 @@ class SharedData(QObject):
172
173
  logger.debug("Thread Pool Max Count: %d", QThreadPool.globalInstance().maxThreadCount())
173
174
  return
174
175
 
175
- def closeDocument(self, tHandle: str | None = None) -> None:
176
+ def closeEditor(self, tHandle: str | None = None) -> None:
176
177
  """Close the document editor, optionally a specific document."""
177
178
  if tHandle is None or tHandle == self.mainGui.docEditor.docHandle:
178
179
  self.mainGui.closeDocument()
179
180
  return
180
181
 
181
- def saveDocument(self) -> None:
182
- """Forward save document call to main GUI."""
183
- self.mainGui.saveDocument()
182
+ def saveEditor(self, tHandle: str | None = None) -> None:
183
+ """Save the editor content, optionally a specific document."""
184
+ docEditor = self.mainGui.docEditor
185
+ if (
186
+ self.hasProject and docEditor.docHandle
187
+ and (tHandle is None or tHandle == docEditor.docHandle)
188
+ ):
189
+ logger.debug("Saving editor document before action")
190
+ docEditor.saveText()
184
191
  return
185
192
 
186
193
  def openProject(self, path: str | Path, clearLock: bool = False) -> bool:
@@ -303,6 +310,14 @@ class SharedData(QObject):
303
310
  self.indexAvailable.emit()
304
311
  return
305
312
 
313
+ def projectSingalProxy(self, data: dict) -> None:
314
+ """Emit signals on project data change."""
315
+ event = data.get("event")
316
+ logger.debug("Received '%s' event from project data", event)
317
+ if event == "statusLabels":
318
+ self.statusLabelsChanged.emit(data.get("kind", ""))
319
+ return
320
+
306
321
  ##
307
322
  # Alert Boxes
308
323
  ##
@@ -316,7 +331,6 @@ class SharedData(QObject):
316
331
  if log:
317
332
  logger.info(self._lastAlert, stacklevel=2)
318
333
  alert.exec()
319
- alert.deleteLater()
320
334
  return
321
335
 
322
336
  def warn(self, text: str, info: str = "", details: str = "", log: bool = True) -> None:
@@ -328,7 +342,6 @@ class SharedData(QObject):
328
342
  if log:
329
343
  logger.warning(self._lastAlert, stacklevel=2)
330
344
  alert.exec()
331
- alert.deleteLater()
332
345
  return
333
346
 
334
347
  def error(self, text: str, info: str = "", details: str = "", log: bool = True,
@@ -343,7 +356,6 @@ class SharedData(QObject):
343
356
  if log:
344
357
  logger.error(self._lastAlert, stacklevel=2)
345
358
  alert.exec()
346
- alert.deleteLater()
347
359
  return
348
360
 
349
361
  def question(self, text: str, info: str = "", details: str = "", warn: bool = False) -> bool:
@@ -354,7 +366,6 @@ class SharedData(QObject):
354
366
  self._lastAlert = alert.logMessage
355
367
  alert.exec()
356
368
  isYes = alert.result() == QMessageBox.StandardButton.Yes
357
- alert.deleteLater()
358
369
  return isYes
359
370
 
360
371
  ##