novelWriter 2.4b1__py3-none-any.whl → 2.4.1__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.4.1.dist-info}/METADATA +5 -6
- {novelWriter-2.4b1.dist-info → novelWriter-2.4.1.dist-info}/RECORD +79 -83
- novelwriter/__init__.py +15 -8
- 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 +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 +11 -3
- novelwriter/config.py +12 -4
- novelwriter/core/buildsettings.py +7 -7
- novelwriter/core/coretools.py +21 -22
- novelwriter/core/docbuild.py +2 -2
- novelwriter/core/projectxml.py +1 -1
- novelwriter/core/spellcheck.py +3 -3
- novelwriter/core/status.py +3 -2
- novelwriter/core/tokenizer.py +3 -3
- novelwriter/core/toodt.py +333 -356
- 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 +29 -34
- novelwriter/dialogs/projectsettings.py +31 -28
- novelwriter/dialogs/quotes.py +10 -9
- novelwriter/dialogs/wordlist.py +17 -14
- novelwriter/error.py +14 -12
- 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 +205 -246
- novelwriter/gui/dochighlight.py +26 -9
- novelwriter/gui/docviewer.py +55 -59
- novelwriter/gui/docviewerpanel.py +16 -13
- novelwriter/gui/editordocument.py +4 -4
- novelwriter/gui/itemdetails.py +45 -48
- novelwriter/gui/mainmenu.py +2 -2
- novelwriter/gui/noveltree.py +23 -21
- novelwriter/gui/outline.py +93 -94
- novelwriter/gui/projtree.py +32 -30
- novelwriter/gui/search.py +75 -29
- novelwriter/gui/sidebar.py +24 -28
- novelwriter/gui/statusbar.py +14 -14
- novelwriter/gui/theme.py +61 -39
- novelwriter/guimain.py +37 -33
- novelwriter/shared.py +21 -9
- novelwriter/text/counting.py +1 -0
- novelwriter/tools/dictionaries.py +15 -14
- novelwriter/tools/lipsum.py +20 -17
- novelwriter/tools/manusbuild.py +44 -35
- novelwriter/tools/manuscript.py +112 -112
- novelwriter/tools/manussettings.py +91 -98
- novelwriter/tools/noveldetails.py +20 -18
- novelwriter/tools/welcome.py +51 -48
- 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.4.1.dist-info}/LICENSE.md +0 -0
- {novelWriter-2.4b1.dist-info → novelWriter-2.4.1.dist-info}/WHEEL +0 -0
- {novelWriter-2.4b1.dist-info → novelWriter-2.4.1.dist-info}/entry_points.txt +0 -0
- {novelWriter-2.4b1.dist-info → novelWriter-2.4.1.dist-info}/top_level.txt +0 -0
novelwriter/gui/projtree.py
CHANGED
@@ -32,10 +32,10 @@ from enum import Enum
|
|
32
32
|
from time import time
|
33
33
|
from typing import TYPE_CHECKING
|
34
34
|
|
35
|
+
from PyQt5.QtCore import QPoint, QTimer, Qt, pyqtSignal, pyqtSlot
|
35
36
|
from PyQt5.QtGui import (
|
36
37
|
QDragEnterEvent, QDragMoveEvent, QDropEvent, QIcon, QMouseEvent, QPalette
|
37
38
|
)
|
38
|
-
from PyQt5.QtCore import QPoint, QTimer, Qt, QSize, pyqtSignal, pyqtSlot
|
39
39
|
from PyQt5.QtWidgets import (
|
40
40
|
QAbstractItemView, QAction, QDialog, QFrame, QHBoxLayout, QHeaderView,
|
41
41
|
QLabel, QMenu, QShortcut, QSizePolicy, QTreeWidget, QTreeWidgetItem,
|
@@ -54,6 +54,7 @@ from novelwriter.dialogs.projectsettings import GuiProjectSettings
|
|
54
54
|
from novelwriter.enum import nwDocMode, nwItemType, nwItemClass, nwItemLayout
|
55
55
|
from novelwriter.extensions.modified import NIconToolButton
|
56
56
|
from novelwriter.gui.theme import STYLES_MIN_TOOLBUTTON
|
57
|
+
from novelwriter.types import QtAlignLeft, QtAlignRight, QtMouseLeft, QtMouseMiddle, QtUserRole
|
57
58
|
|
58
59
|
if TYPE_CHECKING: # pragma: no cover
|
59
60
|
from novelwriter.guimain import GuiMain
|
@@ -263,14 +264,14 @@ class GuiProjectToolBar(QWidget):
|
|
263
264
|
self.projTree = projView.projTree
|
264
265
|
self.mainGui = projView.mainGui
|
265
266
|
|
266
|
-
|
267
|
+
iSz = SHARED.theme.baseIconSize
|
267
268
|
mPx = CONFIG.pxInt(2)
|
268
269
|
|
269
270
|
self.setContentsMargins(0, 0, 0, 0)
|
270
271
|
self.setAutoFillBackground(True)
|
271
272
|
|
272
273
|
# Widget Label
|
273
|
-
self.viewLabel = QLabel(self.tr("Project Content"))
|
274
|
+
self.viewLabel = QLabel(self.tr("Project Content"), self)
|
274
275
|
self.viewLabel.setFont(SHARED.theme.guiFontB)
|
275
276
|
self.viewLabel.setContentsMargins(0, 0, 0, 0)
|
276
277
|
self.viewLabel.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
@@ -278,17 +279,17 @@ class GuiProjectToolBar(QWidget):
|
|
278
279
|
# Quick Links
|
279
280
|
self.mQuick = QMenu(self)
|
280
281
|
|
281
|
-
self.tbQuick = NIconToolButton(self,
|
282
|
+
self.tbQuick = NIconToolButton(self, iSz)
|
282
283
|
self.tbQuick.setToolTip("%s [Ctrl+L]" % self.tr("Quick Links"))
|
283
284
|
self.tbQuick.setShortcut("Ctrl+L")
|
284
285
|
self.tbQuick.setMenu(self.mQuick)
|
285
286
|
|
286
287
|
# Move Buttons
|
287
|
-
self.tbMoveU = NIconToolButton(self,
|
288
|
+
self.tbMoveU = NIconToolButton(self, iSz)
|
288
289
|
self.tbMoveU.setToolTip("%s [Ctrl+Up]" % self.tr("Move Up"))
|
289
290
|
self.tbMoveU.clicked.connect(lambda: self.projTree.moveTreeItem(-1))
|
290
291
|
|
291
|
-
self.tbMoveD = NIconToolButton(self,
|
292
|
+
self.tbMoveD = NIconToolButton(self, iSz)
|
292
293
|
self.tbMoveD.setToolTip("%s [Ctrl+Down]" % self.tr("Move Down"))
|
293
294
|
self.tbMoveD.clicked.connect(lambda: self.projTree.moveTreeItem(1))
|
294
295
|
|
@@ -328,7 +329,7 @@ class GuiProjectToolBar(QWidget):
|
|
328
329
|
self.mAddRoot = self.mAdd.addMenu(trConst(nwLabels.ITEM_DESCRIPTION["root"]))
|
329
330
|
self._buildRootMenu()
|
330
331
|
|
331
|
-
self.tbAdd = NIconToolButton(self,
|
332
|
+
self.tbAdd = NIconToolButton(self, iSz)
|
332
333
|
self.tbAdd.setToolTip("%s [Ctrl+N]" % self.tr("Add Item"))
|
333
334
|
self.tbAdd.setShortcut("Ctrl+N")
|
334
335
|
self.tbAdd.setMenu(self.mAdd)
|
@@ -345,7 +346,7 @@ class GuiProjectToolBar(QWidget):
|
|
345
346
|
self.aEmptyTrash = self.mMore.addAction(self.tr("Empty Trash"))
|
346
347
|
self.aEmptyTrash.triggered.connect(lambda: self.projTree.emptyTrash())
|
347
348
|
|
348
|
-
self.tbMore = NIconToolButton(self,
|
349
|
+
self.tbMore = NIconToolButton(self, iSz)
|
349
350
|
self.tbMore.setToolTip(self.tr("More Options"))
|
350
351
|
self.tbMore.setMenu(self.mMore)
|
351
352
|
|
@@ -384,16 +385,17 @@ class GuiProjectToolBar(QWidget):
|
|
384
385
|
self.tbAdd.setStyleSheet(buttonStyle)
|
385
386
|
self.tbMore.setStyleSheet(buttonStyle)
|
386
387
|
|
387
|
-
self.tbQuick.
|
388
|
-
self.tbMoveU.
|
389
|
-
self.tbMoveD.
|
388
|
+
self.tbQuick.setThemeIcon("bookmark")
|
389
|
+
self.tbMoveU.setThemeIcon("up")
|
390
|
+
self.tbMoveD.setThemeIcon("down")
|
391
|
+
self.tbAdd.setThemeIcon("add")
|
392
|
+
self.tbMore.setThemeIcon("menu")
|
393
|
+
|
390
394
|
self.aAddEmpty.setIcon(SHARED.theme.getIcon("proj_document"))
|
391
395
|
self.aAddChap.setIcon(SHARED.theme.getIcon("proj_chapter"))
|
392
396
|
self.aAddScene.setIcon(SHARED.theme.getIcon("proj_scene"))
|
393
397
|
self.aAddNote.setIcon(SHARED.theme.getIcon("proj_note"))
|
394
398
|
self.aAddFolder.setIcon(SHARED.theme.getIcon("proj_folder"))
|
395
|
-
self.tbAdd.setIcon(SHARED.theme.getIcon("add"))
|
396
|
-
self.tbMore.setIcon(SHARED.theme.getIcon("menu"))
|
397
399
|
|
398
400
|
self.buildQuickLinksMenu()
|
399
401
|
self._buildRootMenu()
|
@@ -451,7 +453,7 @@ class GuiProjectToolBar(QWidget):
|
|
451
453
|
|
452
454
|
def _buildRootMenu(self) -> None:
|
453
455
|
"""Build the rood folder menu."""
|
454
|
-
def addClass(itemClass):
|
456
|
+
def addClass(itemClass: nwItemClass) -> None:
|
455
457
|
aNew = self.mAddRoot.addAction(trConst(nwLabels.CLASS_NAME[itemClass]))
|
456
458
|
aNew.setIcon(SHARED.theme.getIcon(nwLabels.CLASS_ICON[itemClass]))
|
457
459
|
aNew.triggered.connect(lambda: self.projTree.newTreeItem(nwItemType.ROOT, itemClass))
|
@@ -485,8 +487,8 @@ class GuiProjectTree(QTreeWidget):
|
|
485
487
|
C_ACTIVE = 2
|
486
488
|
C_STATUS = 3
|
487
489
|
|
488
|
-
D_HANDLE =
|
489
|
-
D_WORDS =
|
490
|
+
D_HANDLE = QtUserRole
|
491
|
+
D_WORDS = QtUserRole + 1
|
490
492
|
|
491
493
|
itemRefreshed = pyqtSignal(str, NWItem, QIcon)
|
492
494
|
|
@@ -516,10 +518,10 @@ class GuiProjectTree(QTreeWidget):
|
|
516
518
|
self.customContextMenuRequested.connect(self._openContextMenu)
|
517
519
|
|
518
520
|
# Tree Settings
|
519
|
-
iPx = SHARED.theme.
|
521
|
+
iPx = SHARED.theme.baseIconHeight
|
520
522
|
cMg = CONFIG.pxInt(6)
|
521
523
|
|
522
|
-
self.setIconSize(
|
524
|
+
self.setIconSize(SHARED.theme.baseIconSize)
|
523
525
|
self.setFrameStyle(QFrame.Shape.NoFrame)
|
524
526
|
self.setUniformRowHeights(True)
|
525
527
|
self.setAllColumnsShowFocus(True)
|
@@ -563,9 +565,9 @@ class GuiProjectTree(QTreeWidget):
|
|
563
565
|
self.itemSelectionChanged.connect(self._treeSelectionChange)
|
564
566
|
|
565
567
|
# Auto Scroll
|
566
|
-
self._scrollMargin = SHARED.theme.
|
568
|
+
self._scrollMargin = SHARED.theme.baseIconHeight
|
567
569
|
self._scrollDirection = 0
|
568
|
-
self._scrollTimer = QTimer()
|
570
|
+
self._scrollTimer = QTimer(self)
|
569
571
|
self._scrollTimer.timeout.connect(self._doAutoScroll)
|
570
572
|
self._scrollTimer.setInterval(250)
|
571
573
|
|
@@ -1228,7 +1230,7 @@ class GuiProjectTree(QTreeWidget):
|
|
1228
1230
|
else:
|
1229
1231
|
ctxMenu.buildSingleSelectMenu(hasChild)
|
1230
1232
|
|
1231
|
-
ctxMenu.
|
1233
|
+
ctxMenu.exec(self.viewport().mapToGlobal(clickPos))
|
1232
1234
|
ctxMenu.deleteLater()
|
1233
1235
|
|
1234
1236
|
return True
|
@@ -1254,11 +1256,11 @@ class GuiProjectTree(QTreeWidget):
|
|
1254
1256
|
for viewing if the user middle-clicked.
|
1255
1257
|
"""
|
1256
1258
|
super().mousePressEvent(event)
|
1257
|
-
if event.button() ==
|
1259
|
+
if event.button() == QtMouseLeft:
|
1258
1260
|
selItem = self.indexAt(event.pos())
|
1259
1261
|
if not selItem.isValid():
|
1260
1262
|
self.clearSelection()
|
1261
|
-
elif event.button() ==
|
1263
|
+
elif event.button() == QtMouseMiddle:
|
1262
1264
|
selItem = self.itemAt(event.pos())
|
1263
1265
|
if selItem:
|
1264
1266
|
tHandle = selItem.data(self.C_DATA, self.D_HANDLE)
|
@@ -1266,7 +1268,7 @@ class GuiProjectTree(QTreeWidget):
|
|
1266
1268
|
self.projView.openDocumentRequest.emit(tHandle, nwDocMode.VIEW, "", False)
|
1267
1269
|
return
|
1268
1270
|
|
1269
|
-
def startDrag(self, dropAction: Qt.
|
1271
|
+
def startDrag(self, dropAction: Qt.DropAction) -> None:
|
1270
1272
|
"""Capture the drag and drop handling to pop alerts."""
|
1271
1273
|
super().startDrag(dropAction)
|
1272
1274
|
if self._popAlert:
|
@@ -1408,7 +1410,7 @@ class GuiProjectTree(QTreeWidget):
|
|
1408
1410
|
itemList.remove(tHandle)
|
1409
1411
|
|
1410
1412
|
dlgMerge = GuiDocMerge(self.mainGui, tHandle, itemList)
|
1411
|
-
dlgMerge.
|
1413
|
+
dlgMerge.exec()
|
1412
1414
|
|
1413
1415
|
if dlgMerge.result() == QDialog.DialogCode.Accepted:
|
1414
1416
|
|
@@ -1478,7 +1480,7 @@ class GuiProjectTree(QTreeWidget):
|
|
1478
1480
|
return False
|
1479
1481
|
|
1480
1482
|
dlgSplit = GuiDocSplit(self.mainGui, tHandle)
|
1481
|
-
dlgSplit.
|
1483
|
+
dlgSplit.exec()
|
1482
1484
|
|
1483
1485
|
if dlgSplit.result() == QDialog.DialogCode.Accepted:
|
1484
1486
|
|
@@ -1585,10 +1587,10 @@ class GuiProjectTree(QTreeWidget):
|
|
1585
1587
|
newItem.setText(self.C_ACTIVE, "")
|
1586
1588
|
newItem.setText(self.C_STATUS, "")
|
1587
1589
|
|
1588
|
-
newItem.setTextAlignment(self.C_NAME,
|
1589
|
-
newItem.setTextAlignment(self.C_COUNT,
|
1590
|
-
newItem.setTextAlignment(self.C_ACTIVE,
|
1591
|
-
newItem.setTextAlignment(self.C_STATUS,
|
1590
|
+
newItem.setTextAlignment(self.C_NAME, QtAlignLeft)
|
1591
|
+
newItem.setTextAlignment(self.C_COUNT, QtAlignRight)
|
1592
|
+
newItem.setTextAlignment(self.C_ACTIVE, QtAlignLeft)
|
1593
|
+
newItem.setTextAlignment(self.C_STATUS, QtAlignLeft)
|
1592
1594
|
|
1593
1595
|
newItem.setData(self.C_DATA, self.D_HANDLE, tHandle)
|
1594
1596
|
newItem.setData(self.C_DATA, self.D_WORDS, 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 for"))
|
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"QWidget {{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"
|