novelWriter 2.1.1__py3-none-any.whl → 2.2rc1__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.1.1.dist-info → novelWriter-2.2rc1.dist-info}/METADATA +3 -3
- {novelWriter-2.1.1.dist-info → novelWriter-2.2rc1.dist-info}/RECORD +105 -76
- novelwriter/__init__.py +6 -24
- novelwriter/assets/i18n/project_de_DE.json +10 -0
- novelwriter/assets/i18n/project_en_GB.json +11 -0
- novelwriter/assets/i18n/project_en_US.json +10 -0
- novelwriter/assets/i18n/project_ja_JP.json +11 -1
- novelwriter/assets/i18n/project_nb_NO.json +10 -0
- novelwriter/assets/i18n/project_nn_NO.json +10 -0
- novelwriter/assets/icons/novelwriter.ico +0 -0
- novelwriter/assets/icons/novelwriter.svg +8 -183
- novelwriter/assets/icons/typicons_dark/icons.conf +17 -2
- novelwriter/assets/icons/typicons_dark/nw_deco-h2-narrow.svg +4 -0
- novelwriter/assets/icons/typicons_dark/nw_deco-h3-narrow.svg +4 -0
- novelwriter/assets/icons/typicons_dark/nw_deco-h4-narrow.svg +4 -0
- novelwriter/assets/icons/typicons_dark/nw_deco-note.svg +4 -0
- novelwriter/assets/icons/typicons_dark/nw_panel.svg +4 -0
- novelwriter/assets/icons/typicons_dark/nw_tb-bold.svg +4 -0
- novelwriter/assets/icons/typicons_dark/nw_tb-italic.svg +4 -0
- novelwriter/assets/icons/typicons_dark/nw_tb-markdown.svg +8 -0
- novelwriter/assets/icons/typicons_dark/nw_tb-shortcode.svg +8 -0
- novelwriter/assets/icons/typicons_dark/nw_tb-strike.svg +4 -0
- novelwriter/assets/icons/typicons_dark/nw_tb-subscript.svg +5 -0
- novelwriter/assets/icons/typicons_dark/nw_tb-superscript.svg +5 -0
- novelwriter/assets/icons/typicons_dark/nw_tb-underline.svg +5 -0
- novelwriter/assets/icons/typicons_dark/typ_eye.svg +4 -0
- novelwriter/assets/icons/typicons_dark/typ_th-dot-menu.svg +4 -0
- novelwriter/assets/icons/typicons_light/icons.conf +17 -2
- novelwriter/assets/icons/typicons_light/nw_deco-h2-narrow.svg +4 -0
- novelwriter/assets/icons/typicons_light/nw_deco-h3-narrow.svg +4 -0
- novelwriter/assets/icons/typicons_light/nw_deco-h4-narrow.svg +4 -0
- novelwriter/assets/icons/typicons_light/nw_deco-note.svg +4 -0
- novelwriter/assets/icons/typicons_light/nw_panel.svg +4 -0
- novelwriter/assets/icons/typicons_light/nw_tb-bold.svg +4 -0
- novelwriter/assets/icons/typicons_light/nw_tb-italic.svg +4 -0
- novelwriter/assets/icons/typicons_light/nw_tb-markdown.svg +8 -0
- novelwriter/assets/icons/typicons_light/nw_tb-shortcode.svg +8 -0
- novelwriter/assets/icons/typicons_light/nw_tb-strike.svg +4 -0
- novelwriter/assets/icons/typicons_light/nw_tb-subscript.svg +5 -0
- novelwriter/assets/icons/typicons_light/nw_tb-superscript.svg +5 -0
- novelwriter/assets/icons/typicons_light/nw_tb-underline.svg +5 -0
- novelwriter/assets/icons/typicons_light/typ_eye.svg +4 -0
- novelwriter/assets/icons/typicons_light/typ_th-dot-menu.svg +4 -0
- novelwriter/assets/icons/x-novelwriter-project.ico +0 -0
- novelwriter/assets/icons/x-novelwriter-project.svg +7 -206
- 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 +7 -0
- novelwriter/assets/text/release_notes.htm +7 -37
- novelwriter/common.py +22 -1
- novelwriter/config.py +27 -42
- novelwriter/constants.py +45 -7
- novelwriter/core/buildsettings.py +40 -24
- novelwriter/core/coretools.py +8 -1
- novelwriter/core/docbuild.py +2 -6
- novelwriter/core/index.py +264 -175
- novelwriter/core/options.py +8 -3
- novelwriter/core/project.py +2 -2
- novelwriter/core/projectdata.py +3 -3
- novelwriter/core/tohtml.py +60 -59
- novelwriter/core/tokenizer.py +110 -70
- novelwriter/core/tomd.py +51 -38
- novelwriter/core/toodt.py +184 -147
- novelwriter/dialogs/preferences.py +75 -106
- novelwriter/dialogs/projsettings.py +101 -110
- novelwriter/dialogs/updates.py +25 -14
- novelwriter/enum.py +28 -3
- novelwriter/extensions/novelselector.py +1 -1
- novelwriter/gui/doceditor.py +1345 -1235
- novelwriter/gui/dochighlight.py +98 -62
- novelwriter/gui/docviewer.py +151 -340
- novelwriter/gui/docviewerpanel.py +457 -0
- novelwriter/gui/editordocument.py +126 -0
- novelwriter/gui/mainmenu.py +350 -300
- novelwriter/gui/noveltree.py +101 -125
- novelwriter/gui/outline.py +154 -171
- novelwriter/gui/projtree.py +480 -380
- novelwriter/gui/sidebar.py +106 -75
- novelwriter/gui/statusbar.py +1 -1
- novelwriter/gui/theme.py +114 -75
- novelwriter/guimain.py +353 -254
- novelwriter/shared.py +36 -3
- novelwriter/tools/dictionaries.py +268 -0
- novelwriter/tools/manusbuild.py +17 -6
- novelwriter/tools/manuscript.py +11 -3
- novelwriter/tools/manussettings.py +0 -14
- novelwriter/tools/projwizard.py +16 -2
- novelwriter/tools/writingstats.py +1 -1
- novelwriter/assets/icons/typicons_dark/typ_at.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_th-menu.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_at.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_th-menu.svg +0 -4
- {novelWriter-2.1.1.dist-info → novelWriter-2.2rc1.dist-info}/LICENSE.md +0 -0
- {novelWriter-2.1.1.dist-info → novelWriter-2.2rc1.dist-info}/WHEEL +0 -0
- {novelWriter-2.1.1.dist-info → novelWriter-2.2rc1.dist-info}/entry_points.txt +0 -0
- {novelWriter-2.1.1.dist-info → novelWriter-2.2rc1.dist-info}/top_level.txt +0 -0
novelwriter/gui/sidebar.py
CHANGED
@@ -25,119 +25,150 @@ from __future__ import annotations
|
|
25
25
|
|
26
26
|
import logging
|
27
27
|
|
28
|
-
from
|
29
|
-
|
30
|
-
|
31
|
-
|
28
|
+
from typing import TYPE_CHECKING
|
29
|
+
|
30
|
+
from PyQt5.QtCore import QEvent, QPoint, Qt, QSize, pyqtSignal
|
31
|
+
from PyQt5.QtGui import QPalette
|
32
|
+
from PyQt5.QtWidgets import QMenu, QToolButton, QVBoxLayout, QWidget
|
32
33
|
|
33
34
|
from novelwriter import CONFIG, SHARED
|
34
35
|
from novelwriter.enum import nwView
|
35
36
|
|
37
|
+
if TYPE_CHECKING: # pragma: no cover
|
38
|
+
from novelwriter.guimain import GuiMain
|
39
|
+
|
36
40
|
logger = logging.getLogger(__name__)
|
37
41
|
|
38
42
|
|
39
|
-
class GuiSideBar(
|
43
|
+
class GuiSideBar(QWidget):
|
40
44
|
|
41
45
|
viewChangeRequested = pyqtSignal(nwView)
|
42
46
|
|
43
|
-
def __init__(self, mainGui):
|
47
|
+
def __init__(self, mainGui: GuiMain) -> None:
|
44
48
|
super().__init__(parent=mainGui)
|
45
49
|
|
46
50
|
logger.debug("Create: GuiSideBar")
|
47
51
|
|
48
52
|
self.mainGui = mainGui
|
49
53
|
|
50
|
-
|
51
|
-
|
52
|
-
mPx = CONFIG.pxInt(60)
|
53
|
-
|
54
|
-
lblFont = SHARED.theme.guiFont
|
55
|
-
lblFont.setPointSizeF(0.65*SHARED.theme.fontPointSize)
|
56
|
-
|
57
|
-
self.setMovable(False)
|
58
|
-
self.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
|
59
|
-
self.setIconSize(QSize(iPx, iPx))
|
60
|
-
self.setMaximumWidth(mPx)
|
54
|
+
iPx = CONFIG.pxInt(24)
|
55
|
+
iconSize = QSize(iPx, iPx)
|
61
56
|
self.setContentsMargins(0, 0, 0, 0)
|
62
57
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
self.
|
68
|
-
|
69
|
-
self.
|
70
|
-
self.
|
71
|
-
|
72
|
-
self.
|
73
|
-
|
74
|
-
self.
|
75
|
-
self.
|
76
|
-
|
77
|
-
self.
|
78
|
-
|
79
|
-
self.
|
80
|
-
self.
|
81
|
-
|
82
|
-
self.
|
83
|
-
|
84
|
-
self.
|
85
|
-
self.
|
86
|
-
|
87
|
-
self.
|
88
|
-
|
89
|
-
self.
|
90
|
-
self.
|
91
|
-
|
92
|
-
self.
|
93
|
-
self.aStats.setFont(lblFont)
|
94
|
-
self.aStats.setToolTip(self.tr("Writing Statistics"))
|
95
|
-
self.aStats.triggered.connect(lambda: self.mainGui.showWritingStatsDialog())
|
58
|
+
# Buttons
|
59
|
+
self.tbProject = QToolButton(self)
|
60
|
+
self.tbProject.setToolTip("{0} [Ctrl+T]".format(self.tr("Project Tree View")))
|
61
|
+
self.tbProject.setIconSize(iconSize)
|
62
|
+
self.tbProject.clicked.connect(lambda: self.viewChangeRequested.emit(nwView.PROJECT))
|
63
|
+
|
64
|
+
self.tbNovel = QToolButton(self)
|
65
|
+
self.tbNovel.setToolTip("{0} [Ctrl+T]".format(self.tr("Novel Tree View")))
|
66
|
+
self.tbNovel.setIconSize(iconSize)
|
67
|
+
self.tbNovel.clicked.connect(lambda: self.viewChangeRequested.emit(nwView.NOVEL))
|
68
|
+
|
69
|
+
self.tbOutline = QToolButton(self)
|
70
|
+
self.tbOutline.setToolTip("{0} [Ctrl+Shift+T]".format(self.tr("Novel Outline View")))
|
71
|
+
self.tbOutline.setIconSize(iconSize)
|
72
|
+
self.tbOutline.clicked.connect(lambda: self.viewChangeRequested.emit(nwView.OUTLINE))
|
73
|
+
|
74
|
+
self.tbBuild = QToolButton(self)
|
75
|
+
self.tbBuild.setToolTip("{0} [F5]".format(self.tr("Build Manuscript")))
|
76
|
+
self.tbBuild.setIconSize(iconSize)
|
77
|
+
self.tbBuild.clicked.connect(self.mainGui.showBuildManuscriptDialog)
|
78
|
+
|
79
|
+
self.tbDetails = QToolButton(self)
|
80
|
+
self.tbDetails.setToolTip("{0} [Shift+F6]".format(self.tr("Project Details")))
|
81
|
+
self.tbDetails.setIconSize(iconSize)
|
82
|
+
self.tbDetails.clicked.connect(self.mainGui.showProjectDetailsDialog)
|
83
|
+
|
84
|
+
self.tbStats = QToolButton(self)
|
85
|
+
self.tbStats.setToolTip("{0} [F6]".format(self.tr("Writing Statistics")))
|
86
|
+
self.tbStats.setIconSize(iconSize)
|
87
|
+
self.tbStats.clicked.connect(self.mainGui.showWritingStatsDialog)
|
96
88
|
|
97
89
|
# Settings Menu
|
98
|
-
self.
|
90
|
+
self.tbSettings = QToolButton(self)
|
91
|
+
self.tbSettings.setToolTip(self.tr("Settings"))
|
92
|
+
self.tbSettings.setIconSize(iconSize)
|
93
|
+
self.tbSettings.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
|
99
94
|
|
95
|
+
self.mSettings = _PopRightMenu(self.tbSettings)
|
100
96
|
self.mSettings.addAction(self.mainGui.mainMenu.aEditWordList)
|
101
97
|
self.mSettings.addAction(self.mainGui.mainMenu.aProjectSettings)
|
102
98
|
self.mSettings.addSeparator()
|
103
99
|
self.mSettings.addAction(self.mainGui.mainMenu.aPreferences)
|
104
100
|
|
105
|
-
self.tbSettings = QToolButton(self)
|
106
|
-
self.tbSettings.setFont(lblFont)
|
107
|
-
self.tbSettings.setText(self.tr("Settings"))
|
108
101
|
self.tbSettings.setMenu(self.mSettings)
|
109
|
-
self.tbSettings.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
|
110
102
|
self.tbSettings.setPopupMode(QToolButton.InstantPopup)
|
111
103
|
|
112
104
|
# Assemble
|
113
|
-
self.
|
114
|
-
self.
|
115
|
-
self.
|
116
|
-
self.
|
117
|
-
self.addWidget(
|
118
|
-
self.
|
119
|
-
self.
|
120
|
-
self.addWidget(self.
|
121
|
-
|
105
|
+
self.outerBox = QVBoxLayout()
|
106
|
+
self.outerBox.addWidget(self.tbProject)
|
107
|
+
self.outerBox.addWidget(self.tbNovel)
|
108
|
+
self.outerBox.addWidget(self.tbOutline)
|
109
|
+
self.outerBox.addWidget(self.tbBuild)
|
110
|
+
self.outerBox.addStretch(1)
|
111
|
+
self.outerBox.addWidget(self.tbDetails)
|
112
|
+
self.outerBox.addWidget(self.tbStats)
|
113
|
+
self.outerBox.addWidget(self.tbSettings)
|
114
|
+
self.outerBox.setContentsMargins(0, 0, 0, 0)
|
115
|
+
self.outerBox.setSpacing(CONFIG.pxInt(4))
|
116
|
+
|
117
|
+
self.setLayout(self.outerBox)
|
122
118
|
self.updateTheme()
|
123
119
|
|
124
120
|
logger.debug("Ready: GuiSideBar")
|
125
121
|
|
126
122
|
return
|
127
123
|
|
128
|
-
def updateTheme(self):
|
129
|
-
"""Initialise GUI elements that depend on specific settings.
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
124
|
+
def updateTheme(self) -> None:
|
125
|
+
"""Initialise GUI elements that depend on specific settings."""
|
126
|
+
qPalette = self.palette()
|
127
|
+
qPalette.setBrush(QPalette.Window, qPalette.base())
|
128
|
+
self.setPalette(qPalette)
|
129
|
+
|
130
|
+
fadeCol = qPalette.text().color()
|
131
|
+
buttonStyle = (
|
132
|
+
"QToolButton {{padding: {0}px; border: none; background: transparent;}} "
|
133
|
+
"QToolButton:hover {{border: none; background: rgba({1},{2},{3},0.2);}}"
|
134
|
+
).format(CONFIG.pxInt(6), fadeCol.red(), fadeCol.green(), fadeCol.blue())
|
135
|
+
buttonStyleMenu = f"{buttonStyle} QToolButton::menu-indicator {{image: none;}}"
|
136
|
+
|
137
|
+
self.tbProject.setIcon(SHARED.theme.getIcon("view_editor"))
|
138
|
+
self.tbProject.setStyleSheet(buttonStyle)
|
139
|
+
|
140
|
+
self.tbNovel.setIcon(SHARED.theme.getIcon("view_novel"))
|
141
|
+
self.tbNovel.setStyleSheet(buttonStyle)
|
142
|
+
|
143
|
+
self.tbOutline.setIcon(SHARED.theme.getIcon("view_outline"))
|
144
|
+
self.tbOutline.setStyleSheet(buttonStyle)
|
145
|
+
|
146
|
+
self.tbBuild.setIcon(SHARED.theme.getIcon("view_build"))
|
147
|
+
self.tbBuild.setStyleSheet(buttonStyle)
|
148
|
+
|
149
|
+
self.tbDetails.setIcon(SHARED.theme.getIcon("proj_details"))
|
150
|
+
self.tbDetails.setStyleSheet(buttonStyle)
|
151
|
+
|
152
|
+
self.tbStats.setIcon(SHARED.theme.getIcon("proj_stats"))
|
153
|
+
self.tbStats.setStyleSheet(buttonStyle)
|
154
|
+
|
139
155
|
self.tbSettings.setIcon(SHARED.theme.getIcon("settings"))
|
156
|
+
self.tbSettings.setStyleSheet(buttonStyleMenu)
|
140
157
|
|
141
158
|
return
|
142
159
|
|
143
160
|
# END Class GuiSideBar
|
161
|
+
|
162
|
+
|
163
|
+
class _PopRightMenu(QMenu):
|
164
|
+
|
165
|
+
def event(self, event: QEvent):
|
166
|
+
"""Overload the show event and move the menu popup location."""
|
167
|
+
if event.type() == QEvent.Show:
|
168
|
+
parent = self.parent()
|
169
|
+
if isinstance(parent, QWidget):
|
170
|
+
offset = QPoint(parent.width(), parent.height() - self.height())
|
171
|
+
self.move(parent.mapToGlobal(offset))
|
172
|
+
return super(_PopRightMenu, self).event(event)
|
173
|
+
|
174
|
+
# END Class _PopRightMenu
|
novelwriter/gui/statusbar.py
CHANGED
@@ -97,7 +97,7 @@ class GuiMainStatus(QStatusBar):
|
|
97
97
|
self.addPermanentWidget(self.statsText)
|
98
98
|
|
99
99
|
# The Session Clock
|
100
|
-
# Set the
|
100
|
+
# Set the minimum width so the label doesn't rescale every second
|
101
101
|
self.timeIcon = QLabel()
|
102
102
|
self.timeText = QLabel("")
|
103
103
|
self.timeText.setToolTip(self.tr("Session Time"))
|
novelwriter/gui/theme.py
CHANGED
@@ -36,7 +36,7 @@ from PyQt5.QtGui import (
|
|
36
36
|
)
|
37
37
|
|
38
38
|
from novelwriter import CONFIG
|
39
|
-
from novelwriter.enum import nwItemLayout, nwItemType
|
39
|
+
from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType
|
40
40
|
from novelwriter.error import logException
|
41
41
|
from novelwriter.common import NWConfigParser, minmax
|
42
42
|
from novelwriter.constants import nwLabels
|
@@ -51,7 +51,7 @@ logger = logging.getLogger(__name__)
|
|
51
51
|
|
52
52
|
class GuiTheme:
|
53
53
|
|
54
|
-
def __init__(self):
|
54
|
+
def __init__(self) -> None:
|
55
55
|
|
56
56
|
self.iconCache = GuiIcons(self)
|
57
57
|
|
@@ -97,6 +97,7 @@ class GuiTheme:
|
|
97
97
|
self.colDialD = [0, 0, 0]
|
98
98
|
self.colDialS = [0, 0, 0]
|
99
99
|
self.colHidden = [0, 0, 0]
|
100
|
+
self.colCode = [0, 0, 0]
|
100
101
|
self.colKey = [0, 0, 0]
|
101
102
|
self.colVal = [0, 0, 0]
|
102
103
|
self.colSpell = [0, 0, 0]
|
@@ -129,8 +130,10 @@ class GuiTheme:
|
|
129
130
|
self.getIcon = self.iconCache.getIcon
|
130
131
|
self.getPixmap = self.iconCache.getPixmap
|
131
132
|
self.getItemIcon = self.iconCache.getItemIcon
|
133
|
+
self.getToggleIcon = self.iconCache.getToggleIcon
|
132
134
|
self.loadDecoration = self.iconCache.loadDecoration
|
133
135
|
self.getHeaderDecoration = self.iconCache.getHeaderDecoration
|
136
|
+
self.getHeaderDecorationNarrow = self.iconCache.getHeaderDecorationNarrow
|
134
137
|
|
135
138
|
# Extract Other Info
|
136
139
|
self.guiDPI = qApp.primaryScreen().logicalDotsPerInchX()
|
@@ -181,7 +184,7 @@ class GuiTheme:
|
|
181
184
|
# Theme Methods
|
182
185
|
##
|
183
186
|
|
184
|
-
def loadTheme(self):
|
187
|
+
def loadTheme(self) -> bool:
|
185
188
|
"""Load the currently specified GUI theme."""
|
186
189
|
guiTheme = CONFIG.guiTheme
|
187
190
|
if guiTheme not in self._availThemes:
|
@@ -268,7 +271,7 @@ class GuiTheme:
|
|
268
271
|
|
269
272
|
return True
|
270
273
|
|
271
|
-
def loadSyntax(self):
|
274
|
+
def loadSyntax(self) -> bool:
|
272
275
|
"""Load the currently specified syntax highlighter theme."""
|
273
276
|
guiSyntax = CONFIG.guiSyntax
|
274
277
|
if guiSyntax not in self._availSyntax:
|
@@ -316,6 +319,7 @@ class GuiTheme:
|
|
316
319
|
self.colDialD = self._parseColour(confParser, cnfSec, "doublequotes")
|
317
320
|
self.colDialS = self._parseColour(confParser, cnfSec, "singlequotes")
|
318
321
|
self.colHidden = self._parseColour(confParser, cnfSec, "hidden")
|
322
|
+
self.colCode = self._parseColour(confParser, cnfSec, "shortcode")
|
319
323
|
self.colKey = self._parseColour(confParser, cnfSec, "keyword")
|
320
324
|
self.colVal = self._parseColour(confParser, cnfSec, "value")
|
321
325
|
self.colSpell = self._parseColour(confParser, cnfSec, "spellcheckline")
|
@@ -361,7 +365,7 @@ class GuiTheme:
|
|
361
365
|
# Internal Functions
|
362
366
|
##
|
363
367
|
|
364
|
-
def _setGuiFont(self):
|
368
|
+
def _setGuiFont(self) -> None:
|
365
369
|
"""Update the GUI's font style from settings."""
|
366
370
|
theFont = QFont()
|
367
371
|
fontDB = QFontDatabase()
|
@@ -393,9 +397,7 @@ class GuiTheme:
|
|
393
397
|
|
394
398
|
return True
|
395
399
|
|
396
|
-
def _parseColour(
|
397
|
-
self, parser: NWConfigParser, section: str, name: str
|
398
|
-
) -> list[int]:
|
400
|
+
def _parseColour(self, parser: NWConfigParser, section: str, name: str) -> list[int]:
|
399
401
|
"""Parse a colour value from a config string."""
|
400
402
|
if parser.has_option(section, name):
|
401
403
|
values = parser.get(section, name).split(",")
|
@@ -412,9 +414,8 @@ class GuiTheme:
|
|
412
414
|
result = [0, 0, 0]
|
413
415
|
return result
|
414
416
|
|
415
|
-
def _setPalette(
|
416
|
-
|
417
|
-
):
|
417
|
+
def _setPalette(self, parser: NWConfigParser, section: str,
|
418
|
+
name: str, value: QPalette.ColorRole) -> None:
|
418
419
|
"""Set a palette colour value from a config string."""
|
419
420
|
self._guiPalette.setColor(
|
420
421
|
value, QColor(*self._parseColour(parser, section, name))
|
@@ -445,19 +446,27 @@ class GuiIcons:
|
|
445
446
|
ICON_KEYS = {
|
446
447
|
# Project and GUI Icons
|
447
448
|
"novelwriter", "alert_error", "alert_info", "alert_question", "alert_warn",
|
448
|
-
"build_excluded", "build_filtered", "build_included", "
|
449
|
-
"
|
450
|
-
"
|
451
|
-
"
|
452
|
-
|
453
|
-
|
454
|
-
"
|
455
|
-
"
|
449
|
+
"build_excluded", "build_filtered", "build_included", "proj_chapter", "proj_details",
|
450
|
+
"proj_document", "proj_folder", "proj_note", "proj_nwx", "proj_section", "proj_scene",
|
451
|
+
"proj_stats", "proj_title", "status_idle", "status_lang", "status_lines", "status_stats",
|
452
|
+
"status_time", "view_build", "view_editor", "view_novel", "view_outline",
|
453
|
+
|
454
|
+
# Class Icons
|
455
|
+
"cls_archive", "cls_character", "cls_custom", "cls_entity", "cls_none", "cls_novel",
|
456
|
+
"cls_object", "cls_plot", "cls_timeline", "cls_trash", "cls_world",
|
457
|
+
|
458
|
+
# Search Icons
|
459
|
+
"search_cancel", "search_case", "search_loop", "search_preserve", "search_project",
|
460
|
+
"search_regex", "search_word",
|
461
|
+
|
462
|
+
# Format Icons
|
463
|
+
"fmt_bold", "fmt_italic", "fmt_mode-md", "fmt_mode-sc", "fmt_strike", "fmt_subscript",
|
464
|
+
"fmt_superscript", "fmt_underline",
|
456
465
|
|
457
466
|
# General Button Icons
|
458
467
|
"add", "backward", "bookmark", "browse", "checked", "close", "cross", "down", "edit",
|
459
|
-
"export", "forward", "maximise", "menu", "minimise", "noncheckable", "
|
460
|
-
"
|
468
|
+
"export", "forward", "maximise", "menu", "minimise", "noncheckable", "panel", "refresh",
|
469
|
+
"remove", "revert", "search_replace", "search", "settings", "unchecked", "up", "view",
|
461
470
|
|
462
471
|
# Switches
|
463
472
|
"sticky-on", "sticky-off",
|
@@ -465,23 +474,32 @@ class GuiIcons:
|
|
465
474
|
|
466
475
|
# Decorations
|
467
476
|
"deco_doc_h0", "deco_doc_h1", "deco_doc_h2", "deco_doc_h3", "deco_doc_h4", "deco_doc_more",
|
477
|
+
"deco_doc_h0_n", "deco_doc_h1_n", "deco_doc_h2_n", "deco_doc_h3_n", "deco_doc_h4_n",
|
478
|
+
"deco_doc_nt_n",
|
479
|
+
}
|
480
|
+
|
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"),
|
468
485
|
}
|
469
486
|
|
470
487
|
IMAGE_MAP = {
|
471
488
|
"wiz-back": "wizard-back.jpg",
|
472
489
|
}
|
473
490
|
|
474
|
-
def __init__(self, mainTheme):
|
491
|
+
def __init__(self, mainTheme: GuiTheme) -> None:
|
475
492
|
|
476
493
|
self.mainTheme = mainTheme
|
477
494
|
|
478
495
|
# Storage
|
479
|
-
self._qIcons
|
480
|
-
self._themeMap
|
481
|
-
self._headerDec = []
|
482
|
-
self.
|
496
|
+
self._qIcons: dict[str, QIcon] = {}
|
497
|
+
self._themeMap: dict[str, Path] = {}
|
498
|
+
self._headerDec: list[QPixmap] = []
|
499
|
+
self._headerDecNarrow: list[QPixmap] = []
|
483
500
|
|
484
501
|
# Icon Theme Path
|
502
|
+
self._confName = "icons.conf"
|
485
503
|
self._iconPath = CONFIG.assetPath("icons")
|
486
504
|
|
487
505
|
# Icon Theme Meta
|
@@ -499,7 +517,7 @@ class GuiIcons:
|
|
499
517
|
# Actions
|
500
518
|
##
|
501
519
|
|
502
|
-
def loadTheme(self, iconTheme):
|
520
|
+
def loadTheme(self, iconTheme: str) -> bool:
|
503
521
|
"""Update the theme map. This is more of an init, since many of
|
504
522
|
the GUI icons cannot really be replaced without writing specific
|
505
523
|
update functions for the classes where they're used.
|
@@ -566,6 +584,7 @@ class GuiIcons:
|
|
566
584
|
self._qIcons[iconKey] = qIcon
|
567
585
|
|
568
586
|
self._headerDec = []
|
587
|
+
self._headerDecNarrow = []
|
569
588
|
|
570
589
|
return True
|
571
590
|
|
@@ -573,52 +592,60 @@ class GuiIcons:
|
|
573
592
|
# Access Functions
|
574
593
|
##
|
575
594
|
|
576
|
-
def loadDecoration(self,
|
595
|
+
def loadDecoration(self, name: str, w: int | None = None, h: int | None = None) -> QPixmap:
|
577
596
|
"""Load graphical decoration element based on the decoration
|
578
597
|
map or the icon map. This function always returns a QPixmap.
|
579
598
|
"""
|
580
|
-
if
|
581
|
-
imgPath = self._themeMap[
|
582
|
-
elif
|
583
|
-
imgPath = CONFIG.assetPath("images") / self.IMAGE_MAP[
|
599
|
+
if name in self._themeMap:
|
600
|
+
imgPath = self._themeMap[name]
|
601
|
+
elif name in self.IMAGE_MAP:
|
602
|
+
imgPath = CONFIG.assetPath("images") / self.IMAGE_MAP[name]
|
584
603
|
else:
|
585
|
-
logger.error("Decoration with name '%s' does not exist",
|
604
|
+
logger.error("Decoration with name '%s' does not exist", name)
|
586
605
|
return QPixmap()
|
587
606
|
|
588
607
|
if not imgPath.is_file():
|
589
608
|
logger.error("Asset not found: %s", imgPath)
|
590
609
|
return QPixmap()
|
591
610
|
|
592
|
-
|
593
|
-
if
|
594
|
-
return
|
595
|
-
elif
|
596
|
-
return
|
597
|
-
elif
|
598
|
-
return
|
611
|
+
pixmap = QPixmap(str(imgPath))
|
612
|
+
if w is not None and h is not None:
|
613
|
+
return pixmap.scaled(w, h, Qt.IgnoreAspectRatio, Qt.SmoothTransformation)
|
614
|
+
elif w is None and h is not None:
|
615
|
+
return pixmap.scaledToHeight(h, Qt.SmoothTransformation)
|
616
|
+
elif w is not None and h is None:
|
617
|
+
return pixmap.scaledToWidth(w, Qt.SmoothTransformation)
|
599
618
|
|
600
|
-
return
|
619
|
+
return pixmap
|
601
620
|
|
602
|
-
def getIcon(self,
|
603
|
-
"""Return an icon from the icon buffer
|
604
|
-
|
605
|
-
|
606
|
-
"""
|
607
|
-
if iconKey in self._qIcons:
|
608
|
-
return self._qIcons[iconKey]
|
621
|
+
def getIcon(self, name: str) -> QIcon:
|
622
|
+
"""Return an icon from the icon buffer, or load it."""
|
623
|
+
if name in self._qIcons:
|
624
|
+
return self._qIcons[name]
|
609
625
|
else:
|
610
|
-
|
611
|
-
self._qIcons[
|
612
|
-
return
|
626
|
+
icon = self._loadIcon(name)
|
627
|
+
self._qIcons[name] = icon
|
628
|
+
return icon
|
629
|
+
|
630
|
+
def getToggleIcon(self, name: str, size: tuple[int, int]) -> QIcon:
|
631
|
+
"""Return a toggle icon from the icon buffer. or load it."""
|
632
|
+
if name in self.TOGGLE_ICON_KEYS:
|
633
|
+
pOne = self.getPixmap(self.TOGGLE_ICON_KEYS[name][0], size)
|
634
|
+
pTwo = self.getPixmap(self.TOGGLE_ICON_KEYS[name][1], size)
|
635
|
+
icon = QIcon()
|
636
|
+
icon.addPixmap(pOne, QIcon.Normal, QIcon.On)
|
637
|
+
icon.addPixmap(pTwo, QIcon.Normal, QIcon.Off)
|
638
|
+
return icon
|
639
|
+
return QIcon()
|
613
640
|
|
614
|
-
def getPixmap(self,
|
641
|
+
def getPixmap(self, name: str, size: tuple[int, int]) -> QPixmap:
|
615
642
|
"""Return an icon from the icon buffer as a QPixmap. If it
|
616
643
|
doesn't exist, return an empty QPixmap.
|
617
644
|
"""
|
618
|
-
|
619
|
-
return qIcon.pixmap(iconSize[0], iconSize[1], QIcon.Normal)
|
645
|
+
return self.getIcon(name).pixmap(size[0], size[1], QIcon.Normal)
|
620
646
|
|
621
|
-
def getItemIcon(self, tType, tClass
|
647
|
+
def getItemIcon(self, tType: nwItemType, tClass: nwItemClass,
|
648
|
+
tLayout: nwItemLayout, hLevel: str = "H0") -> QIcon:
|
622
649
|
"""Get the correct icon for a project item based on type, class
|
623
650
|
and header level
|
624
651
|
"""
|
@@ -645,45 +672,58 @@ class GuiIcons:
|
|
645
672
|
|
646
673
|
return self.getIcon(iconName)
|
647
674
|
|
648
|
-
def getHeaderDecoration(self, hLevel):
|
649
|
-
"""Get the decoration for a specific header level.
|
650
|
-
"""
|
675
|
+
def getHeaderDecoration(self, hLevel: int) -> QPixmap:
|
676
|
+
"""Get the decoration for a specific header level."""
|
651
677
|
if not self._headerDec:
|
652
678
|
iPx = self.mainTheme.baseIconSize
|
653
679
|
self._headerDec = [
|
654
|
-
self.loadDecoration("deco_doc_h0",
|
655
|
-
self.loadDecoration("deco_doc_h1",
|
656
|
-
self.loadDecoration("deco_doc_h2",
|
657
|
-
self.loadDecoration("deco_doc_h3",
|
658
|
-
self.loadDecoration("deco_doc_h4",
|
680
|
+
self.loadDecoration("deco_doc_h0", h=iPx),
|
681
|
+
self.loadDecoration("deco_doc_h1", h=iPx),
|
682
|
+
self.loadDecoration("deco_doc_h2", h=iPx),
|
683
|
+
self.loadDecoration("deco_doc_h3", h=iPx),
|
684
|
+
self.loadDecoration("deco_doc_h4", h=iPx),
|
659
685
|
]
|
660
686
|
return self._headerDec[minmax(hLevel, 0, 4)]
|
661
687
|
|
688
|
+
def getHeaderDecorationNarrow(self, hLevel: int) -> QPixmap:
|
689
|
+
"""Get the narrow decoration for a specific header level."""
|
690
|
+
if not self._headerDecNarrow:
|
691
|
+
iPx = self.mainTheme.baseIconSize
|
692
|
+
self._headerDecNarrow = [
|
693
|
+
self.loadDecoration("deco_doc_h0_n", h=iPx),
|
694
|
+
self.loadDecoration("deco_doc_h1_n", h=iPx),
|
695
|
+
self.loadDecoration("deco_doc_h2_n", h=iPx),
|
696
|
+
self.loadDecoration("deco_doc_h3_n", h=iPx),
|
697
|
+
self.loadDecoration("deco_doc_h4_n", h=iPx),
|
698
|
+
self.loadDecoration("deco_doc_nt_n", h=iPx),
|
699
|
+
]
|
700
|
+
return self._headerDecNarrow[minmax(hLevel, 0, 5)]
|
701
|
+
|
662
702
|
##
|
663
703
|
# Internal Functions
|
664
704
|
##
|
665
705
|
|
666
|
-
def _loadIcon(self,
|
706
|
+
def _loadIcon(self, name: str) -> QIcon:
|
667
707
|
"""Load an icon from the assets themes folder. Is guaranteed to
|
668
708
|
return a QIcon.
|
669
709
|
"""
|
670
|
-
if
|
671
|
-
logger.error("Requested unknown icon name '%s'",
|
710
|
+
if name not in self.ICON_KEYS:
|
711
|
+
logger.error("Requested unknown icon name '%s'", name)
|
672
712
|
return QIcon()
|
673
713
|
|
674
714
|
# If we just want the app icons, return right away
|
675
|
-
if
|
715
|
+
if name == "novelwriter":
|
676
716
|
return QIcon(str(self._iconPath / "novelwriter.svg"))
|
677
|
-
elif
|
717
|
+
elif name == "proj_nwx":
|
678
718
|
return QIcon(str(self._iconPath / "x-novelwriter-project.svg"))
|
679
719
|
|
680
720
|
# Otherwise, we load from the theme folder
|
681
|
-
if
|
682
|
-
logger.debug("Loading: %s", self._themeMap[
|
683
|
-
return QIcon(str(self._themeMap[
|
721
|
+
if name in self._themeMap:
|
722
|
+
logger.debug("Loading: %s", self._themeMap[name].name)
|
723
|
+
return QIcon(str(self._themeMap[name]))
|
684
724
|
|
685
725
|
# If we didn't find one, give up and return an empty icon
|
686
|
-
logger.warning("Did not load an icon for '%s'",
|
726
|
+
logger.warning("Did not load an icon for '%s'", name)
|
687
727
|
|
688
728
|
return QIcon()
|
689
729
|
|
@@ -694,9 +734,8 @@ class GuiIcons:
|
|
694
734
|
# Module Functions
|
695
735
|
# =============================================================================================== #
|
696
736
|
|
697
|
-
def _loadInternalName(confParser, confFile):
|
698
|
-
"""Open a conf file and read the 'name' setting.
|
699
|
-
"""
|
737
|
+
def _loadInternalName(confParser: NWConfigParser, confFile: str | Path) -> str:
|
738
|
+
"""Open a conf file and read the 'name' setting."""
|
700
739
|
try:
|
701
740
|
with open(confFile, mode="r", encoding="utf-8") as inFile:
|
702
741
|
confParser.read_file(inFile)
|