novelWriter 2.4.4__py3-none-any.whl → 2.5rc1__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.
- {novelWriter-2.4.4.dist-info → novelWriter-2.5rc1.dist-info}/METADATA +4 -5
- {novelWriter-2.4.4.dist-info → novelWriter-2.5rc1.dist-info}/RECORD +109 -101
- {novelWriter-2.4.4.dist-info → novelWriter-2.5rc1.dist-info}/WHEEL +1 -1
- novelwriter/__init__.py +33 -39
- novelwriter/assets/i18n/project_en_GB.json +1 -0
- novelwriter/assets/icons/typicons_dark/icons.conf +2 -0
- novelwriter/assets/icons/typicons_dark/nw_font.svg +4 -0
- novelwriter/assets/icons/typicons_dark/nw_quote.svg +4 -0
- novelwriter/assets/icons/typicons_light/icons.conf +2 -0
- novelwriter/assets/icons/typicons_light/nw_font.svg +4 -0
- novelwriter/assets/icons/typicons_light/nw_quote.svg +4 -0
- novelwriter/assets/manual.pdf +0 -0
- novelwriter/assets/sample.zip +0 -0
- novelwriter/assets/syntax/cyberpunk_night.conf +5 -3
- novelwriter/assets/syntax/default_dark.conf +32 -18
- novelwriter/assets/syntax/default_light.conf +24 -10
- novelwriter/assets/syntax/dracula.conf +44 -0
- novelwriter/assets/syntax/grey_dark.conf +5 -4
- novelwriter/assets/syntax/grey_light.conf +5 -4
- novelwriter/assets/syntax/light_owl.conf +7 -6
- novelwriter/assets/syntax/night_owl.conf +7 -6
- novelwriter/assets/syntax/snazzy.conf +42 -0
- novelwriter/assets/syntax/solarized_dark.conf +4 -3
- novelwriter/assets/syntax/solarized_light.conf +4 -3
- novelwriter/assets/syntax/tango.conf +27 -11
- novelwriter/assets/syntax/tomorrow.conf +6 -5
- novelwriter/assets/syntax/tomorrow_night.conf +7 -6
- novelwriter/assets/syntax/tomorrow_night_blue.conf +6 -5
- novelwriter/assets/syntax/tomorrow_night_bright.conf +6 -5
- novelwriter/assets/syntax/tomorrow_night_eighties.conf +6 -5
- novelwriter/assets/text/credits_en.htm +4 -1
- novelwriter/assets/themes/cyberpunk_night.conf +3 -0
- novelwriter/assets/themes/default_dark.conf +2 -0
- novelwriter/assets/themes/default_light.conf +2 -0
- novelwriter/assets/themes/dracula.conf +48 -0
- novelwriter/assets/themes/solarized_dark.conf +2 -0
- novelwriter/assets/themes/solarized_light.conf +2 -0
- novelwriter/common.py +33 -12
- novelwriter/config.py +184 -98
- novelwriter/constants.py +47 -35
- novelwriter/core/buildsettings.py +68 -69
- novelwriter/core/coretools.py +5 -23
- novelwriter/core/docbuild.py +52 -40
- novelwriter/core/document.py +3 -5
- novelwriter/core/index.py +115 -45
- novelwriter/core/item.py +8 -19
- novelwriter/core/options.py +2 -4
- novelwriter/core/project.py +23 -57
- novelwriter/core/projectdata.py +1 -3
- novelwriter/core/projectxml.py +12 -15
- novelwriter/core/sessions.py +3 -5
- novelwriter/core/spellcheck.py +4 -9
- novelwriter/core/status.py +211 -164
- novelwriter/core/storage.py +0 -8
- novelwriter/core/tohtml.py +139 -105
- novelwriter/core/tokenizer.py +278 -122
- novelwriter/core/{tomd.py → tomarkdown.py} +97 -78
- novelwriter/core/toodt.py +257 -166
- novelwriter/core/toqdoc.py +419 -0
- novelwriter/core/tree.py +5 -7
- novelwriter/dialogs/about.py +11 -18
- novelwriter/dialogs/docmerge.py +17 -19
- novelwriter/dialogs/docsplit.py +17 -19
- novelwriter/dialogs/editlabel.py +6 -10
- novelwriter/dialogs/preferences.py +193 -144
- novelwriter/dialogs/projectsettings.py +225 -189
- novelwriter/dialogs/quotes.py +12 -9
- novelwriter/dialogs/wordlist.py +9 -15
- novelwriter/enum.py +35 -30
- novelwriter/error.py +8 -15
- novelwriter/extensions/configlayout.py +40 -21
- novelwriter/extensions/eventfilters.py +1 -5
- novelwriter/extensions/modified.py +58 -14
- novelwriter/extensions/novelselector.py +1 -3
- novelwriter/extensions/pagedsidebar.py +9 -12
- novelwriter/extensions/{circularprogress.py → progressbars.py} +30 -8
- novelwriter/extensions/statusled.py +29 -25
- novelwriter/extensions/switch.py +4 -6
- novelwriter/extensions/switchbox.py +7 -6
- novelwriter/extensions/versioninfo.py +3 -9
- novelwriter/gui/doceditor.py +118 -137
- novelwriter/gui/dochighlight.py +231 -186
- novelwriter/gui/docviewer.py +66 -107
- novelwriter/gui/docviewerpanel.py +3 -10
- novelwriter/gui/editordocument.py +1 -3
- novelwriter/gui/itemdetails.py +7 -11
- novelwriter/gui/mainmenu.py +22 -18
- novelwriter/gui/noveltree.py +11 -24
- novelwriter/gui/outline.py +14 -26
- novelwriter/gui/projtree.py +35 -60
- novelwriter/gui/search.py +10 -3
- novelwriter/gui/sidebar.py +2 -6
- novelwriter/gui/statusbar.py +29 -37
- novelwriter/gui/theme.py +26 -48
- novelwriter/guimain.py +134 -148
- novelwriter/shared.py +36 -32
- novelwriter/text/patterns.py +113 -0
- novelwriter/tools/dictionaries.py +10 -20
- novelwriter/tools/lipsum.py +10 -16
- novelwriter/tools/manusbuild.py +9 -11
- novelwriter/tools/manuscript.py +71 -145
- novelwriter/tools/manussettings.py +71 -75
- novelwriter/tools/noveldetails.py +16 -21
- novelwriter/tools/welcome.py +12 -26
- novelwriter/tools/writingstats.py +9 -12
- novelwriter/types.py +49 -4
- novelwriter/extensions/simpleprogress.py +0 -55
- {novelWriter-2.4.4.dist-info → novelWriter-2.5rc1.dist-info}/LICENSE.md +0 -0
- {novelWriter-2.4.4.dist-info → novelWriter-2.5rc1.dist-info}/entry_points.txt +0 -0
- {novelWriter-2.4.4.dist-info → novelWriter-2.5rc1.dist-info}/top_level.txt +0 -0
novelwriter/guimain.py
CHANGED
@@ -23,18 +23,18 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
23
23
|
"""
|
24
24
|
from __future__ import annotations
|
25
25
|
|
26
|
-
import sys
|
27
26
|
import logging
|
27
|
+
import sys
|
28
28
|
|
29
|
-
from time import time
|
30
|
-
from pathlib import Path
|
31
29
|
from datetime import datetime
|
30
|
+
from pathlib import Path
|
31
|
+
from time import time
|
32
32
|
|
33
33
|
from PyQt5.QtCore import Qt, QTimer, pyqtSlot
|
34
34
|
from PyQt5.QtGui import QCloseEvent, QCursor, QIcon
|
35
35
|
from PyQt5.QtWidgets import (
|
36
|
-
QApplication, QFileDialog, QHBoxLayout, QMainWindow, QMessageBox,
|
37
|
-
QStackedWidget, QVBoxLayout, QWidget
|
36
|
+
QApplication, QFileDialog, QHBoxLayout, QMainWindow, QMessageBox,
|
37
|
+
QShortcut, QSplitter, QStackedWidget, QVBoxLayout, QWidget
|
38
38
|
)
|
39
39
|
|
40
40
|
from novelwriter import CONFIG, SHARED, __hexversion__, __version__
|
@@ -44,7 +44,7 @@ from novelwriter.dialogs.about import GuiAbout
|
|
44
44
|
from novelwriter.dialogs.preferences import GuiPreferences
|
45
45
|
from novelwriter.dialogs.projectsettings import GuiProjectSettings
|
46
46
|
from novelwriter.dialogs.wordlist import GuiWordList
|
47
|
-
from novelwriter.enum import nwDocAction, nwDocInsert, nwDocMode,
|
47
|
+
from novelwriter.enum import nwDocAction, nwDocInsert, nwDocMode, nwFocus, nwItemType, nwView
|
48
48
|
from novelwriter.gui.doceditor import GuiDocEditor
|
49
49
|
from novelwriter.gui.docviewer import GuiDocViewer
|
50
50
|
from novelwriter.gui.docviewerpanel import GuiDocViewerPanel
|
@@ -210,66 +210,68 @@ class GuiMain(QMainWindow):
|
|
210
210
|
# Connect Signals
|
211
211
|
# ===============
|
212
212
|
|
213
|
-
SHARED.projectStatusChanged.connect(self.mainStatus.updateProjectStatus)
|
214
|
-
SHARED.projectStatusMessage.connect(self.mainStatus.setStatusMessage)
|
215
|
-
SHARED.spellLanguageChanged.connect(self.mainStatus.setLanguage)
|
216
213
|
SHARED.focusModeChanged.connect(self._focusModeChanged)
|
214
|
+
SHARED.indexAvailable.connect(self.docViewerPanel.indexHasAppeared)
|
215
|
+
SHARED.indexChangedTags.connect(self.docEditor.updateChangedTags)
|
217
216
|
SHARED.indexChangedTags.connect(self.docViewerPanel.updateChangedTags)
|
217
|
+
SHARED.indexCleared.connect(self.docViewerPanel.indexWasCleared)
|
218
218
|
SHARED.indexScannedText.connect(self.docViewerPanel.projectItemChanged)
|
219
|
-
SHARED.indexScannedText.connect(self.projView.updateItemValues)
|
220
219
|
SHARED.indexScannedText.connect(self.itemDetails.updateViewBox)
|
221
|
-
SHARED.
|
222
|
-
SHARED.indexAvailable.connect(self.docViewerPanel.indexHasAppeared)
|
220
|
+
SHARED.indexScannedText.connect(self.projView.updateItemValues)
|
223
221
|
SHARED.mainClockTick.connect(self._timeTick)
|
222
|
+
SHARED.projectStatusChanged.connect(self.mainStatus.updateProjectStatus)
|
223
|
+
SHARED.projectStatusMessage.connect(self.mainStatus.setStatusMessage)
|
224
|
+
SHARED.spellLanguageChanged.connect(self.mainStatus.setLanguage)
|
224
225
|
|
225
226
|
self.mainMenu.requestDocAction.connect(self._passDocumentAction)
|
226
227
|
self.mainMenu.requestDocInsert.connect(self._passDocumentInsert)
|
227
228
|
self.mainMenu.requestDocInsertText.connect(self._passDocumentInsert)
|
228
229
|
self.mainMenu.requestDocKeyWordInsert.connect(self.docEditor.insertKeyWord)
|
229
|
-
self.mainMenu.requestFocusChange.connect(self.
|
230
|
+
self.mainMenu.requestFocusChange.connect(self._switchFocus)
|
230
231
|
self.mainMenu.requestViewChange.connect(self._changeView)
|
231
232
|
|
232
233
|
self.sideBar.requestViewChange.connect(self._changeView)
|
233
234
|
|
234
|
-
self.projView.selectedItemChanged.connect(self.itemDetails.updateViewBox)
|
235
235
|
self.projView.openDocumentRequest.connect(self._openDocument)
|
236
|
-
self.projView.
|
236
|
+
self.projView.projectSettingsRequest.connect(self.showProjectSettingsDialog)
|
237
|
+
self.projView.rootFolderChanged.connect(self.novelView.updateRootItem)
|
238
|
+
self.projView.rootFolderChanged.connect(self.outlineView.updateRootItem)
|
239
|
+
self.projView.rootFolderChanged.connect(self.projView.updateRootItem)
|
240
|
+
self.projView.selectedItemChanged.connect(self.itemDetails.updateViewBox)
|
237
241
|
self.projView.treeItemChanged.connect(self.docEditor.updateDocInfo)
|
238
242
|
self.projView.treeItemChanged.connect(self.docViewer.updateDocInfo)
|
239
|
-
self.projView.treeItemChanged.connect(self.itemDetails.updateViewBox)
|
240
243
|
self.projView.treeItemChanged.connect(self.docViewerPanel.projectItemChanged)
|
241
|
-
self.projView.
|
242
|
-
self.projView.
|
243
|
-
self.projView.rootFolderChanged.connect(self.projView.updateRootItem)
|
244
|
-
self.projView.projectSettingsRequest.connect(self.showProjectSettingsDialog)
|
244
|
+
self.projView.treeItemChanged.connect(self.itemDetails.updateViewBox)
|
245
|
+
self.projView.wordCountsChanged.connect(self._updateStatusWordCount)
|
245
246
|
|
246
|
-
self.novelView.selectedItemChanged.connect(self.itemDetails.updateViewBox)
|
247
247
|
self.novelView.openDocumentRequest.connect(self._openDocument)
|
248
|
+
self.novelView.selectedItemChanged.connect(self.itemDetails.updateViewBox)
|
248
249
|
|
249
250
|
self.projSearch.openDocumentSelectRequest.connect(self._openDocumentSelection)
|
250
251
|
self.projSearch.selectedItemChanged.connect(self.itemDetails.updateViewBox)
|
251
252
|
|
252
|
-
self.docEditor.
|
253
|
+
self.docEditor.closeDocumentRequest.connect(self.closeDocEditor)
|
253
254
|
self.docEditor.docCountsChanged.connect(self.itemDetails.updateCounts)
|
254
255
|
self.docEditor.docCountsChanged.connect(self.projView.updateCounts)
|
256
|
+
self.docEditor.docTextChanged.connect(self.projSearch.textChanged)
|
257
|
+
self.docEditor.editedStatusChanged.connect(self.mainStatus.updateDocumentStatus)
|
255
258
|
self.docEditor.loadDocumentTagRequest.connect(self._followTag)
|
256
|
-
self.docEditor.novelStructureChanged.connect(self.novelView.refreshTree)
|
257
259
|
self.docEditor.novelItemMetaChanged.connect(self.novelView.updateNovelItemMeta)
|
258
|
-
self.docEditor.
|
260
|
+
self.docEditor.novelStructureChanged.connect(self.novelView.refreshTree)
|
261
|
+
self.docEditor.requestNewNoteCreation.connect(self.projView.createNewNote)
|
262
|
+
self.docEditor.requestNextDocument.connect(self.openNextDocument)
|
263
|
+
self.docEditor.requestProjectItemRenamed.connect(self.projView.renameTreeItem)
|
264
|
+
self.docEditor.requestProjectItemSelected.connect(self.projView.setSelectedHandle)
|
259
265
|
self.docEditor.spellCheckStateChanged.connect(self.mainMenu.setSpellCheckState)
|
260
|
-
self.docEditor.
|
266
|
+
self.docEditor.statusMessage.connect(self.mainStatus.setStatusMessage)
|
261
267
|
self.docEditor.toggleFocusModeRequest.connect(self.toggleFocusMode)
|
262
|
-
self.docEditor.requestProjectItemSelected.connect(self.projView.setSelectedHandle)
|
263
|
-
self.docEditor.requestProjectItemRenamed.connect(self.projView.renameTreeItem)
|
264
|
-
self.docEditor.requestNewNoteCreation.connect(self.projView.createNewNote)
|
265
|
-
self.docEditor.docTextChanged.connect(self.projSearch.textChanged)
|
266
268
|
|
269
|
+
self.docViewer.closeDocumentRequest.connect(self.closeDocViewer)
|
267
270
|
self.docViewer.documentLoaded.connect(self.docViewerPanel.updateHandle)
|
268
271
|
self.docViewer.loadDocumentTagRequest.connect(self._followTag)
|
269
|
-
self.docViewer.closeDocumentRequest.connect(self.closeDocViewer)
|
270
272
|
self.docViewer.reloadDocumentRequest.connect(self._reloadViewer)
|
271
|
-
self.docViewer.togglePanelVisibility.connect(self._toggleViewerPanelVisibility)
|
272
273
|
self.docViewer.requestProjectItemSelected.connect(self.projView.setSelectedHandle)
|
274
|
+
self.docViewer.togglePanelVisibility.connect(self._toggleViewerPanelVisibility)
|
273
275
|
|
274
276
|
self.docViewerPanel.loadDocumentTagRequest.connect(self._followTag)
|
275
277
|
self.docViewerPanel.openDocumentRequest.connect(self._openDocument)
|
@@ -303,9 +305,6 @@ class GuiMain(QMainWindow):
|
|
303
305
|
self.keyEscape.setKey(Qt.Key.Key_Escape)
|
304
306
|
self.keyEscape.activated.connect(self._keyPressEscape)
|
305
307
|
|
306
|
-
# Check that config loaded fine
|
307
|
-
self.reportConfErr()
|
308
|
-
|
309
308
|
# Initialise Main GUI
|
310
309
|
self.initMain()
|
311
310
|
self.asProjTimer.start()
|
@@ -327,6 +326,15 @@ class GuiMain(QMainWindow):
|
|
327
326
|
|
328
327
|
def postLaunchTasks(self, cmdOpen: str | None) -> None:
|
329
328
|
"""Process tasks after the main window has been created."""
|
329
|
+
QApplication.processEvents()
|
330
|
+
app = QApplication.instance()
|
331
|
+
if isinstance(app, QApplication):
|
332
|
+
app.focusChanged.connect(self._appFocusChanged)
|
333
|
+
|
334
|
+
# Check that config loaded fine
|
335
|
+
if CONFIG.hasError:
|
336
|
+
SHARED.error(CONFIG.errorText())
|
337
|
+
|
330
338
|
if cmdOpen:
|
331
339
|
QApplication.processEvents()
|
332
340
|
logger.info("Command line path: %s", cmdOpen)
|
@@ -369,9 +377,7 @@ class GuiMain(QMainWindow):
|
|
369
377
|
if not msgYes:
|
370
378
|
return False
|
371
379
|
|
372
|
-
|
373
|
-
self.saveDocument()
|
374
|
-
|
380
|
+
self.saveDocument()
|
375
381
|
saveOK = self.saveProject()
|
376
382
|
doBackup = False
|
377
383
|
if SHARED.project.data.doBackup and CONFIG.backupOnClose:
|
@@ -514,9 +520,7 @@ class GuiMain(QMainWindow):
|
|
514
520
|
# Disable focus mode if it is active
|
515
521
|
if SHARED.focusMode:
|
516
522
|
SHARED.setFocusMode(False)
|
517
|
-
self.
|
518
|
-
if self.docEditor.docChanged:
|
519
|
-
self.saveDocument()
|
523
|
+
self.saveDocument()
|
520
524
|
self.docEditor.clearEditor()
|
521
525
|
if not beforeOpen:
|
522
526
|
self.novelView.setActiveHandle(None)
|
@@ -553,42 +557,43 @@ class GuiMain(QMainWindow):
|
|
553
557
|
|
554
558
|
return True
|
555
559
|
|
556
|
-
|
560
|
+
@pyqtSlot(str, bool)
|
561
|
+
def openNextDocument(self, tHandle: str, wrapAround: bool) -> None:
|
557
562
|
"""Open the next document in the project tree, following the
|
558
563
|
document with the given handle. Stop when reaching the end.
|
559
564
|
"""
|
560
|
-
if
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
nHandle =
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
self.openDocument(nHandle, tLine=1, doScroll=True)
|
580
|
-
return True
|
581
|
-
elif wrapAround:
|
582
|
-
self.openDocument(fHandle, tLine=1, doScroll=True)
|
583
|
-
return False
|
584
|
-
|
585
|
-
return False
|
565
|
+
if SHARED.hasProject:
|
566
|
+
nHandle = None # The next handle after tHandle
|
567
|
+
fHandle = None # The first file handle we encounter
|
568
|
+
foundIt = False # We've found tHandle, pick the next we see
|
569
|
+
for tItem in SHARED.project.tree:
|
570
|
+
if not tItem.isFileType():
|
571
|
+
continue
|
572
|
+
if fHandle is None:
|
573
|
+
fHandle = tItem.itemHandle
|
574
|
+
if tItem.itemHandle == tHandle:
|
575
|
+
foundIt = True
|
576
|
+
elif foundIt:
|
577
|
+
nHandle = tItem.itemHandle
|
578
|
+
break
|
579
|
+
if nHandle is not None:
|
580
|
+
self.openDocument(nHandle, tLine=1, doScroll=True)
|
581
|
+
elif wrapAround:
|
582
|
+
self.openDocument(fHandle, tLine=1, doScroll=True)
|
583
|
+
return
|
586
584
|
|
587
|
-
|
588
|
-
def saveDocument(self) -> None:
|
585
|
+
def saveDocument(self, force: bool = False) -> None:
|
589
586
|
"""Save the current documents."""
|
590
587
|
if SHARED.hasProject:
|
591
|
-
self.docEditor.
|
588
|
+
self.docEditor.saveCursorPosition()
|
589
|
+
if force or self.docEditor.docChanged:
|
590
|
+
self.docEditor.saveText()
|
591
|
+
return
|
592
|
+
|
593
|
+
@pyqtSlot()
|
594
|
+
def forceSaveDocument(self) -> None:
|
595
|
+
"""Save document even of it has not changed."""
|
596
|
+
self.saveDocument(force=True)
|
592
597
|
return
|
593
598
|
|
594
599
|
def viewDocument(self, tHandle: str | None = None, sTitle: str | None = None) -> bool:
|
@@ -618,6 +623,10 @@ class GuiMain(QMainWindow):
|
|
618
623
|
# Make sure main tab is in Editor view
|
619
624
|
self._changeView(nwView.EDITOR)
|
620
625
|
|
626
|
+
# If we're loading the document in the editor, it may need to be saved
|
627
|
+
if tHandle == self.docEditor.docHandle and self.docEditor.docChanged:
|
628
|
+
self.saveDocument()
|
629
|
+
|
621
630
|
logger.debug("Viewing document with handle '%s'", tHandle)
|
622
631
|
updateHistory = tHandle != self.docViewer.docHandle
|
623
632
|
if self.docViewer.loadText(tHandle, updateHistory=updateHistory):
|
@@ -649,7 +658,7 @@ class GuiMain(QMainWindow):
|
|
649
658
|
logger.error("No project open")
|
650
659
|
return False
|
651
660
|
|
652
|
-
lastPath = CONFIG.lastPath()
|
661
|
+
lastPath = CONFIG.lastPath("import")
|
653
662
|
ffilter = formatFileFilter(["*.txt", "*.md", "*.nwd", "*"])
|
654
663
|
loadFile, _ = QFileDialog.getOpenFileName(
|
655
664
|
self, self.tr("Import File"), str(lastPath), filter=ffilter
|
@@ -664,7 +673,7 @@ class GuiMain(QMainWindow):
|
|
664
673
|
try:
|
665
674
|
with open(loadFile, mode="rt", encoding="utf-8") as inFile:
|
666
675
|
text = inFile.read()
|
667
|
-
CONFIG.setLastPath(loadFile)
|
676
|
+
CONFIG.setLastPath("import", loadFile)
|
668
677
|
except Exception as exc:
|
669
678
|
SHARED.error(self.tr(
|
670
679
|
"Could not read file. The file must be an existing text file."
|
@@ -721,14 +730,6 @@ class GuiMain(QMainWindow):
|
|
721
730
|
|
722
731
|
return
|
723
732
|
|
724
|
-
def editItemLabel(self, tHandle: str | None = None) -> None:
|
725
|
-
"""Open the edit item dialog."""
|
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
|
731
|
-
|
732
733
|
def rebuildTrees(self) -> None:
|
733
734
|
"""Rebuild the project tree."""
|
734
735
|
self.projView.populateTree()
|
@@ -750,7 +751,6 @@ class GuiMain(QMainWindow):
|
|
750
751
|
self.mainStatus.setStatusMessage(
|
751
752
|
self.tr("Indexing completed in {0} ms").format(f"{(tEnd - tStart)*1000.0:.1f}")
|
752
753
|
)
|
753
|
-
self.docEditor.updateTagHighLighting()
|
754
754
|
self._updateStatusWordCount()
|
755
755
|
QApplication.restoreOverrideCursor()
|
756
756
|
|
@@ -794,10 +794,7 @@ class GuiMain(QMainWindow):
|
|
794
794
|
"""Open the novel details dialog."""
|
795
795
|
if SHARED.hasProject:
|
796
796
|
dialog = GuiNovelDetails(self)
|
797
|
-
dialog.
|
798
|
-
dialog.show()
|
799
|
-
dialog.raise_()
|
800
|
-
QApplication.processEvents()
|
797
|
+
dialog.activateDialog()
|
801
798
|
dialog.updateValues()
|
802
799
|
return
|
803
800
|
|
@@ -805,12 +802,9 @@ class GuiMain(QMainWindow):
|
|
805
802
|
def showBuildManuscriptDialog(self) -> None:
|
806
803
|
"""Open the build manuscript dialog."""
|
807
804
|
if SHARED.hasProject:
|
808
|
-
if (dialog := SHARED.findTopLevelWidget(GuiManuscript))
|
805
|
+
if not (dialog := SHARED.findTopLevelWidget(GuiManuscript)):
|
809
806
|
dialog = GuiManuscript(self)
|
810
|
-
dialog.
|
811
|
-
dialog.show()
|
812
|
-
dialog.raise_()
|
813
|
-
QApplication.processEvents()
|
807
|
+
dialog.activateDialog()
|
814
808
|
dialog.loadContent()
|
815
809
|
return
|
816
810
|
|
@@ -827,12 +821,9 @@ class GuiMain(QMainWindow):
|
|
827
821
|
def showWritingStatsDialog(self) -> None:
|
828
822
|
"""Open the session stats dialog."""
|
829
823
|
if SHARED.hasProject:
|
830
|
-
if (dialog := SHARED.findTopLevelWidget(GuiWritingStats))
|
824
|
+
if not (dialog := SHARED.findTopLevelWidget(GuiWritingStats)):
|
831
825
|
dialog = GuiWritingStats(self)
|
832
|
-
dialog.
|
833
|
-
dialog.show()
|
834
|
-
dialog.raise_()
|
835
|
-
QApplication.processEvents()
|
826
|
+
dialog.activateDialog()
|
836
827
|
dialog.populateGUI()
|
837
828
|
return
|
838
829
|
|
@@ -840,11 +831,7 @@ class GuiMain(QMainWindow):
|
|
840
831
|
def showAboutNWDialog(self) -> None:
|
841
832
|
"""Show the novelWriter about dialog."""
|
842
833
|
dialog = GuiAbout(self)
|
843
|
-
dialog.
|
844
|
-
dialog.show()
|
845
|
-
dialog.raise_()
|
846
|
-
QApplication.processEvents()
|
847
|
-
dialog.populateGUI()
|
834
|
+
dialog.exec()
|
848
835
|
return
|
849
836
|
|
850
837
|
@pyqtSlot()
|
@@ -858,24 +845,12 @@ class GuiMain(QMainWindow):
|
|
858
845
|
def showDictionariesDialog(self) -> None:
|
859
846
|
"""Show the download dictionaries dialog."""
|
860
847
|
dialog = GuiDictionaries(self)
|
861
|
-
dialog.
|
862
|
-
dialog.show()
|
863
|
-
dialog.raise_()
|
864
|
-
QApplication.processEvents()
|
848
|
+
dialog.activateDialog()
|
865
849
|
if not dialog.initDialog():
|
866
850
|
dialog.close()
|
867
851
|
SHARED.error(self.tr("Could not initialise the dialog."))
|
868
852
|
return
|
869
853
|
|
870
|
-
def reportConfErr(self) -> None:
|
871
|
-
"""Checks if the Config module has any errors to report, and let
|
872
|
-
the user know if this is the case. The Config module caches
|
873
|
-
errors since it is initialised before the GUI itself.
|
874
|
-
"""
|
875
|
-
if CONFIG.hasError:
|
876
|
-
SHARED.error(CONFIG.errorText())
|
877
|
-
return
|
878
|
-
|
879
854
|
##
|
880
855
|
# Main Window Actions
|
881
856
|
##
|
@@ -906,9 +881,7 @@ class GuiMain(QMainWindow):
|
|
906
881
|
|
907
882
|
if SHARED.hasProject:
|
908
883
|
self.closeProject(True)
|
909
|
-
|
910
884
|
CONFIG.saveConfig()
|
911
|
-
self.reportConfErr()
|
912
885
|
|
913
886
|
QApplication.quit()
|
914
887
|
|
@@ -976,14 +949,37 @@ class GuiMain(QMainWindow):
|
|
976
949
|
SHARED.setFocusMode(not SHARED.focusMode)
|
977
950
|
return
|
978
951
|
|
952
|
+
##
|
953
|
+
# Private Slots
|
954
|
+
##
|
955
|
+
|
956
|
+
@pyqtSlot("QWidget*", "QWidget*")
|
957
|
+
def _appFocusChanged(self, old: QWidget, new: QWidget) -> None:
|
958
|
+
"""Alert main widgets that they have received or lost focus."""
|
959
|
+
if isinstance(new, QWidget):
|
960
|
+
docEditor = False
|
961
|
+
docViewer = False
|
962
|
+
if self.docEditor.isAncestorOf(new):
|
963
|
+
docEditor = True
|
964
|
+
elif self.docViewer.isAncestorOf(new):
|
965
|
+
docViewer = True
|
966
|
+
|
967
|
+
self.docEditor.changeFocusState(docEditor)
|
968
|
+
self.docViewer.changeFocusState(docViewer)
|
969
|
+
|
970
|
+
logger.debug("Main focus switched to: %s", type(new).__name__)
|
971
|
+
|
972
|
+
return
|
973
|
+
|
979
974
|
@pyqtSlot(bool)
|
980
975
|
def _focusModeChanged(self, focusMode: bool) -> None:
|
981
|
-
"""Handle change of focus mode. The Main GUI Focus Mode hides
|
982
|
-
view, statusbar and menu.
|
976
|
+
"""Handle change of focus mode. The Main GUI Focus Mode hides
|
977
|
+
tree, view, statusbar and menu.
|
983
978
|
"""
|
984
979
|
if focusMode:
|
985
980
|
logger.debug("Activating Focus Mode")
|
986
|
-
self.
|
981
|
+
self._changeView(nwView.EDITOR)
|
982
|
+
self.docEditor.setFocus()
|
987
983
|
else:
|
988
984
|
logger.debug("Deactivating Focus Mode")
|
989
985
|
|
@@ -1006,10 +1002,10 @@ class GuiMain(QMainWindow):
|
|
1006
1002
|
self.docEditor.ensureCursorVisibleNoCentre()
|
1007
1003
|
return
|
1008
1004
|
|
1009
|
-
@pyqtSlot(
|
1010
|
-
def
|
1005
|
+
@pyqtSlot(nwFocus)
|
1006
|
+
def _switchFocus(self, paneNo: nwFocus) -> None:
|
1011
1007
|
"""Switch focus between main GUI views."""
|
1012
|
-
if paneNo ==
|
1008
|
+
if paneNo == nwFocus.TREE:
|
1013
1009
|
if self.projStack.currentWidget() is self.projView:
|
1014
1010
|
if self.projView.treeHasFocus():
|
1015
1011
|
self._changeView(nwView.NOVEL)
|
@@ -1025,21 +1021,19 @@ class GuiMain(QMainWindow):
|
|
1025
1021
|
else:
|
1026
1022
|
self._changeView(nwView.PROJECT)
|
1027
1023
|
self.projView.setTreeFocus()
|
1028
|
-
elif paneNo ==
|
1029
|
-
self._changeView(nwView.EDITOR)
|
1030
|
-
self.docEditor.setFocus()
|
1031
|
-
elif paneNo == nwWidget.VIEWER:
|
1024
|
+
elif paneNo == nwFocus.DOCUMENT:
|
1032
1025
|
self._changeView(nwView.EDITOR)
|
1033
|
-
self.
|
1034
|
-
|
1026
|
+
if self.docEditor.anyFocus():
|
1027
|
+
self.docViewer.setFocus()
|
1028
|
+
elif self.docViewer.anyFocus():
|
1029
|
+
self.docEditor.setFocus()
|
1030
|
+
else:
|
1031
|
+
self.docEditor.setFocus()
|
1032
|
+
elif paneNo == nwFocus.OUTLINE:
|
1035
1033
|
self._changeView(nwView.OUTLINE)
|
1036
1034
|
self.outlineView.setTreeFocus()
|
1037
1035
|
return
|
1038
1036
|
|
1039
|
-
##
|
1040
|
-
# Private Slots
|
1041
|
-
##
|
1042
|
-
|
1043
1037
|
@pyqtSlot(bool, bool, bool, bool)
|
1044
1038
|
def _processConfigChanges(self, restart: bool, tree: bool, theme: bool, syntax: bool) -> None:
|
1045
1039
|
"""Refresh GUI based on flags from the Preferences dialog."""
|
@@ -1148,7 +1142,7 @@ class GuiMain(QMainWindow):
|
|
1148
1142
|
@pyqtSlot()
|
1149
1143
|
def _reloadViewer(self) -> None:
|
1150
1144
|
"""Reload the document in the viewer."""
|
1151
|
-
if self.docEditor.
|
1145
|
+
if self.docEditor.docHandle == self.docViewer.docHandle:
|
1152
1146
|
# If the two panels have the same document, save any changes in the editor
|
1153
1147
|
self.saveDocument()
|
1154
1148
|
self.docViewer.reloadText()
|
@@ -1178,16 +1172,13 @@ class GuiMain(QMainWindow):
|
|
1178
1172
|
|
1179
1173
|
@pyqtSlot(nwDocAction)
|
1180
1174
|
def _passDocumentAction(self, action: nwDocAction) -> None:
|
1181
|
-
"""Pass on a document action to the
|
1182
|
-
focus, or
|
1183
|
-
child widgets have focus. If neither has focus, ignore it.
|
1175
|
+
"""Pass on a document action to the editor or viewer based on
|
1176
|
+
which one has focus, or if neither has focus, ignore it.
|
1184
1177
|
"""
|
1185
|
-
if self.
|
1186
|
-
self.docViewer.docAction(action)
|
1187
|
-
elif self.docEditor.hasFocus():
|
1178
|
+
if self.docEditor.hasFocus():
|
1188
1179
|
self.docEditor.docAction(action)
|
1189
|
-
|
1190
|
-
|
1180
|
+
elif self.docViewer.hasFocus():
|
1181
|
+
self.docViewer.docAction(action)
|
1191
1182
|
return
|
1192
1183
|
|
1193
1184
|
@pyqtSlot(str)
|
@@ -1228,7 +1219,7 @@ class GuiMain(QMainWindow):
|
|
1228
1219
|
doSave &= SHARED.project.projChanged
|
1229
1220
|
doSave &= SHARED.project.storage.isOpen()
|
1230
1221
|
if doSave:
|
1231
|
-
logger.debug("
|
1222
|
+
logger.debug("Auto-saving project")
|
1232
1223
|
self.saveProject(autoSave=True)
|
1233
1224
|
return
|
1234
1225
|
|
@@ -1236,7 +1227,7 @@ class GuiMain(QMainWindow):
|
|
1236
1227
|
def _autoSaveDocument(self) -> None:
|
1237
1228
|
"""Autosave of the document. This is a timer-activated slot."""
|
1238
1229
|
if SHARED.hasProject and self.docEditor.docChanged:
|
1239
|
-
logger.debug("
|
1230
|
+
logger.debug("Auto-saving document")
|
1240
1231
|
self.saveDocument()
|
1241
1232
|
return
|
1242
1233
|
|
@@ -1395,10 +1386,7 @@ class GuiMain(QMainWindow):
|
|
1395
1386
|
|
1396
1387
|
def _updateWindowTitle(self, projName: str | None = None) -> None:
|
1397
1388
|
"""Set the window title and add the project's name."""
|
1398
|
-
|
1399
|
-
if projName is not None:
|
1400
|
-
winTitle += " - %s" % projName
|
1401
|
-
self.setWindowTitle(winTitle)
|
1389
|
+
self.setWindowTitle(" - ".join(filter(None, [projName, CONFIG.appName])))
|
1402
1390
|
return
|
1403
1391
|
|
1404
1392
|
def _getTagSource(self, tag: str) -> tuple[str | None, str | None]:
|
@@ -1416,5 +1404,3 @@ class GuiMain(QMainWindow):
|
|
1416
1404
|
))
|
1417
1405
|
return None, None
|
1418
1406
|
return tHandle, sTitle
|
1419
|
-
|
1420
|
-
# END Class GuiMain
|
novelwriter/shared.py
CHANGED
@@ -31,7 +31,8 @@ from time import time
|
|
31
31
|
from typing import TYPE_CHECKING, TypeVar
|
32
32
|
|
33
33
|
from PyQt5.QtCore import QObject, QRunnable, QThreadPool, QTimer, pyqtSignal
|
34
|
-
from PyQt5.
|
34
|
+
from PyQt5.QtGui import QFont
|
35
|
+
from PyQt5.QtWidgets import QFileDialog, QFontDialog, QMessageBox, QWidget
|
35
36
|
|
36
37
|
from novelwriter.common import formatFileFilter
|
37
38
|
from novelwriter.constants import nwFiles
|
@@ -171,6 +172,23 @@ class SharedData(QObject):
|
|
171
172
|
logger.debug("Thread Pool Max Count: %d", QThreadPool.globalInstance().maxThreadCount())
|
172
173
|
return
|
173
174
|
|
175
|
+
def closeEditor(self, tHandle: str | None = None) -> None:
|
176
|
+
"""Close the document editor, optionally a specific document."""
|
177
|
+
if tHandle is None or tHandle == self.mainGui.docEditor.docHandle:
|
178
|
+
self.mainGui.closeDocument()
|
179
|
+
return
|
180
|
+
|
181
|
+
def saveEditor(self, tHandle: str | None = None) -> None:
|
182
|
+
"""Save the editor content, optionally a specific document."""
|
183
|
+
docEditor = self.mainGui.docEditor
|
184
|
+
if (
|
185
|
+
self.hasProject and docEditor.docHandle
|
186
|
+
and (tHandle is None or tHandle == docEditor.docHandle)
|
187
|
+
):
|
188
|
+
logger.debug("Saving editor document before action")
|
189
|
+
docEditor.saveText()
|
190
|
+
return
|
191
|
+
|
174
192
|
def openProject(self, path: str | Path, clearLock: bool = False) -> bool:
|
175
193
|
"""Open a project."""
|
176
194
|
if self.project.isValid:
|
@@ -198,25 +216,12 @@ class SharedData(QObject):
|
|
198
216
|
|
199
217
|
def closeProject(self) -> None:
|
200
218
|
"""Close the current project."""
|
201
|
-
self.
|
219
|
+
self._closeToolDialogs()
|
202
220
|
self.project.closeProject(self._idleTime)
|
203
221
|
self._resetProject()
|
204
222
|
self._resetIdleTimer()
|
205
223
|
return
|
206
224
|
|
207
|
-
def ensureEditorSaved(self, tHandle: str | None) -> None:
|
208
|
-
"""Ensure that the editor content is saved. Optionally, only if
|
209
|
-
it is a specific handle.
|
210
|
-
"""
|
211
|
-
docEditor = self.mainGui.docEditor
|
212
|
-
if (
|
213
|
-
self.hasProject and docEditor.docHandle
|
214
|
-
and (tHandle is None or tHandle == docEditor.docHandle)
|
215
|
-
):
|
216
|
-
logger.debug("Saving editor document before action")
|
217
|
-
docEditor.saveText()
|
218
|
-
return
|
219
|
-
|
220
225
|
def updateSpellCheckLanguage(self, reload: bool = False) -> None:
|
221
226
|
"""Update the active spell check language from settings."""
|
222
227
|
from novelwriter import CONFIG
|
@@ -257,8 +262,11 @@ class SharedData(QObject):
|
|
257
262
|
QThreadPool.globalInstance().start(runnable, priority=priority)
|
258
263
|
return
|
259
264
|
|
260
|
-
def getProjectPath(
|
261
|
-
|
265
|
+
def getProjectPath(
|
266
|
+
self, parent: QWidget,
|
267
|
+
path: str | Path | None = None,
|
268
|
+
allowZip: bool = False
|
269
|
+
) -> Path | None:
|
262
270
|
"""Open the file dialog and select a novelWriter project file."""
|
263
271
|
label = (self.tr("novelWriter Project File or Zip File")
|
264
272
|
if allowZip else self.tr("novelWriter Project File"))
|
@@ -269,6 +277,13 @@ class SharedData(QObject):
|
|
269
277
|
)
|
270
278
|
return Path(selected) if selected else None
|
271
279
|
|
280
|
+
def getFont(self, current: QFont, native: bool) -> tuple[QFont, bool]:
|
281
|
+
"""Open the font dialog and select a font."""
|
282
|
+
kwargs = {}
|
283
|
+
if not native:
|
284
|
+
kwargs["options"] = QFontDialog.FontDialogOption.DontUseNativeDialog
|
285
|
+
return QFontDialog.getFont(current, self.mainGui, self.tr("Select Font"), **kwargs)
|
286
|
+
|
272
287
|
def findTopLevelWidget(self, kind: type[NWWidget]) -> NWWidget | None:
|
273
288
|
"""Find a top level widget."""
|
274
289
|
for widget in self.mainGui.children():
|
@@ -307,7 +322,6 @@ class SharedData(QObject):
|
|
307
322
|
if log:
|
308
323
|
logger.info(self._lastAlert, stacklevel=2)
|
309
324
|
alert.exec()
|
310
|
-
alert.deleteLater()
|
311
325
|
return
|
312
326
|
|
313
327
|
def warn(self, text: str, info: str = "", details: str = "", log: bool = True) -> None:
|
@@ -319,7 +333,6 @@ class SharedData(QObject):
|
|
319
333
|
if log:
|
320
334
|
logger.warning(self._lastAlert, stacklevel=2)
|
321
335
|
alert.exec()
|
322
|
-
alert.deleteLater()
|
323
336
|
return
|
324
337
|
|
325
338
|
def error(self, text: str, info: str = "", details: str = "", log: bool = True,
|
@@ -334,7 +347,6 @@ class SharedData(QObject):
|
|
334
347
|
if log:
|
335
348
|
logger.error(self._lastAlert, stacklevel=2)
|
336
349
|
alert.exec()
|
337
|
-
alert.deleteLater()
|
338
350
|
return
|
339
351
|
|
340
352
|
def question(self, text: str, info: str = "", details: str = "", warn: bool = False) -> bool:
|
@@ -345,7 +357,6 @@ class SharedData(QObject):
|
|
345
357
|
self._lastAlert = alert.logMessage
|
346
358
|
alert.exec()
|
347
359
|
isYes = alert.result() == QMessageBox.StandardButton.Yes
|
348
|
-
alert.deleteLater()
|
349
360
|
return isYes
|
350
361
|
|
351
362
|
##
|
@@ -370,19 +381,14 @@ class SharedData(QObject):
|
|
370
381
|
self._idleTime = 0.0
|
371
382
|
return
|
372
383
|
|
373
|
-
def
|
374
|
-
"""Close
|
375
|
-
from novelwriter.
|
376
|
-
from novelwriter.tools.writingstats import GuiWritingStats
|
377
|
-
|
384
|
+
def _closeToolDialogs(self) -> None:
|
385
|
+
"""Close all open tool dialogs."""
|
386
|
+
from novelwriter.extensions.modified import NToolDialog
|
378
387
|
for widget in self.mainGui.children():
|
379
|
-
if isinstance(widget,
|
388
|
+
if isinstance(widget, NToolDialog):
|
380
389
|
widget.close()
|
381
|
-
|
382
390
|
return
|
383
391
|
|
384
|
-
# END Class SharedData
|
385
|
-
|
386
392
|
|
387
393
|
class _GuiAlert(QMessageBox):
|
388
394
|
|
@@ -443,5 +449,3 @@ class _GuiAlert(QMessageBox):
|
|
443
449
|
self.setIconPixmap(self._theme.getPixmap("alert_question", (pSz, pSz)))
|
444
450
|
self.setWindowTitle(self.tr("Question"))
|
445
451
|
return
|
446
|
-
|
447
|
-
# END Class _GuiAlert
|