novelWriter 2.5.1__py3-none-any.whl → 2.6b1__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.5.1.dist-info → novelWriter-2.6b1.dist-info}/METADATA +2 -1
- {novelWriter-2.5.1.dist-info → novelWriter-2.6b1.dist-info}/RECORD +61 -56
- {novelWriter-2.5.1.dist-info → novelWriter-2.6b1.dist-info}/WHEEL +1 -1
- novelwriter/__init__.py +3 -3
- novelwriter/assets/i18n/project_en_GB.json +1 -0
- novelwriter/assets/icons/typicons_dark/icons.conf +1 -0
- novelwriter/assets/icons/typicons_dark/mixed_copy.svg +4 -0
- novelwriter/assets/icons/typicons_light/icons.conf +1 -0
- novelwriter/assets/icons/typicons_light/mixed_copy.svg +4 -0
- novelwriter/assets/manual.pdf +0 -0
- novelwriter/assets/sample.zip +0 -0
- novelwriter/assets/themes/default_light.conf +2 -2
- novelwriter/common.py +63 -0
- novelwriter/config.py +10 -3
- novelwriter/constants.py +153 -60
- novelwriter/core/buildsettings.py +66 -39
- novelwriter/core/coretools.py +34 -22
- novelwriter/core/docbuild.py +130 -169
- novelwriter/core/index.py +29 -18
- novelwriter/core/item.py +2 -2
- novelwriter/core/options.py +4 -1
- novelwriter/core/spellcheck.py +9 -14
- novelwriter/dialogs/preferences.py +45 -32
- novelwriter/dialogs/projectsettings.py +3 -3
- novelwriter/enum.py +29 -23
- novelwriter/extensions/configlayout.py +24 -11
- novelwriter/extensions/modified.py +13 -1
- novelwriter/extensions/pagedsidebar.py +5 -5
- novelwriter/formats/shared.py +155 -0
- novelwriter/formats/todocx.py +1195 -0
- novelwriter/formats/tohtml.py +452 -0
- novelwriter/{core → formats}/tokenizer.py +483 -485
- novelwriter/formats/tomarkdown.py +217 -0
- novelwriter/{core → formats}/toodt.py +270 -320
- novelwriter/formats/toqdoc.py +436 -0
- novelwriter/formats/toraw.py +91 -0
- novelwriter/gui/doceditor.py +240 -193
- novelwriter/gui/dochighlight.py +96 -84
- novelwriter/gui/docviewer.py +56 -30
- novelwriter/gui/docviewerpanel.py +3 -3
- novelwriter/gui/editordocument.py +17 -2
- novelwriter/gui/itemdetails.py +8 -4
- novelwriter/gui/mainmenu.py +121 -60
- novelwriter/gui/noveltree.py +35 -37
- novelwriter/gui/outline.py +186 -238
- novelwriter/gui/projtree.py +142 -131
- novelwriter/gui/sidebar.py +7 -6
- novelwriter/gui/theme.py +5 -4
- novelwriter/guimain.py +43 -155
- novelwriter/shared.py +14 -4
- novelwriter/text/counting.py +2 -0
- novelwriter/text/patterns.py +155 -59
- novelwriter/tools/manusbuild.py +1 -1
- novelwriter/tools/manuscript.py +121 -78
- novelwriter/tools/manussettings.py +403 -260
- novelwriter/tools/welcome.py +4 -4
- novelwriter/tools/writingstats.py +3 -3
- novelwriter/types.py +16 -6
- novelwriter/core/tohtml.py +0 -530
- novelwriter/core/tomarkdown.py +0 -252
- novelwriter/core/toqdoc.py +0 -419
- {novelWriter-2.5.1.dist-info → novelWriter-2.6b1.dist-info}/LICENSE.md +0 -0
- {novelWriter-2.5.1.dist-info → novelWriter-2.6b1.dist-info}/entry_points.txt +0 -0
- {novelWriter-2.5.1.dist-info → novelWriter-2.6b1.dist-info}/top_level.txt +0 -0
novelwriter/gui/projtree.py
CHANGED
@@ -29,7 +29,6 @@ from __future__ import annotations
|
|
29
29
|
import logging
|
30
30
|
|
31
31
|
from enum import Enum
|
32
|
-
from time import time
|
33
32
|
|
34
33
|
from PyQt5.QtCore import QPoint, Qt, QTimer, pyqtSignal, pyqtSlot
|
35
34
|
from PyQt5.QtGui import QDragEnterEvent, QDragMoveEvent, QDropEvent, QIcon, QMouseEvent, QPalette
|
@@ -39,8 +38,8 @@ from PyQt5.QtWidgets import (
|
|
39
38
|
)
|
40
39
|
|
41
40
|
from novelwriter import CONFIG, SHARED
|
42
|
-
from novelwriter.common import minmax
|
43
|
-
from novelwriter.constants import
|
41
|
+
from novelwriter.common import minmax, qtLambda
|
42
|
+
from novelwriter.constants import nwLabels, nwStyles, nwUnicode, trConst
|
44
43
|
from novelwriter.core.coretools import DocDuplicator, DocMerger, DocSplitter
|
45
44
|
from novelwriter.core.item import NWItem
|
46
45
|
from novelwriter.dialogs.docmerge import GuiDocMerge
|
@@ -51,8 +50,8 @@ from novelwriter.enum import nwDocMode, nwItemClass, nwItemLayout, nwItemType
|
|
51
50
|
from novelwriter.extensions.modified import NIconToolButton
|
52
51
|
from novelwriter.gui.theme import STYLES_MIN_TOOLBUTTON
|
53
52
|
from novelwriter.types import (
|
54
|
-
QtAlignLeft, QtAlignRight, QtMouseLeft, QtMouseMiddle,
|
55
|
-
QtUserRole
|
53
|
+
QtAlignLeft, QtAlignRight, QtMouseLeft, QtMouseMiddle, QtScrollAlwaysOff,
|
54
|
+
QtScrollAsNeeded, QtSizeExpanding, QtUserRole
|
56
55
|
)
|
57
56
|
|
58
57
|
logger = logging.getLogger(__name__)
|
@@ -97,37 +96,37 @@ class GuiProjectView(QWidget):
|
|
97
96
|
self.keyMoveUp = QShortcut(self.projTree)
|
98
97
|
self.keyMoveUp.setKey("Ctrl+Up")
|
99
98
|
self.keyMoveUp.setContext(Qt.ShortcutContext.WidgetShortcut)
|
100
|
-
self.keyMoveUp.activated.connect(
|
99
|
+
self.keyMoveUp.activated.connect(qtLambda(self.projTree.moveTreeItem, -1))
|
101
100
|
|
102
101
|
self.keyMoveDn = QShortcut(self.projTree)
|
103
102
|
self.keyMoveDn.setKey("Ctrl+Down")
|
104
103
|
self.keyMoveDn.setContext(Qt.ShortcutContext.WidgetShortcut)
|
105
|
-
self.keyMoveDn.activated.connect(
|
104
|
+
self.keyMoveDn.activated.connect(qtLambda(self.projTree.moveTreeItem, 1))
|
106
105
|
|
107
106
|
self.keyGoPrev = QShortcut(self.projTree)
|
108
107
|
self.keyGoPrev.setKey("Alt+Up")
|
109
108
|
self.keyGoPrev.setContext(Qt.ShortcutContext.WidgetShortcut)
|
110
|
-
self.keyGoPrev.activated.connect(
|
109
|
+
self.keyGoPrev.activated.connect(qtLambda(self.projTree.moveToNextItem, -1))
|
111
110
|
|
112
111
|
self.keyGoNext = QShortcut(self.projTree)
|
113
112
|
self.keyGoNext.setKey("Alt+Down")
|
114
113
|
self.keyGoNext.setContext(Qt.ShortcutContext.WidgetShortcut)
|
115
|
-
self.keyGoNext.activated.connect(
|
114
|
+
self.keyGoNext.activated.connect(qtLambda(self.projTree.moveToNextItem, 1))
|
116
115
|
|
117
116
|
self.keyGoUp = QShortcut(self.projTree)
|
118
117
|
self.keyGoUp.setKey("Alt+Left")
|
119
118
|
self.keyGoUp.setContext(Qt.ShortcutContext.WidgetShortcut)
|
120
|
-
self.keyGoUp.activated.connect(
|
119
|
+
self.keyGoUp.activated.connect(qtLambda(self.projTree.moveToLevel, -1))
|
121
120
|
|
122
121
|
self.keyGoDown = QShortcut(self.projTree)
|
123
122
|
self.keyGoDown.setKey("Alt+Right")
|
124
123
|
self.keyGoDown.setContext(Qt.ShortcutContext.WidgetShortcut)
|
125
|
-
self.keyGoDown.activated.connect(
|
124
|
+
self.keyGoDown.activated.connect(qtLambda(self.projTree.moveToLevel, 1))
|
126
125
|
|
127
126
|
self.keyContext = QShortcut(self.projTree)
|
128
127
|
self.keyContext.setKey("Ctrl+.")
|
129
128
|
self.keyContext.setContext(Qt.ShortcutContext.WidgetShortcut)
|
130
|
-
self.keyContext.activated.connect(
|
129
|
+
self.keyContext.activated.connect(self.projTree.openContextMenu)
|
131
130
|
|
132
131
|
# Signals
|
133
132
|
self.selectedItemChanged.connect(self.projBar.treeSelectionChanged)
|
@@ -138,7 +137,6 @@ class GuiProjectView(QWidget):
|
|
138
137
|
self.emptyTrash = self.projTree.emptyTrash
|
139
138
|
self.requestDeleteItem = self.projTree.requestDeleteItem
|
140
139
|
self.getSelectedHandle = self.projTree.getSelectedHandle
|
141
|
-
self.changedSince = self.projTree.changedSince
|
142
140
|
|
143
141
|
return
|
144
142
|
|
@@ -211,6 +209,12 @@ class GuiProjectView(QWidget):
|
|
211
209
|
self.projTree.setSelectedHandle(tHandle, doScroll=doScroll)
|
212
210
|
return
|
213
211
|
|
212
|
+
@pyqtSlot(str)
|
213
|
+
def setActiveHandle(self, tHandle: str | None) -> None:
|
214
|
+
"""Highlight the active handle."""
|
215
|
+
self.projTree.setActiveHandle(tHandle)
|
216
|
+
return
|
217
|
+
|
214
218
|
@pyqtSlot(str)
|
215
219
|
def updateItemValues(self, tHandle: str) -> None:
|
216
220
|
"""Update tree item."""
|
@@ -286,38 +290,38 @@ class GuiProjectToolBar(QWidget):
|
|
286
290
|
# Move Buttons
|
287
291
|
self.tbMoveU = NIconToolButton(self, iSz)
|
288
292
|
self.tbMoveU.setToolTip("%s [Ctrl+Up]" % self.tr("Move Up"))
|
289
|
-
self.tbMoveU.clicked.connect(
|
293
|
+
self.tbMoveU.clicked.connect(qtLambda(self.projTree.moveTreeItem, -1))
|
290
294
|
|
291
295
|
self.tbMoveD = NIconToolButton(self, iSz)
|
292
296
|
self.tbMoveD.setToolTip("%s [Ctrl+Down]" % self.tr("Move Down"))
|
293
|
-
self.tbMoveD.clicked.connect(
|
297
|
+
self.tbMoveD.clicked.connect(qtLambda(self.projTree.moveTreeItem, 1))
|
294
298
|
|
295
299
|
# Add Item Menu
|
296
300
|
self.mAdd = QMenu(self)
|
297
301
|
|
298
302
|
self.aAddEmpty = self.mAdd.addAction(trConst(nwLabels.ITEM_DESCRIPTION["document"]))
|
299
303
|
self.aAddEmpty.triggered.connect(
|
300
|
-
|
304
|
+
qtLambda(self.projTree.newTreeItem, nwItemType.FILE, hLevel=0, isNote=False)
|
301
305
|
)
|
302
306
|
|
303
307
|
self.aAddChap = self.mAdd.addAction(trConst(nwLabels.ITEM_DESCRIPTION["doc_h2"]))
|
304
308
|
self.aAddChap.triggered.connect(
|
305
|
-
|
309
|
+
qtLambda(self.projTree.newTreeItem, nwItemType.FILE, hLevel=2, isNote=False)
|
306
310
|
)
|
307
311
|
|
308
312
|
self.aAddScene = self.mAdd.addAction(trConst(nwLabels.ITEM_DESCRIPTION["doc_h3"]))
|
309
313
|
self.aAddScene.triggered.connect(
|
310
|
-
|
314
|
+
qtLambda(self.projTree.newTreeItem, nwItemType.FILE, hLevel=3, isNote=False)
|
311
315
|
)
|
312
316
|
|
313
317
|
self.aAddNote = self.mAdd.addAction(trConst(nwLabels.ITEM_DESCRIPTION["note"]))
|
314
318
|
self.aAddNote.triggered.connect(
|
315
|
-
|
319
|
+
qtLambda(self.projTree.newTreeItem, nwItemType.FILE, hLevel=1, isNote=True)
|
316
320
|
)
|
317
321
|
|
318
322
|
self.aAddFolder = self.mAdd.addAction(trConst(nwLabels.ITEM_DESCRIPTION["folder"]))
|
319
323
|
self.aAddFolder.triggered.connect(
|
320
|
-
|
324
|
+
qtLambda(self.projTree.newTreeItem, nwItemType.FOLDER)
|
321
325
|
)
|
322
326
|
|
323
327
|
self.mTemplates = _UpdatableMenu(self.mAdd)
|
@@ -337,13 +341,17 @@ class GuiProjectToolBar(QWidget):
|
|
337
341
|
self.mMore = QMenu(self)
|
338
342
|
|
339
343
|
self.aExpand = self.mMore.addAction(self.tr("Expand All"))
|
340
|
-
self.aExpand.triggered.connect(
|
344
|
+
self.aExpand.triggered.connect(
|
345
|
+
qtLambda(self.projTree.setExpandedFromHandle, None, True)
|
346
|
+
)
|
341
347
|
|
342
348
|
self.aCollapse = self.mMore.addAction(self.tr("Collapse All"))
|
343
|
-
self.aCollapse.triggered.connect(
|
349
|
+
self.aCollapse.triggered.connect(
|
350
|
+
qtLambda(self.projTree.setExpandedFromHandle, None, False)
|
351
|
+
)
|
344
352
|
|
345
353
|
self.aEmptyTrash = self.mMore.addAction(self.tr("Empty Trash"))
|
346
|
-
self.aEmptyTrash.triggered.connect(
|
354
|
+
self.aEmptyTrash.triggered.connect(qtLambda(self.projTree.emptyTrash))
|
347
355
|
|
348
356
|
self.tbMore = NIconToolButton(self, iSz)
|
349
357
|
self.tbMore.setToolTip(self.tr("More Options"))
|
@@ -416,7 +424,7 @@ class GuiProjectToolBar(QWidget):
|
|
416
424
|
action.setData(tHandle)
|
417
425
|
action.setIcon(SHARED.theme.getIcon(nwLabels.CLASS_ICON[nwItem.itemClass]))
|
418
426
|
action.triggered.connect(
|
419
|
-
|
427
|
+
qtLambda(self.projView.setSelectedHandle, tHandle, doScroll=True)
|
420
428
|
)
|
421
429
|
return
|
422
430
|
|
@@ -455,7 +463,9 @@ class GuiProjectToolBar(QWidget):
|
|
455
463
|
def addClass(itemClass: nwItemClass) -> None:
|
456
464
|
aNew = self.mAddRoot.addAction(trConst(nwLabels.CLASS_NAME[itemClass]))
|
457
465
|
aNew.setIcon(SHARED.theme.getIcon(nwLabels.CLASS_ICON[itemClass]))
|
458
|
-
aNew.triggered.connect(
|
466
|
+
aNew.triggered.connect(
|
467
|
+
qtLambda(self.projTree.newTreeItem, nwItemType.ROOT, itemClass)
|
468
|
+
)
|
459
469
|
self.mAddRoot.addAction(aNew)
|
460
470
|
return
|
461
471
|
|
@@ -498,8 +508,8 @@ class GuiProjectTree(QTreeWidget):
|
|
498
508
|
|
499
509
|
# Internal Variables
|
500
510
|
self._treeMap: dict[str, QTreeWidgetItem] = {}
|
501
|
-
self._timeChanged = 0.0
|
502
511
|
self._popAlert = None
|
512
|
+
self._actHandle = None
|
503
513
|
|
504
514
|
# Cached Translations
|
505
515
|
self.trActive = self.tr("Active")
|
@@ -511,7 +521,7 @@ class GuiProjectTree(QTreeWidget):
|
|
511
521
|
|
512
522
|
# Context Menu
|
513
523
|
self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
|
514
|
-
self.customContextMenuRequested.connect(self.
|
524
|
+
self.customContextMenuRequested.connect(self.openContextMenu)
|
515
525
|
|
516
526
|
# Tree Settings
|
517
527
|
iPx = SHARED.theme.baseIconHeight
|
@@ -578,13 +588,13 @@ class GuiProjectTree(QTreeWidget):
|
|
578
588
|
"""Set or update tree widget settings."""
|
579
589
|
# Scroll bars
|
580
590
|
if CONFIG.hideVScroll:
|
581
|
-
self.setVerticalScrollBarPolicy(
|
591
|
+
self.setVerticalScrollBarPolicy(QtScrollAlwaysOff)
|
582
592
|
else:
|
583
|
-
self.setVerticalScrollBarPolicy(
|
593
|
+
self.setVerticalScrollBarPolicy(QtScrollAsNeeded)
|
584
594
|
if CONFIG.hideHScroll:
|
585
|
-
self.setHorizontalScrollBarPolicy(
|
595
|
+
self.setHorizontalScrollBarPolicy(QtScrollAlwaysOff)
|
586
596
|
else:
|
587
|
-
self.setHorizontalScrollBarPolicy(
|
597
|
+
self.setHorizontalScrollBarPolicy(QtScrollAsNeeded)
|
588
598
|
return
|
589
599
|
|
590
600
|
##
|
@@ -595,7 +605,6 @@ class GuiProjectTree(QTreeWidget):
|
|
595
605
|
"""Clear the GUI content and the related map."""
|
596
606
|
self.clear()
|
597
607
|
self._treeMap = {}
|
598
|
-
self._timeChanged = 0.0
|
599
608
|
return
|
600
609
|
|
601
610
|
def createNewNote(self, tag: str, itemClass: nwItemClass) -> None:
|
@@ -642,7 +651,7 @@ class GuiProjectTree(QTreeWidget):
|
|
642
651
|
|
643
652
|
# Collect some information about the selected item
|
644
653
|
qItem = self._getTreeItem(sHandle)
|
645
|
-
sLevel =
|
654
|
+
sLevel = nwStyles.H_LEVEL.get(pItem.mainHeading, 0)
|
646
655
|
sIsParent = False if qItem is None else qItem.childCount() > 0
|
647
656
|
|
648
657
|
if SHARED.project.tree.isTrash(sHandle):
|
@@ -1114,12 +1123,10 @@ class GuiProjectTree(QTreeWidget):
|
|
1114
1123
|
"""
|
1115
1124
|
logger.debug("Building the project tree ...")
|
1116
1125
|
self.clearTree()
|
1117
|
-
count = 0
|
1118
1126
|
for nwItem in SHARED.project.iterProjectItems():
|
1119
|
-
count += 1
|
1120
1127
|
self._addTreeItem(nwItem)
|
1121
|
-
|
1122
|
-
|
1128
|
+
self.setActiveHandle(self._actHandle)
|
1129
|
+
logger.info("%d item(s) added to the project tree", len(self._treeMap))
|
1123
1130
|
return
|
1124
1131
|
|
1125
1132
|
def getSelectedHandle(self) -> str | None:
|
@@ -1130,38 +1137,78 @@ class GuiProjectTree(QTreeWidget):
|
|
1130
1137
|
return items[0].data(self.C_DATA, self.D_HANDLE)
|
1131
1138
|
return None
|
1132
1139
|
|
1133
|
-
def setSelectedHandle(self, tHandle: str | None, doScroll: bool = False) ->
|
1140
|
+
def setSelectedHandle(self, tHandle: str | None, doScroll: bool = False) -> None:
|
1134
1141
|
"""Set a specific handle as the selected item."""
|
1135
|
-
tItem = self._getTreeItem(tHandle)
|
1136
|
-
if tItem is None:
|
1137
|
-
return False
|
1138
|
-
|
1139
1142
|
if tHandle in self._treeMap:
|
1140
1143
|
self.setCurrentItem(self._treeMap[tHandle])
|
1144
|
+
if (indexes := self.selectedIndexes()) and doScroll:
|
1145
|
+
self.scrollTo(indexes[0], QAbstractItemView.ScrollHint.PositionAtCenter)
|
1146
|
+
return
|
1141
1147
|
|
1142
|
-
|
1143
|
-
|
1144
|
-
|
1145
|
-
|
1148
|
+
def setActiveHandle(self, tHandle: str | None) -> None:
|
1149
|
+
"""Highlight the rows associated with a given handle."""
|
1150
|
+
brushOn = self.palette().alternateBase()
|
1151
|
+
brushOff = self.palette().base()
|
1152
|
+
if (pHandle := self._actHandle) and (item := self._treeMap.get(pHandle)):
|
1153
|
+
for i in range(self.columnCount()):
|
1154
|
+
item.setBackground(i, brushOff)
|
1155
|
+
if tHandle and (item := self._treeMap.get(tHandle)):
|
1156
|
+
for i in range(self.columnCount()):
|
1157
|
+
item.setBackground(i, brushOn)
|
1158
|
+
self._actHandle = tHandle or None
|
1159
|
+
return
|
1146
1160
|
|
1147
1161
|
def setExpandedFromHandle(self, tHandle: str | None, isExpanded: bool) -> None:
|
1148
1162
|
"""Iterate through items below tHandle and change expanded
|
1149
1163
|
status for all child items. If tHandle is None, it affects the
|
1150
1164
|
entire tree.
|
1151
1165
|
"""
|
1152
|
-
|
1153
|
-
self._recursiveSetExpanded(
|
1166
|
+
item = self._getTreeItem(tHandle) or self.invisibleRootItem()
|
1167
|
+
self._recursiveSetExpanded(item, isExpanded)
|
1154
1168
|
return
|
1155
1169
|
|
1156
|
-
|
1157
|
-
|
1158
|
-
|
1159
|
-
|
1160
|
-
|
1170
|
+
##
|
1171
|
+
# Public Slots
|
1172
|
+
##
|
1173
|
+
|
1174
|
+
@pyqtSlot()
|
1175
|
+
@pyqtSlot("QPoint")
|
1176
|
+
def openContextMenu(self, clickPos: QPoint | None = None) -> None:
|
1177
|
+
"""The user right clicked an element in the project tree, so we
|
1178
|
+
open a context menu in-place.
|
1179
|
+
"""
|
1180
|
+
if clickPos is None and (items := self.selectedItems()):
|
1181
|
+
clickPos = self.visualItemRect(items[0]).center()
|
1182
|
+
|
1183
|
+
if clickPos is not None:
|
1184
|
+
tItem = None
|
1185
|
+
tHandle = None
|
1186
|
+
hasChild = False
|
1187
|
+
sItem = self.itemAt(clickPos)
|
1188
|
+
sItems = self.selectedItems()
|
1189
|
+
if isinstance(sItem, QTreeWidgetItem):
|
1190
|
+
tHandle = sItem.data(self.C_DATA, self.D_HANDLE)
|
1191
|
+
tItem = SHARED.project.tree[tHandle]
|
1192
|
+
hasChild = sItem.childCount() > 0
|
1193
|
+
|
1194
|
+
if tItem is None or tHandle is None:
|
1195
|
+
logger.debug("No item found")
|
1196
|
+
return
|
1197
|
+
|
1198
|
+
ctxMenu = _TreeContextMenu(self, tItem)
|
1199
|
+
trashHandle = SHARED.project.tree.trashRoot
|
1200
|
+
if trashHandle and tHandle == trashHandle:
|
1201
|
+
ctxMenu.buildTrashMenu()
|
1202
|
+
elif len(sItems) > 1:
|
1203
|
+
handles = [str(x.data(self.C_DATA, self.D_HANDLE)) for x in sItems]
|
1204
|
+
ctxMenu.buildMultiSelectMenu(handles)
|
1205
|
+
else:
|
1206
|
+
ctxMenu.buildSingleSelectMenu(hasChild)
|
1161
1207
|
|
1162
|
-
|
1163
|
-
|
1164
|
-
|
1208
|
+
ctxMenu.exec(self.viewport().mapToGlobal(clickPos))
|
1209
|
+
ctxMenu.deleteLater()
|
1210
|
+
|
1211
|
+
return
|
1165
1212
|
|
1166
1213
|
##
|
1167
1214
|
# Private Slots
|
@@ -1204,40 +1251,6 @@ class GuiProjectTree(QTreeWidget):
|
|
1204
1251
|
|
1205
1252
|
return
|
1206
1253
|
|
1207
|
-
@pyqtSlot("QPoint")
|
1208
|
-
def _openContextMenu(self, clickPos: QPoint) -> bool:
|
1209
|
-
"""The user right clicked an element in the project tree, so we
|
1210
|
-
open a context menu in-place.
|
1211
|
-
"""
|
1212
|
-
tItem = None
|
1213
|
-
tHandle = None
|
1214
|
-
hasChild = False
|
1215
|
-
sItem = self.itemAt(clickPos)
|
1216
|
-
sItems = self.selectedItems()
|
1217
|
-
if isinstance(sItem, QTreeWidgetItem):
|
1218
|
-
tHandle = sItem.data(self.C_DATA, self.D_HANDLE)
|
1219
|
-
tItem = SHARED.project.tree[tHandle]
|
1220
|
-
hasChild = sItem.childCount() > 0
|
1221
|
-
|
1222
|
-
if tItem is None or tHandle is None:
|
1223
|
-
logger.debug("No item found")
|
1224
|
-
return False
|
1225
|
-
|
1226
|
-
ctxMenu = _TreeContextMenu(self, tItem)
|
1227
|
-
trashHandle = SHARED.project.tree.trashRoot
|
1228
|
-
if trashHandle and tHandle == trashHandle:
|
1229
|
-
ctxMenu.buildTrashMenu()
|
1230
|
-
elif len(sItems) > 1:
|
1231
|
-
handles = [str(x.data(self.C_DATA, self.D_HANDLE)) for x in sItems]
|
1232
|
-
ctxMenu.buildMultiSelectMenu(handles)
|
1233
|
-
else:
|
1234
|
-
ctxMenu.buildSingleSelectMenu(hasChild)
|
1235
|
-
|
1236
|
-
ctxMenu.exec(self.viewport().mapToGlobal(clickPos))
|
1237
|
-
ctxMenu.deleteLater()
|
1238
|
-
|
1239
|
-
return True
|
1240
|
-
|
1241
1254
|
@pyqtSlot()
|
1242
1255
|
def _doAutoScroll(self) -> None:
|
1243
1256
|
"""Scroll one item up or down based on direction value."""
|
@@ -1377,11 +1390,6 @@ class GuiProjectTree(QTreeWidget):
|
|
1377
1390
|
|
1378
1391
|
return
|
1379
1392
|
|
1380
|
-
def _getItemWordCount(self, tHandle: str) -> int:
|
1381
|
-
"""Return the word count of a given item handle."""
|
1382
|
-
tItem = self._getTreeItem(tHandle)
|
1383
|
-
return int(tItem.data(self.C_DATA, self.D_WORDS)) if tItem else 0
|
1384
|
-
|
1385
1393
|
def _getTreeItem(self, tHandle: str | None) -> QTreeWidgetItem | None:
|
1386
1394
|
"""Return the QTreeWidgetItem of a given item handle."""
|
1387
1395
|
return self._treeMap.get(tHandle, None) if tHandle else None
|
@@ -1638,7 +1646,6 @@ class GuiProjectTree(QTreeWidget):
|
|
1638
1646
|
signals. A flush is only needed if an item is moved, created or
|
1639
1647
|
deleted.
|
1640
1648
|
"""
|
1641
|
-
self._timeChanged = time()
|
1642
1649
|
SHARED.project.setProjectChanged(True)
|
1643
1650
|
if flush:
|
1644
1651
|
self.saveTreeOrder()
|
@@ -1765,7 +1772,7 @@ class _TreeContextMenu(QMenu):
|
|
1765
1772
|
|
1766
1773
|
# Edit Item Settings
|
1767
1774
|
action = self.addAction(self.tr("Rename"))
|
1768
|
-
action.triggered.connect(
|
1775
|
+
action.triggered.connect(qtLambda(self.projTree.renameTreeItem, self._handle))
|
1769
1776
|
if isFile:
|
1770
1777
|
self._itemHeader()
|
1771
1778
|
self._itemActive(False)
|
@@ -1801,13 +1808,15 @@ class _TreeContextMenu(QMenu):
|
|
1801
1808
|
def _docActions(self) -> None:
|
1802
1809
|
"""Add document actions."""
|
1803
1810
|
action = self.addAction(self.tr("Open Document"))
|
1804
|
-
action.triggered.connect(
|
1805
|
-
|
1806
|
-
|
1811
|
+
action.triggered.connect(qtLambda(
|
1812
|
+
self.projView.openDocumentRequest.emit,
|
1813
|
+
self._handle, nwDocMode.EDIT, "", True
|
1814
|
+
))
|
1807
1815
|
action = self.addAction(self.tr("View Document"))
|
1808
|
-
action.triggered.connect(
|
1809
|
-
|
1810
|
-
|
1816
|
+
action.triggered.connect(qtLambda(
|
1817
|
+
self.projView.openDocumentRequest.emit,
|
1818
|
+
self._handle, nwDocMode.VIEW, "", False
|
1819
|
+
))
|
1811
1820
|
return
|
1812
1821
|
|
1813
1822
|
def _itemCreation(self) -> None:
|
@@ -1826,7 +1835,7 @@ class _TreeContextMenu(QMenu):
|
|
1826
1835
|
if hItem := SHARED.project.index.getItemHeading(self._handle, "T0001"):
|
1827
1836
|
action = self.addAction(self.tr("Rename to Heading"))
|
1828
1837
|
action.triggered.connect(
|
1829
|
-
|
1838
|
+
qtLambda(self.projTree.renameTreeItem, self._handle, hItem.title)
|
1830
1839
|
)
|
1831
1840
|
return
|
1832
1841
|
|
@@ -1835,9 +1844,9 @@ class _TreeContextMenu(QMenu):
|
|
1835
1844
|
if multi:
|
1836
1845
|
mSub = self.addMenu(self.tr("Set Active to ..."))
|
1837
1846
|
aOne = mSub.addAction(SHARED.theme.getIcon("checked"), self.projTree.trActive)
|
1838
|
-
aOne.triggered.connect(
|
1847
|
+
aOne.triggered.connect(qtLambda(self._iterItemActive, True))
|
1839
1848
|
aTwo = mSub.addAction(SHARED.theme.getIcon("unchecked"), self.projTree.trInactive)
|
1840
|
-
aTwo.triggered.connect(
|
1849
|
+
aTwo.triggered.connect(qtLambda(self._iterItemActive, False))
|
1841
1850
|
else:
|
1842
1851
|
action = self.addAction(self.tr("Toggle Active"))
|
1843
1852
|
action.triggered.connect(self._toggleItemActive)
|
@@ -1848,37 +1857,39 @@ class _TreeContextMenu(QMenu):
|
|
1848
1857
|
if self._item.isNovelLike():
|
1849
1858
|
menu = self.addMenu(self.tr("Set Status to ..."))
|
1850
1859
|
current = self._item.itemStatus
|
1851
|
-
for
|
1860
|
+
for key, entry in SHARED.project.data.itemStatus.iterItems():
|
1852
1861
|
name = entry.name
|
1853
1862
|
if not multi and current == key:
|
1854
1863
|
name += f" ({nwUnicode.U_CHECK})"
|
1855
1864
|
action = menu.addAction(entry.icon, name)
|
1856
1865
|
if multi:
|
1857
|
-
action.triggered.connect(
|
1866
|
+
action.triggered.connect(qtLambda(self._iterSetItemStatus, key))
|
1858
1867
|
else:
|
1859
|
-
action.triggered.connect(
|
1868
|
+
action.triggered.connect(qtLambda(self._changeItemStatus, key))
|
1860
1869
|
menu.addSeparator()
|
1861
1870
|
action = menu.addAction(self.tr("Manage Labels ..."))
|
1862
|
-
action.triggered.connect(
|
1863
|
-
|
1864
|
-
|
1871
|
+
action.triggered.connect(qtLambda(
|
1872
|
+
self.projView.projectSettingsRequest.emit,
|
1873
|
+
GuiProjectSettings.PAGE_STATUS
|
1874
|
+
))
|
1865
1875
|
else:
|
1866
1876
|
menu = self.addMenu(self.tr("Set Importance to ..."))
|
1867
1877
|
current = self._item.itemImport
|
1868
|
-
for
|
1878
|
+
for key, entry in SHARED.project.data.itemImport.iterItems():
|
1869
1879
|
name = entry.name
|
1870
1880
|
if not multi and current == key:
|
1871
1881
|
name += f" ({nwUnicode.U_CHECK})"
|
1872
1882
|
action = menu.addAction(entry.icon, name)
|
1873
1883
|
if multi:
|
1874
|
-
action.triggered.connect(
|
1884
|
+
action.triggered.connect(qtLambda(self._iterSetItemImport, key))
|
1875
1885
|
else:
|
1876
|
-
action.triggered.connect(
|
1886
|
+
action.triggered.connect(qtLambda(self._changeItemImport, key))
|
1877
1887
|
menu.addSeparator()
|
1878
1888
|
action = menu.addAction(self.tr("Manage Labels ..."))
|
1879
|
-
action.triggered.connect(
|
1880
|
-
|
1881
|
-
|
1889
|
+
action.triggered.connect(qtLambda(
|
1890
|
+
self.projView.projectSettingsRequest.emit,
|
1891
|
+
GuiProjectSettings.PAGE_IMPORT
|
1892
|
+
))
|
1882
1893
|
return
|
1883
1894
|
|
1884
1895
|
def _itemTransform(self, isFile: bool, isFolder: bool, hasChild: bool) -> None:
|
@@ -1897,33 +1908,33 @@ class _TreeContextMenu(QMenu):
|
|
1897
1908
|
|
1898
1909
|
if isNoteFile and self._item.documentAllowed():
|
1899
1910
|
action = menu.addAction(self.tr("Convert to {0}").format(trDoc))
|
1900
|
-
action.triggered.connect(
|
1911
|
+
action.triggered.connect(qtLambda(self._changeItemLayout, loDoc))
|
1901
1912
|
|
1902
1913
|
if isDocFile:
|
1903
1914
|
action = menu.addAction(self.tr("Convert to {0}").format(trNote))
|
1904
|
-
action.triggered.connect(
|
1915
|
+
action.triggered.connect(qtLambda(self._changeItemLayout, loNote))
|
1905
1916
|
|
1906
1917
|
if isFolder and self._item.documentAllowed():
|
1907
1918
|
action = menu.addAction(self.tr("Convert to {0}").format(trDoc))
|
1908
|
-
action.triggered.connect(
|
1919
|
+
action.triggered.connect(qtLambda(self._covertFolderToFile, loDoc))
|
1909
1920
|
|
1910
1921
|
if isFolder:
|
1911
1922
|
action = menu.addAction(self.tr("Convert to {0}").format(trNote))
|
1912
|
-
action.triggered.connect(
|
1923
|
+
action.triggered.connect(qtLambda(self._covertFolderToFile, loNote))
|
1913
1924
|
|
1914
1925
|
if hasChild and isFile:
|
1915
1926
|
action = menu.addAction(self.tr("Merge Child Items into Self"))
|
1916
|
-
action.triggered.connect(
|
1927
|
+
action.triggered.connect(qtLambda(tree._mergeDocuments, tHandle, False))
|
1917
1928
|
action = menu.addAction(self.tr("Merge Child Items into New"))
|
1918
|
-
action.triggered.connect(
|
1929
|
+
action.triggered.connect(qtLambda(tree._mergeDocuments, tHandle, True))
|
1919
1930
|
|
1920
1931
|
if hasChild and isFolder:
|
1921
1932
|
action = menu.addAction(self.tr("Merge Documents in Folder"))
|
1922
|
-
action.triggered.connect(
|
1933
|
+
action.triggered.connect(qtLambda(tree._mergeDocuments, tHandle, True))
|
1923
1934
|
|
1924
1935
|
if isFile:
|
1925
1936
|
action = menu.addAction(self.tr("Split Document by Headings"))
|
1926
|
-
action.triggered.connect(
|
1937
|
+
action.triggered.connect(qtLambda(tree._splitDocument, tHandle))
|
1927
1938
|
|
1928
1939
|
return
|
1929
1940
|
|
@@ -1933,19 +1944,19 @@ class _TreeContextMenu(QMenu):
|
|
1933
1944
|
tHandle = self._handle
|
1934
1945
|
if hasChild:
|
1935
1946
|
action = self.addAction(self.tr("Expand All"))
|
1936
|
-
action.triggered.connect(
|
1947
|
+
action.triggered.connect(qtLambda(tree.setExpandedFromHandle, tHandle, True))
|
1937
1948
|
action = self.addAction(self.tr("Collapse All"))
|
1938
|
-
action.triggered.connect(
|
1949
|
+
action.triggered.connect(qtLambda(tree.setExpandedFromHandle, tHandle, False))
|
1939
1950
|
|
1940
1951
|
action = self.addAction(self.tr("Duplicate"))
|
1941
|
-
action.triggered.connect(
|
1952
|
+
action.triggered.connect(qtLambda(tree._duplicateFromHandle, tHandle))
|
1942
1953
|
|
1943
1954
|
if self._item.itemClass == nwItemClass.TRASH or isRoot or (isFolder and not hasChild):
|
1944
1955
|
action = self.addAction(self.tr("Delete Permanently"))
|
1945
|
-
action.triggered.connect(
|
1956
|
+
action.triggered.connect(qtLambda(tree.permDeleteItem, tHandle))
|
1946
1957
|
else:
|
1947
1958
|
action = self.addAction(self.tr("Move to Trash"))
|
1948
|
-
action.triggered.connect(
|
1959
|
+
action.triggered.connect(qtLambda(tree.moveItemToTrash, tHandle))
|
1949
1960
|
|
1950
1961
|
return
|
1951
1962
|
|
novelwriter/gui/sidebar.py
CHANGED
@@ -32,6 +32,7 @@ from PyQt5.QtGui import QPalette
|
|
32
32
|
from PyQt5.QtWidgets import QMenu, QVBoxLayout, QWidget
|
33
33
|
|
34
34
|
from novelwriter import CONFIG, SHARED
|
35
|
+
from novelwriter.common import qtLambda
|
35
36
|
from novelwriter.enum import nwView
|
36
37
|
from novelwriter.extensions.eventfilters import StatusTipFilter
|
37
38
|
from novelwriter.extensions.modified import NIconToolButton
|
@@ -54,7 +55,7 @@ class GuiSideBar(QWidget):
|
|
54
55
|
|
55
56
|
self.mainGui = mainGui
|
56
57
|
|
57
|
-
iPx = int(1.
|
58
|
+
iPx = int(1.25*SHARED.theme.baseButtonHeight)
|
58
59
|
iSz = QSize(iPx, iPx)
|
59
60
|
|
60
61
|
self.setContentsMargins(0, 0, 0, 0)
|
@@ -63,19 +64,19 @@ class GuiSideBar(QWidget):
|
|
63
64
|
# Buttons
|
64
65
|
self.tbProject = NIconToolButton(self, iSz)
|
65
66
|
self.tbProject.setToolTip("{0} [Ctrl+T]".format(self.tr("Project Tree View")))
|
66
|
-
self.tbProject.clicked.connect(
|
67
|
+
self.tbProject.clicked.connect(qtLambda(self.requestViewChange.emit, nwView.PROJECT))
|
67
68
|
|
68
69
|
self.tbNovel = NIconToolButton(self, iSz)
|
69
70
|
self.tbNovel.setToolTip("{0} [Ctrl+T]".format(self.tr("Novel Tree View")))
|
70
|
-
self.tbNovel.clicked.connect(
|
71
|
+
self.tbNovel.clicked.connect(qtLambda(self.requestViewChange.emit, nwView.NOVEL))
|
71
72
|
|
72
73
|
self.tbSearch = NIconToolButton(self, iSz)
|
73
74
|
self.tbSearch.setToolTip("{0} [Ctrl+Shift+F]".format(self.tr("Project Search")))
|
74
|
-
self.tbSearch.clicked.connect(
|
75
|
+
self.tbSearch.clicked.connect(qtLambda(self.requestViewChange.emit, nwView.SEARCH))
|
75
76
|
|
76
77
|
self.tbOutline = NIconToolButton(self, iSz)
|
77
78
|
self.tbOutline.setToolTip("{0} [Ctrl+Shift+T]".format(self.tr("Novel Outline View")))
|
78
|
-
self.tbOutline.clicked.connect(
|
79
|
+
self.tbOutline.clicked.connect(qtLambda(self.requestViewChange.emit, nwView.OUTLINE))
|
79
80
|
|
80
81
|
self.tbBuild = NIconToolButton(self, iSz)
|
81
82
|
self.tbBuild.setToolTip("{0} [F5]".format(self.tr("Build Manuscript")))
|
@@ -113,7 +114,7 @@ class GuiSideBar(QWidget):
|
|
113
114
|
self.outerBox.addWidget(self.tbStats)
|
114
115
|
self.outerBox.addWidget(self.tbSettings)
|
115
116
|
self.outerBox.setContentsMargins(0, 0, 0, 0)
|
116
|
-
self.outerBox.setSpacing(CONFIG.pxInt(
|
117
|
+
self.outerBox.setSpacing(CONFIG.pxInt(6))
|
117
118
|
|
118
119
|
self.setLayout(self.outerBox)
|
119
120
|
self.updateTheme()
|
novelwriter/gui/theme.py
CHANGED
@@ -503,10 +503,11 @@ class GuiIcons:
|
|
503
503
|
"fmt_strike-md", "fmt_subscript", "fmt_superscript", "fmt_underline",
|
504
504
|
|
505
505
|
# General Button Icons
|
506
|
-
"add", "add_document", "backward", "bookmark", "browse", "checked", "close", "
|
507
|
-
"document", "down", "edit", "export", "font", "forward", "import", "list",
|
508
|
-
"menu", "minimise", "more", "noncheckable", "open", "panel", "quote",
|
509
|
-
"
|
506
|
+
"add", "add_document", "backward", "bookmark", "browse", "checked", "close", "copy",
|
507
|
+
"cross", "document", "down", "edit", "export", "font", "forward", "import", "list",
|
508
|
+
"maximise", "menu", "minimise", "more", "noncheckable", "open", "panel", "quote",
|
509
|
+
"refresh", "remove", "revert", "search_replace", "search", "settings", "star", "unchecked",
|
510
|
+
"up", "view",
|
510
511
|
|
511
512
|
# Switches
|
512
513
|
"sticky-on", "sticky-off",
|