novelWriter 2.5.3__py3-none-any.whl → 2.6b2__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 (83) hide show
  1. {novelWriter-2.5.3.dist-info → novelWriter-2.6b2.dist-info}/METADATA +1 -1
  2. {novelWriter-2.5.3.dist-info → novelWriter-2.6b2.dist-info}/RECORD +80 -60
  3. novelwriter/__init__.py +49 -10
  4. novelwriter/assets/i18n/project_en_GB.json +1 -0
  5. novelwriter/assets/icons/typicons_dark/icons.conf +8 -0
  6. novelwriter/assets/icons/typicons_dark/mixed_copy.svg +4 -0
  7. novelwriter/assets/icons/typicons_dark/mixed_margin-bottom.svg +6 -0
  8. novelwriter/assets/icons/typicons_dark/mixed_margin-left.svg +6 -0
  9. novelwriter/assets/icons/typicons_dark/mixed_margin-right.svg +6 -0
  10. novelwriter/assets/icons/typicons_dark/mixed_margin-top.svg +6 -0
  11. novelwriter/assets/icons/typicons_dark/mixed_size-height.svg +6 -0
  12. novelwriter/assets/icons/typicons_dark/mixed_size-width.svg +6 -0
  13. novelwriter/assets/icons/typicons_dark/nw_toolbar.svg +5 -0
  14. novelwriter/assets/icons/typicons_light/icons.conf +8 -0
  15. novelwriter/assets/icons/typicons_light/mixed_copy.svg +4 -0
  16. novelwriter/assets/icons/typicons_light/mixed_margin-bottom.svg +6 -0
  17. novelwriter/assets/icons/typicons_light/mixed_margin-left.svg +6 -0
  18. novelwriter/assets/icons/typicons_light/mixed_margin-right.svg +6 -0
  19. novelwriter/assets/icons/typicons_light/mixed_margin-top.svg +6 -0
  20. novelwriter/assets/icons/typicons_light/mixed_size-height.svg +6 -0
  21. novelwriter/assets/icons/typicons_light/mixed_size-width.svg +6 -0
  22. novelwriter/assets/icons/typicons_light/nw_toolbar.svg +5 -0
  23. novelwriter/assets/manual.pdf +0 -0
  24. novelwriter/assets/sample.zip +0 -0
  25. novelwriter/common.py +100 -2
  26. novelwriter/config.py +25 -15
  27. novelwriter/constants.py +168 -60
  28. novelwriter/core/buildsettings.py +66 -39
  29. novelwriter/core/coretools.py +145 -147
  30. novelwriter/core/docbuild.py +132 -170
  31. novelwriter/core/index.py +38 -37
  32. novelwriter/core/item.py +41 -8
  33. novelwriter/core/itemmodel.py +518 -0
  34. novelwriter/core/options.py +4 -1
  35. novelwriter/core/project.py +67 -89
  36. novelwriter/core/spellcheck.py +9 -14
  37. novelwriter/core/status.py +7 -5
  38. novelwriter/core/tree.py +268 -287
  39. novelwriter/dialogs/docmerge.py +7 -17
  40. novelwriter/dialogs/preferences.py +46 -33
  41. novelwriter/dialogs/projectsettings.py +5 -5
  42. novelwriter/enum.py +36 -23
  43. novelwriter/extensions/configlayout.py +27 -12
  44. novelwriter/extensions/modified.py +13 -1
  45. novelwriter/extensions/pagedsidebar.py +5 -5
  46. novelwriter/formats/shared.py +155 -0
  47. novelwriter/formats/todocx.py +1191 -0
  48. novelwriter/formats/tohtml.py +451 -0
  49. novelwriter/{core → formats}/tokenizer.py +487 -491
  50. novelwriter/formats/tomarkdown.py +217 -0
  51. novelwriter/{core → formats}/toodt.py +311 -432
  52. novelwriter/formats/toqdoc.py +484 -0
  53. novelwriter/formats/toraw.py +91 -0
  54. novelwriter/gui/doceditor.py +342 -284
  55. novelwriter/gui/dochighlight.py +96 -84
  56. novelwriter/gui/docviewer.py +88 -31
  57. novelwriter/gui/docviewerpanel.py +17 -25
  58. novelwriter/gui/editordocument.py +17 -2
  59. novelwriter/gui/itemdetails.py +25 -28
  60. novelwriter/gui/mainmenu.py +129 -63
  61. novelwriter/gui/noveltree.py +45 -47
  62. novelwriter/gui/outline.py +196 -249
  63. novelwriter/gui/projtree.py +594 -1241
  64. novelwriter/gui/search.py +9 -10
  65. novelwriter/gui/sidebar.py +7 -6
  66. novelwriter/gui/theme.py +10 -5
  67. novelwriter/guimain.py +100 -196
  68. novelwriter/shared.py +66 -27
  69. novelwriter/text/counting.py +2 -0
  70. novelwriter/text/patterns.py +168 -60
  71. novelwriter/tools/manusbuild.py +14 -12
  72. novelwriter/tools/manuscript.py +120 -78
  73. novelwriter/tools/manussettings.py +424 -291
  74. novelwriter/tools/welcome.py +4 -4
  75. novelwriter/tools/writingstats.py +3 -3
  76. novelwriter/types.py +23 -7
  77. novelwriter/core/tohtml.py +0 -530
  78. novelwriter/core/tomarkdown.py +0 -252
  79. novelwriter/core/toqdoc.py +0 -419
  80. {novelWriter-2.5.3.dist-info → novelWriter-2.6b2.dist-info}/LICENSE.md +0 -0
  81. {novelWriter-2.5.3.dist-info → novelWriter-2.6b2.dist-info}/WHEEL +0 -0
  82. {novelWriter-2.5.3.dist-info → novelWriter-2.6b2.dist-info}/entry_points.txt +0 -0
  83. {novelWriter-2.5.3.dist-info → novelWriter-2.6b2.dist-info}/top_level.txt +0 -0
@@ -42,14 +42,14 @@ from PyQt5.QtWidgets import (
42
42
 
43
43
  from novelwriter import CONFIG, SHARED
44
44
  from novelwriter.common import checkInt, formatFileFilter, makeFileNameSafe
45
- from novelwriter.constants import nwHeaders, nwKeyWords, nwLabels, trConst
46
- from novelwriter.enum import nwDocMode, nwItemClass, nwItemLayout, nwItemType, nwOutline
45
+ from novelwriter.constants import nwKeyWords, nwLabels, nwStats, nwStyles, trConst
46
+ from novelwriter.enum import nwChange, nwDocMode, nwItemClass, nwItemLayout, nwItemType, nwOutline
47
47
  from novelwriter.error import logException
48
48
  from novelwriter.extensions.configlayout import NColourLabel
49
49
  from novelwriter.extensions.novelselector import NovelSelector
50
50
  from novelwriter.types import (
51
51
  QtAlignLeftTop, QtAlignRight, QtAlignRightTop, QtDecoration,
52
- QtSizeExpanding, QtUserRole
52
+ QtScrollAlwaysOff, QtScrollAsNeeded, QtSizeExpanding, QtUserRole
53
53
  )
54
54
 
55
55
  logger = logging.getLogger(__name__)
@@ -135,11 +135,15 @@ class GuiOutlineView(QWidget):
135
135
  self.outlineBar.populateNovelList()
136
136
  self.outlineBar.setCurrentRoot(lastOutline)
137
137
  self.outlineBar.setEnabled(True)
138
+ self.outlineData.loadGuiSettings()
138
139
 
139
140
  return
140
141
 
141
142
  def closeProjectTasks(self) -> None:
142
143
  """Run closing project tasks."""
144
+ if self.outlineTree.wasRendered:
145
+ # If the panel hasn't been drawn yet, those values are incorrect
146
+ self.outlineData.saveGuiSettings()
143
147
  self.outlineTree.closeProjectTasks()
144
148
  self.outlineData.updateClasses()
145
149
  self.clearOutline()
@@ -161,8 +165,8 @@ class GuiOutlineView(QWidget):
161
165
  # Public Slots
162
166
  ##
163
167
 
164
- @pyqtSlot(str)
165
- def updateRootItem(self, tHandle: str) -> None:
168
+ @pyqtSlot(str, Enum)
169
+ def updateRootItem(self, tHandle: str, change: nwChange) -> None:
166
170
  """Handle tasks whenever a root folders changes."""
167
171
  self.outlineBar.populateNovelList()
168
172
  self.outlineData.updateClasses()
@@ -312,43 +316,49 @@ class GuiOutlineToolBar(QToolBar):
312
316
  class GuiOutlineTree(QTreeWidget):
313
317
 
314
318
  DEF_WIDTH = {
315
- nwOutline.TITLE: 200,
316
- nwOutline.LEVEL: 40,
317
- nwOutline.LABEL: 150,
318
- nwOutline.LINE: 40,
319
- nwOutline.CCOUNT: 50,
320
- nwOutline.WCOUNT: 50,
321
- nwOutline.PCOUNT: 50,
322
- nwOutline.POV: 100,
323
- nwOutline.FOCUS: 100,
324
- nwOutline.CHAR: 100,
325
- nwOutline.PLOT: 100,
326
- nwOutline.TIME: 100,
327
- nwOutline.WORLD: 100,
328
- nwOutline.OBJECT: 100,
329
- nwOutline.ENTITY: 100,
330
- nwOutline.CUSTOM: 100,
331
- nwOutline.SYNOP: 200,
319
+ nwOutline.TITLE: 200,
320
+ nwOutline.LEVEL: 40,
321
+ nwOutline.LABEL: 150,
322
+ nwOutline.LINE: 40,
323
+ nwOutline.STATUS: 100,
324
+ nwOutline.CCOUNT: 50,
325
+ nwOutline.WCOUNT: 50,
326
+ nwOutline.PCOUNT: 50,
327
+ nwOutline.POV: 100,
328
+ nwOutline.FOCUS: 100,
329
+ nwOutline.CHAR: 100,
330
+ nwOutline.PLOT: 100,
331
+ nwOutline.TIME: 100,
332
+ nwOutline.WORLD: 100,
333
+ nwOutline.OBJECT: 100,
334
+ nwOutline.ENTITY: 100,
335
+ nwOutline.CUSTOM: 100,
336
+ nwOutline.STORY: 100,
337
+ nwOutline.MENTION: 100,
338
+ nwOutline.SYNOP: 200,
332
339
  }
333
340
 
334
341
  DEF_HIDDEN = {
335
- nwOutline.TITLE: False,
336
- nwOutline.LEVEL: True,
337
- nwOutline.LABEL: False,
338
- nwOutline.LINE: True,
339
- nwOutline.CCOUNT: True,
340
- nwOutline.WCOUNT: False,
341
- nwOutline.PCOUNT: False,
342
- nwOutline.POV: False,
343
- nwOutline.FOCUS: True,
344
- nwOutline.CHAR: False,
345
- nwOutline.PLOT: False,
346
- nwOutline.TIME: True,
347
- nwOutline.WORLD: False,
348
- nwOutline.OBJECT: True,
349
- nwOutline.ENTITY: True,
350
- nwOutline.CUSTOM: True,
351
- nwOutline.SYNOP: False,
342
+ nwOutline.TITLE: False,
343
+ nwOutline.LEVEL: True,
344
+ nwOutline.LABEL: False,
345
+ nwOutline.LINE: True,
346
+ nwOutline.STATUS: True,
347
+ nwOutline.CCOUNT: True,
348
+ nwOutline.WCOUNT: False,
349
+ nwOutline.PCOUNT: False,
350
+ nwOutline.POV: False,
351
+ nwOutline.FOCUS: True,
352
+ nwOutline.CHAR: False,
353
+ nwOutline.PLOT: False,
354
+ nwOutline.TIME: True,
355
+ nwOutline.WORLD: False,
356
+ nwOutline.OBJECT: True,
357
+ nwOutline.ENTITY: True,
358
+ nwOutline.CUSTOM: True,
359
+ nwOutline.STORY: True,
360
+ nwOutline.MENTION: True,
361
+ nwOutline.SYNOP: False,
352
362
  }
353
363
 
354
364
  D_HANDLE = QtUserRole
@@ -370,8 +380,8 @@ class GuiOutlineTree(QTreeWidget):
370
380
  self.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
371
381
  self.setExpandsOnDoubleClick(False)
372
382
  self.setDragEnabled(False)
373
- self.itemDoubleClicked.connect(self._treeDoubleClick)
374
- self.itemSelectionChanged.connect(self._itemSelected)
383
+ self.itemDoubleClicked.connect(self._onItemDoubleClicked)
384
+ self.itemSelectionChanged.connect(self._onItemSelectionChanged)
375
385
 
376
386
  self.setIconSize(SHARED.theme.baseIconSize)
377
387
  self.setIndentation(0)
@@ -426,6 +436,11 @@ class GuiOutlineTree(QTreeWidget):
426
436
  def hiddenColumns(self) -> dict[nwOutline, bool]:
427
437
  return self._colHidden
428
438
 
439
+ @property
440
+ def wasRendered(self) -> bool:
441
+ """Returns True after the Outline has been rendered once."""
442
+ return not self._firstView
443
+
429
444
  ##
430
445
  # Methods
431
446
  ##
@@ -433,13 +448,13 @@ class GuiOutlineTree(QTreeWidget):
433
448
  def initSettings(self) -> None:
434
449
  """Set or update outline settings."""
435
450
  if CONFIG.hideVScroll:
436
- self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
451
+ self.setVerticalScrollBarPolicy(QtScrollAlwaysOff)
437
452
  else:
438
- self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
453
+ self.setVerticalScrollBarPolicy(QtScrollAsNeeded)
439
454
  if CONFIG.hideHScroll:
440
- self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
455
+ self.setHorizontalScrollBarPolicy(QtScrollAlwaysOff)
441
456
  else:
442
- self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
457
+ self.setHorizontalScrollBarPolicy(QtScrollAsNeeded)
443
458
  return
444
459
 
445
460
  def clearContent(self) -> None:
@@ -465,8 +480,10 @@ class GuiOutlineTree(QTreeWidget):
465
480
 
466
481
  return
467
482
 
468
- def refreshTree(self, rootHandle: str | None = None,
469
- overRide: bool = False, novelChanged: bool = False) -> None:
483
+ def refreshTree(
484
+ self, rootHandle: str | None = None,
485
+ overRide: bool = False, novelChanged: bool = False
486
+ ) -> None:
470
487
  """Called whenever the Outline tab is activated and controls
471
488
  what data to load, and if necessary, force a rebuild of the
472
489
  tree.
@@ -546,7 +563,7 @@ class GuiOutlineTree(QTreeWidget):
546
563
  ##
547
564
 
548
565
  @pyqtSlot("QTreeWidgetItem*", int)
549
- def _treeDoubleClick(self, tItem: QTreeWidgetItem, tCol: int) -> None:
566
+ def _onItemDoubleClicked(self, tItem: QTreeWidgetItem, tCol: int) -> None:
550
567
  """Extract the handle and line number of the title double-
551
568
  clicked, and send it to the main gui class for opening in the
552
569
  document editor.
@@ -557,14 +574,13 @@ class GuiOutlineTree(QTreeWidget):
557
574
  return
558
575
 
559
576
  @pyqtSlot()
560
- def _itemSelected(self) -> None:
577
+ def _onItemSelectionChanged(self) -> None:
561
578
  """Extract the handle and line number of the currently selected
562
579
  title, and send it to the details panel.
563
580
  """
564
- selItems = self.selectedItems()
565
- if selItems:
566
- tHandle = selItems[0].data(self._colIdx[nwOutline.TITLE], self.D_HANDLE)
567
- sTitle = selItems[0].data(self._colIdx[nwOutline.TITLE], self.D_TITLE)
581
+ if items := self.selectedItems():
582
+ tHandle = items[0].data(self._colIdx[nwOutline.TITLE], self.D_HANDLE)
583
+ sTitle = items[0].data(self._colIdx[nwOutline.TITLE], self.D_TITLE)
568
584
  self.activeItemChanged.emit(tHandle, sTitle)
569
585
  return
570
586
 
@@ -653,6 +669,7 @@ class GuiOutlineTree(QTreeWidget):
653
669
  is fast and doesn't require a rebuild of the tree.
654
670
  """
655
671
  logger.debug("Rebuilding Outline tree")
672
+ tStart = time()
656
673
  self.clear()
657
674
 
658
675
  if self._firstView:
@@ -681,45 +698,51 @@ class GuiOutlineTree(QTreeWidget):
681
698
  novStruct = SHARED.project.index.novelStructure(rootHandle=rootHandle, activeOnly=True)
682
699
  for _, tHandle, sTitle, novIdx in novStruct:
683
700
 
684
- iLevel = nwHeaders.H_LEVEL.get(novIdx.level, 0)
701
+ iLevel = nwStyles.H_LEVEL.get(novIdx.level, 0)
685
702
  nwItem = SHARED.project.tree[tHandle]
686
703
  if iLevel == 0 or nwItem is None:
687
704
  continue
705
+ sLabel, sIcon = nwItem.getImportStatus()
688
706
 
689
- trItem = QTreeWidgetItem()
707
+ item = QTreeWidgetItem()
690
708
  hDec = SHARED.theme.getHeaderDecoration(iLevel)
691
709
 
692
- trItem.setData(self._colIdx[nwOutline.TITLE], QtDecoration, hDec)
693
- trItem.setText(self._colIdx[nwOutline.TITLE], novIdx.title)
694
- trItem.setData(self._colIdx[nwOutline.TITLE], self.D_HANDLE, tHandle)
695
- trItem.setData(self._colIdx[nwOutline.TITLE], self.D_TITLE, sTitle)
696
- trItem.setFont(self._colIdx[nwOutline.TITLE], self._hFonts[iLevel])
697
- trItem.setText(self._colIdx[nwOutline.LEVEL], novIdx.level)
698
- trItem.setIcon(self._colIdx[nwOutline.LABEL], self._dIcon[nwItem.mainHeading])
699
- trItem.setText(self._colIdx[nwOutline.LABEL], nwItem.itemName)
700
- trItem.setText(self._colIdx[nwOutline.LINE], f"{novIdx.line:n}")
701
- trItem.setText(self._colIdx[nwOutline.SYNOP], novIdx.synopsis)
702
- trItem.setText(self._colIdx[nwOutline.CCOUNT], f"{novIdx.charCount:n}")
703
- trItem.setText(self._colIdx[nwOutline.WCOUNT], f"{novIdx.wordCount:n}")
704
- trItem.setText(self._colIdx[nwOutline.PCOUNT], f"{novIdx.paraCount:n}")
705
- trItem.setTextAlignment(self._colIdx[nwOutline.CCOUNT], QtAlignRight)
706
- trItem.setTextAlignment(self._colIdx[nwOutline.WCOUNT], QtAlignRight)
707
- trItem.setTextAlignment(self._colIdx[nwOutline.PCOUNT], QtAlignRight)
710
+ item.setData(self._colIdx[nwOutline.TITLE], QtDecoration, hDec)
711
+ item.setText(self._colIdx[nwOutline.TITLE], novIdx.title)
712
+ item.setData(self._colIdx[nwOutline.TITLE], self.D_HANDLE, tHandle)
713
+ item.setData(self._colIdx[nwOutline.TITLE], self.D_TITLE, sTitle)
714
+ item.setFont(self._colIdx[nwOutline.TITLE], self._hFonts[iLevel])
715
+ item.setText(self._colIdx[nwOutline.LEVEL], novIdx.level)
716
+ item.setIcon(self._colIdx[nwOutline.LABEL], self._dIcon[nwItem.mainHeading])
717
+ item.setText(self._colIdx[nwOutline.LABEL], nwItem.itemName)
718
+ item.setText(self._colIdx[nwOutline.LINE], f"{novIdx.line:n}")
719
+ item.setText(self._colIdx[nwOutline.STATUS], sLabel)
720
+ item.setIcon(self._colIdx[nwOutline.STATUS], sIcon)
721
+ item.setText(self._colIdx[nwOutline.SYNOP], novIdx.synopsis)
722
+ item.setText(self._colIdx[nwOutline.CCOUNT], f"{novIdx.charCount:n}")
723
+ item.setText(self._colIdx[nwOutline.WCOUNT], f"{novIdx.wordCount:n}")
724
+ item.setText(self._colIdx[nwOutline.PCOUNT], f"{novIdx.paraCount:n}")
725
+ item.setTextAlignment(self._colIdx[nwOutline.CCOUNT], QtAlignRight)
726
+ item.setTextAlignment(self._colIdx[nwOutline.WCOUNT], QtAlignRight)
727
+ item.setTextAlignment(self._colIdx[nwOutline.PCOUNT], QtAlignRight)
708
728
 
709
729
  refs = SHARED.project.index.getReferences(tHandle, sTitle)
710
- trItem.setText(self._colIdx[nwOutline.POV], ", ".join(refs[nwKeyWords.POV_KEY]))
711
- trItem.setText(self._colIdx[nwOutline.FOCUS], ", ".join(refs[nwKeyWords.FOCUS_KEY]))
712
- trItem.setText(self._colIdx[nwOutline.CHAR], ", ".join(refs[nwKeyWords.CHAR_KEY]))
713
- trItem.setText(self._colIdx[nwOutline.PLOT], ", ".join(refs[nwKeyWords.PLOT_KEY]))
714
- trItem.setText(self._colIdx[nwOutline.TIME], ", ".join(refs[nwKeyWords.TIME_KEY]))
715
- trItem.setText(self._colIdx[nwOutline.WORLD], ", ".join(refs[nwKeyWords.WORLD_KEY]))
716
- trItem.setText(self._colIdx[nwOutline.OBJECT], ", ".join(refs[nwKeyWords.OBJECT_KEY]))
717
- trItem.setText(self._colIdx[nwOutline.ENTITY], ", ".join(refs[nwKeyWords.ENTITY_KEY]))
718
- trItem.setText(self._colIdx[nwOutline.CUSTOM], ", ".join(refs[nwKeyWords.CUSTOM_KEY]))
719
-
720
- self.addTopLevelItem(trItem)
730
+ item.setText(self._colIdx[nwOutline.POV], ", ".join(refs[nwKeyWords.POV_KEY]))
731
+ item.setText(self._colIdx[nwOutline.FOCUS], ", ".join(refs[nwKeyWords.FOCUS_KEY]))
732
+ item.setText(self._colIdx[nwOutline.CHAR], ", ".join(refs[nwKeyWords.CHAR_KEY]))
733
+ item.setText(self._colIdx[nwOutline.PLOT], ", ".join(refs[nwKeyWords.PLOT_KEY]))
734
+ item.setText(self._colIdx[nwOutline.TIME], ", ".join(refs[nwKeyWords.TIME_KEY]))
735
+ item.setText(self._colIdx[nwOutline.WORLD], ", ".join(refs[nwKeyWords.WORLD_KEY]))
736
+ item.setText(self._colIdx[nwOutline.OBJECT], ", ".join(refs[nwKeyWords.OBJECT_KEY]))
737
+ item.setText(self._colIdx[nwOutline.ENTITY], ", ".join(refs[nwKeyWords.ENTITY_KEY]))
738
+ item.setText(self._colIdx[nwOutline.CUSTOM], ", ".join(refs[nwKeyWords.CUSTOM_KEY]))
739
+ item.setText(self._colIdx[nwOutline.STORY], ", ".join(refs[nwKeyWords.STORY_KEY]))
740
+ item.setText(self._colIdx[nwOutline.MENTION], ", ".join(refs[nwKeyWords.MENTION_KEY]))
741
+
742
+ self.addTopLevelItem(item)
721
743
 
722
744
  self._lastBuild = time()
745
+ logger.debug("Project outline built in %.3f ms", 1000.0*(time() - tStart))
723
746
 
724
747
  return
725
748
 
@@ -793,6 +816,10 @@ class GuiOutlineDetails(QScrollArea):
793
816
 
794
817
  bFont = SHARED.theme.guiFontB
795
818
 
819
+ trStats1 = trConst(nwLabels.STATS_NAME[nwStats.CHARS_ALL])
820
+ trStats2 = trConst(nwLabels.STATS_NAME[nwStats.WORDS_ALL])
821
+ trStats3 = trConst(nwLabels.STATS_NAME[nwStats.PARAGRAPHS])
822
+
796
823
  # Details Area
797
824
  self.titleLabel = QLabel(self.tr("Title"), self)
798
825
  self.fileLabel = QLabel(self.tr("Document"), self)
@@ -813,9 +840,9 @@ class GuiOutlineDetails(QScrollArea):
813
840
  self.itemValue.setMaximumWidth(maxTitle)
814
841
 
815
842
  # Stats Area
816
- self.cCLabel = QLabel(self.tr("Characters"), self)
817
- self.wCLabel = QLabel(self.tr("Words"), self)
818
- self.pCLabel = QLabel(self.tr("Paragraphs"), self)
843
+ self.cCLabel = QLabel(trStats1, self)
844
+ self.wCLabel = QLabel(trStats2, self)
845
+ self.pCLabel = QLabel(trStats3, self)
819
846
  self.cCValue = QLabel("", self)
820
847
  self.wCValue = QLabel("", self)
821
848
  self.pCValue = QLabel("", self)
@@ -842,96 +869,25 @@ class GuiOutlineDetails(QScrollArea):
842
869
  self.synopLWrap = QHBoxLayout()
843
870
  self.synopLWrap.addWidget(self.synopValue, 1)
844
871
 
845
- # Tags
846
- self.povKeyLabel = QLabel(trConst(nwLabels.KEY_NAME[nwKeyWords.POV_KEY]), self)
847
- self.focKeyLabel = QLabel(trConst(nwLabels.KEY_NAME[nwKeyWords.FOCUS_KEY]), self)
848
- self.chrKeyLabel = QLabel(trConst(nwLabels.KEY_NAME[nwKeyWords.CHAR_KEY]), self)
849
- self.pltKeyLabel = QLabel(trConst(nwLabels.KEY_NAME[nwKeyWords.PLOT_KEY]), self)
850
- self.timKeyLabel = QLabel(trConst(nwLabels.KEY_NAME[nwKeyWords.TIME_KEY]), self)
851
- self.wldKeyLabel = QLabel(trConst(nwLabels.KEY_NAME[nwKeyWords.WORLD_KEY]), self)
852
- self.objKeyLabel = QLabel(trConst(nwLabels.KEY_NAME[nwKeyWords.OBJECT_KEY]), self)
853
- self.entKeyLabel = QLabel(trConst(nwLabels.KEY_NAME[nwKeyWords.ENTITY_KEY]), self)
854
- self.cstKeyLabel = QLabel(trConst(nwLabels.KEY_NAME[nwKeyWords.CUSTOM_KEY]), self)
855
-
856
- self.povKeyLabel.setFont(bFont)
857
- self.focKeyLabel.setFont(bFont)
858
- self.chrKeyLabel.setFont(bFont)
859
- self.pltKeyLabel.setFont(bFont)
860
- self.timKeyLabel.setFont(bFont)
861
- self.wldKeyLabel.setFont(bFont)
862
- self.objKeyLabel.setFont(bFont)
863
- self.entKeyLabel.setFont(bFont)
864
- self.cstKeyLabel.setFont(bFont)
865
-
866
- self.povKeyLWrap = QHBoxLayout()
867
- self.focKeyLWrap = QHBoxLayout()
868
- self.chrKeyLWrap = QHBoxLayout()
869
- self.pltKeyLWrap = QHBoxLayout()
870
- self.timKeyLWrap = QHBoxLayout()
871
- self.wldKeyLWrap = QHBoxLayout()
872
- self.objKeyLWrap = QHBoxLayout()
873
- self.entKeyLWrap = QHBoxLayout()
874
- self.cstKeyLWrap = QHBoxLayout()
875
-
876
- self.povKeyValue = QLabel("", self)
877
- self.focKeyValue = QLabel("", self)
878
- self.chrKeyValue = QLabel("", self)
879
- self.pltKeyValue = QLabel("", self)
880
- self.timKeyValue = QLabel("", self)
881
- self.wldKeyValue = QLabel("", self)
882
- self.objKeyValue = QLabel("", self)
883
- self.entKeyValue = QLabel("", self)
884
- self.cstKeyValue = QLabel("", self)
885
-
886
- self.povKeyValue.setWordWrap(True)
887
- self.focKeyValue.setWordWrap(True)
888
- self.chrKeyValue.setWordWrap(True)
889
- self.pltKeyValue.setWordWrap(True)
890
- self.timKeyValue.setWordWrap(True)
891
- self.wldKeyValue.setWordWrap(True)
892
- self.objKeyValue.setWordWrap(True)
893
- self.entKeyValue.setWordWrap(True)
894
- self.cstKeyValue.setWordWrap(True)
895
-
896
- self.povKeyValue.linkActivated.connect(lambda x: self.itemTagClicked.emit(x))
897
- self.focKeyValue.linkActivated.connect(lambda x: self.itemTagClicked.emit(x))
898
- self.chrKeyValue.linkActivated.connect(lambda x: self.itemTagClicked.emit(x))
899
- self.pltKeyValue.linkActivated.connect(lambda x: self.itemTagClicked.emit(x))
900
- self.timKeyValue.linkActivated.connect(lambda x: self.itemTagClicked.emit(x))
901
- self.wldKeyValue.linkActivated.connect(lambda x: self.itemTagClicked.emit(x))
902
- self.objKeyValue.linkActivated.connect(lambda x: self.itemTagClicked.emit(x))
903
- self.entKeyValue.linkActivated.connect(lambda x: self.itemTagClicked.emit(x))
904
- self.cstKeyValue.linkActivated.connect(lambda x: self.itemTagClicked.emit(x))
905
-
906
- self.povKeyLWrap.addWidget(self.povKeyValue, 1)
907
- self.focKeyLWrap.addWidget(self.focKeyValue, 1)
908
- self.chrKeyLWrap.addWidget(self.chrKeyValue, 1)
909
- self.pltKeyLWrap.addWidget(self.pltKeyValue, 1)
910
- self.timKeyLWrap.addWidget(self.timKeyValue, 1)
911
- self.wldKeyLWrap.addWidget(self.wldKeyValue, 1)
912
- self.objKeyLWrap.addWidget(self.objKeyValue, 1)
913
- self.entKeyLWrap.addWidget(self.entKeyValue, 1)
914
- self.cstKeyLWrap.addWidget(self.cstKeyValue, 1)
915
-
916
872
  # Selected Item Details
917
- self.mainGroup = QGroupBox(self.tr("Title Details"), self)
918
873
  self.mainForm = QGridLayout()
874
+ self.mainGroup = QGroupBox(self.tr("Title Details"), self)
919
875
  self.mainGroup.setLayout(self.mainForm)
920
876
 
921
- self.mainForm.addWidget(self.titleLabel, 0, 0, 1, 1, QtAlignLeftTop)
922
- self.mainForm.addWidget(self.titleValue, 0, 1, 1, 1, QtAlignLeftTop)
923
- self.mainForm.addWidget(self.cCLabel, 0, 2, 1, 1, QtAlignLeftTop)
924
- self.mainForm.addWidget(self.cCValue, 0, 3, 1, 1, QtAlignRightTop)
925
- self.mainForm.addWidget(self.fileLabel, 1, 0, 1, 1, QtAlignLeftTop)
926
- self.mainForm.addWidget(self.fileValue, 1, 1, 1, 1, QtAlignLeftTop)
927
- self.mainForm.addWidget(self.wCLabel, 1, 2, 1, 1, QtAlignLeftTop)
928
- self.mainForm.addWidget(self.wCValue, 1, 3, 1, 1, QtAlignRightTop)
929
- self.mainForm.addWidget(self.itemLabel, 2, 0, 1, 1, QtAlignLeftTop)
930
- self.mainForm.addWidget(self.itemValue, 2, 1, 1, 1, QtAlignLeftTop)
931
- self.mainForm.addWidget(self.pCLabel, 2, 2, 1, 1, QtAlignLeftTop)
932
- self.mainForm.addWidget(self.pCValue, 2, 3, 1, 1, QtAlignRightTop)
933
- self.mainForm.addWidget(self.synopLabel, 3, 0, 1, 4, QtAlignLeftTop)
934
- self.mainForm.addLayout(self.synopLWrap, 4, 0, 1, 4, QtAlignLeftTop)
877
+ self.mainForm.addWidget(self.titleLabel, 0, 0, 1, 1, QtAlignLeftTop)
878
+ self.mainForm.addWidget(self.titleValue, 0, 1, 1, 1, QtAlignLeftTop)
879
+ self.mainForm.addWidget(self.cCLabel, 0, 2, 1, 1, QtAlignLeftTop)
880
+ self.mainForm.addWidget(self.cCValue, 0, 3, 1, 1, QtAlignRightTop)
881
+ self.mainForm.addWidget(self.fileLabel, 1, 0, 1, 1, QtAlignLeftTop)
882
+ self.mainForm.addWidget(self.fileValue, 1, 1, 1, 1, QtAlignLeftTop)
883
+ self.mainForm.addWidget(self.wCLabel, 1, 2, 1, 1, QtAlignLeftTop)
884
+ self.mainForm.addWidget(self.wCValue, 1, 3, 1, 1, QtAlignRightTop)
885
+ self.mainForm.addWidget(self.itemLabel, 2, 0, 1, 1, QtAlignLeftTop)
886
+ self.mainForm.addWidget(self.itemValue, 2, 1, 1, 1, QtAlignLeftTop)
887
+ self.mainForm.addWidget(self.pCLabel, 2, 2, 1, 1, QtAlignLeftTop)
888
+ self.mainForm.addWidget(self.pCValue, 2, 3, 1, 1, QtAlignRightTop)
889
+ self.mainForm.addWidget(self.synopLabel, 3, 0, 1, 4, QtAlignLeftTop)
890
+ self.mainForm.addLayout(self.synopLWrap, 4, 0, 1, 4, QtAlignLeftTop)
935
891
 
936
892
  self.mainForm.setColumnStretch(1, 1)
937
893
  self.mainForm.setRowStretch(4, 1)
@@ -939,45 +895,40 @@ class GuiOutlineDetails(QScrollArea):
939
895
  self.mainForm.setVerticalSpacing(vSpace)
940
896
 
941
897
  # Selected Item Tags
942
- self.tagsGroup = QGroupBox(self.tr("Reference Tags"), self)
943
898
  self.tagsForm = QGridLayout()
899
+ self.tagsGroup = QGroupBox(self.tr("Reference Tags"), self)
944
900
  self.tagsGroup.setLayout(self.tagsForm)
945
901
 
946
- self.tagsForm.addWidget(self.povKeyLabel, 0, 0, 1, 1, QtAlignLeftTop)
947
- self.tagsForm.addLayout(self.povKeyLWrap, 0, 1, 1, 1, QtAlignLeftTop)
948
- self.tagsForm.addWidget(self.focKeyLabel, 1, 0, 1, 1, QtAlignLeftTop)
949
- self.tagsForm.addLayout(self.focKeyLWrap, 1, 1, 1, 1, QtAlignLeftTop)
950
- self.tagsForm.addWidget(self.chrKeyLabel, 2, 0, 1, 1, QtAlignLeftTop)
951
- self.tagsForm.addLayout(self.chrKeyLWrap, 2, 1, 1, 1, QtAlignLeftTop)
952
- self.tagsForm.addWidget(self.pltKeyLabel, 3, 0, 1, 1, QtAlignLeftTop)
953
- self.tagsForm.addLayout(self.pltKeyLWrap, 3, 1, 1, 1, QtAlignLeftTop)
954
- self.tagsForm.addWidget(self.timKeyLabel, 4, 0, 1, 1, QtAlignLeftTop)
955
- self.tagsForm.addLayout(self.timKeyLWrap, 4, 1, 1, 1, QtAlignLeftTop)
956
- self.tagsForm.addWidget(self.wldKeyLabel, 5, 0, 1, 1, QtAlignLeftTop)
957
- self.tagsForm.addLayout(self.wldKeyLWrap, 5, 1, 1, 1, QtAlignLeftTop)
958
- self.tagsForm.addWidget(self.objKeyLabel, 6, 0, 1, 1, QtAlignLeftTop)
959
- self.tagsForm.addLayout(self.objKeyLWrap, 6, 1, 1, 1, QtAlignLeftTop)
960
- self.tagsForm.addWidget(self.entKeyLabel, 7, 0, 1, 1, QtAlignLeftTop)
961
- self.tagsForm.addLayout(self.entKeyLWrap, 7, 1, 1, 1, QtAlignLeftTop)
962
- self.tagsForm.addWidget(self.cstKeyLabel, 8, 0, 1, 1, QtAlignLeftTop)
963
- self.tagsForm.addLayout(self.cstKeyLWrap, 8, 1, 1, 1, QtAlignLeftTop)
902
+ self.tagValues: dict[str, tuple[QLabel, QLabel]] = {}
903
+ for key in nwKeyWords.ALL_KEYS:
904
+ label = QLabel(trConst(nwLabels.KEY_NAME[key]), self)
905
+ label.setFont(bFont)
906
+ value = QLabel("", self)
907
+ value.setWordWrap(True)
908
+ value.linkActivated.connect(lambda x: self.itemTagClicked.emit(x))
909
+ layout = QHBoxLayout()
910
+ layout.addWidget(value, 1)
911
+ n = len(self.tagValues)
912
+ self.tagsForm.addWidget(label, n, 0, QtAlignLeftTop)
913
+ self.tagsForm.addLayout(layout, n, 1, QtAlignLeftTop)
914
+ self.tagValues[key] = (label, value)
964
915
 
965
916
  self.tagsForm.setColumnStretch(1, 1)
966
- self.tagsForm.setRowStretch(8, 1)
917
+ self.tagsForm.setRowStretch(len(self.tagValues), 1)
967
918
  self.tagsForm.setHorizontalSpacing(hSpace)
968
919
  self.tagsForm.setVerticalSpacing(vSpace)
969
920
 
970
921
  # Assemble
971
- self.outerWidget = QWidget(self)
972
- self.outerBox = QHBoxLayout()
973
- self.outerBox.addWidget(self.mainGroup, 0)
974
- self.outerBox.addWidget(self.tagsGroup, 1)
922
+ self.mainSplit = QSplitter(Qt.Orientation.Horizontal)
923
+ self.mainSplit.addWidget(self.mainGroup)
924
+ self.mainSplit.addWidget(self.tagsGroup)
925
+ self.mainSplit.setCollapsible(0, False)
926
+ self.mainSplit.setCollapsible(1, False)
975
927
 
976
- self.outerWidget.setLayout(self.outerBox)
977
- self.setWidget(self.outerWidget)
928
+ self.setWidget(self.mainSplit)
978
929
 
979
- self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
980
- self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
930
+ self.setHorizontalScrollBarPolicy(QtScrollAsNeeded)
931
+ self.setVerticalScrollBarPolicy(QtScrollAsNeeded)
981
932
  self.setWidgetResizable(True)
982
933
  self.setFrameStyle(QFrame.Shape.NoFrame)
983
934
 
@@ -990,35 +941,53 @@ class GuiOutlineDetails(QScrollArea):
990
941
  def initSettings(self) -> None:
991
942
  """Set or update outline settings."""
992
943
  if CONFIG.hideVScroll:
993
- self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
944
+ self.setVerticalScrollBarPolicy(QtScrollAlwaysOff)
994
945
  else:
995
- self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
946
+ self.setVerticalScrollBarPolicy(QtScrollAsNeeded)
996
947
  if CONFIG.hideHScroll:
997
- self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
948
+ self.setHorizontalScrollBarPolicy(QtScrollAlwaysOff)
998
949
  else:
999
- self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
950
+ self.setHorizontalScrollBarPolicy(QtScrollAsNeeded)
1000
951
  self.updateClasses()
1001
952
  return
1002
953
 
954
+ def loadGuiSettings(self) -> None:
955
+ """Run open project tasks."""
956
+ parent = self.outlineView.parent() # This widget is rendered already
957
+ width = parent.width() if isinstance(parent, QWidget) else 1000
958
+ pOptions = SHARED.project.options
959
+ self.mainSplit.setSizes([
960
+ CONFIG.pxInt(pOptions.getInt("GuiOutlineDetails", "detailsWidth", width//3)),
961
+ CONFIG.pxInt(pOptions.getInt("GuiOutlineDetails", "tagsWidth", 2*width//3))
962
+ ])
963
+ return
964
+
965
+ def saveGuiSettings(self) -> None:
966
+ """Run close project tasks."""
967
+ mainSplit = self.mainSplit.sizes()
968
+ detailsWidth = CONFIG.rpxInt(mainSplit[0])
969
+ tagsWidth = CONFIG.rpxInt(mainSplit[1])
970
+
971
+ logger.debug("Saving State: GuiOutlineDetails")
972
+ pOptions = SHARED.project.options
973
+ pOptions.setValue("GuiOutlineDetails", "detailsWidth", detailsWidth)
974
+ pOptions.setValue("GuiOutlineDetails", "tagsWidth", tagsWidth)
975
+ return
976
+
1003
977
  def clearDetails(self) -> None:
1004
978
  """Clear all the data labels."""
1005
979
  self.titleLabel.setText(self.tr("Title"))
1006
- self.titleValue.setText("")
1007
- self.fileValue.setText("")
1008
- self.itemValue.setText("")
1009
- self.cCValue.setText("")
1010
- self.wCValue.setText("")
1011
- self.pCValue.setText("")
1012
- self.synopValue.setText("")
1013
- self.povKeyValue.setText("")
1014
- self.focKeyValue.setText("")
1015
- self.chrKeyValue.setText("")
1016
- self.pltKeyValue.setText("")
1017
- self.timKeyValue.setText("")
1018
- self.wldKeyValue.setText("")
1019
- self.objKeyValue.setText("")
1020
- self.entKeyValue.setText("")
1021
- self.cstKeyValue.setText("")
980
+ self.titleValue.clear()
981
+ self.fileValue.clear()
982
+ self.itemValue.clear()
983
+ self.cCValue.clear()
984
+ self.wCValue.clear()
985
+ self.pCValue.clear()
986
+ self.synopValue.clear()
987
+
988
+ for _, value in self.tagValues.values():
989
+ value.clear()
990
+
1022
991
  self.updateClasses()
1023
992
  return
1024
993
 
@@ -1050,15 +1019,8 @@ class GuiOutlineDetails(QScrollArea):
1050
1019
 
1051
1020
  self.synopValue.setText(novIdx.synopsis)
1052
1021
 
1053
- self.povKeyValue.setText(self._formatTags(novRefs, nwKeyWords.POV_KEY))
1054
- self.focKeyValue.setText(self._formatTags(novRefs, nwKeyWords.FOCUS_KEY))
1055
- self.chrKeyValue.setText(self._formatTags(novRefs, nwKeyWords.CHAR_KEY))
1056
- self.pltKeyValue.setText(self._formatTags(novRefs, nwKeyWords.PLOT_KEY))
1057
- self.timKeyValue.setText(self._formatTags(novRefs, nwKeyWords.TIME_KEY))
1058
- self.wldKeyValue.setText(self._formatTags(novRefs, nwKeyWords.WORLD_KEY))
1059
- self.objKeyValue.setText(self._formatTags(novRefs, nwKeyWords.OBJECT_KEY))
1060
- self.entKeyValue.setText(self._formatTags(novRefs, nwKeyWords.ENTITY_KEY))
1061
- self.cstKeyValue.setText(self._formatTags(novRefs, nwKeyWords.CUSTOM_KEY))
1022
+ for key, (_, value) in self.tagValues.items():
1023
+ value.setText(self._formatTags(novRefs, key))
1062
1024
 
1063
1025
  return
1064
1026
 
@@ -1066,27 +1028,12 @@ class GuiOutlineDetails(QScrollArea):
1066
1028
  def updateClasses(self) -> None:
1067
1029
  """Update the visibility status of class details."""
1068
1030
  usedClasses = SHARED.project.tree.rootClasses()
1069
-
1070
- pltVisible = nwItemClass.PLOT in usedClasses
1071
- timVisible = nwItemClass.TIMELINE in usedClasses
1072
- wldVisible = nwItemClass.WORLD in usedClasses
1073
- objVisible = nwItemClass.OBJECT in usedClasses
1074
- entVisible = nwItemClass.ENTITY in usedClasses
1075
- cstVisible = nwItemClass.CUSTOM in usedClasses
1076
-
1077
- self.pltKeyLabel.setVisible(pltVisible)
1078
- self.pltKeyValue.setVisible(pltVisible)
1079
- self.timKeyLabel.setVisible(timVisible)
1080
- self.timKeyValue.setVisible(timVisible)
1081
- self.wldKeyLabel.setVisible(wldVisible)
1082
- self.wldKeyValue.setVisible(wldVisible)
1083
- self.objKeyLabel.setVisible(objVisible)
1084
- self.objKeyValue.setVisible(objVisible)
1085
- self.entKeyLabel.setVisible(entVisible)
1086
- self.entKeyValue.setVisible(entVisible)
1087
- self.cstKeyLabel.setVisible(cstVisible)
1088
- self.cstKeyValue.setVisible(cstVisible)
1089
-
1031
+ for key, itemClass in nwKeyWords.KEY_CLASS.items():
1032
+ visible = itemClass in usedClasses
1033
+ if key in self.tagValues:
1034
+ label, value = self.tagValues[key]
1035
+ label.setVisible(visible)
1036
+ value.setVisible(visible)
1090
1037
  return
1091
1038
 
1092
1039
  @staticmethod