novelWriter 2.5.2__py3-none-any.whl → 2.6__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 (129) hide show
  1. {novelWriter-2.5.2.dist-info → novelWriter-2.6.dist-info}/METADATA +5 -4
  2. {novelWriter-2.5.2.dist-info → novelWriter-2.6.dist-info}/RECORD +126 -105
  3. {novelWriter-2.5.2.dist-info → novelWriter-2.6.dist-info}/WHEEL +1 -1
  4. novelwriter/__init__.py +50 -11
  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_ru_RU.qm +0 -0
  16. novelwriter/assets/i18n/nw_zh_CN.qm +0 -0
  17. novelwriter/assets/i18n/project_de_DE.json +4 -2
  18. novelwriter/assets/i18n/project_en_GB.json +1 -0
  19. novelwriter/assets/i18n/project_en_US.json +2 -0
  20. novelwriter/assets/i18n/project_it_IT.json +2 -0
  21. novelwriter/assets/i18n/project_ja_JP.json +2 -0
  22. novelwriter/assets/i18n/project_nb_NO.json +2 -0
  23. novelwriter/assets/i18n/project_nl_NL.json +2 -0
  24. novelwriter/assets/i18n/project_pl_PL.json +2 -0
  25. novelwriter/assets/i18n/project_pt_BR.json +2 -0
  26. novelwriter/assets/i18n/project_ru_RU.json +11 -0
  27. novelwriter/assets/i18n/project_zh_CN.json +2 -0
  28. novelwriter/assets/icons/typicons_dark/icons.conf +8 -0
  29. novelwriter/assets/icons/typicons_dark/mixed_copy.svg +4 -0
  30. novelwriter/assets/icons/typicons_dark/mixed_margin-bottom.svg +6 -0
  31. novelwriter/assets/icons/typicons_dark/mixed_margin-left.svg +6 -0
  32. novelwriter/assets/icons/typicons_dark/mixed_margin-right.svg +6 -0
  33. novelwriter/assets/icons/typicons_dark/mixed_margin-top.svg +6 -0
  34. novelwriter/assets/icons/typicons_dark/mixed_size-height.svg +6 -0
  35. novelwriter/assets/icons/typicons_dark/mixed_size-width.svg +6 -0
  36. novelwriter/assets/icons/typicons_dark/nw_toolbar.svg +5 -0
  37. novelwriter/assets/icons/typicons_light/icons.conf +8 -0
  38. novelwriter/assets/icons/typicons_light/mixed_copy.svg +4 -0
  39. novelwriter/assets/icons/typicons_light/mixed_margin-bottom.svg +6 -0
  40. novelwriter/assets/icons/typicons_light/mixed_margin-left.svg +6 -0
  41. novelwriter/assets/icons/typicons_light/mixed_margin-right.svg +6 -0
  42. novelwriter/assets/icons/typicons_light/mixed_margin-top.svg +6 -0
  43. novelwriter/assets/icons/typicons_light/mixed_size-height.svg +6 -0
  44. novelwriter/assets/icons/typicons_light/mixed_size-width.svg +6 -0
  45. novelwriter/assets/icons/typicons_light/nw_toolbar.svg +5 -0
  46. novelwriter/assets/manual.pdf +0 -0
  47. novelwriter/assets/sample.zip +0 -0
  48. novelwriter/assets/text/credits_en.htm +1 -0
  49. novelwriter/assets/themes/default_light.conf +2 -2
  50. novelwriter/common.py +101 -3
  51. novelwriter/config.py +30 -17
  52. novelwriter/constants.py +189 -81
  53. novelwriter/core/buildsettings.py +74 -40
  54. novelwriter/core/coretools.py +146 -148
  55. novelwriter/core/docbuild.py +133 -171
  56. novelwriter/core/document.py +1 -1
  57. novelwriter/core/index.py +39 -38
  58. novelwriter/core/item.py +42 -9
  59. novelwriter/core/itemmodel.py +518 -0
  60. novelwriter/core/options.py +5 -2
  61. novelwriter/core/project.py +68 -90
  62. novelwriter/core/projectdata.py +8 -2
  63. novelwriter/core/projectxml.py +1 -1
  64. novelwriter/core/sessions.py +1 -1
  65. novelwriter/core/spellcheck.py +10 -15
  66. novelwriter/core/status.py +24 -8
  67. novelwriter/core/storage.py +1 -1
  68. novelwriter/core/tree.py +269 -288
  69. novelwriter/dialogs/about.py +1 -1
  70. novelwriter/dialogs/docmerge.py +8 -18
  71. novelwriter/dialogs/docsplit.py +1 -1
  72. novelwriter/dialogs/editlabel.py +1 -1
  73. novelwriter/dialogs/preferences.py +47 -34
  74. novelwriter/dialogs/projectsettings.py +149 -99
  75. novelwriter/dialogs/quotes.py +1 -1
  76. novelwriter/dialogs/wordlist.py +11 -10
  77. novelwriter/enum.py +37 -24
  78. novelwriter/error.py +2 -2
  79. novelwriter/extensions/configlayout.py +28 -13
  80. novelwriter/extensions/eventfilters.py +1 -1
  81. novelwriter/extensions/modified.py +30 -6
  82. novelwriter/extensions/novelselector.py +4 -3
  83. novelwriter/extensions/pagedsidebar.py +9 -9
  84. novelwriter/extensions/progressbars.py +4 -4
  85. novelwriter/extensions/statusled.py +3 -3
  86. novelwriter/extensions/switch.py +3 -3
  87. novelwriter/extensions/switchbox.py +1 -1
  88. novelwriter/extensions/versioninfo.py +1 -1
  89. novelwriter/formats/shared.py +156 -0
  90. novelwriter/formats/todocx.py +1191 -0
  91. novelwriter/formats/tohtml.py +454 -0
  92. novelwriter/{core → formats}/tokenizer.py +497 -495
  93. novelwriter/formats/tomarkdown.py +218 -0
  94. novelwriter/{core → formats}/toodt.py +312 -433
  95. novelwriter/formats/toqdoc.py +486 -0
  96. novelwriter/formats/toraw.py +91 -0
  97. novelwriter/gui/doceditor.py +347 -287
  98. novelwriter/gui/dochighlight.py +97 -85
  99. novelwriter/gui/docviewer.py +90 -33
  100. novelwriter/gui/docviewerpanel.py +18 -26
  101. novelwriter/gui/editordocument.py +18 -3
  102. novelwriter/gui/itemdetails.py +27 -29
  103. novelwriter/gui/mainmenu.py +130 -64
  104. novelwriter/gui/noveltree.py +46 -48
  105. novelwriter/gui/outline.py +202 -256
  106. novelwriter/gui/projtree.py +590 -1238
  107. novelwriter/gui/search.py +11 -19
  108. novelwriter/gui/sidebar.py +8 -7
  109. novelwriter/gui/statusbar.py +20 -3
  110. novelwriter/gui/theme.py +11 -6
  111. novelwriter/guimain.py +101 -201
  112. novelwriter/shared.py +67 -28
  113. novelwriter/text/counting.py +3 -1
  114. novelwriter/text/patterns.py +169 -61
  115. novelwriter/tools/dictionaries.py +3 -3
  116. novelwriter/tools/lipsum.py +1 -1
  117. novelwriter/tools/manusbuild.py +15 -13
  118. novelwriter/tools/manuscript.py +121 -79
  119. novelwriter/tools/manussettings.py +424 -291
  120. novelwriter/tools/noveldetails.py +1 -1
  121. novelwriter/tools/welcome.py +6 -6
  122. novelwriter/tools/writingstats.py +4 -4
  123. novelwriter/types.py +25 -9
  124. novelwriter/core/tohtml.py +0 -530
  125. novelwriter/core/tomarkdown.py +0 -252
  126. novelwriter/core/toqdoc.py +0 -419
  127. {novelWriter-2.5.2.dist-info → novelWriter-2.6.dist-info}/LICENSE.md +0 -0
  128. {novelWriter-2.5.2.dist-info → novelWriter-2.6.dist-info}/entry_points.txt +0 -0
  129. {novelWriter-2.5.2.dist-info → novelWriter-2.6.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–2024, Veronica Berglyd Olsen
9
+ Copyright (C) 2018 Veronica Berglyd Olsen and novelWriter contributors
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
@@ -62,6 +62,7 @@ from novelwriter.tools.manuscript import GuiManuscript
62
62
  from novelwriter.tools.noveldetails import GuiNovelDetails
63
63
  from novelwriter.tools.welcome import GuiWelcome
64
64
  from novelwriter.tools.writingstats import GuiWritingStats
65
+ from novelwriter.types import QtModShift
65
66
 
66
67
  logger = logging.getLogger(__name__)
67
68
 
@@ -212,15 +213,19 @@ class GuiMain(QMainWindow):
212
213
  SHARED.indexChangedTags.connect(self.docEditor.updateChangedTags)
213
214
  SHARED.indexChangedTags.connect(self.docViewerPanel.updateChangedTags)
214
215
  SHARED.indexCleared.connect(self.docViewerPanel.indexWasCleared)
215
- SHARED.indexScannedText.connect(self.docViewerPanel.projectItemChanged)
216
- SHARED.indexScannedText.connect(self.itemDetails.updateViewBox)
217
- SHARED.indexScannedText.connect(self.projView.updateItemValues)
218
216
  SHARED.mainClockTick.connect(self._timeTick)
217
+ SHARED.projectItemChanged.connect(self.docEditor.onProjectItemChanged)
218
+ SHARED.projectItemChanged.connect(self.docViewer.onProjectItemChanged)
219
+ SHARED.projectItemChanged.connect(self.docViewerPanel.onProjectItemChanged)
220
+ SHARED.projectItemChanged.connect(self.itemDetails.onProjectItemChanged)
221
+ SHARED.projectItemChanged.connect(self.projView.onProjectItemChanged)
219
222
  SHARED.projectStatusChanged.connect(self.mainStatus.updateProjectStatus)
220
223
  SHARED.projectStatusMessage.connect(self.mainStatus.setStatusMessage)
224
+ SHARED.rootFolderChanged.connect(self.novelView.updateRootItem)
225
+ SHARED.rootFolderChanged.connect(self.outlineView.updateRootItem)
226
+ SHARED.rootFolderChanged.connect(self.projView.updateRootItem)
221
227
  SHARED.spellLanguageChanged.connect(self.mainStatus.setLanguage)
222
228
  SHARED.statusLabelsChanged.connect(self.docViewerPanel.updateStatusLabels)
223
- SHARED.statusLabelsChanged.connect(self.projView.refreshUserLabels)
224
229
 
225
230
  self.mainMenu.requestDocAction.connect(self._passDocumentAction)
226
231
  self.mainMenu.requestDocInsert.connect(self._passDocumentInsert)
@@ -233,15 +238,7 @@ class GuiMain(QMainWindow):
233
238
 
234
239
  self.projView.openDocumentRequest.connect(self._openDocument)
235
240
  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
241
  self.projView.selectedItemChanged.connect(self.itemDetails.updateViewBox)
240
- self.projView.treeItemChanged.connect(self.docEditor.updateDocInfo)
241
- self.projView.treeItemChanged.connect(self.docViewer.updateDocInfo)
242
- self.projView.treeItemChanged.connect(self.docViewerPanel.projectItemChanged)
243
- self.projView.treeItemChanged.connect(self.itemDetails.updateViewBox)
244
- self.projView.wordCountsChanged.connect(self._updateStatusWordCount)
245
242
 
246
243
  self.novelView.openDocumentRequest.connect(self._openDocument)
247
244
  self.novelView.selectedItemChanged.connect(self.itemDetails.updateViewBox)
@@ -249,25 +246,27 @@ class GuiMain(QMainWindow):
249
246
  self.projSearch.openDocumentSelectRequest.connect(self._openDocumentSelection)
250
247
  self.projSearch.selectedItemChanged.connect(self.itemDetails.updateViewBox)
251
248
 
252
- self.docEditor.closeDocumentRequest.connect(self.closeDocEditor)
253
- self.docEditor.docCountsChanged.connect(self.itemDetails.updateCounts)
254
- self.docEditor.docCountsChanged.connect(self.projView.updateCounts)
249
+ self.docEditor.closeEditorRequest.connect(self.closeDocEditor)
255
250
  self.docEditor.docTextChanged.connect(self.projSearch.textChanged)
256
251
  self.docEditor.editedStatusChanged.connect(self.mainStatus.updateDocumentStatus)
252
+ self.docEditor.itemHandleChanged.connect(self.novelView.setActiveHandle)
253
+ self.docEditor.itemHandleChanged.connect(self.projView.setActiveHandle)
257
254
  self.docEditor.loadDocumentTagRequest.connect(self._followTag)
258
255
  self.docEditor.novelItemMetaChanged.connect(self.novelView.updateNovelItemMeta)
259
256
  self.docEditor.novelStructureChanged.connect(self.novelView.refreshTree)
260
- self.docEditor.requestNewNoteCreation.connect(self.projView.createNewNote)
257
+ self.docEditor.openDocumentRequest.connect(self._openDocument)
258
+ self.docEditor.requestNewNoteCreation.connect(SHARED.createNewNote)
261
259
  self.docEditor.requestNextDocument.connect(self.openNextDocument)
262
260
  self.docEditor.requestProjectItemRenamed.connect(self.projView.renameTreeItem)
263
261
  self.docEditor.requestProjectItemSelected.connect(self.projView.setSelectedHandle)
264
262
  self.docEditor.spellCheckStateChanged.connect(self.mainMenu.setSpellCheckState)
265
- self.docEditor.statusMessage.connect(self.mainStatus.setStatusMessage)
266
263
  self.docEditor.toggleFocusModeRequest.connect(self.toggleFocusMode)
264
+ self.docEditor.updateStatusMessage.connect(self.mainStatus.setStatusMessage)
267
265
 
268
266
  self.docViewer.closeDocumentRequest.connect(self.closeDocViewer)
269
267
  self.docViewer.documentLoaded.connect(self.docViewerPanel.updateHandle)
270
268
  self.docViewer.loadDocumentTagRequest.connect(self._followTag)
269
+ self.docViewer.openDocumentRequest.connect(self._openDocument)
271
270
  self.docViewer.reloadDocumentRequest.connect(self._reloadViewer)
272
271
  self.docViewer.requestProjectItemSelected.connect(self.projView.setSelectedHandle)
273
272
  self.docViewer.togglePanelVisibility.connect(self._toggleViewerPanelVisibility)
@@ -289,21 +288,30 @@ class GuiMain(QMainWindow):
289
288
  self.asDocTimer = QTimer(self)
290
289
  self.asDocTimer.timeout.connect(self._autoSaveDocument)
291
290
 
292
- # Shortcuts and Actions
293
- self._connectMenuActions()
294
-
291
+ # Shortcuts
295
292
  self.keyReturn = QShortcut(self)
296
- self.keyReturn.setKey(Qt.Key.Key_Return)
293
+ self.keyReturn.setKey("Return")
297
294
  self.keyReturn.activated.connect(self._keyPressReturn)
298
295
 
296
+ self.keyShiftReturn = QShortcut(self)
297
+ self.keyShiftReturn.setKey("Shift+Return")
298
+ self.keyShiftReturn.activated.connect(self._keyPressReturn)
299
+
299
300
  self.keyEnter = QShortcut(self)
300
- self.keyEnter.setKey(Qt.Key.Key_Enter)
301
+ self.keyEnter.setKey("Enter")
301
302
  self.keyEnter.activated.connect(self._keyPressReturn)
302
303
 
304
+ self.keyShiftEnter = QShortcut(self)
305
+ self.keyShiftEnter.setKey("Shift+Enter")
306
+ self.keyShiftEnter.activated.connect(self._keyPressReturn)
307
+
303
308
  self.keyEscape = QShortcut(self)
304
- self.keyEscape.setKey(Qt.Key.Key_Escape)
309
+ self.keyEscape.setKey("Esc")
305
310
  self.keyEscape.activated.connect(self._keyPressEscape)
306
311
 
312
+ # Internal Variables
313
+ self._lastTotalCount = 0
314
+
307
315
  # Initialise Main GUI
308
316
  self.initMain()
309
317
  self.asProjTimer.start()
@@ -481,8 +489,7 @@ class GuiMain(QMainWindow):
481
489
  QApplication.processEvents()
482
490
  self.openDocument(lastEdited, doScroll=True)
483
491
 
484
- lastViewed = SHARED.project.data.getLastHandle("viewer")
485
- if lastViewed is not None:
492
+ if lastViewed := SHARED.project.data.getLastHandle("viewer"):
486
493
  QApplication.processEvents()
487
494
  self.viewDocument(lastViewed)
488
495
 
@@ -502,17 +509,15 @@ class GuiMain(QMainWindow):
502
509
 
503
510
  def saveProject(self, autoSave: bool = False) -> bool:
504
511
  """Save the current project."""
505
- if not SHARED.hasProject:
506
- logger.error("No project open")
507
- return False
508
- self.projView.saveProjectTasks()
509
- return SHARED.saveProject(autoSave=autoSave)
512
+ if SHARED.hasProject:
513
+ return SHARED.saveProject(autoSave=autoSave)
514
+ return False
510
515
 
511
516
  ##
512
517
  # Document Actions
513
518
  ##
514
519
 
515
- def closeDocument(self, beforeOpen: bool = False) -> None:
520
+ def closeDocument(self) -> None:
516
521
  """Close the document and clear the editor and title field."""
517
522
  if SHARED.hasProject:
518
523
  # Disable focus mode if it is active
@@ -520,8 +525,6 @@ class GuiMain(QMainWindow):
520
525
  SHARED.setFocusMode(False)
521
526
  self.saveDocument()
522
527
  self.docEditor.clearEditor()
523
- if not beforeOpen:
524
- self.novelView.setActiveHandle(None)
525
528
  return
526
529
 
527
530
  def openDocument(
@@ -533,12 +536,8 @@ class GuiMain(QMainWindow):
533
536
  doScroll: bool = False
534
537
  ) -> bool:
535
538
  """Open a specific document, optionally at a given line."""
536
- if not SHARED.hasProject:
537
- logger.error("No project open")
538
- return False
539
-
540
- if not tHandle or not SHARED.project.tree.checkType(tHandle, nwItemType.FILE):
541
- logger.debug("Requested item '%s' is not a document", tHandle)
539
+ if not (SHARED.hasProject and tHandle):
540
+ logger.error("Nothing to open open")
542
541
  return False
543
542
 
544
543
  if sTitle and tLine is None:
@@ -548,19 +547,15 @@ class GuiMain(QMainWindow):
548
547
  self._changeView(nwView.EDITOR)
549
548
  if tHandle == self.docEditor.docHandle:
550
549
  self.docEditor.setCursorLine(tLine)
551
- if changeFocus:
552
- self.docEditor.setFocus()
553
- return True
554
-
555
- self.closeDocument(beforeOpen=True)
556
- if self.docEditor.loadText(tHandle, tLine):
557
- SHARED.project.data.setLastHandle(tHandle, "editor")
558
- self.projView.setSelectedHandle(tHandle, doScroll=doScroll)
559
- self.novelView.setActiveHandle(tHandle, doScroll=doScroll)
560
- if changeFocus:
561
- self.docEditor.setFocus()
562
550
  else:
563
- return False
551
+ self.closeDocument()
552
+ if self.docEditor.loadText(tHandle, tLine):
553
+ self.projView.setSelectedHandle(tHandle, doScroll=doScroll)
554
+ else:
555
+ return False
556
+
557
+ if changeFocus:
558
+ self.docEditor.setFocus()
564
559
 
565
560
  return True
566
561
 
@@ -728,8 +723,11 @@ class GuiMain(QMainWindow):
728
723
  logger.warning("No item selected")
729
724
  return
730
725
 
731
- if tHandle:
732
- self.openDocument(tHandle, sTitle=sTitle, changeFocus=False, doScroll=False)
726
+ if tHandle and SHARED.project.tree.checkType(tHandle, nwItemType.FILE):
727
+ if QApplication.keyboardModifiers() == QtModShift:
728
+ self.viewDocument(tHandle)
729
+ else:
730
+ self.openDocument(tHandle, sTitle=sTitle, changeFocus=False, doScroll=False)
733
731
 
734
732
  return
735
733
 
@@ -740,9 +738,8 @@ class GuiMain(QMainWindow):
740
738
  QApplication.setOverrideCursor(QCursor(Qt.CursorShape.WaitCursor))
741
739
  tStart = time()
742
740
 
743
- self.projView.saveProjectTasks()
744
- SHARED.project.index.rebuildIndex()
745
- self.projView.populateTree()
741
+ SHARED.project.index.rebuild()
742
+ SHARED.project.tree.refreshAllItems()
746
743
  self.novelView.refreshTree()
747
744
 
748
745
  tEnd = time()
@@ -855,13 +852,11 @@ class GuiMain(QMainWindow):
855
852
 
856
853
  def closeMain(self) -> bool:
857
854
  """Save everything, and close novelWriter."""
858
- if SHARED.hasProject:
859
- msgYes = SHARED.question("%s<br>%s" % (
860
- self.tr("Do you want to exit novelWriter?"),
861
- self.tr("Changes are saved automatically.")
862
- ))
863
- if not msgYes:
864
- return False
855
+ if SHARED.hasProject and not SHARED.question("%s<br>%s" % (
856
+ self.tr("Do you want to exit novelWriter?"),
857
+ self.tr("Changes are saved automatically.")
858
+ )):
859
+ return False
865
860
 
866
861
  logger.info("Exiting novelWriter")
867
862
 
@@ -906,11 +901,6 @@ class GuiMain(QMainWindow):
906
901
 
907
902
  return not self.splitView.isVisible()
908
903
 
909
- def toggleFullScreenMode(self) -> None:
910
- """Toggle full screen mode"""
911
- self.setWindowState(self.windowState() ^ Qt.WindowState.WindowFullScreen)
912
- return
913
-
914
904
  ##
915
905
  # Events
916
906
  ##
@@ -926,6 +916,12 @@ class GuiMain(QMainWindow):
926
916
  # Public Slots
927
917
  ##
928
918
 
919
+ @pyqtSlot()
920
+ def toggleFullScreenMode(self) -> None:
921
+ """Toggle full screen mode"""
922
+ self.setWindowState(self.windowState() ^ Qt.WindowState.WindowFullScreen)
923
+ return
924
+
929
925
  @pyqtSlot()
930
926
  def closeDocEditor(self) -> None:
931
927
  """Close the document editor. This does not hide the editor."""
@@ -961,12 +957,8 @@ class GuiMain(QMainWindow):
961
957
  docEditor = True
962
958
  elif self.docViewer.isAncestorOf(new):
963
959
  docViewer = True
964
-
965
960
  self.docEditor.changeFocusState(docEditor)
966
961
  self.docViewer.changeFocusState(docViewer)
967
-
968
- logger.debug("Main focus switched to: %s", type(new).__name__)
969
-
970
962
  return
971
963
 
972
964
  @pyqtSlot(bool)
@@ -1055,13 +1047,8 @@ class GuiMain(QMainWindow):
1055
1047
  self.initMain()
1056
1048
  self.saveDocument()
1057
1049
 
1058
- if restart:
1059
- SHARED.info(self.tr(
1060
- "Some changes will not be applied until novelWriter has been restarted."
1061
- ))
1062
-
1063
1050
  if tree:
1064
- self.projView.populateTree()
1051
+ SHARED.project.tree.refreshAllItems()
1065
1052
 
1066
1053
  if theme:
1067
1054
  # We are doing this manually instead of connecting to
@@ -1087,8 +1074,16 @@ class GuiMain(QMainWindow):
1087
1074
  self.projView.initSettings()
1088
1075
  self.novelView.initSettings()
1089
1076
  self.outlineView.initSettings()
1077
+
1078
+ # Force update of word count
1079
+ self._lastTotalCount = 0
1090
1080
  self._updateStatusWordCount()
1091
1081
 
1082
+ if restart:
1083
+ SHARED.info(self.tr(
1084
+ "Some changes will not be applied until novelWriter has been restarted."
1085
+ ))
1086
+
1092
1087
  return
1093
1088
 
1094
1089
  @pyqtSlot()
@@ -1111,8 +1106,16 @@ class GuiMain(QMainWindow):
1111
1106
  @pyqtSlot(str, nwDocMode)
1112
1107
  def _followTag(self, tag: str, mode: nwDocMode) -> None:
1113
1108
  """Follow a tag after user interaction with a link."""
1114
- tHandle, sTitle = self._getTagSource(tag)
1115
- if tHandle is not None:
1109
+ tHandle, sTitle = SHARED.project.index.getTagSource(tag)
1110
+ if tHandle is None:
1111
+ SHARED.error(self.tr(
1112
+ "Could not find the reference for tag '{0}'. It either doesn't "
1113
+ "exist, or the index is out of date. The index can be updated "
1114
+ "from the Tools menu, or by pressing {1}."
1115
+ ).format(
1116
+ tag, "F9"
1117
+ ))
1118
+ else:
1116
1119
  if mode == nwDocMode.EDIT:
1117
1120
  self.openDocument(tHandle, sTitle=sTitle)
1118
1121
  elif mode == nwDocMode.VIEW:
@@ -1216,8 +1219,10 @@ class GuiMain(QMainWindow):
1216
1219
  self.mainStatus.setUserIdle(editIdle or userIdle)
1217
1220
  SHARED.updateIdleTime(currTime, editIdle or userIdle)
1218
1221
  self.mainStatus.updateTime(idleTime=SHARED.projectIdleTime)
1219
- if CONFIG.memInfo and int(currTime) % 5 == 0: # pragma: no cover
1220
- self.mainStatus.memInfo()
1222
+ if int(currTime) % 5 == 0:
1223
+ self._updateStatusWordCount()
1224
+ if CONFIG.memInfo: # pragma: no cover
1225
+ self.mainStatus.memInfo()
1221
1226
  return
1222
1227
 
1223
1228
  @pyqtSlot()
@@ -1245,15 +1250,19 @@ class GuiMain(QMainWindow):
1245
1250
  if not SHARED.hasProject:
1246
1251
  self.mainStatus.setProjectStats(0, 0)
1247
1252
 
1248
- SHARED.project.updateWordCounts()
1249
- if CONFIG.incNotesWCount:
1250
- iTotal = sum(SHARED.project.data.initCounts)
1251
- cTotal = sum(SHARED.project.data.currCounts)
1252
- self.mainStatus.setProjectStats(cTotal, cTotal - iTotal)
1253
- else:
1254
- iNovel, _ = SHARED.project.data.initCounts
1255
- cNovel, _ = SHARED.project.data.currCounts
1256
- self.mainStatus.setProjectStats(cNovel, cNovel - iNovel)
1253
+ currentTotalCount = SHARED.project.currentTotalCount
1254
+ if self._lastTotalCount != currentTotalCount:
1255
+ self._lastTotalCount = currentTotalCount
1256
+
1257
+ SHARED.project.updateWordCounts()
1258
+ if CONFIG.incNotesWCount:
1259
+ iTotal = sum(SHARED.project.data.initCounts)
1260
+ cTotal = sum(SHARED.project.data.currCounts)
1261
+ self.mainStatus.setProjectStats(cTotal, cTotal - iTotal)
1262
+ else:
1263
+ iNovel, _ = SHARED.project.data.initCounts
1264
+ cNovel, _ = SHARED.project.data.currCounts
1265
+ self.mainStatus.setProjectStats(cNovel, cNovel - iNovel)
1257
1266
 
1258
1267
  return
1259
1268
 
@@ -1299,116 +1308,7 @@ class GuiMain(QMainWindow):
1299
1308
  # Internal Functions
1300
1309
  ##
1301
1310
 
1302
- def _connectMenuActions(self) -> None:
1303
- """Connect to the main window all menu actions that need to be
1304
- available also when the main menu is hidden.
1305
- """
1306
- # Project
1307
- self.addAction(self.mainMenu.aSaveProject)
1308
- self.addAction(self.mainMenu.aEditItem)
1309
- self.addAction(self.mainMenu.aExitNW)
1310
-
1311
- # Document
1312
- self.addAction(self.mainMenu.aSaveDoc)
1313
- self.addAction(self.mainMenu.aCloseDoc)
1314
-
1315
- # Edit
1316
- self.addAction(self.mainMenu.aEditUndo)
1317
- self.addAction(self.mainMenu.aEditRedo)
1318
- self.addAction(self.mainMenu.aEditCut)
1319
- self.addAction(self.mainMenu.aEditCopy)
1320
- self.addAction(self.mainMenu.aEditPaste)
1321
- self.addAction(self.mainMenu.aSelectAll)
1322
- self.addAction(self.mainMenu.aSelectPar)
1323
-
1324
- # View
1325
- self.addAction(self.mainMenu.aFocusMode)
1326
- self.addAction(self.mainMenu.aFullScreen)
1327
-
1328
- # Insert
1329
- self.addAction(self.mainMenu.aInsENDash)
1330
- self.addAction(self.mainMenu.aInsEMDash)
1331
- self.addAction(self.mainMenu.aInsHorBar)
1332
- self.addAction(self.mainMenu.aInsFigDash)
1333
- self.addAction(self.mainMenu.aInsQuoteLS)
1334
- self.addAction(self.mainMenu.aInsQuoteRS)
1335
- self.addAction(self.mainMenu.aInsQuoteLD)
1336
- self.addAction(self.mainMenu.aInsQuoteRD)
1337
- self.addAction(self.mainMenu.aInsMSApos)
1338
- self.addAction(self.mainMenu.aInsEllipsis)
1339
- self.addAction(self.mainMenu.aInsPrime)
1340
- self.addAction(self.mainMenu.aInsDPrime)
1341
- self.addAction(self.mainMenu.aInsNBSpace)
1342
- self.addAction(self.mainMenu.aInsThinSpace)
1343
- self.addAction(self.mainMenu.aInsThinNBSpace)
1344
- self.addAction(self.mainMenu.aInsBullet)
1345
- self.addAction(self.mainMenu.aInsHyBull)
1346
- self.addAction(self.mainMenu.aInsFlower)
1347
- self.addAction(self.mainMenu.aInsPerMille)
1348
- self.addAction(self.mainMenu.aInsDegree)
1349
- self.addAction(self.mainMenu.aInsMinus)
1350
- self.addAction(self.mainMenu.aInsTimes)
1351
- self.addAction(self.mainMenu.aInsDivide)
1352
- self.addAction(self.mainMenu.aInsSynopsis)
1353
- self.addAction(self.mainMenu.aInsShort)
1354
-
1355
- for mAction, _ in self.mainMenu.mInsKWItems.values():
1356
- self.addAction(mAction)
1357
-
1358
- # Search
1359
- self.addAction(self.mainMenu.aFind)
1360
- self.addAction(self.mainMenu.aReplace)
1361
- self.addAction(self.mainMenu.aFindNext)
1362
- self.addAction(self.mainMenu.aFindPrev)
1363
- self.addAction(self.mainMenu.aReplaceNext)
1364
-
1365
- # Format
1366
- self.addAction(self.mainMenu.aFmtItalic)
1367
- self.addAction(self.mainMenu.aFmtBold)
1368
- self.addAction(self.mainMenu.aFmtStrike)
1369
- self.addAction(self.mainMenu.aFmtDQuote)
1370
- self.addAction(self.mainMenu.aFmtSQuote)
1371
- self.addAction(self.mainMenu.aFmtHead1)
1372
- self.addAction(self.mainMenu.aFmtHead2)
1373
- self.addAction(self.mainMenu.aFmtHead3)
1374
- self.addAction(self.mainMenu.aFmtHead4)
1375
- self.addAction(self.mainMenu.aFmtAlignLeft)
1376
- self.addAction(self.mainMenu.aFmtAlignCentre)
1377
- self.addAction(self.mainMenu.aFmtAlignRight)
1378
- self.addAction(self.mainMenu.aFmtIndentLeft)
1379
- self.addAction(self.mainMenu.aFmtIndentRight)
1380
- self.addAction(self.mainMenu.aFmtComment)
1381
- self.addAction(self.mainMenu.aFmtNoFormat)
1382
-
1383
- # Tools
1384
- self.addAction(self.mainMenu.aSpellCheck)
1385
- self.addAction(self.mainMenu.aReRunSpell)
1386
- self.addAction(self.mainMenu.aPreferences)
1387
-
1388
- # Help
1389
- self.addAction(self.mainMenu.aHelpDocs)
1390
- if isinstance(CONFIG.pdfDocs, Path):
1391
- self.addAction(self.mainMenu.aPdfDocs)
1392
-
1393
- return
1394
-
1395
1311
  def _updateWindowTitle(self, projName: str | None = None) -> None:
1396
1312
  """Set the window title and add the project's name."""
1397
1313
  self.setWindowTitle(" - ".join(filter(None, [projName, CONFIG.appName])))
1398
1314
  return
1399
-
1400
- def _getTagSource(self, tag: str) -> tuple[str | None, str | None]:
1401
- """Handle the index lookup of a tag and display an alert if the
1402
- tag cannot be found.
1403
- """
1404
- tHandle, sTitle = SHARED.project.index.getTagSource(tag)
1405
- if tHandle is None:
1406
- SHARED.error(self.tr(
1407
- "Could not find the reference for tag '{0}'. It either doesn't "
1408
- "exist, or the index is out of date. The index can be updated "
1409
- "from the Tools menu, or by pressing {1}."
1410
- ).format(
1411
- tag, "F9"
1412
- ))
1413
- return None, None
1414
- return tHandle, sTitle
novelwriter/shared.py CHANGED
@@ -7,7 +7,7 @@ Created: 2023-08-10 [2.1rc1] SharedData
7
7
  Created: 2023-08-14 [2.1rc1] _GuiAlert
8
8
 
9
9
  This file is a part of novelWriter
10
- Copyright 2018–2024, Veronica Berglyd Olsen
10
+ Copyright (C) 2023 Veronica Berglyd Olsen and novelWriter contributors
11
11
 
12
12
  This program is free software: you can redistribute it and/or modify
13
13
  it under the terms of the GNU General Public License as published by
@@ -26,20 +26,23 @@ from __future__ import annotations
26
26
 
27
27
  import logging
28
28
 
29
+ from enum import Enum
29
30
  from pathlib import Path
30
31
  from time import time
31
32
  from typing import TYPE_CHECKING, TypeVar
32
33
 
33
- from PyQt5.QtCore import QObject, QRunnable, QThreadPool, QTimer, pyqtSignal
34
- from PyQt5.QtGui import QFont
34
+ from PyQt5.QtCore import QObject, QRunnable, QThreadPool, QTimer, QUrl, pyqtSignal, pyqtSlot
35
+ from PyQt5.QtGui import QDesktopServices, QFont
35
36
  from PyQt5.QtWidgets import QFileDialog, QFontDialog, QMessageBox, QWidget
36
37
 
37
38
  from novelwriter.common import formatFileFilter
38
39
  from novelwriter.constants import nwFiles
39
40
  from novelwriter.core.spellcheck import NWSpellEnchant
41
+ from novelwriter.enum import nwChange, nwItemClass
40
42
 
41
43
  if TYPE_CHECKING: # pragma: no cover
42
44
  from novelwriter.core.project import NWProject
45
+ from novelwriter.core.status import T_StatusKind
43
46
  from novelwriter.gui.theme import GuiTheme
44
47
  from novelwriter.guimain import GuiMain
45
48
 
@@ -55,15 +58,16 @@ class SharedData(QObject):
55
58
  "_idleTime", "_idleRefTime",
56
59
  )
57
60
 
58
- projectStatusChanged = pyqtSignal(bool)
59
- projectStatusMessage = pyqtSignal(str)
60
- spellLanguageChanged = pyqtSignal(str, str)
61
61
  focusModeChanged = pyqtSignal(bool)
62
- indexScannedText = pyqtSignal(str)
62
+ indexAvailable = pyqtSignal()
63
63
  indexChangedTags = pyqtSignal(list, list)
64
64
  indexCleared = pyqtSignal()
65
- indexAvailable = pyqtSignal()
66
65
  mainClockTick = pyqtSignal()
66
+ projectItemChanged = pyqtSignal(str, Enum)
67
+ rootFolderChanged = pyqtSignal(str, Enum)
68
+ projectStatusChanged = pyqtSignal(bool)
69
+ projectStatusMessage = pyqtSignal(str)
70
+ spellLanguageChanged = pyqtSignal(str, str)
67
71
  statusLabelsChanged = pyqtSignal(str)
68
72
 
69
73
  def __init__(self) -> None:
@@ -173,10 +177,12 @@ class SharedData(QObject):
173
177
  logger.debug("Thread Pool Max Count: %d", QThreadPool.globalInstance().maxThreadCount())
174
178
  return
175
179
 
176
- def closeEditor(self, tHandle: str | None = None) -> None:
180
+ def closeDocument(self, tHandle: str | None = None) -> None:
177
181
  """Close the document editor, optionally a specific document."""
178
182
  if tHandle is None or tHandle == self.mainGui.docEditor.docHandle:
179
183
  self.mainGui.closeDocument()
184
+ if tHandle is None or tHandle == self.mainGui.docViewer.docHandle:
185
+ self.mainGui.closeViewerPanel()
180
186
  return
181
187
 
182
188
  def saveEditor(self, tHandle: str | None = None) -> None:
@@ -293,29 +299,61 @@ class SharedData(QObject):
293
299
  return None
294
300
 
295
301
  ##
296
- # Signal Proxy
302
+ # Public Slots
297
303
  ##
298
304
 
299
- def indexSignalProxy(self, data: dict) -> None:
300
- """Emit signals on behalf of the index."""
301
- event = data.get("event")
302
- logger.debug("Received '%s' event from the index", event)
303
- if event == "updateTags":
304
- self.indexChangedTags.emit(data.get("updated", []), data.get("deleted", []))
305
- elif event == "scanText":
306
- self.indexScannedText.emit(data.get("handle", ""))
307
- elif event == "clearIndex":
305
+ @pyqtSlot(str)
306
+ def openWebsite(self, url: str) -> None:
307
+ """Open a URL in the system's default browser."""
308
+ QDesktopServices.openUrl(QUrl(url))
309
+ return
310
+
311
+ @pyqtSlot(str, nwItemClass)
312
+ def createNewNote(self, tag: str, itemClass: nwItemClass) -> None:
313
+ """Process new note request."""
314
+ self.project.createNewNote(tag, itemClass)
315
+ return
316
+
317
+ ##
318
+ # Signal Proxies
319
+ ##
320
+
321
+ def emitIndexChangedTags(
322
+ self, project: NWProject, updated: list[str], deleted: list[str]
323
+ ) -> None:
324
+ """Emit the indexChangedTags signal."""
325
+ if self._project and self._project.data.uuid == project.data.uuid:
326
+ self.indexChangedTags.emit(updated, deleted)
327
+ return
328
+
329
+ def emitIndexCleared(self, project: NWProject) -> None:
330
+ """Emit the indexCleared signal."""
331
+ if self._project and self._project.data.uuid == project.data.uuid:
308
332
  self.indexCleared.emit()
309
- elif event == "buildIndex":
333
+ return
334
+
335
+ def emitIndexAvailable(self, project: NWProject) -> None:
336
+ """Emit the indexAvailable signal."""
337
+ if self._project and self._project.data.uuid == project.data.uuid:
310
338
  self.indexAvailable.emit()
311
339
  return
312
340
 
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", ""))
341
+ def emitStatusLabelsChanged(self, project: NWProject, kind: T_StatusKind) -> None:
342
+ """Emit the statusLabelsChanged signal."""
343
+ if self._project and self._project.data.uuid == project.data.uuid:
344
+ self.statusLabelsChanged.emit(kind)
345
+ return
346
+
347
+ def emitProjectItemChanged(self, project: NWProject, handle: str, change: nwChange) -> None:
348
+ """Emit the projectItemChanged signal."""
349
+ if self._project and self._project.data.uuid == project.data.uuid:
350
+ self.projectItemChanged.emit(handle, change)
351
+ return
352
+
353
+ def emitRootFolderChanged(self, project: NWProject, handle: str, change: nwChange) -> None:
354
+ """Emit the rootFolderChanged signal."""
355
+ if self._project and self._project.data.uuid == project.data.uuid:
356
+ self.rootFolderChanged.emit(handle, change)
319
357
  return
320
358
 
321
359
  ##
@@ -376,6 +414,7 @@ class SharedData(QObject):
376
414
  """Create a new project and spell checking instance."""
377
415
  from novelwriter.core.project import NWProject
378
416
  if isinstance(self._project, NWProject):
417
+ self._project.clear()
379
418
  del self._project
380
419
  del self._spelling
381
420
  self._project = NWProject()
@@ -441,9 +480,9 @@ class _GuiAlert(QMessageBox):
441
480
  Yes/No buttons or just an Ok button.
442
481
  """
443
482
  if isYesNo:
444
- self.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
483
+ self.setStandardButtons(QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
445
484
  else:
446
- self.setStandardButtons(QMessageBox.Ok)
485
+ self.setStandardButtons(QMessageBox.StandardButton.Ok)
447
486
  pSz = 2*self._theme.baseIconHeight
448
487
  if level == self.INFO:
449
488
  self.setIconPixmap(self._theme.getPixmap("alert_info", (pSz, pSz)))