novelWriter 2.3rc1__py3-none-any.whl → 2.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {novelWriter-2.3rc1.dist-info → novelWriter-2.4.dist-info}/METADATA +5 -6
- {novelWriter-2.3rc1.dist-info → novelWriter-2.4.dist-info}/RECORD +119 -109
- {novelWriter-2.3rc1.dist-info → novelWriter-2.4.dist-info}/WHEEL +1 -1
- novelWriter-2.4.dist-info/entry_points.txt +2 -0
- novelwriter/__init__.py +17 -10
- novelwriter/assets/i18n/nw_de_DE.qm +0 -0
- novelwriter/assets/i18n/nw_en_US.qm +0 -0
- novelwriter/assets/i18n/nw_es_419.qm +0 -0
- novelwriter/assets/i18n/nw_fr_FR.qm +0 -0
- novelwriter/assets/i18n/nw_it_IT.qm +0 -0
- novelwriter/assets/i18n/nw_ja_JP.qm +0 -0
- novelwriter/assets/i18n/nw_nb_NO.qm +0 -0
- novelwriter/assets/i18n/nw_nl_NL.qm +0 -0
- novelwriter/assets/i18n/nw_pt_BR.qm +0 -0
- novelwriter/assets/i18n/nw_zh_CN.qm +0 -0
- novelwriter/assets/i18n/project_nl_NL.json +11 -0
- novelwriter/assets/i18n/project_pt_BR.json +11 -0
- novelwriter/assets/icons/none.svg +4 -0
- novelwriter/assets/icons/typicons_dark/icons.conf +4 -0
- novelwriter/assets/icons/typicons_dark/nw_tb-mark.svg +7 -0
- novelwriter/assets/icons/typicons_dark/typ_refresh-flipped.svg +1 -1
- novelwriter/assets/icons/typicons_dark/typ_refresh.svg +1 -1
- novelwriter/assets/icons/typicons_dark/typ_search-grey.svg +4 -0
- novelwriter/assets/icons/typicons_dark/typ_times.svg +1 -1
- novelwriter/assets/icons/typicons_dark/typ_unfold-hidden.svg +4 -0
- novelwriter/assets/icons/typicons_dark/typ_unfold-visible.svg +4 -0
- novelwriter/assets/icons/typicons_light/icons.conf +4 -0
- novelwriter/assets/icons/typicons_light/nw_tb-mark.svg +7 -0
- novelwriter/assets/icons/typicons_light/typ_refresh-flipped.svg +1 -1
- novelwriter/assets/icons/typicons_light/typ_refresh.svg +1 -1
- novelwriter/assets/icons/typicons_light/typ_search-grey.svg +4 -0
- novelwriter/assets/icons/typicons_light/typ_times.svg +1 -1
- novelwriter/assets/icons/typicons_light/typ_unfold-hidden.svg +4 -0
- novelwriter/assets/icons/typicons_light/typ_unfold-visible.svg +4 -0
- novelwriter/assets/manual.pdf +0 -0
- novelwriter/assets/sample.zip +0 -0
- novelwriter/assets/syntax/cyberpunk_night.conf +26 -0
- novelwriter/assets/syntax/default_dark.conf +1 -0
- novelwriter/assets/syntax/default_light.conf +1 -0
- novelwriter/assets/syntax/grey_dark.conf +1 -0
- novelwriter/assets/syntax/grey_light.conf +1 -0
- novelwriter/assets/syntax/light_owl.conf +1 -0
- novelwriter/assets/syntax/night_owl.conf +1 -0
- novelwriter/assets/syntax/solarized_dark.conf +1 -0
- novelwriter/assets/syntax/solarized_light.conf +1 -0
- novelwriter/assets/syntax/tango.conf +23 -0
- novelwriter/assets/syntax/tomorrow.conf +1 -0
- novelwriter/assets/syntax/tomorrow_night.conf +1 -0
- novelwriter/assets/syntax/tomorrow_night_blue.conf +1 -0
- novelwriter/assets/syntax/tomorrow_night_bright.conf +1 -0
- novelwriter/assets/syntax/tomorrow_night_eighties.conf +1 -0
- novelwriter/assets/text/credits_en.htm +25 -23
- novelwriter/assets/themes/cyberpunk_night.conf +29 -0
- novelwriter/common.py +12 -4
- novelwriter/config.py +47 -16
- novelwriter/constants.py +5 -6
- novelwriter/core/buildsettings.py +64 -44
- novelwriter/core/coretools.py +97 -13
- novelwriter/core/docbuild.py +74 -7
- novelwriter/core/document.py +24 -3
- novelwriter/core/index.py +31 -112
- novelwriter/core/project.py +11 -15
- novelwriter/core/projectxml.py +3 -2
- novelwriter/core/sessions.py +2 -2
- novelwriter/core/spellcheck.py +3 -3
- novelwriter/core/status.py +6 -5
- novelwriter/core/storage.py +16 -6
- novelwriter/core/tohtml.py +22 -25
- novelwriter/core/tokenizer.py +417 -237
- novelwriter/core/tomd.py +17 -8
- novelwriter/core/toodt.py +386 -351
- novelwriter/core/tree.py +8 -8
- novelwriter/dialogs/about.py +10 -12
- novelwriter/dialogs/docmerge.py +17 -14
- novelwriter/dialogs/docsplit.py +20 -19
- novelwriter/dialogs/editlabel.py +5 -4
- novelwriter/dialogs/preferences.py +32 -40
- novelwriter/dialogs/projectsettings.py +31 -28
- novelwriter/dialogs/quotes.py +10 -9
- novelwriter/dialogs/wordlist.py +18 -15
- novelwriter/enum.py +17 -14
- novelwriter/error.py +14 -12
- novelwriter/extensions/circularprogress.py +12 -8
- novelwriter/extensions/configlayout.py +23 -3
- novelwriter/extensions/modified.py +51 -2
- novelwriter/extensions/pagedsidebar.py +16 -14
- novelwriter/extensions/simpleprogress.py +3 -1
- novelwriter/extensions/statusled.py +3 -1
- novelwriter/extensions/switch.py +10 -9
- novelwriter/extensions/switchbox.py +14 -13
- novelwriter/extensions/versioninfo.py +1 -1
- novelwriter/gui/doceditor.py +433 -496
- novelwriter/gui/dochighlight.py +54 -33
- novelwriter/gui/docviewer.py +162 -175
- novelwriter/gui/docviewerpanel.py +20 -37
- novelwriter/gui/editordocument.py +15 -4
- novelwriter/gui/itemdetails.py +51 -54
- novelwriter/gui/mainmenu.py +37 -17
- novelwriter/gui/noveltree.py +31 -37
- novelwriter/gui/outline.py +120 -98
- novelwriter/gui/projtree.py +114 -112
- novelwriter/gui/search.py +362 -0
- novelwriter/gui/sidebar.py +36 -45
- novelwriter/gui/statusbar.py +14 -14
- novelwriter/gui/theme.py +116 -34
- novelwriter/guimain.py +216 -207
- novelwriter/shared.py +31 -6
- novelwriter/text/counting.py +138 -0
- novelwriter/tools/dictionaries.py +15 -14
- novelwriter/tools/lipsum.py +20 -17
- novelwriter/tools/manusbuild.py +43 -35
- novelwriter/tools/manuscript.py +381 -104
- novelwriter/tools/manussettings.py +263 -125
- novelwriter/tools/noveldetails.py +21 -19
- novelwriter/tools/welcome.py +59 -57
- novelwriter/tools/writingstats.py +61 -55
- novelwriter/types.py +90 -0
- novelWriter-2.3rc1.dist-info/entry_points.txt +0 -5
- novelwriter/core/__init__.py +0 -3
- novelwriter/dialogs/__init__.py +0 -3
- novelwriter/extensions/__init__.py +0 -3
- novelwriter/gui/__init__.py +0 -3
- novelwriter/tools/__init__.py +0 -3
- {novelWriter-2.3rc1.dist-info → novelWriter-2.4.dist-info}/LICENSE.md +0 -0
- {novelWriter-2.3rc1.dist-info → novelWriter-2.4.dist-info}/top_level.txt +0 -0
novelwriter/gui/projtree.py
CHANGED
@@ -32,26 +32,29 @@ 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
|
-
QLabel, QMenu, QShortcut, QSizePolicy,
|
42
|
-
|
41
|
+
QLabel, QMenu, QShortcut, QSizePolicy, QTreeWidget, QTreeWidgetItem,
|
42
|
+
QVBoxLayout, QWidget
|
43
43
|
)
|
44
44
|
|
45
45
|
from novelwriter import CONFIG, SHARED
|
46
|
-
from novelwriter.enum import nwDocMode, nwItemType, nwItemClass, nwItemLayout
|
47
46
|
from novelwriter.common import minmax
|
48
47
|
from novelwriter.constants import nwHeaders, nwUnicode, trConst, nwLabels
|
49
|
-
from novelwriter.core.item import NWItem
|
50
48
|
from novelwriter.core.coretools import DocDuplicator, DocMerger, DocSplitter
|
49
|
+
from novelwriter.core.item import NWItem
|
51
50
|
from novelwriter.dialogs.docmerge import GuiDocMerge
|
52
51
|
from novelwriter.dialogs.docsplit import GuiDocSplit
|
53
52
|
from novelwriter.dialogs.editlabel import GuiEditLabel
|
54
53
|
from novelwriter.dialogs.projectsettings import GuiProjectSettings
|
54
|
+
from novelwriter.enum import nwDocMode, nwItemType, nwItemClass, nwItemLayout
|
55
|
+
from novelwriter.extensions.modified import NIconToolButton
|
56
|
+
from novelwriter.gui.theme import STYLES_MIN_TOOLBUTTON
|
57
|
+
from novelwriter.types import QtAlignLeft, QtAlignRight, QtMouseLeft, QtMouseMiddle, QtUserRole
|
55
58
|
|
56
59
|
if TYPE_CHECKING: # pragma: no cover
|
57
60
|
from novelwriter.guimain import GuiMain
|
@@ -142,7 +145,6 @@ class GuiProjectView(QWidget):
|
|
142
145
|
self.requestDeleteItem = self.projTree.requestDeleteItem
|
143
146
|
self.getSelectedHandle = self.projTree.getSelectedHandle
|
144
147
|
self.changedSince = self.projTree.changedSince
|
145
|
-
self.createNewNote = self.projTree.createNewNote
|
146
148
|
|
147
149
|
return
|
148
150
|
|
@@ -240,6 +242,12 @@ class GuiProjectView(QWidget):
|
|
240
242
|
self.projBar.buildQuickLinksMenu()
|
241
243
|
return
|
242
244
|
|
245
|
+
@pyqtSlot(str, nwItemClass)
|
246
|
+
def createNewNote(self, tag: str, itemClass: nwItemClass) -> None:
|
247
|
+
"""Process new not request."""
|
248
|
+
self.projTree.createNewNote(tag, itemClass)
|
249
|
+
return
|
250
|
+
|
243
251
|
# END Class GuiProjectView
|
244
252
|
|
245
253
|
|
@@ -256,36 +264,33 @@ class GuiProjectToolBar(QWidget):
|
|
256
264
|
self.projTree = projView.projTree
|
257
265
|
self.mainGui = projView.mainGui
|
258
266
|
|
259
|
-
|
267
|
+
iSz = SHARED.theme.baseIconSize
|
260
268
|
mPx = CONFIG.pxInt(2)
|
261
269
|
|
262
270
|
self.setContentsMargins(0, 0, 0, 0)
|
263
271
|
self.setAutoFillBackground(True)
|
264
272
|
|
265
273
|
# Widget Label
|
266
|
-
self.viewLabel = QLabel(
|
274
|
+
self.viewLabel = QLabel(self.tr("Project Content"), self)
|
275
|
+
self.viewLabel.setFont(SHARED.theme.guiFontB)
|
267
276
|
self.viewLabel.setContentsMargins(0, 0, 0, 0)
|
268
277
|
self.viewLabel.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
269
278
|
|
270
279
|
# Quick Links
|
271
280
|
self.mQuick = QMenu(self)
|
272
281
|
|
273
|
-
self.tbQuick =
|
282
|
+
self.tbQuick = NIconToolButton(self, iSz)
|
274
283
|
self.tbQuick.setToolTip("%s [Ctrl+L]" % self.tr("Quick Links"))
|
275
284
|
self.tbQuick.setShortcut("Ctrl+L")
|
276
|
-
self.tbQuick.setIconSize(QSize(iPx, iPx))
|
277
285
|
self.tbQuick.setMenu(self.mQuick)
|
278
|
-
self.tbQuick.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup)
|
279
286
|
|
280
287
|
# Move Buttons
|
281
|
-
self.tbMoveU =
|
288
|
+
self.tbMoveU = NIconToolButton(self, iSz)
|
282
289
|
self.tbMoveU.setToolTip("%s [Ctrl+Up]" % self.tr("Move Up"))
|
283
|
-
self.tbMoveU.setIconSize(QSize(iPx, iPx))
|
284
290
|
self.tbMoveU.clicked.connect(lambda: self.projTree.moveTreeItem(-1))
|
285
291
|
|
286
|
-
self.tbMoveD =
|
292
|
+
self.tbMoveD = NIconToolButton(self, iSz)
|
287
293
|
self.tbMoveD.setToolTip("%s [Ctrl+Down]" % self.tr("Move Down"))
|
288
|
-
self.tbMoveD.setIconSize(QSize(iPx, iPx))
|
289
294
|
self.tbMoveD.clicked.connect(lambda: self.projTree.moveTreeItem(1))
|
290
295
|
|
291
296
|
# Add Item Menu
|
@@ -324,12 +329,10 @@ class GuiProjectToolBar(QWidget):
|
|
324
329
|
self.mAddRoot = self.mAdd.addMenu(trConst(nwLabels.ITEM_DESCRIPTION["root"]))
|
325
330
|
self._buildRootMenu()
|
326
331
|
|
327
|
-
self.tbAdd =
|
332
|
+
self.tbAdd = NIconToolButton(self, iSz)
|
328
333
|
self.tbAdd.setToolTip("%s [Ctrl+N]" % self.tr("Add Item"))
|
329
334
|
self.tbAdd.setShortcut("Ctrl+N")
|
330
|
-
self.tbAdd.setIconSize(QSize(iPx, iPx))
|
331
335
|
self.tbAdd.setMenu(self.mAdd)
|
332
|
-
self.tbAdd.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup)
|
333
336
|
|
334
337
|
# More Options Menu
|
335
338
|
self.mMore = QMenu(self)
|
@@ -343,11 +346,9 @@ class GuiProjectToolBar(QWidget):
|
|
343
346
|
self.aEmptyTrash = self.mMore.addAction(self.tr("Empty Trash"))
|
344
347
|
self.aEmptyTrash.triggered.connect(lambda: self.projTree.emptyTrash())
|
345
348
|
|
346
|
-
self.tbMore =
|
349
|
+
self.tbMore = NIconToolButton(self, iSz)
|
347
350
|
self.tbMore.setToolTip(self.tr("More Options"))
|
348
|
-
self.tbMore.setIconSize(QSize(iPx, iPx))
|
349
351
|
self.tbMore.setMenu(self.mMore)
|
350
|
-
self.tbMore.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup)
|
351
352
|
|
352
353
|
# Assemble
|
353
354
|
self.outerBox = QHBoxLayout()
|
@@ -377,29 +378,24 @@ class GuiProjectToolBar(QWidget):
|
|
377
378
|
qPalette.setBrush(QPalette.ColorRole.Window, qPalette.base())
|
378
379
|
self.setPalette(qPalette)
|
379
380
|
|
380
|
-
|
381
|
-
buttonStyle
|
382
|
-
"QToolButton {{padding: {0}px; border: none; background: transparent;}} "
|
383
|
-
"QToolButton:hover {{border: none; background: rgba({1},{2},{3},0.2);}}"
|
384
|
-
).format(CONFIG.pxInt(2), fadeCol.red(), fadeCol.green(), fadeCol.blue())
|
385
|
-
buttonStyleMenu = f"{buttonStyle} QToolButton::menu-indicator {{image: none;}}"
|
386
|
-
|
387
|
-
self.tbQuick.setStyleSheet(buttonStyleMenu)
|
381
|
+
buttonStyle = SHARED.theme.getStyleSheet(STYLES_MIN_TOOLBUTTON)
|
382
|
+
self.tbQuick.setStyleSheet(buttonStyle)
|
388
383
|
self.tbMoveU.setStyleSheet(buttonStyle)
|
389
384
|
self.tbMoveD.setStyleSheet(buttonStyle)
|
390
|
-
self.tbAdd.setStyleSheet(
|
391
|
-
self.tbMore.setStyleSheet(
|
385
|
+
self.tbAdd.setStyleSheet(buttonStyle)
|
386
|
+
self.tbMore.setStyleSheet(buttonStyle)
|
387
|
+
|
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")
|
392
393
|
|
393
|
-
self.tbQuick.setIcon(SHARED.theme.getIcon("bookmark"))
|
394
|
-
self.tbMoveU.setIcon(SHARED.theme.getIcon("up"))
|
395
|
-
self.tbMoveD.setIcon(SHARED.theme.getIcon("down"))
|
396
394
|
self.aAddEmpty.setIcon(SHARED.theme.getIcon("proj_document"))
|
397
395
|
self.aAddChap.setIcon(SHARED.theme.getIcon("proj_chapter"))
|
398
396
|
self.aAddScene.setIcon(SHARED.theme.getIcon("proj_scene"))
|
399
397
|
self.aAddNote.setIcon(SHARED.theme.getIcon("proj_note"))
|
400
398
|
self.aAddFolder.setIcon(SHARED.theme.getIcon("proj_folder"))
|
401
|
-
self.tbAdd.setIcon(SHARED.theme.getIcon("add"))
|
402
|
-
self.tbMore.setIcon(SHARED.theme.getIcon("menu"))
|
403
399
|
|
404
400
|
self.buildQuickLinksMenu()
|
405
401
|
self._buildRootMenu()
|
@@ -457,7 +453,7 @@ class GuiProjectToolBar(QWidget):
|
|
457
453
|
|
458
454
|
def _buildRootMenu(self) -> None:
|
459
455
|
"""Build the rood folder menu."""
|
460
|
-
def addClass(itemClass):
|
456
|
+
def addClass(itemClass: nwItemClass) -> None:
|
461
457
|
aNew = self.mAddRoot.addAction(trConst(nwLabels.CLASS_NAME[itemClass]))
|
462
458
|
aNew.setIcon(SHARED.theme.getIcon(nwLabels.CLASS_ICON[itemClass]))
|
463
459
|
aNew.triggered.connect(lambda: self.projTree.newTreeItem(nwItemType.ROOT, itemClass))
|
@@ -491,8 +487,8 @@ class GuiProjectTree(QTreeWidget):
|
|
491
487
|
C_ACTIVE = 2
|
492
488
|
C_STATUS = 3
|
493
489
|
|
494
|
-
D_HANDLE =
|
495
|
-
D_WORDS =
|
490
|
+
D_HANDLE = QtUserRole
|
491
|
+
D_WORDS = QtUserRole + 1
|
496
492
|
|
497
493
|
itemRefreshed = pyqtSignal(str, NWItem, QIcon)
|
498
494
|
|
@@ -505,10 +501,15 @@ class GuiProjectTree(QTreeWidget):
|
|
505
501
|
self.mainGui = projView.mainGui
|
506
502
|
|
507
503
|
# Internal Variables
|
508
|
-
self._treeMap = {}
|
504
|
+
self._treeMap: dict[str, QTreeWidgetItem] = {}
|
509
505
|
self._timeChanged = 0.0
|
510
506
|
self._popAlert = None
|
511
507
|
|
508
|
+
# Cached Translations
|
509
|
+
self.trActive = self.tr("Active")
|
510
|
+
self.trInactive = self.tr("Inactive")
|
511
|
+
self.trPermDelete = self.tr("Permanently delete {0} file(s) from Trash?")
|
512
|
+
|
512
513
|
# Build GUI
|
513
514
|
# =========
|
514
515
|
|
@@ -517,10 +518,10 @@ class GuiProjectTree(QTreeWidget):
|
|
517
518
|
self.customContextMenuRequested.connect(self._openContextMenu)
|
518
519
|
|
519
520
|
# Tree Settings
|
520
|
-
iPx = SHARED.theme.
|
521
|
+
iPx = SHARED.theme.baseIconHeight
|
521
522
|
cMg = CONFIG.pxInt(6)
|
522
523
|
|
523
|
-
self.setIconSize(
|
524
|
+
self.setIconSize(SHARED.theme.baseIconSize)
|
524
525
|
self.setFrameStyle(QFrame.Shape.NoFrame)
|
525
526
|
self.setUniformRowHeights(True)
|
526
527
|
self.setAllColumnsShowFocus(True)
|
@@ -555,10 +556,6 @@ class GuiProjectTree(QTreeWidget):
|
|
555
556
|
trRoot = self.invisibleRootItem()
|
556
557
|
trRoot.setFlags(trRoot.flags() ^ Qt.ItemFlag.ItemIsDropEnabled)
|
557
558
|
|
558
|
-
# Cached values
|
559
|
-
self._lblActive = self.tr("Active")
|
560
|
-
self._lblInactive = self.tr("Inactive")
|
561
|
-
|
562
559
|
# Set selection options
|
563
560
|
self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
|
564
561
|
self.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
|
@@ -568,9 +565,9 @@ class GuiProjectTree(QTreeWidget):
|
|
568
565
|
self.itemSelectionChanged.connect(self._treeSelectionChange)
|
569
566
|
|
570
567
|
# Auto Scroll
|
571
|
-
self._scrollMargin = SHARED.theme.
|
568
|
+
self._scrollMargin = SHARED.theme.baseIconHeight
|
572
569
|
self._scrollDirection = 0
|
573
|
-
self._scrollTimer = QTimer()
|
570
|
+
self._scrollTimer = QTimer(self)
|
574
571
|
self._scrollTimer.timeout.connect(self._doAutoScroll)
|
575
572
|
self._scrollTimer.setInterval(250)
|
576
573
|
|
@@ -605,18 +602,18 @@ class GuiProjectTree(QTreeWidget):
|
|
605
602
|
self._timeChanged = 0.0
|
606
603
|
return
|
607
604
|
|
608
|
-
def createNewNote(self, tag: str, itemClass: nwItemClass
|
605
|
+
def createNewNote(self, tag: str, itemClass: nwItemClass) -> None:
|
609
606
|
"""Create a new note. This function is used by the document
|
610
607
|
editor to create note files for unknown tags.
|
611
608
|
"""
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
609
|
+
if itemClass != nwItemClass.NO_CLASS:
|
610
|
+
if not (rHandle := SHARED.project.tree.findRoot(itemClass)):
|
611
|
+
self.newTreeItem(nwItemType.ROOT, itemClass)
|
612
|
+
rHandle = SHARED.project.tree.findRoot(itemClass)
|
613
|
+
if rHandle and (tHandle := SHARED.project.newFile(tag, rHandle)):
|
616
614
|
SHARED.project.writeNewFile(tHandle, 1, False, f"@tag: {tag}\n\n")
|
617
615
|
self.revealNewTreeItem(tHandle, wordCount=True)
|
618
|
-
|
619
|
-
return False
|
616
|
+
return
|
620
617
|
|
621
618
|
def newTreeItem(self, itemType: nwItemType, itemClass: nwItemClass | None = None,
|
622
619
|
hLevel: int = 1, isNote: bool = False, copyDoc: str | None = None) -> bool:
|
@@ -902,10 +899,7 @@ class GuiProjectTree(QTreeWidget):
|
|
902
899
|
SHARED.info(self.tr("The Trash folder is already empty."))
|
903
900
|
return False
|
904
901
|
|
905
|
-
|
906
|
-
self.tr("Permanently delete {0} file(s) from Trash?").format(nTrash)
|
907
|
-
)
|
908
|
-
if not msgYes:
|
902
|
+
if not SHARED.question(self.trPermDelete.format(nTrash)):
|
909
903
|
logger.info("Action cancelled by user")
|
910
904
|
return False
|
911
905
|
|
@@ -1052,7 +1046,7 @@ class GuiProjectTree(QTreeWidget):
|
|
1052
1046
|
|
1053
1047
|
if nwItem.isFileType():
|
1054
1048
|
iconName = "checked" if nwItem.isActive else "unchecked"
|
1055
|
-
toolTip = self.
|
1049
|
+
toolTip = self.trActive if nwItem.isActive else self.trInactive
|
1056
1050
|
trItem.setToolTip(self.C_ACTIVE, toolTip)
|
1057
1051
|
else:
|
1058
1052
|
iconName = "noncheckable"
|
@@ -1129,9 +1123,8 @@ class GuiProjectTree(QTreeWidget):
|
|
1129
1123
|
"""Get the currently selected handle. If multiple items are
|
1130
1124
|
selected, return the first.
|
1131
1125
|
"""
|
1132
|
-
|
1133
|
-
|
1134
|
-
return selItem[0].data(self.C_DATA, self.D_HANDLE)
|
1126
|
+
if items := self.selectedItems():
|
1127
|
+
return items[0].data(self.C_DATA, self.D_HANDLE)
|
1135
1128
|
return None
|
1136
1129
|
|
1137
1130
|
def setSelectedHandle(self, tHandle: str | None, doScroll: bool = False) -> bool:
|
@@ -1143,9 +1136,8 @@ class GuiProjectTree(QTreeWidget):
|
|
1143
1136
|
if tHandle in self._treeMap:
|
1144
1137
|
self.setCurrentItem(self._treeMap[tHandle])
|
1145
1138
|
|
1146
|
-
|
1147
|
-
|
1148
|
-
self.scrollTo(selIndex[0], QAbstractItemView.ScrollHint.PositionAtCenter)
|
1139
|
+
if (indexes := self.selectedIndexes()) and doScroll:
|
1140
|
+
self.scrollTo(indexes[0], QAbstractItemView.ScrollHint.PositionAtCenter)
|
1149
1141
|
|
1150
1142
|
return True
|
1151
1143
|
|
@@ -1160,10 +1152,8 @@ class GuiProjectTree(QTreeWidget):
|
|
1160
1152
|
|
1161
1153
|
def openContextOnSelected(self) -> bool:
|
1162
1154
|
"""Open the context menu on the current selected item."""
|
1163
|
-
|
1164
|
-
|
1165
|
-
pos = self.visualItemRect(selItem[0]).center()
|
1166
|
-
return self._openContextMenu(pos)
|
1155
|
+
if items := self.selectedItems():
|
1156
|
+
return self._openContextMenu(self.visualItemRect(items[0]).center())
|
1167
1157
|
return False
|
1168
1158
|
|
1169
1159
|
def changedSince(self, checkTime: float) -> bool:
|
@@ -1240,7 +1230,7 @@ class GuiProjectTree(QTreeWidget):
|
|
1240
1230
|
else:
|
1241
1231
|
ctxMenu.buildSingleSelectMenu(hasChild)
|
1242
1232
|
|
1243
|
-
ctxMenu.
|
1233
|
+
ctxMenu.exec(self.viewport().mapToGlobal(clickPos))
|
1244
1234
|
ctxMenu.deleteLater()
|
1245
1235
|
|
1246
1236
|
return True
|
@@ -1266,11 +1256,11 @@ class GuiProjectTree(QTreeWidget):
|
|
1266
1256
|
for viewing if the user middle-clicked.
|
1267
1257
|
"""
|
1268
1258
|
super().mousePressEvent(event)
|
1269
|
-
if event.button() ==
|
1259
|
+
if event.button() == QtMouseLeft:
|
1270
1260
|
selItem = self.indexAt(event.pos())
|
1271
1261
|
if not selItem.isValid():
|
1272
1262
|
self.clearSelection()
|
1273
|
-
elif event.button() ==
|
1263
|
+
elif event.button() == QtMouseMiddle:
|
1274
1264
|
selItem = self.itemAt(event.pos())
|
1275
1265
|
if selItem:
|
1276
1266
|
tHandle = selItem.data(self.C_DATA, self.D_HANDLE)
|
@@ -1278,7 +1268,7 @@ class GuiProjectTree(QTreeWidget):
|
|
1278
1268
|
self.projView.openDocumentRequest.emit(tHandle, nwDocMode.VIEW, "", False)
|
1279
1269
|
return
|
1280
1270
|
|
1281
|
-
def startDrag(self, dropAction: Qt.
|
1271
|
+
def startDrag(self, dropAction: Qt.DropAction) -> None:
|
1282
1272
|
"""Capture the drag and drop handling to pop alerts."""
|
1283
1273
|
super().startDrag(dropAction)
|
1284
1274
|
if self._popAlert:
|
@@ -1420,7 +1410,7 @@ class GuiProjectTree(QTreeWidget):
|
|
1420
1410
|
itemList.remove(tHandle)
|
1421
1411
|
|
1422
1412
|
dlgMerge = GuiDocMerge(self.mainGui, tHandle, itemList)
|
1423
|
-
dlgMerge.
|
1413
|
+
dlgMerge.exec()
|
1424
1414
|
|
1425
1415
|
if dlgMerge.result() == QDialog.DialogCode.Accepted:
|
1426
1416
|
|
@@ -1490,7 +1480,7 @@ class GuiProjectTree(QTreeWidget):
|
|
1490
1480
|
return False
|
1491
1481
|
|
1492
1482
|
dlgSplit = GuiDocSplit(self.mainGui, tHandle)
|
1493
|
-
dlgSplit.
|
1483
|
+
dlgSplit.exec()
|
1494
1484
|
|
1495
1485
|
if dlgSplit.result() == QDialog.DialogCode.Accepted:
|
1496
1486
|
|
@@ -1597,10 +1587,10 @@ class GuiProjectTree(QTreeWidget):
|
|
1597
1587
|
newItem.setText(self.C_ACTIVE, "")
|
1598
1588
|
newItem.setText(self.C_STATUS, "")
|
1599
1589
|
|
1600
|
-
newItem.setTextAlignment(self.C_NAME,
|
1601
|
-
newItem.setTextAlignment(self.C_COUNT,
|
1602
|
-
newItem.setTextAlignment(self.C_ACTIVE,
|
1603
|
-
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)
|
1604
1594
|
|
1605
1595
|
newItem.setData(self.C_DATA, self.D_HANDLE, tHandle)
|
1606
1596
|
newItem.setData(self.C_DATA, self.D_WORDS, 0)
|
@@ -1746,7 +1736,7 @@ class _TreeContextMenu(QMenu):
|
|
1746
1736
|
|
1747
1737
|
self._item = nwItem
|
1748
1738
|
self._handle = nwItem.itemHandle
|
1749
|
-
self._items: list[
|
1739
|
+
self._items: list[NWItem] = []
|
1750
1740
|
|
1751
1741
|
logger.debug("Ready: _TreeContextMenu")
|
1752
1742
|
|
@@ -1799,13 +1789,17 @@ class _TreeContextMenu(QMenu):
|
|
1799
1789
|
|
1800
1790
|
return
|
1801
1791
|
|
1802
|
-
def buildMultiSelectMenu(self,
|
1792
|
+
def buildMultiSelectMenu(self, handles: list[str]) -> None:
|
1803
1793
|
"""Build the multi-select menu."""
|
1804
|
-
self._items =
|
1794
|
+
self._items = []
|
1795
|
+
for tHandle in handles:
|
1796
|
+
if (tItem := SHARED.project.tree[tHandle]):
|
1797
|
+
self._items.append(tItem)
|
1798
|
+
|
1805
1799
|
self._itemActive(True)
|
1806
1800
|
self._itemStatusImport(True)
|
1807
1801
|
self.addSeparator()
|
1808
|
-
self.
|
1802
|
+
self._multiMoveToTrash()
|
1809
1803
|
return
|
1810
1804
|
|
1811
1805
|
##
|
@@ -1836,7 +1830,7 @@ class _TreeContextMenu(QMenu):
|
|
1836
1830
|
|
1837
1831
|
def _itemHeader(self) -> None:
|
1838
1832
|
"""Check if there is a header that can be used for rename."""
|
1839
|
-
if hItem := SHARED.project.index.
|
1833
|
+
if hItem := SHARED.project.index.getItemHeading(self._handle, "T0001"):
|
1840
1834
|
action = self.addAction(self.tr("Rename to Heading"))
|
1841
1835
|
action.triggered.connect(
|
1842
1836
|
lambda: self.projTree.renameTreeItem(self._handle, hItem.title)
|
@@ -1847,9 +1841,9 @@ class _TreeContextMenu(QMenu):
|
|
1847
1841
|
"""Add Active/Inactive actions."""
|
1848
1842
|
if multi:
|
1849
1843
|
mSub = self.addMenu(self.tr("Set Active to ..."))
|
1850
|
-
aOne = mSub.addAction(SHARED.theme.getIcon("checked"), self.
|
1844
|
+
aOne = mSub.addAction(SHARED.theme.getIcon("checked"), self.projTree.trActive)
|
1851
1845
|
aOne.triggered.connect(lambda: self._iterItemActive(True))
|
1852
|
-
aTwo = mSub.addAction(SHARED.theme.getIcon("unchecked"), self.
|
1846
|
+
aTwo = mSub.addAction(SHARED.theme.getIcon("unchecked"), self.projTree.trInactive)
|
1853
1847
|
aTwo.triggered.connect(lambda: self._iterItemActive(False))
|
1854
1848
|
else:
|
1855
1849
|
action = self.addAction(self.tr("Toggle Active"))
|
@@ -1935,7 +1929,7 @@ class _TreeContextMenu(QMenu):
|
|
1935
1929
|
action.triggered.connect(lambda: tree._mergeDocuments(tHandle, True))
|
1936
1930
|
|
1937
1931
|
if isFile:
|
1938
|
-
action = menu.addAction(self.tr("Split Document by
|
1932
|
+
action = menu.addAction(self.tr("Split Document by Headings"))
|
1939
1933
|
action.triggered.connect(lambda: tree._splitDocument(tHandle))
|
1940
1934
|
|
1941
1935
|
return
|
@@ -1949,11 +1943,9 @@ class _TreeContextMenu(QMenu):
|
|
1949
1943
|
action.triggered.connect(lambda: tree.setExpandedFromHandle(tHandle, True))
|
1950
1944
|
action = self.addAction(self.tr("Collapse All"))
|
1951
1945
|
action.triggered.connect(lambda: tree.setExpandedFromHandle(tHandle, False))
|
1952
|
-
|
1953
|
-
|
1954
|
-
|
1955
|
-
action = self.addAction(self.tr("Duplicate Document"))
|
1956
|
-
action.triggered.connect(lambda: tree._duplicateFromHandle(tHandle))
|
1946
|
+
|
1947
|
+
action = self.addAction(self.tr("Duplicate"))
|
1948
|
+
action.triggered.connect(lambda: tree._duplicateFromHandle(tHandle))
|
1957
1949
|
|
1958
1950
|
if self._item.itemClass == nwItemClass.TRASH or isRoot or (isFolder and not hasChild):
|
1959
1951
|
action = self.addAction(self.tr("Delete Permanently"))
|
@@ -1964,10 +1956,14 @@ class _TreeContextMenu(QMenu):
|
|
1964
1956
|
|
1965
1957
|
return
|
1966
1958
|
|
1967
|
-
def
|
1959
|
+
def _multiMoveToTrash(self) -> None:
|
1968
1960
|
"""Add move to Trash action."""
|
1969
|
-
|
1970
|
-
if
|
1961
|
+
areTrash = [i.itemClass == nwItemClass.TRASH for i in self._items]
|
1962
|
+
if all(areTrash):
|
1963
|
+
action = self.addAction(self.tr("Delete Permanently"))
|
1964
|
+
action.triggered.connect(self._iterPermDelete)
|
1965
|
+
elif not any(areTrash):
|
1966
|
+
action = self.addAction(self.tr("Move to Trash"))
|
1971
1967
|
action.triggered.connect(self._iterMoveToTrash)
|
1972
1968
|
return
|
1973
1969
|
|
@@ -1979,10 +1975,19 @@ class _TreeContextMenu(QMenu):
|
|
1979
1975
|
def _iterMoveToTrash(self) -> None:
|
1980
1976
|
"""Iterate through files and move them to Trash."""
|
1981
1977
|
if SHARED.question(self.tr("Move {0} items to Trash?").format(len(self._items))):
|
1982
|
-
for
|
1983
|
-
tItem
|
1984
|
-
|
1985
|
-
|
1978
|
+
for tItem in self._items:
|
1979
|
+
if tItem.isFileType() and tItem.itemClass != nwItemClass.TRASH:
|
1980
|
+
self.projTree.moveItemToTrash(tItem.itemHandle, askFirst=False, flush=False)
|
1981
|
+
self.projTree.saveTreeOrder()
|
1982
|
+
return
|
1983
|
+
|
1984
|
+
@pyqtSlot()
|
1985
|
+
def _iterPermDelete(self) -> None:
|
1986
|
+
"""Iterate through files and delete them."""
|
1987
|
+
if SHARED.question(self.projTree.trPermDelete.format(len(self._items))):
|
1988
|
+
for tItem in self._items:
|
1989
|
+
if tItem.isFileType() and tItem.itemClass == nwItemClass.TRASH:
|
1990
|
+
self.projTree.permDeleteItem(tItem.itemHandle, askFirst=False, flush=False)
|
1986
1991
|
self.projTree.saveTreeOrder()
|
1987
1992
|
return
|
1988
1993
|
|
@@ -2000,12 +2005,11 @@ class _TreeContextMenu(QMenu):
|
|
2000
2005
|
|
2001
2006
|
def _iterItemActive(self, isActive: bool) -> None:
|
2002
2007
|
"""Set the active status of multiple items."""
|
2003
|
-
for
|
2004
|
-
tItem = SHARED.project.tree[tHandle]
|
2008
|
+
for tItem in self._items:
|
2005
2009
|
if tItem and tItem.isFileType():
|
2006
2010
|
tItem.setActive(isActive)
|
2007
|
-
self.projTree.setTreeItemValues(
|
2008
|
-
self.projTree._alertTreeChange(
|
2011
|
+
self.projTree.setTreeItemValues(tItem.itemHandle)
|
2012
|
+
self.projTree._alertTreeChange(tItem.itemHandle, flush=False)
|
2009
2013
|
return
|
2010
2014
|
|
2011
2015
|
def _changeItemStatus(self, key: str) -> None:
|
@@ -2017,12 +2021,11 @@ class _TreeContextMenu(QMenu):
|
|
2017
2021
|
|
2018
2022
|
def _iterSetItemStatus(self, key: str) -> None:
|
2019
2023
|
"""Change the status value for multiple items."""
|
2020
|
-
for
|
2021
|
-
tItem = SHARED.project.tree[tHandle]
|
2024
|
+
for tItem in self._items:
|
2022
2025
|
if tItem and tItem.isNovelLike():
|
2023
2026
|
tItem.setStatus(key)
|
2024
|
-
self.projTree.setTreeItemValues(
|
2025
|
-
self.projTree._alertTreeChange(
|
2027
|
+
self.projTree.setTreeItemValues(tItem.itemHandle)
|
2028
|
+
self.projTree._alertTreeChange(tItem.itemHandle, flush=False)
|
2026
2029
|
return
|
2027
2030
|
|
2028
2031
|
def _changeItemImport(self, key: str) -> None:
|
@@ -2034,12 +2037,11 @@ class _TreeContextMenu(QMenu):
|
|
2034
2037
|
|
2035
2038
|
def _iterSetItemImport(self, key: str) -> None:
|
2036
2039
|
"""Change the status value for multiple items."""
|
2037
|
-
for
|
2038
|
-
tItem = SHARED.project.tree[tHandle]
|
2040
|
+
for tItem in self._items:
|
2039
2041
|
if tItem and not tItem.isNovelLike():
|
2040
2042
|
tItem.setImport(key)
|
2041
|
-
self.projTree.setTreeItemValues(
|
2042
|
-
self.projTree._alertTreeChange(
|
2043
|
+
self.projTree.setTreeItemValues(tItem.itemHandle)
|
2044
|
+
self.projTree._alertTreeChange(tItem.itemHandle, flush=False)
|
2043
2045
|
return
|
2044
2046
|
|
2045
2047
|
def _changeItemLayout(self, itemLayout: nwItemLayout) -> None:
|