novelWriter 2.3.1__py3-none-any.whl → 2.4b1__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 (81) hide show
  1. {novelWriter-2.3.1.dist-info → novelWriter-2.4b1.dist-info}/METADATA +1 -1
  2. {novelWriter-2.3.1.dist-info → novelWriter-2.4b1.dist-info}/RECORD +81 -70
  3. novelwriter/__init__.py +5 -5
  4. novelwriter/assets/icons/typicons_dark/icons.conf +4 -0
  5. novelwriter/assets/icons/typicons_dark/nw_tb-mark.svg +7 -0
  6. novelwriter/assets/icons/typicons_dark/typ_arrow-down.svg +4 -0
  7. novelwriter/assets/icons/typicons_dark/typ_arrow-right.svg +4 -0
  8. novelwriter/assets/icons/typicons_dark/typ_refresh-flipped.svg +1 -1
  9. novelwriter/assets/icons/typicons_dark/typ_refresh.svg +1 -1
  10. novelwriter/assets/icons/typicons_dark/typ_search-grey.svg +4 -0
  11. novelwriter/assets/icons/typicons_dark/typ_times.svg +1 -1
  12. novelwriter/assets/icons/typicons_light/icons.conf +4 -0
  13. novelwriter/assets/icons/typicons_light/nw_tb-mark.svg +7 -0
  14. novelwriter/assets/icons/typicons_light/typ_arrow-down.svg +4 -0
  15. novelwriter/assets/icons/typicons_light/typ_arrow-right.svg +4 -0
  16. novelwriter/assets/icons/typicons_light/typ_refresh-flipped.svg +1 -1
  17. novelwriter/assets/icons/typicons_light/typ_refresh.svg +1 -1
  18. novelwriter/assets/icons/typicons_light/typ_search-grey.svg +4 -0
  19. novelwriter/assets/icons/typicons_light/typ_times.svg +1 -1
  20. novelwriter/assets/manual.pdf +0 -0
  21. novelwriter/assets/sample.zip +0 -0
  22. novelwriter/assets/syntax/default_dark.conf +1 -0
  23. novelwriter/assets/syntax/default_light.conf +1 -0
  24. novelwriter/assets/syntax/grey_dark.conf +1 -0
  25. novelwriter/assets/syntax/grey_light.conf +1 -0
  26. novelwriter/assets/syntax/light_owl.conf +1 -0
  27. novelwriter/assets/syntax/night_owl.conf +1 -0
  28. novelwriter/assets/syntax/solarized_dark.conf +1 -0
  29. novelwriter/assets/syntax/solarized_light.conf +1 -0
  30. novelwriter/assets/syntax/tomorrow.conf +1 -0
  31. novelwriter/assets/syntax/tomorrow_night.conf +1 -0
  32. novelwriter/assets/syntax/tomorrow_night_blue.conf +1 -0
  33. novelwriter/assets/syntax/tomorrow_night_bright.conf +1 -0
  34. novelwriter/assets/syntax/tomorrow_night_eighties.conf +1 -0
  35. novelwriter/assets/text/credits_en.htm +25 -23
  36. novelwriter/common.py +1 -1
  37. novelwriter/config.py +35 -12
  38. novelwriter/constants.py +5 -6
  39. novelwriter/core/buildsettings.py +60 -40
  40. novelwriter/core/coretools.py +98 -13
  41. novelwriter/core/docbuild.py +74 -7
  42. novelwriter/core/document.py +24 -3
  43. novelwriter/core/index.py +31 -112
  44. novelwriter/core/project.py +10 -15
  45. novelwriter/core/sessions.py +2 -2
  46. novelwriter/core/status.py +4 -4
  47. novelwriter/core/storage.py +8 -2
  48. novelwriter/core/tohtml.py +22 -25
  49. novelwriter/core/tokenizer.py +416 -232
  50. novelwriter/core/tomd.py +17 -8
  51. novelwriter/core/toodt.py +65 -7
  52. novelwriter/core/tree.py +8 -8
  53. novelwriter/dialogs/docsplit.py +7 -8
  54. novelwriter/dialogs/preferences.py +3 -6
  55. novelwriter/enum.py +17 -14
  56. novelwriter/extensions/modified.py +20 -2
  57. novelwriter/extensions/versioninfo.py +1 -1
  58. novelwriter/gui/doceditor.py +257 -279
  59. novelwriter/gui/dochighlight.py +29 -25
  60. novelwriter/gui/docviewer.py +139 -148
  61. novelwriter/gui/docviewerpanel.py +4 -24
  62. novelwriter/gui/editordocument.py +12 -1
  63. novelwriter/gui/itemdetails.py +6 -6
  64. novelwriter/gui/mainmenu.py +37 -16
  65. novelwriter/gui/noveltree.py +11 -19
  66. novelwriter/gui/outline.py +43 -20
  67. novelwriter/gui/projtree.py +35 -43
  68. novelwriter/gui/search.py +316 -0
  69. novelwriter/gui/sidebar.py +25 -30
  70. novelwriter/gui/theme.py +59 -6
  71. novelwriter/guimain.py +176 -173
  72. novelwriter/shared.py +26 -1
  73. novelwriter/text/__init__.py +3 -0
  74. novelwriter/text/counting.py +137 -0
  75. novelwriter/tools/manuscript.py +344 -55
  76. novelwriter/tools/manussettings.py +213 -71
  77. novelwriter/tools/welcome.py +1 -1
  78. {novelWriter-2.3.1.dist-info → novelWriter-2.4b1.dist-info}/LICENSE.md +0 -0
  79. {novelWriter-2.3.1.dist-info → novelWriter-2.4b1.dist-info}/WHEEL +0 -0
  80. {novelWriter-2.3.1.dist-info → novelWriter-2.4b1.dist-info}/entry_points.txt +0 -0
  81. {novelWriter-2.3.1.dist-info → novelWriter-2.4b1.dist-info}/top_level.txt +0 -0
novelwriter/guimain.py CHANGED
@@ -38,33 +38,31 @@ from PyQt5.QtWidgets import (
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()
@@ -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
@@ -157,6 +154,8 @@ class GuiMain(QMainWindow):
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
161
  self.splitDocs = QSplitter(Qt.Horizontal, self)
@@ -164,6 +163,8 @@ class GuiMain(QMainWindow):
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
170
  self.splitMain = QSplitter(Qt.Horizontal)
@@ -173,6 +174,10 @@ class GuiMain(QMainWindow):
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()
@@ -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,12 @@ 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)
278
265
 
279
266
  self.docViewer.documentLoaded.connect(self.docViewerPanel.updateHandle)
280
267
  self.docViewer.loadDocumentTagRequest.connect(self._followTag)
268
+ self.docViewer.closeDocumentRequest.connect(self.closeDocViewer)
269
+ self.docViewer.reloadDocumentRequest.connect(self._reloadViewer)
281
270
  self.docViewer.togglePanelVisibility.connect(self._toggleViewerPanelVisibility)
282
271
  self.docViewer.requestProjectItemSelected.connect(self.projView.setSelectedHandle)
283
272
 
@@ -298,12 +287,6 @@ class GuiMain(QMainWindow):
298
287
  self.asDocTimer = QTimer(self)
299
288
  self.asDocTimer.timeout.connect(self._autoSaveDocument)
300
289
 
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
290
  # Shortcuts and Actions
308
291
  self._connectMenuActions()
309
292
 
@@ -401,12 +384,13 @@ class GuiMain(QMainWindow):
401
384
  if saveOK:
402
385
  self.closeDocument()
403
386
  self.docViewer.clearNavHistory()
404
- self.closeDocViewer(byUser=False)
387
+ self.closeViewerPanel(byUser=False)
405
388
 
406
389
  self.docViewerPanel.closeProjectTasks()
407
390
  self.outlineView.closeProjectTasks()
408
391
  self.novelView.closeProjectTasks()
409
392
  self.projView.closeProjectTasks()
393
+ self.projSearch.closeProjectTasks()
410
394
  self.itemDetails.clearDetails()
411
395
  self.mainStatus.clearStatus()
412
396
 
@@ -457,7 +441,7 @@ class GuiMain(QMainWindow):
457
441
  "'{0}' ({1} {2}), last active on {3}."
458
442
  ).format(
459
443
  lockStatus[0], lockStatus[1], lockStatus[2],
460
- datetime.fromtimestamp(int(lockStatus[3])).strftime("%x %X")
444
+ CONFIG.localDateTime(datetime.fromtimestamp(int(lockStatus[3])))
461
445
  )
462
446
  except Exception:
463
447
  lockDetails = ""
@@ -523,24 +507,19 @@ class GuiMain(QMainWindow):
523
507
  # Document Actions
524
508
  ##
525
509
 
526
- def closeDocument(self, beforeOpen: bool = False) -> bool:
510
+ def closeDocument(self, beforeOpen: bool = False) -> None:
527
511
  """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
512
+ if SHARED.hasProject:
513
+ # Disable focus mode if it is active
514
+ if SHARED.focusMode:
515
+ SHARED.setFocusMode(False)
516
+ self.docEditor.saveCursorPosition()
517
+ if self.docEditor.docChanged:
518
+ self.saveDocument()
519
+ self.docEditor.clearEditor()
520
+ if not beforeOpen:
521
+ self.novelView.setActiveHandle(None)
522
+ return
544
523
 
545
524
  def openDocument(self, tHandle: str | None, tLine: int | None = None,
546
525
  changeFocus: bool = True, doScroll: bool = False) -> bool:
@@ -604,13 +583,12 @@ class GuiMain(QMainWindow):
604
583
 
605
584
  return False
606
585
 
607
- def saveDocument(self) -> bool:
586
+ @pyqtSlot()
587
+ def saveDocument(self) -> None:
608
588
  """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
589
+ if SHARED.hasProject:
590
+ self.docEditor.saveText()
591
+ return
614
592
 
615
593
  def viewDocument(self, tHandle: str | None = None, sTitle: str | None = None) -> bool:
616
594
  """Load a document for viewing in the view panel."""
@@ -640,7 +618,8 @@ class GuiMain(QMainWindow):
640
618
  self._changeView(nwView.EDITOR)
641
619
 
642
620
  logger.debug("Viewing document with handle '%s'", tHandle)
643
- if self.docViewer.loadText(tHandle):
621
+ updateHistory = tHandle != self.docViewer.docHandle
622
+ if self.docViewer.loadText(tHandle, updateHistory=updateHistory):
644
623
  if not self.splitView.isVisible():
645
624
  cursorVisible = self.docEditor.cursorIsVisible()
646
625
  bPos = self.splitMain.sizes()
@@ -657,7 +636,7 @@ class GuiMain(QMainWindow):
657
636
  self.docEditor.ensureCursorVisibleNoCentre()
658
637
 
659
638
  if sTitle:
660
- self.docViewer.navigateTo(f"#{sTitle}")
639
+ self.docViewer.navigateTo(f"#{tHandle}:{sTitle}")
661
640
 
662
641
  return True
663
642
 
@@ -713,80 +692,71 @@ class GuiMain(QMainWindow):
713
692
  # Tree Item Actions
714
693
  ##
715
694
 
716
- def openSelectedItem(self) -> bool:
695
+ @pyqtSlot()
696
+ def openSelectedItem(self) -> None:
717
697
  """Open the selected item from the tree that is currently
718
698
  active. It is not checked that the item is actually a document.
719
699
  That should be handled by the openDocument function.
720
700
  """
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
701
+ if SHARED.hasProject:
702
+ tHandle = None
703
+ sTitle = None
704
+ tLine = None
705
+ if self.projView.treeHasFocus():
706
+ tHandle = self.projView.getSelectedHandle()
707
+ elif self.novelView.treeHasFocus():
708
+ tHandle, sTitle = self.novelView.getSelectedHandle()
709
+ elif self.outlineView.treeHasFocus():
710
+ tHandle, sTitle = self.outlineView.getSelectedHandle()
711
+ else:
712
+ logger.warning("No item selected")
713
+ return
742
714
 
743
- if tHandle is not None:
744
- self.openDocument(tHandle, tLine=tLine, changeFocus=False, doScroll=False)
715
+ if tHandle and sTitle:
716
+ if hItem := SHARED.project.index.getItemHeading(tHandle, sTitle):
717
+ tLine = hItem.line
718
+ if tHandle:
719
+ self.openDocument(tHandle, tLine=tLine, changeFocus=False, doScroll=False)
745
720
 
746
- return True
721
+ return
747
722
 
748
- def editItemLabel(self, tHandle: str | None = None) -> bool:
723
+ def editItemLabel(self, tHandle: str | None = None) -> None:
749
724
  """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
725
+ if SHARED.hasProject:
726
+ if tHandle is None and (self.docEditor.anyFocus() or SHARED.focusMode):
727
+ tHandle = self.docEditor.docHandle
728
+ self.projView.renameTreeItem(tHandle)
729
+ return
757
730
 
758
731
  def rebuildTrees(self) -> None:
759
732
  """Rebuild the project tree."""
760
733
  self.projView.populateTree()
761
734
  return
762
735
 
763
- def rebuildIndex(self, beQuiet: bool = False) -> bool:
736
+ def rebuildIndex(self, beQuiet: bool = False) -> None:
764
737
  """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()
738
+ if SHARED.hasProject:
739
+ logger.info("Rebuilding index ...")
740
+ qApp.setOverrideCursor(QCursor(Qt.WaitCursor))
741
+ tStart = time()
772
742
 
773
- self.projView.saveProjectTasks()
774
- SHARED.project.index.rebuildIndex()
775
- self.projView.populateTree()
776
- self.novelView.refreshTree()
743
+ self.projView.saveProjectTasks()
744
+ SHARED.project.index.rebuildIndex()
745
+ self.projView.populateTree()
746
+ self.novelView.refreshTree()
777
747
 
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()
748
+ tEnd = time()
749
+ self.mainStatus.setStatusMessage(
750
+ self.tr("Indexing completed in {0} ms").format(f"{(tEnd - tStart)*1000.0:.1f}")
751
+ )
752
+ self.docEditor.updateTagHighLighting()
753
+ self._updateStatusWordCount()
754
+ qApp.restoreOverrideCursor()
785
755
 
786
- if not beQuiet:
787
- SHARED.info(self.tr("The project index has been successfully rebuilt."))
756
+ if not beQuiet:
757
+ SHARED.info(self.tr("The project index has been successfully rebuilt."))
788
758
 
789
- return True
759
+ return
790
760
 
791
761
  ##
792
762
  # Main Dialogs
@@ -896,15 +866,14 @@ class GuiMain(QMainWindow):
896
866
  SHARED.error(self.tr("Could not initialise the dialog."))
897
867
  return
898
868
 
899
- def reportConfErr(self) -> bool:
869
+ def reportConfErr(self) -> None:
900
870
  """Checks if the Config module has any errors to report, and let
901
871
  the user know if this is the case. The Config module caches
902
872
  errors since it is initialised before the GUI itself.
903
873
  """
904
874
  if CONFIG.hasError:
905
875
  SHARED.error(CONFIG.errorText())
906
- return True
907
- return False
876
+ return
908
877
 
909
878
  ##
910
879
  # Main Window Actions
@@ -922,7 +891,7 @@ class GuiMain(QMainWindow):
922
891
 
923
892
  logger.info("Exiting novelWriter")
924
893
 
925
- if not self.isFocusMode:
894
+ if not SHARED.focusMode:
926
895
  CONFIG.setMainPanePos(self.splitMain.sizes())
927
896
  CONFIG.setOutlinePanePos(self.outlineView.splitSizes())
928
897
  if self.docViewerPanel.isVisible():
@@ -943,7 +912,7 @@ class GuiMain(QMainWindow):
943
912
 
944
913
  return True
945
914
 
946
- def closeDocViewer(self, byUser: bool = True) -> bool:
915
+ def closeViewerPanel(self, byUser: bool = True) -> bool:
947
916
  """Close the document view panel."""
948
917
  self.docViewer.clearViewer()
949
918
  if byUser:
@@ -991,32 +960,40 @@ class GuiMain(QMainWindow):
991
960
  SHARED.project.data.setLastHandle(None, "editor")
992
961
  return
993
962
 
963
+ @pyqtSlot()
964
+ def closeDocViewer(self) -> None:
965
+ """Close the document viewer."""
966
+ self.closeViewerPanel()
967
+ SHARED.project.data.setLastHandle(None, "viewer")
968
+ return
969
+
994
970
  @pyqtSlot()
995
971
  def toggleFocusMode(self) -> None:
996
- """Handle toggle focus mode. The Main GUI Focus Mode hides tree,
972
+ """Toggle focus mode."""
973
+ if self.docEditor.docHandle:
974
+ SHARED.setFocusMode(not SHARED.focusMode)
975
+ return
976
+
977
+ @pyqtSlot(bool)
978
+ def _focusModeChanged(self, focusMode: bool) -> None:
979
+ """Handle change of focus mode. The Main GUI Focus Mode hides tree,
997
980
  view, statusbar and menu.
998
981
  """
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:
982
+ if focusMode:
1005
983
  logger.debug("Activating Focus Mode")
1006
984
  self.switchFocus(nwWidget.EDITOR)
1007
985
  else:
1008
986
  logger.debug("Deactivating Focus Mode")
1009
987
 
1010
988
  cursorVisible = self.docEditor.cursorIsVisible()
1011
- isVisible = not self.isFocusMode
989
+ isVisible = not focusMode
1012
990
  self.treePane.setVisible(isVisible)
1013
991
  self.mainStatus.setVisible(isVisible)
1014
992
  self.mainMenu.setVisible(isVisible)
1015
993
  self.sideBar.setVisible(isVisible)
1016
994
 
1017
- hideDocFooter = self.isFocusMode and CONFIG.hideFocusFooter
995
+ hideDocFooter = focusMode and CONFIG.hideFocusFooter
1018
996
  self.docEditor.docFooter.setVisible(not hideDocFooter)
1019
- self.docEditor.docHeader.updateFocusMode()
1020
997
 
1021
998
  if self.splitView.isVisible():
1022
999
  self.splitView.setVisible(False)
@@ -1025,7 +1002,6 @@ class GuiMain(QMainWindow):
1025
1002
 
1026
1003
  if cursorVisible:
1027
1004
  self.docEditor.ensureCursorVisibleNoCentre()
1028
-
1029
1005
  return
1030
1006
 
1031
1007
  @pyqtSlot(nwWidget)
@@ -1038,12 +1014,15 @@ class GuiMain(QMainWindow):
1038
1014
  self.novelView.setTreeFocus()
1039
1015
  else:
1040
1016
  self.projView.setTreeFocus()
1041
- else:
1017
+ elif self.projStack.currentWidget() is self.novelView:
1042
1018
  if self.novelView.treeHasFocus():
1043
1019
  self._changeView(nwView.PROJECT)
1044
1020
  self.projView.setTreeFocus()
1045
1021
  else:
1046
1022
  self.novelView.setTreeFocus()
1023
+ else:
1024
+ self._changeView(nwView.PROJECT)
1025
+ self.projView.setTreeFocus()
1047
1026
  elif paneNo == nwWidget.EDITOR:
1048
1027
  self._changeView(nwView.EDITOR)
1049
1028
  self.docEditor.setFocus()
@@ -1084,6 +1063,7 @@ class GuiMain(QMainWindow):
1084
1063
  self.sideBar.updateTheme()
1085
1064
  self.projView.updateTheme()
1086
1065
  self.novelView.updateTheme()
1066
+ self.projSearch.updateTheme()
1087
1067
  self.outlineView.updateTheme()
1088
1068
  self.itemDetails.updateTheme()
1089
1069
  self.mainStatus.updateTheme()
@@ -1146,7 +1126,7 @@ class GuiMain(QMainWindow):
1146
1126
  if tHandle is not None:
1147
1127
  if mode == nwDocMode.EDIT:
1148
1128
  tLine = None
1149
- hItem = SHARED.project.index.getItemHeader(tHandle, sTitle)
1129
+ hItem = SHARED.project.index.getItemHeading(tHandle, sTitle)
1150
1130
  if hItem is not None:
1151
1131
  tLine = hItem.line
1152
1132
  self.openDocument(tHandle, tLine=tLine, changeFocus=setFocus)
@@ -1154,6 +1134,24 @@ class GuiMain(QMainWindow):
1154
1134
  self.viewDocument(tHandle=tHandle, sTitle=sTitle)
1155
1135
  return
1156
1136
 
1137
+ @pyqtSlot(str, int, int, bool)
1138
+ def _openDocumentSelection(
1139
+ self, tHandle: str, selStart: int, selLength: int, changeFocus: bool
1140
+ ) -> None:
1141
+ """Open a document and select a section of the text."""
1142
+ if self.openDocument(tHandle, changeFocus=changeFocus):
1143
+ self.docEditor.setCursorSelection(selStart, selLength)
1144
+ return
1145
+
1146
+ @pyqtSlot()
1147
+ def _reloadViewer(self) -> None:
1148
+ """Reload the document in the viewer."""
1149
+ if self.docEditor.docChanged and self.docEditor.docHandle == self.docViewer.docHandle:
1150
+ # If the two panels have the same document, save any changes in the editor
1151
+ self.saveDocument()
1152
+ self.docViewer.reloadText()
1153
+ return
1154
+
1157
1155
  @pyqtSlot(nwView)
1158
1156
  def _changeView(self, view: nwView) -> None:
1159
1157
  """Handle the requested change of view from the GuiViewBar."""
@@ -1166,6 +1164,10 @@ class GuiMain(QMainWindow):
1166
1164
  elif view == nwView.NOVEL:
1167
1165
  self.mainStack.setCurrentWidget(self.splitMain)
1168
1166
  self.projStack.setCurrentWidget(self.novelView)
1167
+ elif view == nwView.SEARCH:
1168
+ self.mainStack.setCurrentWidget(self.splitMain)
1169
+ self.projStack.setCurrentWidget(self.projSearch)
1170
+ self.projSearch.beginSearch()
1169
1171
  elif view == nwView.OUTLINE:
1170
1172
  self.mainStack.setCurrentWidget(self.outlineView)
1171
1173
  return
@@ -1204,16 +1206,15 @@ class GuiMain(QMainWindow):
1204
1206
  @pyqtSlot()
1205
1207
  def _timeTick(self) -> None:
1206
1208
  """Process time tick of the main timer."""
1207
- if not SHARED.hasProject:
1208
- return
1209
- currTime = time()
1210
- editIdle = currTime - self.docEditor.lastActive > CONFIG.userIdleTime
1211
- userIdle = qApp.applicationState() != Qt.ApplicationActive
1212
- self.mainStatus.setUserIdle(editIdle or userIdle)
1213
- SHARED.updateIdleTime(currTime, editIdle or userIdle)
1214
- self.mainStatus.updateTime(idleTime=SHARED.projectIdleTime)
1215
- if CONFIG.memInfo and int(currTime) % 5 == 0: # pragma: no cover
1216
- self.mainStatus.memInfo()
1209
+ if SHARED.hasProject:
1210
+ currTime = time()
1211
+ editIdle = currTime - self.docEditor.lastActive > CONFIG.userIdleTime
1212
+ userIdle = qApp.applicationState() != Qt.ApplicationActive
1213
+ self.mainStatus.setUserIdle(editIdle or userIdle)
1214
+ SHARED.updateIdleTime(currTime, editIdle or userIdle)
1215
+ self.mainStatus.updateTime(idleTime=SHARED.projectIdleTime)
1216
+ if CONFIG.memInfo and int(currTime) % 5 == 0: # pragma: no cover
1217
+ self.mainStatus.memInfo()
1217
1218
  return
1218
1219
 
1219
1220
  @pyqtSlot()
@@ -1255,25 +1256,26 @@ class GuiMain(QMainWindow):
1255
1256
 
1256
1257
  @pyqtSlot()
1257
1258
  def _keyPressReturn(self) -> None:
1258
- """Forward the return/enter keypress to the function that opens
1259
- the currently selected item.
1260
- """
1261
- self.openSelectedItem()
1259
+ """Process a return or enter keypress in the main window."""
1260
+ if self.projStack.currentWidget() == self.projSearch:
1261
+ self.projSearch.processReturn()
1262
+ else:
1263
+ self.openSelectedItem()
1262
1264
  return
1263
1265
 
1264
1266
  @pyqtSlot()
1265
1267
  def _keyPressEscape(self) -> None:
1266
- """Process escape keypress in the main window."""
1268
+ """Process an escape keypress in the main window."""
1267
1269
  if self.docEditor.docSearch.isVisible():
1268
1270
  self.docEditor.closeSearch()
1269
- elif self.isFocusMode:
1270
- self.toggleFocusMode()
1271
+ elif SHARED.focusMode:
1272
+ SHARED.setFocusMode(False)
1271
1273
  return
1272
1274
 
1273
1275
  @pyqtSlot(int)
1274
1276
  def _mainStackChanged(self, index: int) -> None:
1275
1277
  """Process main window tab change."""
1276
- if index == self.idxOutlineView:
1278
+ if self.mainStack.widget(index) == self.outlineView:
1277
1279
  if SHARED.hasProject:
1278
1280
  self.outlineView.refreshTree()
1279
1281
  return
@@ -1282,9 +1284,10 @@ class GuiMain(QMainWindow):
1282
1284
  def _projStackChanged(self, index: int) -> None:
1283
1285
  """Process project view tab change."""
1284
1286
  sHandle = None
1285
- if index == self.idxProjView:
1287
+ widget = self.projStack.widget(index)
1288
+ if widget == self.projView:
1286
1289
  sHandle = self.projView.getSelectedHandle()
1287
- elif index == self.idxNovelView:
1290
+ elif widget == self.novelView:
1288
1291
  sHandle, _ = self.novelView.getSelectedHandle()
1289
1292
  self.itemDetails.updateViewBox(sHandle)
1290
1293
  return