novelWriter 2.3.1__py3-none-any.whl → 2.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. {novelWriter-2.3.1.dist-info → novelWriter-2.4.dist-info}/METADATA +5 -6
  2. {novelWriter-2.3.1.dist-info → novelWriter-2.4.dist-info}/RECORD +114 -107
  3. novelwriter/__init__.py +17 -10
  4. novelwriter/assets/i18n/nw_de_DE.qm +0 -0
  5. novelwriter/assets/i18n/nw_en_US.qm +0 -0
  6. novelwriter/assets/i18n/nw_es_419.qm +0 -0
  7. novelwriter/assets/i18n/nw_fr_FR.qm +0 -0
  8. novelwriter/assets/i18n/nw_it_IT.qm +0 -0
  9. novelwriter/assets/i18n/nw_ja_JP.qm +0 -0
  10. novelwriter/assets/i18n/nw_nb_NO.qm +0 -0
  11. novelwriter/assets/i18n/nw_nl_NL.qm +0 -0
  12. novelwriter/assets/i18n/nw_pt_BR.qm +0 -0
  13. novelwriter/assets/i18n/nw_zh_CN.qm +0 -0
  14. novelwriter/assets/icons/none.svg +4 -0
  15. novelwriter/assets/icons/typicons_dark/icons.conf +4 -0
  16. novelwriter/assets/icons/typicons_dark/nw_tb-mark.svg +7 -0
  17. novelwriter/assets/icons/typicons_dark/typ_refresh-flipped.svg +1 -1
  18. novelwriter/assets/icons/typicons_dark/typ_refresh.svg +1 -1
  19. novelwriter/assets/icons/typicons_dark/typ_search-grey.svg +4 -0
  20. novelwriter/assets/icons/typicons_dark/typ_times.svg +1 -1
  21. novelwriter/assets/icons/typicons_dark/typ_unfold-hidden.svg +4 -0
  22. novelwriter/assets/icons/typicons_dark/typ_unfold-visible.svg +4 -0
  23. novelwriter/assets/icons/typicons_light/icons.conf +4 -0
  24. novelwriter/assets/icons/typicons_light/nw_tb-mark.svg +7 -0
  25. novelwriter/assets/icons/typicons_light/typ_refresh-flipped.svg +1 -1
  26. novelwriter/assets/icons/typicons_light/typ_refresh.svg +1 -1
  27. novelwriter/assets/icons/typicons_light/typ_search-grey.svg +4 -0
  28. novelwriter/assets/icons/typicons_light/typ_times.svg +1 -1
  29. novelwriter/assets/icons/typicons_light/typ_unfold-hidden.svg +4 -0
  30. novelwriter/assets/icons/typicons_light/typ_unfold-visible.svg +4 -0
  31. novelwriter/assets/manual.pdf +0 -0
  32. novelwriter/assets/sample.zip +0 -0
  33. novelwriter/assets/syntax/default_dark.conf +1 -0
  34. novelwriter/assets/syntax/default_light.conf +1 -0
  35. novelwriter/assets/syntax/grey_dark.conf +1 -0
  36. novelwriter/assets/syntax/grey_light.conf +1 -0
  37. novelwriter/assets/syntax/light_owl.conf +1 -0
  38. novelwriter/assets/syntax/night_owl.conf +1 -0
  39. novelwriter/assets/syntax/solarized_dark.conf +1 -0
  40. novelwriter/assets/syntax/solarized_light.conf +1 -0
  41. novelwriter/assets/syntax/tomorrow.conf +1 -0
  42. novelwriter/assets/syntax/tomorrow_night.conf +1 -0
  43. novelwriter/assets/syntax/tomorrow_night_blue.conf +1 -0
  44. novelwriter/assets/syntax/tomorrow_night_bright.conf +1 -0
  45. novelwriter/assets/syntax/tomorrow_night_eighties.conf +1 -0
  46. novelwriter/assets/text/credits_en.htm +25 -23
  47. novelwriter/common.py +12 -4
  48. novelwriter/config.py +47 -16
  49. novelwriter/constants.py +5 -6
  50. novelwriter/core/buildsettings.py +64 -44
  51. novelwriter/core/coretools.py +97 -13
  52. novelwriter/core/docbuild.py +74 -7
  53. novelwriter/core/document.py +24 -3
  54. novelwriter/core/index.py +31 -112
  55. novelwriter/core/project.py +10 -15
  56. novelwriter/core/projectxml.py +1 -1
  57. novelwriter/core/sessions.py +2 -2
  58. novelwriter/core/spellcheck.py +3 -3
  59. novelwriter/core/status.py +6 -5
  60. novelwriter/core/storage.py +8 -2
  61. novelwriter/core/tohtml.py +22 -25
  62. novelwriter/core/tokenizer.py +417 -233
  63. novelwriter/core/tomd.py +17 -8
  64. novelwriter/core/toodt.py +386 -351
  65. novelwriter/core/tree.py +8 -8
  66. novelwriter/dialogs/about.py +9 -11
  67. novelwriter/dialogs/docmerge.py +17 -14
  68. novelwriter/dialogs/docsplit.py +20 -19
  69. novelwriter/dialogs/editlabel.py +5 -4
  70. novelwriter/dialogs/preferences.py +32 -40
  71. novelwriter/dialogs/projectsettings.py +31 -28
  72. novelwriter/dialogs/quotes.py +10 -9
  73. novelwriter/dialogs/wordlist.py +17 -14
  74. novelwriter/enum.py +17 -14
  75. novelwriter/error.py +14 -12
  76. novelwriter/extensions/circularprogress.py +12 -8
  77. novelwriter/extensions/configlayout.py +1 -3
  78. novelwriter/extensions/modified.py +51 -2
  79. novelwriter/extensions/pagedsidebar.py +16 -14
  80. novelwriter/extensions/simpleprogress.py +3 -1
  81. novelwriter/extensions/statusled.py +3 -1
  82. novelwriter/extensions/switch.py +10 -9
  83. novelwriter/extensions/switchbox.py +14 -13
  84. novelwriter/extensions/versioninfo.py +1 -1
  85. novelwriter/gui/doceditor.py +433 -496
  86. novelwriter/gui/dochighlight.py +54 -33
  87. novelwriter/gui/docviewer.py +162 -175
  88. novelwriter/gui/docviewerpanel.py +20 -37
  89. novelwriter/gui/editordocument.py +15 -4
  90. novelwriter/gui/itemdetails.py +51 -54
  91. novelwriter/gui/mainmenu.py +37 -16
  92. novelwriter/gui/noveltree.py +31 -37
  93. novelwriter/gui/outline.py +120 -98
  94. novelwriter/gui/projtree.py +61 -67
  95. novelwriter/gui/search.py +362 -0
  96. novelwriter/gui/sidebar.py +36 -45
  97. novelwriter/gui/statusbar.py +14 -14
  98. novelwriter/gui/theme.py +107 -32
  99. novelwriter/guimain.py +209 -202
  100. novelwriter/shared.py +31 -6
  101. novelwriter/text/counting.py +138 -0
  102. novelwriter/tools/dictionaries.py +15 -14
  103. novelwriter/tools/lipsum.py +20 -17
  104. novelwriter/tools/manusbuild.py +43 -35
  105. novelwriter/tools/manuscript.py +381 -104
  106. novelwriter/tools/manussettings.py +262 -125
  107. novelwriter/tools/noveldetails.py +20 -18
  108. novelwriter/tools/welcome.py +52 -49
  109. novelwriter/tools/writingstats.py +61 -55
  110. novelwriter/types.py +90 -0
  111. novelwriter/core/__init__.py +0 -3
  112. novelwriter/dialogs/__init__.py +0 -3
  113. novelwriter/extensions/__init__.py +0 -3
  114. novelwriter/gui/__init__.py +0 -3
  115. novelwriter/tools/__init__.py +0 -3
  116. {novelWriter-2.3.1.dist-info → novelWriter-2.4.dist-info}/LICENSE.md +0 -0
  117. {novelWriter-2.3.1.dist-info → novelWriter-2.4.dist-info}/WHEEL +0 -0
  118. {novelWriter-2.3.1.dist-info → novelWriter-2.4.dist-info}/entry_points.txt +0 -0
  119. {novelWriter-2.3.1.dist-info → novelWriter-2.4.dist-info}/top_level.txt +0 -0
@@ -28,12 +28,14 @@ import logging
28
28
  from typing import TYPE_CHECKING
29
29
 
30
30
  from PyQt5.QtGui import QPalette
31
- from PyQt5.QtCore import QEvent, QPoint, Qt, QSize, pyqtSignal
32
- from PyQt5.QtWidgets import QMenu, QToolButton, QVBoxLayout, QWidget
31
+ from PyQt5.QtCore import QEvent, QPoint, QSize, pyqtSignal
32
+ from PyQt5.QtWidgets import QMenu, QVBoxLayout, QWidget
33
33
 
34
34
  from novelwriter import CONFIG, SHARED
35
35
  from novelwriter.enum import nwView
36
36
  from novelwriter.extensions.eventfilters import StatusTipFilter
37
+ from novelwriter.extensions.modified import NIconToolButton
38
+ from novelwriter.gui.theme import STYLES_BIG_TOOLBUTTON
37
39
 
38
40
  if TYPE_CHECKING: # pragma: no cover
39
41
  from novelwriter.guimain import GuiMain
@@ -43,7 +45,7 @@ logger = logging.getLogger(__name__)
43
45
 
44
46
  class GuiSideBar(QWidget):
45
47
 
46
- viewChangeRequested = pyqtSignal(nwView)
48
+ requestViewChange = pyqtSignal(nwView)
47
49
 
48
50
  def __init__(self, mainGui: GuiMain) -> None:
49
51
  super().__init__(parent=mainGui)
@@ -52,47 +54,44 @@ class GuiSideBar(QWidget):
52
54
 
53
55
  self.mainGui = mainGui
54
56
 
55
- iPx = CONFIG.pxInt(24)
56
- iconSize = QSize(iPx, iPx)
57
+ iPx = int(1.2*SHARED.theme.baseButtonHeight)
58
+ iSz = QSize(iPx, iPx)
59
+
57
60
  self.setContentsMargins(0, 0, 0, 0)
58
61
  self.installEventFilter(StatusTipFilter(mainGui))
59
62
 
60
63
  # Buttons
61
- self.tbProject = QToolButton(self)
64
+ self.tbProject = NIconToolButton(self, iSz)
62
65
  self.tbProject.setToolTip("{0} [Ctrl+T]".format(self.tr("Project Tree View")))
63
- self.tbProject.setIconSize(iconSize)
64
- self.tbProject.clicked.connect(lambda: self.viewChangeRequested.emit(nwView.PROJECT))
66
+ self.tbProject.clicked.connect(lambda: self.requestViewChange.emit(nwView.PROJECT))
65
67
 
66
- self.tbNovel = QToolButton(self)
68
+ self.tbNovel = NIconToolButton(self, iSz)
67
69
  self.tbNovel.setToolTip("{0} [Ctrl+T]".format(self.tr("Novel Tree View")))
68
- self.tbNovel.setIconSize(iconSize)
69
- self.tbNovel.clicked.connect(lambda: self.viewChangeRequested.emit(nwView.NOVEL))
70
+ self.tbNovel.clicked.connect(lambda: self.requestViewChange.emit(nwView.NOVEL))
71
+
72
+ self.tbSearch = NIconToolButton(self, iSz)
73
+ self.tbSearch.setToolTip("{0} [Ctrl+Shift+F]".format(self.tr("Project Search")))
74
+ self.tbSearch.clicked.connect(lambda: self.requestViewChange.emit(nwView.SEARCH))
70
75
 
71
- self.tbOutline = QToolButton(self)
76
+ self.tbOutline = NIconToolButton(self, iSz)
72
77
  self.tbOutline.setToolTip("{0} [Ctrl+Shift+T]".format(self.tr("Novel Outline View")))
73
- self.tbOutline.setIconSize(iconSize)
74
- self.tbOutline.clicked.connect(lambda: self.viewChangeRequested.emit(nwView.OUTLINE))
78
+ self.tbOutline.clicked.connect(lambda: self.requestViewChange.emit(nwView.OUTLINE))
75
79
 
76
- self.tbBuild = QToolButton(self)
80
+ self.tbBuild = NIconToolButton(self, iSz)
77
81
  self.tbBuild.setToolTip("{0} [F5]".format(self.tr("Build Manuscript")))
78
- self.tbBuild.setIconSize(iconSize)
79
82
  self.tbBuild.clicked.connect(self.mainGui.showBuildManuscriptDialog)
80
83
 
81
- self.tbDetails = QToolButton(self)
84
+ self.tbDetails = NIconToolButton(self, iSz)
82
85
  self.tbDetails.setToolTip("{0} [Shift+F6]".format(self.tr("Novel Details")))
83
- self.tbDetails.setIconSize(iconSize)
84
86
  self.tbDetails.clicked.connect(self.mainGui.showNovelDetailsDialog)
85
87
 
86
- self.tbStats = QToolButton(self)
88
+ self.tbStats = NIconToolButton(self, iSz)
87
89
  self.tbStats.setToolTip("{0} [F6]".format(self.tr("Writing Statistics")))
88
- self.tbStats.setIconSize(iconSize)
89
90
  self.tbStats.clicked.connect(self.mainGui.showWritingStatsDialog)
90
91
 
91
92
  # Settings Menu
92
- self.tbSettings = QToolButton(self)
93
+ self.tbSettings = NIconToolButton(self, iSz)
93
94
  self.tbSettings.setToolTip(self.tr("Settings"))
94
- self.tbSettings.setIconSize(iconSize)
95
- self.tbSettings.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
96
95
 
97
96
  self.mSettings = _PopRightMenu(self.tbSettings)
98
97
  self.mSettings.addAction(self.mainGui.mainMenu.aEditWordList)
@@ -101,12 +100,12 @@ class GuiSideBar(QWidget):
101
100
  self.mSettings.addAction(self.mainGui.mainMenu.aPreferences)
102
101
 
103
102
  self.tbSettings.setMenu(self.mSettings)
104
- self.tbSettings.setPopupMode(QToolButton.InstantPopup)
105
103
 
106
104
  # Assemble
107
105
  self.outerBox = QVBoxLayout()
108
106
  self.outerBox.addWidget(self.tbProject)
109
107
  self.outerBox.addWidget(self.tbNovel)
108
+ self.outerBox.addWidget(self.tbSearch)
110
109
  self.outerBox.addWidget(self.tbOutline)
111
110
  self.outerBox.addWidget(self.tbBuild)
112
111
  self.outerBox.addStretch(1)
@@ -126,36 +125,28 @@ class GuiSideBar(QWidget):
126
125
  def updateTheme(self) -> None:
127
126
  """Initialise GUI elements that depend on specific settings."""
128
127
  qPalette = self.palette()
129
- qPalette.setBrush(QPalette.Window, qPalette.base())
128
+ qPalette.setBrush(QPalette.ColorRole.Window, qPalette.base())
130
129
  self.setPalette(qPalette)
131
130
 
132
- fadeCol = qPalette.text().color()
133
- buttonStyle = (
134
- "QToolButton {{padding: {0}px; border: none; background: transparent;}} "
135
- "QToolButton:hover {{border: none; background: rgba({1},{2},{3},0.2);}}"
136
- ).format(CONFIG.pxInt(6), fadeCol.red(), fadeCol.green(), fadeCol.blue())
137
- buttonStyleMenu = f"{buttonStyle} QToolButton::menu-indicator {{image: none;}}"
131
+ buttonStyle = SHARED.theme.getStyleSheet(STYLES_BIG_TOOLBUTTON)
138
132
 
139
- self.tbProject.setIcon(SHARED.theme.getIcon("view_editor"))
140
133
  self.tbProject.setStyleSheet(buttonStyle)
141
-
142
- self.tbNovel.setIcon(SHARED.theme.getIcon("view_novel"))
143
134
  self.tbNovel.setStyleSheet(buttonStyle)
144
-
145
- self.tbOutline.setIcon(SHARED.theme.getIcon("view_outline"))
135
+ self.tbSearch.setStyleSheet(buttonStyle)
146
136
  self.tbOutline.setStyleSheet(buttonStyle)
147
-
148
- self.tbBuild.setIcon(SHARED.theme.getIcon("view_build"))
149
137
  self.tbBuild.setStyleSheet(buttonStyle)
150
-
151
- self.tbDetails.setIcon(SHARED.theme.getIcon("proj_details"))
152
138
  self.tbDetails.setStyleSheet(buttonStyle)
153
-
154
- self.tbStats.setIcon(SHARED.theme.getIcon("proj_stats"))
155
139
  self.tbStats.setStyleSheet(buttonStyle)
140
+ self.tbSettings.setStyleSheet(buttonStyle)
156
141
 
157
- self.tbSettings.setIcon(SHARED.theme.getIcon("settings"))
158
- self.tbSettings.setStyleSheet(buttonStyleMenu)
142
+ self.tbProject.setThemeIcon("view_editor")
143
+ self.tbNovel.setThemeIcon("view_novel")
144
+ self.tbSearch.setThemeIcon("view_search")
145
+ self.tbOutline.setThemeIcon("view_outline")
146
+ self.tbBuild.setThemeIcon("view_build")
147
+ self.tbDetails.setThemeIcon("proj_details")
148
+ self.tbStats.setThemeIcon("proj_stats")
149
+ self.tbSettings.setThemeIcon("settings")
159
150
 
160
151
  return
161
152
 
@@ -166,7 +157,7 @@ class _PopRightMenu(QMenu):
166
157
 
167
158
  def event(self, event: QEvent) -> bool:
168
159
  """Overload the show event and move the menu popup location."""
169
- if event.type() == QEvent.Show:
160
+ if event.type() == QEvent.Type.Show:
170
161
  if isinstance(parent := self.parent(), QWidget):
171
162
  offset = QPoint(parent.width(), parent.height() - self.height())
172
163
  self.move(parent.mapToGlobal(offset))
@@ -25,12 +25,12 @@ from __future__ import annotations
25
25
 
26
26
  import logging
27
27
 
28
+ from datetime import datetime
28
29
  from time import time
29
30
  from typing import TYPE_CHECKING, Literal
30
- from datetime import datetime
31
31
 
32
32
  from PyQt5.QtCore import pyqtSlot, QLocale
33
- from PyQt5.QtWidgets import qApp, QStatusBar, QLabel
33
+ from PyQt5.QtWidgets import QApplication, QStatusBar, QLabel
34
34
 
35
35
  from novelwriter import CONFIG, SHARED
36
36
  from novelwriter.common import formatTime
@@ -58,7 +58,7 @@ class GuiMainStatus(QStatusBar):
58
58
  colSaved = SHARED.theme.statSaved
59
59
  colUnsaved = SHARED.theme.statUnsaved
60
60
 
61
- iPx = SHARED.theme.baseIconSize
61
+ iPx = SHARED.theme.baseIconHeight
62
62
 
63
63
  # Permanent Widgets
64
64
  # =================
@@ -66,8 +66,8 @@ class GuiMainStatus(QStatusBar):
66
66
  xM = CONFIG.pxInt(8)
67
67
 
68
68
  # The Spell Checker Language
69
- self.langIcon = QLabel("")
70
- self.langText = QLabel(self.tr("None"))
69
+ self.langIcon = QLabel("", self)
70
+ self.langText = QLabel(self.tr("None"), self)
71
71
  self.langIcon.setContentsMargins(0, 0, 0, 0)
72
72
  self.langText.setContentsMargins(0, 0, xM, 0)
73
73
  self.addPermanentWidget(self.langIcon)
@@ -75,7 +75,7 @@ class GuiMainStatus(QStatusBar):
75
75
 
76
76
  # The Editor Status
77
77
  self.docIcon = StatusLED(colNone, colSaved, colUnsaved, iPx, iPx, self)
78
- self.docText = QLabel(self.tr("Editor"))
78
+ self.docText = QLabel(self.tr("Editor"), self)
79
79
  self.docIcon.setContentsMargins(0, 0, 0, 0)
80
80
  self.docText.setContentsMargins(0, 0, xM, 0)
81
81
  self.addPermanentWidget(self.docIcon)
@@ -83,15 +83,15 @@ class GuiMainStatus(QStatusBar):
83
83
 
84
84
  # The Project Status
85
85
  self.projIcon = StatusLED(colNone, colSaved, colUnsaved, iPx, iPx, self)
86
- self.projText = QLabel(self.tr("Project"))
86
+ self.projText = QLabel(self.tr("Project"), self)
87
87
  self.projIcon.setContentsMargins(0, 0, 0, 0)
88
88
  self.projText.setContentsMargins(0, 0, xM, 0)
89
89
  self.addPermanentWidget(self.projIcon)
90
90
  self.addPermanentWidget(self.projText)
91
91
 
92
92
  # The Project and Session Stats
93
- self.statsIcon = QLabel()
94
- self.statsText = QLabel("")
93
+ self.statsIcon = QLabel(self)
94
+ self.statsText = QLabel("", self)
95
95
  self.statsIcon.setContentsMargins(0, 0, 0, 0)
96
96
  self.statsText.setContentsMargins(0, 0, xM, 0)
97
97
  self.addPermanentWidget(self.statsIcon)
@@ -99,8 +99,8 @@ class GuiMainStatus(QStatusBar):
99
99
 
100
100
  # The Session Clock
101
101
  # Set the minimum width so the label doesn't rescale every second
102
- self.timeIcon = QLabel()
103
- self.timeText = QLabel("")
102
+ self.timeIcon = QLabel(self)
103
+ self.timeText = QLabel("", self)
104
104
  self.timeText.setToolTip(self.tr("Session Time"))
105
105
  self.timeText.setMinimumWidth(SHARED.theme.getTextWidth("00:00:00:"))
106
106
  self.timeIcon.setContentsMargins(0, 0, 0, 0)
@@ -130,7 +130,7 @@ class GuiMainStatus(QStatusBar):
130
130
 
131
131
  def updateTheme(self) -> None:
132
132
  """Update theme elements."""
133
- iPx = SHARED.theme.baseIconSize
133
+ iPx = SHARED.theme.baseIconHeight
134
134
  self.langIcon.setPixmap(SHARED.theme.getPixmap("status_lang", (iPx, iPx)))
135
135
  self.statsIcon.setPixmap(SHARED.theme.getPixmap("status_stats", (iPx, iPx)))
136
136
  self.timePixmap = SHARED.theme.getPixmap("status_time", (iPx, iPx))
@@ -198,7 +198,7 @@ class GuiMainStatus(QStatusBar):
198
198
  def setStatusMessage(self, message: str) -> None:
199
199
  """Set the status bar message to display."""
200
200
  self.showMessage(message, nwConst.STATUS_MSG_TIMEOUT)
201
- qApp.processEvents()
201
+ QApplication.processEvents()
202
202
  return
203
203
 
204
204
  @pyqtSlot(str, str)
@@ -240,7 +240,7 @@ class GuiMainStatus(QStatusBar):
240
240
  import tracemalloc
241
241
  from collections import Counter
242
242
 
243
- widgets = qApp.allWidgets()
243
+ widgets = QApplication.allWidgets()
244
244
  if not self._debugInfo:
245
245
  if tracemalloc.is_tracing():
246
246
  self._traceMallocRef = "Total"
novelwriter/gui/theme.py CHANGED
@@ -29,20 +29,24 @@ import logging
29
29
  from math import ceil
30
30
  from pathlib import Path
31
31
 
32
- from PyQt5.QtCore import Qt
33
- from PyQt5.QtWidgets import qApp
32
+ from PyQt5.QtCore import QSize, Qt
34
33
  from PyQt5.QtGui import (
35
34
  QPalette, QColor, QIcon, QFont, QFontMetrics, QFontDatabase, QPixmap
36
35
  )
36
+ from PyQt5.QtWidgets import QApplication
37
37
 
38
38
  from novelwriter import CONFIG
39
+ from novelwriter.common import NWConfigParser, cssCol, minmax
40
+ from novelwriter.constants import nwLabels
39
41
  from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType
40
42
  from novelwriter.error import logException
41
- from novelwriter.common import NWConfigParser, minmax
42
- from novelwriter.constants import nwLabels
43
43
 
44
44
  logger = logging.getLogger(__name__)
45
45
 
46
+ STYLES_FLAT_TABS = "flatTabWidget"
47
+ STYLES_MIN_TOOLBUTTON = "minimalToolButton"
48
+ STYLES_BIG_TOOLBUTTON = "bigToolButton"
49
+
46
50
 
47
51
  # =============================================================================================== #
48
52
  # Gui Theme Class
@@ -70,9 +74,9 @@ class GuiTheme:
70
74
  self.isLightTheme = True
71
75
 
72
76
  # GUI
73
- self.statNone = QColor(120, 120, 120)
74
- self.statUnsaved = QColor(200, 15, 39)
75
- self.statSaved = QColor(2, 133, 37)
77
+ self.statNone = QColor(0, 0, 0)
78
+ self.statUnsaved = QColor(0, 0, 0)
79
+ self.statSaved = QColor(0, 0, 0)
76
80
  self.helpText = QColor(0, 0, 0)
77
81
 
78
82
  # Loaded Syntax Settings
@@ -106,6 +110,7 @@ class GuiTheme:
106
110
  self.colError = QColor(0, 0, 0)
107
111
  self.colRepTag = QColor(0, 0, 0)
108
112
  self.colMod = QColor(0, 0, 0)
113
+ self.colMark = QColor(255, 255, 255, 128)
109
114
 
110
115
  # Class Setup
111
116
  # ===========
@@ -119,6 +124,7 @@ class GuiTheme:
119
124
  self._syntaxList: list[tuple[str, str]] = []
120
125
  self._availThemes: dict[str, Path] = {}
121
126
  self._availSyntax: dict[str, Path] = {}
127
+ self._styleSheets: dict[str, str] = {}
122
128
 
123
129
  self._listConf(self._availSyntax, CONFIG.assetPath("syntax"))
124
130
  self._listConf(self._availThemes, CONFIG.assetPath("themes"))
@@ -138,31 +144,42 @@ class GuiTheme:
138
144
  self.getHeaderDecorationNarrow = self.iconCache.getHeaderDecorationNarrow
139
145
 
140
146
  # Extract Other Info
141
- self.guiDPI = qApp.primaryScreen().logicalDotsPerInchX()
142
- self.guiScale = qApp.primaryScreen().logicalDotsPerInchX()/96.0
147
+ self.guiDPI = QApplication.primaryScreen().logicalDotsPerInchX()
148
+ self.guiScale = QApplication.primaryScreen().logicalDotsPerInchX()/96.0
143
149
  CONFIG.guiScale = self.guiScale
144
150
  logger.debug("GUI DPI: %.1f", self.guiDPI)
145
151
  logger.debug("GUI Scale: %.2f", self.guiScale)
146
152
 
147
153
  # Fonts
148
- self.guiFont = qApp.font()
154
+ self.guiFont = QApplication.font()
155
+ self.guiFontB = QApplication.font()
156
+ self.guiFontB.setBold(True)
149
157
 
150
158
  qMetric = QFontMetrics(self.guiFont)
159
+ fHeight = qMetric.height()
160
+ fAscent = qMetric.ascent()
151
161
  self.fontPointSize = self.guiFont.pointSizeF()
152
- self.fontPixelSize = int(round(qMetric.height()))
153
- self.baseIconSize = int(round(qMetric.ascent()))
162
+ self.fontPixelSize = int(round(fHeight))
163
+ self.baseIconHeight = int(round(fAscent))
164
+ self.baseButtonHeight = int(round(1.35*fAscent))
154
165
  self.textNHeight = qMetric.boundingRect("N").height()
155
166
  self.textNWidth = qMetric.boundingRect("N").width()
156
167
 
168
+ self.baseIconSize = QSize(self.baseIconHeight, self.baseIconHeight)
169
+ self.buttonIconSize = QSize(int(0.9*self.baseIconHeight), int(0.9*self.baseIconHeight))
170
+
157
171
  # Monospace Font
158
172
  self.guiFontFixed = QFont()
159
173
  self.guiFontFixed.setPointSizeF(0.95*self.fontPointSize)
160
- self.guiFontFixed.setFamily(QFontDatabase.systemFont(QFontDatabase.FixedFont).family())
174
+ self.guiFontFixed.setFamily(
175
+ QFontDatabase.systemFont(QFontDatabase.SystemFont.FixedFont).family()
176
+ )
161
177
 
162
178
  logger.debug("GUI Font Family: %s", self.guiFont.family())
163
179
  logger.debug("GUI Font Point Size: %.2f", self.fontPointSize)
164
180
  logger.debug("GUI Font Pixel Size: %d", self.fontPixelSize)
165
- logger.debug("GUI Base Icon Size: %d", self.baseIconSize)
181
+ logger.debug("GUI Base Icon Height: %d", self.baseIconHeight)
182
+ logger.debug("GUI Base Button Height: %d", self.baseButtonHeight)
166
183
  logger.debug("Text 'N' Height: %d", self.textNHeight)
167
184
  logger.debug("Text 'N' Width: %d", self.textNWidth)
168
185
 
@@ -210,6 +227,10 @@ class GuiTheme:
210
227
  logException()
211
228
  return False
212
229
 
230
+ # Reset Palette
231
+ self._guiPalette = QApplication.style().standardPalette()
232
+ self._resetGuiColors()
233
+
213
234
  # Main
214
235
  sec = "Main"
215
236
  if parser.has_section(sec):
@@ -239,8 +260,6 @@ class GuiTheme:
239
260
  self._setPalette(parser, sec, "highlightedtext", QPalette.ColorRole.HighlightedText)
240
261
  self._setPalette(parser, sec, "link", QPalette.ColorRole.Link)
241
262
  self._setPalette(parser, sec, "linkvisited", QPalette.ColorRole.LinkVisited)
242
- else:
243
- self._guiPalette = qApp.style().standardPalette()
244
263
 
245
264
  # GUI
246
265
  sec = "GUI"
@@ -269,7 +288,10 @@ class GuiTheme:
269
288
  self.iconCache.loadTheme(self.themeIcons or defaultIcons)
270
289
 
271
290
  # Apply Styles
272
- qApp.setPalette(self._guiPalette)
291
+ QApplication.setPalette(self._guiPalette)
292
+
293
+ # Reset stylesheets so that they are regenerated
294
+ self._buildStyleSheets(self._guiPalette)
273
295
 
274
296
  return True
275
297
 
@@ -329,6 +351,7 @@ class GuiTheme:
329
351
  self.colError = self._parseColour(confParser, cnfSec, "errorline")
330
352
  self.colRepTag = self._parseColour(confParser, cnfSec, "replacetag")
331
353
  self.colMod = self._parseColour(confParser, cnfSec, "modifier")
354
+ self.colMark = self._parseColour(confParser, cnfSec, "texthighlight")
332
355
 
333
356
  return True
334
357
 
@@ -364,10 +387,22 @@ class GuiTheme:
364
387
 
365
388
  return self._syntaxList
366
389
 
390
+ def getStyleSheet(self, name: str) -> str:
391
+ """Load a standard style sheet."""
392
+ return self._styleSheets.get(name, "")
393
+
367
394
  ##
368
395
  # Internal Functions
369
396
  ##
370
397
 
398
+ def _resetGuiColors(self) -> None:
399
+ """Reset GUI colours to default values."""
400
+ self.statNone = QColor(120, 120, 120)
401
+ self.statUnsaved = QColor(200, 15, 39)
402
+ self.statSaved = QColor(2, 133, 37)
403
+ self.helpText = QColor(0, 0, 0)
404
+ return
405
+
371
406
  def _setGuiFont(self) -> None:
372
407
  """Update the GUI's font style from settings."""
373
408
  font = QFont()
@@ -378,14 +413,14 @@ class GuiTheme:
378
413
  font.setFamily("Arial")
379
414
  font.setPointSize(10)
380
415
  else:
381
- font = fontDB.systemFont(QFontDatabase.GeneralFont)
416
+ font = fontDB.systemFont(QFontDatabase.SystemFont.GeneralFont)
382
417
  CONFIG.guiFont = font.family()
383
418
  CONFIG.guiFontSize = font.pointSize()
384
419
  else:
385
420
  font.setFamily(CONFIG.guiFont)
386
421
  font.setPointSize(CONFIG.guiFontSize)
387
422
 
388
- qApp.setFont(font)
423
+ QApplication.setFont(font)
389
424
 
390
425
  return
391
426
 
@@ -410,6 +445,41 @@ class GuiTheme:
410
445
  self._guiPalette.setColor(value, self._parseColour(parser, section, name))
411
446
  return
412
447
 
448
+ def _buildStyleSheets(self, palette: QPalette) -> None:
449
+ """Build default style sheets."""
450
+ self._styleSheets = {}
451
+
452
+ aPx = CONFIG.pxInt(2)
453
+ bPx = CONFIG.pxInt(4)
454
+ cPx = CONFIG.pxInt(6)
455
+ dPx = CONFIG.pxInt(8)
456
+
457
+ tCol = palette.text().color()
458
+ hCol = palette.highlight().color()
459
+
460
+ # Flat Tab Widget and Tab Bar:
461
+ self._styleSheets[STYLES_FLAT_TABS] = (
462
+ "QTabWidget::pane {border: 0;} "
463
+ f"QTabWidget QTabBar::tab {{border: 0; padding: {bPx}px {dPx}px;}} "
464
+ f"QTabWidget QTabBar::tab:selected {{color: {cssCol(hCol)};}} "
465
+ )
466
+
467
+ # Minimal Tool Button
468
+ self._styleSheets[STYLES_MIN_TOOLBUTTON] = (
469
+ f"QToolButton {{padding: {aPx}px; margin: 0; border: none; background: transparent;}} "
470
+ f"QToolButton:hover {{border: none; background: {cssCol(tCol, 48)};}} "
471
+ "QToolButton::menu-indicator {image: none;} "
472
+ )
473
+
474
+ # Big Tool Button
475
+ self._styleSheets[STYLES_BIG_TOOLBUTTON] = (
476
+ f"QToolButton {{padding: {cPx}px; margin: 0; border: none; background: transparent;}} "
477
+ f"QToolButton:hover {{border: none; background: {cssCol(tCol, 48)};}} "
478
+ "QToolButton::menu-indicator {image: none;} "
479
+ )
480
+
481
+ return
482
+
413
483
  # End Class GuiTheme
414
484
 
415
485
 
@@ -437,7 +507,7 @@ class GuiIcons:
437
507
  "build_excluded", "build_filtered", "build_included", "proj_chapter", "proj_details",
438
508
  "proj_document", "proj_folder", "proj_note", "proj_nwx", "proj_section", "proj_scene",
439
509
  "proj_stats", "proj_title", "status_idle", "status_lang", "status_lines", "status_stats",
440
- "status_time", "view_build", "view_editor", "view_novel", "view_outline",
510
+ "status_time", "view_build", "view_editor", "view_novel", "view_outline", "view_search",
441
511
 
442
512
  # Class Icons
443
513
  "cls_archive", "cls_character", "cls_custom", "cls_entity", "cls_none", "cls_novel",
@@ -448,8 +518,8 @@ class GuiIcons:
448
518
  "search_regex", "search_word",
449
519
 
450
520
  # Format Icons
451
- "fmt_bold", "fmt_bold-md", "fmt_italic", "fmt_italic-md", "fmt_strike", "fmt_strike-md",
452
- "fmt_subscript", "fmt_superscript", "fmt_underline",
521
+ "fmt_bold", "fmt_bold-md", "fmt_italic", "fmt_italic-md", "fmt_mark", "fmt_strike",
522
+ "fmt_strike-md", "fmt_subscript", "fmt_superscript", "fmt_underline",
453
523
 
454
524
  # General Button Icons
455
525
  "add", "add_document", "backward", "bookmark", "browse", "checked", "close", "cross",
@@ -460,6 +530,7 @@ class GuiIcons:
460
530
  # Switches
461
531
  "sticky-on", "sticky-off",
462
532
  "bullet-on", "bullet-off",
533
+ "unfold-show", "unfold-hide",
463
534
 
464
535
  # Decorations
465
536
  "deco_doc_h0", "deco_doc_h1", "deco_doc_h2", "deco_doc_h3", "deco_doc_h4", "deco_doc_more",
@@ -470,6 +541,7 @@ class GuiIcons:
470
541
  TOGGLE_ICON_KEYS: dict[str, tuple[str, str]] = {
471
542
  "sticky": ("sticky-on", "sticky-off"),
472
543
  "bullet": ("bullet-on", "bullet-off"),
544
+ "unfold": ("unfold-show", "unfold-hide"),
473
545
  }
474
546
 
475
547
  IMAGE_MAP: dict[str, tuple[str, str]] = {
@@ -491,6 +563,9 @@ class GuiIcons:
491
563
  self._confName = "icons.conf"
492
564
  self._iconPath = CONFIG.assetPath("icons")
493
565
 
566
+ # None Icon
567
+ self._noIcon = QIcon(str(self._iconPath / "none.svg"))
568
+
494
569
  # Icon Theme Meta
495
570
  self.themeName = ""
496
571
  self.themeDescription = ""
@@ -621,13 +696,13 @@ class GuiIcons:
621
696
  def getToggleIcon(self, name: str, size: tuple[int, int]) -> QIcon:
622
697
  """Return a toggle icon from the icon buffer. or load it."""
623
698
  if name in self.TOGGLE_ICON_KEYS:
624
- pOne = self.getPixmap(self.TOGGLE_ICON_KEYS[name][0], size)
699
+ pOne = self.getPixmap(self.TOGGLE_ICON_KEYS[name][0], size)
625
700
  pTwo = self.getPixmap(self.TOGGLE_ICON_KEYS[name][1], size)
626
701
  icon = QIcon()
627
702
  icon.addPixmap(pOne, QIcon.Mode.Normal, QIcon.State.On)
628
703
  icon.addPixmap(pTwo, QIcon.Mode.Normal, QIcon.State.Off)
629
704
  return icon
630
- return QIcon()
705
+ return self._noIcon
631
706
 
632
707
  def getPixmap(self, name: str, size: tuple[int, int]) -> QPixmap:
633
708
  """Return an icon from the icon buffer as a QPixmap. If it
@@ -638,7 +713,7 @@ class GuiIcons:
638
713
  def getItemIcon(self, tType: nwItemType, tClass: nwItemClass,
639
714
  tLayout: nwItemLayout, hLevel: str = "H0") -> QIcon:
640
715
  """Get the correct icon for a project item based on type, class
641
- and header level
716
+ and heading level
642
717
  """
643
718
  iconName = None
644
719
  if tType == nwItemType.ROOT:
@@ -659,14 +734,14 @@ class GuiIcons:
659
734
  elif tLayout == nwItemLayout.NOTE:
660
735
  iconName = "proj_note"
661
736
  if iconName is None:
662
- return QIcon()
737
+ return self._noIcon
663
738
 
664
739
  return self.getIcon(iconName)
665
740
 
666
741
  def getHeaderDecoration(self, hLevel: int) -> QPixmap:
667
- """Get the decoration for a specific header level."""
742
+ """Get the decoration for a specific heading level."""
668
743
  if not self._headerDec:
669
- iPx = self.mainTheme.baseIconSize
744
+ iPx = self.mainTheme.baseIconHeight
670
745
  self._headerDec = [
671
746
  self.loadDecoration("deco_doc_h0", h=iPx),
672
747
  self.loadDecoration("deco_doc_h1", h=iPx),
@@ -677,9 +752,9 @@ class GuiIcons:
677
752
  return self._headerDec[minmax(hLevel, 0, 4)]
678
753
 
679
754
  def getHeaderDecorationNarrow(self, hLevel: int) -> QPixmap:
680
- """Get the narrow decoration for a specific header level."""
755
+ """Get the narrow decoration for a specific heading level."""
681
756
  if not self._headerDecNarrow:
682
- iPx = self.mainTheme.baseIconSize
757
+ iPx = self.mainTheme.baseIconHeight
683
758
  self._headerDecNarrow = [
684
759
  self.loadDecoration("deco_doc_h0_n", h=iPx),
685
760
  self.loadDecoration("deco_doc_h1_n", h=iPx),
@@ -700,7 +775,7 @@ class GuiIcons:
700
775
  """
701
776
  if name not in self.ICON_KEYS:
702
777
  logger.error("Requested unknown icon name '%s'", name)
703
- return QIcon()
778
+ return self._noIcon
704
779
 
705
780
  # If we just want the app icons, return right away
706
781
  if name == "novelwriter":
@@ -716,7 +791,7 @@ class GuiIcons:
716
791
  # If we didn't find one, give up and return an empty icon
717
792
  logger.warning("Did not load an icon for '%s'", name)
718
793
 
719
- return QIcon()
794
+ return self._noIcon
720
795
 
721
796
  # END Class GuiIcons
722
797