novelWriter 2.2.1__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.
- {novelWriter-2.2.1.dist-info → novelWriter-2.3.dist-info}/METADATA +1 -1
- {novelWriter-2.2.1.dist-info → novelWriter-2.3.dist-info}/RECORD +116 -101
- novelWriter-2.3.dist-info/entry_points.txt +2 -0
- novelwriter/__init__.py +4 -4
- novelwriter/assets/i18n/nw_de_DE.qm +0 -0
- novelwriter/assets/i18n/nw_en_US.qm +0 -0
- novelwriter/assets/i18n/nw_es_419.qm +0 -0
- novelwriter/assets/i18n/nw_it_IT.qm +0 -0
- novelwriter/assets/i18n/nw_ja_JP.qm +0 -0
- novelwriter/assets/i18n/nw_nb_NO.qm +0 -0
- novelwriter/assets/i18n/nw_nl_NL.qm +0 -0
- novelwriter/assets/i18n/project_nl_NL.json +11 -0
- novelwriter/assets/i18n/project_pt_BR.json +11 -0
- novelwriter/assets/icons/typicons_dark/icons.conf +8 -0
- novelwriter/assets/icons/typicons_dark/mixed_document-new.svg +6 -0
- novelwriter/assets/icons/typicons_dark/mixed_import.svg +5 -0
- novelwriter/assets/icons/typicons_dark/typ_document-add.svg +4 -0
- novelwriter/assets/icons/typicons_dark/typ_document.svg +4 -0
- novelwriter/assets/icons/typicons_dark/typ_th-dot-more.svg +4 -0
- novelwriter/assets/icons/typicons_dark/typ_th-list.svg +9 -0
- novelwriter/assets/icons/typicons_light/icons.conf +8 -0
- novelwriter/assets/icons/typicons_light/mixed_document-new.svg +6 -0
- novelwriter/assets/icons/typicons_light/mixed_import.svg +5 -0
- novelwriter/assets/icons/typicons_light/typ_document-add.svg +4 -0
- novelwriter/assets/icons/typicons_light/typ_document.svg +4 -0
- novelwriter/assets/icons/typicons_light/typ_th-dot-more.svg +4 -0
- novelwriter/assets/icons/typicons_light/typ_th-list.svg +9 -0
- novelwriter/assets/images/novelwriter-text-dark.svg +4 -0
- novelwriter/assets/images/novelwriter-text-light.svg +4 -0
- novelwriter/assets/images/welcome-dark.jpg +0 -0
- novelwriter/assets/images/welcome-light.jpg +0 -0
- novelwriter/assets/manual.pdf +0 -0
- novelwriter/assets/sample.zip +0 -0
- novelwriter/assets/syntax/cyberpunk_night.conf +26 -0
- novelwriter/assets/syntax/default_dark.conf +1 -0
- novelwriter/assets/syntax/default_light.conf +1 -0
- novelwriter/assets/syntax/grey_dark.conf +1 -0
- novelwriter/assets/syntax/grey_light.conf +1 -0
- novelwriter/assets/syntax/light_owl.conf +1 -0
- novelwriter/assets/syntax/night_owl.conf +1 -0
- novelwriter/assets/syntax/solarized_dark.conf +1 -0
- novelwriter/assets/syntax/solarized_light.conf +1 -0
- novelwriter/assets/syntax/tango.conf +23 -0
- novelwriter/assets/syntax/tomorrow.conf +1 -0
- novelwriter/assets/syntax/tomorrow_night.conf +1 -0
- novelwriter/assets/syntax/tomorrow_night_blue.conf +1 -0
- novelwriter/assets/syntax/tomorrow_night_bright.conf +1 -0
- novelwriter/assets/syntax/tomorrow_night_eighties.conf +1 -0
- novelwriter/assets/text/credits_en.htm +4 -2
- novelwriter/assets/themes/cyberpunk_night.conf +29 -0
- novelwriter/assets/themes/default_dark.conf +2 -2
- novelwriter/assets/themes/default_light.conf +2 -2
- novelwriter/common.py +48 -37
- novelwriter/config.py +36 -41
- novelwriter/constants.py +38 -16
- novelwriter/core/buildsettings.py +7 -7
- novelwriter/core/coretools.py +196 -156
- novelwriter/core/docbuild.py +6 -3
- novelwriter/core/document.py +6 -6
- novelwriter/core/index.py +89 -56
- novelwriter/core/item.py +21 -3
- novelwriter/core/options.py +8 -7
- novelwriter/core/project.py +70 -44
- novelwriter/core/projectdata.py +1 -14
- novelwriter/core/projectxml.py +13 -41
- novelwriter/core/sessions.py +2 -1
- novelwriter/core/spellcheck.py +2 -1
- novelwriter/core/status.py +2 -1
- novelwriter/core/storage.py +182 -140
- novelwriter/core/tohtml.py +4 -2
- novelwriter/core/tokenizer.py +109 -82
- novelwriter/core/toodt.py +40 -30
- novelwriter/core/tree.py +3 -2
- novelwriter/dialogs/about.py +70 -160
- novelwriter/dialogs/docmerge.py +6 -5
- novelwriter/dialogs/docsplit.py +6 -6
- novelwriter/dialogs/editlabel.py +1 -1
- novelwriter/dialogs/preferences.py +553 -703
- novelwriter/dialogs/{projsettings.py → projectsettings.py} +288 -262
- novelwriter/dialogs/quotes.py +27 -23
- novelwriter/dialogs/wordlist.py +96 -40
- novelwriter/enum.py +20 -18
- novelwriter/error.py +1 -1
- novelwriter/extensions/circularprogress.py +11 -11
- novelwriter/extensions/configlayout.py +185 -134
- novelwriter/extensions/modified.py +81 -0
- novelwriter/extensions/novelselector.py +26 -12
- novelwriter/extensions/pagedsidebar.py +14 -16
- novelwriter/extensions/simpleprogress.py +5 -5
- novelwriter/extensions/statusled.py +8 -8
- novelwriter/extensions/switch.py +31 -63
- novelwriter/extensions/switchbox.py +1 -1
- novelwriter/extensions/versioninfo.py +153 -0
- novelwriter/gui/doceditor.py +178 -150
- novelwriter/gui/dochighlight.py +63 -92
- novelwriter/gui/docviewer.py +49 -51
- novelwriter/gui/docviewerpanel.py +72 -24
- novelwriter/gui/itemdetails.py +7 -7
- novelwriter/gui/mainmenu.py +14 -19
- novelwriter/gui/noveltree.py +9 -8
- novelwriter/gui/outline.py +98 -75
- novelwriter/gui/projtree.py +241 -106
- novelwriter/gui/sidebar.py +3 -4
- novelwriter/gui/statusbar.py +3 -4
- novelwriter/gui/theme.py +69 -70
- novelwriter/guimain.py +51 -156
- novelwriter/shared.py +15 -1
- novelwriter/tools/dictionaries.py +5 -6
- novelwriter/tools/manuscript.py +6 -6
- novelwriter/tools/manussettings.py +192 -221
- novelwriter/tools/noveldetails.py +525 -0
- novelwriter/tools/welcome.py +819 -0
- novelwriter/tools/writingstats.py +9 -9
- novelWriter-2.2.1.dist-info/entry_points.txt +0 -5
- novelwriter/assets/images/wizard-back.jpg +0 -0
- novelwriter/assets/text/gplv3_en.htm +0 -641
- novelwriter/assets/text/release_notes.htm +0 -60
- novelwriter/dialogs/projdetails.py +0 -518
- novelwriter/dialogs/projload.py +0 -294
- novelwriter/dialogs/updates.py +0 -172
- novelwriter/extensions/pageddialog.py +0 -130
- novelwriter/tools/projwizard.py +0 -478
- {novelWriter-2.2.1.dist-info → novelWriter-2.3.dist-info}/LICENSE.md +0 -0
- {novelWriter-2.2.1.dist-info → novelWriter-2.3.dist-info}/WHEEL +0 -0
- {novelWriter-2.2.1.dist-info → novelWriter-2.3.dist-info}/top_level.txt +0 -0
novelwriter/core/tokenizer.py
CHANGED
@@ -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
|
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
|
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
|
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.
|
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
|
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
|
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,
|
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
|
362
|
-
|
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, "",
|
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, "",
|
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, "",
|
461
|
+
(self.T_SKIP, nHead, "", [], sAlign)
|
458
462
|
)
|
459
463
|
if nSkip > 1:
|
460
464
|
self._tokens += (nSkip - 1) * [
|
461
|
-
(self.T_SKIP, nHead, "",
|
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,
|
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,
|
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,
|
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(),
|
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(),
|
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(),
|
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(),
|
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(),
|
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(),
|
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(),
|
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
|
-
|
618
|
+
token = self._tokens[0]
|
611
619
|
self._tokens[0] = (
|
612
|
-
|
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, "",
|
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, "",
|
630
|
-
nToken = (self.T_EMPTY, 0, "",
|
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,
|
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
|
640
|
-
aStyle =
|
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
|
-
|
664
|
+
self._hFormatter.setHandle(self._nwItem.itemHandle if self._nwItem else None)
|
659
665
|
|
660
|
-
|
661
|
-
if tToken[0] == self.T_TEXT:
|
662
|
-
self._firstScene = False
|
666
|
+
for n, token in enumerate(self._tokens):
|
663
667
|
|
664
|
-
|
665
|
-
#
|
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
|
-
|
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
|
-
|
677
|
+
token[0], token[1], tTemp, [], token[4]
|
670
678
|
)
|
671
679
|
|
672
|
-
|
673
|
-
|
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
|
677
|
-
tTemp = self._hFormatter.apply(self._fmtUnNum,
|
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,
|
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
|
-
|
695
|
+
token[0], token[1], tTemp, [], token[4]
|
685
696
|
)
|
686
697
|
|
687
698
|
# Set scene variables
|
688
|
-
self.
|
699
|
+
self._skipSeparator = True
|
689
700
|
self._hFormatter.resetScene()
|
690
701
|
|
691
|
-
elif
|
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,
|
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,
|
709
|
+
self.T_EMPTY, token[1], "", [], self.A_NONE
|
700
710
|
)
|
701
711
|
elif tTemp == "" and not self._hideScene:
|
702
|
-
|
703
|
-
self.
|
704
|
-
|
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
|
-
|
712
|
-
self.
|
713
|
-
|
714
|
-
)
|
715
|
-
|
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
|
-
|
724
|
+
token[0], token[1], tTemp, [], token[4]
|
722
725
|
)
|
723
726
|
|
724
|
-
self.
|
727
|
+
self._skipSeparator = False
|
725
728
|
|
726
|
-
elif
|
727
|
-
# Section
|
729
|
+
elif token[0] == self.T_HEAD4: # Section
|
728
730
|
|
729
|
-
tTemp = self._hFormatter.apply(self._fmtSection,
|
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,
|
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,
|
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,
|
742
|
+
self.T_SEP, token[1], tTemp, [], token[4] | self.A_CENTRE
|
741
743
|
)
|
742
744
|
else:
|
743
745
|
self._tokens[n] = (
|
744
|
-
|
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/toodt.py
CHANGED
@@ -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
|
136
|
-
self._textSize
|
137
|
-
self._textFixed
|
138
|
-
self._colourHead
|
139
|
-
self.
|
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 = "'Liberation Serif'"
|
@@ -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.
|
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
|
-
|
688
|
-
if
|
689
|
-
return self._autoPara[
|
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[
|
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
|
-
|
1016
|
-
|
1017
|
-
|
1018
|
-
|
1019
|
-
|
1020
|
-
|
1021
|
-
|
1022
|
-
|
1023
|
-
|
1024
|
-
|
1025
|
-
|
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
@@ -26,8 +26,9 @@ from __future__ import annotations
|
|
26
26
|
import random
|
27
27
|
import logging
|
28
28
|
|
29
|
-
from typing import TYPE_CHECKING,
|
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
|
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
|