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
@@ -35,10 +35,10 @@ from PyQt5.QtGui import (
35
35
  )
36
36
 
37
37
  from novelwriter import CONFIG, SHARED
38
+ from novelwriter.enum import nwComment
38
39
  from novelwriter.common import checkInt
39
40
  from novelwriter.constants import nwRegEx, nwUnicode
40
41
  from novelwriter.core.index import processComment
41
- from novelwriter.enum import nwComment
42
42
 
43
43
  logger = logging.getLogger(__name__)
44
44
 
@@ -48,6 +48,8 @@ SPELLRX.setPatternOptions(QRegularExpression.UseUnicodePropertiesOption)
48
48
 
49
49
  class GuiDocHighlighter(QSyntaxHighlighter):
50
50
 
51
+ __slots__ = ("_tItem", "_tHandle", "_spellCheck", "_spellErr", "_hRules", "_hStyles")
52
+
51
53
  BLOCK_NONE = 0
52
54
  BLOCK_TEXT = 1
53
55
  BLOCK_META = 2
@@ -58,29 +60,14 @@ class GuiDocHighlighter(QSyntaxHighlighter):
58
60
 
59
61
  logger.debug("Create: GuiDocHighlighter")
60
62
 
61
- self._tItem = None
62
- self._tHandle = None
63
+ self._tHandle = None
64
+ self._isInactive = False
63
65
  self._spellCheck = False
66
+ self._spellErr = QTextCharFormat()
64
67
 
65
68
  self._hRules: list[tuple[str, dict]] = []
66
69
  self._hStyles: dict[str, QTextCharFormat] = {}
67
70
 
68
- self._colHead = QColor(0, 0, 0)
69
- self._colHeadH = QColor(0, 0, 0)
70
- self._colEmph = QColor(0, 0, 0)
71
- self._colDialN = QColor(0, 0, 0)
72
- self._colDialD = QColor(0, 0, 0)
73
- self._colDialS = QColor(0, 0, 0)
74
- self._colHidden = QColor(0, 0, 0)
75
- self._colCode = QColor(0, 0, 0)
76
- self._colKey = QColor(0, 0, 0)
77
- self._colVal = QColor(0, 0, 0)
78
- self._colSpell = QColor(0, 0, 0)
79
- self._colError = QColor(0, 0, 0)
80
- self._colRepTag = QColor(0, 0, 0)
81
- self._colMod = QColor(0, 0, 0)
82
- self._colBreak = QColor(0, 0, 0)
83
-
84
71
  self.initHighlighter()
85
72
 
86
73
  logger.debug("Ready: GuiDocHighlighter")
@@ -93,54 +80,43 @@ class GuiDocHighlighter(QSyntaxHighlighter):
93
80
  """
94
81
  logger.debug("Setting up highlighting rules")
95
82
 
96
- self._colHead = QColor(*SHARED.theme.colHead)
97
- self._colHeadH = QColor(*SHARED.theme.colHeadH)
98
- self._colDialN = QColor(*SHARED.theme.colDialN)
99
- self._colDialD = QColor(*SHARED.theme.colDialD)
100
- self._colDialS = QColor(*SHARED.theme.colDialS)
101
- self._colHidden = QColor(*SHARED.theme.colHidden)
102
- self._colCode = QColor(*SHARED.theme.colCode)
103
- self._colKey = QColor(*SHARED.theme.colKey)
104
- self._colVal = QColor(*SHARED.theme.colVal)
105
- self._colSpell = QColor(*SHARED.theme.colSpell)
106
- self._colError = QColor(*SHARED.theme.colError)
107
- self._colRepTag = QColor(*SHARED.theme.colRepTag)
108
- self._colMod = QColor(*SHARED.theme.colMod)
109
- self._colBreak = QColor(*SHARED.theme.colEmph)
110
- self._colBreak.setAlpha(64)
111
-
112
- self._colEmph = None
113
- if CONFIG.highlightEmph:
114
- self._colEmph = QColor(*SHARED.theme.colEmph)
83
+ colEmph = SHARED.theme.colEmph if CONFIG.highlightEmph else None
84
+ colBreak = QColor(SHARED.theme.colEmph)
85
+ colBreak.setAlpha(64)
115
86
 
87
+ self._hRules = []
116
88
  self._hStyles = {
117
- "header1": self._makeFormat(self._colHead, "bold", 1.8),
118
- "header2": self._makeFormat(self._colHead, "bold", 1.6),
119
- "header3": self._makeFormat(self._colHead, "bold", 1.4),
120
- "header4": self._makeFormat(self._colHead, "bold", 1.2),
121
- "header1h": self._makeFormat(self._colHeadH, "bold", 1.8),
122
- "header2h": self._makeFormat(self._colHeadH, "bold", 1.6),
123
- "header3h": self._makeFormat(self._colHeadH, "bold", 1.4),
124
- "header4h": self._makeFormat(self._colHeadH, "bold", 1.2),
125
- "bold": self._makeFormat(self._colEmph, "bold"),
126
- "italic": self._makeFormat(self._colEmph, "italic"),
127
- "strike": self._makeFormat(self._colHidden, "strike"),
128
- "mspaces": self._makeFormat(self._colError, "errline"),
129
- "nobreak": self._makeFormat(self._colBreak, "background"),
130
- "dialogue1": self._makeFormat(self._colDialN),
131
- "dialogue2": self._makeFormat(self._colDialD),
132
- "dialogue3": self._makeFormat(self._colDialS),
133
- "replace": self._makeFormat(self._colRepTag),
134
- "hidden": self._makeFormat(self._colHidden),
135
- "code": self._makeFormat(self._colCode),
136
- "keyword": self._makeFormat(self._colKey),
137
- "modifier": self._makeFormat(self._colMod),
138
- "value": self._makeFormat(self._colVal, "underline"),
139
- "codevalue": self._makeFormat(self._colVal),
89
+ "header1": self._makeFormat(SHARED.theme.colHead, "bold", 1.8),
90
+ "header2": self._makeFormat(SHARED.theme.colHead, "bold", 1.6),
91
+ "header3": self._makeFormat(SHARED.theme.colHead, "bold", 1.4),
92
+ "header4": self._makeFormat(SHARED.theme.colHead, "bold", 1.2),
93
+ "header1h": self._makeFormat(SHARED.theme.colHeadH, "bold", 1.8),
94
+ "header2h": self._makeFormat(SHARED.theme.colHeadH, "bold", 1.6),
95
+ "header3h": self._makeFormat(SHARED.theme.colHeadH, "bold", 1.4),
96
+ "header4h": self._makeFormat(SHARED.theme.colHeadH, "bold", 1.2),
97
+ "bold": self._makeFormat(colEmph, "bold"),
98
+ "italic": self._makeFormat(colEmph, "italic"),
99
+ "strike": self._makeFormat(SHARED.theme.colHidden, "strike"),
100
+ "mspaces": self._makeFormat(SHARED.theme.colError, "errline"),
101
+ "nobreak": self._makeFormat(colBreak, "background"),
102
+ "dialogue1": self._makeFormat(SHARED.theme.colDialN),
103
+ "dialogue2": self._makeFormat(SHARED.theme.colDialD),
104
+ "dialogue3": self._makeFormat(SHARED.theme.colDialS),
105
+ "replace": self._makeFormat(SHARED.theme.colRepTag),
106
+ "hidden": self._makeFormat(SHARED.theme.colHidden),
107
+ "code": self._makeFormat(SHARED.theme.colCode),
108
+ "keyword": self._makeFormat(SHARED.theme.colKey),
109
+ "modifier": self._makeFormat(SHARED.theme.colMod),
110
+ "value": self._makeFormat(SHARED.theme.colVal),
111
+ "optional": self._makeFormat(SHARED.theme.colOpt),
112
+ "codevalue": self._makeFormat(SHARED.theme.colVal),
140
113
  "codeinval": self._makeFormat(None, "errline"),
141
114
  }
142
115
 
143
- self._hRules = []
116
+ # Cache Spell Error Format
117
+ self._spellErr = QTextCharFormat()
118
+ self._spellErr.setUnderlineColor(SHARED.theme.colSpell)
119
+ self._spellErr.setUnderlineStyle(QTextCharFormat.SpellCheckUnderline)
144
120
 
145
121
  # Multiple or Trailing Spaces
146
122
  if CONFIG.showMultiSpaces:
@@ -262,11 +238,10 @@ class GuiDocHighlighter(QSyntaxHighlighter):
262
238
  def setHandle(self, tHandle: str) -> None:
263
239
  """Set the handle of the currently highlighted document."""
264
240
  self._tHandle = tHandle
265
- self._tItem = SHARED.project.tree[tHandle]
266
- logger.debug(
267
- "Syntax highlighter %s for item '%s'",
268
- "enabled" if self._tItem else "disabled", tHandle
241
+ self._isInactive = (
242
+ item.isInactiveClass() if (item := SHARED.project.tree[tHandle]) else False
269
243
  )
244
+ logger.debug("Syntax highlighter enabled for item '%s'", tHandle)
270
245
  return
271
246
 
272
247
  ##
@@ -281,9 +256,9 @@ class GuiDocHighlighter(QSyntaxHighlighter):
281
256
  nBlocks = qDoc.blockCount()
282
257
  tStart = time()
283
258
  for i in range(nBlocks):
284
- theBlock = qDoc.findBlockByNumber(i)
285
- if theBlock.userState() & cType > 0:
286
- self.rehighlightBlock(theBlock)
259
+ block = qDoc.findBlockByNumber(i)
260
+ if block.userState() & cType > 0:
261
+ self.rehighlightBlock(block)
287
262
  logger.debug("Document highlighted in %.3f ms" % (1000*(time() - tStart)))
288
263
  return
289
264
 
@@ -303,24 +278,23 @@ class GuiDocHighlighter(QSyntaxHighlighter):
303
278
 
304
279
  if text.startswith("@"): # Keywords and commands
305
280
  self.setCurrentBlockState(self.BLOCK_META)
306
- if self._tItem:
307
- pIndex = SHARED.project.index
308
- isValid, theBits, thePos = pIndex.scanThis(text)
309
- isGood = pIndex.checkThese(theBits, self._tItem)
310
- if isValid:
311
- for n, theBit in enumerate(theBits):
312
- xPos = thePos[n]
313
- xLen = len(theBit)
314
- if isGood[n]:
315
- if n == 0:
316
- self.setFormat(xPos, xLen, self._hStyles["keyword"])
317
- else:
318
- self.setFormat(xPos, xLen, self._hStyles["value"])
319
- else:
320
- kwFmt = self.format(xPos)
321
- kwFmt.setUnderlineColor(self._colError)
322
- kwFmt.setUnderlineStyle(QTextCharFormat.SpellCheckUnderline)
323
- self.setFormat(xPos, xLen, kwFmt)
281
+ index = SHARED.project.index
282
+ isValid, bits, pos = index.scanThis(text)
283
+ isGood = index.checkThese(bits, self._tHandle)
284
+ if isValid:
285
+ for n, bit in enumerate(bits):
286
+ xPos = pos[n]
287
+ xLen = len(bit)
288
+ if n == 0 and isGood[n]:
289
+ self.setFormat(xPos, xLen, self._hStyles["keyword"])
290
+ elif isGood[n] and not self._isInactive:
291
+ one, two = index.parseValue(bit)
292
+ self.setFormat(xPos, len(one), self._hStyles["value"])
293
+ if two:
294
+ yPos = xPos + len(bit) - len(two)
295
+ self.setFormat(yPos, len(two), self._hStyles["optional"])
296
+ elif not self._isInactive:
297
+ self.setFormat(xPos, xLen, self._hStyles["codeinval"])
324
298
 
325
299
  # We never want to run the spell checker on keyword/values,
326
300
  # so we force a return here
@@ -402,8 +376,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
402
376
  for xPos, xLen in data.spellCheck(text):
403
377
  for x in range(xPos, xPos+xLen):
404
378
  spFmt = self.format(x)
405
- spFmt.setUnderlineColor(self._colSpell)
406
- spFmt.setUnderlineStyle(QTextCharFormat.SpellCheckUnderline)
379
+ spFmt.merge(self._spellErr)
407
380
  self.setFormat(x, 1, spFmt)
408
381
 
409
382
  return
@@ -431,10 +404,8 @@ class GuiDocHighlighter(QSyntaxHighlighter):
431
404
  if "strike" in styles:
432
405
  charFormat.setFontStrikeOut(True)
433
406
  if "errline" in styles:
434
- charFormat.setUnderlineColor(self._colError)
407
+ charFormat.setUnderlineColor(SHARED.theme.colError)
435
408
  charFormat.setUnderlineStyle(QTextCharFormat.SpellCheckUnderline)
436
- if "underline" in styles:
437
- charFormat.setFontUnderline(True)
438
409
  if "background" in styles and color is not None:
439
410
  charFormat.setBackground(QBrush(color, Qt.SolidPattern))
440
411
 
@@ -33,7 +33,7 @@ from typing import TYPE_CHECKING
33
33
 
34
34
  from PyQt5.QtCore import pyqtSignal, pyqtSlot, QPoint, QSize, Qt, QUrl
35
35
  from PyQt5.QtGui import (
36
- QColor, QCursor, QFont, QMouseEvent, QPalette, QResizeEvent, QTextCursor,
36
+ QCursor, QFont, QMouseEvent, QPalette, QResizeEvent, QTextCursor,
37
37
  QTextOption
38
38
  )
39
39
  from PyQt5.QtWidgets import (
@@ -142,21 +142,21 @@ class GuiDocViewer(QTextBrowser):
142
142
  self._makeStyleSheet()
143
143
 
144
144
  # Set Font
145
- textFont = QFont()
146
- textFont.setFamily(CONFIG.textFont)
147
- textFont.setPointSize(CONFIG.textSize)
148
- self.setFont(textFont)
145
+ font = QFont()
146
+ font.setFamily(CONFIG.textFont)
147
+ font.setPointSize(CONFIG.textSize)
148
+ self.setFont(font)
149
149
 
150
150
  # Set the widget colours to match syntax theme
151
151
  mainPalette = self.palette()
152
- mainPalette.setColor(QPalette.ColorRole.Window, QColor(*SHARED.theme.colBack))
153
- mainPalette.setColor(QPalette.ColorRole.Base, QColor(*SHARED.theme.colBack))
154
- mainPalette.setColor(QPalette.ColorRole.Text, QColor(*SHARED.theme.colText))
152
+ mainPalette.setColor(QPalette.ColorRole.Window, SHARED.theme.colBack)
153
+ mainPalette.setColor(QPalette.ColorRole.Base, SHARED.theme.colBack)
154
+ mainPalette.setColor(QPalette.ColorRole.Text, SHARED.theme.colText)
155
155
  self.setPalette(mainPalette)
156
156
 
157
157
  docPalette = self.viewport().palette()
158
- docPalette.setColor(QPalette.ColorRole.Base, QColor(*SHARED.theme.colBack))
159
- docPalette.setColor(QPalette.ColorRole.Text, QColor(*SHARED.theme.colText))
158
+ docPalette.setColor(QPalette.ColorRole.Base, SHARED.theme.colBack)
159
+ docPalette.setColor(QPalette.ColorRole.Text, SHARED.theme.colText)
160
160
  self.viewport().setPalette(docPalette)
161
161
 
162
162
  self.docHeader.matchColours()
@@ -164,10 +164,10 @@ class GuiDocViewer(QTextBrowser):
164
164
 
165
165
  # Set default text margins
166
166
  self.document().setDocumentMargin(0)
167
- theOpt = QTextOption()
167
+ options = QTextOption()
168
168
  if CONFIG.doJustify:
169
- theOpt.setAlignment(Qt.AlignmentFlag.AlignJustify)
170
- self.document().setDefaultTextOption(theOpt)
169
+ options.setAlignment(Qt.AlignmentFlag.AlignJustify)
170
+ self.document().setDefaultTextOption(options)
171
171
 
172
172
  # Scroll bars
173
173
  if CONFIG.hideVScroll:
@@ -229,7 +229,7 @@ class GuiDocViewer(QTextBrowser):
229
229
  self.setDocumentTitle(tHandle)
230
230
 
231
231
  # Replace tabs before setting the HTML, and then put them back in
232
- self.setHtml(aDoc.theResult.replace("\t", "!!tab!!"))
232
+ self.setHtml(aDoc.result.replace("\t", "!!tab!!"))
233
233
  while self.find("!!tab!!"):
234
234
  self.textCursor().insertText("\t")
235
235
 
@@ -367,9 +367,9 @@ class GuiDocViewer(QTextBrowser):
367
367
  link = url.url()
368
368
  logger.debug("Clicked link: '%s'", link)
369
369
  if len(link) > 0:
370
- theBits = link.split("=")
371
- if len(theBits) == 2:
372
- self.loadDocumentTagRequest.emit(theBits[1], nwDocMode.VIEW)
370
+ bits = link.split("=")
371
+ if len(bits) == 2:
372
+ self.loadDocumentTagRequest.emit(bits[1], nwDocMode.VIEW)
373
373
  return
374
374
 
375
375
  @pyqtSlot("QPoint")
@@ -461,9 +461,16 @@ class GuiDocViewer(QTextBrowser):
461
461
 
462
462
  def _makeStyleSheet(self) -> None:
463
463
  """Generate an appropriate style sheet for the document viewer,
464
- based on the current syntax highlighter theme,
464
+ based on the current syntax highlighter theme.
465
465
  """
466
- pTheme = SHARED.theme
466
+ colText = SHARED.theme.colText
467
+ colHead = SHARED.theme.colHead
468
+ colVals = SHARED.theme.colVal
469
+ colEmph = SHARED.theme.colEmph
470
+ colKeys = SHARED.theme.colKey
471
+ colHide = SHARED.theme.colHidden
472
+ colMods = SHARED.theme.colMod
473
+ colOpts = SHARED.theme.colOpt
467
474
  styleSheet = (
468
475
  "body {{"
469
476
  " color: rgb({tColR}, {tColG}, {tColB});"
@@ -480,6 +487,9 @@ class GuiDocViewer(QTextBrowser):
480
487
  ".tags {{"
481
488
  " color: rgb({kColR}, {kColG}, {kColB});"
482
489
  "}}\n"
490
+ ".optional {{"
491
+ " color: rgb({oColR}, {oColG}, {oColB});"
492
+ "}}\n"
483
493
  ".comment {{"
484
494
  " color: rgb({cColR}, {cColG}, {cColB});"
485
495
  "}}\n"
@@ -490,27 +500,14 @@ class GuiDocViewer(QTextBrowser):
490
500
  " text-align: center;"
491
501
  "}}\n"
492
502
  ).format(
493
- tColR=pTheme.colText[0],
494
- tColG=pTheme.colText[1],
495
- tColB=pTheme.colText[2],
496
- hColR=pTheme.colHead[0],
497
- hColG=pTheme.colHead[1],
498
- hColB=pTheme.colHead[2],
499
- aColR=pTheme.colVal[0],
500
- aColG=pTheme.colVal[1],
501
- aColB=pTheme.colVal[2],
502
- eColR=pTheme.colEmph[0],
503
- eColG=pTheme.colEmph[1],
504
- eColB=pTheme.colEmph[2],
505
- kColR=pTheme.colKey[0],
506
- kColG=pTheme.colKey[1],
507
- kColB=pTheme.colKey[2],
508
- cColR=pTheme.colHidden[0],
509
- cColG=pTheme.colHidden[1],
510
- cColB=pTheme.colHidden[2],
511
- mColR=pTheme.colMod[0],
512
- mColG=pTheme.colMod[1],
513
- mColB=pTheme.colMod[2],
503
+ tColR=colText.red(), tColG=colText.green(), tColB=colText.blue(),
504
+ hColR=colHead.red(), hColG=colHead.green(), hColB=colHead.blue(),
505
+ aColR=colVals.red(), aColG=colVals.green(), aColB=colVals.blue(),
506
+ eColR=colEmph.red(), eColG=colEmph.green(), eColB=colEmph.blue(),
507
+ kColR=colKeys.red(), kColG=colKeys.green(), kColB=colKeys.blue(),
508
+ cColR=colHide.red(), cColG=colHide.green(), cColB=colHide.blue(),
509
+ mColR=colMods.red(), mColG=colMods.green(), mColB=colMods.blue(),
510
+ oColR=colOpts.red(), oColG=colOpts.green(), oColB=colOpts.blue(),
514
511
  )
515
512
  self.document().setDefaultStyleSheet(styleSheet)
516
513
 
@@ -744,10 +741,11 @@ class GuiDocViewHeader(QWidget):
744
741
  self.refreshButton.setIcon(SHARED.theme.getIcon("refresh"))
745
742
  self.closeButton.setIcon(SHARED.theme.getIcon("close"))
746
743
 
744
+ colText = SHARED.theme.colText
747
745
  buttonStyle = (
748
746
  "QToolButton {{border: none; background: transparent;}} "
749
- "QToolButton:hover {{border: none; background: rgba({0},{1},{2},0.2);}}"
750
- ).format(*SHARED.theme.colText)
747
+ "QToolButton:hover {{border: none; background: rgba({0}, {1}, {2}, 0.2);}}"
748
+ ).format(colText.red(), colText.green(), colText.blue())
751
749
 
752
750
  self.backButton.setStyleSheet(buttonStyle)
753
751
  self.forwardButton.setStyleSheet(buttonStyle)
@@ -763,9 +761,9 @@ class GuiDocViewHeader(QWidget):
763
761
  theme rather than the main GUI.
764
762
  """
765
763
  palette = QPalette()
766
- palette.setColor(QPalette.ColorRole.Window, QColor(*SHARED.theme.colBack))
767
- palette.setColor(QPalette.ColorRole.WindowText, QColor(*SHARED.theme.colText))
768
- palette.setColor(QPalette.ColorRole.Text, QColor(*SHARED.theme.colText))
764
+ palette.setColor(QPalette.ColorRole.Window, SHARED.theme.colBack)
765
+ palette.setColor(QPalette.ColorRole.WindowText, SHARED.theme.colText)
766
+ palette.setColor(QPalette.ColorRole.Text, SHARED.theme.colText)
769
767
  self.setPalette(palette)
770
768
  self.docTitle.setPalette(palette)
771
769
  return
@@ -937,11 +935,11 @@ class GuiDocViewFooter(QWidget):
937
935
  self.showComments.setIcon(bulletIcon)
938
936
  self.showSynopsis.setIcon(bulletIcon)
939
937
 
940
- # StyleSheets
938
+ colText = SHARED.theme.colText
941
939
  buttonStyle = (
942
940
  "QToolButton {{border: none; background: transparent;}} "
943
- "QToolButton:hover {{border: none; background: rgba({0},{1},{2},0.2);}}"
944
- ).format(*SHARED.theme.colText)
941
+ "QToolButton:hover {{border: none; background: rgba({0}, {1}, {2}, 0.2);}}"
942
+ ).format(colText.red(), colText.green(), colText.blue())
945
943
 
946
944
  self.showHide.setStyleSheet(buttonStyle)
947
945
  self.showComments.setStyleSheet(buttonStyle)
@@ -956,9 +954,9 @@ class GuiDocViewFooter(QWidget):
956
954
  theme rather than the main GUI.
957
955
  """
958
956
  palette = QPalette()
959
- palette.setColor(QPalette.ColorRole.Window, QColor(*SHARED.theme.colBack))
960
- palette.setColor(QPalette.ColorRole.WindowText, QColor(*SHARED.theme.colText))
961
- palette.setColor(QPalette.ColorRole.Text, QColor(*SHARED.theme.colText))
957
+ palette.setColor(QPalette.ColorRole.Window, SHARED.theme.colBack)
958
+ palette.setColor(QPalette.ColorRole.WindowText, SHARED.theme.colText)
959
+ palette.setColor(QPalette.ColorRole.Text, SHARED.theme.colText)
962
960
  self.setPalette(palette)
963
961
  return
964
962
 
@@ -29,8 +29,8 @@ from enum import Enum
29
29
 
30
30
  from PyQt5.QtCore import QModelIndex, QSize, Qt, pyqtSignal, pyqtSlot
31
31
  from PyQt5.QtWidgets import (
32
- QAbstractItemView, QFrame, QHeaderView, QTabWidget, QTreeWidget,
33
- QTreeWidgetItem, QVBoxLayout, QWidget
32
+ QAbstractItemView, QFrame, QHeaderView, QMenu, QTabWidget, QToolButton,
33
+ QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget
34
34
  )
35
35
 
36
36
  from novelwriter import CONFIG, SHARED
@@ -54,10 +54,24 @@ class GuiDocViewerPanel(QWidget):
54
54
 
55
55
  self._lastHandle = None
56
56
 
57
+ iPx = int(1.0*SHARED.theme.baseIconSize)
58
+
57
59
  self.tabBackRefs = _ViewPanelBackRefs(self)
58
60
 
61
+ self.optsMenu = QMenu(self)
62
+
63
+ self.aInactive = self.optsMenu.addAction(self.tr("Hide Inactive Tags"))
64
+ self.aInactive.setCheckable(True)
65
+ self.aInactive.toggled.connect(self._toggleHideInactive)
66
+
67
+ self.optsButton = QToolButton(self)
68
+ self.optsButton.setIconSize(QSize(iPx, iPx))
69
+ self.optsButton.setMenu(self.optsMenu)
70
+ self.optsButton.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup)
71
+
59
72
  self.mainTabs = QTabWidget(self)
60
73
  self.mainTabs.addTab(self.tabBackRefs, self.tr("References"))
74
+ self.mainTabs.setCornerWidget(self.optsButton, Qt.Corner.TopLeftCorner)
61
75
 
62
76
  self.kwTabs: dict[str, _ViewPanelKeyWords] = {}
63
77
  self.idTabs: dict[str, int] = {}
@@ -85,20 +99,27 @@ class GuiDocViewerPanel(QWidget):
85
99
 
86
100
  def updateTheme(self, updateTabs: bool = True) -> None:
87
101
  """Update theme elements."""
102
+ qPalette = self.palette()
103
+ mPx = CONFIG.pxInt(2)
88
104
  vPx = CONFIG.pxInt(4)
89
- lPx = CONFIG.pxInt(2)
90
- rPx = CONFIG.pxInt(14)
91
- hCol = self.palette().highlight().color()
105
+ hPx = CONFIG.pxInt(8)
106
+ hCol = qPalette.highlight().color()
107
+ fCol = qPalette.text().color()
108
+
109
+ buttonStyle = (
110
+ "QToolButton {{padding: {0}px; margin: 0 0 {1}px 0; border: none; "
111
+ "background: transparent;}} "
112
+ "QToolButton:hover {{border: none; background: rgba({2}, {3}, {4}, 0.2);}} "
113
+ "QToolButton::menu-indicator {{image: none;}} "
114
+ ).format(mPx, mPx, fCol.red(), fCol.green(), fCol.blue())
115
+ self.optsButton.setIcon(SHARED.theme.getIcon("menu"))
116
+ self.optsButton.setStyleSheet(buttonStyle)
92
117
 
93
118
  styleSheet = (
94
- "QTabWidget::pane {border: 0;} "
95
- "QTabWidget QTabBar::tab {"
96
- f"border: 0; padding: {vPx}px {rPx}px {vPx}px {lPx}px;"
97
- "} "
98
- "QTabWidget QTabBar::tab:selected {"
99
- f"color: rgb({hCol.red()}, {hCol.green()}, {hCol.blue()});"
100
- "} "
101
- )
119
+ "QTabWidget::pane {{border: 0;}} "
120
+ "QTabWidget QTabBar::tab {{border: 0; padding: {0}px {1}px;}} "
121
+ "QTabWidget QTabBar::tab:selected {{color: rgb({2}, {3}, {4});}} "
122
+ ).format(vPx, hPx, hCol.red(), hCol.green(), hCol.blue())
102
123
  self.mainTabs.setStyleSheet(styleSheet)
103
124
  self.updateHandle(self._lastHandle)
104
125
 
@@ -111,20 +132,22 @@ class GuiDocViewerPanel(QWidget):
111
132
 
112
133
  def openProjectTasks(self) -> None:
113
134
  """Run open project tasks."""
114
- widths = SHARED.project.options.getValue("GuiDocViewerPanel", "colWidths", {})
115
- if isinstance(widths, dict):
116
- for key, value in widths.items():
135
+ colWidths = SHARED.project.options.getValue("GuiDocViewerPanel", "colWidths", {})
136
+ hideInactive = SHARED.project.options.getBool("GuiDocViewerPanel", "hideInactive", False)
137
+ self.aInactive.setChecked(hideInactive)
138
+ if isinstance(colWidths, dict):
139
+ for key, value in colWidths.items():
117
140
  if key in self.kwTabs and isinstance(value, list):
118
141
  self.kwTabs[key].setColumnWidths(value)
119
142
  return
120
143
 
121
144
  def closeProjectTasks(self) -> None:
122
145
  """Run close project tasks."""
123
- widths = {}
124
- for key, tab in self.kwTabs.items():
125
- widths[key] = tab.getColumnWidths()
126
146
  logger.debug("Saving State: GuiDocViewerPanel")
127
- SHARED.project.options.setValue("GuiDocViewerPanel", "colWidths", widths)
147
+ colWidths = {k: t.getColumnWidths() for k, t in self.kwTabs.items()}
148
+ hideInactive = self.aInactive.isChecked()
149
+ SHARED.project.options.setValue("GuiDocViewerPanel", "colWidths", colWidths)
150
+ SHARED.project.options.setValue("GuiDocViewerPanel", "hideInactive", hideInactive)
128
151
  return
129
152
 
130
153
  ##
@@ -142,9 +165,7 @@ class GuiDocViewerPanel(QWidget):
142
165
  @pyqtSlot()
143
166
  def indexHasAppeared(self) -> None:
144
167
  """Handle event when the index has appeared."""
145
- for key, name, tClass, iItem, hItem in SHARED.project.index.getTagsData():
146
- if tClass in self.kwTabs and iItem and hItem:
147
- self.kwTabs[tClass].addUpdateEntry(key, name, iItem, hItem)
168
+ self._loadAllTags()
148
169
  self._updateTabVisibility()
149
170
  self.updateHandle(self._lastHandle)
150
171
  return
@@ -153,10 +174,15 @@ class GuiDocViewerPanel(QWidget):
153
174
  def projectItemChanged(self, tHandle: str) -> None:
154
175
  """Update meta data for project item."""
155
176
  self.tabBackRefs.refreshDocument(tHandle)
177
+ activeOnly = self.aInactive.isChecked()
156
178
  for key in SHARED.project.index.getDocumentTags(tHandle):
157
179
  name, tClass, iItem, hItem = SHARED.project.index.getSingleTag(key)
158
180
  if tClass in self.kwTabs and iItem and hItem:
159
- self.kwTabs[tClass].addUpdateEntry(key, name, iItem, hItem)
181
+ if not activeOnly or (iItem and iItem.item.isActive):
182
+ self.kwTabs[tClass].addUpdateEntry(key, name, iItem, hItem)
183
+ else:
184
+ self.kwTabs[tClass].removeEntry(key)
185
+ self._updateTabVisibility()
160
186
  return
161
187
 
162
188
  @pyqtSlot(str)
@@ -182,6 +208,20 @@ class GuiDocViewerPanel(QWidget):
182
208
  self._updateTabVisibility()
183
209
  return
184
210
 
211
+ ##
212
+ # Private Slots
213
+ ##
214
+
215
+ @pyqtSlot(bool)
216
+ def _toggleHideInactive(self, state: bool) -> None:
217
+ """Process toggling of active/inactive visibility."""
218
+ logger.debug("Setting inactive items to %s", "hidden" if state else "visible")
219
+ for cTab in self.kwTabs.values():
220
+ cTab.clearContent()
221
+ self._loadAllTags()
222
+ self._updateTabVisibility()
223
+ return
224
+
185
225
  ##
186
226
  # Internal Functions
187
227
  ##
@@ -193,6 +233,14 @@ class GuiDocViewerPanel(QWidget):
193
233
  self.mainTabs.setTabVisible(self.idTabs[tClass], cTab.countEntries() > 0)
194
234
  return
195
235
 
236
+ def _loadAllTags(self) -> None:
237
+ """Load all tags into the tabs."""
238
+ data = SHARED.project.index.getTagsData(activeOnly=self.aInactive.isChecked())
239
+ for key, name, tClass, iItem, hItem in data:
240
+ if tClass in self.kwTabs and iItem and hItem:
241
+ self.kwTabs[tClass].addUpdateEntry(key, name, iItem, hItem)
242
+ return
243
+
196
244
  # END Class GuiDocViewerPanel
197
245
 
198
246
 
@@ -239,9 +239,9 @@ class GuiItemDetails(QWidget):
239
239
  # Label
240
240
  # =====
241
241
 
242
- theLabel = nwItem.itemName
243
- if len(theLabel) > 100:
244
- theLabel = theLabel[:96].rstrip()+" ..."
242
+ label = nwItem.itemName
243
+ if len(label) > 100:
244
+ label = label[:96].rstrip()+" ..."
245
245
 
246
246
  if nwItem.isFileType():
247
247
  if nwItem.isActive:
@@ -251,14 +251,14 @@ class GuiItemDetails(QWidget):
251
251
  else:
252
252
  self.labelIcon.setPixmap(SHARED.theme.getPixmap("noncheckable", (iPx, iPx)))
253
253
 
254
- self.labelData.setText(theLabel)
254
+ self.labelData.setText(label)
255
255
 
256
256
  # Status
257
257
  # ======
258
258
 
259
- theStatus, theIcon = nwItem.getImportStatus(incIcon=True)
260
- self.statusIcon.setPixmap(theIcon.pixmap(iPx, iPx))
261
- self.statusData.setText(theStatus)
259
+ status, icon = nwItem.getImportStatus(incIcon=True)
260
+ self.statusIcon.setPixmap(icon.pixmap(iPx, iPx))
261
+ self.statusData.setText(status)
262
262
 
263
263
  # Class
264
264
  # =====