novelWriter 2.4b1__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.4b1.dist-info → novelWriter-2.4rc1.dist-info}/METADATA +5 -6
- {novelWriter-2.4b1.dist-info → novelWriter-2.4rc1.dist-info}/RECORD +62 -66
- novelwriter/__init__.py +5 -5
- novelwriter/assets/icons/none.svg +4 -0
- novelwriter/assets/icons/typicons_dark/icons.conf +2 -2
- 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 +2 -2
- 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/common.py +6 -1
- novelwriter/config.py +8 -4
- novelwriter/core/coretools.py +21 -22
- novelwriter/core/status.py +3 -2
- novelwriter/core/toodt.py +332 -355
- novelwriter/dialogs/about.py +9 -11
- novelwriter/dialogs/docmerge.py +17 -14
- novelwriter/dialogs/docsplit.py +14 -12
- novelwriter/dialogs/editlabel.py +5 -4
- novelwriter/dialogs/preferences.py +28 -33
- novelwriter/dialogs/projectsettings.py +29 -26
- novelwriter/dialogs/quotes.py +10 -9
- novelwriter/dialogs/wordlist.py +15 -12
- novelwriter/error.py +13 -11
- novelwriter/extensions/circularprogress.py +12 -8
- novelwriter/extensions/configlayout.py +1 -3
- novelwriter/extensions/modified.py +33 -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/gui/doceditor.py +182 -225
- novelwriter/gui/dochighlight.py +4 -4
- novelwriter/gui/docviewer.py +53 -57
- novelwriter/gui/docviewerpanel.py +16 -13
- novelwriter/gui/editordocument.py +4 -4
- novelwriter/gui/itemdetails.py +45 -48
- novelwriter/gui/noveltree.py +22 -20
- novelwriter/gui/outline.py +87 -88
- novelwriter/gui/projtree.py +31 -29
- novelwriter/gui/search.py +75 -29
- novelwriter/gui/sidebar.py +24 -28
- novelwriter/gui/statusbar.py +14 -14
- novelwriter/gui/theme.py +47 -35
- novelwriter/guimain.py +35 -31
- novelwriter/shared.py +5 -5
- novelwriter/tools/dictionaries.py +13 -12
- novelwriter/tools/lipsum.py +20 -17
- novelwriter/tools/manusbuild.py +35 -27
- novelwriter/tools/manuscript.py +68 -73
- novelwriter/tools/manussettings.py +68 -73
- novelwriter/tools/noveldetails.py +20 -18
- novelwriter/tools/welcome.py +47 -43
- novelwriter/tools/writingstats.py +61 -55
- novelwriter/types.py +90 -0
- novelwriter/assets/icons/typicons_dark/typ_arrow-down.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_arrow-right.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_arrow-down.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_arrow-right.svg +0 -4
- 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/text/__init__.py +0 -3
- novelwriter/tools/__init__.py +0 -3
- {novelWriter-2.4b1.dist-info → novelWriter-2.4rc1.dist-info}/LICENSE.md +0 -0
- {novelWriter-2.4b1.dist-info → novelWriter-2.4rc1.dist-info}/WHEEL +0 -0
- {novelWriter-2.4b1.dist-info → novelWriter-2.4rc1.dist-info}/entry_points.txt +0 -0
- {novelWriter-2.4b1.dist-info → novelWriter-2.4rc1.dist-info}/top_level.txt +0 -0
novelwriter/gui/search.py
CHANGED
@@ -27,22 +27,21 @@ import logging
|
|
27
27
|
|
28
28
|
from time import time
|
29
29
|
|
30
|
-
from PyQt5.QtCore import
|
31
|
-
from PyQt5.QtGui import QCursor, QKeyEvent
|
30
|
+
from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot
|
31
|
+
from PyQt5.QtGui import QCursor, QKeyEvent
|
32
32
|
from PyQt5.QtWidgets import (
|
33
|
-
QHBoxLayout, QHeaderView, QLabel, QLineEdit,
|
34
|
-
QTreeWidgetItem, QVBoxLayout, QWidget
|
33
|
+
QApplication, QFrame, QHBoxLayout, QHeaderView, QLabel, QLineEdit,
|
34
|
+
QToolBar, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget
|
35
35
|
)
|
36
36
|
|
37
37
|
from novelwriter import CONFIG, SHARED
|
38
|
-
from novelwriter.common import checkInt
|
38
|
+
from novelwriter.common import checkInt, cssCol
|
39
39
|
from novelwriter.core.coretools import DocSearch
|
40
40
|
from novelwriter.core.item import NWItem
|
41
|
+
from novelwriter.types import QtAlignMiddle, QtAlignRight, QtUserRole
|
41
42
|
|
42
43
|
logger = logging.getLogger(__name__)
|
43
44
|
|
44
|
-
CACHE_TIMEOUT = 120.0 # 2 minutes
|
45
|
-
|
46
45
|
|
47
46
|
class GuiProjectSearch(QWidget):
|
48
47
|
|
@@ -50,8 +49,8 @@ class GuiProjectSearch(QWidget):
|
|
50
49
|
C_RESULT = 0
|
51
50
|
C_COUNT = 1
|
52
51
|
|
53
|
-
D_HANDLE =
|
54
|
-
D_RESULT =
|
52
|
+
D_HANDLE = QtUserRole
|
53
|
+
D_RESULT = QtUserRole + 1
|
55
54
|
|
56
55
|
selectedItemChanged = pyqtSignal(str)
|
57
56
|
openDocumentSelectRequest = pyqtSignal(str, int, int, bool)
|
@@ -61,22 +60,25 @@ class GuiProjectSearch(QWidget):
|
|
61
60
|
|
62
61
|
logger.debug("Create: GuiProjectSearch")
|
63
62
|
|
64
|
-
iPx = SHARED.theme.
|
63
|
+
iPx = SHARED.theme.baseIconHeight
|
64
|
+
iSz = SHARED.theme.baseIconSize
|
65
65
|
mPx = CONFIG.pxInt(2)
|
66
|
+
tPx = CONFIG.pxInt(4)
|
66
67
|
|
67
68
|
self._time = time()
|
68
69
|
self._search = DocSearch()
|
69
70
|
self._blocked = False
|
71
|
+
self._map: dict[str, tuple[int, float]] = {}
|
70
72
|
|
71
73
|
# Header
|
72
|
-
self.viewLabel = QLabel(self.tr("Project Search"))
|
74
|
+
self.viewLabel = QLabel(self.tr("Project Search"), self)
|
73
75
|
self.viewLabel.setFont(SHARED.theme.guiFontB)
|
74
|
-
self.viewLabel.setContentsMargins(mPx,
|
76
|
+
self.viewLabel.setContentsMargins(mPx, tPx, 0, mPx)
|
75
77
|
|
76
78
|
# Options
|
77
79
|
self.searchOpt = QToolBar(self)
|
78
80
|
self.searchOpt.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
|
79
|
-
self.searchOpt.setIconSize(
|
81
|
+
self.searchOpt.setIconSize(iSz)
|
80
82
|
self.searchOpt.setContentsMargins(0, 0, 0, 0)
|
81
83
|
|
82
84
|
self.toggleCase = self.searchOpt.addAction(self.tr("Case Sensitive"))
|
@@ -96,7 +98,7 @@ class GuiProjectSearch(QWidget):
|
|
96
98
|
|
97
99
|
# Search Box
|
98
100
|
self.searchText = QLineEdit(self)
|
99
|
-
self.searchText.setPlaceholderText(self.tr("Search
|
101
|
+
self.searchText.setPlaceholderText(self.tr("Search"))
|
100
102
|
self.searchText.setClearButtonEnabled(True)
|
101
103
|
|
102
104
|
self.searchAction = self.searchText.addAction(
|
@@ -108,8 +110,11 @@ class GuiProjectSearch(QWidget):
|
|
108
110
|
self.searchResult = QTreeWidget(self)
|
109
111
|
self.searchResult.setHeaderHidden(True)
|
110
112
|
self.searchResult.setColumnCount(2)
|
111
|
-
self.searchResult.setIconSize(
|
113
|
+
self.searchResult.setIconSize(iSz)
|
112
114
|
self.searchResult.setIndentation(iPx)
|
115
|
+
self.searchResult.setFrameStyle(QFrame.Shape.NoFrame)
|
116
|
+
self.searchResult.setUniformRowHeights(True)
|
117
|
+
self.searchResult.setAllColumnsShowFocus(True)
|
113
118
|
self.searchResult.itemDoubleClicked.connect(self._searchResultDoubleClicked)
|
114
119
|
self.searchResult.itemSelectionChanged.connect(self._searchResultSelected)
|
115
120
|
|
@@ -121,11 +126,16 @@ class GuiProjectSearch(QWidget):
|
|
121
126
|
# Assemble
|
122
127
|
self.headerBox = QHBoxLayout()
|
123
128
|
self.headerBox.addWidget(self.viewLabel, 1)
|
124
|
-
self.headerBox.addWidget(self.searchOpt, 0)
|
129
|
+
self.headerBox.addWidget(self.searchOpt, 0, QtAlignMiddle)
|
125
130
|
self.headerBox.setContentsMargins(0, 0, 0, 0)
|
131
|
+
self.headerBox.setSpacing(0)
|
132
|
+
|
133
|
+
self.headerWidget = QWidget(self)
|
134
|
+
self.headerWidget.setLayout(self.headerBox)
|
135
|
+
self.headerWidget.setContentsMargins(0, 0, 0, 0)
|
126
136
|
|
127
137
|
self.outerBox = QVBoxLayout()
|
128
|
-
self.outerBox.
|
138
|
+
self.outerBox.addWidget(self.headerWidget, 0)
|
129
139
|
self.outerBox.addWidget(self.searchText, 0)
|
130
140
|
self.outerBox.addWidget(self.searchResult, 1)
|
131
141
|
self.outerBox.setContentsMargins(0, 0, 0, 0)
|
@@ -144,9 +154,21 @@ class GuiProjectSearch(QWidget):
|
|
144
154
|
|
145
155
|
def updateTheme(self) -> None:
|
146
156
|
"""Update theme elements."""
|
157
|
+
bPx = CONFIG.pxInt(1)
|
158
|
+
mPx = CONFIG.pxInt(2)
|
159
|
+
|
147
160
|
qPalette = self.palette()
|
148
|
-
|
149
|
-
|
161
|
+
colBase = cssCol(qPalette.base().color())
|
162
|
+
colFocus = cssCol(qPalette.highlight().color())
|
163
|
+
|
164
|
+
self.headerWidget.setStyleSheet(f"background: {colBase};")
|
165
|
+
self.headerWidget.setAutoFillBackground(True)
|
166
|
+
|
167
|
+
self.setStyleSheet(
|
168
|
+
"QToolBar {padding: 0; background: none;} "
|
169
|
+
f"QLineEdit {{border: {bPx}px solid {colBase}; padding: {mPx}px;}} "
|
170
|
+
f"QLineEdit:focus {{border: {bPx}px solid {colFocus};}} "
|
171
|
+
)
|
150
172
|
|
151
173
|
self.searchAction.setIcon(SHARED.theme.getIcon("search"))
|
152
174
|
self.toggleCase.setIcon(SHARED.theme.getIcon("search_case"))
|
@@ -170,14 +192,18 @@ class GuiProjectSearch(QWidget):
|
|
170
192
|
)
|
171
193
|
return
|
172
194
|
|
173
|
-
def beginSearch(self) -> None:
|
195
|
+
def beginSearch(self, text: str = "") -> None:
|
174
196
|
"""Focus the search box and select its text, if any."""
|
175
197
|
self.searchText.setFocus()
|
176
198
|
self.searchText.selectAll()
|
199
|
+
if text:
|
200
|
+
self.searchText.setText(text.partition("\n")[0])
|
201
|
+
self.searchText.selectAll()
|
177
202
|
return
|
178
203
|
|
179
204
|
def closeProjectTasks(self) -> None:
|
180
205
|
"""Run close project tasks."""
|
206
|
+
self._map = {}
|
181
207
|
self.searchText.clear()
|
182
208
|
self.searchResult.clear()
|
183
209
|
return
|
@@ -209,6 +235,20 @@ class GuiProjectSearch(QWidget):
|
|
209
235
|
super().keyPressEvent(event)
|
210
236
|
return
|
211
237
|
|
238
|
+
##
|
239
|
+
# Public Slots
|
240
|
+
##
|
241
|
+
|
242
|
+
@pyqtSlot(str, float)
|
243
|
+
def textChanged(self, tHandle: str, timeStamp: float) -> None:
|
244
|
+
"""Update search result for a specific document."""
|
245
|
+
if (entry := self._map.get(tHandle)) and timeStamp > entry[1]:
|
246
|
+
start = time()
|
247
|
+
results, capped = self._search.searchText(SHARED.mainGui.docEditor.getText())
|
248
|
+
self._displayResultSet(SHARED.project.tree[tHandle], results, capped)
|
249
|
+
logger.debug("Updated search for '%s' in %.3f ms", tHandle, 1000*(time() - start))
|
250
|
+
return
|
251
|
+
|
212
252
|
##
|
213
253
|
# Private Slots
|
214
254
|
##
|
@@ -217,19 +257,21 @@ class GuiProjectSearch(QWidget):
|
|
217
257
|
def _processSearch(self) -> None:
|
218
258
|
"""Perform a search."""
|
219
259
|
if not self._blocked:
|
220
|
-
|
260
|
+
QApplication.setOverrideCursor(QCursor(Qt.CursorShape.WaitCursor))
|
221
261
|
start = time()
|
262
|
+
SHARED.mainGui.saveDocument()
|
222
263
|
self._blocked = True
|
264
|
+
self._map = {}
|
223
265
|
self.searchResult.clear()
|
224
266
|
if text := self.searchText.text():
|
225
267
|
self._search.setUserRegEx(self.toggleRegEx.isChecked())
|
226
268
|
self._search.setCaseSensitive(self.toggleCase.isChecked())
|
227
269
|
self._search.setWholeWords(self.toggleWord.isChecked())
|
228
270
|
for item, results, capped in self._search.iterSearch(SHARED.project, text):
|
229
|
-
self.
|
271
|
+
self._displayResultSet(item, results, capped)
|
230
272
|
logger.debug("Search took %.3f ms", 1000*(time() - start))
|
231
273
|
self._time = time()
|
232
|
-
|
274
|
+
QApplication.restoreOverrideCursor()
|
233
275
|
self._blocked = False
|
234
276
|
return
|
235
277
|
|
@@ -274,11 +316,11 @@ class GuiProjectSearch(QWidget):
|
|
274
316
|
# Internal Functions
|
275
317
|
##
|
276
318
|
|
277
|
-
def
|
278
|
-
self, nwItem: NWItem, results: list[tuple[int, int, str]], capped: bool
|
319
|
+
def _displayResultSet(
|
320
|
+
self, nwItem: NWItem | None, results: list[tuple[int, int, str]], capped: bool
|
279
321
|
) -> None:
|
280
322
|
"""Populate the result tree."""
|
281
|
-
if results:
|
323
|
+
if results and nwItem:
|
282
324
|
tHandle = nwItem.itemHandle
|
283
325
|
docIcon = SHARED.theme.getItemIcon(
|
284
326
|
nwItem.itemType, nwItem.itemClass,
|
@@ -291,9 +333,13 @@ class GuiProjectSearch(QWidget):
|
|
291
333
|
tItem.setIcon(self.C_NAME, docIcon)
|
292
334
|
tItem.setData(self.C_NAME, self.D_HANDLE, tHandle)
|
293
335
|
tItem.setText(self.C_COUNT, f"({len(results):n}{ext})")
|
294
|
-
tItem.setTextAlignment(self.C_COUNT,
|
336
|
+
tItem.setTextAlignment(self.C_COUNT, QtAlignRight)
|
295
337
|
tItem.setForeground(self.C_COUNT, self.palette().highlight())
|
296
|
-
|
338
|
+
|
339
|
+
index = self._map.get(tHandle, (self.searchResult.topLevelItemCount(), 0.0))[0]
|
340
|
+
self.searchResult.takeTopLevelItem(index)
|
341
|
+
self.searchResult.insertTopLevelItem(index, tItem)
|
342
|
+
self._map[tHandle] = (index, time())
|
297
343
|
|
298
344
|
rItems = []
|
299
345
|
for start, length, context in results:
|
@@ -309,7 +355,7 @@ class GuiProjectSearch(QWidget):
|
|
309
355
|
for i in range(tItem.childCount()):
|
310
356
|
self.searchResult.setFirstColumnSpanned(i, parent, True)
|
311
357
|
|
312
|
-
|
358
|
+
QApplication.processEvents()
|
313
359
|
|
314
360
|
return
|
315
361
|
|
novelwriter/gui/sidebar.py
CHANGED
@@ -28,7 +28,7 @@ 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, pyqtSignal
|
31
|
+
from PyQt5.QtCore import QEvent, QPoint, QSize, pyqtSignal
|
32
32
|
from PyQt5.QtWidgets import QMenu, QVBoxLayout, QWidget
|
33
33
|
|
34
34
|
from novelwriter import CONFIG, SHARED
|
@@ -54,41 +54,43 @@ class GuiSideBar(QWidget):
|
|
54
54
|
|
55
55
|
self.mainGui = mainGui
|
56
56
|
|
57
|
-
iPx =
|
57
|
+
iPx = int(1.2*SHARED.theme.baseButtonHeight)
|
58
|
+
iSz = QSize(iPx, iPx)
|
59
|
+
|
58
60
|
self.setContentsMargins(0, 0, 0, 0)
|
59
61
|
self.installEventFilter(StatusTipFilter(mainGui))
|
60
62
|
|
61
63
|
# Buttons
|
62
|
-
self.tbProject = NIconToolButton(self,
|
64
|
+
self.tbProject = NIconToolButton(self, iSz)
|
63
65
|
self.tbProject.setToolTip("{0} [Ctrl+T]".format(self.tr("Project Tree View")))
|
64
66
|
self.tbProject.clicked.connect(lambda: self.requestViewChange.emit(nwView.PROJECT))
|
65
67
|
|
66
|
-
self.tbNovel = NIconToolButton(self,
|
68
|
+
self.tbNovel = NIconToolButton(self, iSz)
|
67
69
|
self.tbNovel.setToolTip("{0} [Ctrl+T]".format(self.tr("Novel Tree View")))
|
68
70
|
self.tbNovel.clicked.connect(lambda: self.requestViewChange.emit(nwView.NOVEL))
|
69
71
|
|
70
|
-
self.tbSearch = NIconToolButton(self,
|
71
|
-
self.tbSearch.setToolTip("{0} [Ctrl+Shift+F]".format(self.tr("Search
|
72
|
+
self.tbSearch = NIconToolButton(self, iSz)
|
73
|
+
self.tbSearch.setToolTip("{0} [Ctrl+Shift+F]".format(self.tr("Project Search")))
|
72
74
|
self.tbSearch.clicked.connect(lambda: self.requestViewChange.emit(nwView.SEARCH))
|
73
75
|
|
74
|
-
self.tbOutline = NIconToolButton(self,
|
76
|
+
self.tbOutline = NIconToolButton(self, iSz)
|
75
77
|
self.tbOutline.setToolTip("{0} [Ctrl+Shift+T]".format(self.tr("Novel Outline View")))
|
76
78
|
self.tbOutline.clicked.connect(lambda: self.requestViewChange.emit(nwView.OUTLINE))
|
77
79
|
|
78
|
-
self.tbBuild = NIconToolButton(self,
|
80
|
+
self.tbBuild = NIconToolButton(self, iSz)
|
79
81
|
self.tbBuild.setToolTip("{0} [F5]".format(self.tr("Build Manuscript")))
|
80
82
|
self.tbBuild.clicked.connect(self.mainGui.showBuildManuscriptDialog)
|
81
83
|
|
82
|
-
self.tbDetails = NIconToolButton(self,
|
84
|
+
self.tbDetails = NIconToolButton(self, iSz)
|
83
85
|
self.tbDetails.setToolTip("{0} [Shift+F6]".format(self.tr("Novel Details")))
|
84
86
|
self.tbDetails.clicked.connect(self.mainGui.showNovelDetailsDialog)
|
85
87
|
|
86
|
-
self.tbStats = NIconToolButton(self,
|
88
|
+
self.tbStats = NIconToolButton(self, iSz)
|
87
89
|
self.tbStats.setToolTip("{0} [F6]".format(self.tr("Writing Statistics")))
|
88
90
|
self.tbStats.clicked.connect(self.mainGui.showWritingStatsDialog)
|
89
91
|
|
90
92
|
# Settings Menu
|
91
|
-
self.tbSettings = NIconToolButton(self,
|
93
|
+
self.tbSettings = NIconToolButton(self, iSz)
|
92
94
|
self.tbSettings.setToolTip(self.tr("Settings"))
|
93
95
|
|
94
96
|
self.mSettings = _PopRightMenu(self.tbSettings)
|
@@ -123,35 +125,29 @@ class GuiSideBar(QWidget):
|
|
123
125
|
def updateTheme(self) -> None:
|
124
126
|
"""Initialise GUI elements that depend on specific settings."""
|
125
127
|
qPalette = self.palette()
|
126
|
-
qPalette.setBrush(QPalette.Window, qPalette.base())
|
128
|
+
qPalette.setBrush(QPalette.ColorRole.Window, qPalette.base())
|
127
129
|
self.setPalette(qPalette)
|
128
130
|
|
129
131
|
buttonStyle = SHARED.theme.getStyleSheet(STYLES_BIG_TOOLBUTTON)
|
130
132
|
|
131
|
-
self.tbProject.setIcon(SHARED.theme.getIcon("view_editor"))
|
132
133
|
self.tbProject.setStyleSheet(buttonStyle)
|
133
|
-
|
134
|
-
self.tbNovel.setIcon(SHARED.theme.getIcon("view_novel"))
|
135
134
|
self.tbNovel.setStyleSheet(buttonStyle)
|
136
|
-
|
137
|
-
self.tbSearch.setIcon(SHARED.theme.getIcon("view_search"))
|
138
135
|
self.tbSearch.setStyleSheet(buttonStyle)
|
139
|
-
|
140
|
-
self.tbOutline.setIcon(SHARED.theme.getIcon("view_outline"))
|
141
136
|
self.tbOutline.setStyleSheet(buttonStyle)
|
142
|
-
|
143
|
-
self.tbBuild.setIcon(SHARED.theme.getIcon("view_build"))
|
144
137
|
self.tbBuild.setStyleSheet(buttonStyle)
|
145
|
-
|
146
|
-
self.tbDetails.setIcon(SHARED.theme.getIcon("proj_details"))
|
147
138
|
self.tbDetails.setStyleSheet(buttonStyle)
|
148
|
-
|
149
|
-
self.tbStats.setIcon(SHARED.theme.getIcon("proj_stats"))
|
150
139
|
self.tbStats.setStyleSheet(buttonStyle)
|
151
|
-
|
152
|
-
self.tbSettings.setIcon(SHARED.theme.getIcon("settings"))
|
153
140
|
self.tbSettings.setStyleSheet(buttonStyle)
|
154
141
|
|
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")
|
150
|
+
|
155
151
|
return
|
156
152
|
|
157
153
|
# END Class GuiSideBar
|
@@ -161,7 +157,7 @@ class _PopRightMenu(QMenu):
|
|
161
157
|
|
162
158
|
def event(self, event: QEvent) -> bool:
|
163
159
|
"""Overload the show event and move the menu popup location."""
|
164
|
-
if event.type() == QEvent.Show:
|
160
|
+
if event.type() == QEvent.Type.Show:
|
165
161
|
if isinstance(parent := self.parent(), QWidget):
|
166
162
|
offset = QPoint(parent.width(), parent.height() - self.height())
|
167
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,17 +29,17 @@ 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
|
|
@@ -144,33 +144,42 @@ class GuiTheme:
|
|
144
144
|
self.getHeaderDecorationNarrow = self.iconCache.getHeaderDecorationNarrow
|
145
145
|
|
146
146
|
# Extract Other Info
|
147
|
-
self.guiDPI =
|
148
|
-
self.guiScale =
|
147
|
+
self.guiDPI = QApplication.primaryScreen().logicalDotsPerInchX()
|
148
|
+
self.guiScale = QApplication.primaryScreen().logicalDotsPerInchX()/96.0
|
149
149
|
CONFIG.guiScale = self.guiScale
|
150
150
|
logger.debug("GUI DPI: %.1f", self.guiDPI)
|
151
151
|
logger.debug("GUI Scale: %.2f", self.guiScale)
|
152
152
|
|
153
153
|
# Fonts
|
154
|
-
self.guiFont =
|
155
|
-
self.guiFontB =
|
154
|
+
self.guiFont = QApplication.font()
|
155
|
+
self.guiFontB = QApplication.font()
|
156
156
|
self.guiFontB.setBold(True)
|
157
157
|
|
158
158
|
qMetric = QFontMetrics(self.guiFont)
|
159
|
+
fHeight = qMetric.height()
|
160
|
+
fAscent = qMetric.ascent()
|
159
161
|
self.fontPointSize = self.guiFont.pointSizeF()
|
160
|
-
self.fontPixelSize = int(round(
|
161
|
-
self.
|
162
|
+
self.fontPixelSize = int(round(fHeight))
|
163
|
+
self.baseIconHeight = int(round(fAscent))
|
164
|
+
self.baseButtonHeight = int(round(1.35*fAscent))
|
162
165
|
self.textNHeight = qMetric.boundingRect("N").height()
|
163
166
|
self.textNWidth = qMetric.boundingRect("N").width()
|
164
167
|
|
168
|
+
self.baseIconSize = QSize(self.baseIconHeight, self.baseIconHeight)
|
169
|
+
self.buttonIconSize = QSize(int(0.9*self.baseIconHeight), int(0.9*self.baseIconHeight))
|
170
|
+
|
165
171
|
# Monospace Font
|
166
172
|
self.guiFontFixed = QFont()
|
167
173
|
self.guiFontFixed.setPointSizeF(0.95*self.fontPointSize)
|
168
|
-
self.guiFontFixed.setFamily(
|
174
|
+
self.guiFontFixed.setFamily(
|
175
|
+
QFontDatabase.systemFont(QFontDatabase.SystemFont.FixedFont).family()
|
176
|
+
)
|
169
177
|
|
170
178
|
logger.debug("GUI Font Family: %s", self.guiFont.family())
|
171
179
|
logger.debug("GUI Font Point Size: %.2f", self.fontPointSize)
|
172
180
|
logger.debug("GUI Font Pixel Size: %d", self.fontPixelSize)
|
173
|
-
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)
|
174
183
|
logger.debug("Text 'N' Height: %d", self.textNHeight)
|
175
184
|
logger.debug("Text 'N' Width: %d", self.textNWidth)
|
176
185
|
|
@@ -248,7 +257,7 @@ class GuiTheme:
|
|
248
257
|
self._setPalette(parser, sec, "link", QPalette.ColorRole.Link)
|
249
258
|
self._setPalette(parser, sec, "linkvisited", QPalette.ColorRole.LinkVisited)
|
250
259
|
else:
|
251
|
-
self._guiPalette =
|
260
|
+
self._guiPalette = QApplication.style().standardPalette()
|
252
261
|
|
253
262
|
# GUI
|
254
263
|
sec = "GUI"
|
@@ -277,7 +286,7 @@ class GuiTheme:
|
|
277
286
|
self.iconCache.loadTheme(self.themeIcons or defaultIcons)
|
278
287
|
|
279
288
|
# Apply Styles
|
280
|
-
|
289
|
+
QApplication.setPalette(self._guiPalette)
|
281
290
|
|
282
291
|
# Reset stylesheets so that they are regenerated
|
283
292
|
self._buildStyleSheets(self._guiPalette)
|
@@ -394,14 +403,14 @@ class GuiTheme:
|
|
394
403
|
font.setFamily("Arial")
|
395
404
|
font.setPointSize(10)
|
396
405
|
else:
|
397
|
-
font = fontDB.systemFont(QFontDatabase.GeneralFont)
|
406
|
+
font = fontDB.systemFont(QFontDatabase.SystemFont.GeneralFont)
|
398
407
|
CONFIG.guiFont = font.family()
|
399
408
|
CONFIG.guiFontSize = font.pointSize()
|
400
409
|
else:
|
401
410
|
font.setFamily(CONFIG.guiFont)
|
402
411
|
font.setPointSize(CONFIG.guiFontSize)
|
403
412
|
|
404
|
-
|
413
|
+
QApplication.setFont(font)
|
405
414
|
|
406
415
|
return
|
407
416
|
|
@@ -440,24 +449,24 @@ class GuiTheme:
|
|
440
449
|
|
441
450
|
# Flat Tab Widget and Tab Bar:
|
442
451
|
self._styleSheets[STYLES_FLAT_TABS] = (
|
443
|
-
"QTabWidget::pane {
|
444
|
-
"QTabWidget QTabBar::tab {{border: 0; padding: {
|
445
|
-
"QTabWidget QTabBar::tab:selected {{color:
|
446
|
-
)
|
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
|
+
)
|
447
456
|
|
448
457
|
# Minimal Tool Button
|
449
458
|
self._styleSheets[STYLES_MIN_TOOLBUTTON] = (
|
450
|
-
"QToolButton {{padding: {
|
451
|
-
"QToolButton:hover {{border: none; background:
|
452
|
-
"QToolButton::menu-indicator {
|
453
|
-
)
|
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
|
+
)
|
454
463
|
|
455
464
|
# Big Tool Button
|
456
465
|
self._styleSheets[STYLES_BIG_TOOLBUTTON] = (
|
457
|
-
"QToolButton {{padding: {
|
458
|
-
"QToolButton:hover {{border: none; background:
|
459
|
-
"QToolButton::menu-indicator {
|
460
|
-
)
|
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
|
+
)
|
461
470
|
|
462
471
|
return
|
463
472
|
|
@@ -544,6 +553,9 @@ class GuiIcons:
|
|
544
553
|
self._confName = "icons.conf"
|
545
554
|
self._iconPath = CONFIG.assetPath("icons")
|
546
555
|
|
556
|
+
# None Icon
|
557
|
+
self._noIcon = QIcon(str(self._iconPath / "none.svg"))
|
558
|
+
|
547
559
|
# Icon Theme Meta
|
548
560
|
self.themeName = ""
|
549
561
|
self.themeDescription = ""
|
@@ -674,13 +686,13 @@ class GuiIcons:
|
|
674
686
|
def getToggleIcon(self, name: str, size: tuple[int, int]) -> QIcon:
|
675
687
|
"""Return a toggle icon from the icon buffer. or load it."""
|
676
688
|
if name in self.TOGGLE_ICON_KEYS:
|
677
|
-
pOne
|
689
|
+
pOne = self.getPixmap(self.TOGGLE_ICON_KEYS[name][0], size)
|
678
690
|
pTwo = self.getPixmap(self.TOGGLE_ICON_KEYS[name][1], size)
|
679
691
|
icon = QIcon()
|
680
692
|
icon.addPixmap(pOne, QIcon.Mode.Normal, QIcon.State.On)
|
681
693
|
icon.addPixmap(pTwo, QIcon.Mode.Normal, QIcon.State.Off)
|
682
694
|
return icon
|
683
|
-
return
|
695
|
+
return self._noIcon
|
684
696
|
|
685
697
|
def getPixmap(self, name: str, size: tuple[int, int]) -> QPixmap:
|
686
698
|
"""Return an icon from the icon buffer as a QPixmap. If it
|
@@ -712,14 +724,14 @@ class GuiIcons:
|
|
712
724
|
elif tLayout == nwItemLayout.NOTE:
|
713
725
|
iconName = "proj_note"
|
714
726
|
if iconName is None:
|
715
|
-
return
|
727
|
+
return self._noIcon
|
716
728
|
|
717
729
|
return self.getIcon(iconName)
|
718
730
|
|
719
731
|
def getHeaderDecoration(self, hLevel: int) -> QPixmap:
|
720
732
|
"""Get the decoration for a specific heading level."""
|
721
733
|
if not self._headerDec:
|
722
|
-
iPx = self.mainTheme.
|
734
|
+
iPx = self.mainTheme.baseIconHeight
|
723
735
|
self._headerDec = [
|
724
736
|
self.loadDecoration("deco_doc_h0", h=iPx),
|
725
737
|
self.loadDecoration("deco_doc_h1", h=iPx),
|
@@ -732,7 +744,7 @@ class GuiIcons:
|
|
732
744
|
def getHeaderDecorationNarrow(self, hLevel: int) -> QPixmap:
|
733
745
|
"""Get the narrow decoration for a specific heading level."""
|
734
746
|
if not self._headerDecNarrow:
|
735
|
-
iPx = self.mainTheme.
|
747
|
+
iPx = self.mainTheme.baseIconHeight
|
736
748
|
self._headerDecNarrow = [
|
737
749
|
self.loadDecoration("deco_doc_h0_n", h=iPx),
|
738
750
|
self.loadDecoration("deco_doc_h1_n", h=iPx),
|
@@ -753,7 +765,7 @@ class GuiIcons:
|
|
753
765
|
"""
|
754
766
|
if name not in self.ICON_KEYS:
|
755
767
|
logger.error("Requested unknown icon name '%s'", name)
|
756
|
-
return
|
768
|
+
return self._noIcon
|
757
769
|
|
758
770
|
# If we just want the app icons, return right away
|
759
771
|
if name == "novelwriter":
|
@@ -769,7 +781,7 @@ class GuiIcons:
|
|
769
781
|
# If we didn't find one, give up and return an empty icon
|
770
782
|
logger.warning("Did not load an icon for '%s'", name)
|
771
783
|
|
772
|
-
return
|
784
|
+
return self._noIcon
|
773
785
|
|
774
786
|
# END Class GuiIcons
|
775
787
|
|