novelWriter 2.2.1__py3-none-any.whl → 2.3b1__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.2.1.dist-info → novelWriter-2.3b1.dist-info}/METADATA +1 -1
- {novelWriter-2.2.1.dist-info → novelWriter-2.3b1.dist-info}/RECORD +102 -92
- novelwriter/__init__.py +4 -4
- novelwriter/assets/icons/typicons_dark/icons.conf +6 -0
- novelwriter/assets/icons/typicons_dark/mixed_import.svg +5 -0
- novelwriter/assets/icons/typicons_dark/typ_document-add-col.svg +8 -0
- novelwriter/assets/icons/typicons_dark/typ_document-add.svg +4 -0
- novelwriter/assets/icons/typicons_dark/typ_document.svg +4 -0
- novelwriter/assets/icons/typicons_dark/typ_th-dot-more.svg +4 -0
- novelwriter/assets/icons/typicons_light/icons.conf +6 -0
- novelwriter/assets/icons/typicons_light/mixed_import.svg +5 -0
- novelwriter/assets/icons/typicons_light/typ_document-add-col.svg +8 -0
- novelwriter/assets/icons/typicons_light/typ_document-add.svg +4 -0
- novelwriter/assets/icons/typicons_light/typ_document.svg +4 -0
- novelwriter/assets/icons/typicons_light/typ_th-dot-more.svg +4 -0
- novelwriter/assets/images/novelwriter-text-dark.svg +4 -0
- novelwriter/assets/images/novelwriter-text-light.svg +4 -0
- novelwriter/assets/images/welcome-dark.jpg +0 -0
- novelwriter/assets/images/welcome-light.jpg +0 -0
- novelwriter/assets/manual.pdf +0 -0
- novelwriter/assets/sample.zip +0 -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/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 +4 -2
- novelwriter/assets/themes/default_dark.conf +2 -2
- novelwriter/assets/themes/default_light.conf +2 -2
- novelwriter/common.py +48 -37
- novelwriter/config.py +36 -41
- novelwriter/constants.py +38 -16
- novelwriter/core/buildsettings.py +7 -7
- novelwriter/core/coretools.py +192 -154
- novelwriter/core/docbuild.py +6 -3
- novelwriter/core/document.py +6 -6
- novelwriter/core/index.py +89 -56
- novelwriter/core/item.py +21 -3
- novelwriter/core/options.py +8 -7
- novelwriter/core/project.py +69 -44
- novelwriter/core/projectdata.py +1 -14
- novelwriter/core/projectxml.py +13 -41
- novelwriter/core/sessions.py +2 -1
- novelwriter/core/spellcheck.py +2 -1
- novelwriter/core/status.py +2 -1
- novelwriter/core/storage.py +178 -140
- novelwriter/core/tohtml.py +4 -2
- novelwriter/core/tokenizer.py +73 -45
- novelwriter/core/toodt.py +40 -30
- novelwriter/core/tree.py +3 -2
- novelwriter/dialogs/about.py +70 -160
- novelwriter/dialogs/docmerge.py +6 -5
- novelwriter/dialogs/docsplit.py +6 -6
- novelwriter/dialogs/editlabel.py +1 -1
- novelwriter/dialogs/preferences.py +553 -703
- novelwriter/dialogs/{projsettings.py → projectsettings.py} +288 -262
- novelwriter/dialogs/quotes.py +27 -23
- novelwriter/dialogs/wordlist.py +96 -40
- novelwriter/enum.py +20 -18
- novelwriter/error.py +1 -1
- novelwriter/extensions/circularprogress.py +11 -11
- novelwriter/extensions/configlayout.py +185 -134
- novelwriter/extensions/modified.py +81 -0
- novelwriter/extensions/novelselector.py +26 -12
- novelwriter/extensions/pagedsidebar.py +14 -16
- novelwriter/extensions/simpleprogress.py +5 -5
- novelwriter/extensions/statusled.py +8 -8
- novelwriter/extensions/switch.py +31 -63
- novelwriter/extensions/switchbox.py +1 -1
- novelwriter/extensions/versioninfo.py +153 -0
- novelwriter/gui/doceditor.py +178 -150
- novelwriter/gui/dochighlight.py +63 -92
- novelwriter/gui/docviewer.py +49 -51
- novelwriter/gui/docviewerpanel.py +72 -24
- novelwriter/gui/itemdetails.py +7 -7
- novelwriter/gui/mainmenu.py +14 -18
- novelwriter/gui/noveltree.py +9 -8
- novelwriter/gui/outline.py +98 -75
- novelwriter/gui/projtree.py +188 -61
- novelwriter/gui/sidebar.py +3 -4
- novelwriter/gui/statusbar.py +3 -4
- novelwriter/gui/theme.py +60 -68
- novelwriter/guimain.py +49 -156
- novelwriter/shared.py +15 -1
- novelwriter/tools/dictionaries.py +5 -6
- novelwriter/tools/manuscript.py +6 -6
- novelwriter/tools/manussettings.py +192 -221
- novelwriter/tools/noveldetails.py +525 -0
- novelwriter/tools/welcome.py +802 -0
- novelwriter/tools/writingstats.py +9 -9
- novelwriter/assets/images/wizard-back.jpg +0 -0
- novelwriter/assets/text/gplv3_en.htm +0 -641
- novelwriter/assets/text/release_notes.htm +0 -60
- novelwriter/dialogs/projdetails.py +0 -518
- novelwriter/dialogs/projload.py +0 -294
- novelwriter/dialogs/updates.py +0 -172
- novelwriter/extensions/pageddialog.py +0 -130
- novelwriter/tools/projwizard.py +0 -478
- {novelWriter-2.2.1.dist-info → novelWriter-2.3b1.dist-info}/LICENSE.md +0 -0
- {novelWriter-2.2.1.dist-info → novelWriter-2.3b1.dist-info}/WHEEL +0 -0
- {novelWriter-2.2.1.dist-info → novelWriter-2.3b1.dist-info}/entry_points.txt +0 -0
- {novelWriter-2.2.1.dist-info → novelWriter-2.3b1.dist-info}/top_level.txt +0 -0
novelwriter/gui/projtree.py
CHANGED
@@ -32,12 +32,14 @@ from enum import Enum
|
|
32
32
|
from time import time
|
33
33
|
from typing import TYPE_CHECKING
|
34
34
|
|
35
|
-
from PyQt5.QtGui import
|
35
|
+
from PyQt5.QtGui import (
|
36
|
+
QDragEnterEvent, QDragMoveEvent, QDropEvent, QIcon, QMouseEvent, QPalette
|
37
|
+
)
|
36
38
|
from PyQt5.QtCore import QPoint, QTimer, Qt, QSize, pyqtSignal, pyqtSlot
|
37
39
|
from PyQt5.QtWidgets import (
|
38
|
-
QAbstractItemView, QDialog, QFrame, QHBoxLayout, QHeaderView,
|
39
|
-
QMenu, QShortcut, QSizePolicy, QToolButton, QTreeWidget,
|
40
|
-
QVBoxLayout, QWidget
|
40
|
+
QAbstractItemView, QAction, QDialog, QFrame, QHBoxLayout, QHeaderView,
|
41
|
+
QLabel, QMenu, QShortcut, QSizePolicy, QToolButton, QTreeWidget,
|
42
|
+
QTreeWidgetItem, QVBoxLayout, QWidget
|
41
43
|
)
|
42
44
|
|
43
45
|
from novelwriter import CONFIG, SHARED
|
@@ -49,7 +51,7 @@ from novelwriter.core.coretools import DocDuplicator, DocMerger, DocSplitter
|
|
49
51
|
from novelwriter.dialogs.docmerge import GuiDocMerge
|
50
52
|
from novelwriter.dialogs.docsplit import GuiDocSplit
|
51
53
|
from novelwriter.dialogs.editlabel import GuiEditLabel
|
52
|
-
from novelwriter.dialogs.
|
54
|
+
from novelwriter.dialogs.projectsettings import GuiProjectSettings
|
53
55
|
|
54
56
|
if TYPE_CHECKING: # pragma: no cover
|
55
57
|
from novelwriter.guimain import GuiMain
|
@@ -131,7 +133,9 @@ class GuiProjectView(QWidget):
|
|
131
133
|
self.keyContext.activated.connect(lambda: self.projTree.openContextOnSelected())
|
132
134
|
|
133
135
|
# Signals
|
134
|
-
self.selectedItemChanged.connect(self.projBar.
|
136
|
+
self.selectedItemChanged.connect(self.projBar.treeSelectionChanged)
|
137
|
+
self.projTree.itemRefreshed.connect(self.projBar.treeItemRefreshed)
|
138
|
+
self.projBar.newDocumentFromTemplate.connect(self.createFileFromTemplate)
|
135
139
|
|
136
140
|
# Function Mappings
|
137
141
|
self.emptyTrash = self.projTree.emptyTrash
|
@@ -157,7 +161,7 @@ class GuiProjectView(QWidget):
|
|
157
161
|
self.projTree.initSettings()
|
158
162
|
return
|
159
163
|
|
160
|
-
def
|
164
|
+
def closeProjectTasks(self) -> None:
|
161
165
|
"""Clear project-related GUI content."""
|
162
166
|
self.projBar.clearContent()
|
163
167
|
self.projBar.setEnabled(False)
|
@@ -166,7 +170,7 @@ class GuiProjectView(QWidget):
|
|
166
170
|
|
167
171
|
def openProjectTasks(self) -> None:
|
168
172
|
"""Run open project tasks."""
|
169
|
-
self.projBar.
|
173
|
+
self.projBar.buildQuickLinksMenu()
|
170
174
|
self.projBar.setEnabled(True)
|
171
175
|
return
|
172
176
|
|
@@ -212,10 +216,17 @@ class GuiProjectView(QWidget):
|
|
212
216
|
|
213
217
|
@pyqtSlot(str)
|
214
218
|
def updateItemValues(self, tHandle: str) -> None:
|
215
|
-
"""Update tree item"""
|
219
|
+
"""Update tree item."""
|
216
220
|
self.projTree.setTreeItemValues(tHandle)
|
217
221
|
return
|
218
222
|
|
223
|
+
@pyqtSlot(str)
|
224
|
+
def createFileFromTemplate(self, tHandle: str) -> None:
|
225
|
+
"""Create a new document from a template."""
|
226
|
+
logger.debug("Template selected: '%s'", tHandle)
|
227
|
+
self.projTree.newTreeItem(nwItemType.FILE, copyDoc=tHandle)
|
228
|
+
return
|
229
|
+
|
219
230
|
@pyqtSlot(str, int, int, int)
|
220
231
|
def updateCounts(self, tHandle: str, cCount: int, wCount: int, pCount: int) -> None:
|
221
232
|
"""Slot for updating the word count of a specific item."""
|
@@ -225,8 +236,8 @@ class GuiProjectView(QWidget):
|
|
225
236
|
|
226
237
|
@pyqtSlot(str)
|
227
238
|
def updateRootItem(self, tHandle: str) -> None:
|
228
|
-
"""
|
229
|
-
self.projBar.
|
239
|
+
"""Process root item changes."""
|
240
|
+
self.projBar.buildQuickLinksMenu()
|
230
241
|
return
|
231
242
|
|
232
243
|
# END Class GuiProjectView
|
@@ -234,6 +245,8 @@ class GuiProjectView(QWidget):
|
|
234
245
|
|
235
246
|
class GuiProjectToolBar(QWidget):
|
236
247
|
|
248
|
+
newDocumentFromTemplate = pyqtSignal(str)
|
249
|
+
|
237
250
|
def __init__(self, projView: GuiProjectView) -> None:
|
238
251
|
super().__init__(parent=projView)
|
239
252
|
|
@@ -303,6 +316,11 @@ class GuiProjectToolBar(QWidget):
|
|
303
316
|
lambda: self.projTree.newTreeItem(nwItemType.FOLDER)
|
304
317
|
)
|
305
318
|
|
319
|
+
self.mTemplates = _UpdatableMenu(self.mAdd)
|
320
|
+
self.mTemplates.setActionsVisible(False)
|
321
|
+
self.mTemplates.menuItemTriggered.connect(lambda h: self.newDocumentFromTemplate.emit(h))
|
322
|
+
self.mAdd.addMenu(self.mTemplates)
|
323
|
+
|
306
324
|
self.mAddRoot = self.mAdd.addMenu(trConst(nwLabels.ITEM_DESCRIPTION["root"]))
|
307
325
|
self._buildRootMenu()
|
308
326
|
|
@@ -383,7 +401,7 @@ class GuiProjectToolBar(QWidget):
|
|
383
401
|
self.tbAdd.setIcon(SHARED.theme.getIcon("add"))
|
384
402
|
self.tbMore.setIcon(SHARED.theme.getIcon("menu"))
|
385
403
|
|
386
|
-
self.
|
404
|
+
self.buildQuickLinksMenu()
|
387
405
|
self._buildRootMenu()
|
388
406
|
|
389
407
|
return
|
@@ -391,21 +409,48 @@ class GuiProjectToolBar(QWidget):
|
|
391
409
|
def clearContent(self) -> None:
|
392
410
|
"""Clear dynamic content on the tool bar."""
|
393
411
|
self.mQuick.clear()
|
412
|
+
self.mTemplates.clearMenu()
|
394
413
|
return
|
395
414
|
|
396
|
-
def
|
415
|
+
def buildQuickLinksMenu(self) -> None:
|
397
416
|
"""Build the quick link menu."""
|
398
417
|
logger.debug("Rebuilding quick links menu")
|
399
418
|
self.mQuick.clear()
|
400
|
-
for
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
lambda
|
419
|
+
for tHandle, nwItem in SHARED.project.tree.iterRoots(None):
|
420
|
+
action = self.mQuick.addAction(nwItem.itemName)
|
421
|
+
action.setData(tHandle)
|
422
|
+
action.setIcon(SHARED.theme.getIcon(nwLabels.CLASS_ICON[nwItem.itemClass]))
|
423
|
+
action.triggered.connect(
|
424
|
+
lambda _, tHandle=tHandle: self.projView.setSelectedHandle(tHandle, doScroll=True)
|
406
425
|
)
|
407
426
|
return
|
408
427
|
|
428
|
+
##
|
429
|
+
# Public Slots
|
430
|
+
##
|
431
|
+
|
432
|
+
@pyqtSlot(str, NWItem, QIcon)
|
433
|
+
def treeItemRefreshed(self, tHandle: str, nwItem: NWItem, icon: QIcon) -> None:
|
434
|
+
"""Process change in tree items to update menu content."""
|
435
|
+
if nwItem.isTemplateFile() and nwItem.isActive:
|
436
|
+
self.mTemplates.addUpdate(tHandle, nwItem.itemName, icon)
|
437
|
+
elif tHandle in self.mTemplates:
|
438
|
+
self.mTemplates.remove(tHandle)
|
439
|
+
return
|
440
|
+
|
441
|
+
@pyqtSlot(str)
|
442
|
+
def treeSelectionChanged(self, tHandle: str) -> None:
|
443
|
+
"""Toggle the visibility of the new item entries for novel
|
444
|
+
documents. They should only be visible if novel documents can
|
445
|
+
actually be added.
|
446
|
+
"""
|
447
|
+
nwItem = SHARED.project.tree[tHandle]
|
448
|
+
allowDoc = isinstance(nwItem, NWItem) and nwItem.documentAllowed()
|
449
|
+
self.aAddEmpty.setVisible(allowDoc)
|
450
|
+
self.aAddChap.setVisible(allowDoc)
|
451
|
+
self.aAddScene.setVisible(allowDoc)
|
452
|
+
return
|
453
|
+
|
409
454
|
##
|
410
455
|
# Internal Functions
|
411
456
|
##
|
@@ -421,7 +466,6 @@ class GuiProjectToolBar(QWidget):
|
|
421
466
|
|
422
467
|
self.mAddRoot.clear()
|
423
468
|
addClass(nwItemClass.NOVEL)
|
424
|
-
addClass(nwItemClass.ARCHIVE)
|
425
469
|
self.mAddRoot.addSeparator()
|
426
470
|
addClass(nwItemClass.PLOT)
|
427
471
|
addClass(nwItemClass.CHARACTER)
|
@@ -430,26 +474,12 @@ class GuiProjectToolBar(QWidget):
|
|
430
474
|
addClass(nwItemClass.OBJECT)
|
431
475
|
addClass(nwItemClass.ENTITY)
|
432
476
|
addClass(nwItemClass.CUSTOM)
|
477
|
+
self.mAddRoot.addSeparator()
|
478
|
+
addClass(nwItemClass.ARCHIVE)
|
479
|
+
addClass(nwItemClass.TEMPLATE)
|
433
480
|
|
434
481
|
return
|
435
482
|
|
436
|
-
##
|
437
|
-
# Private Slots
|
438
|
-
##
|
439
|
-
|
440
|
-
@pyqtSlot(str)
|
441
|
-
def _treeSelectionChanged(self, tHandle: str) -> None:
|
442
|
-
"""Toggle the visibility of the new item entries for novel
|
443
|
-
documents. They should only be visible if novel documents can
|
444
|
-
actually be added.
|
445
|
-
"""
|
446
|
-
nwItem = SHARED.project.tree[tHandle]
|
447
|
-
allowDoc = isinstance(nwItem, NWItem) and nwItem.documentAllowed()
|
448
|
-
self.aAddEmpty.setVisible(allowDoc)
|
449
|
-
self.aAddChap.setVisible(allowDoc)
|
450
|
-
self.aAddScene.setVisible(allowDoc)
|
451
|
-
return
|
452
|
-
|
453
483
|
# END Class GuiProjectToolBar
|
454
484
|
|
455
485
|
|
@@ -464,6 +494,8 @@ class GuiProjectTree(QTreeWidget):
|
|
464
494
|
D_HANDLE = Qt.ItemDataRole.UserRole
|
465
495
|
D_WORDS = Qt.ItemDataRole.UserRole + 1
|
466
496
|
|
497
|
+
itemRefreshed = pyqtSignal(str, NWItem, QIcon)
|
498
|
+
|
467
499
|
def __init__(self, projView: GuiProjectView) -> None:
|
468
500
|
super().__init__(parent=projView)
|
469
501
|
|
@@ -587,7 +619,7 @@ class GuiProjectTree(QTreeWidget):
|
|
587
619
|
return False
|
588
620
|
|
589
621
|
def newTreeItem(self, itemType: nwItemType, itemClass: nwItemClass | None = None,
|
590
|
-
hLevel: int = 1, isNote: bool = False) -> bool:
|
622
|
+
hLevel: int = 1, isNote: bool = False, copyDoc: str | None = None) -> bool:
|
591
623
|
"""Add new item to the tree, with a given itemType (and
|
592
624
|
itemClass if Root), and attach it to the selected handle. Also
|
593
625
|
make sure the item is added in a place it can be added, and that
|
@@ -627,7 +659,10 @@ class GuiProjectTree(QTreeWidget):
|
|
627
659
|
# Set default label and determine if new item is to be added
|
628
660
|
# as child or sibling to the selected item
|
629
661
|
if itemType == nwItemType.FILE:
|
630
|
-
if
|
662
|
+
if copyDoc and (cItem := SHARED.project.tree[copyDoc]):
|
663
|
+
newLabel = cItem.itemName
|
664
|
+
asChild = sIsParent and pItem.isDocumentLayout()
|
665
|
+
elif isNote:
|
631
666
|
newLabel = self.tr("New Note")
|
632
667
|
asChild = sIsParent
|
633
668
|
elif hLevel == 2:
|
@@ -675,7 +710,9 @@ class GuiProjectTree(QTreeWidget):
|
|
675
710
|
return True
|
676
711
|
|
677
712
|
# Handle new file creation
|
678
|
-
if itemType == nwItemType.FILE and
|
713
|
+
if itemType == nwItemType.FILE and copyDoc:
|
714
|
+
SHARED.project.copyFileContent(tHandle, copyDoc)
|
715
|
+
elif itemType == nwItemType.FILE and hLevel > 0:
|
679
716
|
SHARED.project.writeNewFile(tHandle, hLevel, not isNote)
|
680
717
|
|
681
718
|
# Add the new item to the project tree
|
@@ -770,8 +807,7 @@ class GuiProjectTree(QTreeWidget):
|
|
770
807
|
|
771
808
|
def renameTreeItem(self, tHandle: str, name: str = "") -> None:
|
772
809
|
"""Open a dialog to edit the label of an item."""
|
773
|
-
tItem
|
774
|
-
if tItem:
|
810
|
+
if tItem := SHARED.project.tree[tHandle]:
|
775
811
|
newLabel, dlgOk = GuiEditLabel.getLabel(self, text=name or tItem.itemName)
|
776
812
|
if dlgOk:
|
777
813
|
tItem.setName(newLabel)
|
@@ -785,24 +821,24 @@ class GuiProjectTree(QTreeWidget):
|
|
785
821
|
project structure, and must be called before any code that
|
786
822
|
depends on this order to be up to date.
|
787
823
|
"""
|
788
|
-
|
824
|
+
items = []
|
789
825
|
for i in range(self.topLevelItemCount()):
|
790
826
|
item = self.topLevelItem(i)
|
791
827
|
if isinstance(item, QTreeWidgetItem):
|
792
|
-
|
828
|
+
items = self._scanChildren(items, item, i)
|
793
829
|
logger.debug("Saving project tree item order")
|
794
|
-
SHARED.project.setTreeOrder(
|
830
|
+
SHARED.project.setTreeOrder(items)
|
795
831
|
return
|
796
832
|
|
797
833
|
def getTreeFromHandle(self, tHandle: str) -> list[str]:
|
798
834
|
"""Recursively return all the child items starting from a given
|
799
835
|
item handle.
|
800
836
|
"""
|
801
|
-
|
802
|
-
|
803
|
-
if
|
804
|
-
|
805
|
-
return
|
837
|
+
result = []
|
838
|
+
tIten = self._getTreeItem(tHandle)
|
839
|
+
if tIten is not None:
|
840
|
+
result = self._scanChildren(result, tIten, 0)
|
841
|
+
return result
|
806
842
|
|
807
843
|
def requestDeleteItem(self, tHandle: str | None = None) -> bool:
|
808
844
|
"""Request an item deleted from the project tree. This function
|
@@ -857,11 +893,11 @@ class GuiProjectTree(QTreeWidget):
|
|
857
893
|
SHARED.info(self.tr("There is currently no Trash folder in this project."))
|
858
894
|
return False
|
859
895
|
|
860
|
-
|
861
|
-
if trashHandle in
|
862
|
-
|
896
|
+
trashItems = self.getTreeFromHandle(trashHandle)
|
897
|
+
if trashHandle in trashItems:
|
898
|
+
trashItems.remove(trashHandle)
|
863
899
|
|
864
|
-
nTrash = len(
|
900
|
+
nTrash = len(trashItems)
|
865
901
|
if nTrash == 0:
|
866
902
|
SHARED.info(self.tr("The Trash folder is already empty."))
|
867
903
|
return False
|
@@ -1029,6 +1065,9 @@ class GuiProjectTree(QTreeWidget):
|
|
1029
1065
|
trFont.setUnderline(hLevel == "H1")
|
1030
1066
|
trItem.setFont(self.C_NAME, trFont)
|
1031
1067
|
|
1068
|
+
# Emit Refresh Signal
|
1069
|
+
self.itemRefreshed.emit(tHandle, nwItem, itemIcon)
|
1070
|
+
|
1032
1071
|
return
|
1033
1072
|
|
1034
1073
|
def propagateCount(self, tHandle: str, newCount: int, countChildren: bool = False) -> None:
|
@@ -1358,8 +1397,7 @@ class GuiProjectTree(QTreeWidget):
|
|
1358
1397
|
not including) a given item.
|
1359
1398
|
"""
|
1360
1399
|
if isinstance(trItem, QTreeWidgetItem):
|
1361
|
-
|
1362
|
-
for i in range(chCount):
|
1400
|
+
for i in range(trItem.childCount()):
|
1363
1401
|
chItem = trItem.child(i)
|
1364
1402
|
chItem.setExpanded(isExpanded)
|
1365
1403
|
self._recursiveSetExpanded(chItem, isExpanded)
|
@@ -1633,6 +1671,71 @@ class GuiProjectTree(QTreeWidget):
|
|
1633
1671
|
# END Class GuiProjectTree
|
1634
1672
|
|
1635
1673
|
|
1674
|
+
class _UpdatableMenu(QMenu):
|
1675
|
+
|
1676
|
+
menuItemTriggered = pyqtSignal(str)
|
1677
|
+
|
1678
|
+
def __init__(self, parent: QWidget) -> None:
|
1679
|
+
super().__init__(parent=parent)
|
1680
|
+
self._map: dict[str, QAction] = {}
|
1681
|
+
self.setTitle(self.tr("From Template"))
|
1682
|
+
self.triggered.connect(self._actionTriggered)
|
1683
|
+
return
|
1684
|
+
|
1685
|
+
def __contains__(self, tHandle: str) -> bool:
|
1686
|
+
"""Look up a handle in the menu."""
|
1687
|
+
return tHandle in self._map
|
1688
|
+
|
1689
|
+
##
|
1690
|
+
# Methods
|
1691
|
+
##
|
1692
|
+
|
1693
|
+
def addUpdate(self, tHandle: str, name: str, icon: QIcon) -> None:
|
1694
|
+
"""Add or update a template item."""
|
1695
|
+
if tHandle in self._map:
|
1696
|
+
action = self._map[tHandle]
|
1697
|
+
action.setText(name)
|
1698
|
+
action.setIcon(icon)
|
1699
|
+
else:
|
1700
|
+
action = QAction(icon, name, self)
|
1701
|
+
action.setData(tHandle)
|
1702
|
+
self.addAction(action)
|
1703
|
+
self._map[tHandle] = action
|
1704
|
+
self.setActionsVisible(True)
|
1705
|
+
return
|
1706
|
+
|
1707
|
+
def remove(self, tHandle: str) -> None:
|
1708
|
+
"""Remove a template item."""
|
1709
|
+
if action := self._map.pop(tHandle, None):
|
1710
|
+
self.removeAction(action)
|
1711
|
+
if not self._map:
|
1712
|
+
self.setActionsVisible(False)
|
1713
|
+
return
|
1714
|
+
|
1715
|
+
def clearMenu(self) -> None:
|
1716
|
+
"""Clear all menu content."""
|
1717
|
+
self._map.clear()
|
1718
|
+
self.clear()
|
1719
|
+
return
|
1720
|
+
|
1721
|
+
def setActionsVisible(self, value: bool) -> None:
|
1722
|
+
"""Set the visibility of root action."""
|
1723
|
+
self.menuAction().setVisible(value)
|
1724
|
+
return
|
1725
|
+
|
1726
|
+
##
|
1727
|
+
# Private Slots
|
1728
|
+
##
|
1729
|
+
|
1730
|
+
@pyqtSlot(QAction)
|
1731
|
+
def _actionTriggered(self, action: QAction) -> None:
|
1732
|
+
"""Translate the menu trigger into an item trigger."""
|
1733
|
+
self.menuItemTriggered.emit(str(action.data()))
|
1734
|
+
return
|
1735
|
+
|
1736
|
+
# END Class _UpdatableMenu
|
1737
|
+
|
1738
|
+
|
1636
1739
|
class _TreeContextMenu(QMenu):
|
1637
1740
|
|
1638
1741
|
def __init__(self, projTree: GuiProjectTree, nwItem: NWItem) -> None:
|
@@ -1674,10 +1777,15 @@ class _TreeContextMenu(QMenu):
|
|
1674
1777
|
self._docActions()
|
1675
1778
|
self.addSeparator()
|
1676
1779
|
|
1780
|
+
# Create New Items
|
1781
|
+
self._itemCreation()
|
1782
|
+
self.addSeparator()
|
1783
|
+
|
1677
1784
|
# Edit Item Settings
|
1678
|
-
|
1679
|
-
|
1785
|
+
action = self.addAction(self.tr("Rename"))
|
1786
|
+
action.triggered.connect(lambda: self.projTree.renameTreeItem(self._handle))
|
1680
1787
|
if isFile:
|
1788
|
+
self._itemHeader()
|
1681
1789
|
self._itemActive(False)
|
1682
1790
|
self._itemStatusImport(False)
|
1683
1791
|
|
@@ -1716,6 +1824,25 @@ class _TreeContextMenu(QMenu):
|
|
1716
1824
|
)
|
1717
1825
|
return
|
1718
1826
|
|
1827
|
+
def _itemCreation(self) -> None:
|
1828
|
+
"""Add create item actions."""
|
1829
|
+
menu = self.addMenu(self.tr("Create New ..."))
|
1830
|
+
menu.addAction(self.projView.projBar.aAddEmpty)
|
1831
|
+
menu.addAction(self.projView.projBar.aAddChap)
|
1832
|
+
menu.addAction(self.projView.projBar.aAddScene)
|
1833
|
+
menu.addAction(self.projView.projBar.aAddNote)
|
1834
|
+
menu.addAction(self.projView.projBar.aAddFolder)
|
1835
|
+
return
|
1836
|
+
|
1837
|
+
def _itemHeader(self) -> None:
|
1838
|
+
"""Check if there is a header that can be used for rename."""
|
1839
|
+
if hItem := SHARED.project.index.getItemHeader(self._handle, "T0001"):
|
1840
|
+
action = self.addAction(self.tr("Rename to Heading"))
|
1841
|
+
action.triggered.connect(
|
1842
|
+
lambda: self.projTree.renameTreeItem(self._handle, hItem.title)
|
1843
|
+
)
|
1844
|
+
return
|
1845
|
+
|
1719
1846
|
def _itemActive(self, multi: bool) -> None:
|
1720
1847
|
"""Add Active/Inactive actions."""
|
1721
1848
|
if multi:
|
@@ -1746,7 +1873,7 @@ class _TreeContextMenu(QMenu):
|
|
1746
1873
|
menu.addSeparator()
|
1747
1874
|
action = menu.addAction(self.tr("Manage Labels ..."))
|
1748
1875
|
action.triggered.connect(
|
1749
|
-
lambda: self.projView.projectSettingsRequest.emit(GuiProjectSettings.
|
1876
|
+
lambda: self.projView.projectSettingsRequest.emit(GuiProjectSettings.PAGE_STATUS)
|
1750
1877
|
)
|
1751
1878
|
else:
|
1752
1879
|
menu = self.addMenu(self.tr("Set Importance to ..."))
|
@@ -1763,13 +1890,13 @@ class _TreeContextMenu(QMenu):
|
|
1763
1890
|
menu.addSeparator()
|
1764
1891
|
action = menu.addAction(self.tr("Manage Labels ..."))
|
1765
1892
|
action.triggered.connect(
|
1766
|
-
lambda: self.projView.projectSettingsRequest.emit(GuiProjectSettings.
|
1893
|
+
lambda: self.projView.projectSettingsRequest.emit(GuiProjectSettings.PAGE_IMPORT)
|
1767
1894
|
)
|
1768
1895
|
return
|
1769
1896
|
|
1770
1897
|
def _itemTransform(self, isFile: bool, isFolder: bool, hasChild: bool) -> None:
|
1771
1898
|
"""Add actions for the Transform menu."""
|
1772
|
-
menu = self.addMenu(self.tr("Transform"))
|
1899
|
+
menu = self.addMenu(self.tr("Transform ..."))
|
1773
1900
|
|
1774
1901
|
tree = self.projTree
|
1775
1902
|
tHandle = self._handle
|
novelwriter/gui/sidebar.py
CHANGED
@@ -79,9 +79,9 @@ class GuiSideBar(QWidget):
|
|
79
79
|
self.tbBuild.clicked.connect(self.mainGui.showBuildManuscriptDialog)
|
80
80
|
|
81
81
|
self.tbDetails = QToolButton(self)
|
82
|
-
self.tbDetails.setToolTip("{0} [Shift+F6]".format(self.tr("
|
82
|
+
self.tbDetails.setToolTip("{0} [Shift+F6]".format(self.tr("Novel Details")))
|
83
83
|
self.tbDetails.setIconSize(iconSize)
|
84
|
-
self.tbDetails.clicked.connect(self.mainGui.
|
84
|
+
self.tbDetails.clicked.connect(self.mainGui.showNovelDetailsDialog)
|
85
85
|
|
86
86
|
self.tbStats = QToolButton(self)
|
87
87
|
self.tbStats.setToolTip("{0} [F6]".format(self.tr("Writing Statistics")))
|
@@ -167,8 +167,7 @@ class _PopRightMenu(QMenu):
|
|
167
167
|
def event(self, event: QEvent) -> bool:
|
168
168
|
"""Overload the show event and move the menu popup location."""
|
169
169
|
if event.type() == QEvent.Show:
|
170
|
-
parent
|
171
|
-
if isinstance(parent, QWidget):
|
170
|
+
if isinstance(parent := self.parent(), QWidget):
|
172
171
|
offset = QPoint(parent.width(), parent.height() - self.height())
|
173
172
|
self.move(parent.mapToGlobal(offset))
|
174
173
|
return super(_PopRightMenu, self).event(event)
|
novelwriter/gui/statusbar.py
CHANGED
@@ -29,7 +29,6 @@ from time import time
|
|
29
29
|
from typing import TYPE_CHECKING, Literal
|
30
30
|
from datetime import datetime
|
31
31
|
|
32
|
-
from PyQt5.QtGui import QColor
|
33
32
|
from PyQt5.QtCore import pyqtSlot, QLocale
|
34
33
|
from PyQt5.QtWidgets import qApp, QStatusBar, QLabel
|
35
34
|
|
@@ -55,9 +54,9 @@ class GuiMainStatus(QStatusBar):
|
|
55
54
|
self._userIdle = False
|
56
55
|
self._debugInfo = False
|
57
56
|
|
58
|
-
colNone =
|
59
|
-
colSaved =
|
60
|
-
colUnsaved =
|
57
|
+
colNone = SHARED.theme.statNone
|
58
|
+
colSaved = SHARED.theme.statSaved
|
59
|
+
colUnsaved = SHARED.theme.statUnsaved
|
61
60
|
|
62
61
|
iPx = SHARED.theme.baseIconSize
|
63
62
|
|