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.
Files changed (110) hide show
  1. {novelWriter-2.2.1.dist-info → novelWriter-2.3b1.dist-info}/METADATA +1 -1
  2. {novelWriter-2.2.1.dist-info → novelWriter-2.3b1.dist-info}/RECORD +102 -92
  3. novelwriter/__init__.py +4 -4
  4. novelwriter/assets/icons/typicons_dark/icons.conf +6 -0
  5. novelwriter/assets/icons/typicons_dark/mixed_import.svg +5 -0
  6. novelwriter/assets/icons/typicons_dark/typ_document-add-col.svg +8 -0
  7. novelwriter/assets/icons/typicons_dark/typ_document-add.svg +4 -0
  8. novelwriter/assets/icons/typicons_dark/typ_document.svg +4 -0
  9. novelwriter/assets/icons/typicons_dark/typ_th-dot-more.svg +4 -0
  10. novelwriter/assets/icons/typicons_light/icons.conf +6 -0
  11. novelwriter/assets/icons/typicons_light/mixed_import.svg +5 -0
  12. novelwriter/assets/icons/typicons_light/typ_document-add-col.svg +8 -0
  13. novelwriter/assets/icons/typicons_light/typ_document-add.svg +4 -0
  14. novelwriter/assets/icons/typicons_light/typ_document.svg +4 -0
  15. novelwriter/assets/icons/typicons_light/typ_th-dot-more.svg +4 -0
  16. novelwriter/assets/images/novelwriter-text-dark.svg +4 -0
  17. novelwriter/assets/images/novelwriter-text-light.svg +4 -0
  18. novelwriter/assets/images/welcome-dark.jpg +0 -0
  19. novelwriter/assets/images/welcome-light.jpg +0 -0
  20. novelwriter/assets/manual.pdf +0 -0
  21. novelwriter/assets/sample.zip +0 -0
  22. novelwriter/assets/syntax/default_dark.conf +1 -0
  23. novelwriter/assets/syntax/default_light.conf +1 -0
  24. novelwriter/assets/syntax/grey_dark.conf +1 -0
  25. novelwriter/assets/syntax/grey_light.conf +1 -0
  26. novelwriter/assets/syntax/light_owl.conf +1 -0
  27. novelwriter/assets/syntax/night_owl.conf +1 -0
  28. novelwriter/assets/syntax/solarized_dark.conf +1 -0
  29. novelwriter/assets/syntax/solarized_light.conf +1 -0
  30. novelwriter/assets/syntax/tomorrow.conf +1 -0
  31. novelwriter/assets/syntax/tomorrow_night.conf +1 -0
  32. novelwriter/assets/syntax/tomorrow_night_blue.conf +1 -0
  33. novelwriter/assets/syntax/tomorrow_night_bright.conf +1 -0
  34. novelwriter/assets/syntax/tomorrow_night_eighties.conf +1 -0
  35. novelwriter/assets/text/credits_en.htm +4 -2
  36. novelwriter/assets/themes/default_dark.conf +2 -2
  37. novelwriter/assets/themes/default_light.conf +2 -2
  38. novelwriter/common.py +48 -37
  39. novelwriter/config.py +36 -41
  40. novelwriter/constants.py +38 -16
  41. novelwriter/core/buildsettings.py +7 -7
  42. novelwriter/core/coretools.py +192 -154
  43. novelwriter/core/docbuild.py +6 -3
  44. novelwriter/core/document.py +6 -6
  45. novelwriter/core/index.py +89 -56
  46. novelwriter/core/item.py +21 -3
  47. novelwriter/core/options.py +8 -7
  48. novelwriter/core/project.py +69 -44
  49. novelwriter/core/projectdata.py +1 -14
  50. novelwriter/core/projectxml.py +13 -41
  51. novelwriter/core/sessions.py +2 -1
  52. novelwriter/core/spellcheck.py +2 -1
  53. novelwriter/core/status.py +2 -1
  54. novelwriter/core/storage.py +178 -140
  55. novelwriter/core/tohtml.py +4 -2
  56. novelwriter/core/tokenizer.py +73 -45
  57. novelwriter/core/toodt.py +40 -30
  58. novelwriter/core/tree.py +3 -2
  59. novelwriter/dialogs/about.py +70 -160
  60. novelwriter/dialogs/docmerge.py +6 -5
  61. novelwriter/dialogs/docsplit.py +6 -6
  62. novelwriter/dialogs/editlabel.py +1 -1
  63. novelwriter/dialogs/preferences.py +553 -703
  64. novelwriter/dialogs/{projsettings.py → projectsettings.py} +288 -262
  65. novelwriter/dialogs/quotes.py +27 -23
  66. novelwriter/dialogs/wordlist.py +96 -40
  67. novelwriter/enum.py +20 -18
  68. novelwriter/error.py +1 -1
  69. novelwriter/extensions/circularprogress.py +11 -11
  70. novelwriter/extensions/configlayout.py +185 -134
  71. novelwriter/extensions/modified.py +81 -0
  72. novelwriter/extensions/novelselector.py +26 -12
  73. novelwriter/extensions/pagedsidebar.py +14 -16
  74. novelwriter/extensions/simpleprogress.py +5 -5
  75. novelwriter/extensions/statusled.py +8 -8
  76. novelwriter/extensions/switch.py +31 -63
  77. novelwriter/extensions/switchbox.py +1 -1
  78. novelwriter/extensions/versioninfo.py +153 -0
  79. novelwriter/gui/doceditor.py +178 -150
  80. novelwriter/gui/dochighlight.py +63 -92
  81. novelwriter/gui/docviewer.py +49 -51
  82. novelwriter/gui/docviewerpanel.py +72 -24
  83. novelwriter/gui/itemdetails.py +7 -7
  84. novelwriter/gui/mainmenu.py +14 -18
  85. novelwriter/gui/noveltree.py +9 -8
  86. novelwriter/gui/outline.py +98 -75
  87. novelwriter/gui/projtree.py +188 -61
  88. novelwriter/gui/sidebar.py +3 -4
  89. novelwriter/gui/statusbar.py +3 -4
  90. novelwriter/gui/theme.py +60 -68
  91. novelwriter/guimain.py +49 -156
  92. novelwriter/shared.py +15 -1
  93. novelwriter/tools/dictionaries.py +5 -6
  94. novelwriter/tools/manuscript.py +6 -6
  95. novelwriter/tools/manussettings.py +192 -221
  96. novelwriter/tools/noveldetails.py +525 -0
  97. novelwriter/tools/welcome.py +802 -0
  98. novelwriter/tools/writingstats.py +9 -9
  99. novelwriter/assets/images/wizard-back.jpg +0 -0
  100. novelwriter/assets/text/gplv3_en.htm +0 -641
  101. novelwriter/assets/text/release_notes.htm +0 -60
  102. novelwriter/dialogs/projdetails.py +0 -518
  103. novelwriter/dialogs/projload.py +0 -294
  104. novelwriter/dialogs/updates.py +0 -172
  105. novelwriter/extensions/pageddialog.py +0 -130
  106. novelwriter/tools/projwizard.py +0 -478
  107. {novelWriter-2.2.1.dist-info → novelWriter-2.3b1.dist-info}/LICENSE.md +0 -0
  108. {novelWriter-2.2.1.dist-info → novelWriter-2.3b1.dist-info}/WHEEL +0 -0
  109. {novelWriter-2.2.1.dist-info → novelWriter-2.3b1.dist-info}/entry_points.txt +0 -0
  110. {novelWriter-2.2.1.dist-info → novelWriter-2.3b1.dist-info}/top_level.txt +0 -0
@@ -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 QDragEnterEvent, QDragMoveEvent, QDropEvent, QMouseEvent, QPalette
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, QLabel,
39
- QMenu, QShortcut, QSizePolicy, QToolButton, QTreeWidget, QTreeWidgetItem,
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.projsettings import GuiProjectSettings
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._treeSelectionChanged)
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 clearProjectView(self) -> None:
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.buildQuickLinkMenu()
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
- """If any root item changes, rebuild the quick link menu."""
229
- self.projBar.buildQuickLinkMenu()
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.buildQuickLinkMenu()
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 buildQuickLinkMenu(self) -> None:
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 n, (tHandle, nwItem) in enumerate(SHARED.project.tree.iterRoots(None)):
401
- aRoot = self.mQuick.addAction(nwItem.itemName)
402
- aRoot.setData(tHandle)
403
- aRoot.setIcon(SHARED.theme.getIcon(nwLabels.CLASS_ICON[nwItem.itemClass]))
404
- aRoot.triggered.connect(
405
- lambda n, tHandle=tHandle: self.projView.setSelectedHandle(tHandle, doScroll=True)
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 isNote:
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 hLevel > 0:
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 = SHARED.project.tree[tHandle]
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
- theList = []
824
+ items = []
789
825
  for i in range(self.topLevelItemCount()):
790
826
  item = self.topLevelItem(i)
791
827
  if isinstance(item, QTreeWidgetItem):
792
- theList = self._scanChildren(theList, item, i)
828
+ items = self._scanChildren(items, item, i)
793
829
  logger.debug("Saving project tree item order")
794
- SHARED.project.setTreeOrder(theList)
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
- theList = []
802
- theItem = self._getTreeItem(tHandle)
803
- if theItem is not None:
804
- theList = self._scanChildren(theList, theItem, 0)
805
- return theList
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
- theTrash = self.getTreeFromHandle(trashHandle)
861
- if trashHandle in theTrash:
862
- theTrash.remove(trashHandle)
896
+ trashItems = self.getTreeFromHandle(trashHandle)
897
+ if trashHandle in trashItems:
898
+ trashItems.remove(trashHandle)
863
899
 
864
- nTrash = len(theTrash)
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
- chCount = trItem.childCount()
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
- aLabel = self.addAction(self.tr("Rename"))
1679
- aLabel.triggered.connect(lambda: self.projTree.renameTreeItem(self._handle))
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.TAB_STATUS)
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.TAB_IMPORT)
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
@@ -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("Project Details")))
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.showProjectDetailsDialog)
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 = self.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)
@@ -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 = QColor(*SHARED.theme.statNone)
59
- colSaved = QColor(*SHARED.theme.statSaved)
60
- colUnsaved = QColor(*SHARED.theme.statUnsaved)
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