novelWriter 2.2rc1__py3-none-any.whl → 2.3__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 (162) hide show
  1. {novelWriter-2.2rc1.dist-info → novelWriter-2.3.dist-info}/METADATA +1 -1
  2. {novelWriter-2.2rc1.dist-info → novelWriter-2.3.dist-info}/RECORD +149 -132
  3. {novelWriter-2.2rc1.dist-info → novelWriter-2.3.dist-info}/WHEEL +1 -1
  4. novelWriter-2.3.dist-info/entry_points.txt +2 -0
  5. novelwriter/__init__.py +11 -6
  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/nw_zh_CN.qm +0 -0
  15. novelwriter/assets/i18n/project_de_DE.json +1 -0
  16. novelwriter/assets/i18n/project_en_US.json +1 -0
  17. novelwriter/assets/i18n/project_es_419.json +11 -0
  18. novelwriter/assets/i18n/project_fr_FR.json +11 -0
  19. novelwriter/assets/i18n/project_it_IT.json +11 -0
  20. novelwriter/assets/i18n/project_ja_JP.json +2 -1
  21. novelwriter/assets/i18n/project_nb_NO.json +1 -0
  22. novelwriter/assets/i18n/project_nl_NL.json +11 -0
  23. novelwriter/assets/i18n/project_pt_BR.json +11 -0
  24. novelwriter/assets/i18n/project_zh_CN.json +11 -0
  25. novelwriter/assets/icons/typicons_dark/icons.conf +11 -2
  26. novelwriter/assets/icons/typicons_dark/mixed_document-new.svg +6 -0
  27. novelwriter/assets/icons/typicons_dark/mixed_import.svg +5 -0
  28. novelwriter/assets/icons/typicons_dark/nw_tb-bold-md.svg +4 -0
  29. novelwriter/assets/icons/typicons_dark/nw_tb-bold.svg +3 -1
  30. novelwriter/assets/icons/typicons_dark/nw_tb-italic-md.svg +4 -0
  31. novelwriter/assets/icons/typicons_dark/nw_tb-italic.svg +3 -1
  32. novelwriter/assets/icons/typicons_dark/nw_tb-strike-md.svg +4 -0
  33. novelwriter/assets/icons/typicons_dark/nw_tb-strike.svg +3 -1
  34. novelwriter/assets/icons/typicons_dark/nw_tb-subscript.svg +4 -2
  35. novelwriter/assets/icons/typicons_dark/nw_tb-superscript.svg +4 -2
  36. novelwriter/assets/icons/typicons_dark/nw_tb-underline.svg +4 -2
  37. novelwriter/assets/icons/typicons_dark/typ_document-add.svg +4 -0
  38. novelwriter/assets/icons/typicons_dark/typ_document.svg +4 -0
  39. novelwriter/assets/icons/typicons_dark/typ_th-dot-more.svg +4 -0
  40. novelwriter/assets/icons/typicons_dark/typ_th-list.svg +9 -0
  41. novelwriter/assets/icons/typicons_light/icons.conf +11 -2
  42. novelwriter/assets/icons/typicons_light/mixed_document-new.svg +6 -0
  43. novelwriter/assets/icons/typicons_light/mixed_import.svg +5 -0
  44. novelwriter/assets/icons/typicons_light/nw_tb-bold-md.svg +4 -0
  45. novelwriter/assets/icons/typicons_light/nw_tb-bold.svg +3 -1
  46. novelwriter/assets/icons/typicons_light/nw_tb-italic-md.svg +4 -0
  47. novelwriter/assets/icons/typicons_light/nw_tb-italic.svg +3 -1
  48. novelwriter/assets/icons/typicons_light/nw_tb-strike-md.svg +4 -0
  49. novelwriter/assets/icons/typicons_light/nw_tb-strike.svg +3 -1
  50. novelwriter/assets/icons/typicons_light/nw_tb-subscript.svg +4 -2
  51. novelwriter/assets/icons/typicons_light/nw_tb-superscript.svg +4 -2
  52. novelwriter/assets/icons/typicons_light/nw_tb-underline.svg +4 -2
  53. novelwriter/assets/icons/typicons_light/typ_document-add.svg +4 -0
  54. novelwriter/assets/icons/typicons_light/typ_document.svg +4 -0
  55. novelwriter/assets/icons/typicons_light/typ_th-dot-more.svg +4 -0
  56. novelwriter/assets/icons/typicons_light/typ_th-list.svg +9 -0
  57. novelwriter/assets/images/novelwriter-text-dark.svg +4 -0
  58. novelwriter/assets/images/novelwriter-text-light.svg +4 -0
  59. novelwriter/assets/images/welcome-dark.jpg +0 -0
  60. novelwriter/assets/images/welcome-light.jpg +0 -0
  61. novelwriter/assets/manual.pdf +0 -0
  62. novelwriter/assets/sample.zip +0 -0
  63. novelwriter/assets/syntax/cyberpunk_night.conf +26 -0
  64. novelwriter/assets/syntax/default_dark.conf +1 -0
  65. novelwriter/assets/syntax/default_light.conf +1 -0
  66. novelwriter/assets/syntax/grey_dark.conf +1 -0
  67. novelwriter/assets/syntax/grey_light.conf +1 -0
  68. novelwriter/assets/syntax/light_owl.conf +1 -0
  69. novelwriter/assets/syntax/night_owl.conf +1 -0
  70. novelwriter/assets/syntax/solarized_dark.conf +1 -0
  71. novelwriter/assets/syntax/solarized_light.conf +1 -0
  72. novelwriter/assets/syntax/tango.conf +23 -0
  73. novelwriter/assets/syntax/tomorrow.conf +1 -0
  74. novelwriter/assets/syntax/tomorrow_night.conf +1 -0
  75. novelwriter/assets/syntax/tomorrow_night_blue.conf +1 -0
  76. novelwriter/assets/syntax/tomorrow_night_bright.conf +1 -0
  77. novelwriter/assets/syntax/tomorrow_night_eighties.conf +1 -0
  78. novelwriter/assets/text/credits_en.htm +4 -2
  79. novelwriter/assets/themes/cyberpunk_night.conf +29 -0
  80. novelwriter/assets/themes/default_dark.conf +2 -2
  81. novelwriter/assets/themes/default_light.conf +2 -2
  82. novelwriter/common.py +64 -66
  83. novelwriter/config.py +39 -44
  84. novelwriter/constants.py +39 -17
  85. novelwriter/core/buildsettings.py +8 -8
  86. novelwriter/core/coretools.py +198 -157
  87. novelwriter/core/docbuild.py +7 -4
  88. novelwriter/core/document.py +7 -7
  89. novelwriter/core/index.py +90 -57
  90. novelwriter/core/item.py +23 -5
  91. novelwriter/core/options.py +11 -10
  92. novelwriter/core/project.py +73 -47
  93. novelwriter/core/projectdata.py +3 -16
  94. novelwriter/core/projectxml.py +14 -42
  95. novelwriter/core/sessions.py +4 -3
  96. novelwriter/core/spellcheck.py +6 -4
  97. novelwriter/core/status.py +5 -4
  98. novelwriter/core/storage.py +183 -141
  99. novelwriter/core/tohtml.py +6 -4
  100. novelwriter/core/tokenizer.py +110 -83
  101. novelwriter/core/tomd.py +2 -2
  102. novelwriter/core/toodt.py +41 -31
  103. novelwriter/core/tree.py +5 -4
  104. novelwriter/dialogs/about.py +88 -179
  105. novelwriter/dialogs/docmerge.py +30 -20
  106. novelwriter/dialogs/docsplit.py +33 -22
  107. novelwriter/dialogs/editlabel.py +20 -8
  108. novelwriter/dialogs/preferences.py +562 -725
  109. novelwriter/dialogs/{projsettings.py → projectsettings.py} +301 -270
  110. novelwriter/dialogs/quotes.py +47 -36
  111. novelwriter/dialogs/wordlist.py +128 -59
  112. novelwriter/enum.py +25 -22
  113. novelwriter/error.py +2 -2
  114. novelwriter/extensions/circularprogress.py +12 -12
  115. novelwriter/extensions/configlayout.py +185 -146
  116. novelwriter/extensions/{wheeleventfilter.py → eventfilters.py} +15 -5
  117. novelwriter/extensions/modified.py +81 -0
  118. novelwriter/extensions/novelselector.py +27 -13
  119. novelwriter/extensions/pagedsidebar.py +15 -20
  120. novelwriter/extensions/simpleprogress.py +8 -9
  121. novelwriter/extensions/statusled.py +9 -9
  122. novelwriter/extensions/switch.py +32 -64
  123. novelwriter/extensions/switchbox.py +2 -7
  124. novelwriter/extensions/versioninfo.py +153 -0
  125. novelwriter/gui/doceditor.py +250 -214
  126. novelwriter/gui/dochighlight.py +66 -94
  127. novelwriter/gui/docviewer.py +71 -98
  128. novelwriter/gui/docviewerpanel.py +140 -47
  129. novelwriter/gui/editordocument.py +3 -3
  130. novelwriter/gui/itemdetails.py +9 -9
  131. novelwriter/gui/mainmenu.py +47 -47
  132. novelwriter/gui/noveltree.py +53 -61
  133. novelwriter/gui/outline.py +100 -76
  134. novelwriter/gui/projtree.py +246 -112
  135. novelwriter/gui/sidebar.py +9 -8
  136. novelwriter/gui/statusbar.py +49 -7
  137. novelwriter/gui/theme.py +74 -76
  138. novelwriter/guimain.py +175 -330
  139. novelwriter/shared.py +68 -30
  140. novelwriter/tools/dictionaries.py +7 -8
  141. novelwriter/tools/lipsum.py +34 -28
  142. novelwriter/tools/manusbuild.py +3 -4
  143. novelwriter/tools/manuscript.py +25 -32
  144. novelwriter/tools/manussettings.py +194 -225
  145. novelwriter/tools/noveldetails.py +525 -0
  146. novelwriter/tools/welcome.py +819 -0
  147. novelwriter/tools/writingstats.py +26 -13
  148. novelWriter-2.2rc1.dist-info/entry_points.txt +0 -5
  149. novelwriter/assets/icons/typicons_dark/nw_tb-markdown.svg +0 -8
  150. novelwriter/assets/icons/typicons_dark/nw_tb-shortcode.svg +0 -8
  151. novelwriter/assets/icons/typicons_light/nw_tb-markdown.svg +0 -8
  152. novelwriter/assets/icons/typicons_light/nw_tb-shortcode.svg +0 -8
  153. novelwriter/assets/images/wizard-back.jpg +0 -0
  154. novelwriter/assets/text/gplv3_en.htm +0 -641
  155. novelwriter/assets/text/release_notes.htm +0 -17
  156. novelwriter/dialogs/projdetails.py +0 -525
  157. novelwriter/dialogs/projload.py +0 -298
  158. novelwriter/dialogs/updates.py +0 -182
  159. novelwriter/extensions/pageddialog.py +0 -130
  160. novelwriter/tools/projwizard.py +0 -478
  161. {novelWriter-2.2rc1.dist-info → novelWriter-2.3.dist-info}/LICENSE.md +0 -0
  162. {novelWriter-2.2rc1.dist-info → novelWriter-2.3.dist-info}/top_level.txt +0 -0
@@ -7,7 +7,7 @@ Created: 2019-05-05 [0.0.1] Tokenizer
7
7
  Created: 2023-05-23 [2.1b1] HeadingFormatter
8
8
 
9
9
  This file is a part of novelWriter
10
- Copyright 2018–2023, Veronica Berglyd Olsen
10
+ Copyright 2018–2024, Veronica Berglyd Olsen
11
11
 
12
12
  This program is free software: you can redistribute it and/or modify
13
13
  it under the terms of the GNU General Public License as published by
@@ -34,11 +34,13 @@ from pathlib import Path
34
34
  from functools import partial
35
35
 
36
36
  from PyQt5.QtCore import QCoreApplication, QRegularExpression
37
- from novelwriter.core.index import processComment
38
37
 
39
38
  from novelwriter.enum import nwComment, nwItemLayout
40
39
  from novelwriter.common import formatTimeStamp, numberToRoman, checkInt
41
- from novelwriter.constants import nwHeadFmt, nwRegEx, nwShortcode, nwUnicode
40
+ from novelwriter.constants import (
41
+ nwHeadFmt, nwKeyWords, nwLabels, nwRegEx, nwShortcode, nwUnicode, trConst
42
+ )
43
+ from novelwriter.core.index import processComment
42
44
  from novelwriter.core.project import NWProject
43
45
 
44
46
  logger = logging.getLogger(__name__)
@@ -87,7 +89,7 @@ class Tokenizer(ABC):
87
89
  T_UNNUM = 7 # Unnumbered
88
90
  T_HEAD1 = 8 # Header 1
89
91
  T_HEAD2 = 9 # Header 2
90
- T_HEAD3 = 10 # Header 3
92
+ T_HEAD3 = 10 # Header 3
91
93
  T_HEAD4 = 11 # Header 4
92
94
  T_TEXT = 12 # Text line
93
95
  T_SEP = 13 # Scene separator
@@ -112,13 +114,15 @@ class Tokenizer(ABC):
112
114
 
113
115
  # Data Variables
114
116
  self._text = "" # The raw text to be tokenized
115
- self._nwItem = None # The NWItem associated with the handle
116
- self._tokens = [] # The list of the processed tokens
117
+ self._nwItem = None # The NWItem currently being processed
117
118
  self._result = "" # The result of the last document
118
119
 
119
120
  self._keepMarkdown = False # Whether to keep the markdown text
120
121
  self._allMarkdown = [] # The result novelWriter markdown of all documents
121
122
 
123
+ # Processed Tokens
124
+ self._tokens: list[tuple[int, int, str, list[tuple[int, int]], int]] = []
125
+
122
126
  # User Settings
123
127
  self._textFont = "Serif" # Output text font
124
128
  self._textSize = 11 # Output text size
@@ -154,7 +158,7 @@ class Tokenizer(ABC):
154
158
 
155
159
  # Instance Variables
156
160
  self._hFormatter = HeadingFormatter(self._project)
157
- self._firstScene = False # Flag to indicate that the first scene of the chapter
161
+ self._skipSeparator = False # Flag to indicate that we skip the scene separator
158
162
 
159
163
  # This File
160
164
  self._isNone = False # Document has unknown layout
@@ -194,12 +198,12 @@ class Tokenizer(ABC):
194
198
  ##
195
199
 
196
200
  @property
197
- def theResult(self) -> str:
201
+ def result(self) -> str:
198
202
  """The result of the build process."""
199
203
  return self._result
200
204
 
201
205
  @property
202
- def theMarkdown(self) -> list:
206
+ def allMarkdown(self) -> list:
203
207
  """The combined novelWriter Markdown text."""
204
208
  return self._allMarkdown
205
209
 
@@ -350,7 +354,7 @@ class Tokenizer(ABC):
350
354
  title = f"{trNotes}: {tItem.itemName}"
351
355
  self._tokens = []
352
356
  self._tokens.append((
353
- self.T_TITLE, 0, title, None, textAlign
357
+ self.T_TITLE, 0, title, [], textAlign
354
358
  ))
355
359
  if self._keepMarkdown:
356
360
  self._allMarkdown.append(f"# {title}\n\n")
@@ -358,8 +362,8 @@ class Tokenizer(ABC):
358
362
  return True
359
363
 
360
364
  def setText(self, tHandle: str, text: str | None = None) -> bool:
361
- """Set the text for the tokenizer from a handle. If theText is
362
- not set, load it from the file.
365
+ """Set the text for the tokenizer from a handle. If text is not
366
+ set, load it from the file.
363
367
  """
364
368
  self._nwItem = self._project.tree[tHandle]
365
369
  if self._nwItem is None:
@@ -418,7 +422,7 @@ class Tokenizer(ABC):
418
422
  # Check for blank lines
419
423
  if len(sLine) == 0:
420
424
  self._tokens.append((
421
- self.T_EMPTY, nHead, "", None, self.A_NONE
425
+ self.T_EMPTY, nHead, "", [], self.A_NONE
422
426
  ))
423
427
  if self._keepMarkdown:
424
428
  tmpMarkdown.append("\n")
@@ -446,7 +450,7 @@ class Tokenizer(ABC):
446
450
 
447
451
  elif sLine == "[vspace]":
448
452
  self._tokens.append(
449
- (self.T_SKIP, nHead, "", None, sAlign)
453
+ (self.T_SKIP, nHead, "", [], sAlign)
450
454
  )
451
455
  continue
452
456
 
@@ -454,38 +458,42 @@ class Tokenizer(ABC):
454
458
  nSkip = checkInt(sLine[8:-1], 0)
455
459
  if nSkip >= 1:
456
460
  self._tokens.append(
457
- (self.T_SKIP, nHead, "", None, sAlign)
461
+ (self.T_SKIP, nHead, "", [], sAlign)
458
462
  )
459
463
  if nSkip > 1:
460
464
  self._tokens += (nSkip - 1) * [
461
- (self.T_SKIP, nHead, "", None, self.A_NONE)
465
+ (self.T_SKIP, nHead, "", [], self.A_NONE)
462
466
  ]
463
467
  continue
464
468
 
465
469
  if aLine[0] == "%":
470
+ if aLine[1] == "~":
471
+ # Completely ignore the paragraph
472
+ continue
473
+
466
474
  cStyle, cText, _ = processComment(aLine)
467
475
  if cStyle == nwComment.SYNOPSIS:
468
476
  self._tokens.append((
469
- self.T_SYNOPSIS, nHead, cText, None, sAlign
477
+ self.T_SYNOPSIS, nHead, cText, [], sAlign
470
478
  ))
471
479
  if self._doSynopsis and self._keepMarkdown:
472
480
  tmpMarkdown.append("%s\n" % aLine)
473
481
  elif cStyle == nwComment.SHORT:
474
482
  self._tokens.append((
475
- self.T_SHORT, nHead, cText, None, sAlign
483
+ self.T_SHORT, nHead, cText, [], sAlign
476
484
  ))
477
485
  if self._doSynopsis and self._keepMarkdown:
478
486
  tmpMarkdown.append("%s\n" % aLine)
479
487
  else:
480
488
  self._tokens.append((
481
- self.T_COMMENT, nHead, cText, None, sAlign
489
+ self.T_COMMENT, nHead, cText, [], sAlign
482
490
  ))
483
491
  if self._doComments and self._keepMarkdown:
484
492
  tmpMarkdown.append("%s\n" % aLine)
485
493
 
486
494
  elif aLine[0] == "@":
487
495
  self._tokens.append((
488
- self.T_KEYWORD, nHead, aLine[1:].strip(), None, sAlign
496
+ self.T_KEYWORD, nHead, aLine[1:].strip(), [], sAlign
489
497
  ))
490
498
  if self._doKeywords and self._keepMarkdown:
491
499
  tmpMarkdown.append("%s\n" % aLine)
@@ -497,7 +505,7 @@ class Tokenizer(ABC):
497
505
 
498
506
  nHead += 1
499
507
  self._tokens.append((
500
- self.T_HEAD1, nHead, aLine[2:].strip(), None, sAlign
508
+ self.T_HEAD1, nHead, aLine[2:].strip(), [], sAlign
501
509
  ))
502
510
  if self._keepMarkdown:
503
511
  tmpMarkdown.append("%s\n" % aLine)
@@ -508,7 +516,7 @@ class Tokenizer(ABC):
508
516
 
509
517
  nHead += 1
510
518
  self._tokens.append((
511
- self.T_HEAD2, nHead, aLine[3:].strip(), None, sAlign
519
+ self.T_HEAD2, nHead, aLine[3:].strip(), [], sAlign
512
520
  ))
513
521
  if self._keepMarkdown:
514
522
  tmpMarkdown.append("%s\n" % aLine)
@@ -516,7 +524,7 @@ class Tokenizer(ABC):
516
524
  elif aLine[:4] == "### ":
517
525
  nHead += 1
518
526
  self._tokens.append((
519
- self.T_HEAD3, nHead, aLine[4:].strip(), None, sAlign
527
+ self.T_HEAD3, nHead, aLine[4:].strip(), [], sAlign
520
528
  ))
521
529
  if self._keepMarkdown:
522
530
  tmpMarkdown.append("%s\n" % aLine)
@@ -524,7 +532,7 @@ class Tokenizer(ABC):
524
532
  elif aLine[:5] == "#### ":
525
533
  nHead += 1
526
534
  self._tokens.append((
527
- self.T_HEAD4, nHead, aLine[5:].strip(), None, sAlign
535
+ self.T_HEAD4, nHead, aLine[5:].strip(), [], sAlign
528
536
  ))
529
537
  if self._keepMarkdown:
530
538
  tmpMarkdown.append("%s\n" % aLine)
@@ -537,7 +545,7 @@ class Tokenizer(ABC):
537
545
  tStyle = self.T_HEAD1
538
546
 
539
547
  self._tokens.append((
540
- tStyle, nHead, aLine[3:].strip(), None, sAlign | self.A_CENTRE
548
+ tStyle, nHead, aLine[3:].strip(), [], sAlign | self.A_CENTRE
541
549
  ))
542
550
  if self._keepMarkdown:
543
551
  tmpMarkdown.append("%s\n" % aLine)
@@ -551,7 +559,7 @@ class Tokenizer(ABC):
551
559
  tStyle = self.T_HEAD2
552
560
 
553
561
  self._tokens.append((
554
- tStyle, nHead, aLine[4:].strip(), None, sAlign
562
+ tStyle, nHead, aLine[4:].strip(), [], sAlign
555
563
  ))
556
564
  if self._keepMarkdown:
557
565
  tmpMarkdown.append("%s\n" % aLine)
@@ -607,14 +615,14 @@ class Tokenizer(ABC):
607
615
  # Make sure the token array doesn't start with a page break
608
616
  # on the very first page, adding a blank first page.
609
617
  if self._tokens[0][4] & self.A_PBB:
610
- tToken = self._tokens[0]
618
+ token = self._tokens[0]
611
619
  self._tokens[0] = (
612
- tToken[0], tToken[1], tToken[2], tToken[3], tToken[4] & ~self.A_PBB
620
+ token[0], token[1], token[2], token[3], token[4] & ~self.A_PBB
613
621
  )
614
622
 
615
623
  # Always add an empty line at the end of the file
616
624
  self._tokens.append((
617
- self.T_EMPTY, nHead, "", None, self.A_NONE
625
+ self.T_EMPTY, nHead, "", [], self.A_NONE
618
626
  ))
619
627
  if self._keepMarkdown:
620
628
  tmpMarkdown.append("\n")
@@ -626,25 +634,23 @@ class Tokenizer(ABC):
626
634
  # ===========
627
635
  # Some items need a second pass
628
636
 
629
- pToken = (self.T_EMPTY, 0, "", None, self.A_NONE)
630
- nToken = (self.T_EMPTY, 0, "", None, self.A_NONE)
637
+ pToken = (self.T_EMPTY, 0, "", [], self.A_NONE)
638
+ nToken = (self.T_EMPTY, 0, "", [], self.A_NONE)
631
639
  tCount = len(self._tokens)
632
- for n, tToken in enumerate(self._tokens):
640
+ for n, token in enumerate(self._tokens):
633
641
 
634
642
  if n > 0:
635
643
  pToken = self._tokens[n-1]
636
644
  if n < tCount - 1:
637
645
  nToken = self._tokens[n+1]
638
646
 
639
- if tToken[0] == self.T_KEYWORD:
640
- aStyle = tToken[4]
647
+ if token[0] == self.T_KEYWORD:
648
+ aStyle = token[4]
641
649
  if pToken[0] == self.T_KEYWORD:
642
650
  aStyle |= self.A_Z_TOPMRG
643
651
  if nToken[0] == self.T_KEYWORD:
644
652
  aStyle |= self.A_Z_BTMMRG
645
- self._tokens[n] = (
646
- tToken[0], tToken[1], tToken[2], tToken[3], aStyle
647
- )
653
+ self._tokens[n] = (token[0], token[1], token[2], token[3], aStyle)
648
654
 
649
655
  return
650
656
 
@@ -655,93 +661,89 @@ class Tokenizer(ABC):
655
661
  if not self._isNovel:
656
662
  return False
657
663
 
658
- for n, tToken in enumerate(self._tokens):
664
+ self._hFormatter.setHandle(self._nwItem.itemHandle if self._nwItem else None)
659
665
 
660
- # In case we see text before a scene, we reset the flag
661
- if tToken[0] == self.T_TEXT:
662
- self._firstScene = False
666
+ for n, token in enumerate(self._tokens):
663
667
 
664
- elif tToken[0] == self.T_HEAD1:
665
- # Partition
668
+ if token[0] == self.T_TITLE: # Title
669
+ # For new titles, we reset all counters
670
+ self._skipSeparator = True
671
+ self._hFormatter.resetAll()
666
672
 
667
- tTemp = self._hFormatter.apply(self._fmtTitle, tToken[2])
673
+ elif token[0] == self.T_HEAD1: # Partition
674
+
675
+ tTemp = self._hFormatter.apply(self._fmtTitle, token[2], token[1])
668
676
  self._tokens[n] = (
669
- tToken[0], tToken[1], tTemp, None, tToken[4]
677
+ token[0], token[1], tTemp, [], token[4]
670
678
  )
671
679
 
672
- elif tToken[0] in (self.T_HEAD2, self.T_UNNUM):
673
- # Chapter
680
+ # Set scene variables
681
+ self._skipSeparator = True
682
+ self._hFormatter.resetScene()
683
+
684
+ elif token[0] in (self.T_HEAD2, self.T_UNNUM): # Chapter, Unnumbered
674
685
 
675
686
  # Numbered or Unnumbered
676
- if tToken[0] == self.T_UNNUM:
677
- tTemp = self._hFormatter.apply(self._fmtUnNum, tToken[2])
687
+ if token[0] == self.T_UNNUM:
688
+ tTemp = self._hFormatter.apply(self._fmtUnNum, token[2], token[1])
678
689
  else:
679
690
  self._hFormatter.incChapter()
680
- tTemp = self._hFormatter.apply(self._fmtChapter, tToken[2])
691
+ tTemp = self._hFormatter.apply(self._fmtChapter, token[2], token[1])
681
692
 
682
693
  # Format the chapter header
683
694
  self._tokens[n] = (
684
- tToken[0], tToken[1], tTemp, None, tToken[4]
695
+ token[0], token[1], tTemp, [], token[4]
685
696
  )
686
697
 
687
698
  # Set scene variables
688
- self._firstScene = True
699
+ self._skipSeparator = True
689
700
  self._hFormatter.resetScene()
690
701
 
691
- elif tToken[0] == self.T_HEAD3:
692
- # Scene
702
+ elif token[0] == self.T_HEAD3: # Scene
693
703
 
694
704
  self._hFormatter.incScene()
695
705
 
696
- tTemp = self._hFormatter.apply(self._fmtScene, tToken[2])
706
+ tTemp = self._hFormatter.apply(self._fmtScene, token[2], token[1])
697
707
  if tTemp == "" and self._hideScene:
698
708
  self._tokens[n] = (
699
- self.T_EMPTY, tToken[1], "", None, self.A_NONE
709
+ self.T_EMPTY, token[1], "", [], self.A_NONE
700
710
  )
701
711
  elif tTemp == "" and not self._hideScene:
702
- if self._firstScene:
703
- self._tokens[n] = (
704
- self.T_EMPTY, tToken[1], "", None, self.A_NONE
705
- )
706
- else:
707
- self._tokens[n] = (
708
- self.T_SKIP, tToken[1], "", None, tToken[4]
709
- )
712
+ self._tokens[n] = (
713
+ self.T_EMPTY if self._skipSeparator else self.T_SKIP, token[1],
714
+ "", [], self.A_NONE if self._skipSeparator else token[4]
715
+ )
710
716
  elif tTemp == self._fmtScene:
711
- if self._firstScene:
712
- self._tokens[n] = (
713
- self.T_EMPTY, tToken[1], "", None, self.A_NONE
714
- )
715
- else:
716
- self._tokens[n] = (
717
- self.T_SEP, tToken[1], tTemp, None, tToken[4] | self.A_CENTRE
718
- )
717
+ self._tokens[n] = (
718
+ self.T_EMPTY if self._skipSeparator else self.T_SEP, token[1],
719
+ "" if self._skipSeparator else tTemp, [],
720
+ self.A_NONE if self._skipSeparator else (token[4] | self.A_CENTRE)
721
+ )
719
722
  else:
720
723
  self._tokens[n] = (
721
- tToken[0], tToken[1], tTemp, None, tToken[4]
724
+ token[0], token[1], tTemp, [], token[4]
722
725
  )
723
726
 
724
- self._firstScene = False
727
+ self._skipSeparator = False
725
728
 
726
- elif tToken[0] == self.T_HEAD4:
727
- # Section
729
+ elif token[0] == self.T_HEAD4: # Section
728
730
 
729
- tTemp = self._hFormatter.apply(self._fmtSection, tToken[2])
731
+ tTemp = self._hFormatter.apply(self._fmtSection, token[2], token[1])
730
732
  if tTemp == "" and self._hideSection:
731
733
  self._tokens[n] = (
732
- self.T_EMPTY, tToken[1], "", None, self.A_NONE
734
+ self.T_EMPTY, token[1], "", [], self.A_NONE
733
735
  )
734
736
  elif tTemp == "" and not self._hideSection:
735
737
  self._tokens[n] = (
736
- self.T_SKIP, tToken[1], "", None, tToken[4]
738
+ self.T_SKIP, token[1], "", [], token[4]
737
739
  )
738
740
  elif tTemp == self._fmtSection:
739
741
  self._tokens[n] = (
740
- self.T_SEP, tToken[1], tTemp, None, tToken[4] | self.A_CENTRE
742
+ self.T_SEP, token[1], tTemp, [], token[4] | self.A_CENTRE
741
743
  )
742
744
  else:
743
745
  self._tokens[n] = (
744
- tToken[0], tToken[1], tTemp, None, tToken[4]
746
+ token[0], token[1], tTemp, [], token[4]
745
747
  )
746
748
 
747
749
  return True
@@ -759,7 +761,6 @@ class Tokenizer(ABC):
759
761
  data = {
760
762
  "meta": {
761
763
  "projectName": self._project.data.name,
762
- "novelTitle": self._project.data.title,
763
764
  "novelAuthor": self._project.data.author,
764
765
  "buildTime": int(timeStamp),
765
766
  "buildTimeStr": formatTimeStamp(timeStamp),
@@ -818,11 +819,17 @@ class HeadingFormatter:
818
819
 
819
820
  def __init__(self, project: NWProject) -> None:
820
821
  self._project = project
822
+ self._handle = None
821
823
  self._chCount = 0
822
824
  self._scChCount = 0
823
825
  self._scAbsCount = 0
824
826
  return
825
827
 
828
+ def setHandle(self, tHandle: str | None) -> None:
829
+ """Set the handle currently being processed."""
830
+ self._handle = tHandle
831
+ return
832
+
826
833
  def incChapter(self) -> None:
827
834
  """Increment the chapter counter."""
828
835
  self._chCount += 1
@@ -834,12 +841,19 @@ class HeadingFormatter:
834
841
  self._scAbsCount += 1
835
842
  return
836
843
 
844
+ def resetAll(self) -> None:
845
+ """Reset all counters."""
846
+ self._chCount = 0
847
+ self._scChCount = 0
848
+ self._scAbsCount = 0
849
+ return
850
+
837
851
  def resetScene(self) -> None:
838
852
  """Reset the chapter scene counter."""
839
853
  self._scChCount = 0
840
854
  return
841
855
 
842
- def apply(self, hFormat: str, text: str) -> str:
856
+ def apply(self, hFormat: str, text: str, nHead: int) -> str:
843
857
  """Apply formatting to a specific heading."""
844
858
  hFormat = hFormat.replace(nwHeadFmt.TITLE, text)
845
859
  hFormat = hFormat.replace(nwHeadFmt.CH_NUM, str(self._chCount))
@@ -855,6 +869,19 @@ class HeadingFormatter:
855
869
  chRom = numberToRoman(self._chCount, toLower=False)
856
870
  hFormat = hFormat.replace(nwHeadFmt.CH_ROMU, chRom)
857
871
 
872
+ if nwHeadFmt.CHAR_POV in hFormat or nwHeadFmt.CHAR_FOCUS in hFormat:
873
+ if self._handle and nHead > 0:
874
+ index = self._project.index
875
+ pList = index.getReferenceForHeader(self._handle, nHead, nwKeyWords.POV_KEY)
876
+ fList = index.getReferenceForHeader(self._handle, nHead, nwKeyWords.FOCUS_KEY)
877
+ pText = pList[0] if pList else nwUnicode.U_ENDASH
878
+ fText = fList[0] if fList else nwUnicode.U_ENDASH
879
+ else:
880
+ pText = trConst(nwLabels.KEY_NAME[nwKeyWords.POV_KEY])
881
+ fText = trConst(nwLabels.KEY_NAME[nwKeyWords.FOCUS_KEY])
882
+ hFormat = hFormat.replace(nwHeadFmt.CHAR_POV, pText)
883
+ hFormat = hFormat.replace(nwHeadFmt.CHAR_FOCUS, fText)
884
+
858
885
  return hFormat
859
886
 
860
887
  # END Class HeadingFormatter
novelwriter/core/tomd.py CHANGED
@@ -3,10 +3,10 @@ novelWriter – Markdown Text Converter
3
3
  =====================================
4
4
 
5
5
  File History:
6
- Created: 2021-02-06 [1.2b1]
6
+ Created: 2021-02-06 [1.2b1] ToMarkdown
7
7
 
8
8
  This file is a part of novelWriter
9
- Copyright 2018–2023, Veronica Berglyd Olsen
9
+ Copyright 2018–2024, Veronica Berglyd Olsen
10
10
 
11
11
  This program is free software: you can redistribute it and/or modify
12
12
  it under the terms of the GNU General Public License as published by
novelwriter/core/toodt.py CHANGED
@@ -9,7 +9,7 @@ Created: 2021-01-27 [1.2b1] ODTTextStyle
9
9
  Created: 2021-08-14 [1.5b1] XMLParagraph
10
10
 
11
11
  This file is a part of novelWriter
12
- Copyright 2018–2023, Veronica Berglyd Olsen
12
+ Copyright 2018–2024, Veronica Berglyd Olsen
13
13
 
14
14
  This program is free software: you can redistribute it and/or modify
15
15
  it under the terms of the GNU General Public License as published by
@@ -29,11 +29,11 @@ from __future__ import annotations
29
29
  import logging
30
30
  import xml.etree.ElementTree as ET
31
31
 
32
- from typing import Sequence
33
32
  from hashlib import sha256
34
33
  from pathlib import Path
35
34
  from zipfile import ZipFile
36
35
  from datetime import datetime
36
+ from collections.abc import Sequence
37
37
 
38
38
  from novelwriter import __version__
39
39
  from novelwriter.common import xmlIndent
@@ -132,11 +132,12 @@ class ToOdt(Tokenizer):
132
132
  self._errData = [] # List of errors encountered
133
133
 
134
134
  # Properties
135
- self._textFont = "Liberation Serif"
136
- self._textSize = 12
137
- self._textFixed = False
138
- self._colourHead = False
139
- self._headerText = ""
135
+ self._textFont = "Liberation Serif"
136
+ self._textSize = 12
137
+ self._textFixed = False
138
+ self._colourHead = False
139
+ self._headerFormat = ""
140
+ self._pageOffset = 0
140
141
 
141
142
  # Internal
142
143
  self._fontFamily = "&apos;Liberation Serif&apos;"
@@ -222,6 +223,12 @@ class ToOdt(Tokenizer):
222
223
  self._mDocRight = f"{right/10.0:.3f}cm"
223
224
  return
224
225
 
226
+ def setHeaderFormat(self, format: str, offset: int) -> None:
227
+ """Set the document header format."""
228
+ self._headerFormat = format.strip()
229
+ self._pageOffset = offset
230
+ return
231
+
225
232
  ##
226
233
  # Class Methods
227
234
  ##
@@ -279,14 +286,6 @@ class ToOdt(Tokenizer):
279
286
  # Clear Errors
280
287
  self._errData = []
281
288
 
282
- # Document Header
283
- # ===============
284
-
285
- if self._headerText == "":
286
- theTitle = self._project.data.title or self._project.data.name
287
- theAuth = self._project.data.author
288
- self._headerText = f"{theTitle} / {theAuth} /"
289
-
290
289
  # Create Roots
291
290
  # ============
292
291
 
@@ -373,7 +372,7 @@ class ToOdt(Tokenizer):
373
372
 
374
373
  # Dublin Core Meta Data
375
374
  xMeta = ET.SubElement(self._xMeta, _mkTag("dc", "title"))
376
- xMeta.text = self._project.data.title or self._project.data.name
375
+ xMeta.text = self._project.data.name
377
376
 
378
377
  xMeta = ET.SubElement(self._xMeta, _mkTag("dc", "date"))
379
378
  xMeta.text = timeStamp
@@ -684,12 +683,12 @@ class ToOdt(Tokenizer):
684
683
  return parName
685
684
 
686
685
  oStyle.setParentStyleName(parName)
687
- theID = oStyle.getID()
688
- if theID in self._autoPara:
689
- return self._autoPara[theID][0]
686
+ pID = oStyle.getID()
687
+ if pID in self._autoPara:
688
+ return self._autoPara[pID][0]
690
689
 
691
690
  newName = "P%d" % (len(self._autoPara) + 1)
692
- self._autoPara[theID] = (newName, oStyle)
691
+ self._autoPara[pID] = (newName, oStyle)
693
692
 
694
693
  return newName
695
694
 
@@ -1012,17 +1011,28 @@ class ToOdt(Tokenizer):
1012
1011
  xPage = ET.SubElement(self._xMast, _mkTag("style", "master-page"), attrib=tAttr)
1013
1012
 
1014
1013
  # Standard Page Header
1015
- xHead = ET.SubElement(xPage, _mkTag("style", "header"))
1016
- xPar = ET.SubElement(xHead, _mkTag("text", "p"), attrib={
1017
- _mkTag("text", "style-name"): "Header"
1018
- })
1019
- xPar.text = self._headerText.strip() + " "
1020
-
1021
- xTail = ET.SubElement(xPar, _mkTag("text", "page-number"), attrib={
1022
- _mkTag("text", "select-page"): "current"
1023
- })
1024
- xTail.text = "2"
1025
- xTail.tail = "" # Prevent line break in indented XML
1014
+ if self._headerFormat:
1015
+ pre, page, post = self._headerFormat.partition(nwHeadFmt.ODT_PAGE)
1016
+
1017
+ pre = pre.replace(nwHeadFmt.ODT_PROJECT, self._project.data.name)
1018
+ pre = pre.replace(nwHeadFmt.ODT_AUTHOR, self._project.data.author)
1019
+ post = post.replace(nwHeadFmt.ODT_PROJECT, self._project.data.name)
1020
+ post = post.replace(nwHeadFmt.ODT_AUTHOR, self._project.data.author)
1021
+
1022
+ xHead = ET.SubElement(xPage, _mkTag("style", "header"))
1023
+ xPar = ET.SubElement(xHead, _mkTag("text", "p"), attrib={
1024
+ _mkTag("text", "style-name"): "Header"
1025
+ })
1026
+ xPar.text = pre
1027
+ if page:
1028
+ attrib = {_mkTag("text", "select-page"): "current"}
1029
+ if self._pageOffset > 0:
1030
+ attrib = {_mkTag("text", "page-adjust"): str(0 - self._pageOffset)}
1031
+ xTail = ET.SubElement(xPar, _mkTag("text", "page-number"), attrib=attrib)
1032
+ xTail.text = "2"
1033
+ xTail.tail = post
1034
+ else:
1035
+ xPar.text += post
1026
1036
 
1027
1037
  # First Page Header
1028
1038
  xHead = ET.SubElement(xPage, _mkTag("style", "header-first"))
novelwriter/core/tree.py CHANGED
@@ -3,10 +3,10 @@ novelWriter – Project Tree Class
3
3
  ================================
4
4
 
5
5
  File History:
6
- Created: 2020-05-07 [0.4.5]
6
+ Created: 2020-05-07 [0.4.5] NWTree
7
7
 
8
8
  This file is a part of novelWriter
9
- Copyright 2018–2023, Veronica Berglyd Olsen
9
+ Copyright 2018–2024, Veronica Berglyd Olsen
10
10
 
11
11
  This program is free software: you can redistribute it and/or modify
12
12
  it under the terms of the GNU General Public License as published by
@@ -26,8 +26,9 @@ from __future__ import annotations
26
26
  import random
27
27
  import logging
28
28
 
29
- from typing import TYPE_CHECKING, Iterator, Literal, overload
29
+ from typing import TYPE_CHECKING, Literal, overload
30
30
  from pathlib import Path
31
+ from collections.abc import Iterator
31
32
 
32
33
  from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType
33
34
  from novelwriter.error import logException
@@ -500,7 +501,7 @@ class NWTree:
500
501
  ##
501
502
 
502
503
  def _setTreeChanged(self, state: bool) -> None:
503
- """Set the changed flag to theState, and if being set to True,
504
+ """Set the changed flag to state, and if being set to True,
504
505
  propagate that state change to the parent NWProject class.
505
506
  """
506
507
  self._changed = state