novelWriter 2.2rc1__py3-none-any.whl → 2.3__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 (162) hide show
  1. {novelWriter-2.2rc1.dist-info → novelWriter-2.3.dist-info}/METADATA +1 -1
  2. {novelWriter-2.2rc1.dist-info → novelWriter-2.3.dist-info}/RECORD +149 -132
  3. {novelWriter-2.2rc1.dist-info → novelWriter-2.3.dist-info}/WHEEL +1 -1
  4. novelWriter-2.3.dist-info/entry_points.txt +2 -0
  5. novelwriter/__init__.py +11 -6
  6. novelwriter/assets/i18n/nw_de_DE.qm +0 -0
  7. novelwriter/assets/i18n/nw_en_US.qm +0 -0
  8. novelwriter/assets/i18n/nw_es_419.qm +0 -0
  9. novelwriter/assets/i18n/nw_fr_FR.qm +0 -0
  10. novelwriter/assets/i18n/nw_it_IT.qm +0 -0
  11. novelwriter/assets/i18n/nw_ja_JP.qm +0 -0
  12. novelwriter/assets/i18n/nw_nb_NO.qm +0 -0
  13. novelwriter/assets/i18n/nw_nl_NL.qm +0 -0
  14. novelwriter/assets/i18n/nw_zh_CN.qm +0 -0
  15. novelwriter/assets/i18n/project_de_DE.json +1 -0
  16. novelwriter/assets/i18n/project_en_US.json +1 -0
  17. novelwriter/assets/i18n/project_es_419.json +11 -0
  18. novelwriter/assets/i18n/project_fr_FR.json +11 -0
  19. novelwriter/assets/i18n/project_it_IT.json +11 -0
  20. novelwriter/assets/i18n/project_ja_JP.json +2 -1
  21. novelwriter/assets/i18n/project_nb_NO.json +1 -0
  22. novelwriter/assets/i18n/project_nl_NL.json +11 -0
  23. novelwriter/assets/i18n/project_pt_BR.json +11 -0
  24. novelwriter/assets/i18n/project_zh_CN.json +11 -0
  25. novelwriter/assets/icons/typicons_dark/icons.conf +11 -2
  26. novelwriter/assets/icons/typicons_dark/mixed_document-new.svg +6 -0
  27. novelwriter/assets/icons/typicons_dark/mixed_import.svg +5 -0
  28. novelwriter/assets/icons/typicons_dark/nw_tb-bold-md.svg +4 -0
  29. novelwriter/assets/icons/typicons_dark/nw_tb-bold.svg +3 -1
  30. novelwriter/assets/icons/typicons_dark/nw_tb-italic-md.svg +4 -0
  31. novelwriter/assets/icons/typicons_dark/nw_tb-italic.svg +3 -1
  32. novelwriter/assets/icons/typicons_dark/nw_tb-strike-md.svg +4 -0
  33. novelwriter/assets/icons/typicons_dark/nw_tb-strike.svg +3 -1
  34. novelwriter/assets/icons/typicons_dark/nw_tb-subscript.svg +4 -2
  35. novelwriter/assets/icons/typicons_dark/nw_tb-superscript.svg +4 -2
  36. novelwriter/assets/icons/typicons_dark/nw_tb-underline.svg +4 -2
  37. novelwriter/assets/icons/typicons_dark/typ_document-add.svg +4 -0
  38. novelwriter/assets/icons/typicons_dark/typ_document.svg +4 -0
  39. novelwriter/assets/icons/typicons_dark/typ_th-dot-more.svg +4 -0
  40. novelwriter/assets/icons/typicons_dark/typ_th-list.svg +9 -0
  41. novelwriter/assets/icons/typicons_light/icons.conf +11 -2
  42. novelwriter/assets/icons/typicons_light/mixed_document-new.svg +6 -0
  43. novelwriter/assets/icons/typicons_light/mixed_import.svg +5 -0
  44. novelwriter/assets/icons/typicons_light/nw_tb-bold-md.svg +4 -0
  45. novelwriter/assets/icons/typicons_light/nw_tb-bold.svg +3 -1
  46. novelwriter/assets/icons/typicons_light/nw_tb-italic-md.svg +4 -0
  47. novelwriter/assets/icons/typicons_light/nw_tb-italic.svg +3 -1
  48. novelwriter/assets/icons/typicons_light/nw_tb-strike-md.svg +4 -0
  49. novelwriter/assets/icons/typicons_light/nw_tb-strike.svg +3 -1
  50. novelwriter/assets/icons/typicons_light/nw_tb-subscript.svg +4 -2
  51. novelwriter/assets/icons/typicons_light/nw_tb-superscript.svg +4 -2
  52. novelwriter/assets/icons/typicons_light/nw_tb-underline.svg +4 -2
  53. novelwriter/assets/icons/typicons_light/typ_document-add.svg +4 -0
  54. novelwriter/assets/icons/typicons_light/typ_document.svg +4 -0
  55. novelwriter/assets/icons/typicons_light/typ_th-dot-more.svg +4 -0
  56. novelwriter/assets/icons/typicons_light/typ_th-list.svg +9 -0
  57. novelwriter/assets/images/novelwriter-text-dark.svg +4 -0
  58. novelwriter/assets/images/novelwriter-text-light.svg +4 -0
  59. novelwriter/assets/images/welcome-dark.jpg +0 -0
  60. novelwriter/assets/images/welcome-light.jpg +0 -0
  61. novelwriter/assets/manual.pdf +0 -0
  62. novelwriter/assets/sample.zip +0 -0
  63. novelwriter/assets/syntax/cyberpunk_night.conf +26 -0
  64. novelwriter/assets/syntax/default_dark.conf +1 -0
  65. novelwriter/assets/syntax/default_light.conf +1 -0
  66. novelwriter/assets/syntax/grey_dark.conf +1 -0
  67. novelwriter/assets/syntax/grey_light.conf +1 -0
  68. novelwriter/assets/syntax/light_owl.conf +1 -0
  69. novelwriter/assets/syntax/night_owl.conf +1 -0
  70. novelwriter/assets/syntax/solarized_dark.conf +1 -0
  71. novelwriter/assets/syntax/solarized_light.conf +1 -0
  72. novelwriter/assets/syntax/tango.conf +23 -0
  73. novelwriter/assets/syntax/tomorrow.conf +1 -0
  74. novelwriter/assets/syntax/tomorrow_night.conf +1 -0
  75. novelwriter/assets/syntax/tomorrow_night_blue.conf +1 -0
  76. novelwriter/assets/syntax/tomorrow_night_bright.conf +1 -0
  77. novelwriter/assets/syntax/tomorrow_night_eighties.conf +1 -0
  78. novelwriter/assets/text/credits_en.htm +4 -2
  79. novelwriter/assets/themes/cyberpunk_night.conf +29 -0
  80. novelwriter/assets/themes/default_dark.conf +2 -2
  81. novelwriter/assets/themes/default_light.conf +2 -2
  82. novelwriter/common.py +64 -66
  83. novelwriter/config.py +39 -44
  84. novelwriter/constants.py +39 -17
  85. novelwriter/core/buildsettings.py +8 -8
  86. novelwriter/core/coretools.py +198 -157
  87. novelwriter/core/docbuild.py +7 -4
  88. novelwriter/core/document.py +7 -7
  89. novelwriter/core/index.py +90 -57
  90. novelwriter/core/item.py +23 -5
  91. novelwriter/core/options.py +11 -10
  92. novelwriter/core/project.py +73 -47
  93. novelwriter/core/projectdata.py +3 -16
  94. novelwriter/core/projectxml.py +14 -42
  95. novelwriter/core/sessions.py +4 -3
  96. novelwriter/core/spellcheck.py +6 -4
  97. novelwriter/core/status.py +5 -4
  98. novelwriter/core/storage.py +183 -141
  99. novelwriter/core/tohtml.py +6 -4
  100. novelwriter/core/tokenizer.py +110 -83
  101. novelwriter/core/tomd.py +2 -2
  102. novelwriter/core/toodt.py +41 -31
  103. novelwriter/core/tree.py +5 -4
  104. novelwriter/dialogs/about.py +88 -179
  105. novelwriter/dialogs/docmerge.py +30 -20
  106. novelwriter/dialogs/docsplit.py +33 -22
  107. novelwriter/dialogs/editlabel.py +20 -8
  108. novelwriter/dialogs/preferences.py +562 -725
  109. novelwriter/dialogs/{projsettings.py → projectsettings.py} +301 -270
  110. novelwriter/dialogs/quotes.py +47 -36
  111. novelwriter/dialogs/wordlist.py +128 -59
  112. novelwriter/enum.py +25 -22
  113. novelwriter/error.py +2 -2
  114. novelwriter/extensions/circularprogress.py +12 -12
  115. novelwriter/extensions/configlayout.py +185 -146
  116. novelwriter/extensions/{wheeleventfilter.py → eventfilters.py} +15 -5
  117. novelwriter/extensions/modified.py +81 -0
  118. novelwriter/extensions/novelselector.py +27 -13
  119. novelwriter/extensions/pagedsidebar.py +15 -20
  120. novelwriter/extensions/simpleprogress.py +8 -9
  121. novelwriter/extensions/statusled.py +9 -9
  122. novelwriter/extensions/switch.py +32 -64
  123. novelwriter/extensions/switchbox.py +2 -7
  124. novelwriter/extensions/versioninfo.py +153 -0
  125. novelwriter/gui/doceditor.py +250 -214
  126. novelwriter/gui/dochighlight.py +66 -94
  127. novelwriter/gui/docviewer.py +71 -98
  128. novelwriter/gui/docviewerpanel.py +140 -47
  129. novelwriter/gui/editordocument.py +3 -3
  130. novelwriter/gui/itemdetails.py +9 -9
  131. novelwriter/gui/mainmenu.py +47 -47
  132. novelwriter/gui/noveltree.py +53 -61
  133. novelwriter/gui/outline.py +100 -76
  134. novelwriter/gui/projtree.py +246 -112
  135. novelwriter/gui/sidebar.py +9 -8
  136. novelwriter/gui/statusbar.py +49 -7
  137. novelwriter/gui/theme.py +74 -76
  138. novelwriter/guimain.py +175 -330
  139. novelwriter/shared.py +68 -30
  140. novelwriter/tools/dictionaries.py +7 -8
  141. novelwriter/tools/lipsum.py +34 -28
  142. novelwriter/tools/manusbuild.py +3 -4
  143. novelwriter/tools/manuscript.py +25 -32
  144. novelwriter/tools/manussettings.py +194 -225
  145. novelwriter/tools/noveldetails.py +525 -0
  146. novelwriter/tools/welcome.py +819 -0
  147. novelwriter/tools/writingstats.py +26 -13
  148. novelWriter-2.2rc1.dist-info/entry_points.txt +0 -5
  149. novelwriter/assets/icons/typicons_dark/nw_tb-markdown.svg +0 -8
  150. novelwriter/assets/icons/typicons_dark/nw_tb-shortcode.svg +0 -8
  151. novelwriter/assets/icons/typicons_light/nw_tb-markdown.svg +0 -8
  152. novelwriter/assets/icons/typicons_light/nw_tb-shortcode.svg +0 -8
  153. novelwriter/assets/images/wizard-back.jpg +0 -0
  154. novelwriter/assets/text/gplv3_en.htm +0 -641
  155. novelwriter/assets/text/release_notes.htm +0 -17
  156. novelwriter/dialogs/projdetails.py +0 -525
  157. novelwriter/dialogs/projload.py +0 -298
  158. novelwriter/dialogs/updates.py +0 -182
  159. novelwriter/extensions/pageddialog.py +0 -130
  160. novelwriter/tools/projwizard.py +0 -478
  161. {novelWriter-2.2rc1.dist-info → novelWriter-2.3.dist-info}/LICENSE.md +0 -0
  162. {novelWriter-2.2rc1.dist-info → novelWriter-2.3.dist-info}/top_level.txt +0 -0
@@ -3,10 +3,10 @@ novelWriter – GUI Main Window SideBar
3
3
  =====================================
4
4
 
5
5
  File History:
6
- Created: 2022-05-10 [2.0rc1]
6
+ Created: 2022-05-10 [2.0rc1] GuiSideBar
7
7
 
8
8
  This file is a part of novelWriter
9
- Copyright 2018–2023, Veronica Berglyd Olsen
9
+ Copyright 2018–2024, Veronica Berglyd Olsen
10
10
 
11
11
  This program is free software: you can redistribute it and/or modify
12
12
  it under the terms of the GNU General Public License as published by
@@ -27,12 +27,13 @@ import logging
27
27
 
28
28
  from typing import TYPE_CHECKING
29
29
 
30
- from PyQt5.QtCore import QEvent, QPoint, Qt, QSize, pyqtSignal
31
30
  from PyQt5.QtGui import QPalette
31
+ from PyQt5.QtCore import QEvent, QPoint, Qt, QSize, pyqtSignal
32
32
  from PyQt5.QtWidgets import QMenu, QToolButton, QVBoxLayout, QWidget
33
33
 
34
34
  from novelwriter import CONFIG, SHARED
35
35
  from novelwriter.enum import nwView
36
+ from novelwriter.extensions.eventfilters import StatusTipFilter
36
37
 
37
38
  if TYPE_CHECKING: # pragma: no cover
38
39
  from novelwriter.guimain import GuiMain
@@ -54,6 +55,7 @@ class GuiSideBar(QWidget):
54
55
  iPx = CONFIG.pxInt(24)
55
56
  iconSize = QSize(iPx, iPx)
56
57
  self.setContentsMargins(0, 0, 0, 0)
58
+ self.installEventFilter(StatusTipFilter(mainGui))
57
59
 
58
60
  # Buttons
59
61
  self.tbProject = QToolButton(self)
@@ -77,9 +79,9 @@ class GuiSideBar(QWidget):
77
79
  self.tbBuild.clicked.connect(self.mainGui.showBuildManuscriptDialog)
78
80
 
79
81
  self.tbDetails = QToolButton(self)
80
- self.tbDetails.setToolTip("{0} [Shift+F6]".format(self.tr("Project Details")))
82
+ self.tbDetails.setToolTip("{0} [Shift+F6]".format(self.tr("Novel Details")))
81
83
  self.tbDetails.setIconSize(iconSize)
82
- self.tbDetails.clicked.connect(self.mainGui.showProjectDetailsDialog)
84
+ self.tbDetails.clicked.connect(self.mainGui.showNovelDetailsDialog)
83
85
 
84
86
  self.tbStats = QToolButton(self)
85
87
  self.tbStats.setToolTip("{0} [F6]".format(self.tr("Writing Statistics")))
@@ -162,11 +164,10 @@ class GuiSideBar(QWidget):
162
164
 
163
165
  class _PopRightMenu(QMenu):
164
166
 
165
- def event(self, event: QEvent):
167
+ def event(self, event: QEvent) -> bool:
166
168
  """Overload the show event and move the menu popup location."""
167
169
  if event.type() == QEvent.Show:
168
- parent = self.parent()
169
- if isinstance(parent, QWidget):
170
+ if isinstance(parent := self.parent(), QWidget):
170
171
  offset = QPoint(parent.width(), parent.height() - self.height())
171
172
  self.move(parent.mapToGlobal(offset))
172
173
  return super(_PopRightMenu, self).event(event)
@@ -3,10 +3,10 @@ novelWriter – GUI Main Window Status Bar
3
3
  ========================================
4
4
 
5
5
  File History:
6
- Created: 2019-04-20 [0.0.1]
6
+ Created: 2019-04-20 [0.0.1] GuiMainStatus
7
7
 
8
8
  This file is a part of novelWriter
9
- Copyright 2018–2023, Veronica Berglyd Olsen
9
+ Copyright 2018–2024, Veronica Berglyd Olsen
10
10
 
11
11
  This program is free software: you can redistribute it and/or modify
12
12
  it under the terms of the GNU General Public License as published by
@@ -27,9 +27,9 @@ import logging
27
27
 
28
28
  from time import time
29
29
  from typing import TYPE_CHECKING, Literal
30
+ from datetime import datetime
30
31
 
31
32
  from PyQt5.QtCore import pyqtSlot, QLocale
32
- from PyQt5.QtGui import QColor
33
33
  from PyQt5.QtWidgets import qApp, QStatusBar, QLabel
34
34
 
35
35
  from novelwriter import CONFIG, SHARED
@@ -50,12 +50,13 @@ class GuiMainStatus(QStatusBar):
50
50
 
51
51
  logger.debug("Create: GuiMainStatus")
52
52
 
53
- self._refTime = -1.0
53
+ self._refTime = -1.0
54
54
  self._userIdle = False
55
+ self._debugInfo = False
55
56
 
56
- colNone = QColor(*SHARED.theme.statNone)
57
- colSaved = QColor(*SHARED.theme.statSaved)
58
- colUnsaved = QColor(*SHARED.theme.statUnsaved)
57
+ colNone = SHARED.theme.statNone
58
+ colSaved = SHARED.theme.statSaved
59
+ colUnsaved = SHARED.theme.statUnsaved
59
60
 
60
61
  iPx = SHARED.theme.baseIconSize
61
62
 
@@ -223,4 +224,45 @@ class GuiMainStatus(QStatusBar):
223
224
  self.setDocumentStatus(StatusLED.S_BAD if status else StatusLED.S_GOOD)
224
225
  return
225
226
 
227
+ ##
228
+ # Debug
229
+ ##
230
+
231
+ def memInfo(self) -> None: # pragma: no cover
232
+ """Display memory info on the status bar. This is used to
233
+ investigate memory usage and Qt widgets that get left in memory.
234
+ Enabled by the --meminfo command line flag.
235
+
236
+ By default, this tracks memory usage diff after launch. To track
237
+ full memory usage, set environment variable PYTHONTRACEMALLOC=1
238
+ before starting novelWriter.
239
+ """
240
+ import tracemalloc
241
+ from collections import Counter
242
+
243
+ widgets = qApp.allWidgets()
244
+ if not self._debugInfo:
245
+ if tracemalloc.is_tracing():
246
+ self._traceMallocRef = "Total"
247
+ else:
248
+ self._traceMallocRef = "Relative"
249
+ tracemalloc.start()
250
+ self._debugInfo = True
251
+ self._wCounts = Counter([type(x).__name__ for x in widgets])
252
+
253
+ if hasattr(self, "_wCounts"):
254
+ diff = Counter([type(x).__name__ for x in widgets]) - self._wCounts
255
+ for name, count in diff.items():
256
+ logger.debug("Widget '%s': +%d", name, count)
257
+
258
+ mem = tracemalloc.get_traced_memory()
259
+ stamp = datetime.now().strftime("%H:%M:%S")
260
+ self.showMessage((
261
+ f"Debug [{stamp}]"
262
+ f" \u2013 Widgets: {len(widgets)}"
263
+ f" \u2013 {self._traceMallocRef} Memory: {mem[0]:n}"
264
+ f" \u2013 Peak: {mem[1]:n}"
265
+ ), 6000)
266
+ return
267
+
226
268
  # END Class GuiMainStatus
novelwriter/gui/theme.py CHANGED
@@ -7,7 +7,7 @@ Created: 2019-05-18 [0.1.3] GuiTheme
7
7
  Created: 2019-11-08 [0.4] GuiIcons
8
8
 
9
9
  This file is a part of novelWriter
10
- Copyright 2018–2023, Veronica Berglyd Olsen
10
+ Copyright 2018–2024, Veronica Berglyd Olsen
11
11
 
12
12
  This program is free software: you can redistribute it and/or modify
13
13
  it under the terms of the GNU General Public License as published by
@@ -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 = [120, 120, 120]
73
- self.statUnsaved = [200, 15, 39]
74
- self.statSaved = [2, 133, 37]
75
- self.helpText = [0, 0, 0]
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 = [255, 255, 255]
91
- self.colText = [0, 0, 0]
92
- self.colLink = [0, 0, 0]
93
- self.colHead = [0, 0, 0]
94
- self.colHeadH = [0, 0, 0]
95
- self.colEmph = [0, 0, 0]
96
- self.colDialN = [0, 0, 0]
97
- self.colDialD = [0, 0, 0]
98
- self.colDialS = [0, 0, 0]
99
- self.colHidden = [0, 0, 0]
100
- self.colCode = [0, 0, 0]
101
- self.colKey = [0, 0, 0]
102
- self.colVal = [0, 0, 0]
103
- self.colSpell = [0, 0, 0]
104
- self.colError = [0, 0, 0]
105
- self.colRepTag = [0, 0, 0]
106
- self.colMod = [0, 0, 0]
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 = QPalette()
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 == [0, 0, 0]:
259
- if backLNess > textLNess:
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 = [int(255*helpLCol)]*3
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")
@@ -341,7 +344,7 @@ class GuiTheme:
341
344
  if themeName:
342
345
  self._themeList.append((themeKey, themeName))
343
346
 
344
- self._themeList = sorted(self._themeList, key=lambda x: x[1])
347
+ self._themeList = sorted(self._themeList, key=_sortTheme)
345
348
 
346
349
  return self._themeList
347
350
 
@@ -357,7 +360,7 @@ class GuiTheme:
357
360
  if syntaxName:
358
361
  self._syntaxList.append((syntaxKey, syntaxName))
359
362
 
360
- self._syntaxList = sorted(self._syntaxList, key=lambda x: x[1])
363
+ self._syntaxList = sorted(self._syntaxList, key=_sortTheme)
361
364
 
362
365
  return self._syntaxList
363
366
 
@@ -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
- theFont = QFont()
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
- theFont.setFamily("Arial")
376
- theFont.setPointSize(10)
378
+ font.setFamily("Arial")
379
+ font.setPointSize(10)
377
380
  else:
378
- theFont = fontDB.systemFont(QFontDatabase.GeneralFont)
379
- CONFIG.guiFont = theFont.family()
380
- CONFIG.guiFontSize = theFont.pointSize()
381
+ font = fontDB.systemFont(QFontDatabase.GeneralFont)
382
+ CONFIG.guiFont = font.family()
383
+ CONFIG.guiFontSize = font.pointSize()
381
384
  else:
382
- theFont.setFamily(CONFIG.guiFont)
383
- theFont.setPointSize(CONFIG.guiFontSize)
385
+ font.setFamily(CONFIG.guiFont)
386
+ font.setPointSize(CONFIG.guiFontSize)
384
387
 
385
- qApp.setFont(theFont)
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) -> list[int]:
403
+ def _parseColour(self, parser: NWConfigParser, section: str, name: str) -> QColor:
401
404
  """Parse a colour value from a config string."""
402
- if parser.has_option(section, name):
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,20 +441,21 @@ 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",
460
448
  "search_regex", "search_word",
461
449
 
462
450
  # Format Icons
463
- "fmt_bold", "fmt_italic", "fmt_mode-md", "fmt_mode-sc", "fmt_strike", "fmt_subscript",
464
- "fmt_superscript", "fmt_underline",
451
+ "fmt_bold", "fmt_bold-md", "fmt_italic", "fmt_italic-md", "fmt_strike", "fmt_strike-md",
452
+ "fmt_subscript", "fmt_superscript", "fmt_underline",
465
453
 
466
454
  # General Button Icons
467
- "add", "backward", "bookmark", "browse", "checked", "close", "cross", "down", "edit",
468
- "export", "forward", "maximise", "menu", "minimise", "noncheckable", "panel", "refresh",
469
- "remove", "revert", "search_replace", "search", "settings", "unchecked", "up", "view",
455
+ "add", "add_document", "backward", "bookmark", "browse", "checked", "close", "cross",
456
+ "document", "down", "edit", "export", "forward", "import", "list", "maximise", "menu",
457
+ "minimise", "more", "noncheckable", "open", "panel", "refresh", "remove", "revert",
458
+ "search_replace", "search", "settings", "star", "unchecked", "up", "view",
470
459
 
471
460
  # Switches
472
461
  "sticky-on", "sticky-off",
@@ -478,14 +467,14 @@ class GuiIcons:
478
467
  "deco_doc_nt_n",
479
468
  }
480
469
 
481
- TOGGLE_ICON_KEYS = {
482
- "sticky": ("sticky-on", "sticky-off"),
483
- "bullet": ("bullet-on", "bullet-off"),
484
- "fmt_mode": ("fmt_mode-sc", "fmt_mode-md"),
470
+ TOGGLE_ICON_KEYS: dict[str, tuple[str, str]] = {
471
+ "sticky": ("sticky-on", "sticky-off"),
472
+ "bullet": ("bullet-on", "bullet-off"),
485
473
  }
486
474
 
487
- IMAGE_MAP = {
488
- "wiz-back": "wizard-back.jpg",
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"),
489
478
  }
490
479
 
491
480
  def __init__(self, mainTheme: GuiTheme) -> None:
@@ -599,7 +588,8 @@ class GuiIcons:
599
588
  if name in self._themeMap:
600
589
  imgPath = self._themeMap[name]
601
590
  elif name in self.IMAGE_MAP:
602
- imgPath = CONFIG.assetPath("images") / self.IMAGE_MAP[name]
591
+ idx = 0 if self.mainTheme.isLightTheme else 1
592
+ imgPath = CONFIG.assetPath("images") / self.IMAGE_MAP[name][idx]
603
593
  else:
604
594
  logger.error("Decoration with name '%s' does not exist", name)
605
595
  return QPixmap()
@@ -609,12 +599,13 @@ class GuiIcons:
609
599
  return QPixmap()
610
600
 
611
601
  pixmap = QPixmap(str(imgPath))
602
+ tMode = Qt.TransformationMode.SmoothTransformation
612
603
  if w is not None and h is not None:
613
- return pixmap.scaled(w, h, Qt.IgnoreAspectRatio, Qt.SmoothTransformation)
604
+ return pixmap.scaled(w, h, Qt.AspectRatioMode.IgnoreAspectRatio, tMode)
614
605
  elif w is None and h is not None:
615
- return pixmap.scaledToHeight(h, Qt.SmoothTransformation)
606
+ return pixmap.scaledToHeight(h, tMode)
616
607
  elif w is not None and h is None:
617
- return pixmap.scaledToWidth(w, Qt.SmoothTransformation)
608
+ return pixmap.scaledToWidth(w, tMode)
618
609
 
619
610
  return pixmap
620
611
 
@@ -633,8 +624,8 @@ class GuiIcons:
633
624
  pOne = self.getPixmap(self.TOGGLE_ICON_KEYS[name][0], size)
634
625
  pTwo = self.getPixmap(self.TOGGLE_ICON_KEYS[name][1], size)
635
626
  icon = QIcon()
636
- icon.addPixmap(pOne, QIcon.Normal, QIcon.On)
637
- 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)
638
629
  return icon
639
630
  return QIcon()
640
631
 
@@ -642,7 +633,7 @@ class GuiIcons:
642
633
  """Return an icon from the icon buffer as a QPixmap. If it
643
634
  doesn't exist, return an empty QPixmap.
644
635
  """
645
- return self.getIcon(name).pixmap(size[0], size[1], QIcon.Normal)
636
+ return self.getIcon(name).pixmap(size[0], size[1], QIcon.Mode.Normal)
646
637
 
647
638
  def getItemIcon(self, tType: nwItemType, tClass: nwItemClass,
648
639
  tLayout: nwItemLayout, hLevel: str = "H0") -> QIcon:
@@ -734,6 +725,13 @@ class GuiIcons:
734
725
  # Module Functions
735
726
  # =============================================================================================== #
736
727
 
728
+
729
+ def _sortTheme(data: tuple[str, str]) -> str:
730
+ """Key function for theme sorting."""
731
+ key, name = data
732
+ return f"*{name}" if key.startswith("default_") else name
733
+
734
+
737
735
  def _loadInternalName(confParser: NWConfigParser, confFile: str | Path) -> str:
738
736
  """Open a conf file and read the 'name' setting."""
739
737
  try: