novelWriter 2.3rc1__py3-none-any.whl → 2.4__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 (125) hide show
  1. {novelWriter-2.3rc1.dist-info → novelWriter-2.4.dist-info}/METADATA +5 -6
  2. {novelWriter-2.3rc1.dist-info → novelWriter-2.4.dist-info}/RECORD +119 -109
  3. {novelWriter-2.3rc1.dist-info → novelWriter-2.4.dist-info}/WHEEL +1 -1
  4. novelWriter-2.4.dist-info/entry_points.txt +2 -0
  5. novelwriter/__init__.py +17 -10
  6. novelwriter/assets/i18n/nw_de_DE.qm +0 -0
  7. novelwriter/assets/i18n/nw_en_US.qm +0 -0
  8. novelwriter/assets/i18n/nw_es_419.qm +0 -0
  9. novelwriter/assets/i18n/nw_fr_FR.qm +0 -0
  10. novelwriter/assets/i18n/nw_it_IT.qm +0 -0
  11. novelwriter/assets/i18n/nw_ja_JP.qm +0 -0
  12. novelwriter/assets/i18n/nw_nb_NO.qm +0 -0
  13. novelwriter/assets/i18n/nw_nl_NL.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_nl_NL.json +11 -0
  17. novelwriter/assets/i18n/project_pt_BR.json +11 -0
  18. novelwriter/assets/icons/none.svg +4 -0
  19. novelwriter/assets/icons/typicons_dark/icons.conf +4 -0
  20. novelwriter/assets/icons/typicons_dark/nw_tb-mark.svg +7 -0
  21. novelwriter/assets/icons/typicons_dark/typ_refresh-flipped.svg +1 -1
  22. novelwriter/assets/icons/typicons_dark/typ_refresh.svg +1 -1
  23. novelwriter/assets/icons/typicons_dark/typ_search-grey.svg +4 -0
  24. novelwriter/assets/icons/typicons_dark/typ_times.svg +1 -1
  25. novelwriter/assets/icons/typicons_dark/typ_unfold-hidden.svg +4 -0
  26. novelwriter/assets/icons/typicons_dark/typ_unfold-visible.svg +4 -0
  27. novelwriter/assets/icons/typicons_light/icons.conf +4 -0
  28. novelwriter/assets/icons/typicons_light/nw_tb-mark.svg +7 -0
  29. novelwriter/assets/icons/typicons_light/typ_refresh-flipped.svg +1 -1
  30. novelwriter/assets/icons/typicons_light/typ_refresh.svg +1 -1
  31. novelwriter/assets/icons/typicons_light/typ_search-grey.svg +4 -0
  32. novelwriter/assets/icons/typicons_light/typ_times.svg +1 -1
  33. novelwriter/assets/icons/typicons_light/typ_unfold-hidden.svg +4 -0
  34. novelwriter/assets/icons/typicons_light/typ_unfold-visible.svg +4 -0
  35. novelwriter/assets/manual.pdf +0 -0
  36. novelwriter/assets/sample.zip +0 -0
  37. novelwriter/assets/syntax/cyberpunk_night.conf +26 -0
  38. novelwriter/assets/syntax/default_dark.conf +1 -0
  39. novelwriter/assets/syntax/default_light.conf +1 -0
  40. novelwriter/assets/syntax/grey_dark.conf +1 -0
  41. novelwriter/assets/syntax/grey_light.conf +1 -0
  42. novelwriter/assets/syntax/light_owl.conf +1 -0
  43. novelwriter/assets/syntax/night_owl.conf +1 -0
  44. novelwriter/assets/syntax/solarized_dark.conf +1 -0
  45. novelwriter/assets/syntax/solarized_light.conf +1 -0
  46. novelwriter/assets/syntax/tango.conf +23 -0
  47. novelwriter/assets/syntax/tomorrow.conf +1 -0
  48. novelwriter/assets/syntax/tomorrow_night.conf +1 -0
  49. novelwriter/assets/syntax/tomorrow_night_blue.conf +1 -0
  50. novelwriter/assets/syntax/tomorrow_night_bright.conf +1 -0
  51. novelwriter/assets/syntax/tomorrow_night_eighties.conf +1 -0
  52. novelwriter/assets/text/credits_en.htm +25 -23
  53. novelwriter/assets/themes/cyberpunk_night.conf +29 -0
  54. novelwriter/common.py +12 -4
  55. novelwriter/config.py +47 -16
  56. novelwriter/constants.py +5 -6
  57. novelwriter/core/buildsettings.py +64 -44
  58. novelwriter/core/coretools.py +97 -13
  59. novelwriter/core/docbuild.py +74 -7
  60. novelwriter/core/document.py +24 -3
  61. novelwriter/core/index.py +31 -112
  62. novelwriter/core/project.py +11 -15
  63. novelwriter/core/projectxml.py +3 -2
  64. novelwriter/core/sessions.py +2 -2
  65. novelwriter/core/spellcheck.py +3 -3
  66. novelwriter/core/status.py +6 -5
  67. novelwriter/core/storage.py +16 -6
  68. novelwriter/core/tohtml.py +22 -25
  69. novelwriter/core/tokenizer.py +417 -237
  70. novelwriter/core/tomd.py +17 -8
  71. novelwriter/core/toodt.py +386 -351
  72. novelwriter/core/tree.py +8 -8
  73. novelwriter/dialogs/about.py +10 -12
  74. novelwriter/dialogs/docmerge.py +17 -14
  75. novelwriter/dialogs/docsplit.py +20 -19
  76. novelwriter/dialogs/editlabel.py +5 -4
  77. novelwriter/dialogs/preferences.py +32 -40
  78. novelwriter/dialogs/projectsettings.py +31 -28
  79. novelwriter/dialogs/quotes.py +10 -9
  80. novelwriter/dialogs/wordlist.py +18 -15
  81. novelwriter/enum.py +17 -14
  82. novelwriter/error.py +14 -12
  83. novelwriter/extensions/circularprogress.py +12 -8
  84. novelwriter/extensions/configlayout.py +23 -3
  85. novelwriter/extensions/modified.py +51 -2
  86. novelwriter/extensions/pagedsidebar.py +16 -14
  87. novelwriter/extensions/simpleprogress.py +3 -1
  88. novelwriter/extensions/statusled.py +3 -1
  89. novelwriter/extensions/switch.py +10 -9
  90. novelwriter/extensions/switchbox.py +14 -13
  91. novelwriter/extensions/versioninfo.py +1 -1
  92. novelwriter/gui/doceditor.py +433 -496
  93. novelwriter/gui/dochighlight.py +54 -33
  94. novelwriter/gui/docviewer.py +162 -175
  95. novelwriter/gui/docviewerpanel.py +20 -37
  96. novelwriter/gui/editordocument.py +15 -4
  97. novelwriter/gui/itemdetails.py +51 -54
  98. novelwriter/gui/mainmenu.py +37 -17
  99. novelwriter/gui/noveltree.py +31 -37
  100. novelwriter/gui/outline.py +120 -98
  101. novelwriter/gui/projtree.py +114 -112
  102. novelwriter/gui/search.py +362 -0
  103. novelwriter/gui/sidebar.py +36 -45
  104. novelwriter/gui/statusbar.py +14 -14
  105. novelwriter/gui/theme.py +116 -34
  106. novelwriter/guimain.py +216 -207
  107. novelwriter/shared.py +31 -6
  108. novelwriter/text/counting.py +138 -0
  109. novelwriter/tools/dictionaries.py +15 -14
  110. novelwriter/tools/lipsum.py +20 -17
  111. novelwriter/tools/manusbuild.py +43 -35
  112. novelwriter/tools/manuscript.py +381 -104
  113. novelwriter/tools/manussettings.py +263 -125
  114. novelwriter/tools/noveldetails.py +21 -19
  115. novelwriter/tools/welcome.py +59 -57
  116. novelwriter/tools/writingstats.py +61 -55
  117. novelwriter/types.py +90 -0
  118. novelWriter-2.3rc1.dist-info/entry_points.txt +0 -5
  119. novelwriter/core/__init__.py +0 -3
  120. novelwriter/dialogs/__init__.py +0 -3
  121. novelwriter/extensions/__init__.py +0 -3
  122. novelwriter/gui/__init__.py +0 -3
  123. novelwriter/tools/__init__.py +0 -3
  124. {novelWriter-2.3rc1.dist-info → novelWriter-2.4.dist-info}/LICENSE.md +0 -0
  125. {novelWriter-2.3rc1.dist-info → novelWriter-2.4.dist-info}/top_level.txt +0 -0
novelwriter/guimain.py CHANGED
@@ -30,41 +30,39 @@ from time import time
30
30
  from pathlib import Path
31
31
  from datetime import datetime
32
32
 
33
- from PyQt5.QtGui import QCloseEvent, QCursor, QIcon
34
33
  from PyQt5.QtCore import Qt, QTimer, pyqtSlot
34
+ from PyQt5.QtGui import QCloseEvent, QCursor, QIcon
35
35
  from PyQt5.QtWidgets import (
36
- QFileDialog, QHBoxLayout, QMainWindow, QMessageBox, QShortcut, QSplitter,
37
- QStackedWidget, QVBoxLayout, QWidget, qApp
36
+ QApplication, QFileDialog, QHBoxLayout, QMainWindow, QMessageBox, QShortcut, QSplitter,
37
+ QStackedWidget, QVBoxLayout, QWidget
38
38
  )
39
39
 
40
40
  from novelwriter import CONFIG, SHARED, __hexversion__, __version__
41
+ from novelwriter.common import formatFileFilter, formatVersion, hexToInt
41
42
  from novelwriter.constants import nwConst
42
- from novelwriter.gui.theme import GuiTheme
43
- from novelwriter.gui.sidebar import GuiSideBar
44
- from novelwriter.gui.outline import GuiOutlineView
45
- from novelwriter.gui.mainmenu import GuiMainMenu
46
- from novelwriter.gui.projtree import GuiProjectView
43
+ from novelwriter.dialogs.about import GuiAbout
44
+ from novelwriter.dialogs.preferences import GuiPreferences
45
+ from novelwriter.dialogs.projectsettings import GuiProjectSettings
46
+ from novelwriter.dialogs.wordlist import GuiWordList
47
+ from novelwriter.enum import nwDocAction, nwDocInsert, nwDocMode, nwItemType, nwWidget, nwView
47
48
  from novelwriter.gui.doceditor import GuiDocEditor
48
49
  from novelwriter.gui.docviewer import GuiDocViewer
50
+ from novelwriter.gui.docviewerpanel import GuiDocViewerPanel
51
+ from novelwriter.gui.itemdetails import GuiItemDetails
52
+ from novelwriter.gui.mainmenu import GuiMainMenu
49
53
  from novelwriter.gui.noveltree import GuiNovelView
54
+ from novelwriter.gui.outline import GuiOutlineView
55
+ from novelwriter.gui.projtree import GuiProjectView
56
+ from novelwriter.gui.search import GuiProjectSearch
57
+ from novelwriter.gui.sidebar import GuiSideBar
50
58
  from novelwriter.gui.statusbar import GuiMainStatus
51
- from novelwriter.gui.itemdetails import GuiItemDetails
52
- from novelwriter.gui.docviewerpanel import GuiDocViewerPanel
53
- from novelwriter.dialogs.about import GuiAbout
54
- from novelwriter.dialogs.wordlist import GuiWordList
55
- from novelwriter.dialogs.preferences import GuiPreferences
56
- from novelwriter.dialogs.projectsettings import GuiProjectSettings
57
- from novelwriter.tools.welcome import GuiWelcome
58
- from novelwriter.tools.manuscript import GuiManuscript
59
+ from novelwriter.gui.theme import GuiTheme
59
60
  from novelwriter.tools.dictionaries import GuiDictionaries
61
+ from novelwriter.tools.manuscript import GuiManuscript
60
62
  from novelwriter.tools.noveldetails import GuiNovelDetails
63
+ from novelwriter.tools.welcome import GuiWelcome
61
64
  from novelwriter.tools.writingstats import GuiWritingStats
62
65
 
63
- from novelwriter.enum import (
64
- nwDocAction, nwDocInsert, nwDocMode, nwItemType, nwWidget, nwView
65
- )
66
- from novelwriter.common import formatFileFilter, formatVersion, hexToInt
67
-
68
66
  logger = logging.getLogger(__name__)
69
67
 
70
68
 
@@ -104,9 +102,6 @@ class GuiMain(QMainWindow):
104
102
  # Initialise UserData Instance
105
103
  SHARED.initSharedData(self, GuiTheme())
106
104
 
107
- # Core Settings
108
- self.isFocusMode = False
109
-
110
105
  # Prepare Main Window
111
106
  self.resize(*CONFIG.mainWinSize)
112
107
  self._updateWindowTitle()
@@ -114,7 +109,7 @@ class GuiMain(QMainWindow):
114
109
  nwIcon = CONFIG.assetPath("icons") / "novelwriter.svg"
115
110
  self.nwIcon = QIcon(str(nwIcon)) if nwIcon.is_file() else QIcon()
116
111
  self.setWindowIcon(self.nwIcon)
117
- qApp.setWindowIcon(self.nwIcon)
112
+ QApplication.setWindowIcon(self.nwIcon)
118
113
 
119
114
  # Build the GUI
120
115
  # =============
@@ -126,6 +121,7 @@ class GuiMain(QMainWindow):
126
121
  # Main GUI Elements
127
122
  self.mainStatus = GuiMainStatus(self)
128
123
  self.projView = GuiProjectView(self)
124
+ self.projSearch = GuiProjectSearch(self)
129
125
  self.novelView = GuiNovelView(self)
130
126
  self.docEditor = GuiDocEditor(self)
131
127
  self.docViewer = GuiDocViewer(self)
@@ -139,6 +135,7 @@ class GuiMain(QMainWindow):
139
135
  self.projStack = QStackedWidget(self)
140
136
  self.projStack.addWidget(self.projView)
141
137
  self.projStack.addWidget(self.novelView)
138
+ self.projStack.addWidget(self.projSearch)
142
139
  self.projStack.currentChanged.connect(self._projStackChanged)
143
140
 
144
141
  # Project Tree View
@@ -151,28 +148,36 @@ class GuiMain(QMainWindow):
151
148
  self.treePane.setLayout(self.treeBox)
152
149
 
153
150
  # Splitter : Document Viewer / Document Meta
154
- self.splitView = QSplitter(Qt.Vertical, self)
151
+ self.splitView = QSplitter(Qt.Orientation.Vertical, self)
155
152
  self.splitView.addWidget(self.docViewer)
156
153
  self.splitView.addWidget(self.docViewerPanel)
157
154
  self.splitView.setHandleWidth(hWd)
158
155
  self.splitView.setOpaqueResize(False)
159
156
  self.splitView.setSizes(CONFIG.viewPanePos)
157
+ self.splitView.setCollapsible(0, False)
158
+ self.splitView.setCollapsible(1, False)
160
159
 
161
160
  # Splitter : Document Editor / Document Viewer
162
- self.splitDocs = QSplitter(Qt.Horizontal, self)
161
+ self.splitDocs = QSplitter(Qt.Orientation.Horizontal, self)
163
162
  self.splitDocs.addWidget(self.docEditor)
164
163
  self.splitDocs.addWidget(self.splitView)
165
164
  self.splitDocs.setOpaqueResize(False)
166
165
  self.splitDocs.setHandleWidth(hWd)
166
+ self.splitDocs.setCollapsible(0, False)
167
+ self.splitDocs.setCollapsible(1, False)
167
168
 
168
169
  # Splitter : Project Tree / Document Area
169
- self.splitMain = QSplitter(Qt.Horizontal)
170
+ self.splitMain = QSplitter(Qt.Orientation.Horizontal)
170
171
  self.splitMain.setContentsMargins(0, 0, 0, 0)
171
172
  self.splitMain.addWidget(self.treePane)
172
173
  self.splitMain.addWidget(self.splitDocs)
173
174
  self.splitMain.setOpaqueResize(False)
174
175
  self.splitMain.setHandleWidth(hWd)
175
176
  self.splitMain.setSizes(CONFIG.mainPanePos)
177
+ self.splitMain.setCollapsible(0, False)
178
+ self.splitMain.setCollapsible(0, False)
179
+ self.splitMain.setStretchFactor(1, 0)
180
+ self.splitMain.setStretchFactor(1, 1)
176
181
 
177
182
  # Main Stack : Editor / Outline
178
183
  self.mainStack = QStackedWidget(self)
@@ -180,31 +185,6 @@ class GuiMain(QMainWindow):
180
185
  self.mainStack.addWidget(self.outlineView)
181
186
  self.mainStack.currentChanged.connect(self._mainStackChanged)
182
187
 
183
- # Indices of Splitter Widgets
184
- self.idxTree = self.splitMain.indexOf(self.treePane)
185
- self.idxMain = self.splitMain.indexOf(self.splitDocs)
186
- self.idxEditor = self.splitDocs.indexOf(self.docEditor)
187
- self.idxViewer = self.splitDocs.indexOf(self.splitView)
188
- self.idxViewDoc = self.splitView.indexOf(self.docViewer)
189
- self.idxViewDocPanel = self.splitView.indexOf(self.docViewerPanel)
190
-
191
- # Indices of Stack Widgets
192
- self.idxEditorView = self.mainStack.indexOf(self.splitMain)
193
- self.idxOutlineView = self.mainStack.indexOf(self.outlineView)
194
- self.idxProjView = self.projStack.indexOf(self.projView)
195
- self.idxNovelView = self.projStack.indexOf(self.novelView)
196
-
197
- # Splitter Behaviour
198
- self.splitMain.setCollapsible(self.idxTree, False)
199
- self.splitMain.setCollapsible(self.idxMain, False)
200
- self.splitDocs.setCollapsible(self.idxEditor, False)
201
- self.splitDocs.setCollapsible(self.idxViewer, False)
202
- self.splitView.setCollapsible(self.idxViewDoc, False)
203
- self.splitView.setCollapsible(self.idxViewDocPanel, False)
204
-
205
- self.splitMain.setStretchFactor(self.idxTree, 0)
206
- self.splitMain.setStretchFactor(self.idxMain, 1)
207
-
208
188
  # Editor / Viewer Default State
209
189
  self.splitView.setVisible(False)
210
190
  self.docEditor.closeSearch()
@@ -225,7 +205,7 @@ class GuiMain(QMainWindow):
225
205
  self.setMenuBar(self.mainMenu)
226
206
  self.setCentralWidget(self.mainWidget)
227
207
  self.setStatusBar(self.mainStatus)
228
- self.setContextMenuPolicy(Qt.NoContextMenu) # Issue #1147
208
+ self.setContextMenuPolicy(Qt.ContextMenuPolicy.NoContextMenu) # Issue #1147
229
209
 
230
210
  # Connect Signals
231
211
  # ===============
@@ -233,20 +213,23 @@ class GuiMain(QMainWindow):
233
213
  SHARED.projectStatusChanged.connect(self.mainStatus.updateProjectStatus)
234
214
  SHARED.projectStatusMessage.connect(self.mainStatus.setStatusMessage)
235
215
  SHARED.spellLanguageChanged.connect(self.mainStatus.setLanguage)
216
+ SHARED.focusModeChanged.connect(self._focusModeChanged)
236
217
  SHARED.indexChangedTags.connect(self.docViewerPanel.updateChangedTags)
237
218
  SHARED.indexScannedText.connect(self.docViewerPanel.projectItemChanged)
238
219
  SHARED.indexScannedText.connect(self.projView.updateItemValues)
239
220
  SHARED.indexScannedText.connect(self.itemDetails.updateViewBox)
240
221
  SHARED.indexCleared.connect(self.docViewerPanel.indexWasCleared)
241
222
  SHARED.indexAvailable.connect(self.docViewerPanel.indexHasAppeared)
223
+ SHARED.mainClockTick.connect(self._timeTick)
242
224
 
243
225
  self.mainMenu.requestDocAction.connect(self._passDocumentAction)
244
226
  self.mainMenu.requestDocInsert.connect(self._passDocumentInsert)
245
227
  self.mainMenu.requestDocInsertText.connect(self._passDocumentInsert)
246
228
  self.mainMenu.requestDocKeyWordInsert.connect(self.docEditor.insertKeyWord)
247
229
  self.mainMenu.requestFocusChange.connect(self.switchFocus)
230
+ self.mainMenu.requestViewChange.connect(self._changeView)
248
231
 
249
- self.sideBar.viewChangeRequested.connect(self._changeView)
232
+ self.sideBar.requestViewChange.connect(self._changeView)
250
233
 
251
234
  self.projView.selectedItemChanged.connect(self.itemDetails.updateViewBox)
252
235
  self.projView.openDocumentRequest.connect(self._openDocument)
@@ -263,6 +246,9 @@ class GuiMain(QMainWindow):
263
246
  self.novelView.selectedItemChanged.connect(self.itemDetails.updateViewBox)
264
247
  self.novelView.openDocumentRequest.connect(self._openDocument)
265
248
 
249
+ self.projSearch.openDocumentSelectRequest.connect(self._openDocumentSelection)
250
+ self.projSearch.selectedItemChanged.connect(self.itemDetails.updateViewBox)
251
+
266
252
  self.docEditor.editedStatusChanged.connect(self.mainStatus.updateDocumentStatus)
267
253
  self.docEditor.docCountsChanged.connect(self.itemDetails.updateCounts)
268
254
  self.docEditor.docCountsChanged.connect(self.projView.updateCounts)
@@ -275,9 +261,13 @@ class GuiMain(QMainWindow):
275
261
  self.docEditor.toggleFocusModeRequest.connect(self.toggleFocusMode)
276
262
  self.docEditor.requestProjectItemSelected.connect(self.projView.setSelectedHandle)
277
263
  self.docEditor.requestProjectItemRenamed.connect(self.projView.renameTreeItem)
264
+ self.docEditor.requestNewNoteCreation.connect(self.projView.createNewNote)
265
+ self.docEditor.docTextChanged.connect(self.projSearch.textChanged)
278
266
 
279
267
  self.docViewer.documentLoaded.connect(self.docViewerPanel.updateHandle)
280
268
  self.docViewer.loadDocumentTagRequest.connect(self._followTag)
269
+ self.docViewer.closeDocumentRequest.connect(self.closeDocViewer)
270
+ self.docViewer.reloadDocumentRequest.connect(self._reloadViewer)
281
271
  self.docViewer.togglePanelVisibility.connect(self._toggleViewerPanelVisibility)
282
272
  self.docViewer.requestProjectItemSelected.connect(self.projView.setSelectedHandle)
283
273
 
@@ -298,12 +288,6 @@ class GuiMain(QMainWindow):
298
288
  self.asDocTimer = QTimer(self)
299
289
  self.asDocTimer.timeout.connect(self._autoSaveDocument)
300
290
 
301
- # Main Clock
302
- self.mainTimer = QTimer(self)
303
- self.mainTimer.setInterval(1000)
304
- self.mainTimer.timeout.connect(self._timeTick)
305
- self.mainTimer.start()
306
-
307
291
  # Shortcuts and Actions
308
292
  self._connectMenuActions()
309
293
 
@@ -344,7 +328,7 @@ class GuiMain(QMainWindow):
344
328
  def postLaunchTasks(self, cmdOpen: str | None) -> None:
345
329
  """Process tasks after the main window has been created."""
346
330
  if cmdOpen:
347
- qApp.processEvents()
331
+ QApplication.processEvents()
348
332
  logger.info("Command line path: %s", cmdOpen)
349
333
  self.openProject(cmdOpen)
350
334
 
@@ -355,8 +339,8 @@ class GuiMain(QMainWindow):
355
339
  if hexToInt(CONFIG.lastNotes) < hexToInt(__hexversion__):
356
340
  CONFIG.lastNotes = __hexversion__
357
341
  trVersion = self.tr(
358
- "You are now running novelWriter version {0}.".format(formatVersion(__version__))
359
- )
342
+ "You are now running novelWriter version {0}."
343
+ ).format(formatVersion(__version__))
360
344
  trRelease = self.tr(
361
345
  "Please check the {0}release notes{1} for further details."
362
346
  ).format(f"<a href='{nwConst.URL_RELEASES}'>", "</a>")
@@ -401,12 +385,13 @@ class GuiMain(QMainWindow):
401
385
  if saveOK:
402
386
  self.closeDocument()
403
387
  self.docViewer.clearNavHistory()
404
- self.closeDocViewer(byUser=False)
388
+ self.closeViewerPanel(byUser=False)
405
389
 
406
390
  self.docViewerPanel.closeProjectTasks()
407
391
  self.outlineView.closeProjectTasks()
408
392
  self.novelView.closeProjectTasks()
409
393
  self.projView.closeProjectTasks()
394
+ self.projSearch.closeProjectTasks()
410
395
  self.itemDetails.clearDetails()
411
396
  self.mainStatus.clearStatus()
412
397
 
@@ -457,7 +442,7 @@ class GuiMain(QMainWindow):
457
442
  "'{0}' ({1} {2}), last active on {3}."
458
443
  ).format(
459
444
  lockStatus[0], lockStatus[1], lockStatus[2],
460
- datetime.fromtimestamp(int(lockStatus[3])).strftime("%x %X")
445
+ CONFIG.localDateTime(datetime.fromtimestamp(int(lockStatus[3])))
461
446
  )
462
447
  except Exception:
463
448
  lockDetails = ""
@@ -489,12 +474,12 @@ class GuiMain(QMainWindow):
489
474
  break
490
475
 
491
476
  if lastEdited is not None:
492
- qApp.processEvents()
477
+ QApplication.processEvents()
493
478
  self.openDocument(lastEdited, doScroll=True)
494
479
 
495
480
  lastViewed = SHARED.project.data.getLastHandle("viewer")
496
481
  if lastViewed is not None:
497
- qApp.processEvents()
482
+ QApplication.processEvents()
498
483
  self.viewDocument(lastViewed)
499
484
 
500
485
  # Check if we need to rebuild the index
@@ -503,7 +488,7 @@ class GuiMain(QMainWindow):
503
488
  self.rebuildIndex()
504
489
 
505
490
  # Make sure the changed status is set to false on things opened
506
- qApp.processEvents()
491
+ QApplication.processEvents()
507
492
  self.docEditor.setDocumentChanged(False)
508
493
  SHARED.project.setProjectChanged(False)
509
494
 
@@ -523,24 +508,19 @@ class GuiMain(QMainWindow):
523
508
  # Document Actions
524
509
  ##
525
510
 
526
- def closeDocument(self, beforeOpen: bool = False) -> bool:
511
+ def closeDocument(self, beforeOpen: bool = False) -> None:
527
512
  """Close the document and clear the editor and title field."""
528
- if not SHARED.hasProject:
529
- logger.error("No project open")
530
- return False
531
-
532
- # Disable focus mode if it is active
533
- if self.isFocusMode:
534
- self.toggleFocusMode()
535
-
536
- self.docEditor.saveCursorPosition()
537
- if self.docEditor.docChanged:
538
- self.saveDocument()
539
- self.docEditor.clearEditor()
540
- if not beforeOpen:
541
- self.novelView.setActiveHandle(None)
542
-
543
- return True
513
+ if SHARED.hasProject:
514
+ # Disable focus mode if it is active
515
+ if SHARED.focusMode:
516
+ SHARED.setFocusMode(False)
517
+ self.docEditor.saveCursorPosition()
518
+ if self.docEditor.docChanged:
519
+ self.saveDocument()
520
+ self.docEditor.clearEditor()
521
+ if not beforeOpen:
522
+ self.novelView.setActiveHandle(None)
523
+ return
544
524
 
545
525
  def openDocument(self, tHandle: str | None, tLine: int | None = None,
546
526
  changeFocus: bool = True, doScroll: bool = False) -> bool:
@@ -604,13 +584,12 @@ class GuiMain(QMainWindow):
604
584
 
605
585
  return False
606
586
 
607
- def saveDocument(self) -> bool:
587
+ @pyqtSlot()
588
+ def saveDocument(self) -> None:
608
589
  """Save the current documents."""
609
- if not SHARED.hasProject:
610
- logger.error("No project open")
611
- return False
612
- self.docEditor.saveText()
613
- return True
590
+ if SHARED.hasProject:
591
+ self.docEditor.saveText()
592
+ return
614
593
 
615
594
  def viewDocument(self, tHandle: str | None = None, sTitle: str | None = None) -> bool:
616
595
  """Load a document for viewing in the view panel."""
@@ -640,7 +619,8 @@ class GuiMain(QMainWindow):
640
619
  self._changeView(nwView.EDITOR)
641
620
 
642
621
  logger.debug("Viewing document with handle '%s'", tHandle)
643
- if self.docViewer.loadText(tHandle):
622
+ updateHistory = tHandle != self.docViewer.docHandle
623
+ if self.docViewer.loadText(tHandle, updateHistory=updateHistory):
644
624
  if not self.splitView.isVisible():
645
625
  cursorVisible = self.docEditor.cursorIsVisible()
646
626
  bPos = self.splitMain.sizes()
@@ -657,7 +637,7 @@ class GuiMain(QMainWindow):
657
637
  self.docEditor.ensureCursorVisibleNoCentre()
658
638
 
659
639
  if sTitle:
660
- self.docViewer.navigateTo(f"#{sTitle}")
640
+ self.docViewer.navigateTo(f"#{tHandle}:{sTitle}")
661
641
 
662
642
  return True
663
643
 
@@ -713,80 +693,71 @@ class GuiMain(QMainWindow):
713
693
  # Tree Item Actions
714
694
  ##
715
695
 
716
- def openSelectedItem(self) -> bool:
696
+ @pyqtSlot()
697
+ def openSelectedItem(self) -> None:
717
698
  """Open the selected item from the tree that is currently
718
699
  active. It is not checked that the item is actually a document.
719
700
  That should be handled by the openDocument function.
720
701
  """
721
- if not SHARED.hasProject:
722
- logger.error("No project open")
723
- return False
724
-
725
- tHandle = None
726
- sTitle = None
727
- tLine = None
728
- if self.projView.treeHasFocus():
729
- tHandle = self.projView.getSelectedHandle()
730
- elif self.novelView.treeHasFocus():
731
- tHandle, sTitle = self.novelView.getSelectedHandle()
732
- elif self.outlineView.treeHasFocus():
733
- tHandle, sTitle = self.outlineView.getSelectedHandle()
734
- else:
735
- logger.warning("No item selected")
736
- return False
737
-
738
- if tHandle is not None and sTitle is not None:
739
- hItem = SHARED.project.index.getItemHeader(tHandle, sTitle)
740
- if hItem is not None:
741
- tLine = hItem.line
702
+ if SHARED.hasProject:
703
+ tHandle = None
704
+ sTitle = None
705
+ tLine = None
706
+ if self.projView.treeHasFocus():
707
+ tHandle = self.projView.getSelectedHandle()
708
+ elif self.novelView.treeHasFocus():
709
+ tHandle, sTitle = self.novelView.getSelectedHandle()
710
+ elif self.outlineView.treeHasFocus():
711
+ tHandle, sTitle = self.outlineView.getSelectedHandle()
712
+ else:
713
+ logger.warning("No item selected")
714
+ return
742
715
 
743
- if tHandle is not None:
744
- self.openDocument(tHandle, tLine=tLine, changeFocus=False, doScroll=False)
716
+ if tHandle and sTitle:
717
+ if hItem := SHARED.project.index.getItemHeading(tHandle, sTitle):
718
+ tLine = hItem.line
719
+ if tHandle:
720
+ self.openDocument(tHandle, tLine=tLine, changeFocus=False, doScroll=False)
745
721
 
746
- return True
722
+ return
747
723
 
748
- def editItemLabel(self, tHandle: str | None = None) -> bool:
724
+ def editItemLabel(self, tHandle: str | None = None) -> None:
749
725
  """Open the edit item dialog."""
750
- if not SHARED.hasProject:
751
- logger.error("No project open")
752
- return False
753
- if tHandle is None and (self.docEditor.anyFocus() or self.isFocusMode):
754
- tHandle = self.docEditor.docHandle
755
- self.projView.renameTreeItem(tHandle)
756
- return True
726
+ if SHARED.hasProject:
727
+ if tHandle is None and (self.docEditor.anyFocus() or SHARED.focusMode):
728
+ tHandle = self.docEditor.docHandle
729
+ self.projView.renameTreeItem(tHandle)
730
+ return
757
731
 
758
732
  def rebuildTrees(self) -> None:
759
733
  """Rebuild the project tree."""
760
734
  self.projView.populateTree()
761
735
  return
762
736
 
763
- def rebuildIndex(self, beQuiet: bool = False) -> bool:
737
+ def rebuildIndex(self, beQuiet: bool = False) -> None:
764
738
  """Rebuild the entire index."""
765
- if not SHARED.hasProject:
766
- logger.error("No project open")
767
- return False
768
-
769
- logger.info("Rebuilding index ...")
770
- qApp.setOverrideCursor(QCursor(Qt.WaitCursor))
771
- tStart = time()
739
+ if SHARED.hasProject:
740
+ logger.info("Rebuilding index ...")
741
+ QApplication.setOverrideCursor(QCursor(Qt.CursorShape.WaitCursor))
742
+ tStart = time()
772
743
 
773
- self.projView.saveProjectTasks()
774
- SHARED.project.index.rebuildIndex()
775
- self.projView.populateTree()
776
- self.novelView.refreshTree()
744
+ self.projView.saveProjectTasks()
745
+ SHARED.project.index.rebuildIndex()
746
+ self.projView.populateTree()
747
+ self.novelView.refreshTree()
777
748
 
778
- tEnd = time()
779
- self.mainStatus.setStatusMessage(
780
- self.tr("Indexing completed in {0} ms").format(f"{(tEnd - tStart)*1000.0:.1f}")
781
- )
782
- self.docEditor.updateTagHighLighting()
783
- self._updateStatusWordCount()
784
- qApp.restoreOverrideCursor()
749
+ tEnd = time()
750
+ self.mainStatus.setStatusMessage(
751
+ self.tr("Indexing completed in {0} ms").format(f"{(tEnd - tStart)*1000.0:.1f}")
752
+ )
753
+ self.docEditor.updateTagHighLighting()
754
+ self._updateStatusWordCount()
755
+ QApplication.restoreOverrideCursor()
785
756
 
786
- if not beQuiet:
787
- SHARED.info(self.tr("The project index has been successfully rebuilt."))
757
+ if not beQuiet:
758
+ SHARED.info(self.tr("The project index has been successfully rebuilt."))
788
759
 
789
- return True
760
+ return
790
761
 
791
762
  ##
792
763
  # Main Dialogs
@@ -796,8 +767,8 @@ class GuiMain(QMainWindow):
796
767
  def showWelcomeDialog(self) -> None:
797
768
  """Open the welcome dialog."""
798
769
  dialog = GuiWelcome(self)
799
- dialog.openProjectRequest.connect(self._openProject)
800
- dialog.exec_()
770
+ dialog.openProjectRequest.connect(self._openProjectFromWelcome)
771
+ dialog.exec()
801
772
  return
802
773
 
803
774
  @pyqtSlot()
@@ -805,7 +776,7 @@ class GuiMain(QMainWindow):
805
776
  """Open the preferences dialog."""
806
777
  dialog = GuiPreferences(self)
807
778
  dialog.newPreferencesReady.connect(self._processConfigChanges)
808
- dialog.exec_()
779
+ dialog.exec()
809
780
  return
810
781
 
811
782
  @pyqtSlot()
@@ -815,7 +786,7 @@ class GuiMain(QMainWindow):
815
786
  if SHARED.hasProject:
816
787
  dialog = GuiProjectSettings(self, gotoPage=focusTab)
817
788
  dialog.newProjectSettingsReady.connect(self._processProjectSettingsChanges)
818
- dialog.exec_()
789
+ dialog.exec()
819
790
  return
820
791
 
821
792
  @pyqtSlot()
@@ -826,7 +797,7 @@ class GuiMain(QMainWindow):
826
797
  dialog.setModal(True)
827
798
  dialog.show()
828
799
  dialog.raise_()
829
- qApp.processEvents()
800
+ QApplication.processEvents()
830
801
  dialog.updateValues()
831
802
  return
832
803
 
@@ -839,7 +810,7 @@ class GuiMain(QMainWindow):
839
810
  dialog.setModal(False)
840
811
  dialog.show()
841
812
  dialog.raise_()
842
- qApp.processEvents()
813
+ QApplication.processEvents()
843
814
  dialog.loadContent()
844
815
  return
845
816
 
@@ -849,7 +820,7 @@ class GuiMain(QMainWindow):
849
820
  if SHARED.hasProject:
850
821
  dialog = GuiWordList(self)
851
822
  dialog.newWordListReady.connect(self._processWordListChanges)
852
- dialog.exec_()
823
+ dialog.exec()
853
824
  return
854
825
 
855
826
  @pyqtSlot()
@@ -861,7 +832,7 @@ class GuiMain(QMainWindow):
861
832
  dialog.setModal(False)
862
833
  dialog.show()
863
834
  dialog.raise_()
864
- qApp.processEvents()
835
+ QApplication.processEvents()
865
836
  dialog.populateGUI()
866
837
  return
867
838
 
@@ -872,7 +843,7 @@ class GuiMain(QMainWindow):
872
843
  dialog.setModal(True)
873
844
  dialog.show()
874
845
  dialog.raise_()
875
- qApp.processEvents()
846
+ QApplication.processEvents()
876
847
  dialog.populateGUI()
877
848
  return
878
849
 
@@ -890,21 +861,20 @@ class GuiMain(QMainWindow):
890
861
  dialog.setModal(True)
891
862
  dialog.show()
892
863
  dialog.raise_()
893
- qApp.processEvents()
864
+ QApplication.processEvents()
894
865
  if not dialog.initDialog():
895
866
  dialog.close()
896
867
  SHARED.error(self.tr("Could not initialise the dialog."))
897
868
  return
898
869
 
899
- def reportConfErr(self) -> bool:
870
+ def reportConfErr(self) -> None:
900
871
  """Checks if the Config module has any errors to report, and let
901
872
  the user know if this is the case. The Config module caches
902
873
  errors since it is initialised before the GUI itself.
903
874
  """
904
875
  if CONFIG.hasError:
905
876
  SHARED.error(CONFIG.errorText())
906
- return True
907
- return False
877
+ return
908
878
 
909
879
  ##
910
880
  # Main Window Actions
@@ -922,14 +892,15 @@ class GuiMain(QMainWindow):
922
892
 
923
893
  logger.info("Exiting novelWriter")
924
894
 
925
- if not self.isFocusMode:
895
+ if not SHARED.focusMode:
926
896
  CONFIG.setMainPanePos(self.splitMain.sizes())
927
897
  CONFIG.setOutlinePanePos(self.outlineView.splitSizes())
928
898
  if self.docViewerPanel.isVisible():
929
899
  CONFIG.setViewPanePos(self.splitView.sizes())
930
900
 
931
901
  CONFIG.showViewerPanel = self.docViewerPanel.isVisible()
932
- if self.windowState() & Qt.WindowFullScreen != Qt.WindowFullScreen:
902
+ wFull = Qt.WindowState.WindowFullScreen
903
+ if self.windowState() & wFull != wFull:
933
904
  # Ignore window size if in full screen mode
934
905
  CONFIG.setMainWinSize(self.width(), self.height())
935
906
 
@@ -939,11 +910,11 @@ class GuiMain(QMainWindow):
939
910
  CONFIG.saveConfig()
940
911
  self.reportConfErr()
941
912
 
942
- qApp.quit()
913
+ QApplication.quit()
943
914
 
944
915
  return True
945
916
 
946
- def closeDocViewer(self, byUser: bool = True) -> bool:
917
+ def closeViewerPanel(self, byUser: bool = True) -> bool:
947
918
  """Close the document view panel."""
948
919
  self.docViewer.clearViewer()
949
920
  if byUser:
@@ -966,14 +937,14 @@ class GuiMain(QMainWindow):
966
937
 
967
938
  def toggleFullScreenMode(self) -> None:
968
939
  """Toggle full screen mode"""
969
- self.setWindowState(self.windowState() ^ Qt.WindowFullScreen)
940
+ self.setWindowState(self.windowState() ^ Qt.WindowState.WindowFullScreen)
970
941
  return
971
942
 
972
943
  ##
973
944
  # Events
974
945
  ##
975
946
 
976
- def closeEvent(self, event: QCloseEvent):
947
+ def closeEvent(self, event: QCloseEvent) -> None:
977
948
  """Capture the closing event of the GUI and call the close
978
949
  function to handle all the close process steps.
979
950
  """
@@ -991,32 +962,40 @@ class GuiMain(QMainWindow):
991
962
  SHARED.project.data.setLastHandle(None, "editor")
992
963
  return
993
964
 
965
+ @pyqtSlot()
966
+ def closeDocViewer(self) -> None:
967
+ """Close the document viewer."""
968
+ self.closeViewerPanel()
969
+ SHARED.project.data.setLastHandle(None, "viewer")
970
+ return
971
+
994
972
  @pyqtSlot()
995
973
  def toggleFocusMode(self) -> None:
996
- """Handle toggle focus mode. The Main GUI Focus Mode hides tree,
974
+ """Toggle focus mode."""
975
+ if self.docEditor.docHandle:
976
+ SHARED.setFocusMode(not SHARED.focusMode)
977
+ return
978
+
979
+ @pyqtSlot(bool)
980
+ def _focusModeChanged(self, focusMode: bool) -> None:
981
+ """Handle change of focus mode. The Main GUI Focus Mode hides tree,
997
982
  view, statusbar and menu.
998
983
  """
999
- if self.docEditor.docHandle is None:
1000
- logger.error("No document open, so not activating Focus Mode")
1001
- return
1002
-
1003
- self.isFocusMode = not self.isFocusMode
1004
- if self.isFocusMode:
984
+ if focusMode:
1005
985
  logger.debug("Activating Focus Mode")
1006
986
  self.switchFocus(nwWidget.EDITOR)
1007
987
  else:
1008
988
  logger.debug("Deactivating Focus Mode")
1009
989
 
1010
990
  cursorVisible = self.docEditor.cursorIsVisible()
1011
- isVisible = not self.isFocusMode
991
+ isVisible = not focusMode
1012
992
  self.treePane.setVisible(isVisible)
1013
993
  self.mainStatus.setVisible(isVisible)
1014
994
  self.mainMenu.setVisible(isVisible)
1015
995
  self.sideBar.setVisible(isVisible)
1016
996
 
1017
- hideDocFooter = self.isFocusMode and CONFIG.hideFocusFooter
997
+ hideDocFooter = focusMode and CONFIG.hideFocusFooter
1018
998
  self.docEditor.docFooter.setVisible(not hideDocFooter)
1019
- self.docEditor.docHeader.updateFocusMode()
1020
999
 
1021
1000
  if self.splitView.isVisible():
1022
1001
  self.splitView.setVisible(False)
@@ -1025,7 +1004,6 @@ class GuiMain(QMainWindow):
1025
1004
 
1026
1005
  if cursorVisible:
1027
1006
  self.docEditor.ensureCursorVisibleNoCentre()
1028
-
1029
1007
  return
1030
1008
 
1031
1009
  @pyqtSlot(nwWidget)
@@ -1038,12 +1016,15 @@ class GuiMain(QMainWindow):
1038
1016
  self.novelView.setTreeFocus()
1039
1017
  else:
1040
1018
  self.projView.setTreeFocus()
1041
- else:
1019
+ elif self.projStack.currentWidget() is self.novelView:
1042
1020
  if self.novelView.treeHasFocus():
1043
1021
  self._changeView(nwView.PROJECT)
1044
1022
  self.projView.setTreeFocus()
1045
1023
  else:
1046
1024
  self.novelView.setTreeFocus()
1025
+ else:
1026
+ self._changeView(nwView.PROJECT)
1027
+ self.projView.setTreeFocus()
1047
1028
  elif paneNo == nwWidget.EDITOR:
1048
1029
  self._changeView(nwView.EDITOR)
1049
1030
  self.docEditor.setFocus()
@@ -1076,7 +1057,7 @@ class GuiMain(QMainWindow):
1076
1057
 
1077
1058
  if theme:
1078
1059
  # We are doing this manually instead of connecting to
1079
- # qApp.paletteChanged since the processing order matters
1060
+ # paletteChanged since the processing order matters
1080
1061
  SHARED.theme.loadTheme()
1081
1062
  self.docEditor.updateTheme()
1082
1063
  self.docViewer.updateTheme()
@@ -1084,6 +1065,7 @@ class GuiMain(QMainWindow):
1084
1065
  self.sideBar.updateTheme()
1085
1066
  self.projView.updateTheme()
1086
1067
  self.novelView.updateTheme()
1068
+ self.projSearch.updateTheme()
1087
1069
  self.outlineView.updateTheme()
1088
1070
  self.itemDetails.updateTheme()
1089
1071
  self.mainStatus.updateTheme()
@@ -1132,10 +1114,12 @@ class GuiMain(QMainWindow):
1132
1114
  return
1133
1115
 
1134
1116
  @pyqtSlot(Path)
1135
- def _openProject(self, path: Path) -> None:
1136
- """Handle an open project request."""
1137
- qApp.processEvents()
1117
+ def _openProjectFromWelcome(self, path: Path) -> None:
1118
+ """Handle an open project request from the welcome dialog."""
1119
+ QApplication.processEvents()
1138
1120
  self.openProject(path)
1121
+ if not SHARED.hasProject:
1122
+ self.showWelcomeDialog()
1139
1123
  return
1140
1124
 
1141
1125
  @pyqtSlot(str, nwDocMode, str, bool)
@@ -1144,7 +1128,7 @@ class GuiMain(QMainWindow):
1144
1128
  if tHandle is not None:
1145
1129
  if mode == nwDocMode.EDIT:
1146
1130
  tLine = None
1147
- hItem = SHARED.project.index.getItemHeader(tHandle, sTitle)
1131
+ hItem = SHARED.project.index.getItemHeading(tHandle, sTitle)
1148
1132
  if hItem is not None:
1149
1133
  tLine = hItem.line
1150
1134
  self.openDocument(tHandle, tLine=tLine, changeFocus=setFocus)
@@ -1152,6 +1136,24 @@ class GuiMain(QMainWindow):
1152
1136
  self.viewDocument(tHandle=tHandle, sTitle=sTitle)
1153
1137
  return
1154
1138
 
1139
+ @pyqtSlot(str, int, int, bool)
1140
+ def _openDocumentSelection(
1141
+ self, tHandle: str, selStart: int, selLength: int, changeFocus: bool
1142
+ ) -> None:
1143
+ """Open a document and select a section of the text."""
1144
+ if self.openDocument(tHandle, changeFocus=changeFocus):
1145
+ self.docEditor.setCursorSelection(selStart, selLength)
1146
+ return
1147
+
1148
+ @pyqtSlot()
1149
+ def _reloadViewer(self) -> None:
1150
+ """Reload the document in the viewer."""
1151
+ if self.docEditor.docChanged and self.docEditor.docHandle == self.docViewer.docHandle:
1152
+ # If the two panels have the same document, save any changes in the editor
1153
+ self.saveDocument()
1154
+ self.docViewer.reloadText()
1155
+ return
1156
+
1155
1157
  @pyqtSlot(nwView)
1156
1158
  def _changeView(self, view: nwView) -> None:
1157
1159
  """Handle the requested change of view from the GuiViewBar."""
@@ -1164,6 +1166,12 @@ class GuiMain(QMainWindow):
1164
1166
  elif view == nwView.NOVEL:
1165
1167
  self.mainStack.setCurrentWidget(self.splitMain)
1166
1168
  self.projStack.setCurrentWidget(self.novelView)
1169
+ elif view == nwView.SEARCH:
1170
+ self.mainStack.setCurrentWidget(self.splitMain)
1171
+ self.projStack.setCurrentWidget(self.projSearch)
1172
+ self.projSearch.beginSearch(
1173
+ self.docEditor.getSelectedText() if self.docEditor.anyFocus() else ""
1174
+ )
1167
1175
  elif view == nwView.OUTLINE:
1168
1176
  self.mainStack.setCurrentWidget(self.outlineView)
1169
1177
  return
@@ -1193,7 +1201,7 @@ class GuiMain(QMainWindow):
1193
1201
  return
1194
1202
 
1195
1203
  @pyqtSlot()
1196
- def _toggleViewerPanelVisibility(self):
1204
+ def _toggleViewerPanelVisibility(self) -> None:
1197
1205
  """Toggle the visibility of the document viewer panel."""
1198
1206
  CONFIG.showViewerPanel = not CONFIG.showViewerPanel
1199
1207
  self.docViewerPanel.setVisible(CONFIG.showViewerPanel)
@@ -1202,16 +1210,15 @@ class GuiMain(QMainWindow):
1202
1210
  @pyqtSlot()
1203
1211
  def _timeTick(self) -> None:
1204
1212
  """Process time tick of the main timer."""
1205
- if not SHARED.hasProject:
1206
- return
1207
- currTime = time()
1208
- editIdle = currTime - self.docEditor.lastActive > CONFIG.userIdleTime
1209
- userIdle = qApp.applicationState() != Qt.ApplicationActive
1210
- self.mainStatus.setUserIdle(editIdle or userIdle)
1211
- SHARED.updateIdleTime(currTime, editIdle or userIdle)
1212
- self.mainStatus.updateTime(idleTime=SHARED.projectIdleTime)
1213
- if CONFIG.memInfo and int(currTime) % 5 == 0: # pragma: no cover
1214
- self.mainStatus.memInfo()
1213
+ if SHARED.hasProject:
1214
+ currTime = time()
1215
+ editIdle = currTime - self.docEditor.lastActive > CONFIG.userIdleTime
1216
+ userIdle = QApplication.applicationState() != Qt.ApplicationState.ApplicationActive
1217
+ self.mainStatus.setUserIdle(editIdle or userIdle)
1218
+ SHARED.updateIdleTime(currTime, editIdle or userIdle)
1219
+ self.mainStatus.updateTime(idleTime=SHARED.projectIdleTime)
1220
+ if CONFIG.memInfo and int(currTime) % 5 == 0: # pragma: no cover
1221
+ self.mainStatus.memInfo()
1215
1222
  return
1216
1223
 
1217
1224
  @pyqtSlot()
@@ -1253,25 +1260,26 @@ class GuiMain(QMainWindow):
1253
1260
 
1254
1261
  @pyqtSlot()
1255
1262
  def _keyPressReturn(self) -> None:
1256
- """Forward the return/enter keypress to the function that opens
1257
- the currently selected item.
1258
- """
1259
- self.openSelectedItem()
1263
+ """Process a return or enter keypress in the main window."""
1264
+ if self.projStack.currentWidget() == self.projSearch:
1265
+ self.projSearch.processReturn()
1266
+ else:
1267
+ self.openSelectedItem()
1260
1268
  return
1261
1269
 
1262
1270
  @pyqtSlot()
1263
1271
  def _keyPressEscape(self) -> None:
1264
- """Process escape keypress in the main window."""
1265
- if self.docEditor.docSearch.isVisible():
1272
+ """Process an escape keypress in the main window."""
1273
+ if self.docEditor.searchVisible():
1266
1274
  self.docEditor.closeSearch()
1267
- elif self.isFocusMode:
1268
- self.toggleFocusMode()
1275
+ elif SHARED.focusMode:
1276
+ SHARED.setFocusMode(False)
1269
1277
  return
1270
1278
 
1271
1279
  @pyqtSlot(int)
1272
1280
  def _mainStackChanged(self, index: int) -> None:
1273
1281
  """Process main window tab change."""
1274
- if index == self.idxOutlineView:
1282
+ if self.mainStack.widget(index) == self.outlineView:
1275
1283
  if SHARED.hasProject:
1276
1284
  self.outlineView.refreshTree()
1277
1285
  return
@@ -1280,9 +1288,10 @@ class GuiMain(QMainWindow):
1280
1288
  def _projStackChanged(self, index: int) -> None:
1281
1289
  """Process project view tab change."""
1282
1290
  sHandle = None
1283
- if index == self.idxProjView:
1291
+ widget = self.projStack.widget(index)
1292
+ if widget == self.projView:
1284
1293
  sHandle = self.projView.getSelectedHandle()
1285
- elif index == self.idxNovelView:
1294
+ elif widget == self.novelView:
1286
1295
  sHandle, _ = self.novelView.getSelectedHandle()
1287
1296
  self.itemDetails.updateViewBox(sHandle)
1288
1297
  return