novelWriter 2.2b1__py3-none-any.whl → 2.2.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.2b1.dist-info → novelWriter-2.2.1.dist-info}/METADATA +3 -3
- {novelWriter-2.2b1.dist-info → novelWriter-2.2.1.dist-info}/RECORD +128 -114
- {novelWriter-2.2b1.dist-info → novelWriter-2.2.1.dist-info}/WHEEL +1 -1
- novelwriter/__init__.py +10 -5
- 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_zh_CN.qm +0 -0
- novelwriter/assets/i18n/project_de_DE.json +1 -0
- novelwriter/assets/i18n/project_en_GB.json +1 -0
- novelwriter/assets/i18n/project_en_US.json +1 -0
- novelwriter/assets/i18n/project_es_419.json +11 -0
- novelwriter/assets/i18n/project_fr_FR.json +11 -0
- novelwriter/assets/i18n/project_it_IT.json +11 -0
- novelwriter/assets/i18n/project_ja_JP.json +2 -1
- novelwriter/assets/i18n/project_nb_NO.json +1 -0
- novelwriter/assets/i18n/project_zh_CN.json +11 -0
- novelwriter/assets/icons/novelwriter.ico +0 -0
- novelwriter/assets/icons/typicons_dark/icons.conf +11 -3
- novelwriter/assets/icons/typicons_dark/nw_deco-h2-narrow.svg +4 -0
- novelwriter/assets/icons/typicons_dark/nw_deco-h3-narrow.svg +4 -0
- novelwriter/assets/icons/typicons_dark/nw_deco-h4-narrow.svg +4 -0
- novelwriter/assets/icons/typicons_dark/nw_deco-note.svg +4 -0
- novelwriter/assets/icons/typicons_dark/nw_panel.svg +4 -0
- novelwriter/assets/icons/typicons_dark/nw_tb-bold-md.svg +4 -0
- novelwriter/assets/icons/typicons_dark/nw_tb-bold.svg +3 -1
- novelwriter/assets/icons/typicons_dark/nw_tb-italic-md.svg +4 -0
- novelwriter/assets/icons/typicons_dark/nw_tb-italic.svg +3 -1
- novelwriter/assets/icons/typicons_dark/nw_tb-strike-md.svg +4 -0
- novelwriter/assets/icons/typicons_dark/nw_tb-strike.svg +3 -1
- novelwriter/assets/icons/typicons_dark/nw_tb-subscript.svg +4 -2
- novelwriter/assets/icons/typicons_dark/nw_tb-superscript.svg +4 -2
- novelwriter/assets/icons/typicons_dark/nw_tb-underline.svg +4 -2
- novelwriter/assets/icons/typicons_dark/typ_eye.svg +4 -0
- novelwriter/assets/icons/typicons_light/icons.conf +11 -3
- novelwriter/assets/icons/typicons_light/nw_deco-h2-narrow.svg +4 -0
- novelwriter/assets/icons/typicons_light/nw_deco-h3-narrow.svg +4 -0
- novelwriter/assets/icons/typicons_light/nw_deco-h4-narrow.svg +4 -0
- novelwriter/assets/icons/typicons_light/nw_deco-note.svg +4 -0
- novelwriter/assets/icons/typicons_light/nw_panel.svg +4 -0
- novelwriter/assets/icons/typicons_light/nw_tb-bold-md.svg +4 -0
- novelwriter/assets/icons/typicons_light/nw_tb-bold.svg +3 -1
- novelwriter/assets/icons/typicons_light/nw_tb-italic-md.svg +4 -0
- novelwriter/assets/icons/typicons_light/nw_tb-italic.svg +3 -1
- novelwriter/assets/icons/typicons_light/nw_tb-strike-md.svg +4 -0
- novelwriter/assets/icons/typicons_light/nw_tb-strike.svg +3 -1
- novelwriter/assets/icons/typicons_light/nw_tb-subscript.svg +4 -2
- novelwriter/assets/icons/typicons_light/nw_tb-superscript.svg +4 -2
- novelwriter/assets/icons/typicons_light/nw_tb-underline.svg +4 -2
- novelwriter/assets/icons/typicons_light/typ_eye.svg +4 -0
- novelwriter/assets/icons/x-novelwriter-project.ico +0 -0
- novelwriter/assets/manual.pdf +0 -0
- novelwriter/assets/sample.zip +0 -0
- novelwriter/assets/text/release_notes.htm +50 -7
- novelwriter/common.py +35 -27
- novelwriter/config.py +13 -28
- novelwriter/constants.py +21 -4
- novelwriter/core/buildsettings.py +2 -2
- novelwriter/core/coretools.py +8 -2
- novelwriter/core/docbuild.py +1 -1
- novelwriter/core/document.py +1 -1
- novelwriter/core/index.py +102 -36
- novelwriter/core/item.py +2 -2
- novelwriter/core/options.py +6 -3
- novelwriter/core/project.py +5 -5
- novelwriter/core/projectdata.py +3 -3
- novelwriter/core/projectxml.py +1 -1
- novelwriter/core/sessions.py +2 -2
- novelwriter/core/spellcheck.py +4 -3
- novelwriter/core/status.py +3 -3
- novelwriter/core/storage.py +1 -1
- novelwriter/core/tohtml.py +11 -5
- novelwriter/core/tokenizer.py +28 -21
- novelwriter/core/tomd.py +6 -2
- novelwriter/core/toodt.py +12 -5
- novelwriter/core/tree.py +2 -2
- novelwriter/dialogs/about.py +30 -31
- novelwriter/dialogs/docmerge.py +24 -15
- novelwriter/dialogs/docsplit.py +27 -16
- novelwriter/dialogs/editlabel.py +19 -7
- novelwriter/dialogs/preferences.py +116 -131
- novelwriter/dialogs/projdetails.py +29 -36
- novelwriter/dialogs/projload.py +32 -36
- novelwriter/dialogs/projsettings.py +20 -15
- novelwriter/dialogs/quotes.py +32 -25
- novelwriter/dialogs/updates.py +17 -16
- novelwriter/dialogs/wordlist.py +34 -21
- novelwriter/enum.py +19 -8
- novelwriter/error.py +1 -1
- novelwriter/extensions/circularprogress.py +1 -1
- novelwriter/extensions/configlayout.py +3 -15
- novelwriter/extensions/{wheeleventfilter.py → eventfilters.py} +15 -5
- novelwriter/extensions/novelselector.py +1 -1
- novelwriter/extensions/pageddialog.py +1 -1
- novelwriter/extensions/pagedsidebar.py +2 -5
- novelwriter/extensions/simpleprogress.py +8 -9
- novelwriter/extensions/statusled.py +1 -1
- novelwriter/extensions/switch.py +4 -4
- novelwriter/extensions/switchbox.py +1 -6
- novelwriter/gui/doceditor.py +349 -236
- novelwriter/gui/dochighlight.py +10 -11
- novelwriter/gui/docviewer.py +158 -360
- novelwriter/gui/docviewerpanel.py +502 -0
- novelwriter/gui/editordocument.py +4 -4
- novelwriter/gui/itemdetails.py +2 -2
- novelwriter/gui/mainmenu.py +50 -36
- novelwriter/gui/noveltree.py +44 -53
- novelwriter/gui/outline.py +12 -7
- novelwriter/gui/projtree.py +465 -381
- novelwriter/gui/sidebar.py +9 -7
- novelwriter/gui/statusbar.py +48 -5
- novelwriter/gui/theme.py +26 -8
- novelwriter/guimain.py +212 -208
- novelwriter/shared.py +76 -30
- novelwriter/tools/dictionaries.py +268 -0
- novelwriter/tools/lipsum.py +34 -28
- novelwriter/tools/manusbuild.py +20 -10
- novelwriter/tools/manuscript.py +20 -27
- novelwriter/tools/manussettings.py +2 -4
- novelwriter/tools/projwizard.py +3 -3
- novelwriter/tools/writingstats.py +18 -5
- novelwriter/assets/icons/typicons_dark/nw_tb-markdown.svg +0 -8
- novelwriter/assets/icons/typicons_dark/nw_tb-shortcode.svg +0 -8
- novelwriter/assets/icons/typicons_dark/typ_at.svg +0 -4
- novelwriter/assets/icons/typicons_light/nw_tb-markdown.svg +0 -8
- novelwriter/assets/icons/typicons_light/nw_tb-shortcode.svg +0 -8
- novelwriter/assets/icons/typicons_light/typ_at.svg +0 -4
- {novelWriter-2.2b1.dist-info → novelWriter-2.2.1.dist-info}/LICENSE.md +0 -0
- {novelWriter-2.2b1.dist-info → novelWriter-2.2.1.dist-info}/entry_points.txt +0 -0
- {novelWriter-2.2b1.dist-info → novelWriter-2.2.1.dist-info}/top_level.txt +0 -0
novelwriter/gui/projtree.py
CHANGED
@@ -6,9 +6,10 @@ File History:
|
|
6
6
|
Created: 2018-09-29 [0.0.1] GuiProjectTree
|
7
7
|
Created: 2022-06-06 [2.0rc1] GuiProjectView
|
8
8
|
Created: 2022-06-06 [2.0rc1] GuiProjectToolBar
|
9
|
+
Created: 2023-11-22 [2.2rc1] _TreeContextMenu
|
9
10
|
|
10
11
|
This file is a part of novelWriter
|
11
|
-
Copyright 2018–
|
12
|
+
Copyright 2018–2024, Veronica Berglyd Olsen
|
12
13
|
|
13
14
|
This program is free software: you can redistribute it and/or modify
|
14
15
|
it under the terms of the GNU General Public License as published by
|
@@ -31,7 +32,7 @@ from enum import Enum
|
|
31
32
|
from time import time
|
32
33
|
from typing import TYPE_CHECKING
|
33
34
|
|
34
|
-
from PyQt5.QtGui import QDragMoveEvent, QDropEvent, QMouseEvent, QPalette
|
35
|
+
from PyQt5.QtGui import QDragEnterEvent, QDragMoveEvent, QDropEvent, QMouseEvent, QPalette
|
35
36
|
from PyQt5.QtCore import QPoint, QTimer, Qt, QSize, pyqtSignal, pyqtSlot
|
36
37
|
from PyQt5.QtWidgets import (
|
37
38
|
QAbstractItemView, QDialog, QFrame, QHBoxLayout, QHeaderView, QLabel,
|
@@ -40,6 +41,7 @@ from PyQt5.QtWidgets import (
|
|
40
41
|
)
|
41
42
|
|
42
43
|
from novelwriter import CONFIG, SHARED
|
44
|
+
from novelwriter.enum import nwDocMode, nwItemType, nwItemClass, nwItemLayout
|
43
45
|
from novelwriter.common import minmax
|
44
46
|
from novelwriter.constants import nwHeaders, nwUnicode, trConst, nwLabels
|
45
47
|
from novelwriter.core.item import NWItem
|
@@ -48,9 +50,6 @@ from novelwriter.dialogs.docmerge import GuiDocMerge
|
|
48
50
|
from novelwriter.dialogs.docsplit import GuiDocSplit
|
49
51
|
from novelwriter.dialogs.editlabel import GuiEditLabel
|
50
52
|
from novelwriter.dialogs.projsettings import GuiProjectSettings
|
51
|
-
from novelwriter.enum import (
|
52
|
-
nwDocMode, nwItemType, nwItemClass, nwItemLayout, nwWidget
|
53
|
-
)
|
54
53
|
|
55
54
|
if TYPE_CHECKING: # pragma: no cover
|
56
55
|
from novelwriter.guimain import GuiMain
|
@@ -98,42 +97,37 @@ class GuiProjectView(QWidget):
|
|
98
97
|
# Keyboard Shortcuts
|
99
98
|
self.keyMoveUp = QShortcut(self.projTree)
|
100
99
|
self.keyMoveUp.setKey("Ctrl+Up")
|
101
|
-
self.keyMoveUp.setContext(Qt.WidgetShortcut)
|
100
|
+
self.keyMoveUp.setContext(Qt.ShortcutContext.WidgetShortcut)
|
102
101
|
self.keyMoveUp.activated.connect(lambda: self.projTree.moveTreeItem(-1))
|
103
102
|
|
104
103
|
self.keyMoveDn = QShortcut(self.projTree)
|
105
104
|
self.keyMoveDn.setKey("Ctrl+Down")
|
106
|
-
self.keyMoveDn.setContext(Qt.WidgetShortcut)
|
105
|
+
self.keyMoveDn.setContext(Qt.ShortcutContext.WidgetShortcut)
|
107
106
|
self.keyMoveDn.activated.connect(lambda: self.projTree.moveTreeItem(1))
|
108
107
|
|
109
108
|
self.keyGoPrev = QShortcut(self.projTree)
|
110
109
|
self.keyGoPrev.setKey("Alt+Up")
|
111
|
-
self.keyGoPrev.setContext(Qt.WidgetShortcut)
|
110
|
+
self.keyGoPrev.setContext(Qt.ShortcutContext.WidgetShortcut)
|
112
111
|
self.keyGoPrev.activated.connect(lambda: self.projTree.moveToNextItem(-1))
|
113
112
|
|
114
113
|
self.keyGoNext = QShortcut(self.projTree)
|
115
114
|
self.keyGoNext.setKey("Alt+Down")
|
116
|
-
self.keyGoNext.setContext(Qt.WidgetShortcut)
|
115
|
+
self.keyGoNext.setContext(Qt.ShortcutContext.WidgetShortcut)
|
117
116
|
self.keyGoNext.activated.connect(lambda: self.projTree.moveToNextItem(1))
|
118
117
|
|
119
118
|
self.keyGoUp = QShortcut(self.projTree)
|
120
119
|
self.keyGoUp.setKey("Alt+Left")
|
121
|
-
self.keyGoUp.setContext(Qt.WidgetShortcut)
|
120
|
+
self.keyGoUp.setContext(Qt.ShortcutContext.WidgetShortcut)
|
122
121
|
self.keyGoUp.activated.connect(lambda: self.projTree.moveToLevel(-1))
|
123
122
|
|
124
123
|
self.keyGoDown = QShortcut(self.projTree)
|
125
124
|
self.keyGoDown.setKey("Alt+Right")
|
126
|
-
self.keyGoDown.setContext(Qt.WidgetShortcut)
|
125
|
+
self.keyGoDown.setContext(Qt.ShortcutContext.WidgetShortcut)
|
127
126
|
self.keyGoDown.activated.connect(lambda: self.projTree.moveToLevel(1))
|
128
127
|
|
129
|
-
self.keyUndoMv = QShortcut(self.projTree)
|
130
|
-
self.keyUndoMv.setKey("Ctrl+Shift+Z")
|
131
|
-
self.keyUndoMv.setContext(Qt.WidgetShortcut)
|
132
|
-
self.keyUndoMv.activated.connect(lambda: self.projTree.undoLastMove())
|
133
|
-
|
134
128
|
self.keyContext = QShortcut(self.projTree)
|
135
129
|
self.keyContext.setKey("Ctrl+.")
|
136
|
-
self.keyContext.setContext(Qt.WidgetShortcut)
|
130
|
+
self.keyContext.setContext(Qt.ShortcutContext.WidgetShortcut)
|
137
131
|
self.keyContext.activated.connect(lambda: self.projTree.openContextOnSelected())
|
138
132
|
|
139
133
|
# Signals
|
@@ -142,10 +136,7 @@ class GuiProjectView(QWidget):
|
|
142
136
|
# Function Mappings
|
143
137
|
self.emptyTrash = self.projTree.emptyTrash
|
144
138
|
self.requestDeleteItem = self.projTree.requestDeleteItem
|
145
|
-
self.setTreeItemValues = self.projTree.setTreeItemValues
|
146
|
-
self.propagateCount = self.projTree.propagateCount
|
147
139
|
self.getSelectedHandle = self.projTree.getSelectedHandle
|
148
|
-
self.setSelectedHandle = self.projTree.setSelectedHandle
|
149
140
|
self.changedSince = self.projTree.changedSince
|
150
141
|
self.createNewNote = self.projTree.createNewNote
|
151
142
|
|
@@ -198,17 +189,32 @@ class GuiProjectView(QWidget):
|
|
198
189
|
"""Check if the project tree has focus."""
|
199
190
|
return self.projTree.hasFocus()
|
200
191
|
|
201
|
-
|
192
|
+
##
|
193
|
+
# Public Slots
|
194
|
+
##
|
195
|
+
|
196
|
+
@pyqtSlot(str, str)
|
197
|
+
def renameTreeItem(self, tHandle: str | None = None, name: str = "") -> None:
|
202
198
|
"""External request to rename an item or the currently selected
|
203
199
|
item. This is triggered by the global menu or keyboard shortcut.
|
204
200
|
"""
|
205
201
|
if tHandle is None:
|
206
202
|
tHandle = self.projTree.getSelectedHandle()
|
207
|
-
|
203
|
+
if tHandle:
|
204
|
+
self.projTree.renameTreeItem(tHandle, name=name)
|
205
|
+
return
|
208
206
|
|
209
|
-
|
210
|
-
|
211
|
-
|
207
|
+
@pyqtSlot(str, bool)
|
208
|
+
def setSelectedHandle(self, tHandle: str, doScroll: bool = False) -> None:
|
209
|
+
"""Select an item and optionally scroll it into view."""
|
210
|
+
self.projTree.setSelectedHandle(tHandle, doScroll=doScroll)
|
211
|
+
return
|
212
|
+
|
213
|
+
@pyqtSlot(str)
|
214
|
+
def updateItemValues(self, tHandle: str) -> None:
|
215
|
+
"""Update tree item"""
|
216
|
+
self.projTree.setTreeItemValues(tHandle)
|
217
|
+
return
|
212
218
|
|
213
219
|
@pyqtSlot(str, int, int, int)
|
214
220
|
def updateCounts(self, tHandle: str, cCount: int, wCount: int, pCount: int) -> None:
|
@@ -256,7 +262,7 @@ class GuiProjectToolBar(QWidget):
|
|
256
262
|
self.tbQuick.setShortcut("Ctrl+L")
|
257
263
|
self.tbQuick.setIconSize(QSize(iPx, iPx))
|
258
264
|
self.tbQuick.setMenu(self.mQuick)
|
259
|
-
self.tbQuick.setPopupMode(QToolButton.InstantPopup)
|
265
|
+
self.tbQuick.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup)
|
260
266
|
|
261
267
|
# Move Buttons
|
262
268
|
self.tbMoveU = QToolButton(self)
|
@@ -305,7 +311,7 @@ class GuiProjectToolBar(QWidget):
|
|
305
311
|
self.tbAdd.setShortcut("Ctrl+N")
|
306
312
|
self.tbAdd.setIconSize(QSize(iPx, iPx))
|
307
313
|
self.tbAdd.setMenu(self.mAdd)
|
308
|
-
self.tbAdd.setPopupMode(QToolButton.InstantPopup)
|
314
|
+
self.tbAdd.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup)
|
309
315
|
|
310
316
|
# More Options Menu
|
311
317
|
self.mMore = QMenu(self)
|
@@ -316,9 +322,6 @@ class GuiProjectToolBar(QWidget):
|
|
316
322
|
self.aCollapse = self.mMore.addAction(self.tr("Collapse All"))
|
317
323
|
self.aCollapse.triggered.connect(lambda: self.projTree.setExpandedFromHandle(None, False))
|
318
324
|
|
319
|
-
self.aMoreUndo = self.mMore.addAction(self.tr("Undo Move"))
|
320
|
-
self.aMoreUndo.triggered.connect(lambda: self.projTree.undoLastMove())
|
321
|
-
|
322
325
|
self.aEmptyTrash = self.mMore.addAction(self.tr("Empty Trash"))
|
323
326
|
self.aEmptyTrash.triggered.connect(lambda: self.projTree.emptyTrash())
|
324
327
|
|
@@ -326,7 +329,7 @@ class GuiProjectToolBar(QWidget):
|
|
326
329
|
self.tbMore.setToolTip(self.tr("More Options"))
|
327
330
|
self.tbMore.setIconSize(QSize(iPx, iPx))
|
328
331
|
self.tbMore.setMenu(self.mMore)
|
329
|
-
self.tbMore.setPopupMode(QToolButton.InstantPopup)
|
332
|
+
self.tbMore.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup)
|
330
333
|
|
331
334
|
# Assemble
|
332
335
|
self.outerBox = QHBoxLayout()
|
@@ -353,7 +356,7 @@ class GuiProjectToolBar(QWidget):
|
|
353
356
|
def updateTheme(self) -> None:
|
354
357
|
"""Update theme elements."""
|
355
358
|
qPalette = self.palette()
|
356
|
-
qPalette.setBrush(QPalette.Window, qPalette.base())
|
359
|
+
qPalette.setBrush(QPalette.ColorRole.Window, qPalette.base())
|
357
360
|
self.setPalette(qPalette)
|
358
361
|
|
359
362
|
fadeCol = qPalette.text().color()
|
@@ -431,7 +434,7 @@ class GuiProjectToolBar(QWidget):
|
|
431
434
|
return
|
432
435
|
|
433
436
|
##
|
434
|
-
# Slots
|
437
|
+
# Private Slots
|
435
438
|
##
|
436
439
|
|
437
440
|
@pyqtSlot(str)
|
@@ -471,14 +474,14 @@ class GuiProjectTree(QTreeWidget):
|
|
471
474
|
|
472
475
|
# Internal Variables
|
473
476
|
self._treeMap = {}
|
474
|
-
self._lastMove = {}
|
475
477
|
self._timeChanged = 0.0
|
478
|
+
self._popAlert = None
|
476
479
|
|
477
480
|
# Build GUI
|
478
481
|
# =========
|
479
482
|
|
480
483
|
# Context Menu
|
481
|
-
self.setContextMenuPolicy(Qt.CustomContextMenu)
|
484
|
+
self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
|
482
485
|
self.customContextMenuRequested.connect(self._openContextMenu)
|
483
486
|
|
484
487
|
# Tree Settings
|
@@ -486,7 +489,7 @@ class GuiProjectTree(QTreeWidget):
|
|
486
489
|
cMg = CONFIG.pxInt(6)
|
487
490
|
|
488
491
|
self.setIconSize(QSize(iPx, iPx))
|
489
|
-
self.setFrameStyle(QFrame.NoFrame)
|
492
|
+
self.setFrameStyle(QFrame.Shape.NoFrame)
|
490
493
|
self.setUniformRowHeights(True)
|
491
494
|
self.setAllColumnsShowFocus(True)
|
492
495
|
self.setExpandsOnDoubleClick(False)
|
@@ -499,19 +502,18 @@ class GuiProjectTree(QTreeWidget):
|
|
499
502
|
treeHeader = self.header()
|
500
503
|
treeHeader.setStretchLastSection(False)
|
501
504
|
treeHeader.setMinimumSectionSize(iPx + cMg)
|
502
|
-
treeHeader.setSectionResizeMode(self.C_NAME, QHeaderView.Stretch)
|
503
|
-
treeHeader.setSectionResizeMode(self.C_COUNT, QHeaderView.ResizeToContents)
|
504
|
-
treeHeader.setSectionResizeMode(self.C_ACTIVE, QHeaderView.Fixed)
|
505
|
-
treeHeader.setSectionResizeMode(self.C_STATUS, QHeaderView.Fixed)
|
505
|
+
treeHeader.setSectionResizeMode(self.C_NAME, QHeaderView.ResizeMode.Stretch)
|
506
|
+
treeHeader.setSectionResizeMode(self.C_COUNT, QHeaderView.ResizeMode.ResizeToContents)
|
507
|
+
treeHeader.setSectionResizeMode(self.C_ACTIVE, QHeaderView.ResizeMode.Fixed)
|
508
|
+
treeHeader.setSectionResizeMode(self.C_STATUS, QHeaderView.ResizeMode.Fixed)
|
506
509
|
treeHeader.resizeSection(self.C_ACTIVE, iPx + cMg)
|
507
510
|
treeHeader.resizeSection(self.C_STATUS, iPx + cMg)
|
508
511
|
|
509
512
|
# Allow Move by Drag & Drop
|
510
513
|
self.setDragEnabled(True)
|
511
|
-
self.setDragDropMode(QAbstractItemView.InternalMove)
|
512
|
-
self.setDropIndicatorShown(True)
|
514
|
+
self.setDragDropMode(QAbstractItemView.DragDropMode.InternalMove)
|
513
515
|
|
514
|
-
# Disable built-in
|
516
|
+
# Disable built-in auto scroll as it isn't working in some Qt
|
515
517
|
# releases (see #1561) and instead use our own implementation
|
516
518
|
self.setAutoScroll(False)
|
517
519
|
|
@@ -519,21 +521,21 @@ class GuiProjectTree(QTreeWidget):
|
|
519
521
|
# Due to a bug, this stops working somewhere between Qt 5.15.3
|
520
522
|
# and 5.15.8, so this is also blocked in dropEvent (see #1569)
|
521
523
|
trRoot = self.invisibleRootItem()
|
522
|
-
trRoot.setFlags(trRoot.flags() ^ Qt.ItemIsDropEnabled)
|
524
|
+
trRoot.setFlags(trRoot.flags() ^ Qt.ItemFlag.ItemIsDropEnabled)
|
523
525
|
|
524
526
|
# Cached values
|
525
527
|
self._lblActive = self.tr("Active")
|
526
528
|
self._lblInactive = self.tr("Inactive")
|
527
529
|
|
528
530
|
# Set selection options
|
529
|
-
self.setSelectionMode(QAbstractItemView.
|
530
|
-
self.setSelectionBehavior(QAbstractItemView.SelectRows)
|
531
|
+
self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
|
532
|
+
self.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
|
531
533
|
|
532
534
|
# Connect signals
|
533
535
|
self.itemDoubleClicked.connect(self._treeDoubleClick)
|
534
536
|
self.itemSelectionChanged.connect(self._treeSelectionChange)
|
535
537
|
|
536
|
-
#
|
538
|
+
# Auto Scroll
|
537
539
|
self._scrollMargin = SHARED.theme.baseIconSize
|
538
540
|
self._scrollDirection = 0
|
539
541
|
self._scrollTimer = QTimer()
|
@@ -551,13 +553,13 @@ class GuiProjectTree(QTreeWidget):
|
|
551
553
|
"""Set or update tree widget settings."""
|
552
554
|
# Scroll bars
|
553
555
|
if CONFIG.hideVScroll:
|
554
|
-
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
556
|
+
self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
|
555
557
|
else:
|
556
|
-
self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
558
|
+
self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
|
557
559
|
if CONFIG.hideHScroll:
|
558
|
-
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
560
|
+
self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
|
559
561
|
else:
|
560
|
-
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
562
|
+
self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
|
561
563
|
return
|
562
564
|
|
563
565
|
##
|
@@ -568,7 +570,6 @@ class GuiProjectTree(QTreeWidget):
|
|
568
570
|
"""Clear the GUI content and the related map."""
|
569
571
|
self.clear()
|
570
572
|
self._treeMap = {}
|
571
|
-
self._lastMove = {}
|
572
573
|
self._timeChanged = 0.0
|
573
574
|
return
|
574
575
|
|
@@ -679,7 +680,7 @@ class GuiProjectTree(QTreeWidget):
|
|
679
680
|
|
680
681
|
# Add the new item to the project tree
|
681
682
|
self.revealNewTreeItem(tHandle, nHandle=nHandle, wordCount=True)
|
682
|
-
self.
|
683
|
+
self.projView.setTreeFocus() # See issue #1376
|
683
684
|
|
684
685
|
return True
|
685
686
|
|
@@ -739,7 +740,6 @@ class GuiProjectTree(QTreeWidget):
|
|
739
740
|
|
740
741
|
cItem = pItem.takeChild(tIndex)
|
741
742
|
pItem.insertChild(nIndex, cItem)
|
742
|
-
self._recordLastMove(cItem, pItem, tIndex)
|
743
743
|
|
744
744
|
self._alertTreeChange(tHandle, flush=True)
|
745
745
|
self.setCurrentItem(tItem)
|
@@ -768,19 +768,16 @@ class GuiProjectTree(QTreeWidget):
|
|
768
768
|
self.setCurrentItem(tItem.child(0))
|
769
769
|
return
|
770
770
|
|
771
|
-
def renameTreeItem(self, tHandle: str) ->
|
771
|
+
def renameTreeItem(self, tHandle: str, name: str = "") -> None:
|
772
772
|
"""Open a dialog to edit the label of an item."""
|
773
773
|
tItem = SHARED.project.tree[tHandle]
|
774
|
-
if tItem
|
775
|
-
|
776
|
-
|
777
|
-
|
778
|
-
|
779
|
-
|
780
|
-
|
781
|
-
self._alertTreeChange(tHandle, flush=False)
|
782
|
-
|
783
|
-
return True
|
774
|
+
if tItem:
|
775
|
+
newLabel, dlgOk = GuiEditLabel.getLabel(self, text=name or tItem.itemName)
|
776
|
+
if dlgOk:
|
777
|
+
tItem.setName(newLabel)
|
778
|
+
self.setTreeItemValues(tHandle)
|
779
|
+
self._alertTreeChange(tHandle, flush=False)
|
780
|
+
return
|
784
781
|
|
785
782
|
def saveTreeOrder(self) -> None:
|
786
783
|
"""Build a list of the items in the project tree and send them
|
@@ -843,6 +840,7 @@ class GuiProjectTree(QTreeWidget):
|
|
843
840
|
|
844
841
|
return status
|
845
842
|
|
843
|
+
@pyqtSlot()
|
846
844
|
def emptyTrash(self) -> bool:
|
847
845
|
"""Permanently delete all documents in the Trash folder. This
|
848
846
|
function only asks for confirmation once, and calls the regular
|
@@ -921,15 +919,13 @@ class GuiProjectTree(QTreeWidget):
|
|
921
919
|
logger.info("Action cancelled by user")
|
922
920
|
return False
|
923
921
|
|
924
|
-
wCount = self._getItemWordCount(tHandle)
|
925
922
|
self.propagateCount(tHandle, 0)
|
926
923
|
|
927
924
|
tIndex = trItemP.indexOfChild(trItemS)
|
928
925
|
trItemC = trItemP.takeChild(tIndex)
|
929
926
|
trItemT.addChild(trItemC)
|
930
927
|
|
931
|
-
self._postItemMove(tHandle
|
932
|
-
self._recordLastMove(trItemS, trItemP, tIndex)
|
928
|
+
self._postItemMove(tHandle)
|
933
929
|
self._alertTreeChange(tHandle, flush=flush)
|
934
930
|
|
935
931
|
logger.debug("Moved item '%s' to Trash", tHandle)
|
@@ -1090,46 +1086,6 @@ class GuiProjectTree(QTreeWidget):
|
|
1090
1086
|
logger.info("%d item(s) added to the project tree", count)
|
1091
1087
|
return
|
1092
1088
|
|
1093
|
-
def undoLastMove(self) -> bool:
|
1094
|
-
"""Attempt to undo the last action."""
|
1095
|
-
srcItem = self._lastMove.get("item", None)
|
1096
|
-
dstItem = self._lastMove.get("parent", None)
|
1097
|
-
dstIndex = self._lastMove.get("index", None)
|
1098
|
-
|
1099
|
-
srcOK = isinstance(srcItem, QTreeWidgetItem)
|
1100
|
-
dstOk = isinstance(dstItem, QTreeWidgetItem)
|
1101
|
-
if not srcOK or not dstOk or dstIndex is None:
|
1102
|
-
logger.debug("No tree move to undo")
|
1103
|
-
return False
|
1104
|
-
|
1105
|
-
if srcItem not in self._treeMap.values():
|
1106
|
-
logger.warning("Source item no longer exists")
|
1107
|
-
return False
|
1108
|
-
|
1109
|
-
if dstItem not in self._treeMap.values():
|
1110
|
-
logger.warning("Previous parent item no longer exists")
|
1111
|
-
return False
|
1112
|
-
|
1113
|
-
dstIndex = min(max(0, dstIndex), dstItem.childCount())
|
1114
|
-
sHandle = srcItem.data(self.C_DATA, self.D_HANDLE)
|
1115
|
-
dHandle = dstItem.data(self.C_DATA, self.D_HANDLE)
|
1116
|
-
logger.debug("Moving item '%s' back to '%s', index %d", sHandle, dHandle, dstIndex)
|
1117
|
-
|
1118
|
-
wCount = self._getItemWordCount(sHandle)
|
1119
|
-
self.propagateCount(sHandle, 0)
|
1120
|
-
parItem = srcItem.parent()
|
1121
|
-
srcIndex = parItem.indexOfChild(srcItem)
|
1122
|
-
movItem = parItem.takeChild(srcIndex)
|
1123
|
-
dstItem.insertChild(dstIndex, movItem)
|
1124
|
-
|
1125
|
-
self._postItemMove(sHandle, wCount)
|
1126
|
-
self._alertTreeChange(sHandle, flush=True)
|
1127
|
-
|
1128
|
-
self.setCurrentItem(movItem)
|
1129
|
-
self._lastMove = {}
|
1130
|
-
|
1131
|
-
return True
|
1132
|
-
|
1133
1089
|
def getSelectedHandle(self) -> str | None:
|
1134
1090
|
"""Get the currently selected handle. If multiple items are
|
1135
1091
|
selected, return the first.
|
@@ -1150,7 +1106,7 @@ class GuiProjectTree(QTreeWidget):
|
|
1150
1106
|
|
1151
1107
|
selIndex = self.selectedIndexes()
|
1152
1108
|
if selIndex and doScroll:
|
1153
|
-
self.scrollTo(selIndex[0], QAbstractItemView.PositionAtCenter)
|
1109
|
+
self.scrollTo(selIndex[0], QAbstractItemView.ScrollHint.PositionAtCenter)
|
1154
1110
|
|
1155
1111
|
return True
|
1156
1112
|
|
@@ -1185,6 +1141,15 @@ class GuiProjectTree(QTreeWidget):
|
|
1185
1141
|
tHandle = self.getSelectedHandle()
|
1186
1142
|
if tHandle is not None:
|
1187
1143
|
self.projView.selectedItemChanged.emit(tHandle)
|
1144
|
+
|
1145
|
+
# When selecting multiple items, don't allow including root
|
1146
|
+
# items in the selection and instead deselect them
|
1147
|
+
items = self.selectedItems()
|
1148
|
+
if items and len(items) > 1:
|
1149
|
+
for item in items:
|
1150
|
+
if item.parent() is None:
|
1151
|
+
item.setSelected(False)
|
1152
|
+
|
1188
1153
|
return
|
1189
1154
|
|
1190
1155
|
@pyqtSlot("QTreeWidgetItem*", int)
|
@@ -1215,160 +1180,29 @@ class GuiProjectTree(QTreeWidget):
|
|
1215
1180
|
tItem = None
|
1216
1181
|
tHandle = None
|
1217
1182
|
hasChild = False
|
1218
|
-
|
1219
|
-
|
1220
|
-
|
1183
|
+
sItem = self.itemAt(clickPos)
|
1184
|
+
sItems = self.selectedItems()
|
1185
|
+
if isinstance(sItem, QTreeWidgetItem):
|
1186
|
+
tHandle = sItem.data(self.C_DATA, self.D_HANDLE)
|
1221
1187
|
tItem = SHARED.project.tree[tHandle]
|
1222
|
-
hasChild =
|
1188
|
+
hasChild = sItem.childCount() > 0
|
1223
1189
|
|
1224
1190
|
if tItem is None or tHandle is None:
|
1225
1191
|
logger.debug("No item found")
|
1226
1192
|
return False
|
1227
1193
|
|
1228
|
-
ctxMenu =
|
1229
|
-
|
1230
|
-
# Trash Folder
|
1231
|
-
# ============
|
1232
|
-
|
1194
|
+
ctxMenu = _TreeContextMenu(self, tItem)
|
1233
1195
|
trashHandle = SHARED.project.tree.trashRoot
|
1234
|
-
if
|
1235
|
-
|
1236
|
-
|
1237
|
-
|
1238
|
-
ctxMenu.
|
1239
|
-
return True
|
1240
|
-
|
1241
|
-
# Document Actions
|
1242
|
-
# ================
|
1243
|
-
|
1244
|
-
isRoot = tItem.isRootType()
|
1245
|
-
isFolder = tItem.isFolderType()
|
1246
|
-
isFile = tItem.isFileType()
|
1247
|
-
|
1248
|
-
if isFile:
|
1249
|
-
aOpenDoc = ctxMenu.addAction(self.tr("Open Document"))
|
1250
|
-
aOpenDoc.triggered.connect(
|
1251
|
-
lambda: self.projView.openDocumentRequest.emit(tHandle, nwDocMode.EDIT, "", True)
|
1252
|
-
)
|
1253
|
-
aViewDoc = ctxMenu.addAction(self.tr("View Document"))
|
1254
|
-
aViewDoc.triggered.connect(
|
1255
|
-
lambda: self.projView.openDocumentRequest.emit(tHandle, nwDocMode.VIEW, "", False)
|
1256
|
-
)
|
1257
|
-
ctxMenu.addSeparator()
|
1258
|
-
|
1259
|
-
# Edit Item Settings
|
1260
|
-
# ==================
|
1261
|
-
|
1262
|
-
aLabel = ctxMenu.addAction(self.tr("Rename"))
|
1263
|
-
aLabel.triggered.connect(lambda: self.renameTreeItem(tHandle))
|
1264
|
-
|
1265
|
-
if isFile:
|
1266
|
-
aActive = ctxMenu.addAction(self.tr("Toggle Active"))
|
1267
|
-
aActive.triggered.connect(lambda: self._toggleItemActive(tHandle))
|
1268
|
-
|
1269
|
-
checkMark = f" ({nwUnicode.U_CHECK})"
|
1270
|
-
if tItem.isNovelLike():
|
1271
|
-
mStatus = ctxMenu.addMenu(self.tr("Set Status to ..."))
|
1272
|
-
for n, (key, entry) in enumerate(SHARED.project.data.itemStatus.items()):
|
1273
|
-
entryName = entry["name"] + (checkMark if tItem.itemStatus == key else "")
|
1274
|
-
aStatus = mStatus.addAction(entry["icon"], entryName)
|
1275
|
-
aStatus.triggered.connect(
|
1276
|
-
lambda n, key=key: self._changeItemStatus(tHandle, key)
|
1277
|
-
)
|
1278
|
-
mStatus.addSeparator()
|
1279
|
-
aManage1 = mStatus.addAction("Manage Labels ...")
|
1280
|
-
aManage1.triggered.connect(
|
1281
|
-
lambda: self.projView.projectSettingsRequest.emit(GuiProjectSettings.TAB_STATUS)
|
1282
|
-
)
|
1196
|
+
if trashHandle and tHandle == trashHandle:
|
1197
|
+
ctxMenu.buildTrashMenu()
|
1198
|
+
elif len(sItems) > 1:
|
1199
|
+
handles = [str(x.data(self.C_DATA, self.D_HANDLE)) for x in sItems]
|
1200
|
+
ctxMenu.buildMultiSelectMenu(handles)
|
1283
1201
|
else:
|
1284
|
-
|
1285
|
-
for n, (key, entry) in enumerate(SHARED.project.data.itemImport.items()):
|
1286
|
-
entryName = entry["name"] + (checkMark if tItem.itemImport == key else "")
|
1287
|
-
aImport = mImport.addAction(entry["icon"], entryName)
|
1288
|
-
aImport.triggered.connect(
|
1289
|
-
lambda n, key=key: self._changeItemImport(tHandle, key)
|
1290
|
-
)
|
1291
|
-
mImport.addSeparator()
|
1292
|
-
aManage2 = mImport.addAction("Manage Labels ...")
|
1293
|
-
aManage2.triggered.connect(
|
1294
|
-
lambda: self.projView.projectSettingsRequest.emit(GuiProjectSettings.TAB_IMPORT)
|
1295
|
-
)
|
1202
|
+
ctxMenu.buildSingleSelectMenu(hasChild)
|
1296
1203
|
|
1297
|
-
# Transform Item
|
1298
|
-
# ==============
|
1299
|
-
|
1300
|
-
if not isRoot:
|
1301
|
-
mTrans = ctxMenu.addMenu(self.tr("Transform"))
|
1302
|
-
|
1303
|
-
trDoc = trConst(nwLabels.LAYOUT_NAME[nwItemLayout.DOCUMENT])
|
1304
|
-
trNote = trConst(nwLabels.LAYOUT_NAME[nwItemLayout.NOTE])
|
1305
|
-
|
1306
|
-
isDocFile = isFile and tItem.isDocumentLayout()
|
1307
|
-
isNoteFile = isFile and tItem.isNoteLayout()
|
1308
|
-
|
1309
|
-
if isNoteFile and tItem.documentAllowed():
|
1310
|
-
aConvert1 = mTrans.addAction(self.tr("Convert to {0}").format(trDoc))
|
1311
|
-
aConvert1.triggered.connect(
|
1312
|
-
lambda: self._changeItemLayout(tHandle, nwItemLayout.DOCUMENT)
|
1313
|
-
)
|
1314
|
-
|
1315
|
-
if isDocFile:
|
1316
|
-
aConvert2 = mTrans.addAction(self.tr("Convert to {0}").format(trNote))
|
1317
|
-
aConvert2.triggered.connect(
|
1318
|
-
lambda: self._changeItemLayout(tHandle, nwItemLayout.NOTE)
|
1319
|
-
)
|
1320
|
-
|
1321
|
-
if isFolder and tItem.documentAllowed():
|
1322
|
-
aConvert3 = mTrans.addAction(self.tr("Convert to {0}").format(trDoc))
|
1323
|
-
aConvert3.triggered.connect(
|
1324
|
-
lambda: self._covertFolderToFile(tHandle, nwItemLayout.DOCUMENT)
|
1325
|
-
)
|
1326
|
-
|
1327
|
-
if isFolder:
|
1328
|
-
aConvert4 = mTrans.addAction(self.tr("Convert to {0}").format(trNote))
|
1329
|
-
aConvert4.triggered.connect(
|
1330
|
-
lambda: self._covertFolderToFile(tHandle, nwItemLayout.NOTE)
|
1331
|
-
)
|
1332
|
-
|
1333
|
-
if hasChild and isFile:
|
1334
|
-
aMerge1 = mTrans.addAction(self.tr("Merge Child Items into Self"))
|
1335
|
-
aMerge1.triggered.connect(lambda: self._mergeDocuments(tHandle, False))
|
1336
|
-
aMerge2 = mTrans.addAction(self.tr("Merge Child Items into New"))
|
1337
|
-
aMerge2.triggered.connect(lambda: self._mergeDocuments(tHandle, True))
|
1338
|
-
|
1339
|
-
if hasChild and isFolder:
|
1340
|
-
aMerge3 = mTrans.addAction(self.tr("Merge Documents in Folder"))
|
1341
|
-
aMerge3.triggered.connect(lambda: self._mergeDocuments(tHandle, True))
|
1342
|
-
|
1343
|
-
if isFile:
|
1344
|
-
aSplit1 = mTrans.addAction(self.tr("Split Document by Headers"))
|
1345
|
-
aSplit1.triggered.connect(lambda: self._splitDocument(tHandle))
|
1346
|
-
|
1347
|
-
# Expand/Collapse/Delete/Duplicate
|
1348
|
-
# ================================
|
1349
|
-
|
1350
|
-
ctxMenu.addSeparator()
|
1351
|
-
|
1352
|
-
if hasChild:
|
1353
|
-
aExpand = ctxMenu.addAction(self.tr("Expand All"))
|
1354
|
-
aExpand.triggered.connect(lambda: self.setExpandedFromHandle(tHandle, True))
|
1355
|
-
aCollapse = ctxMenu.addAction(self.tr("Collapse All"))
|
1356
|
-
aCollapse.triggered.connect(lambda: self.setExpandedFromHandle(tHandle, False))
|
1357
|
-
aDuplicate = ctxMenu.addAction(self.tr("Duplicate from Here"))
|
1358
|
-
aDuplicate.triggered.connect(lambda: self._duplicateFromHandle(tHandle))
|
1359
|
-
elif isFile:
|
1360
|
-
aDuplicate = ctxMenu.addAction(self.tr("Duplicate Document"))
|
1361
|
-
aDuplicate.triggered.connect(lambda: self._duplicateFromHandle(tHandle))
|
1362
|
-
|
1363
|
-
if tItem.itemClass == nwItemClass.TRASH or isRoot or (isFolder and not hasChild):
|
1364
|
-
aDelete = ctxMenu.addAction(self.tr("Delete Permanently"))
|
1365
|
-
aDelete.triggered.connect(lambda: self.permDeleteItem(tHandle))
|
1366
|
-
else:
|
1367
|
-
aMoveTrash = ctxMenu.addAction(self.tr("Move to Trash"))
|
1368
|
-
aMoveTrash.triggered.connect(lambda: self.moveItemToTrash(tHandle))
|
1369
|
-
|
1370
|
-
# Show Context Menu
|
1371
1204
|
ctxMenu.exec_(self.viewport().mapToGlobal(clickPos))
|
1205
|
+
ctxMenu.deleteLater()
|
1372
1206
|
|
1373
1207
|
return True
|
1374
1208
|
|
@@ -1393,29 +1227,45 @@ class GuiProjectTree(QTreeWidget):
|
|
1393
1227
|
for viewing if the user middle-clicked.
|
1394
1228
|
"""
|
1395
1229
|
super().mousePressEvent(event)
|
1396
|
-
|
1397
|
-
if event.button() == Qt.LeftButton:
|
1230
|
+
if event.button() == Qt.MouseButton.LeftButton:
|
1398
1231
|
selItem = self.indexAt(event.pos())
|
1399
1232
|
if not selItem.isValid():
|
1400
1233
|
self.clearSelection()
|
1401
|
-
|
1402
|
-
elif event.button() == Qt.MiddleButton:
|
1234
|
+
elif event.button() == Qt.MouseButton.MiddleButton:
|
1403
1235
|
selItem = self.itemAt(event.pos())
|
1404
|
-
if
|
1405
|
-
|
1406
|
-
|
1407
|
-
|
1408
|
-
|
1409
|
-
if tItem is None:
|
1410
|
-
return
|
1236
|
+
if selItem:
|
1237
|
+
tHandle = selItem.data(self.C_DATA, self.D_HANDLE)
|
1238
|
+
if (tItem := SHARED.project.tree[tHandle]) and tItem.isFileType():
|
1239
|
+
self.projView.openDocumentRequest.emit(tHandle, nwDocMode.VIEW, "", False)
|
1240
|
+
return
|
1411
1241
|
|
1412
|
-
|
1413
|
-
|
1242
|
+
def startDrag(self, dropAction: Qt.DropActions) -> None:
|
1243
|
+
"""Capture the drag and drop handling to pop alerts."""
|
1244
|
+
super().startDrag(dropAction)
|
1245
|
+
if self._popAlert:
|
1246
|
+
SHARED.error(self._popAlert)
|
1247
|
+
self._popAlert = None
|
1248
|
+
return
|
1414
1249
|
|
1250
|
+
def dragEnterEvent(self, event: QDragEnterEvent) -> None:
|
1251
|
+
"""Check that we're only dragging items that are siblings, and
|
1252
|
+
not a root level item.
|
1253
|
+
"""
|
1254
|
+
items = self.selectedItems()
|
1255
|
+
if items and (parent := items[0].parent()) and all(x.parent() is parent for x in items):
|
1256
|
+
super().dragEnterEvent(event)
|
1257
|
+
else:
|
1258
|
+
logger.warning("Drag action is not allowed and has been cancelled")
|
1259
|
+
self._popAlert = self.tr(
|
1260
|
+
"Drag and drop is only allowed for single items, non-root "
|
1261
|
+
"items, or multiple items with the same parent."
|
1262
|
+
)
|
1263
|
+
event.mimeData().clear()
|
1264
|
+
event.ignore()
|
1415
1265
|
return
|
1416
1266
|
|
1417
1267
|
def dragMoveEvent(self, event: QDragMoveEvent) -> None:
|
1418
|
-
"""Capture the drag move event to enable edge
|
1268
|
+
"""Capture the drag move event to enable edge auto scroll."""
|
1419
1269
|
y = event.pos().y()
|
1420
1270
|
if y < self._scrollMargin:
|
1421
1271
|
if not self._scrollTimer.isActive():
|
@@ -1429,38 +1279,33 @@ class GuiProjectTree(QTreeWidget):
|
|
1429
1279
|
return
|
1430
1280
|
|
1431
1281
|
def dropEvent(self, event: QDropEvent) -> None:
|
1432
|
-
"""Overload the drop item event to ensure
|
1433
|
-
|
1282
|
+
"""Overload the drop item event to ensure the drag and drop
|
1283
|
+
action is allowed, and update relevant data.
|
1434
1284
|
"""
|
1435
|
-
|
1436
|
-
|
1437
|
-
|
1438
|
-
|
1439
|
-
event.ignore()
|
1440
|
-
return
|
1441
|
-
|
1442
|
-
if not self.indexAt(event.pos()).isValid():
|
1443
|
-
# Needed due to a bug somewhere around Qt 5.15.8 that
|
1444
|
-
# ignores the invisible root item flags
|
1285
|
+
tItem = self.itemAt(event.pos())
|
1286
|
+
dropOn = self.dropIndicatorPosition() == QAbstractItemView.DropIndicatorPosition.OnItem
|
1287
|
+
# Make sure nothing can be dropped on invisible root (see #1569)
|
1288
|
+
if not tItem or tItem.parent() is None and not dropOn:
|
1445
1289
|
logger.error("Invalid drop location")
|
1446
1290
|
event.ignore()
|
1447
1291
|
return
|
1448
1292
|
|
1449
|
-
|
1293
|
+
mItems: dict[str, tuple[QTreeWidgetItem, bool]] = {}
|
1294
|
+
sItems = self.selectedItems()
|
1295
|
+
if sItems and (parent := sItems[0].parent()) and all(x.parent() is parent for x in sItems):
|
1296
|
+
for sItem in sItems:
|
1297
|
+
mHandle = str(sItem.data(self.C_DATA, self.D_HANDLE))
|
1298
|
+
mItems[mHandle] = (sItem, sItem.isExpanded())
|
1299
|
+
self.propagateCount(mHandle, 0)
|
1450
1300
|
|
1451
|
-
|
1452
|
-
pItem = sItem.parent()
|
1453
|
-
pIndex = pItem.indexOfChild(sItem) if pItem else 0
|
1301
|
+
super().dropEvent(event)
|
1454
1302
|
|
1455
|
-
|
1456
|
-
|
1303
|
+
for mHandle, (sItem, isExpanded) in mItems.items():
|
1304
|
+
self._postItemMove(mHandle)
|
1305
|
+
sItem.setExpanded(isExpanded)
|
1306
|
+
self._alertTreeChange(mHandle, flush=False)
|
1457
1307
|
|
1458
|
-
|
1459
|
-
self._postItemMove(sHandle, wCount)
|
1460
|
-
self._recordLastMove(sItem, pItem, pIndex)
|
1461
|
-
self._alertTreeChange(sHandle, flush=True)
|
1462
|
-
|
1463
|
-
sItem.setExpanded(isExpanded)
|
1308
|
+
self.saveTreeOrder()
|
1464
1309
|
|
1465
1310
|
return
|
1466
1311
|
|
@@ -1468,17 +1313,16 @@ class GuiProjectTree(QTreeWidget):
|
|
1468
1313
|
# Internal Functions
|
1469
1314
|
##
|
1470
1315
|
|
1471
|
-
def _postItemMove(self, tHandle: str
|
1316
|
+
def _postItemMove(self, tHandle: str) -> None:
|
1472
1317
|
"""Run various maintenance tasks for a moved item."""
|
1473
1318
|
trItemS = self._getTreeItem(tHandle)
|
1474
1319
|
nwItemS = SHARED.project.tree[tHandle]
|
1475
1320
|
trItemP = trItemS.parent() if trItemS else None
|
1476
1321
|
if trItemP is None or nwItemS is None:
|
1477
1322
|
logger.error("Failed to find new parent item of '%s'", tHandle)
|
1478
|
-
return
|
1323
|
+
return
|
1479
1324
|
|
1480
|
-
# Update item parent handle in the project
|
1481
|
-
# is updated accordingly, and update word count
|
1325
|
+
# Update item parent handle in the project
|
1482
1326
|
pHandle = trItemP.data(self.C_DATA, self.D_HANDLE)
|
1483
1327
|
nwItemS.setParent(pHandle)
|
1484
1328
|
trItemP.setExpanded(True)
|
@@ -1489,19 +1333,16 @@ class GuiProjectTree(QTreeWidget):
|
|
1489
1333
|
for mHandle in mHandles:
|
1490
1334
|
logger.debug("Updating item '%s'", mHandle)
|
1491
1335
|
SHARED.project.tree.updateItemData(mHandle)
|
1492
|
-
|
1493
|
-
# Update the index
|
1494
1336
|
if nwItemS.isInactiveClass():
|
1495
1337
|
SHARED.project.index.deleteHandle(mHandle)
|
1496
1338
|
else:
|
1497
1339
|
SHARED.project.index.reIndexHandle(mHandle)
|
1498
|
-
|
1499
1340
|
self.setTreeItemValues(mHandle)
|
1500
1341
|
|
1501
|
-
#
|
1502
|
-
self.propagateCount(tHandle,
|
1342
|
+
# Update word count
|
1343
|
+
self.propagateCount(tHandle, nwItemS.wordCount, countChildren=True)
|
1503
1344
|
|
1504
|
-
return
|
1345
|
+
return
|
1505
1346
|
|
1506
1347
|
def _getItemWordCount(self, tHandle: str) -> int:
|
1507
1348
|
"""Return the word count of a given item handle."""
|
@@ -1512,15 +1353,6 @@ class GuiProjectTree(QTreeWidget):
|
|
1512
1353
|
"""Return the QTreeWidgetItem of a given item handle."""
|
1513
1354
|
return self._treeMap.get(tHandle, None) if tHandle else None
|
1514
1355
|
|
1515
|
-
def _toggleItemActive(self, tHandle: str) -> None:
|
1516
|
-
"""Toggle the active status of an item."""
|
1517
|
-
tItem = SHARED.project.tree[tHandle]
|
1518
|
-
if tItem is not None:
|
1519
|
-
tItem.setActive(not tItem.isActive)
|
1520
|
-
self.setTreeItemValues(tItem.itemHandle)
|
1521
|
-
self._alertTreeChange(tHandle, flush=False)
|
1522
|
-
return
|
1523
|
-
|
1524
1356
|
def _recursiveSetExpanded(self, trItem: QTreeWidgetItem, isExpanded: bool) -> None:
|
1525
1357
|
"""Recursive function to set expanded status starting from (and
|
1526
1358
|
not including) a given item.
|
@@ -1533,60 +1365,6 @@ class GuiProjectTree(QTreeWidget):
|
|
1533
1365
|
self._recursiveSetExpanded(chItem, isExpanded)
|
1534
1366
|
return
|
1535
1367
|
|
1536
|
-
def _changeItemStatus(self, tHandle: str, tStatus: str) -> None:
|
1537
|
-
"""Set a new status value of an item."""
|
1538
|
-
tItem = SHARED.project.tree[tHandle]
|
1539
|
-
if tItem is not None:
|
1540
|
-
tItem.setStatus(tStatus)
|
1541
|
-
self.setTreeItemValues(tItem.itemHandle)
|
1542
|
-
self._alertTreeChange(tHandle, flush=False)
|
1543
|
-
return
|
1544
|
-
|
1545
|
-
def _changeItemImport(self, tHandle: str, tImport: str) -> None:
|
1546
|
-
"""Set a new importance value of an item."""
|
1547
|
-
tItem = SHARED.project.tree[tHandle]
|
1548
|
-
if tItem is not None:
|
1549
|
-
tItem.setImport(tImport)
|
1550
|
-
self.setTreeItemValues(tItem.itemHandle)
|
1551
|
-
self._alertTreeChange(tHandle, flush=False)
|
1552
|
-
return
|
1553
|
-
|
1554
|
-
def _changeItemLayout(self, tHandle: str, itemLayout: nwItemLayout) -> None:
|
1555
|
-
"""Set a new item layout value of an item."""
|
1556
|
-
tItem = SHARED.project.tree[tHandle]
|
1557
|
-
if tItem is not None:
|
1558
|
-
if itemLayout == nwItemLayout.DOCUMENT and tItem.documentAllowed():
|
1559
|
-
tItem.setLayout(nwItemLayout.DOCUMENT)
|
1560
|
-
self.setTreeItemValues(tHandle)
|
1561
|
-
self._alertTreeChange(tHandle, flush=False)
|
1562
|
-
elif itemLayout == nwItemLayout.NOTE:
|
1563
|
-
tItem.setLayout(nwItemLayout.NOTE)
|
1564
|
-
self.setTreeItemValues(tHandle)
|
1565
|
-
self._alertTreeChange(tHandle, flush=False)
|
1566
|
-
return
|
1567
|
-
|
1568
|
-
def _covertFolderToFile(self, tHandle: str, itemLayout: nwItemLayout) -> None:
|
1569
|
-
"""Convert a folder to a note or document."""
|
1570
|
-
tItem = SHARED.project.tree[tHandle]
|
1571
|
-
if tItem is not None and tItem.isFolderType():
|
1572
|
-
msgYes = SHARED.question(self.tr(
|
1573
|
-
"Do you want to convert the folder to a {0}? "
|
1574
|
-
"This action cannot be reversed."
|
1575
|
-
).format(trConst(nwLabels.LAYOUT_NAME[itemLayout])))
|
1576
|
-
if msgYes and itemLayout == nwItemLayout.DOCUMENT and tItem.documentAllowed():
|
1577
|
-
tItem.setType(nwItemType.FILE)
|
1578
|
-
tItem.setLayout(nwItemLayout.DOCUMENT)
|
1579
|
-
self.setTreeItemValues(tHandle)
|
1580
|
-
self._alertTreeChange(tHandle, flush=False)
|
1581
|
-
elif msgYes and itemLayout == nwItemLayout.NOTE:
|
1582
|
-
tItem.setType(nwItemType.FILE)
|
1583
|
-
tItem.setLayout(nwItemLayout.NOTE)
|
1584
|
-
self.setTreeItemValues(tHandle)
|
1585
|
-
self._alertTreeChange(tHandle, flush=False)
|
1586
|
-
else:
|
1587
|
-
logger.info("Folder conversion cancelled")
|
1588
|
-
return
|
1589
|
-
|
1590
1368
|
def _mergeDocuments(self, tHandle: str, newFile: bool) -> bool:
|
1591
1369
|
"""Merge an item's child documents into a single document."""
|
1592
1370
|
logger.info("Request to merge items under handle '%s'", tHandle)
|
@@ -1606,7 +1384,7 @@ class GuiProjectTree(QTreeWidget):
|
|
1606
1384
|
dlgMerge = GuiDocMerge(self.mainGui, tHandle, itemList)
|
1607
1385
|
dlgMerge.exec_()
|
1608
1386
|
|
1609
|
-
if dlgMerge.result() == QDialog.Accepted:
|
1387
|
+
if dlgMerge.result() == QDialog.DialogCode.Accepted:
|
1610
1388
|
|
1611
1389
|
mrgData = dlgMerge.getData()
|
1612
1390
|
mrgList = mrgData.get("finalItems", [])
|
@@ -1676,7 +1454,7 @@ class GuiProjectTree(QTreeWidget):
|
|
1676
1454
|
dlgSplit = GuiDocSplit(self.mainGui, tHandle)
|
1677
1455
|
dlgSplit.exec_()
|
1678
1456
|
|
1679
|
-
if dlgSplit.result() == QDialog.Accepted:
|
1457
|
+
if dlgSplit.result() == QDialog.DialogCode.Accepted:
|
1680
1458
|
|
1681
1459
|
splitData, splitText = dlgSplit.getData()
|
1682
1460
|
|
@@ -1781,10 +1559,10 @@ class GuiProjectTree(QTreeWidget):
|
|
1781
1559
|
newItem.setText(self.C_ACTIVE, "")
|
1782
1560
|
newItem.setText(self.C_STATUS, "")
|
1783
1561
|
|
1784
|
-
newItem.setTextAlignment(self.C_NAME, Qt.AlignLeft)
|
1785
|
-
newItem.setTextAlignment(self.C_COUNT, Qt.AlignRight)
|
1786
|
-
newItem.setTextAlignment(self.C_ACTIVE, Qt.AlignLeft)
|
1787
|
-
newItem.setTextAlignment(self.C_STATUS, Qt.AlignLeft)
|
1562
|
+
newItem.setTextAlignment(self.C_NAME, Qt.AlignmentFlag.AlignLeft)
|
1563
|
+
newItem.setTextAlignment(self.C_COUNT, Qt.AlignmentFlag.AlignRight)
|
1564
|
+
newItem.setTextAlignment(self.C_ACTIVE, Qt.AlignmentFlag.AlignLeft)
|
1565
|
+
newItem.setTextAlignment(self.C_STATUS, Qt.AlignmentFlag.AlignLeft)
|
1788
1566
|
|
1789
1567
|
newItem.setData(self.C_DATA, self.D_HANDLE, tHandle)
|
1790
1568
|
newItem.setData(self.C_DATA, self.D_WORDS, 0)
|
@@ -1852,16 +1630,322 @@ class GuiProjectTree(QTreeWidget):
|
|
1852
1630
|
|
1853
1631
|
return
|
1854
1632
|
|
1855
|
-
|
1856
|
-
|
1857
|
-
|
1858
|
-
|
1859
|
-
|
1860
|
-
|
1861
|
-
|
1862
|
-
|
1863
|
-
|
1864
|
-
|
1633
|
+
# END Class GuiProjectTree
|
1634
|
+
|
1635
|
+
|
1636
|
+
class _TreeContextMenu(QMenu):
|
1637
|
+
|
1638
|
+
def __init__(self, projTree: GuiProjectTree, nwItem: NWItem) -> None:
|
1639
|
+
super().__init__(parent=projTree)
|
1640
|
+
|
1641
|
+
self.projTree = projTree
|
1642
|
+
self.projView = projTree.projView
|
1643
|
+
|
1644
|
+
self._item = nwItem
|
1645
|
+
self._handle = nwItem.itemHandle
|
1646
|
+
self._items: list[str] = []
|
1647
|
+
|
1648
|
+
logger.debug("Ready: _TreeContextMenu")
|
1649
|
+
|
1865
1650
|
return
|
1866
1651
|
|
1867
|
-
#
|
1652
|
+
def __del__(self) -> None: # pragma: no cover
|
1653
|
+
logger.debug("Delete: _TreeContextMenu")
|
1654
|
+
return
|
1655
|
+
|
1656
|
+
##
|
1657
|
+
# Methods
|
1658
|
+
##
|
1659
|
+
|
1660
|
+
def buildTrashMenu(self) -> None:
|
1661
|
+
"""Build the special menu for the Trash folder."""
|
1662
|
+
action = self.addAction(self.tr("Empty Trash"))
|
1663
|
+
action.triggered.connect(self.projTree.emptyTrash)
|
1664
|
+
return
|
1665
|
+
|
1666
|
+
def buildSingleSelectMenu(self, hasChild: bool) -> None:
|
1667
|
+
"""Build the single-select menu."""
|
1668
|
+
isFile = self._item.isFileType()
|
1669
|
+
isFolder = self._item.isFolderType()
|
1670
|
+
isRoot = self._item.isRootType()
|
1671
|
+
|
1672
|
+
# Document Actions
|
1673
|
+
if isFile:
|
1674
|
+
self._docActions()
|
1675
|
+
self.addSeparator()
|
1676
|
+
|
1677
|
+
# Edit Item Settings
|
1678
|
+
aLabel = self.addAction(self.tr("Rename"))
|
1679
|
+
aLabel.triggered.connect(lambda: self.projTree.renameTreeItem(self._handle))
|
1680
|
+
if isFile:
|
1681
|
+
self._itemActive(False)
|
1682
|
+
self._itemStatusImport(False)
|
1683
|
+
|
1684
|
+
# Transform Item
|
1685
|
+
if isFile or isFolder:
|
1686
|
+
self._itemTransform(isFile, isFolder, hasChild)
|
1687
|
+
self.addSeparator()
|
1688
|
+
|
1689
|
+
# Process Item
|
1690
|
+
self._itemProcess(isFile, isFolder, isRoot, hasChild)
|
1691
|
+
|
1692
|
+
return
|
1693
|
+
|
1694
|
+
def buildMultiSelectMenu(self, items: list[str]) -> None:
|
1695
|
+
"""Build the multi-select menu."""
|
1696
|
+
self._items = items
|
1697
|
+
self._itemActive(True)
|
1698
|
+
self._itemStatusImport(True)
|
1699
|
+
self.addSeparator()
|
1700
|
+
self._moveToTrash(True)
|
1701
|
+
return
|
1702
|
+
|
1703
|
+
##
|
1704
|
+
# Menu Builders
|
1705
|
+
##
|
1706
|
+
|
1707
|
+
def _docActions(self) -> None:
|
1708
|
+
"""Add document actions."""
|
1709
|
+
action = self.addAction(self.tr("Open Document"))
|
1710
|
+
action.triggered.connect(
|
1711
|
+
lambda: self.projView.openDocumentRequest.emit(self._handle, nwDocMode.EDIT, "", True)
|
1712
|
+
)
|
1713
|
+
action = self.addAction(self.tr("View Document"))
|
1714
|
+
action.triggered.connect(
|
1715
|
+
lambda: self.projView.openDocumentRequest.emit(self._handle, nwDocMode.VIEW, "", False)
|
1716
|
+
)
|
1717
|
+
return
|
1718
|
+
|
1719
|
+
def _itemActive(self, multi: bool) -> None:
|
1720
|
+
"""Add Active/Inactive actions."""
|
1721
|
+
if multi:
|
1722
|
+
mSub = self.addMenu(self.tr("Set Active to ..."))
|
1723
|
+
aOne = mSub.addAction(SHARED.theme.getIcon("checked"), self.tr("Active"))
|
1724
|
+
aOne.triggered.connect(lambda: self._iterItemActive(True))
|
1725
|
+
aTwo = mSub.addAction(SHARED.theme.getIcon("unchecked"), self.tr("Inactive"))
|
1726
|
+
aTwo.triggered.connect(lambda: self._iterItemActive(False))
|
1727
|
+
else:
|
1728
|
+
action = self.addAction(self.tr("Toggle Active"))
|
1729
|
+
action.triggered.connect(self._toggleItemActive)
|
1730
|
+
return
|
1731
|
+
|
1732
|
+
def _itemStatusImport(self, multi: bool) -> None:
|
1733
|
+
"""Add actions for changing status or importance."""
|
1734
|
+
if self._item.isNovelLike():
|
1735
|
+
menu = self.addMenu(self.tr("Set Status to ..."))
|
1736
|
+
current = self._item.itemStatus
|
1737
|
+
for n, (key, entry) in enumerate(SHARED.project.data.itemStatus.items()):
|
1738
|
+
name = entry["name"]
|
1739
|
+
if not multi and current == key:
|
1740
|
+
name += f" ({nwUnicode.U_CHECK})"
|
1741
|
+
action = menu.addAction(entry["icon"], name)
|
1742
|
+
if multi:
|
1743
|
+
action.triggered.connect(lambda n, key=key: self._iterSetItemStatus(key))
|
1744
|
+
else:
|
1745
|
+
action.triggered.connect(lambda n, key=key: self._changeItemStatus(key))
|
1746
|
+
menu.addSeparator()
|
1747
|
+
action = menu.addAction(self.tr("Manage Labels ..."))
|
1748
|
+
action.triggered.connect(
|
1749
|
+
lambda: self.projView.projectSettingsRequest.emit(GuiProjectSettings.TAB_STATUS)
|
1750
|
+
)
|
1751
|
+
else:
|
1752
|
+
menu = self.addMenu(self.tr("Set Importance to ..."))
|
1753
|
+
current = self._item.itemImport
|
1754
|
+
for n, (key, entry) in enumerate(SHARED.project.data.itemImport.items()):
|
1755
|
+
name = entry["name"]
|
1756
|
+
if not multi and current == key:
|
1757
|
+
name += f" ({nwUnicode.U_CHECK})"
|
1758
|
+
action = menu.addAction(entry["icon"], name)
|
1759
|
+
if multi:
|
1760
|
+
action.triggered.connect(lambda n, key=key: self._iterSetItemImport(key))
|
1761
|
+
else:
|
1762
|
+
action.triggered.connect(lambda n, key=key: self._changeItemImport(key))
|
1763
|
+
menu.addSeparator()
|
1764
|
+
action = menu.addAction(self.tr("Manage Labels ..."))
|
1765
|
+
action.triggered.connect(
|
1766
|
+
lambda: self.projView.projectSettingsRequest.emit(GuiProjectSettings.TAB_IMPORT)
|
1767
|
+
)
|
1768
|
+
return
|
1769
|
+
|
1770
|
+
def _itemTransform(self, isFile: bool, isFolder: bool, hasChild: bool) -> None:
|
1771
|
+
"""Add actions for the Transform menu."""
|
1772
|
+
menu = self.addMenu(self.tr("Transform"))
|
1773
|
+
|
1774
|
+
tree = self.projTree
|
1775
|
+
tHandle = self._handle
|
1776
|
+
|
1777
|
+
trDoc = trConst(nwLabels.LAYOUT_NAME[nwItemLayout.DOCUMENT])
|
1778
|
+
trNote = trConst(nwLabels.LAYOUT_NAME[nwItemLayout.NOTE])
|
1779
|
+
loDoc = nwItemLayout.DOCUMENT
|
1780
|
+
loNote = nwItemLayout.NOTE
|
1781
|
+
isDocFile = isFile and self._item.isDocumentLayout()
|
1782
|
+
isNoteFile = isFile and self._item.isNoteLayout()
|
1783
|
+
|
1784
|
+
if isNoteFile and self._item.documentAllowed():
|
1785
|
+
action = menu.addAction(self.tr("Convert to {0}").format(trDoc))
|
1786
|
+
action.triggered.connect(lambda: self._changeItemLayout(loDoc))
|
1787
|
+
|
1788
|
+
if isDocFile:
|
1789
|
+
action = menu.addAction(self.tr("Convert to {0}").format(trNote))
|
1790
|
+
action.triggered.connect(lambda: self._changeItemLayout(loNote))
|
1791
|
+
|
1792
|
+
if isFolder and self._item.documentAllowed():
|
1793
|
+
action = menu.addAction(self.tr("Convert to {0}").format(trDoc))
|
1794
|
+
action.triggered.connect(lambda: self._covertFolderToFile(loDoc))
|
1795
|
+
|
1796
|
+
if isFolder:
|
1797
|
+
action = menu.addAction(self.tr("Convert to {0}").format(trNote))
|
1798
|
+
action.triggered.connect(lambda: self._covertFolderToFile(loNote))
|
1799
|
+
|
1800
|
+
if hasChild and isFile:
|
1801
|
+
action = menu.addAction(self.tr("Merge Child Items into Self"))
|
1802
|
+
action.triggered.connect(lambda: tree._mergeDocuments(tHandle, False))
|
1803
|
+
action = menu.addAction(self.tr("Merge Child Items into New"))
|
1804
|
+
action.triggered.connect(lambda: tree._mergeDocuments(tHandle, True))
|
1805
|
+
|
1806
|
+
if hasChild and isFolder:
|
1807
|
+
action = menu.addAction(self.tr("Merge Documents in Folder"))
|
1808
|
+
action.triggered.connect(lambda: tree._mergeDocuments(tHandle, True))
|
1809
|
+
|
1810
|
+
if isFile:
|
1811
|
+
action = menu.addAction(self.tr("Split Document by Headers"))
|
1812
|
+
action.triggered.connect(lambda: tree._splitDocument(tHandle))
|
1813
|
+
|
1814
|
+
return
|
1815
|
+
|
1816
|
+
def _itemProcess(self, isFile: bool, isFolder: bool, isRoot: bool, hasChild: bool) -> None:
|
1817
|
+
"""Add actions for item processing."""
|
1818
|
+
tree = self.projTree
|
1819
|
+
tHandle = self._handle
|
1820
|
+
if hasChild:
|
1821
|
+
action = self.addAction(self.tr("Expand All"))
|
1822
|
+
action.triggered.connect(lambda: tree.setExpandedFromHandle(tHandle, True))
|
1823
|
+
action = self.addAction(self.tr("Collapse All"))
|
1824
|
+
action.triggered.connect(lambda: tree.setExpandedFromHandle(tHandle, False))
|
1825
|
+
action = self.addAction(self.tr("Duplicate from Here"))
|
1826
|
+
action.triggered.connect(lambda: tree._duplicateFromHandle(tHandle))
|
1827
|
+
elif isFile:
|
1828
|
+
action = self.addAction(self.tr("Duplicate Document"))
|
1829
|
+
action.triggered.connect(lambda: tree._duplicateFromHandle(tHandle))
|
1830
|
+
|
1831
|
+
if self._item.itemClass == nwItemClass.TRASH or isRoot or (isFolder and not hasChild):
|
1832
|
+
action = self.addAction(self.tr("Delete Permanently"))
|
1833
|
+
action.triggered.connect(lambda: tree.permDeleteItem(tHandle))
|
1834
|
+
else:
|
1835
|
+
action = self.addAction(self.tr("Move to Trash"))
|
1836
|
+
action.triggered.connect(lambda: tree.moveItemToTrash(tHandle))
|
1837
|
+
|
1838
|
+
return
|
1839
|
+
|
1840
|
+
def _moveToTrash(self, multi: bool) -> None:
|
1841
|
+
"""Add move to Trash action."""
|
1842
|
+
action = self.addAction(self.tr("Move to Trash"))
|
1843
|
+
if multi:
|
1844
|
+
action.triggered.connect(self._iterMoveToTrash)
|
1845
|
+
return
|
1846
|
+
|
1847
|
+
##
|
1848
|
+
# Private Slots
|
1849
|
+
##
|
1850
|
+
|
1851
|
+
@pyqtSlot()
|
1852
|
+
def _iterMoveToTrash(self) -> None:
|
1853
|
+
"""Iterate through files and move them to Trash."""
|
1854
|
+
if SHARED.question(self.tr("Move {0} items to Trash?").format(len(self._items))):
|
1855
|
+
for tHandle in self._items:
|
1856
|
+
tItem = SHARED.project.tree[tHandle]
|
1857
|
+
if tItem and tItem.isFileType():
|
1858
|
+
self.projTree.moveItemToTrash(tHandle, askFirst=False, flush=False)
|
1859
|
+
self.projTree.saveTreeOrder()
|
1860
|
+
return
|
1861
|
+
|
1862
|
+
@pyqtSlot()
|
1863
|
+
def _toggleItemActive(self) -> None:
|
1864
|
+
"""Toggle the active status of an item."""
|
1865
|
+
self._item.setActive(not self._item.isActive)
|
1866
|
+
self.projTree.setTreeItemValues(self._handle)
|
1867
|
+
self.projTree._alertTreeChange(self._handle, flush=False)
|
1868
|
+
return
|
1869
|
+
|
1870
|
+
##
|
1871
|
+
# Internal Functions
|
1872
|
+
##
|
1873
|
+
|
1874
|
+
def _iterItemActive(self, isActive: bool) -> None:
|
1875
|
+
"""Set the active status of multiple items."""
|
1876
|
+
for tHandle in self._items:
|
1877
|
+
tItem = SHARED.project.tree[tHandle]
|
1878
|
+
if tItem and tItem.isFileType():
|
1879
|
+
tItem.setActive(isActive)
|
1880
|
+
self.projTree.setTreeItemValues(tHandle)
|
1881
|
+
self.projTree._alertTreeChange(tHandle, flush=False)
|
1882
|
+
return
|
1883
|
+
|
1884
|
+
def _changeItemStatus(self, key: str) -> None:
|
1885
|
+
"""Set a new status value of an item."""
|
1886
|
+
self._item.setStatus(key)
|
1887
|
+
self.projTree.setTreeItemValues(self._handle)
|
1888
|
+
self.projTree._alertTreeChange(self._handle, flush=False)
|
1889
|
+
return
|
1890
|
+
|
1891
|
+
def _iterSetItemStatus(self, key: str) -> None:
|
1892
|
+
"""Change the status value for multiple items."""
|
1893
|
+
for tHandle in self._items:
|
1894
|
+
tItem = SHARED.project.tree[tHandle]
|
1895
|
+
if tItem and tItem.isNovelLike():
|
1896
|
+
tItem.setStatus(key)
|
1897
|
+
self.projTree.setTreeItemValues(tHandle)
|
1898
|
+
self.projTree._alertTreeChange(tHandle, flush=False)
|
1899
|
+
return
|
1900
|
+
|
1901
|
+
def _changeItemImport(self, key: str) -> None:
|
1902
|
+
"""Set a new importance value of an item."""
|
1903
|
+
self._item.setImport(key)
|
1904
|
+
self.projTree.setTreeItemValues(self._handle)
|
1905
|
+
self.projTree._alertTreeChange(self._handle, flush=False)
|
1906
|
+
return
|
1907
|
+
|
1908
|
+
def _iterSetItemImport(self, key: str) -> None:
|
1909
|
+
"""Change the status value for multiple items."""
|
1910
|
+
for tHandle in self._items:
|
1911
|
+
tItem = SHARED.project.tree[tHandle]
|
1912
|
+
if tItem and not tItem.isNovelLike():
|
1913
|
+
tItem.setImport(key)
|
1914
|
+
self.projTree.setTreeItemValues(tHandle)
|
1915
|
+
self.projTree._alertTreeChange(tHandle, flush=False)
|
1916
|
+
return
|
1917
|
+
|
1918
|
+
def _changeItemLayout(self, itemLayout: nwItemLayout) -> None:
|
1919
|
+
"""Set a new item layout value of an item."""
|
1920
|
+
if itemLayout == nwItemLayout.DOCUMENT and self._item.documentAllowed():
|
1921
|
+
self._item.setLayout(nwItemLayout.DOCUMENT)
|
1922
|
+
self.projTree.setTreeItemValues(self._handle)
|
1923
|
+
self.projTree._alertTreeChange(self._handle, flush=False)
|
1924
|
+
elif itemLayout == nwItemLayout.NOTE:
|
1925
|
+
self._item.setLayout(nwItemLayout.NOTE)
|
1926
|
+
self.projTree.setTreeItemValues(self._handle)
|
1927
|
+
self.projTree._alertTreeChange(self._handle, flush=False)
|
1928
|
+
return
|
1929
|
+
|
1930
|
+
def _covertFolderToFile(self, itemLayout: nwItemLayout) -> None:
|
1931
|
+
"""Convert a folder to a note or document."""
|
1932
|
+
if self._item.isFolderType():
|
1933
|
+
msgYes = SHARED.question(self.tr(
|
1934
|
+
"Do you want to convert the folder to a {0}? "
|
1935
|
+
"This action cannot be reversed."
|
1936
|
+
).format(trConst(nwLabels.LAYOUT_NAME[itemLayout])))
|
1937
|
+
if msgYes and itemLayout == nwItemLayout.DOCUMENT and self._item.documentAllowed():
|
1938
|
+
self._item.setType(nwItemType.FILE)
|
1939
|
+
self._item.setLayout(nwItemLayout.DOCUMENT)
|
1940
|
+
self.projTree.setTreeItemValues(self._handle)
|
1941
|
+
self.projTree._alertTreeChange(self._handle, flush=False)
|
1942
|
+
elif msgYes and itemLayout == nwItemLayout.NOTE:
|
1943
|
+
self._item.setType(nwItemType.FILE)
|
1944
|
+
self._item.setLayout(nwItemLayout.NOTE)
|
1945
|
+
self.projTree.setTreeItemValues(self._handle)
|
1946
|
+
self.projTree._alertTreeChange(self._handle, flush=False)
|
1947
|
+
else:
|
1948
|
+
logger.info("Folder conversion cancelled")
|
1949
|
+
return
|
1950
|
+
|
1951
|
+
# END Class _TreeContextMenu
|