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.
- {novelWriter-2.3.1.dist-info → novelWriter-2.4rc1.dist-info}/METADATA +5 -6
- {novelWriter-2.3.1.dist-info → novelWriter-2.4rc1.dist-info}/RECORD +102 -95
- novelwriter/__init__.py +7 -7
- novelwriter/assets/icons/none.svg +4 -0
- novelwriter/assets/icons/typicons_dark/icons.conf +4 -0
- novelwriter/assets/icons/typicons_dark/nw_tb-mark.svg +7 -0
- novelwriter/assets/icons/typicons_dark/typ_refresh-flipped.svg +1 -1
- novelwriter/assets/icons/typicons_dark/typ_refresh.svg +1 -1
- novelwriter/assets/icons/typicons_dark/typ_search-grey.svg +4 -0
- novelwriter/assets/icons/typicons_dark/typ_times.svg +1 -1
- novelwriter/assets/icons/typicons_dark/typ_unfold-hidden.svg +4 -0
- novelwriter/assets/icons/typicons_dark/typ_unfold-visible.svg +4 -0
- novelwriter/assets/icons/typicons_light/icons.conf +4 -0
- novelwriter/assets/icons/typicons_light/nw_tb-mark.svg +7 -0
- novelwriter/assets/icons/typicons_light/typ_refresh-flipped.svg +1 -1
- novelwriter/assets/icons/typicons_light/typ_refresh.svg +1 -1
- novelwriter/assets/icons/typicons_light/typ_search-grey.svg +4 -0
- novelwriter/assets/icons/typicons_light/typ_times.svg +1 -1
- novelwriter/assets/icons/typicons_light/typ_unfold-hidden.svg +4 -0
- novelwriter/assets/icons/typicons_light/typ_unfold-visible.svg +4 -0
- novelwriter/assets/manual.pdf +0 -0
- novelwriter/assets/sample.zip +0 -0
- novelwriter/assets/syntax/default_dark.conf +1 -0
- novelwriter/assets/syntax/default_light.conf +1 -0
- novelwriter/assets/syntax/grey_dark.conf +1 -0
- novelwriter/assets/syntax/grey_light.conf +1 -0
- novelwriter/assets/syntax/light_owl.conf +1 -0
- novelwriter/assets/syntax/night_owl.conf +1 -0
- novelwriter/assets/syntax/solarized_dark.conf +1 -0
- novelwriter/assets/syntax/solarized_light.conf +1 -0
- novelwriter/assets/syntax/tomorrow.conf +1 -0
- novelwriter/assets/syntax/tomorrow_night.conf +1 -0
- novelwriter/assets/syntax/tomorrow_night_blue.conf +1 -0
- novelwriter/assets/syntax/tomorrow_night_bright.conf +1 -0
- novelwriter/assets/syntax/tomorrow_night_eighties.conf +1 -0
- novelwriter/assets/text/credits_en.htm +25 -23
- novelwriter/common.py +7 -2
- novelwriter/config.py +43 -16
- novelwriter/constants.py +5 -6
- novelwriter/core/buildsettings.py +60 -40
- novelwriter/core/coretools.py +97 -13
- novelwriter/core/docbuild.py +74 -7
- novelwriter/core/document.py +24 -3
- novelwriter/core/index.py +31 -112
- novelwriter/core/project.py +10 -15
- novelwriter/core/sessions.py +2 -2
- novelwriter/core/status.py +6 -5
- novelwriter/core/storage.py +8 -2
- novelwriter/core/tohtml.py +22 -25
- novelwriter/core/tokenizer.py +416 -232
- novelwriter/core/tomd.py +17 -8
- novelwriter/core/toodt.py +385 -350
- novelwriter/core/tree.py +8 -8
- novelwriter/dialogs/about.py +9 -11
- novelwriter/dialogs/docmerge.py +17 -14
- novelwriter/dialogs/docsplit.py +20 -19
- novelwriter/dialogs/editlabel.py +5 -4
- novelwriter/dialogs/preferences.py +31 -39
- novelwriter/dialogs/projectsettings.py +29 -26
- novelwriter/dialogs/quotes.py +10 -9
- novelwriter/dialogs/wordlist.py +15 -12
- novelwriter/enum.py +17 -14
- novelwriter/error.py +13 -11
- novelwriter/extensions/circularprogress.py +12 -8
- novelwriter/extensions/configlayout.py +1 -3
- novelwriter/extensions/modified.py +51 -2
- novelwriter/extensions/pagedsidebar.py +16 -14
- novelwriter/extensions/simpleprogress.py +3 -1
- novelwriter/extensions/statusled.py +3 -1
- novelwriter/extensions/switch.py +10 -9
- novelwriter/extensions/switchbox.py +14 -13
- novelwriter/extensions/versioninfo.py +1 -1
- novelwriter/gui/doceditor.py +413 -478
- novelwriter/gui/dochighlight.py +33 -29
- novelwriter/gui/docviewer.py +162 -175
- novelwriter/gui/docviewerpanel.py +20 -37
- novelwriter/gui/editordocument.py +15 -4
- novelwriter/gui/itemdetails.py +51 -54
- novelwriter/gui/mainmenu.py +37 -16
- novelwriter/gui/noveltree.py +30 -36
- novelwriter/gui/outline.py +114 -92
- novelwriter/gui/projtree.py +60 -66
- novelwriter/gui/search.py +362 -0
- novelwriter/gui/sidebar.py +36 -45
- novelwriter/gui/statusbar.py +14 -14
- novelwriter/gui/theme.py +93 -28
- novelwriter/guimain.py +207 -200
- novelwriter/shared.py +31 -6
- novelwriter/text/counting.py +137 -0
- novelwriter/tools/dictionaries.py +13 -12
- novelwriter/tools/lipsum.py +20 -17
- novelwriter/tools/manusbuild.py +35 -27
- novelwriter/tools/manuscript.py +374 -90
- novelwriter/tools/manussettings.py +261 -124
- novelwriter/tools/noveldetails.py +20 -18
- novelwriter/tools/welcome.py +48 -44
- novelwriter/tools/writingstats.py +61 -55
- novelwriter/types.py +90 -0
- novelwriter/core/__init__.py +0 -3
- novelwriter/dialogs/__init__.py +0 -3
- novelwriter/extensions/__init__.py +0 -3
- novelwriter/gui/__init__.py +0 -3
- novelwriter/tools/__init__.py +0 -3
- {novelWriter-2.3.1.dist-info → novelWriter-2.4rc1.dist-info}/LICENSE.md +0 -0
- {novelWriter-2.3.1.dist-info → novelWriter-2.4rc1.dist-info}/WHEEL +0 -0
- {novelWriter-2.3.1.dist-info → novelWriter-2.4rc1.dist-info}/entry_points.txt +0 -0
- {novelWriter-2.3.1.dist-info → novelWriter-2.4rc1.dist-info}/top_level.txt +0 -0
novelwriter/gui/sidebar.py
CHANGED
@@ -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,
|
32
|
-
from PyQt5.QtWidgets import QMenu,
|
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
|
-
|
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 =
|
56
|
-
|
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 =
|
64
|
+
self.tbProject = NIconToolButton(self, iSz)
|
62
65
|
self.tbProject.setToolTip("{0} [Ctrl+T]".format(self.tr("Project Tree View")))
|
63
|
-
self.tbProject.
|
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 =
|
68
|
+
self.tbNovel = NIconToolButton(self, iSz)
|
67
69
|
self.tbNovel.setToolTip("{0} [Ctrl+T]".format(self.tr("Novel Tree View")))
|
68
|
-
self.tbNovel.
|
69
|
-
|
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 =
|
76
|
+
self.tbOutline = NIconToolButton(self, iSz)
|
72
77
|
self.tbOutline.setToolTip("{0} [Ctrl+Shift+T]".format(self.tr("Novel Outline View")))
|
73
|
-
self.tbOutline.
|
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 =
|
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 =
|
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 =
|
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 =
|
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
|
-
|
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.
|
158
|
-
self.
|
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))
|
novelwriter/gui/statusbar.py
CHANGED
@@ -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
|
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.
|
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.
|
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
|
-
|
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 =
|
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 =
|
142
|
-
self.guiScale =
|
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 =
|
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(
|
153
|
-
self.
|
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(
|
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
|
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 =
|
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
|
-
|
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
|
-
|
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", "
|
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
|
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
|
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
|
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
|
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
|
732
|
+
"""Get the decoration for a specific heading level."""
|
668
733
|
if not self._headerDec:
|
669
|
-
iPx = self.mainTheme.
|
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
|
745
|
+
"""Get the narrow decoration for a specific heading level."""
|
681
746
|
if not self._headerDecNarrow:
|
682
|
-
iPx = self.mainTheme.
|
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
|
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
|
784
|
+
return self._noIcon
|
720
785
|
|
721
786
|
# END Class GuiIcons
|
722
787
|
|