novelWriter 2.2.1__py3-none-any.whl → 2.3b1__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.2.1.dist-info → novelWriter-2.3b1.dist-info}/METADATA +1 -1
- {novelWriter-2.2.1.dist-info → novelWriter-2.3b1.dist-info}/RECORD +102 -92
- novelwriter/__init__.py +4 -4
- novelwriter/assets/icons/typicons_dark/icons.conf +6 -0
- novelwriter/assets/icons/typicons_dark/mixed_import.svg +5 -0
- novelwriter/assets/icons/typicons_dark/typ_document-add-col.svg +8 -0
- novelwriter/assets/icons/typicons_dark/typ_document-add.svg +4 -0
- novelwriter/assets/icons/typicons_dark/typ_document.svg +4 -0
- novelwriter/assets/icons/typicons_dark/typ_th-dot-more.svg +4 -0
- novelwriter/assets/icons/typicons_light/icons.conf +6 -0
- novelwriter/assets/icons/typicons_light/mixed_import.svg +5 -0
- novelwriter/assets/icons/typicons_light/typ_document-add-col.svg +8 -0
- novelwriter/assets/icons/typicons_light/typ_document-add.svg +4 -0
- novelwriter/assets/icons/typicons_light/typ_document.svg +4 -0
- novelwriter/assets/icons/typicons_light/typ_th-dot-more.svg +4 -0
- novelwriter/assets/images/novelwriter-text-dark.svg +4 -0
- novelwriter/assets/images/novelwriter-text-light.svg +4 -0
- novelwriter/assets/images/welcome-dark.jpg +0 -0
- novelwriter/assets/images/welcome-light.jpg +0 -0
- novelwriter/assets/manual.pdf +0 -0
- novelwriter/assets/sample.zip +0 -0
- novelwriter/assets/syntax/default_dark.conf +1 -0
- novelwriter/assets/syntax/default_light.conf +1 -0
- novelwriter/assets/syntax/grey_dark.conf +1 -0
- novelwriter/assets/syntax/grey_light.conf +1 -0
- novelwriter/assets/syntax/light_owl.conf +1 -0
- novelwriter/assets/syntax/night_owl.conf +1 -0
- novelwriter/assets/syntax/solarized_dark.conf +1 -0
- novelwriter/assets/syntax/solarized_light.conf +1 -0
- novelwriter/assets/syntax/tomorrow.conf +1 -0
- novelwriter/assets/syntax/tomorrow_night.conf +1 -0
- novelwriter/assets/syntax/tomorrow_night_blue.conf +1 -0
- novelwriter/assets/syntax/tomorrow_night_bright.conf +1 -0
- novelwriter/assets/syntax/tomorrow_night_eighties.conf +1 -0
- novelwriter/assets/text/credits_en.htm +4 -2
- novelwriter/assets/themes/default_dark.conf +2 -2
- novelwriter/assets/themes/default_light.conf +2 -2
- novelwriter/common.py +48 -37
- novelwriter/config.py +36 -41
- novelwriter/constants.py +38 -16
- novelwriter/core/buildsettings.py +7 -7
- novelwriter/core/coretools.py +192 -154
- novelwriter/core/docbuild.py +6 -3
- novelwriter/core/document.py +6 -6
- novelwriter/core/index.py +89 -56
- novelwriter/core/item.py +21 -3
- novelwriter/core/options.py +8 -7
- novelwriter/core/project.py +69 -44
- novelwriter/core/projectdata.py +1 -14
- novelwriter/core/projectxml.py +13 -41
- novelwriter/core/sessions.py +2 -1
- novelwriter/core/spellcheck.py +2 -1
- novelwriter/core/status.py +2 -1
- novelwriter/core/storage.py +178 -140
- novelwriter/core/tohtml.py +4 -2
- novelwriter/core/tokenizer.py +73 -45
- novelwriter/core/toodt.py +40 -30
- novelwriter/core/tree.py +3 -2
- novelwriter/dialogs/about.py +70 -160
- novelwriter/dialogs/docmerge.py +6 -5
- novelwriter/dialogs/docsplit.py +6 -6
- novelwriter/dialogs/editlabel.py +1 -1
- novelwriter/dialogs/preferences.py +553 -703
- novelwriter/dialogs/{projsettings.py → projectsettings.py} +288 -262
- novelwriter/dialogs/quotes.py +27 -23
- novelwriter/dialogs/wordlist.py +96 -40
- novelwriter/enum.py +20 -18
- novelwriter/error.py +1 -1
- novelwriter/extensions/circularprogress.py +11 -11
- novelwriter/extensions/configlayout.py +185 -134
- novelwriter/extensions/modified.py +81 -0
- novelwriter/extensions/novelselector.py +26 -12
- novelwriter/extensions/pagedsidebar.py +14 -16
- novelwriter/extensions/simpleprogress.py +5 -5
- novelwriter/extensions/statusled.py +8 -8
- novelwriter/extensions/switch.py +31 -63
- novelwriter/extensions/switchbox.py +1 -1
- novelwriter/extensions/versioninfo.py +153 -0
- novelwriter/gui/doceditor.py +178 -150
- novelwriter/gui/dochighlight.py +63 -92
- novelwriter/gui/docviewer.py +49 -51
- novelwriter/gui/docviewerpanel.py +72 -24
- novelwriter/gui/itemdetails.py +7 -7
- novelwriter/gui/mainmenu.py +14 -18
- novelwriter/gui/noveltree.py +9 -8
- novelwriter/gui/outline.py +98 -75
- novelwriter/gui/projtree.py +188 -61
- novelwriter/gui/sidebar.py +3 -4
- novelwriter/gui/statusbar.py +3 -4
- novelwriter/gui/theme.py +60 -68
- novelwriter/guimain.py +49 -156
- novelwriter/shared.py +15 -1
- novelwriter/tools/dictionaries.py +5 -6
- novelwriter/tools/manuscript.py +6 -6
- novelwriter/tools/manussettings.py +192 -221
- novelwriter/tools/noveldetails.py +525 -0
- novelwriter/tools/welcome.py +802 -0
- novelwriter/tools/writingstats.py +9 -9
- novelwriter/assets/images/wizard-back.jpg +0 -0
- novelwriter/assets/text/gplv3_en.htm +0 -641
- novelwriter/assets/text/release_notes.htm +0 -60
- novelwriter/dialogs/projdetails.py +0 -518
- novelwriter/dialogs/projload.py +0 -294
- novelwriter/dialogs/updates.py +0 -172
- novelwriter/extensions/pageddialog.py +0 -130
- novelwriter/tools/projwizard.py +0 -478
- {novelWriter-2.2.1.dist-info → novelWriter-2.3b1.dist-info}/LICENSE.md +0 -0
- {novelWriter-2.2.1.dist-info → novelWriter-2.3b1.dist-info}/WHEEL +0 -0
- {novelWriter-2.2.1.dist-info → novelWriter-2.3b1.dist-info}/entry_points.txt +0 -0
- {novelWriter-2.2.1.dist-info → novelWriter-2.3b1.dist-info}/top_level.txt +0 -0
novelwriter/gui/theme.py
CHANGED
@@ -67,12 +67,13 @@ class GuiTheme:
|
|
67
67
|
self.themeLicense = ""
|
68
68
|
self.themeLicenseUrl = ""
|
69
69
|
self.themeIcons = ""
|
70
|
+
self.isLightTheme = True
|
70
71
|
|
71
72
|
# GUI
|
72
|
-
self.statNone =
|
73
|
-
self.statUnsaved =
|
74
|
-
self.statSaved =
|
75
|
-
self.helpText =
|
73
|
+
self.statNone = QColor(120, 120, 120)
|
74
|
+
self.statUnsaved = QColor(200, 15, 39)
|
75
|
+
self.statSaved = QColor(2, 133, 37)
|
76
|
+
self.helpText = QColor(0, 0, 0)
|
76
77
|
|
77
78
|
# Loaded Syntax Settings
|
78
79
|
# ======================
|
@@ -87,23 +88,24 @@ class GuiTheme:
|
|
87
88
|
self.syntaxLicenseUrl = ""
|
88
89
|
|
89
90
|
# Colours
|
90
|
-
self.colBack =
|
91
|
-
self.colText =
|
92
|
-
self.colLink =
|
93
|
-
self.colHead =
|
94
|
-
self.colHeadH =
|
95
|
-
self.colEmph =
|
96
|
-
self.colDialN =
|
97
|
-
self.colDialD =
|
98
|
-
self.colDialS =
|
99
|
-
self.colHidden =
|
100
|
-
self.colCode =
|
101
|
-
self.colKey =
|
102
|
-
self.colVal =
|
103
|
-
self.
|
104
|
-
self.
|
105
|
-
self.
|
106
|
-
self.
|
91
|
+
self.colBack = QColor(255, 255, 255)
|
92
|
+
self.colText = QColor(0, 0, 0)
|
93
|
+
self.colLink = QColor(0, 0, 0)
|
94
|
+
self.colHead = QColor(0, 0, 0)
|
95
|
+
self.colHeadH = QColor(0, 0, 0)
|
96
|
+
self.colEmph = QColor(0, 0, 0)
|
97
|
+
self.colDialN = QColor(0, 0, 0)
|
98
|
+
self.colDialD = QColor(0, 0, 0)
|
99
|
+
self.colDialS = QColor(0, 0, 0)
|
100
|
+
self.colHidden = QColor(0, 0, 0)
|
101
|
+
self.colCode = QColor(0, 0, 0)
|
102
|
+
self.colKey = QColor(0, 0, 0)
|
103
|
+
self.colVal = QColor(0, 0, 0)
|
104
|
+
self.colOpt = QColor(0, 0, 0)
|
105
|
+
self.colSpell = QColor(0, 0, 0)
|
106
|
+
self.colError = QColor(0, 0, 0)
|
107
|
+
self.colRepTag = QColor(0, 0, 0)
|
108
|
+
self.colMod = QColor(0, 0, 0)
|
107
109
|
|
108
110
|
# Class Setup
|
109
111
|
# ===========
|
@@ -112,7 +114,7 @@ class GuiTheme:
|
|
112
114
|
self._setGuiFont()
|
113
115
|
|
114
116
|
# Load Themes
|
115
|
-
self._guiPalette
|
117
|
+
self._guiPalette = QPalette()
|
116
118
|
self._themeList: list[tuple[str, str]] = []
|
117
119
|
self._syntaxList: list[tuple[str, str]] = []
|
118
120
|
self._availThemes: dict[str, Path] = {}
|
@@ -254,13 +256,13 @@ class GuiTheme:
|
|
254
256
|
|
255
257
|
backLNess = backCol.lightnessF()
|
256
258
|
textLNess = textCol.lightnessF()
|
257
|
-
|
258
|
-
if self.helpText ==
|
259
|
-
if
|
259
|
+
self.isLightTheme = backLNess > textLNess
|
260
|
+
if self.helpText == QColor(0, 0, 0):
|
261
|
+
if self.isLightTheme:
|
260
262
|
helpLCol = textLNess + 0.35*(backLNess - textLNess)
|
261
263
|
else:
|
262
264
|
helpLCol = backLNess + 0.65*(textLNess - backLNess)
|
263
|
-
self.helpText =
|
265
|
+
self.helpText = QColor.fromHsl(0, 0, int(255*helpLCol))
|
264
266
|
|
265
267
|
# Icons
|
266
268
|
defaultIcons = "typicons_light" if backLNess >= 0.5 else "typicons_dark"
|
@@ -322,6 +324,7 @@ class GuiTheme:
|
|
322
324
|
self.colCode = self._parseColour(confParser, cnfSec, "shortcode")
|
323
325
|
self.colKey = self._parseColour(confParser, cnfSec, "keyword")
|
324
326
|
self.colVal = self._parseColour(confParser, cnfSec, "value")
|
327
|
+
self.colOpt = self._parseColour(confParser, cnfSec, "optional")
|
325
328
|
self.colSpell = self._parseColour(confParser, cnfSec, "spellcheckline")
|
326
329
|
self.colError = self._parseColour(confParser, cnfSec, "errorline")
|
327
330
|
self.colRepTag = self._parseColour(confParser, cnfSec, "replacetag")
|
@@ -367,22 +370,22 @@ class GuiTheme:
|
|
367
370
|
|
368
371
|
def _setGuiFont(self) -> None:
|
369
372
|
"""Update the GUI's font style from settings."""
|
370
|
-
|
373
|
+
font = QFont()
|
371
374
|
fontDB = QFontDatabase()
|
372
375
|
if CONFIG.guiFont not in fontDB.families():
|
373
376
|
if CONFIG.osWindows and "Arial" in fontDB.families():
|
374
377
|
# On Windows we default to Arial if possible
|
375
|
-
|
376
|
-
|
378
|
+
font.setFamily("Arial")
|
379
|
+
font.setPointSize(10)
|
377
380
|
else:
|
378
|
-
|
379
|
-
CONFIG.guiFont =
|
380
|
-
CONFIG.guiFontSize =
|
381
|
+
font = fontDB.systemFont(QFontDatabase.GeneralFont)
|
382
|
+
CONFIG.guiFont = font.family()
|
383
|
+
CONFIG.guiFontSize = font.pointSize()
|
381
384
|
else:
|
382
|
-
|
383
|
-
|
385
|
+
font.setFamily(CONFIG.guiFont)
|
386
|
+
font.setPointSize(CONFIG.guiFontSize)
|
384
387
|
|
385
|
-
qApp.setFont(
|
388
|
+
qApp.setFont(font)
|
386
389
|
|
387
390
|
return
|
388
391
|
|
@@ -397,29 +400,14 @@ class GuiTheme:
|
|
397
400
|
|
398
401
|
return True
|
399
402
|
|
400
|
-
def _parseColour(self, parser: NWConfigParser, section: str, name: str) ->
|
403
|
+
def _parseColour(self, parser: NWConfigParser, section: str, name: str) -> QColor:
|
401
404
|
"""Parse a colour value from a config string."""
|
402
|
-
|
403
|
-
values = parser.get(section, name).split(",")
|
404
|
-
result = []
|
405
|
-
try:
|
406
|
-
result.append(minmax(int(values[0]), 0, 255))
|
407
|
-
result.append(minmax(int(values[1]), 0, 255))
|
408
|
-
result.append(minmax(int(values[2]), 0, 255))
|
409
|
-
except Exception:
|
410
|
-
logger.error("Could not load theme colours for '%s' from config file", name)
|
411
|
-
result = [0, 0, 0]
|
412
|
-
else:
|
413
|
-
logger.warning("Could not find theme colours for '%s' in config file", name)
|
414
|
-
result = [0, 0, 0]
|
415
|
-
return result
|
405
|
+
return QColor(*parser.rdIntList(section, name, [0, 0, 0, 255]))
|
416
406
|
|
417
407
|
def _setPalette(self, parser: NWConfigParser, section: str,
|
418
408
|
name: str, value: QPalette.ColorRole) -> None:
|
419
409
|
"""Set a palette colour value from a config string."""
|
420
|
-
self._guiPalette.setColor(
|
421
|
-
value, QColor(*self._parseColour(parser, section, name))
|
422
|
-
)
|
410
|
+
self._guiPalette.setColor(value, self._parseColour(parser, section, name))
|
423
411
|
return
|
424
412
|
|
425
413
|
# End Class GuiTheme
|
@@ -443,7 +431,7 @@ class GuiIcons:
|
|
443
431
|
missing, a blank icon is returned and a warning issued.
|
444
432
|
"""
|
445
433
|
|
446
|
-
ICON_KEYS = {
|
434
|
+
ICON_KEYS: set[str] = {
|
447
435
|
# Project and GUI Icons
|
448
436
|
"novelwriter", "alert_error", "alert_info", "alert_question", "alert_warn",
|
449
437
|
"build_excluded", "build_filtered", "build_included", "proj_chapter", "proj_details",
|
@@ -453,7 +441,7 @@ class GuiIcons:
|
|
453
441
|
|
454
442
|
# Class Icons
|
455
443
|
"cls_archive", "cls_character", "cls_custom", "cls_entity", "cls_none", "cls_novel",
|
456
|
-
"cls_object", "cls_plot", "cls_timeline", "cls_trash", "cls_world",
|
444
|
+
"cls_object", "cls_plot", "cls_template", "cls_timeline", "cls_trash", "cls_world",
|
457
445
|
|
458
446
|
# Search Icons
|
459
447
|
"search_cancel", "search_case", "search_loop", "search_preserve", "search_project",
|
@@ -464,9 +452,10 @@ class GuiIcons:
|
|
464
452
|
"fmt_subscript", "fmt_superscript", "fmt_underline",
|
465
453
|
|
466
454
|
# General Button Icons
|
467
|
-
"add", "backward", "bookmark", "browse", "checked", "close", "cross",
|
468
|
-
"
|
469
|
-
"
|
455
|
+
"add", "add_document", "backward", "bookmark", "browse", "checked", "close", "cross",
|
456
|
+
"document", "down", "edit", "export", "forward", "import", "maximise", "menu", "minimise",
|
457
|
+
"more", "noncheckable", "panel", "refresh", "remove", "revert", "search_replace", "search",
|
458
|
+
"settings", "star", "unchecked", "up", "view",
|
470
459
|
|
471
460
|
# Switches
|
472
461
|
"sticky-on", "sticky-off",
|
@@ -478,13 +467,14 @@ class GuiIcons:
|
|
478
467
|
"deco_doc_nt_n",
|
479
468
|
}
|
480
469
|
|
481
|
-
TOGGLE_ICON_KEYS = {
|
470
|
+
TOGGLE_ICON_KEYS: dict[str, tuple[str, str]] = {
|
482
471
|
"sticky": ("sticky-on", "sticky-off"),
|
483
472
|
"bullet": ("bullet-on", "bullet-off"),
|
484
473
|
}
|
485
474
|
|
486
|
-
IMAGE_MAP = {
|
487
|
-
"
|
475
|
+
IMAGE_MAP: dict[str, tuple[str, str]] = {
|
476
|
+
"welcome": ("welcome-light.jpg", "welcome-dark.jpg"),
|
477
|
+
"nw-text": ("novelwriter-text-light.svg", "novelwriter-text-dark.svg"),
|
488
478
|
}
|
489
479
|
|
490
480
|
def __init__(self, mainTheme: GuiTheme) -> None:
|
@@ -598,7 +588,8 @@ class GuiIcons:
|
|
598
588
|
if name in self._themeMap:
|
599
589
|
imgPath = self._themeMap[name]
|
600
590
|
elif name in self.IMAGE_MAP:
|
601
|
-
|
591
|
+
idx = 0 if self.mainTheme.isLightTheme else 1
|
592
|
+
imgPath = CONFIG.assetPath("images") / self.IMAGE_MAP[name][idx]
|
602
593
|
else:
|
603
594
|
logger.error("Decoration with name '%s' does not exist", name)
|
604
595
|
return QPixmap()
|
@@ -608,12 +599,13 @@ class GuiIcons:
|
|
608
599
|
return QPixmap()
|
609
600
|
|
610
601
|
pixmap = QPixmap(str(imgPath))
|
602
|
+
tMode = Qt.TransformationMode.SmoothTransformation
|
611
603
|
if w is not None and h is not None:
|
612
|
-
return pixmap.scaled(w, h, Qt.IgnoreAspectRatio,
|
604
|
+
return pixmap.scaled(w, h, Qt.AspectRatioMode.IgnoreAspectRatio, tMode)
|
613
605
|
elif w is None and h is not None:
|
614
|
-
return pixmap.scaledToHeight(h,
|
606
|
+
return pixmap.scaledToHeight(h, tMode)
|
615
607
|
elif w is not None and h is None:
|
616
|
-
return pixmap.scaledToWidth(w,
|
608
|
+
return pixmap.scaledToWidth(w, tMode)
|
617
609
|
|
618
610
|
return pixmap
|
619
611
|
|
@@ -632,8 +624,8 @@ class GuiIcons:
|
|
632
624
|
pOne = self.getPixmap(self.TOGGLE_ICON_KEYS[name][0], size)
|
633
625
|
pTwo = self.getPixmap(self.TOGGLE_ICON_KEYS[name][1], size)
|
634
626
|
icon = QIcon()
|
635
|
-
icon.addPixmap(pOne, QIcon.Normal, QIcon.On)
|
636
|
-
icon.addPixmap(pTwo, QIcon.Normal, QIcon.Off)
|
627
|
+
icon.addPixmap(pOne, QIcon.Mode.Normal, QIcon.State.On)
|
628
|
+
icon.addPixmap(pTwo, QIcon.Mode.Normal, QIcon.State.Off)
|
637
629
|
return icon
|
638
630
|
return QIcon()
|
639
631
|
|
@@ -641,7 +633,7 @@ class GuiIcons:
|
|
641
633
|
"""Return an icon from the icon buffer as a QPixmap. If it
|
642
634
|
doesn't exist, return an empty QPixmap.
|
643
635
|
"""
|
644
|
-
return self.getIcon(name).pixmap(size[0], size[1], QIcon.Normal)
|
636
|
+
return self.getIcon(name).pixmap(size[0], size[1], QIcon.Mode.Normal)
|
645
637
|
|
646
638
|
def getItemIcon(self, tType: nwItemType, tClass: nwItemClass,
|
647
639
|
tLayout: nwItemLayout, hLevel: str = "H0") -> QIcon:
|
novelwriter/guimain.py
CHANGED
@@ -30,14 +30,15 @@ from time import time
|
|
30
30
|
from pathlib import Path
|
31
31
|
from datetime import datetime
|
32
32
|
|
33
|
-
from PyQt5.QtCore import Qt, QTimer, pyqtSlot
|
34
33
|
from PyQt5.QtGui import QCloseEvent, QCursor, QIcon
|
34
|
+
from PyQt5.QtCore import Qt, QTimer, pyqtSlot
|
35
35
|
from PyQt5.QtWidgets import (
|
36
|
-
|
37
|
-
|
36
|
+
QFileDialog, QHBoxLayout, QMainWindow, QMessageBox, QShortcut, QSplitter,
|
37
|
+
QStackedWidget, QVBoxLayout, QWidget, qApp
|
38
38
|
)
|
39
39
|
|
40
|
-
from novelwriter import CONFIG, SHARED, __hexversion__
|
40
|
+
from novelwriter import CONFIG, SHARED, __hexversion__, __version__
|
41
|
+
from novelwriter.constants import nwConst
|
41
42
|
from novelwriter.gui.theme import GuiTheme
|
42
43
|
from novelwriter.gui.sidebar import GuiSideBar
|
43
44
|
from novelwriter.gui.outline import GuiOutlineView
|
@@ -50,23 +51,19 @@ from novelwriter.gui.statusbar import GuiMainStatus
|
|
50
51
|
from novelwriter.gui.itemdetails import GuiItemDetails
|
51
52
|
from novelwriter.gui.docviewerpanel import GuiDocViewerPanel
|
52
53
|
from novelwriter.dialogs.about import GuiAbout
|
53
|
-
from novelwriter.dialogs.updates import GuiUpdates
|
54
|
-
from novelwriter.dialogs.projload import GuiProjectLoad
|
55
54
|
from novelwriter.dialogs.wordlist import GuiWordList
|
56
55
|
from novelwriter.dialogs.preferences import GuiPreferences
|
57
|
-
from novelwriter.dialogs.
|
58
|
-
from novelwriter.
|
56
|
+
from novelwriter.dialogs.projectsettings import GuiProjectSettings
|
57
|
+
from novelwriter.tools.welcome import GuiWelcome
|
59
58
|
from novelwriter.tools.manuscript import GuiManuscript
|
60
|
-
from novelwriter.tools.projwizard import GuiProjectWizard
|
61
59
|
from novelwriter.tools.dictionaries import GuiDictionaries
|
60
|
+
from novelwriter.tools.noveldetails import GuiNovelDetails
|
62
61
|
from novelwriter.tools.writingstats import GuiWritingStats
|
63
|
-
from novelwriter.core.coretools import ProjectBuilder
|
64
62
|
|
65
63
|
from novelwriter.enum import (
|
66
|
-
nwDocAction, nwDocInsert, nwDocMode, nwItemType,
|
64
|
+
nwDocAction, nwDocInsert, nwDocMode, nwItemType, nwWidget, nwView
|
67
65
|
)
|
68
|
-
from novelwriter.common import hexToInt
|
69
|
-
from novelwriter.constants import nwFiles
|
66
|
+
from novelwriter.common import formatFileFilter, formatVersion, hexToInt
|
70
67
|
|
71
68
|
logger = logging.getLogger(__name__)
|
72
69
|
|
@@ -82,13 +79,6 @@ class GuiMain(QMainWindow):
|
|
82
79
|
function. Also, the project instance and theme instance are created
|
83
80
|
here. These should be passed around to all other objects who need
|
84
81
|
them and new instances of them should generally not be created.
|
85
|
-
|
86
|
-
* All other GUI classes that depend on any components from the
|
87
|
-
main GUI should be passed a reference to the instance of this
|
88
|
-
class.
|
89
|
-
* All non-GUI classes can be passed a reference to the NWProject
|
90
|
-
instance if the Main GUI is not needed (which it generally
|
91
|
-
shouldn't need).
|
92
82
|
"""
|
93
83
|
|
94
84
|
def __init__(self) -> None:
|
@@ -359,12 +349,18 @@ class GuiMain(QMainWindow):
|
|
359
349
|
self.openProject(cmdOpen)
|
360
350
|
|
361
351
|
if not SHARED.hasProject:
|
362
|
-
self.
|
352
|
+
self.showWelcomeDialog()
|
363
353
|
|
364
|
-
#
|
354
|
+
# If this is a new release, let the user know
|
365
355
|
if hexToInt(CONFIG.lastNotes) < hexToInt(__hexversion__):
|
366
356
|
CONFIG.lastNotes = __hexversion__
|
367
|
-
self.
|
357
|
+
trVersion = self.tr(
|
358
|
+
"You are now running novelWriter version {0}.".format(formatVersion(__version__))
|
359
|
+
)
|
360
|
+
trRelease = self.tr(
|
361
|
+
"Please check the {0}release notes{1} for further details."
|
362
|
+
).format(f"<a href='{nwConst.URL_RELEASES}'>", "</a>")
|
363
|
+
SHARED.info(f"{trVersion}<br>{trRelease}")
|
368
364
|
|
369
365
|
return
|
370
366
|
|
@@ -372,42 +368,6 @@ class GuiMain(QMainWindow):
|
|
372
368
|
# Project Actions
|
373
369
|
##
|
374
370
|
|
375
|
-
def newProject(self, projData: dict | None = None) -> bool:
|
376
|
-
"""Create a new project via the new project wizard."""
|
377
|
-
if SHARED.hasProject:
|
378
|
-
if not self.closeProject():
|
379
|
-
SHARED.error(self.tr(
|
380
|
-
"Cannot create a new project when another project is open."
|
381
|
-
))
|
382
|
-
return False
|
383
|
-
|
384
|
-
if projData is None:
|
385
|
-
projData = self.showNewProjectDialog()
|
386
|
-
|
387
|
-
if projData is None:
|
388
|
-
return False
|
389
|
-
|
390
|
-
projPath = projData.get("projPath", None)
|
391
|
-
if projPath is None or projData is None:
|
392
|
-
logger.error("No projData or projPath set")
|
393
|
-
return False
|
394
|
-
|
395
|
-
if (Path(projPath) / nwFiles.PROJ_FILE).is_file():
|
396
|
-
SHARED.error(self.tr(
|
397
|
-
"A project already exists in that location. "
|
398
|
-
"Please choose another folder."
|
399
|
-
))
|
400
|
-
return False
|
401
|
-
|
402
|
-
logger.info("Creating new project")
|
403
|
-
nwProject = ProjectBuilder()
|
404
|
-
if nwProject.buildProject(projData):
|
405
|
-
self.openProject(projPath)
|
406
|
-
else:
|
407
|
-
return False
|
408
|
-
|
409
|
-
return True
|
410
|
-
|
411
371
|
def closeProject(self, isYes: bool = False) -> bool:
|
412
372
|
"""Close the project if one is open. isYes is passed on from the
|
413
373
|
close application event so the user doesn't get prompted twice
|
@@ -446,7 +406,7 @@ class GuiMain(QMainWindow):
|
|
446
406
|
self.docViewerPanel.closeProjectTasks()
|
447
407
|
self.outlineView.closeProjectTasks()
|
448
408
|
self.novelView.closeProjectTasks()
|
449
|
-
self.projView.
|
409
|
+
self.projView.closeProjectTasks()
|
450
410
|
self.itemDetails.clearDetails()
|
451
411
|
self.mainStatus.clearStatus()
|
452
412
|
|
@@ -462,8 +422,7 @@ class GuiMain(QMainWindow):
|
|
462
422
|
if projFile is None:
|
463
423
|
return False
|
464
424
|
|
465
|
-
# Make sure any open project is cleared out first
|
466
|
-
# another one
|
425
|
+
# Make sure any open project is cleared out first
|
467
426
|
if not self.closeProject():
|
468
427
|
return False
|
469
428
|
|
@@ -711,14 +670,9 @@ class GuiMain(QMainWindow):
|
|
711
670
|
return False
|
712
671
|
|
713
672
|
lastPath = CONFIG.lastPath()
|
714
|
-
|
715
|
-
self.tr("Text files ({0})").format("*.txt"),
|
716
|
-
self.tr("Markdown files ({0})").format("*.md"),
|
717
|
-
self.tr("novelWriter files ({0})").format("*.nwd"),
|
718
|
-
self.tr("All files ({0})").format("*"),
|
719
|
-
]
|
673
|
+
ffilter = formatFileFilter(["*.txt", "*.md", "*.nwd", "*"])
|
720
674
|
loadFile, _ = QFileDialog.getOpenFileName(
|
721
|
-
self, self.tr("Import File"), str(lastPath), filter=
|
675
|
+
self, self.tr("Import File"), str(lastPath), filter=ffilter
|
722
676
|
)
|
723
677
|
if not loadFile:
|
724
678
|
return False
|
@@ -726,10 +680,10 @@ class GuiMain(QMainWindow):
|
|
726
680
|
if loadFile.strip() == "":
|
727
681
|
return False
|
728
682
|
|
729
|
-
|
683
|
+
text = None
|
730
684
|
try:
|
731
685
|
with open(loadFile, mode="rt", encoding="utf-8") as inFile:
|
732
|
-
|
686
|
+
text = inFile.read()
|
733
687
|
CONFIG.setLastPath(loadFile)
|
734
688
|
except Exception as exc:
|
735
689
|
SHARED.error(self.tr(
|
@@ -751,7 +705,7 @@ class GuiMain(QMainWindow):
|
|
751
705
|
if not msgYes:
|
752
706
|
return False
|
753
707
|
|
754
|
-
self.docEditor.replaceText(
|
708
|
+
self.docEditor.replaceText(text)
|
755
709
|
|
756
710
|
return True
|
757
711
|
|
@@ -838,33 +792,14 @@ class GuiMain(QMainWindow):
|
|
838
792
|
# Main Dialogs
|
839
793
|
##
|
840
794
|
|
841
|
-
|
842
|
-
|
843
|
-
|
844
|
-
|
845
|
-
|
846
|
-
|
847
|
-
dlgProj = GuiProjectLoad(self)
|
848
|
-
dlgProj.exec_()
|
849
|
-
|
850
|
-
if dlgProj.result() == QDialog.Accepted:
|
851
|
-
if dlgProj.openState == GuiProjectLoad.OPEN_STATE:
|
852
|
-
self.openProject(dlgProj.openPath)
|
853
|
-
elif dlgProj.openState == GuiProjectLoad.NEW_STATE:
|
854
|
-
self.newProject()
|
855
|
-
|
795
|
+
@pyqtSlot()
|
796
|
+
def showWelcomeDialog(self) -> None:
|
797
|
+
"""Open the welcome dialog."""
|
798
|
+
dialog = GuiWelcome(self)
|
799
|
+
dialog.openProjectRequest.connect(self._openProject)
|
800
|
+
dialog.exec_()
|
856
801
|
return
|
857
802
|
|
858
|
-
def showNewProjectDialog(self) -> dict | None:
|
859
|
-
"""Open the wizard and assemble a project options dict."""
|
860
|
-
newProj = GuiProjectWizard(self)
|
861
|
-
newProj.exec_()
|
862
|
-
|
863
|
-
if newProj.result() == QDialog.Accepted:
|
864
|
-
return self._assembleProjectWizardData(newProj)
|
865
|
-
|
866
|
-
return None
|
867
|
-
|
868
803
|
@pyqtSlot()
|
869
804
|
def showPreferencesDialog(self) -> None:
|
870
805
|
"""Open the preferences dialog."""
|
@@ -875,19 +810,19 @@ class GuiMain(QMainWindow):
|
|
875
810
|
|
876
811
|
@pyqtSlot()
|
877
812
|
@pyqtSlot(int)
|
878
|
-
def showProjectSettingsDialog(self, focusTab: int = GuiProjectSettings.
|
813
|
+
def showProjectSettingsDialog(self, focusTab: int = GuiProjectSettings.PAGE_SETTINGS) -> None:
|
879
814
|
"""Open the project settings dialog."""
|
880
815
|
if SHARED.hasProject:
|
881
|
-
dialog = GuiProjectSettings(self,
|
816
|
+
dialog = GuiProjectSettings(self, gotoPage=focusTab)
|
882
817
|
dialog.newProjectSettingsReady.connect(self._processProjectSettingsChanges)
|
883
818
|
dialog.exec_()
|
884
819
|
return
|
885
820
|
|
886
821
|
@pyqtSlot()
|
887
|
-
def
|
888
|
-
"""Open the
|
822
|
+
def showNovelDetailsDialog(self) -> None:
|
823
|
+
"""Open the novel details dialog."""
|
889
824
|
if SHARED.hasProject:
|
890
|
-
dialog =
|
825
|
+
dialog = GuiNovelDetails(self)
|
891
826
|
dialog.setModal(True)
|
892
827
|
dialog.show()
|
893
828
|
dialog.raise_()
|
@@ -931,7 +866,7 @@ class GuiMain(QMainWindow):
|
|
931
866
|
return
|
932
867
|
|
933
868
|
@pyqtSlot()
|
934
|
-
def showAboutNWDialog(self
|
869
|
+
def showAboutNWDialog(self) -> None:
|
935
870
|
"""Show the novelWriter about dialog."""
|
936
871
|
dialog = GuiAbout(self)
|
937
872
|
dialog.setModal(True)
|
@@ -939,8 +874,6 @@ class GuiMain(QMainWindow):
|
|
939
874
|
dialog.raise_()
|
940
875
|
qApp.processEvents()
|
941
876
|
dialog.populateGUI()
|
942
|
-
if showNotes:
|
943
|
-
dialog.showReleaseNotes()
|
944
877
|
return
|
945
878
|
|
946
879
|
@pyqtSlot()
|
@@ -950,17 +883,6 @@ class GuiMain(QMainWindow):
|
|
950
883
|
msgBox.aboutQt(self, "About Qt")
|
951
884
|
return
|
952
885
|
|
953
|
-
@pyqtSlot()
|
954
|
-
def showUpdatesDialog(self) -> None:
|
955
|
-
"""Show the check for updates dialog."""
|
956
|
-
dialog = GuiUpdates(self)
|
957
|
-
dialog.setModal(True)
|
958
|
-
dialog.show()
|
959
|
-
dialog.raise_()
|
960
|
-
qApp.processEvents()
|
961
|
-
dialog.checkLatest()
|
962
|
-
return
|
963
|
-
|
964
886
|
@pyqtSlot()
|
965
887
|
def showDictionariesDialog(self) -> None:
|
966
888
|
"""Show the download dictionaries dialog."""
|
@@ -1179,13 +1101,15 @@ class GuiMain(QMainWindow):
|
|
1179
1101
|
|
1180
1102
|
return
|
1181
1103
|
|
1182
|
-
@pyqtSlot()
|
1183
|
-
def _processProjectSettingsChanges(self) -> None:
|
1104
|
+
@pyqtSlot(bool)
|
1105
|
+
def _processProjectSettingsChanges(self, rebuildTrees: bool) -> None:
|
1184
1106
|
"""Refresh data dependent on project settings."""
|
1185
1107
|
logger.debug("Applying new project settings")
|
1186
1108
|
SHARED.updateSpellCheckLanguage()
|
1187
1109
|
self.itemDetails.refreshDetails()
|
1188
1110
|
self._updateWindowTitle(SHARED.project.data.name)
|
1111
|
+
if rebuildTrees:
|
1112
|
+
self.rebuildTrees()
|
1189
1113
|
return
|
1190
1114
|
|
1191
1115
|
@pyqtSlot()
|
@@ -1207,6 +1131,13 @@ class GuiMain(QMainWindow):
|
|
1207
1131
|
self.viewDocument(tHandle=tHandle, sTitle=sTitle)
|
1208
1132
|
return
|
1209
1133
|
|
1134
|
+
@pyqtSlot(Path)
|
1135
|
+
def _openProject(self, path: Path) -> None:
|
1136
|
+
"""Handle an open project request."""
|
1137
|
+
qApp.processEvents()
|
1138
|
+
self.openProject(path)
|
1139
|
+
return
|
1140
|
+
|
1210
1141
|
@pyqtSlot(str, nwDocMode, str, bool)
|
1211
1142
|
def _openDocument(self, tHandle: str, mode: nwDocMode, sTitle: str, setFocus: bool) -> None:
|
1212
1143
|
"""Handle an open document request."""
|
@@ -1461,44 +1392,6 @@ class GuiMain(QMainWindow):
|
|
1461
1392
|
self.setWindowTitle(winTitle)
|
1462
1393
|
return
|
1463
1394
|
|
1464
|
-
def _assembleProjectWizardData(self, newProj: GuiProjectWizard) -> dict:
|
1465
|
-
"""Extract the user choices from the New Project Wizard and
|
1466
|
-
store them in a dictionary.
|
1467
|
-
"""
|
1468
|
-
projData = {
|
1469
|
-
"projName": newProj.field("projName"),
|
1470
|
-
"projTitle": newProj.field("projTitle"),
|
1471
|
-
"projAuthor": newProj.field("projAuthor"),
|
1472
|
-
"projPath": newProj.field("projPath"),
|
1473
|
-
"popSample": newProj.field("popSample"),
|
1474
|
-
"popMinimal": newProj.field("popMinimal"),
|
1475
|
-
"popCustom": newProj.field("popCustom"),
|
1476
|
-
"addRoots": [],
|
1477
|
-
"addNotes": False,
|
1478
|
-
"numChapters": 0,
|
1479
|
-
"numScenes": 0,
|
1480
|
-
}
|
1481
|
-
if newProj.field("popCustom"):
|
1482
|
-
addRoots = []
|
1483
|
-
if newProj.field("addPlot"):
|
1484
|
-
addRoots.append(nwItemClass.PLOT)
|
1485
|
-
if newProj.field("addChar"):
|
1486
|
-
addRoots.append(nwItemClass.CHARACTER)
|
1487
|
-
if newProj.field("addWorld"):
|
1488
|
-
addRoots.append(nwItemClass.WORLD)
|
1489
|
-
projData["addRoots"] = addRoots
|
1490
|
-
projData["addNotes"] = newProj.field("addNotes")
|
1491
|
-
projData["numChapters"] = newProj.field("numChapters")
|
1492
|
-
projData["numScenes"] = newProj.field("numScenes")
|
1493
|
-
|
1494
|
-
try:
|
1495
|
-
langIdx = newProj.field("projLang")
|
1496
|
-
projData["projLang"] = CONFIG.listLanguages(CONFIG.LANG_PROJ)[langIdx][0]
|
1497
|
-
except Exception:
|
1498
|
-
projData["projLang"] = "en_GB"
|
1499
|
-
|
1500
|
-
return projData
|
1501
|
-
|
1502
1395
|
def _getTagSource(self, tag: str) -> tuple[str | None, str | None]:
|
1503
1396
|
"""Handle the index lookup of a tag and display an alert if the
|
1504
1397
|
tag cannot be found.
|
novelwriter/shared.py
CHANGED
@@ -31,8 +31,10 @@ from typing import TYPE_CHECKING, TypeVar
|
|
31
31
|
from pathlib import Path
|
32
32
|
|
33
33
|
from PyQt5.QtCore import QObject, QRunnable, QThreadPool, pyqtSignal
|
34
|
-
from PyQt5.QtWidgets import QMessageBox, QWidget
|
34
|
+
from PyQt5.QtWidgets import QFileDialog, QMessageBox, QWidget
|
35
|
+
from novelwriter.common import formatFileFilter
|
35
36
|
|
37
|
+
from novelwriter.constants import nwFiles
|
36
38
|
from novelwriter.core.spellcheck import NWSpellEnchant
|
37
39
|
|
38
40
|
if TYPE_CHECKING: # pragma: no cover
|
@@ -217,6 +219,18 @@ class SharedData(QObject):
|
|
217
219
|
QThreadPool.globalInstance().start(runnable, priority=priority)
|
218
220
|
return
|
219
221
|
|
222
|
+
def getProjectPath(self, parent: QWidget, path: str | Path | None = None,
|
223
|
+
allowZip: bool = False) -> Path | None:
|
224
|
+
"""Open the file dialog and select a novelWriter project file."""
|
225
|
+
label = (self.tr("novelWriter Project File or Zip File")
|
226
|
+
if allowZip else self.tr("novelWriter Project File"))
|
227
|
+
ext = f"{nwFiles.PROJ_FILE} *.zip" if allowZip else nwFiles.PROJ_FILE
|
228
|
+
ffilter = formatFileFilter([(label, ext), "*"])
|
229
|
+
selected, _ = QFileDialog.getOpenFileName(
|
230
|
+
parent, self.tr("Open Project"), str(path or ""), filter=ffilter
|
231
|
+
)
|
232
|
+
return Path(selected) if selected else None
|
233
|
+
|
220
234
|
def findTopLevelWidget(self, kind: type[NWWidget]) -> NWWidget | None:
|
221
235
|
"""Find a top level widget."""
|
222
236
|
for widget in self.mainGui.children():
|
@@ -37,7 +37,7 @@ from PyQt5.QtWidgets import (
|
|
37
37
|
|
38
38
|
from novelwriter import CONFIG, SHARED
|
39
39
|
from novelwriter.error import formatException
|
40
|
-
from novelwriter.common import openExternalPath, formatInt, getFileSize
|
40
|
+
from novelwriter.common import formatFileFilter, openExternalPath, formatInt, getFileSize
|
41
41
|
|
42
42
|
logger = logging.getLogger(__name__)
|
43
43
|
|
@@ -180,12 +180,11 @@ class GuiDictionaries(QDialog):
|
|
180
180
|
@pyqtSlot()
|
181
181
|
def _doBrowseHunspell(self):
|
182
182
|
"""Browse for a Free/Libre Office dictionary."""
|
183
|
-
|
184
|
-
self.tr("Free or Libre Office extension
|
185
|
-
|
186
|
-
]
|
183
|
+
ffilter = formatFileFilter([
|
184
|
+
(self.tr("Free or Libre Office extension"), "*.sox *.oxt"), "*"
|
185
|
+
])
|
187
186
|
soxFile, _ = QFileDialog.getOpenFileName(
|
188
|
-
self, self.tr("Browse Files"), "", filter=
|
187
|
+
self, self.tr("Browse Files"), "", filter=ffilter
|
189
188
|
)
|
190
189
|
if soxFile:
|
191
190
|
path = Path(soxFile).absolute()
|