novelWriter 2.5.3__py3-none-any.whl → 2.6b2__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 (83) hide show
  1. {novelWriter-2.5.3.dist-info → novelWriter-2.6b2.dist-info}/METADATA +1 -1
  2. {novelWriter-2.5.3.dist-info → novelWriter-2.6b2.dist-info}/RECORD +80 -60
  3. novelwriter/__init__.py +49 -10
  4. novelwriter/assets/i18n/project_en_GB.json +1 -0
  5. novelwriter/assets/icons/typicons_dark/icons.conf +8 -0
  6. novelwriter/assets/icons/typicons_dark/mixed_copy.svg +4 -0
  7. novelwriter/assets/icons/typicons_dark/mixed_margin-bottom.svg +6 -0
  8. novelwriter/assets/icons/typicons_dark/mixed_margin-left.svg +6 -0
  9. novelwriter/assets/icons/typicons_dark/mixed_margin-right.svg +6 -0
  10. novelwriter/assets/icons/typicons_dark/mixed_margin-top.svg +6 -0
  11. novelwriter/assets/icons/typicons_dark/mixed_size-height.svg +6 -0
  12. novelwriter/assets/icons/typicons_dark/mixed_size-width.svg +6 -0
  13. novelwriter/assets/icons/typicons_dark/nw_toolbar.svg +5 -0
  14. novelwriter/assets/icons/typicons_light/icons.conf +8 -0
  15. novelwriter/assets/icons/typicons_light/mixed_copy.svg +4 -0
  16. novelwriter/assets/icons/typicons_light/mixed_margin-bottom.svg +6 -0
  17. novelwriter/assets/icons/typicons_light/mixed_margin-left.svg +6 -0
  18. novelwriter/assets/icons/typicons_light/mixed_margin-right.svg +6 -0
  19. novelwriter/assets/icons/typicons_light/mixed_margin-top.svg +6 -0
  20. novelwriter/assets/icons/typicons_light/mixed_size-height.svg +6 -0
  21. novelwriter/assets/icons/typicons_light/mixed_size-width.svg +6 -0
  22. novelwriter/assets/icons/typicons_light/nw_toolbar.svg +5 -0
  23. novelwriter/assets/manual.pdf +0 -0
  24. novelwriter/assets/sample.zip +0 -0
  25. novelwriter/common.py +100 -2
  26. novelwriter/config.py +25 -15
  27. novelwriter/constants.py +168 -60
  28. novelwriter/core/buildsettings.py +66 -39
  29. novelwriter/core/coretools.py +145 -147
  30. novelwriter/core/docbuild.py +132 -170
  31. novelwriter/core/index.py +38 -37
  32. novelwriter/core/item.py +41 -8
  33. novelwriter/core/itemmodel.py +518 -0
  34. novelwriter/core/options.py +4 -1
  35. novelwriter/core/project.py +67 -89
  36. novelwriter/core/spellcheck.py +9 -14
  37. novelwriter/core/status.py +7 -5
  38. novelwriter/core/tree.py +268 -287
  39. novelwriter/dialogs/docmerge.py +7 -17
  40. novelwriter/dialogs/preferences.py +46 -33
  41. novelwriter/dialogs/projectsettings.py +5 -5
  42. novelwriter/enum.py +36 -23
  43. novelwriter/extensions/configlayout.py +27 -12
  44. novelwriter/extensions/modified.py +13 -1
  45. novelwriter/extensions/pagedsidebar.py +5 -5
  46. novelwriter/formats/shared.py +155 -0
  47. novelwriter/formats/todocx.py +1191 -0
  48. novelwriter/formats/tohtml.py +451 -0
  49. novelwriter/{core → formats}/tokenizer.py +487 -491
  50. novelwriter/formats/tomarkdown.py +217 -0
  51. novelwriter/{core → formats}/toodt.py +311 -432
  52. novelwriter/formats/toqdoc.py +484 -0
  53. novelwriter/formats/toraw.py +91 -0
  54. novelwriter/gui/doceditor.py +342 -284
  55. novelwriter/gui/dochighlight.py +96 -84
  56. novelwriter/gui/docviewer.py +88 -31
  57. novelwriter/gui/docviewerpanel.py +17 -25
  58. novelwriter/gui/editordocument.py +17 -2
  59. novelwriter/gui/itemdetails.py +25 -28
  60. novelwriter/gui/mainmenu.py +129 -63
  61. novelwriter/gui/noveltree.py +45 -47
  62. novelwriter/gui/outline.py +196 -249
  63. novelwriter/gui/projtree.py +594 -1241
  64. novelwriter/gui/search.py +9 -10
  65. novelwriter/gui/sidebar.py +7 -6
  66. novelwriter/gui/theme.py +10 -5
  67. novelwriter/guimain.py +100 -196
  68. novelwriter/shared.py +66 -27
  69. novelwriter/text/counting.py +2 -0
  70. novelwriter/text/patterns.py +168 -60
  71. novelwriter/tools/manusbuild.py +14 -12
  72. novelwriter/tools/manuscript.py +120 -78
  73. novelwriter/tools/manussettings.py +424 -291
  74. novelwriter/tools/welcome.py +4 -4
  75. novelwriter/tools/writingstats.py +3 -3
  76. novelwriter/types.py +23 -7
  77. novelwriter/core/tohtml.py +0 -530
  78. novelwriter/core/tomarkdown.py +0 -252
  79. novelwriter/core/toqdoc.py +0 -419
  80. {novelWriter-2.5.3.dist-info → novelWriter-2.6b2.dist-info}/LICENSE.md +0 -0
  81. {novelWriter-2.5.3.dist-info → novelWriter-2.6b2.dist-info}/WHEEL +0 -0
  82. {novelWriter-2.5.3.dist-info → novelWriter-2.6b2.dist-info}/entry_points.txt +0 -0
  83. {novelWriter-2.5.3.dist-info → novelWriter-2.6b2.dist-info}/top_level.txt +0 -0
novelwriter/gui/search.py CHANGED
@@ -30,15 +30,18 @@ from time import time
30
30
  from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot
31
31
  from PyQt5.QtGui import QCursor, QKeyEvent
32
32
  from PyQt5.QtWidgets import (
33
- QApplication, QFrame, QHBoxLayout, QHeaderView, QLabel, QLineEdit,
34
- QToolBar, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget
33
+ QApplication, QFrame, QHBoxLayout, QLabel, QLineEdit, QToolBar,
34
+ QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget
35
35
  )
36
36
 
37
37
  from novelwriter import CONFIG, SHARED
38
38
  from novelwriter.common import checkInt, cssCol
39
39
  from novelwriter.core.coretools import DocSearch
40
40
  from novelwriter.core.item import NWItem
41
- from novelwriter.types import QtAlignMiddle, QtAlignRight, QtUserRole
41
+ from novelwriter.types import (
42
+ QtAlignMiddle, QtAlignRight, QtHeaderStretch, QtHeaderToContents,
43
+ QtUserRole
44
+ )
42
45
 
43
46
  logger = logging.getLogger(__name__)
44
47
 
@@ -120,8 +123,8 @@ class GuiProjectSearch(QWidget):
120
123
 
121
124
  treeHeader = self.searchResult.header()
122
125
  treeHeader.setStretchLastSection(False)
123
- treeHeader.setSectionResizeMode(self.C_NAME, QHeaderView.ResizeMode.Stretch)
124
- treeHeader.setSectionResizeMode(self.C_COUNT, QHeaderView.ResizeMode.ResizeToContents)
126
+ treeHeader.setSectionResizeMode(self.C_NAME, QtHeaderStretch)
127
+ treeHeader.setSectionResizeMode(self.C_COUNT, QtHeaderToContents)
125
128
 
126
129
  # Assemble
127
130
  self.headerBox = QHBoxLayout()
@@ -331,15 +334,11 @@ class GuiProjectSearch(QWidget):
331
334
  """Populate the result tree."""
332
335
  if results and nwItem:
333
336
  tHandle = nwItem.itemHandle
334
- docIcon = SHARED.theme.getItemIcon(
335
- nwItem.itemType, nwItem.itemClass,
336
- nwItem.itemLayout, nwItem.mainHeading
337
- )
338
337
  ext = "+" if capped else ""
339
338
 
340
339
  tItem = QTreeWidgetItem()
341
340
  tItem.setText(self.C_NAME, nwItem.itemName)
342
- tItem.setIcon(self.C_NAME, docIcon)
341
+ tItem.setIcon(self.C_NAME, nwItem.getMainIcon())
343
342
  tItem.setData(self.C_NAME, self.D_HANDLE, tHandle)
344
343
  tItem.setText(self.C_COUNT, f"({len(results):n}{ext})")
345
344
  tItem.setTextAlignment(self.C_COUNT, QtAlignRight)
@@ -32,6 +32,7 @@ from PyQt5.QtGui import QPalette
32
32
  from PyQt5.QtWidgets import QMenu, QVBoxLayout, QWidget
33
33
 
34
34
  from novelwriter import CONFIG, SHARED
35
+ from novelwriter.common import qtLambda
35
36
  from novelwriter.enum import nwView
36
37
  from novelwriter.extensions.eventfilters import StatusTipFilter
37
38
  from novelwriter.extensions.modified import NIconToolButton
@@ -54,7 +55,7 @@ class GuiSideBar(QWidget):
54
55
 
55
56
  self.mainGui = mainGui
56
57
 
57
- iPx = int(1.2*SHARED.theme.baseButtonHeight)
58
+ iPx = int(1.25*SHARED.theme.baseButtonHeight)
58
59
  iSz = QSize(iPx, iPx)
59
60
 
60
61
  self.setContentsMargins(0, 0, 0, 0)
@@ -63,19 +64,19 @@ class GuiSideBar(QWidget):
63
64
  # Buttons
64
65
  self.tbProject = NIconToolButton(self, iSz)
65
66
  self.tbProject.setToolTip("{0} [Ctrl+T]".format(self.tr("Project Tree View")))
66
- self.tbProject.clicked.connect(lambda: self.requestViewChange.emit(nwView.PROJECT))
67
+ self.tbProject.clicked.connect(qtLambda(self.requestViewChange.emit, nwView.PROJECT))
67
68
 
68
69
  self.tbNovel = NIconToolButton(self, iSz)
69
70
  self.tbNovel.setToolTip("{0} [Ctrl+T]".format(self.tr("Novel Tree View")))
70
- self.tbNovel.clicked.connect(lambda: self.requestViewChange.emit(nwView.NOVEL))
71
+ self.tbNovel.clicked.connect(qtLambda(self.requestViewChange.emit, nwView.NOVEL))
71
72
 
72
73
  self.tbSearch = NIconToolButton(self, iSz)
73
74
  self.tbSearch.setToolTip("{0} [Ctrl+Shift+F]".format(self.tr("Project Search")))
74
- self.tbSearch.clicked.connect(lambda: self.requestViewChange.emit(nwView.SEARCH))
75
+ self.tbSearch.clicked.connect(qtLambda(self.requestViewChange.emit, nwView.SEARCH))
75
76
 
76
77
  self.tbOutline = NIconToolButton(self, iSz)
77
78
  self.tbOutline.setToolTip("{0} [Ctrl+Shift+T]".format(self.tr("Novel Outline View")))
78
- self.tbOutline.clicked.connect(lambda: self.requestViewChange.emit(nwView.OUTLINE))
79
+ self.tbOutline.clicked.connect(qtLambda(self.requestViewChange.emit, nwView.OUTLINE))
79
80
 
80
81
  self.tbBuild = NIconToolButton(self, iSz)
81
82
  self.tbBuild.setToolTip("{0} [F5]".format(self.tr("Build Manuscript")))
@@ -113,7 +114,7 @@ class GuiSideBar(QWidget):
113
114
  self.outerBox.addWidget(self.tbStats)
114
115
  self.outerBox.addWidget(self.tbSettings)
115
116
  self.outerBox.setContentsMargins(0, 0, 0, 0)
116
- self.outerBox.setSpacing(CONFIG.pxInt(4))
117
+ self.outerBox.setSpacing(CONFIG.pxInt(6))
117
118
 
118
119
  self.setLayout(self.outerBox)
119
120
  self.updateTheme()
novelwriter/gui/theme.py CHANGED
@@ -151,6 +151,9 @@ class GuiTheme:
151
151
  self.guiFont = QApplication.font()
152
152
  self.guiFontB = QApplication.font()
153
153
  self.guiFontB.setBold(True)
154
+ self.guiFontBU = QApplication.font()
155
+ self.guiFontBU.setBold(True)
156
+ self.guiFontBU.setUnderline(True)
154
157
  self.guiFontSmall = QApplication.font()
155
158
  self.guiFontSmall.setPointSizeF(0.9*self.guiFont.pointSizeF())
156
159
 
@@ -500,13 +503,15 @@ class GuiIcons:
500
503
 
501
504
  # Format Icons
502
505
  "fmt_bold", "fmt_bold-md", "fmt_italic", "fmt_italic-md", "fmt_mark", "fmt_strike",
503
- "fmt_strike-md", "fmt_subscript", "fmt_superscript", "fmt_underline",
506
+ "fmt_strike-md", "fmt_subscript", "fmt_superscript", "fmt_underline", "margin_bottom",
507
+ "margin_left", "margin_right", "margin_top", "size_height", "size_width",
504
508
 
505
509
  # General Button Icons
506
- "add", "add_document", "backward", "bookmark", "browse", "checked", "close", "cross",
507
- "document", "down", "edit", "export", "font", "forward", "import", "list", "maximise",
508
- "menu", "minimise", "more", "noncheckable", "open", "panel", "quote", "refresh", "remove",
509
- "revert", "search_replace", "search", "settings", "star", "unchecked", "up", "view",
510
+ "add", "add_document", "backward", "bookmark", "browse", "checked", "close", "copy",
511
+ "cross", "document", "down", "edit", "export", "font", "forward", "import", "list",
512
+ "maximise", "menu", "minimise", "more", "noncheckable", "open", "panel", "quote",
513
+ "refresh", "remove", "revert", "search_replace", "search", "settings", "star", "toolbar",
514
+ "unchecked", "up", "view",
510
515
 
511
516
  # Switches
512
517
  "sticky-on", "sticky-off",
novelwriter/guimain.py CHANGED
@@ -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."""
@@ -1055,13 +1051,8 @@ class GuiMain(QMainWindow):
1055
1051
  self.initMain()
1056
1052
  self.saveDocument()
1057
1053
 
1058
- if restart:
1059
- SHARED.info(self.tr(
1060
- "Some changes will not be applied until novelWriter has been restarted."
1061
- ))
1062
-
1063
1054
  if tree:
1064
- self.projView.populateTree()
1055
+ SHARED.project.tree.refreshAllItems()
1065
1056
 
1066
1057
  if theme:
1067
1058
  # We are doing this manually instead of connecting to
@@ -1087,8 +1078,16 @@ class GuiMain(QMainWindow):
1087
1078
  self.projView.initSettings()
1088
1079
  self.novelView.initSettings()
1089
1080
  self.outlineView.initSettings()
1081
+
1082
+ # Force update of word count
1083
+ self._lastTotalCount = 0
1090
1084
  self._updateStatusWordCount()
1091
1085
 
1086
+ if restart:
1087
+ SHARED.info(self.tr(
1088
+ "Some changes will not be applied until novelWriter has been restarted."
1089
+ ))
1090
+
1092
1091
  return
1093
1092
 
1094
1093
  @pyqtSlot()
@@ -1111,8 +1110,16 @@ class GuiMain(QMainWindow):
1111
1110
  @pyqtSlot(str, nwDocMode)
1112
1111
  def _followTag(self, tag: str, mode: nwDocMode) -> None:
1113
1112
  """Follow a tag after user interaction with a link."""
1114
- tHandle, sTitle = self._getTagSource(tag)
1115
- if tHandle is not None:
1113
+ tHandle, sTitle = SHARED.project.index.getTagSource(tag)
1114
+ if tHandle is None:
1115
+ SHARED.error(self.tr(
1116
+ "Could not find the reference for tag '{0}'. It either doesn't "
1117
+ "exist, or the index is out of date. The index can be updated "
1118
+ "from the Tools menu, or by pressing {1}."
1119
+ ).format(
1120
+ tag, "F9"
1121
+ ))
1122
+ else:
1116
1123
  if mode == nwDocMode.EDIT:
1117
1124
  self.openDocument(tHandle, sTitle=sTitle)
1118
1125
  elif mode == nwDocMode.VIEW:
@@ -1216,8 +1223,10 @@ class GuiMain(QMainWindow):
1216
1223
  self.mainStatus.setUserIdle(editIdle or userIdle)
1217
1224
  SHARED.updateIdleTime(currTime, editIdle or userIdle)
1218
1225
  self.mainStatus.updateTime(idleTime=SHARED.projectIdleTime)
1219
- if CONFIG.memInfo and int(currTime) % 5 == 0: # pragma: no cover
1220
- self.mainStatus.memInfo()
1226
+ if int(currTime) % 5 == 0:
1227
+ self._updateStatusWordCount()
1228
+ if CONFIG.memInfo: # pragma: no cover
1229
+ self.mainStatus.memInfo()
1221
1230
  return
1222
1231
 
1223
1232
  @pyqtSlot()
@@ -1245,15 +1254,19 @@ class GuiMain(QMainWindow):
1245
1254
  if not SHARED.hasProject:
1246
1255
  self.mainStatus.setProjectStats(0, 0)
1247
1256
 
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)
1257
+ currentTotalCount = SHARED.project.currentTotalCount
1258
+ if self._lastTotalCount != currentTotalCount:
1259
+ self._lastTotalCount = currentTotalCount
1260
+
1261
+ SHARED.project.updateWordCounts()
1262
+ if CONFIG.incNotesWCount:
1263
+ iTotal = sum(SHARED.project.data.initCounts)
1264
+ cTotal = sum(SHARED.project.data.currCounts)
1265
+ self.mainStatus.setProjectStats(cTotal, cTotal - iTotal)
1266
+ else:
1267
+ iNovel, _ = SHARED.project.data.initCounts
1268
+ cNovel, _ = SHARED.project.data.currCounts
1269
+ self.mainStatus.setProjectStats(cNovel, cNovel - iNovel)
1257
1270
 
1258
1271
  return
1259
1272
 
@@ -1299,116 +1312,7 @@ class GuiMain(QMainWindow):
1299
1312
  # Internal Functions
1300
1313
  ##
1301
1314
 
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
1315
  def _updateWindowTitle(self, projName: str | None = None) -> None:
1396
1316
  """Set the window title and add the project's name."""
1397
1317
  self.setWindowTitle(" - ".join(filter(None, [projName, CONFIG.appName])))
1398
1318
  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