novelWriter 2.3rc1__py3-none-any.whl → 2.4b1__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 (100) hide show
  1. {novelWriter-2.3rc1.dist-info → novelWriter-2.4b1.dist-info}/METADATA +1 -1
  2. {novelWriter-2.3rc1.dist-info → novelWriter-2.4b1.dist-info}/RECORD +99 -85
  3. {novelWriter-2.3rc1.dist-info → novelWriter-2.4b1.dist-info}/WHEEL +1 -1
  4. novelWriter-2.4b1.dist-info/entry_points.txt +2 -0
  5. novelwriter/__init__.py +5 -5
  6. novelwriter/assets/i18n/nw_de_DE.qm +0 -0
  7. novelwriter/assets/i18n/nw_en_US.qm +0 -0
  8. novelwriter/assets/i18n/nw_es_419.qm +0 -0
  9. novelwriter/assets/i18n/nw_fr_FR.qm +0 -0
  10. novelwriter/assets/i18n/nw_it_IT.qm +0 -0
  11. novelwriter/assets/i18n/nw_ja_JP.qm +0 -0
  12. novelwriter/assets/i18n/nw_nb_NO.qm +0 -0
  13. novelwriter/assets/i18n/nw_nl_NL.qm +0 -0
  14. novelwriter/assets/i18n/project_nl_NL.json +11 -0
  15. novelwriter/assets/i18n/project_pt_BR.json +11 -0
  16. novelwriter/assets/icons/typicons_dark/icons.conf +4 -0
  17. novelwriter/assets/icons/typicons_dark/nw_tb-mark.svg +7 -0
  18. novelwriter/assets/icons/typicons_dark/typ_arrow-down.svg +4 -0
  19. novelwriter/assets/icons/typicons_dark/typ_arrow-right.svg +4 -0
  20. novelwriter/assets/icons/typicons_dark/typ_refresh-flipped.svg +1 -1
  21. novelwriter/assets/icons/typicons_dark/typ_refresh.svg +1 -1
  22. novelwriter/assets/icons/typicons_dark/typ_search-grey.svg +4 -0
  23. novelwriter/assets/icons/typicons_dark/typ_times.svg +1 -1
  24. novelwriter/assets/icons/typicons_light/icons.conf +4 -0
  25. novelwriter/assets/icons/typicons_light/nw_tb-mark.svg +7 -0
  26. novelwriter/assets/icons/typicons_light/typ_arrow-down.svg +4 -0
  27. novelwriter/assets/icons/typicons_light/typ_arrow-right.svg +4 -0
  28. novelwriter/assets/icons/typicons_light/typ_refresh-flipped.svg +1 -1
  29. novelwriter/assets/icons/typicons_light/typ_refresh.svg +1 -1
  30. novelwriter/assets/icons/typicons_light/typ_search-grey.svg +4 -0
  31. novelwriter/assets/icons/typicons_light/typ_times.svg +1 -1
  32. novelwriter/assets/manual.pdf +0 -0
  33. novelwriter/assets/sample.zip +0 -0
  34. novelwriter/assets/syntax/cyberpunk_night.conf +26 -0
  35. novelwriter/assets/syntax/default_dark.conf +1 -0
  36. novelwriter/assets/syntax/default_light.conf +1 -0
  37. novelwriter/assets/syntax/grey_dark.conf +1 -0
  38. novelwriter/assets/syntax/grey_light.conf +1 -0
  39. novelwriter/assets/syntax/light_owl.conf +1 -0
  40. novelwriter/assets/syntax/night_owl.conf +1 -0
  41. novelwriter/assets/syntax/solarized_dark.conf +1 -0
  42. novelwriter/assets/syntax/solarized_light.conf +1 -0
  43. novelwriter/assets/syntax/tango.conf +23 -0
  44. novelwriter/assets/syntax/tomorrow.conf +1 -0
  45. novelwriter/assets/syntax/tomorrow_night.conf +1 -0
  46. novelwriter/assets/syntax/tomorrow_night_blue.conf +1 -0
  47. novelwriter/assets/syntax/tomorrow_night_bright.conf +1 -0
  48. novelwriter/assets/syntax/tomorrow_night_eighties.conf +1 -0
  49. novelwriter/assets/text/credits_en.htm +25 -23
  50. novelwriter/assets/themes/cyberpunk_night.conf +29 -0
  51. novelwriter/common.py +1 -1
  52. novelwriter/config.py +35 -12
  53. novelwriter/constants.py +5 -6
  54. novelwriter/core/buildsettings.py +60 -40
  55. novelwriter/core/coretools.py +98 -13
  56. novelwriter/core/docbuild.py +74 -7
  57. novelwriter/core/document.py +24 -3
  58. novelwriter/core/index.py +31 -112
  59. novelwriter/core/project.py +11 -15
  60. novelwriter/core/projectxml.py +2 -1
  61. novelwriter/core/sessions.py +2 -2
  62. novelwriter/core/status.py +4 -4
  63. novelwriter/core/storage.py +16 -6
  64. novelwriter/core/tohtml.py +22 -25
  65. novelwriter/core/tokenizer.py +416 -236
  66. novelwriter/core/tomd.py +17 -8
  67. novelwriter/core/toodt.py +65 -7
  68. novelwriter/core/tree.py +8 -8
  69. novelwriter/dialogs/about.py +2 -2
  70. novelwriter/dialogs/docsplit.py +7 -8
  71. novelwriter/dialogs/preferences.py +3 -6
  72. novelwriter/dialogs/wordlist.py +1 -1
  73. novelwriter/enum.py +17 -14
  74. novelwriter/extensions/configlayout.py +22 -0
  75. novelwriter/extensions/modified.py +20 -2
  76. novelwriter/extensions/versioninfo.py +1 -1
  77. novelwriter/gui/doceditor.py +257 -279
  78. novelwriter/gui/dochighlight.py +29 -25
  79. novelwriter/gui/docviewer.py +139 -148
  80. novelwriter/gui/docviewerpanel.py +4 -24
  81. novelwriter/gui/editordocument.py +12 -1
  82. novelwriter/gui/itemdetails.py +6 -6
  83. novelwriter/gui/mainmenu.py +37 -17
  84. novelwriter/gui/noveltree.py +11 -19
  85. novelwriter/gui/outline.py +43 -20
  86. novelwriter/gui/projtree.py +88 -88
  87. novelwriter/gui/search.py +316 -0
  88. novelwriter/gui/sidebar.py +25 -30
  89. novelwriter/gui/theme.py +68 -8
  90. novelwriter/guimain.py +183 -178
  91. novelwriter/shared.py +26 -1
  92. novelwriter/text/__init__.py +3 -0
  93. novelwriter/text/counting.py +137 -0
  94. novelwriter/tools/manuscript.py +344 -55
  95. novelwriter/tools/manussettings.py +214 -71
  96. novelwriter/tools/noveldetails.py +1 -1
  97. novelwriter/tools/welcome.py +8 -9
  98. novelWriter-2.3rc1.dist-info/entry_points.txt +0 -5
  99. {novelWriter-2.3rc1.dist-info → novelWriter-2.4b1.dist-info}/LICENSE.md +0 -0
  100. {novelWriter-2.3rc1.dist-info → novelWriter-2.4b1.dist-info}/top_level.txt +0 -0
@@ -45,16 +45,16 @@ logger = logging.getLogger(__name__)
45
45
  SPELLRX = QRegularExpression(r"\b[^\s\-\+\/–—\[\]:]+\b")
46
46
  SPELLRX.setPatternOptions(QRegularExpression.UseUnicodePropertiesOption)
47
47
 
48
+ BLOCK_NONE = 0
49
+ BLOCK_TEXT = 1
50
+ BLOCK_META = 2
51
+ BLOCK_TITLE = 4
52
+
48
53
 
49
54
  class GuiDocHighlighter(QSyntaxHighlighter):
50
55
 
51
56
  __slots__ = ("_tItem", "_tHandle", "_spellCheck", "_spellErr", "_hRules", "_hStyles")
52
57
 
53
- BLOCK_NONE = 0
54
- BLOCK_TEXT = 1
55
- BLOCK_META = 2
56
- BLOCK_TITLE = 4
57
-
58
58
  def __init__(self, document: QTextDocument) -> None:
59
59
  super().__init__(document)
60
60
 
@@ -90,10 +90,10 @@ class GuiDocHighlighter(QSyntaxHighlighter):
90
90
  "header2": self._makeFormat(SHARED.theme.colHead, "bold", 1.6),
91
91
  "header3": self._makeFormat(SHARED.theme.colHead, "bold", 1.4),
92
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),
93
+ "head1h": self._makeFormat(SHARED.theme.colHeadH, "bold", 1.8),
94
+ "head2h": self._makeFormat(SHARED.theme.colHeadH, "bold", 1.6),
95
+ "head3h": self._makeFormat(SHARED.theme.colHeadH, "bold", 1.4),
96
+ "head4h": self._makeFormat(SHARED.theme.colHeadH, "bold", 1.2),
97
97
  "bold": self._makeFormat(colEmph, "bold"),
98
98
  "italic": self._makeFormat(colEmph, "italic"),
99
99
  "strike": self._makeFormat(SHARED.theme.colHidden, "strike"),
@@ -272,12 +272,12 @@ class GuiDocHighlighter(QSyntaxHighlighter):
272
272
  is significantly faster than running the regex checks used for
273
273
  text paragraphs.
274
274
  """
275
- self.setCurrentBlockState(self.BLOCK_NONE)
275
+ self.setCurrentBlockState(BLOCK_NONE)
276
276
  if self._tHandle is None or not text:
277
277
  return
278
278
 
279
279
  if text.startswith("@"): # Keywords and commands
280
- self.setCurrentBlockState(self.BLOCK_META)
280
+ self.setCurrentBlockState(BLOCK_META)
281
281
  index = SHARED.project.index
282
282
  isValid, bits, pos = index.scanThis(text)
283
283
  isGood = index.checkThese(bits, self._tHandle)
@@ -300,35 +300,39 @@ class GuiDocHighlighter(QSyntaxHighlighter):
300
300
  # so we force a return here
301
301
  return
302
302
 
303
- elif text.startswith(("# ", "#! ", "## ", "##! ", "### ", "#### ")):
304
- self.setCurrentBlockState(self.BLOCK_TITLE)
303
+ elif text.startswith(("# ", "#! ", "## ", "##! ", "### ", "###! ", "#### ")):
304
+ self.setCurrentBlockState(BLOCK_TITLE)
305
305
 
306
- if text.startswith("# "): # Header 1
307
- self.setFormat(0, 1, self._hStyles["header1h"])
306
+ if text.startswith("# "): # Heading 1
307
+ self.setFormat(0, 1, self._hStyles["head1h"])
308
308
  self.setFormat(1, len(text), self._hStyles["header1"])
309
309
 
310
- elif text.startswith("## "): # Header 2
311
- self.setFormat(0, 2, self._hStyles["header2h"])
310
+ elif text.startswith("## "): # Heading 2
311
+ self.setFormat(0, 2, self._hStyles["head2h"])
312
312
  self.setFormat(2, len(text), self._hStyles["header2"])
313
313
 
314
- elif text.startswith("### "): # Header 3
315
- self.setFormat(0, 3, self._hStyles["header3h"])
314
+ elif text.startswith("### "): # Heading 3
315
+ self.setFormat(0, 3, self._hStyles["head3h"])
316
316
  self.setFormat(3, len(text), self._hStyles["header3"])
317
317
 
318
- elif text.startswith("#### "): # Header 4
319
- self.setFormat(0, 4, self._hStyles["header4h"])
318
+ elif text.startswith("#### "): # Heading 4
319
+ self.setFormat(0, 4, self._hStyles["head4h"])
320
320
  self.setFormat(4, len(text), self._hStyles["header4"])
321
321
 
322
322
  elif text.startswith("#! "): # Title
323
- self.setFormat(0, 2, self._hStyles["header1h"])
323
+ self.setFormat(0, 2, self._hStyles["head1h"])
324
324
  self.setFormat(2, len(text), self._hStyles["header1"])
325
325
 
326
326
  elif text.startswith("##! "): # Unnumbered
327
- self.setFormat(0, 3, self._hStyles["header2h"])
327
+ self.setFormat(0, 3, self._hStyles["head2h"])
328
328
  self.setFormat(3, len(text), self._hStyles["header2"])
329
329
 
330
+ elif text.startswith("###! "): # Hard Scene
331
+ self.setFormat(0, 4, self._hStyles["head3h"])
332
+ self.setFormat(4, len(text), self._hStyles["header3"])
333
+
330
334
  elif text.startswith("%"): # Comments
331
- self.setCurrentBlockState(self.BLOCK_TEXT)
335
+ self.setCurrentBlockState(BLOCK_TEXT)
332
336
  cStyle, _, cPos = processComment(text)
333
337
  if cStyle == nwComment.PLAIN:
334
338
  self.setFormat(0, len(text), self._hStyles["hidden"])
@@ -353,7 +357,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
353
357
  return
354
358
 
355
359
  # Regular Text
356
- self.setCurrentBlockState(self.BLOCK_TEXT)
360
+ self.setCurrentBlockState(BLOCK_TEXT)
357
361
  for rX, xFmt in self.rxRules:
358
362
  rxItt = rX.globalMatch(text, 0)
359
363
  while rxItt.hasNext():
@@ -29,7 +29,6 @@ from __future__ import annotations
29
29
  import logging
30
30
 
31
31
  from enum import Enum
32
- from typing import TYPE_CHECKING
33
32
 
34
33
  from PyQt5.QtCore import pyqtSignal, pyqtSlot, QPoint, QSize, Qt, QUrl
35
34
  from PyQt5.QtGui import (
@@ -42,14 +41,13 @@ from PyQt5.QtWidgets import (
42
41
  )
43
42
 
44
43
  from novelwriter import CONFIG, SHARED
44
+ from novelwriter.constants import nwHeaders, nwUnicode
45
+ from novelwriter.core.tohtml import ToHtml
45
46
  from novelwriter.enum import nwItemType, nwDocAction, nwDocMode
46
47
  from novelwriter.error import logException
47
- from novelwriter.constants import nwUnicode
48
- from novelwriter.core.tohtml import ToHtml
49
48
  from novelwriter.extensions.eventfilters import WheelEventFilter
50
-
51
- if TYPE_CHECKING: # pragma: no cover
52
- from novelwriter.guimain import GuiMain
49
+ from novelwriter.extensions.modified import NIconToolButton
50
+ from novelwriter.gui.theme import STYLES_MIN_TOOLBUTTON
53
51
 
54
52
  logger = logging.getLogger(__name__)
55
53
 
@@ -58,17 +56,16 @@ class GuiDocViewer(QTextBrowser):
58
56
 
59
57
  documentLoaded = pyqtSignal(str)
60
58
  loadDocumentTagRequest = pyqtSignal(str, Enum)
59
+ closeDocumentRequest = pyqtSignal()
60
+ reloadDocumentRequest = pyqtSignal()
61
61
  togglePanelVisibility = pyqtSignal()
62
62
  requestProjectItemSelected = pyqtSignal(str, bool)
63
63
 
64
- def __init__(self, mainGui: GuiMain) -> None:
65
- super().__init__(parent=mainGui)
64
+ def __init__(self, parent: QWidget) -> None:
65
+ super().__init__(parent=parent)
66
66
 
67
67
  logger.debug("Create: GuiDocViewer")
68
68
 
69
- # Class Variables
70
- self.mainGui = mainGui
71
-
72
69
  # Internal Variables
73
70
  self._docHandle = None
74
71
 
@@ -128,7 +125,7 @@ class GuiDocViewer(QTextBrowser):
128
125
  self.clear()
129
126
  self.setSearchPaths([""])
130
127
  self._docHandle = None
131
- self.docHeader.setTitleFromHandle(self._docHandle)
128
+ self.docHeader.clearHeader()
132
129
  return
133
130
 
134
131
  def updateTheme(self) -> None:
@@ -184,8 +181,7 @@ class GuiDocViewer(QTextBrowser):
184
181
  self.setTabStopDistance(CONFIG.getTabWidth())
185
182
 
186
183
  # If we have a document open, we should reload it in case the font changed
187
- if self._docHandle is not None:
188
- self.reloadText()
184
+ self.reloadText()
189
185
 
190
186
  return
191
187
 
@@ -201,8 +197,11 @@ class GuiDocViewer(QTextBrowser):
201
197
 
202
198
  sPos = self.verticalScrollBar().value()
203
199
  aDoc = ToHtml(SHARED.project)
204
- aDoc.setPreview(CONFIG.viewComments, CONFIG.viewSynopsis)
205
- aDoc.setLinkHeaders(True)
200
+ aDoc.setPreview(True)
201
+ aDoc.setKeywords(True)
202
+ aDoc.setComments(CONFIG.viewComments)
203
+ aDoc.setSynopsis(CONFIG.viewSynopsis)
204
+ aDoc.setLinkHeadings(True)
206
205
 
207
206
  # Be extra careful here to prevent crashes when first opening a
208
207
  # project as a crash here leaves no way of recovering.
@@ -239,7 +238,11 @@ class GuiDocViewer(QTextBrowser):
239
238
 
240
239
  self._docHandle = tHandle
241
240
  SHARED.project.data.setLastHandle(tHandle, "viewer")
242
- self.docHeader.setTitleFromHandle(self._docHandle)
241
+ self.docHeader.setHandle(tHandle)
242
+ self.docHeader.setOutline({
243
+ sTitle: (hItem.title, nwHeaders.H_LEVEL.get(hItem.level, 0))
244
+ for sTitle, hItem in SHARED.project.index.iterItemHeadings(tHandle)
245
+ })
243
246
  self.updateDocMargins()
244
247
 
245
248
  # Since we change the content while it may still be rendering, we mark
@@ -281,13 +284,6 @@ class GuiDocViewer(QTextBrowser):
281
284
  return False
282
285
  return True
283
286
 
284
- def navigateTo(self, tAnchor: str) -> None:
285
- """Go to a specific #link in the document."""
286
- if isinstance(tAnchor, str) and tAnchor.startswith("#"):
287
- logger.debug("Moving to anchor '%s'", tAnchor)
288
- self.setSource(QUrl(tAnchor))
289
- return
290
-
291
287
  def clearNavHistory(self) -> None:
292
288
  """Clear the navigation history."""
293
289
  self.docHistory.clear()
@@ -340,11 +336,19 @@ class GuiDocViewer(QTextBrowser):
340
336
  @pyqtSlot(str)
341
337
  def updateDocInfo(self, tHandle: str) -> None:
342
338
  """Update the header title bar if needed."""
343
- if tHandle == self._docHandle:
344
- self.docHeader.setTitleFromHandle(self._docHandle)
339
+ if tHandle and tHandle == self._docHandle:
340
+ self.docHeader.setHandle(tHandle)
345
341
  self.updateDocMargins()
346
342
  return
347
343
 
344
+ @pyqtSlot(str)
345
+ def navigateTo(self, anchor: str) -> None:
346
+ """Go to a specific #link in the document."""
347
+ if isinstance(anchor, str) and anchor.startswith("#"):
348
+ logger.debug("Moving to anchor '%s'", anchor)
349
+ self.setSource(QUrl(anchor))
350
+ return
351
+
348
352
  ##
349
353
  # Private Slots
350
354
  ##
@@ -466,48 +470,30 @@ class GuiDocViewer(QTextBrowser):
466
470
  colText = SHARED.theme.colText
467
471
  colHead = SHARED.theme.colHead
468
472
  colVals = SHARED.theme.colVal
469
- colEmph = SHARED.theme.colEmph
473
+ colMark = SHARED.theme.colMark
470
474
  colKeys = SHARED.theme.colKey
471
475
  colHide = SHARED.theme.colHidden
472
476
  colMods = SHARED.theme.colMod
473
477
  colOpts = SHARED.theme.colOpt
474
478
  styleSheet = (
475
- "body {{"
476
- " color: rgb({tColR}, {tColG}, {tColB});"
477
- "}}\n"
478
- "h1, h2, h3, h4 {{"
479
- " color: rgb({hColR}, {hColG}, {hColB});"
480
- "}}\n"
481
- "a {{"
482
- " color: rgb({aColR}, {aColG}, {aColB});"
483
- "}}\n"
484
- "mark {{"
485
- " color: rgb({eColR}, {eColG}, {eColB});"
486
- "}}\n"
487
- ".tags {{"
488
- " color: rgb({kColR}, {kColG}, {kColB});"
489
- "}}\n"
490
- ".optional {{"
491
- " color: rgb({oColR}, {oColG}, {oColB});"
492
- "}}\n"
493
- ".comment {{"
494
- " color: rgb({cColR}, {cColG}, {cColB});"
495
- "}}\n"
496
- ".synopsis {{"
497
- " color: rgb({mColR}, {mColG}, {mColB});"
498
- "}}\n"
499
- ".title {{"
500
- " text-align: center;"
501
- "}}\n"
479
+ "body {{color: rgb({rT}, {gT}, {bT});}}\n"
480
+ "h1, h2, h3, h4 {{color: rgb({rH}, {gH}, {bH});}}\n"
481
+ "a {{color: rgb({rA}, {gA}, {bA});}}\n"
482
+ "mark {{background-color: rgba({rE}, {gE}, {bE}, {aE});}}\n"
483
+ ".tags {{color: rgb({rK}, {gK}, {bK});}}\n"
484
+ ".optional {{color: rgb({rO}, {gO}, {bO});}}\n"
485
+ ".comment {{color: rgb({rC}, {gC}, {bC});}}\n"
486
+ ".synopsis {{color: rgb({rM}, {gM}, {bM});}}\n"
487
+ ".title {{text-align: center;}}\n"
502
488
  ).format(
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(),
489
+ rT=colText.red(), gT=colText.green(), bT=colText.blue(),
490
+ rH=colHead.red(), gH=colHead.green(), bH=colHead.blue(),
491
+ rA=colVals.red(), gA=colVals.green(), bA=colVals.blue(),
492
+ rE=colMark.red(), gE=colMark.green(), bE=colMark.blue(), aE=colMark.alpha(),
493
+ rK=colKeys.red(), gK=colKeys.green(), bK=colKeys.blue(),
494
+ rC=colHide.red(), gC=colHide.green(), bC=colHide.blue(),
495
+ rM=colMods.red(), gM=colMods.green(), bM=colMods.blue(),
496
+ rO=colOpts.red(), gO=colOpts.green(), bO=colOpts.blue(),
511
497
  )
512
498
  self.document().setDefaultStyleSheet(styleSheet)
513
499
 
@@ -644,84 +630,80 @@ class GuiDocViewHeader(QWidget):
644
630
  logger.debug("Create: GuiDocViewHeader")
645
631
 
646
632
  self.docViewer = docViewer
647
- self.mainGui = docViewer.mainGui
648
633
 
649
634
  # Internal Variables
650
635
  self._docHandle = None
636
+ self._docOutline: dict[str, tuple[str, int]] = {}
651
637
 
652
- fPx = int(0.9*SHARED.theme.fontPixelSize)
653
- hSp = CONFIG.pxInt(6)
638
+ iPx = SHARED.theme.baseIconSize
639
+ mPx = CONFIG.pxInt(4)
654
640
 
655
641
  # Main Widget Settings
656
642
  self.setAutoFillBackground(True)
657
643
 
658
644
  # Title Label
659
- self.docTitle = QLabel()
660
- self.docTitle.setText("")
661
- self.docTitle.setIndent(0)
662
- self.docTitle.setMargin(0)
663
- self.docTitle.setContentsMargins(0, 0, 0, 0)
664
- self.docTitle.setAutoFillBackground(True)
665
- self.docTitle.setAlignment(Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignTop)
666
- self.docTitle.setFixedHeight(fPx)
667
-
668
- lblFont = self.docTitle.font()
645
+ self.itemTitle = QLabel()
646
+ self.itemTitle.setText("")
647
+ self.itemTitle.setIndent(0)
648
+ self.itemTitle.setMargin(0)
649
+ self.itemTitle.setContentsMargins(0, 0, 0, 0)
650
+ self.itemTitle.setAutoFillBackground(True)
651
+ self.itemTitle.setAlignment(Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignTop)
652
+ self.itemTitle.setFixedHeight(iPx)
653
+
654
+ lblFont = self.itemTitle.font()
669
655
  lblFont.setPointSizeF(0.9*SHARED.theme.fontPointSize)
670
- self.docTitle.setFont(lblFont)
656
+ self.itemTitle.setFont(lblFont)
657
+
658
+ # Other Widgets
659
+ self.outlineMenu = QMenu(self)
671
660
 
672
661
  # Buttons
673
- self.backButton = QToolButton(self)
674
- self.backButton.setContentsMargins(0, 0, 0, 0)
675
- self.backButton.setIconSize(QSize(fPx, fPx))
676
- self.backButton.setFixedSize(fPx, fPx)
677
- self.backButton.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
662
+ self.outlineButton = NIconToolButton(self, iPx)
663
+ self.outlineButton.setVisible(False)
664
+ self.outlineButton.setToolTip(self.tr("Outline"))
665
+ self.outlineButton.setMenu(self.outlineMenu)
666
+
667
+ self.backButton = NIconToolButton(self, iPx)
678
668
  self.backButton.setVisible(False)
679
669
  self.backButton.setToolTip(self.tr("Go Backward"))
680
670
  self.backButton.clicked.connect(self.docViewer.navBackward)
681
671
 
682
- self.forwardButton = QToolButton(self)
683
- self.forwardButton.setContentsMargins(0, 0, 0, 0)
684
- self.forwardButton.setIconSize(QSize(fPx, fPx))
685
- self.forwardButton.setFixedSize(fPx, fPx)
686
- self.forwardButton.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
672
+ self.forwardButton = NIconToolButton(self, iPx)
687
673
  self.forwardButton.setVisible(False)
688
674
  self.forwardButton.setToolTip(self.tr("Go Forward"))
689
675
  self.forwardButton.clicked.connect(self.docViewer.navForward)
690
676
 
691
- self.refreshButton = QToolButton(self)
692
- self.refreshButton.setContentsMargins(0, 0, 0, 0)
693
- self.refreshButton.setIconSize(QSize(fPx, fPx))
694
- self.refreshButton.setFixedSize(fPx, fPx)
695
- self.refreshButton.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
677
+ self.refreshButton = NIconToolButton(self, iPx)
696
678
  self.refreshButton.setVisible(False)
697
679
  self.refreshButton.setToolTip(self.tr("Reload"))
698
680
  self.refreshButton.clicked.connect(self._refreshDocument)
699
681
 
700
- self.closeButton = QToolButton(self)
701
- self.closeButton.setContentsMargins(0, 0, 0, 0)
702
- self.closeButton.setIconSize(QSize(fPx, fPx))
703
- self.closeButton.setFixedSize(fPx, fPx)
704
- self.closeButton.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
682
+ self.closeButton = NIconToolButton(self, iPx)
705
683
  self.closeButton.setVisible(False)
706
684
  self.closeButton.setToolTip(self.tr("Close"))
707
685
  self.closeButton.clicked.connect(self._closeDocument)
708
686
 
709
687
  # Assemble Layout
710
688
  self.outerBox = QHBoxLayout()
711
- self.outerBox.setSpacing(hSp)
689
+ self.outerBox.addWidget(self.outlineButton, 0)
712
690
  self.outerBox.addWidget(self.backButton, 0)
713
691
  self.outerBox.addWidget(self.forwardButton, 0)
714
- self.outerBox.addWidget(self.docTitle, 1)
692
+ self.outerBox.addSpacing(mPx)
693
+ self.outerBox.addWidget(self.itemTitle, 1)
694
+ self.outerBox.addSpacing(mPx)
695
+ self.outerBox.addSpacing(iPx)
715
696
  self.outerBox.addWidget(self.refreshButton, 0)
716
697
  self.outerBox.addWidget(self.closeButton, 0)
698
+ self.outerBox.setSpacing(0)
699
+
717
700
  self.setLayout(self.outerBox)
718
701
 
719
702
  # Fix Margins and Size
720
703
  # This is needed for high DPI systems. See issue #499.
721
- cM = CONFIG.pxInt(8)
722
704
  self.setContentsMargins(0, 0, 0, 0)
723
- self.outerBox.setContentsMargins(cM, cM, cM, cM)
724
- self.setMinimumHeight(fPx + 2*cM)
705
+ self.outerBox.setContentsMargins(mPx, mPx, mPx, mPx)
706
+ self.setMinimumHeight(iPx + 2*mPx)
725
707
 
726
708
  # Fix the Colours
727
709
  self.updateTheme()
@@ -734,19 +716,50 @@ class GuiDocViewHeader(QWidget):
734
716
  # Methods
735
717
  ##
736
718
 
719
+ def clearHeader(self) -> None:
720
+ """Clear the header."""
721
+ self._docHandle = None
722
+ self._docOutline = {}
723
+
724
+ self.itemTitle.setText("")
725
+ self.outlineMenu.clear()
726
+ self.outlineButton.setVisible(False)
727
+ self.backButton.setVisible(False)
728
+ self.forwardButton.setVisible(False)
729
+ self.closeButton.setVisible(False)
730
+ self.refreshButton.setVisible(False)
731
+ return
732
+
733
+ def setOutline(self, data: dict[str, tuple[str, int]]) -> None:
734
+ """Set the document outline dataset."""
735
+ tHandle = self._docHandle
736
+ if data != self._docOutline and tHandle:
737
+ self.outlineMenu.clear()
738
+ entries = []
739
+ minLevel = 5
740
+ for title, (text, level) in data.items():
741
+ if title != "T0000":
742
+ entries.append((title, text, level))
743
+ minLevel = min(minLevel, level)
744
+ for title, text, level in entries[:30]:
745
+ indent = " "*(level - minLevel)
746
+ action = self.outlineMenu.addAction(f"{indent}{text}")
747
+ action.triggered.connect(
748
+ lambda _, title=title: self.docViewer.navigateTo(f"#{tHandle}:{title}")
749
+ )
750
+ self._docOutline = data
751
+ return
752
+
737
753
  def updateTheme(self) -> None:
738
754
  """Update theme elements."""
755
+ self.outlineButton.setIcon(SHARED.theme.getIcon("list"))
739
756
  self.backButton.setIcon(SHARED.theme.getIcon("backward"))
740
757
  self.forwardButton.setIcon(SHARED.theme.getIcon("forward"))
741
758
  self.refreshButton.setIcon(SHARED.theme.getIcon("refresh"))
742
759
  self.closeButton.setIcon(SHARED.theme.getIcon("close"))
743
760
 
744
- colText = SHARED.theme.colText
745
- buttonStyle = (
746
- "QToolButton {{border: none; background: transparent;}} "
747
- "QToolButton:hover {{border: none; background: rgba({0}, {1}, {2}, 0.2);}}"
748
- ).format(colText.red(), colText.green(), colText.blue())
749
-
761
+ buttonStyle = SHARED.theme.getStyleSheet(STYLES_MIN_TOOLBUTTON)
762
+ self.outlineButton.setStyleSheet(buttonStyle)
750
763
  self.backButton.setStyleSheet(buttonStyle)
751
764
  self.forwardButton.setStyleSheet(buttonStyle)
752
765
  self.refreshButton.setStyleSheet(buttonStyle)
@@ -765,38 +778,25 @@ class GuiDocViewHeader(QWidget):
765
778
  palette.setColor(QPalette.ColorRole.WindowText, SHARED.theme.colText)
766
779
  palette.setColor(QPalette.ColorRole.Text, SHARED.theme.colText)
767
780
  self.setPalette(palette)
768
- self.docTitle.setPalette(palette)
781
+ self.itemTitle.setPalette(palette)
769
782
  return
770
783
 
771
- def setTitleFromHandle(self, tHandle: str | None) -> None:
784
+ def setHandle(self, tHandle: str) -> None:
772
785
  """Sets the document title from the handle, or alternatively,
773
786
  set the whole document path.
774
787
  """
775
788
  self._docHandle = tHandle
776
- if tHandle is None:
777
- self.docTitle.setText("")
778
- self.backButton.setVisible(False)
779
- self.forwardButton.setVisible(False)
780
- self.closeButton.setVisible(False)
781
- self.refreshButton.setVisible(False)
782
- return
783
-
784
- pTree = SHARED.project.tree
789
+
785
790
  if CONFIG.showFullPath:
786
- tTitle = []
787
- tTree = pTree.getItemPath(tHandle)
788
- for aHandle in reversed(tTree):
789
- nwItem = pTree[aHandle]
790
- if nwItem is not None:
791
- tTitle.append(nwItem.itemName)
792
- sSep = " %s " % nwUnicode.U_RSAQUO
793
- self.docTitle.setText(sSep.join(tTitle))
791
+ self.itemTitle.setText(f" {nwUnicode.U_RSAQUO} ".join(reversed(
792
+ [name for name in SHARED.project.tree.getItemPath(tHandle, asName=True)]
793
+ )))
794
794
  else:
795
- if nwItem := pTree[tHandle]:
796
- self.docTitle.setText(nwItem.itemName)
795
+ self.itemTitle.setText(i.itemName if (i := SHARED.project.tree[tHandle]) else "")
797
796
 
798
797
  self.backButton.setVisible(True)
799
798
  self.forwardButton.setVisible(True)
799
+ self.outlineButton.setVisible(True)
800
800
  self.closeButton.setVisible(True)
801
801
  self.refreshButton.setVisible(True)
802
802
 
@@ -815,15 +815,14 @@ class GuiDocViewHeader(QWidget):
815
815
  @pyqtSlot()
816
816
  def _closeDocument(self) -> None:
817
817
  """Trigger the close editor/viewer on the main window."""
818
- self.mainGui.closeDocViewer()
818
+ self.clearHeader()
819
+ self.docViewer.closeDocumentRequest.emit()
819
820
  return
820
821
 
821
822
  @pyqtSlot()
822
823
  def _refreshDocument(self) -> None:
823
824
  """Reload the content of the document."""
824
- if self.docViewer.docHandle == self.mainGui.docEditor.docHandle:
825
- self.mainGui.saveDocument()
826
- self.docViewer.reloadText()
825
+ self.docViewer.reloadDocumentRequest.emit()
827
826
  return
828
827
 
829
828
  ##
@@ -854,22 +853,20 @@ class GuiDocViewFooter(QWidget):
854
853
  logger.debug("Create: GuiDocViewFooter")
855
854
 
856
855
  self.docViewer = docViewer
857
- self.mainGui = docViewer.mainGui
858
856
 
859
857
  # Internal Variables
860
858
  self._docHandle = None
861
859
 
862
- fPx = int(0.9*SHARED.theme.fontPixelSize)
860
+ iPx = SHARED.theme.baseIconSize
863
861
  hSp = CONFIG.pxInt(4)
862
+ mPx = CONFIG.pxInt(4)
864
863
 
865
864
  # Main Widget Settings
866
865
  self.setContentsMargins(0, 0, 0, 0)
867
866
  self.setAutoFillBackground(True)
868
867
 
869
868
  # Show/Hide Details
870
- self.showHide = QToolButton(self)
871
- self.showHide.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
872
- self.showHide.setIconSize(QSize(fPx, fPx))
869
+ self.showHide = NIconToolButton(self, iPx)
873
870
  self.showHide.clicked.connect(lambda: self.docViewer.togglePanelVisibility.emit())
874
871
  self.showHide.setToolTip(self.tr("Show/Hide Viewer Panel"))
875
872
 
@@ -879,7 +876,7 @@ class GuiDocViewFooter(QWidget):
879
876
  self.showComments.setCheckable(True)
880
877
  self.showComments.setChecked(CONFIG.viewComments)
881
878
  self.showComments.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonTextBesideIcon)
882
- self.showComments.setIconSize(QSize(fPx, fPx))
879
+ self.showComments.setIconSize(QSize(iPx, iPx))
883
880
  self.showComments.toggled.connect(self._doToggleComments)
884
881
  self.showComments.setToolTip(self.tr("Show Comments"))
885
882
 
@@ -889,7 +886,7 @@ class GuiDocViewFooter(QWidget):
889
886
  self.showSynopsis.setCheckable(True)
890
887
  self.showSynopsis.setChecked(CONFIG.viewSynopsis)
891
888
  self.showSynopsis.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonTextBesideIcon)
892
- self.showSynopsis.setIconSize(QSize(fPx, fPx))
889
+ self.showSynopsis.setIconSize(QSize(iPx, iPx))
893
890
  self.showSynopsis.toggled.connect(self._doToggleSynopsis)
894
891
  self.showSynopsis.setToolTip(self.tr("Show Synopsis Comments"))
895
892
 
@@ -909,10 +906,9 @@ class GuiDocViewFooter(QWidget):
909
906
 
910
907
  # Fix Margins and Size
911
908
  # This is needed for high DPI systems. See issue #499.
912
- cM = CONFIG.pxInt(8)
913
909
  self.setContentsMargins(0, 0, 0, 0)
914
- self.outerBox.setContentsMargins(cM, cM, cM, cM)
915
- self.setMinimumHeight(fPx + 2*cM)
910
+ self.outerBox.setContentsMargins(mPx, mPx, mPx, mPx)
911
+ self.setMinimumHeight(iPx + 2*mPx)
916
912
 
917
913
  # Fix the Colours
918
914
  self.updateTheme()
@@ -935,12 +931,7 @@ class GuiDocViewFooter(QWidget):
935
931
  self.showComments.setIcon(bulletIcon)
936
932
  self.showSynopsis.setIcon(bulletIcon)
937
933
 
938
- colText = SHARED.theme.colText
939
- buttonStyle = (
940
- "QToolButton {{border: none; background: transparent;}} "
941
- "QToolButton:hover {{border: none; background: rgba({0}, {1}, {2}, 0.2);}}"
942
- ).format(colText.red(), colText.green(), colText.blue())
943
-
934
+ buttonStyle = SHARED.theme.getStyleSheet(STYLES_MIN_TOOLBUTTON)
944
935
  self.showHide.setStyleSheet(buttonStyle)
945
936
  self.showComments.setStyleSheet(buttonStyle)
946
937
  self.showSynopsis.setStyleSheet(buttonStyle)
@@ -34,10 +34,11 @@ from PyQt5.QtWidgets import (
34
34
  )
35
35
 
36
36
  from novelwriter import CONFIG, SHARED
37
- from novelwriter.enum import nwDocMode, nwItemClass
38
37
  from novelwriter.common import checkInt
39
38
  from novelwriter.constants import nwHeaders, nwLabels, nwLists, trConst
40
39
  from novelwriter.core.index import IndexHeading, IndexItem
40
+ from novelwriter.enum import nwDocMode, nwItemClass
41
+ from novelwriter.gui.theme import STYLES_FLAT_TABS, STYLES_MIN_TOOLBUTTON
41
42
 
42
43
  logger = logging.getLogger(__name__)
43
44
 
@@ -99,35 +100,14 @@ class GuiDocViewerPanel(QWidget):
99
100
 
100
101
  def updateTheme(self, updateTabs: bool = True) -> None:
101
102
  """Update theme elements."""
102
- qPalette = self.palette()
103
- mPx = CONFIG.pxInt(2)
104
- vPx = CONFIG.pxInt(4)
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
103
  self.optsButton.setIcon(SHARED.theme.getIcon("menu"))
116
- self.optsButton.setStyleSheet(buttonStyle)
117
-
118
- styleSheet = (
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())
123
- self.mainTabs.setStyleSheet(styleSheet)
104
+ self.optsButton.setStyleSheet(SHARED.theme.getStyleSheet(STYLES_MIN_TOOLBUTTON))
105
+ self.mainTabs.setStyleSheet(SHARED.theme.getStyleSheet(STYLES_FLAT_TABS))
124
106
  self.updateHandle(self._lastHandle)
125
-
126
107
  if updateTabs:
127
108
  self.tabBackRefs.updateTheme()
128
109
  for tab in self.kwTabs.values():
129
110
  tab.updateTheme()
130
-
131
111
  return
132
112
 
133
113
  def openProjectTasks(self) -> None: