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.
- {novelWriter-2.3.1.dist-info → novelWriter-2.4.dist-info}/METADATA +5 -6
- {novelWriter-2.3.1.dist-info → novelWriter-2.4.dist-info}/RECORD +114 -107
- novelwriter/__init__.py +17 -10
- novelwriter/assets/i18n/nw_de_DE.qm +0 -0
- novelwriter/assets/i18n/nw_en_US.qm +0 -0
- novelwriter/assets/i18n/nw_es_419.qm +0 -0
- novelwriter/assets/i18n/nw_fr_FR.qm +0 -0
- novelwriter/assets/i18n/nw_it_IT.qm +0 -0
- novelwriter/assets/i18n/nw_ja_JP.qm +0 -0
- novelwriter/assets/i18n/nw_nb_NO.qm +0 -0
- novelwriter/assets/i18n/nw_nl_NL.qm +0 -0
- novelwriter/assets/i18n/nw_pt_BR.qm +0 -0
- novelwriter/assets/i18n/nw_zh_CN.qm +0 -0
- 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 +12 -4
- novelwriter/config.py +47 -16
- novelwriter/constants.py +5 -6
- novelwriter/core/buildsettings.py +64 -44
- 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/projectxml.py +1 -1
- novelwriter/core/sessions.py +2 -2
- novelwriter/core/spellcheck.py +3 -3
- novelwriter/core/status.py +6 -5
- novelwriter/core/storage.py +8 -2
- novelwriter/core/tohtml.py +22 -25
- novelwriter/core/tokenizer.py +417 -233
- novelwriter/core/tomd.py +17 -8
- novelwriter/core/toodt.py +386 -351
- 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 +32 -40
- novelwriter/dialogs/projectsettings.py +31 -28
- novelwriter/dialogs/quotes.py +10 -9
- novelwriter/dialogs/wordlist.py +17 -14
- novelwriter/enum.py +17 -14
- novelwriter/error.py +14 -12
- 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 +433 -496
- novelwriter/gui/dochighlight.py +54 -33
- 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 +31 -37
- novelwriter/gui/outline.py +120 -98
- novelwriter/gui/projtree.py +61 -67
- novelwriter/gui/search.py +362 -0
- novelwriter/gui/sidebar.py +36 -45
- novelwriter/gui/statusbar.py +14 -14
- novelwriter/gui/theme.py +107 -32
- novelwriter/guimain.py +209 -202
- novelwriter/shared.py +31 -6
- novelwriter/text/counting.py +138 -0
- novelwriter/tools/dictionaries.py +15 -14
- novelwriter/tools/lipsum.py +20 -17
- novelwriter/tools/manusbuild.py +43 -35
- novelwriter/tools/manuscript.py +381 -104
- novelwriter/tools/manussettings.py +262 -125
- novelwriter/tools/noveldetails.py +20 -18
- novelwriter/tools/welcome.py +52 -49
- 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.4.dist-info}/LICENSE.md +0 -0
- {novelWriter-2.3.1.dist-info → novelWriter-2.4.dist-info}/WHEEL +0 -0
- {novelWriter-2.3.1.dist-info → novelWriter-2.4.dist-info}/entry_points.txt +0 -0
- {novelWriter-2.3.1.dist-info → novelWriter-2.4.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
|
@@ -70,9 +74,9 @@ class GuiTheme:
|
|
70
74
|
self.isLightTheme = True
|
71
75
|
|
72
76
|
# GUI
|
73
|
-
self.statNone = QColor(
|
74
|
-
self.statUnsaved = QColor(
|
75
|
-
self.statSaved = QColor(
|
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 =
|
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
|
|
@@ -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
|
-
|
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
|
-
|
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", "
|
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
|
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
|
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
|
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
|
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
|
742
|
+
"""Get the decoration for a specific heading level."""
|
668
743
|
if not self._headerDec:
|
669
|
-
iPx = self.mainTheme.
|
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
|
755
|
+
"""Get the narrow decoration for a specific heading level."""
|
681
756
|
if not self._headerDecNarrow:
|
682
|
-
iPx = self.mainTheme.
|
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
|
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
|
794
|
+
return self._noIcon
|
720
795
|
|
721
796
|
# END Class GuiIcons
|
722
797
|
|