novelWriter 2.3.1__py3-none-any.whl → 2.4rc1__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 (107) hide show
  1. {novelWriter-2.3.1.dist-info → novelWriter-2.4rc1.dist-info}/METADATA +5 -6
  2. {novelWriter-2.3.1.dist-info → novelWriter-2.4rc1.dist-info}/RECORD +102 -95
  3. novelwriter/__init__.py +7 -7
  4. novelwriter/assets/icons/none.svg +4 -0
  5. novelwriter/assets/icons/typicons_dark/icons.conf +4 -0
  6. novelwriter/assets/icons/typicons_dark/nw_tb-mark.svg +7 -0
  7. novelwriter/assets/icons/typicons_dark/typ_refresh-flipped.svg +1 -1
  8. novelwriter/assets/icons/typicons_dark/typ_refresh.svg +1 -1
  9. novelwriter/assets/icons/typicons_dark/typ_search-grey.svg +4 -0
  10. novelwriter/assets/icons/typicons_dark/typ_times.svg +1 -1
  11. novelwriter/assets/icons/typicons_dark/typ_unfold-hidden.svg +4 -0
  12. novelwriter/assets/icons/typicons_dark/typ_unfold-visible.svg +4 -0
  13. novelwriter/assets/icons/typicons_light/icons.conf +4 -0
  14. novelwriter/assets/icons/typicons_light/nw_tb-mark.svg +7 -0
  15. novelwriter/assets/icons/typicons_light/typ_refresh-flipped.svg +1 -1
  16. novelwriter/assets/icons/typicons_light/typ_refresh.svg +1 -1
  17. novelwriter/assets/icons/typicons_light/typ_search-grey.svg +4 -0
  18. novelwriter/assets/icons/typicons_light/typ_times.svg +1 -1
  19. novelwriter/assets/icons/typicons_light/typ_unfold-hidden.svg +4 -0
  20. novelwriter/assets/icons/typicons_light/typ_unfold-visible.svg +4 -0
  21. novelwriter/assets/manual.pdf +0 -0
  22. novelwriter/assets/sample.zip +0 -0
  23. novelwriter/assets/syntax/default_dark.conf +1 -0
  24. novelwriter/assets/syntax/default_light.conf +1 -0
  25. novelwriter/assets/syntax/grey_dark.conf +1 -0
  26. novelwriter/assets/syntax/grey_light.conf +1 -0
  27. novelwriter/assets/syntax/light_owl.conf +1 -0
  28. novelwriter/assets/syntax/night_owl.conf +1 -0
  29. novelwriter/assets/syntax/solarized_dark.conf +1 -0
  30. novelwriter/assets/syntax/solarized_light.conf +1 -0
  31. novelwriter/assets/syntax/tomorrow.conf +1 -0
  32. novelwriter/assets/syntax/tomorrow_night.conf +1 -0
  33. novelwriter/assets/syntax/tomorrow_night_blue.conf +1 -0
  34. novelwriter/assets/syntax/tomorrow_night_bright.conf +1 -0
  35. novelwriter/assets/syntax/tomorrow_night_eighties.conf +1 -0
  36. novelwriter/assets/text/credits_en.htm +25 -23
  37. novelwriter/common.py +7 -2
  38. novelwriter/config.py +43 -16
  39. novelwriter/constants.py +5 -6
  40. novelwriter/core/buildsettings.py +60 -40
  41. novelwriter/core/coretools.py +97 -13
  42. novelwriter/core/docbuild.py +74 -7
  43. novelwriter/core/document.py +24 -3
  44. novelwriter/core/index.py +31 -112
  45. novelwriter/core/project.py +10 -15
  46. novelwriter/core/sessions.py +2 -2
  47. novelwriter/core/status.py +6 -5
  48. novelwriter/core/storage.py +8 -2
  49. novelwriter/core/tohtml.py +22 -25
  50. novelwriter/core/tokenizer.py +416 -232
  51. novelwriter/core/tomd.py +17 -8
  52. novelwriter/core/toodt.py +385 -350
  53. novelwriter/core/tree.py +8 -8
  54. novelwriter/dialogs/about.py +9 -11
  55. novelwriter/dialogs/docmerge.py +17 -14
  56. novelwriter/dialogs/docsplit.py +20 -19
  57. novelwriter/dialogs/editlabel.py +5 -4
  58. novelwriter/dialogs/preferences.py +31 -39
  59. novelwriter/dialogs/projectsettings.py +29 -26
  60. novelwriter/dialogs/quotes.py +10 -9
  61. novelwriter/dialogs/wordlist.py +15 -12
  62. novelwriter/enum.py +17 -14
  63. novelwriter/error.py +13 -11
  64. novelwriter/extensions/circularprogress.py +12 -8
  65. novelwriter/extensions/configlayout.py +1 -3
  66. novelwriter/extensions/modified.py +51 -2
  67. novelwriter/extensions/pagedsidebar.py +16 -14
  68. novelwriter/extensions/simpleprogress.py +3 -1
  69. novelwriter/extensions/statusled.py +3 -1
  70. novelwriter/extensions/switch.py +10 -9
  71. novelwriter/extensions/switchbox.py +14 -13
  72. novelwriter/extensions/versioninfo.py +1 -1
  73. novelwriter/gui/doceditor.py +413 -478
  74. novelwriter/gui/dochighlight.py +33 -29
  75. novelwriter/gui/docviewer.py +162 -175
  76. novelwriter/gui/docviewerpanel.py +20 -37
  77. novelwriter/gui/editordocument.py +15 -4
  78. novelwriter/gui/itemdetails.py +51 -54
  79. novelwriter/gui/mainmenu.py +37 -16
  80. novelwriter/gui/noveltree.py +30 -36
  81. novelwriter/gui/outline.py +114 -92
  82. novelwriter/gui/projtree.py +60 -66
  83. novelwriter/gui/search.py +362 -0
  84. novelwriter/gui/sidebar.py +36 -45
  85. novelwriter/gui/statusbar.py +14 -14
  86. novelwriter/gui/theme.py +93 -28
  87. novelwriter/guimain.py +207 -200
  88. novelwriter/shared.py +31 -6
  89. novelwriter/text/counting.py +137 -0
  90. novelwriter/tools/dictionaries.py +13 -12
  91. novelwriter/tools/lipsum.py +20 -17
  92. novelwriter/tools/manusbuild.py +35 -27
  93. novelwriter/tools/manuscript.py +374 -90
  94. novelwriter/tools/manussettings.py +261 -124
  95. novelwriter/tools/noveldetails.py +20 -18
  96. novelwriter/tools/welcome.py +48 -44
  97. novelwriter/tools/writingstats.py +61 -55
  98. novelwriter/types.py +90 -0
  99. novelwriter/core/__init__.py +0 -3
  100. novelwriter/dialogs/__init__.py +0 -3
  101. novelwriter/extensions/__init__.py +0 -3
  102. novelwriter/gui/__init__.py +0 -3
  103. novelwriter/tools/__init__.py +0 -3
  104. {novelWriter-2.3.1.dist-info → novelWriter-2.4rc1.dist-info}/LICENSE.md +0 -0
  105. {novelWriter-2.3.1.dist-info → novelWriter-2.4rc1.dist-info}/WHEEL +0 -0
  106. {novelWriter-2.3.1.dist-info → novelWriter-2.4rc1.dist-info}/entry_points.txt +0 -0
  107. {novelWriter-2.3.1.dist-info → novelWriter-2.4rc1.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
@@ -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
 
@@ -240,7 +257,7 @@ class GuiTheme:
240
257
  self._setPalette(parser, sec, "link", QPalette.ColorRole.Link)
241
258
  self._setPalette(parser, sec, "linkvisited", QPalette.ColorRole.LinkVisited)
242
259
  else:
243
- self._guiPalette = qApp.style().standardPalette()
260
+ self._guiPalette = QApplication.style().standardPalette()
244
261
 
245
262
  # GUI
246
263
  sec = "GUI"
@@ -269,7 +286,10 @@ class GuiTheme:
269
286
  self.iconCache.loadTheme(self.themeIcons or defaultIcons)
270
287
 
271
288
  # Apply Styles
272
- qApp.setPalette(self._guiPalette)
289
+ QApplication.setPalette(self._guiPalette)
290
+
291
+ # Reset stylesheets so that they are regenerated
292
+ self._buildStyleSheets(self._guiPalette)
273
293
 
274
294
  return True
275
295
 
@@ -329,6 +349,7 @@ class GuiTheme:
329
349
  self.colError = self._parseColour(confParser, cnfSec, "errorline")
330
350
  self.colRepTag = self._parseColour(confParser, cnfSec, "replacetag")
331
351
  self.colMod = self._parseColour(confParser, cnfSec, "modifier")
352
+ self.colMark = self._parseColour(confParser, cnfSec, "texthighlight")
332
353
 
333
354
  return True
334
355
 
@@ -364,6 +385,10 @@ class GuiTheme:
364
385
 
365
386
  return self._syntaxList
366
387
 
388
+ def getStyleSheet(self, name: str) -> str:
389
+ """Load a standard style sheet."""
390
+ return self._styleSheets.get(name, "")
391
+
367
392
  ##
368
393
  # Internal Functions
369
394
  ##
@@ -378,14 +403,14 @@ class GuiTheme:
378
403
  font.setFamily("Arial")
379
404
  font.setPointSize(10)
380
405
  else:
381
- font = fontDB.systemFont(QFontDatabase.GeneralFont)
406
+ font = fontDB.systemFont(QFontDatabase.SystemFont.GeneralFont)
382
407
  CONFIG.guiFont = font.family()
383
408
  CONFIG.guiFontSize = font.pointSize()
384
409
  else:
385
410
  font.setFamily(CONFIG.guiFont)
386
411
  font.setPointSize(CONFIG.guiFontSize)
387
412
 
388
- qApp.setFont(font)
413
+ QApplication.setFont(font)
389
414
 
390
415
  return
391
416
 
@@ -410,6 +435,41 @@ class GuiTheme:
410
435
  self._guiPalette.setColor(value, self._parseColour(parser, section, name))
411
436
  return
412
437
 
438
+ def _buildStyleSheets(self, palette: QPalette) -> None:
439
+ """Build default style sheets."""
440
+ self._styleSheets = {}
441
+
442
+ aPx = CONFIG.pxInt(2)
443
+ bPx = CONFIG.pxInt(4)
444
+ cPx = CONFIG.pxInt(6)
445
+ dPx = CONFIG.pxInt(8)
446
+
447
+ tCol = palette.text().color()
448
+ hCol = palette.highlight().color()
449
+
450
+ # Flat Tab Widget and Tab Bar:
451
+ self._styleSheets[STYLES_FLAT_TABS] = (
452
+ "QTabWidget::pane {border: 0;} "
453
+ f"QTabWidget QTabBar::tab {{border: 0; padding: {bPx}px {dPx}px;}} "
454
+ f"QTabWidget QTabBar::tab:selected {{color: {cssCol(hCol)};}} "
455
+ )
456
+
457
+ # Minimal Tool Button
458
+ self._styleSheets[STYLES_MIN_TOOLBUTTON] = (
459
+ f"QToolButton {{padding: {aPx}px; margin: 0; border: none; background: transparent;}} "
460
+ f"QToolButton:hover {{border: none; background: {cssCol(tCol, 48)};}} "
461
+ "QToolButton::menu-indicator {image: none;} "
462
+ )
463
+
464
+ # Big Tool Button
465
+ self._styleSheets[STYLES_BIG_TOOLBUTTON] = (
466
+ f"QToolButton {{padding: {cPx}px; margin: 0; border: none; background: transparent;}} "
467
+ f"QToolButton:hover {{border: none; background: {cssCol(tCol, 48)};}} "
468
+ "QToolButton::menu-indicator {image: none;} "
469
+ )
470
+
471
+ return
472
+
413
473
  # End Class GuiTheme
414
474
 
415
475
 
@@ -437,7 +497,7 @@ class GuiIcons:
437
497
  "build_excluded", "build_filtered", "build_included", "proj_chapter", "proj_details",
438
498
  "proj_document", "proj_folder", "proj_note", "proj_nwx", "proj_section", "proj_scene",
439
499
  "proj_stats", "proj_title", "status_idle", "status_lang", "status_lines", "status_stats",
440
- "status_time", "view_build", "view_editor", "view_novel", "view_outline",
500
+ "status_time", "view_build", "view_editor", "view_novel", "view_outline", "view_search",
441
501
 
442
502
  # Class Icons
443
503
  "cls_archive", "cls_character", "cls_custom", "cls_entity", "cls_none", "cls_novel",
@@ -448,8 +508,8 @@ class GuiIcons:
448
508
  "search_regex", "search_word",
449
509
 
450
510
  # 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",
511
+ "fmt_bold", "fmt_bold-md", "fmt_italic", "fmt_italic-md", "fmt_mark", "fmt_strike",
512
+ "fmt_strike-md", "fmt_subscript", "fmt_superscript", "fmt_underline",
453
513
 
454
514
  # General Button Icons
455
515
  "add", "add_document", "backward", "bookmark", "browse", "checked", "close", "cross",
@@ -460,6 +520,7 @@ class GuiIcons:
460
520
  # Switches
461
521
  "sticky-on", "sticky-off",
462
522
  "bullet-on", "bullet-off",
523
+ "unfold-show", "unfold-hide",
463
524
 
464
525
  # Decorations
465
526
  "deco_doc_h0", "deco_doc_h1", "deco_doc_h2", "deco_doc_h3", "deco_doc_h4", "deco_doc_more",
@@ -470,6 +531,7 @@ class GuiIcons:
470
531
  TOGGLE_ICON_KEYS: dict[str, tuple[str, str]] = {
471
532
  "sticky": ("sticky-on", "sticky-off"),
472
533
  "bullet": ("bullet-on", "bullet-off"),
534
+ "unfold": ("unfold-show", "unfold-hide"),
473
535
  }
474
536
 
475
537
  IMAGE_MAP: dict[str, tuple[str, str]] = {
@@ -491,6 +553,9 @@ class GuiIcons:
491
553
  self._confName = "icons.conf"
492
554
  self._iconPath = CONFIG.assetPath("icons")
493
555
 
556
+ # None Icon
557
+ self._noIcon = QIcon(str(self._iconPath / "none.svg"))
558
+
494
559
  # Icon Theme Meta
495
560
  self.themeName = ""
496
561
  self.themeDescription = ""
@@ -621,13 +686,13 @@ class GuiIcons:
621
686
  def getToggleIcon(self, name: str, size: tuple[int, int]) -> QIcon:
622
687
  """Return a toggle icon from the icon buffer. or load it."""
623
688
  if name in self.TOGGLE_ICON_KEYS:
624
- pOne = self.getPixmap(self.TOGGLE_ICON_KEYS[name][0], size)
689
+ pOne = self.getPixmap(self.TOGGLE_ICON_KEYS[name][0], size)
625
690
  pTwo = self.getPixmap(self.TOGGLE_ICON_KEYS[name][1], size)
626
691
  icon = QIcon()
627
692
  icon.addPixmap(pOne, QIcon.Mode.Normal, QIcon.State.On)
628
693
  icon.addPixmap(pTwo, QIcon.Mode.Normal, QIcon.State.Off)
629
694
  return icon
630
- return QIcon()
695
+ return self._noIcon
631
696
 
632
697
  def getPixmap(self, name: str, size: tuple[int, int]) -> QPixmap:
633
698
  """Return an icon from the icon buffer as a QPixmap. If it
@@ -638,7 +703,7 @@ class GuiIcons:
638
703
  def getItemIcon(self, tType: nwItemType, tClass: nwItemClass,
639
704
  tLayout: nwItemLayout, hLevel: str = "H0") -> QIcon:
640
705
  """Get the correct icon for a project item based on type, class
641
- and header level
706
+ and heading level
642
707
  """
643
708
  iconName = None
644
709
  if tType == nwItemType.ROOT:
@@ -659,14 +724,14 @@ class GuiIcons:
659
724
  elif tLayout == nwItemLayout.NOTE:
660
725
  iconName = "proj_note"
661
726
  if iconName is None:
662
- return QIcon()
727
+ return self._noIcon
663
728
 
664
729
  return self.getIcon(iconName)
665
730
 
666
731
  def getHeaderDecoration(self, hLevel: int) -> QPixmap:
667
- """Get the decoration for a specific header level."""
732
+ """Get the decoration for a specific heading level."""
668
733
  if not self._headerDec:
669
- iPx = self.mainTheme.baseIconSize
734
+ iPx = self.mainTheme.baseIconHeight
670
735
  self._headerDec = [
671
736
  self.loadDecoration("deco_doc_h0", h=iPx),
672
737
  self.loadDecoration("deco_doc_h1", h=iPx),
@@ -677,9 +742,9 @@ class GuiIcons:
677
742
  return self._headerDec[minmax(hLevel, 0, 4)]
678
743
 
679
744
  def getHeaderDecorationNarrow(self, hLevel: int) -> QPixmap:
680
- """Get the narrow decoration for a specific header level."""
745
+ """Get the narrow decoration for a specific heading level."""
681
746
  if not self._headerDecNarrow:
682
- iPx = self.mainTheme.baseIconSize
747
+ iPx = self.mainTheme.baseIconHeight
683
748
  self._headerDecNarrow = [
684
749
  self.loadDecoration("deco_doc_h0_n", h=iPx),
685
750
  self.loadDecoration("deco_doc_h1_n", h=iPx),
@@ -700,7 +765,7 @@ class GuiIcons:
700
765
  """
701
766
  if name not in self.ICON_KEYS:
702
767
  logger.error("Requested unknown icon name '%s'", name)
703
- return QIcon()
768
+ return self._noIcon
704
769
 
705
770
  # If we just want the app icons, return right away
706
771
  if name == "novelwriter":
@@ -716,7 +781,7 @@ class GuiIcons:
716
781
  # If we didn't find one, give up and return an empty icon
717
782
  logger.warning("Did not load an icon for '%s'", name)
718
783
 
719
- return QIcon()
784
+ return self._noIcon
720
785
 
721
786
  # END Class GuiIcons
722
787