novelWriter 2.5.3__py3-none-any.whl → 2.6b2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {novelWriter-2.5.3.dist-info → novelWriter-2.6b2.dist-info}/METADATA +1 -1
- {novelWriter-2.5.3.dist-info → novelWriter-2.6b2.dist-info}/RECORD +80 -60
- novelwriter/__init__.py +49 -10
- novelwriter/assets/i18n/project_en_GB.json +1 -0
- novelwriter/assets/icons/typicons_dark/icons.conf +8 -0
- novelwriter/assets/icons/typicons_dark/mixed_copy.svg +4 -0
- novelwriter/assets/icons/typicons_dark/mixed_margin-bottom.svg +6 -0
- novelwriter/assets/icons/typicons_dark/mixed_margin-left.svg +6 -0
- novelwriter/assets/icons/typicons_dark/mixed_margin-right.svg +6 -0
- novelwriter/assets/icons/typicons_dark/mixed_margin-top.svg +6 -0
- novelwriter/assets/icons/typicons_dark/mixed_size-height.svg +6 -0
- novelwriter/assets/icons/typicons_dark/mixed_size-width.svg +6 -0
- novelwriter/assets/icons/typicons_dark/nw_toolbar.svg +5 -0
- novelwriter/assets/icons/typicons_light/icons.conf +8 -0
- novelwriter/assets/icons/typicons_light/mixed_copy.svg +4 -0
- novelwriter/assets/icons/typicons_light/mixed_margin-bottom.svg +6 -0
- novelwriter/assets/icons/typicons_light/mixed_margin-left.svg +6 -0
- novelwriter/assets/icons/typicons_light/mixed_margin-right.svg +6 -0
- novelwriter/assets/icons/typicons_light/mixed_margin-top.svg +6 -0
- novelwriter/assets/icons/typicons_light/mixed_size-height.svg +6 -0
- novelwriter/assets/icons/typicons_light/mixed_size-width.svg +6 -0
- novelwriter/assets/icons/typicons_light/nw_toolbar.svg +5 -0
- novelwriter/assets/manual.pdf +0 -0
- novelwriter/assets/sample.zip +0 -0
- novelwriter/common.py +100 -2
- novelwriter/config.py +25 -15
- novelwriter/constants.py +168 -60
- novelwriter/core/buildsettings.py +66 -39
- novelwriter/core/coretools.py +145 -147
- novelwriter/core/docbuild.py +132 -170
- novelwriter/core/index.py +38 -37
- novelwriter/core/item.py +41 -8
- novelwriter/core/itemmodel.py +518 -0
- novelwriter/core/options.py +4 -1
- novelwriter/core/project.py +67 -89
- novelwriter/core/spellcheck.py +9 -14
- novelwriter/core/status.py +7 -5
- novelwriter/core/tree.py +268 -287
- novelwriter/dialogs/docmerge.py +7 -17
- novelwriter/dialogs/preferences.py +46 -33
- novelwriter/dialogs/projectsettings.py +5 -5
- novelwriter/enum.py +36 -23
- novelwriter/extensions/configlayout.py +27 -12
- novelwriter/extensions/modified.py +13 -1
- novelwriter/extensions/pagedsidebar.py +5 -5
- novelwriter/formats/shared.py +155 -0
- novelwriter/formats/todocx.py +1191 -0
- novelwriter/formats/tohtml.py +451 -0
- novelwriter/{core → formats}/tokenizer.py +487 -491
- novelwriter/formats/tomarkdown.py +217 -0
- novelwriter/{core → formats}/toodt.py +311 -432
- novelwriter/formats/toqdoc.py +484 -0
- novelwriter/formats/toraw.py +91 -0
- novelwriter/gui/doceditor.py +342 -284
- novelwriter/gui/dochighlight.py +96 -84
- novelwriter/gui/docviewer.py +88 -31
- novelwriter/gui/docviewerpanel.py +17 -25
- novelwriter/gui/editordocument.py +17 -2
- novelwriter/gui/itemdetails.py +25 -28
- novelwriter/gui/mainmenu.py +129 -63
- novelwriter/gui/noveltree.py +45 -47
- novelwriter/gui/outline.py +196 -249
- novelwriter/gui/projtree.py +594 -1241
- novelwriter/gui/search.py +9 -10
- novelwriter/gui/sidebar.py +7 -6
- novelwriter/gui/theme.py +10 -5
- novelwriter/guimain.py +100 -196
- novelwriter/shared.py +66 -27
- novelwriter/text/counting.py +2 -0
- novelwriter/text/patterns.py +168 -60
- novelwriter/tools/manusbuild.py +14 -12
- novelwriter/tools/manuscript.py +120 -78
- novelwriter/tools/manussettings.py +424 -291
- novelwriter/tools/welcome.py +4 -4
- novelwriter/tools/writingstats.py +3 -3
- novelwriter/types.py +23 -7
- novelwriter/core/tohtml.py +0 -530
- novelwriter/core/tomarkdown.py +0 -252
- novelwriter/core/toqdoc.py +0 -419
- {novelWriter-2.5.3.dist-info → novelWriter-2.6b2.dist-info}/LICENSE.md +0 -0
- {novelWriter-2.5.3.dist-info → novelWriter-2.6b2.dist-info}/WHEEL +0 -0
- {novelWriter-2.5.3.dist-info → novelWriter-2.6b2.dist-info}/entry_points.txt +0 -0
- {novelWriter-2.5.3.dist-info → novelWriter-2.6b2.dist-info}/top_level.txt +0 -0
@@ -36,7 +36,7 @@ from PyQt5.QtCore import QT_TRANSLATE_NOOP, QCoreApplication
|
|
36
36
|
|
37
37
|
from novelwriter import CONFIG
|
38
38
|
from novelwriter.common import checkUuid, isHandle, jsonEncode
|
39
|
-
from novelwriter.constants import nwFiles, nwHeadFmt
|
39
|
+
from novelwriter.constants import nwFiles, nwHeadFmt, nwStyles
|
40
40
|
from novelwriter.core.project import NWProject
|
41
41
|
from novelwriter.enum import nwBuildFmt
|
42
42
|
from novelwriter.error import logException
|
@@ -45,29 +45,30 @@ logger = logging.getLogger(__name__)
|
|
45
45
|
|
46
46
|
# The Settings Template
|
47
47
|
# =====================
|
48
|
-
# Each entry contains a tuple on the form:
|
49
|
-
# (type, default, [min value, max value])
|
48
|
+
# Each entry contains a tuple on the form: (type, default)
|
50
49
|
|
51
|
-
SETTINGS_TEMPLATE = {
|
50
|
+
SETTINGS_TEMPLATE: dict[str, tuple[type, str | int | float | bool]] = {
|
52
51
|
"filter.includeNovel": (bool, True),
|
53
52
|
"filter.includeNotes": (bool, False),
|
54
53
|
"filter.includeInactive": (bool, False),
|
55
|
-
"headings.
|
54
|
+
"headings.fmtPart": (str, nwHeadFmt.TITLE),
|
56
55
|
"headings.fmtChapter": (str, nwHeadFmt.TITLE),
|
57
56
|
"headings.fmtUnnumbered": (str, nwHeadFmt.TITLE),
|
58
57
|
"headings.fmtScene": (str, "* * *"),
|
59
58
|
"headings.fmtAltScene": (str, ""),
|
60
59
|
"headings.fmtSection": (str, ""),
|
61
|
-
"headings.
|
60
|
+
"headings.hidePart": (bool, False),
|
62
61
|
"headings.hideChapter": (bool, False),
|
63
62
|
"headings.hideUnnumbered": (bool, False),
|
64
63
|
"headings.hideScene": (bool, False),
|
65
64
|
"headings.hideAltScene": (bool, False),
|
66
65
|
"headings.hideSection": (bool, True),
|
67
66
|
"headings.centerTitle": (bool, True),
|
67
|
+
"headings.centerPart": (bool, True),
|
68
68
|
"headings.centerChapter": (bool, False),
|
69
69
|
"headings.centerScene": (bool, False),
|
70
|
-
"headings.breakTitle": (bool,
|
70
|
+
"headings.breakTitle": (bool, False),
|
71
|
+
"headings.breakPart": (bool, True),
|
71
72
|
"headings.breakChapter": (bool, True),
|
72
73
|
"headings.breakScene": (bool, False),
|
73
74
|
"text.includeSynopsis": (bool, False),
|
@@ -77,7 +78,7 @@ SETTINGS_TEMPLATE = {
|
|
77
78
|
"text.ignoredKeywords": (str, ""),
|
78
79
|
"text.addNoteHeadings": (bool, True),
|
79
80
|
"format.textFont": (str, CONFIG.textFont.toString()),
|
80
|
-
"format.lineHeight": (float, 1.15
|
81
|
+
"format.lineHeight": (float, 1.15),
|
81
82
|
"format.justifyText": (bool, False),
|
82
83
|
"format.stripUnicode": (bool, False),
|
83
84
|
"format.replaceTabs": (bool, False),
|
@@ -86,6 +87,20 @@ SETTINGS_TEMPLATE = {
|
|
86
87
|
"format.firstLineIndent": (bool, False),
|
87
88
|
"format.firstIndentWidth": (float, 1.4),
|
88
89
|
"format.indentFirstPar": (bool, False),
|
90
|
+
"format.titleMarginT": (float, nwStyles.T_MARGIN["H0"][0]),
|
91
|
+
"format.titleMarginB": (float, nwStyles.T_MARGIN["H0"][1]),
|
92
|
+
"format.h1MarginT": (float, nwStyles.T_MARGIN["H1"][0]),
|
93
|
+
"format.h1MarginB": (float, nwStyles.T_MARGIN["H1"][1]),
|
94
|
+
"format.h2MarginT": (float, nwStyles.T_MARGIN["H2"][0]),
|
95
|
+
"format.h2MarginB": (float, nwStyles.T_MARGIN["H2"][1]),
|
96
|
+
"format.h3MarginT": (float, nwStyles.T_MARGIN["H3"][0]),
|
97
|
+
"format.h3MarginB": (float, nwStyles.T_MARGIN["H3"][1]),
|
98
|
+
"format.h4MarginT": (float, nwStyles.T_MARGIN["H4"][0]),
|
99
|
+
"format.h4MarginB": (float, nwStyles.T_MARGIN["H4"][1]),
|
100
|
+
"format.textMarginT": (float, nwStyles.T_MARGIN["TT"][0]),
|
101
|
+
"format.textMarginB": (float, nwStyles.T_MARGIN["TT"][1]),
|
102
|
+
"format.sepMarginT": (float, nwStyles.T_MARGIN["SP"][0]),
|
103
|
+
"format.sepMarginB": (float, nwStyles.T_MARGIN["SP"][1]),
|
89
104
|
"format.pageUnit": (str, "cm"),
|
90
105
|
"format.pageSize": (str, "A4"),
|
91
106
|
"format.pageWidth": (float, 21.0),
|
@@ -94,9 +109,11 @@ SETTINGS_TEMPLATE = {
|
|
94
109
|
"format.bottomMargin": (float, 2.0),
|
95
110
|
"format.leftMargin": (float, 2.0),
|
96
111
|
"format.rightMargin": (float, 2.0),
|
97
|
-
"
|
98
|
-
"
|
99
|
-
"
|
112
|
+
"doc.pageHeader": (str, nwHeadFmt.DOC_AUTO),
|
113
|
+
"doc.pageCountOffset": (int, 0),
|
114
|
+
"doc.colorHeadings": (bool, True),
|
115
|
+
"doc.scaleHeadings": (bool, True),
|
116
|
+
"doc.boldHeadings": (bool, True),
|
100
117
|
"html.addStyles": (bool, True),
|
101
118
|
"html.preserveTabs": (bool, False),
|
102
119
|
}
|
@@ -108,12 +125,16 @@ SETTINGS_LABELS = {
|
|
108
125
|
"filter.includeInactive": QT_TRANSLATE_NOOP("Builds", "Inactive Documents"),
|
109
126
|
|
110
127
|
"headings": QT_TRANSLATE_NOOP("Builds", "Headings"),
|
111
|
-
"headings.
|
128
|
+
"headings.fmtPart": QT_TRANSLATE_NOOP("Builds", "Partition Format"),
|
112
129
|
"headings.fmtChapter": QT_TRANSLATE_NOOP("Builds", "Chapter Format"),
|
113
130
|
"headings.fmtUnnumbered": QT_TRANSLATE_NOOP("Builds", "Unnumbered Format"),
|
114
131
|
"headings.fmtScene": QT_TRANSLATE_NOOP("Builds", "Scene Format"),
|
115
132
|
"headings.fmtAltScene": QT_TRANSLATE_NOOP("Builds", "Alt. Scene Format"),
|
116
133
|
"headings.fmtSection": QT_TRANSLATE_NOOP("Builds", "Section Format"),
|
134
|
+
"headings.styleTitle": QT_TRANSLATE_NOOP("Builds", "Title Styling"),
|
135
|
+
"headings.stylePart": QT_TRANSLATE_NOOP("Builds", "Partition Styling"),
|
136
|
+
"headings.styleChapter": QT_TRANSLATE_NOOP("Builds", "Chapter Styling"),
|
137
|
+
"headings.styleScene": QT_TRANSLATE_NOOP("Builds", "Scene Styling"),
|
117
138
|
|
118
139
|
"text.grpContent": QT_TRANSLATE_NOOP("Builds", "Text Content"),
|
119
140
|
"text.includeSynopsis": QT_TRANSLATE_NOOP("Builds", "Include Synopsis"),
|
@@ -121,13 +142,11 @@ SETTINGS_LABELS = {
|
|
121
142
|
"text.includeKeywords": QT_TRANSLATE_NOOP("Builds", "Include Keywords"),
|
122
143
|
"text.includeBodyText": QT_TRANSLATE_NOOP("Builds", "Include Body Text"),
|
123
144
|
"text.ignoredKeywords": QT_TRANSLATE_NOOP("Builds", "Ignore These Keywords"),
|
124
|
-
"text.grpInsert": QT_TRANSLATE_NOOP("Builds", "Insert Content"),
|
125
145
|
"text.addNoteHeadings": QT_TRANSLATE_NOOP("Builds", "Add Titles for Notes"),
|
126
146
|
|
127
147
|
"format.grpFormat": QT_TRANSLATE_NOOP("Builds", "Text Format"),
|
128
148
|
"format.textFont": QT_TRANSLATE_NOOP("Builds", "Text Font"),
|
129
149
|
"format.lineHeight": QT_TRANSLATE_NOOP("Builds", "Line Height"),
|
130
|
-
"format.grpOptions": QT_TRANSLATE_NOOP("Builds", "Text Options"),
|
131
150
|
"format.justifyText": QT_TRANSLATE_NOOP("Builds", "Justify Text Margins"),
|
132
151
|
"format.stripUnicode": QT_TRANSLATE_NOOP("Builds", "Replace Unicode Characters"),
|
133
152
|
"format.replaceTabs": QT_TRANSLATE_NOOP("Builds", "Replace Tabs with Spaces"),
|
@@ -139,26 +158,31 @@ SETTINGS_LABELS = {
|
|
139
158
|
"format.firstIndentWidth": QT_TRANSLATE_NOOP("Builds", "Indent Width"),
|
140
159
|
"format.indentFirstPar": QT_TRANSLATE_NOOP("Builds", "Indent First Paragraph"),
|
141
160
|
|
161
|
+
"format.grpMargins": QT_TRANSLATE_NOOP("Builds", "Text Margins"),
|
162
|
+
|
142
163
|
"format.grpPage": QT_TRANSLATE_NOOP("Builds", "Page Layout"),
|
143
164
|
"format.pageUnit": QT_TRANSLATE_NOOP("Builds", "Unit"),
|
144
165
|
"format.pageSize": QT_TRANSLATE_NOOP("Builds", "Page Size"),
|
145
|
-
"format.
|
146
|
-
|
147
|
-
"
|
148
|
-
"
|
149
|
-
"
|
150
|
-
"
|
151
|
-
|
152
|
-
"
|
153
|
-
|
154
|
-
"
|
155
|
-
"odt.pageCountOffset": QT_TRANSLATE_NOOP("Builds", "Page Counter Offset"),
|
156
|
-
|
157
|
-
"html": QT_TRANSLATE_NOOP("Builds", "HTML (.html)"),
|
166
|
+
"format.pageMargins": QT_TRANSLATE_NOOP("Builds", "Page Margins"),
|
167
|
+
|
168
|
+
"doc": QT_TRANSLATE_NOOP("Builds", "Document Style"),
|
169
|
+
"doc.pageHeader": QT_TRANSLATE_NOOP("Builds", "Page Header"),
|
170
|
+
"doc.pageCountOffset": QT_TRANSLATE_NOOP("Builds", "Page Counter Offset"),
|
171
|
+
"doc.colorHeadings": QT_TRANSLATE_NOOP("Builds", "Add Colours to Headings"),
|
172
|
+
"doc.scaleHeadings": QT_TRANSLATE_NOOP("Builds", "Increase Size of Headings"),
|
173
|
+
"doc.boldHeadings": QT_TRANSLATE_NOOP("Builds", "Bold Headings"),
|
174
|
+
|
175
|
+
"html": QT_TRANSLATE_NOOP("Builds", "HTML Options"),
|
158
176
|
"html.addStyles": QT_TRANSLATE_NOOP("Builds", "Add CSS Styles"),
|
159
177
|
"html.preserveTabs": QT_TRANSLATE_NOOP("Builds", "Preserve Tab Characters"),
|
160
178
|
}
|
161
179
|
|
180
|
+
RENAMED = {
|
181
|
+
"odt.addColours": "doc.addColours",
|
182
|
+
"odt.pageHeader": "doc.pageHeader",
|
183
|
+
"odt.pageCountOffset": "doc.pageCountOffset",
|
184
|
+
}
|
185
|
+
|
162
186
|
|
163
187
|
class FilterMode(Enum):
|
164
188
|
"""The decision reason for an item in a filtered project."""
|
@@ -348,18 +372,12 @@ class BuildSettings:
|
|
348
372
|
self._changed = True
|
349
373
|
return
|
350
374
|
|
351
|
-
def setValue(self, key: str, value: str | int |
|
375
|
+
def setValue(self, key: str, value: str | int | float | bool) -> None:
|
352
376
|
"""Set a specific value for a build setting."""
|
353
|
-
if key
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
return False
|
358
|
-
if len(definition) == 4 and isinstance(value, (int, float)):
|
359
|
-
value = min(max(value, definition[2]), definition[3])
|
360
|
-
self._changed = value != self._settings[key]
|
361
|
-
self._settings[key] = value
|
362
|
-
return True
|
377
|
+
if (d := SETTINGS_TEMPLATE.get(key)) and len(d) == 2 and isinstance(value, d[0]):
|
378
|
+
self._changed = value != self._settings[key]
|
379
|
+
self._settings[key] = value
|
380
|
+
return
|
363
381
|
|
364
382
|
##
|
365
383
|
# Methods
|
@@ -478,12 +496,21 @@ class BuildSettings:
|
|
478
496
|
self._settings = {k: v[1] for k, v in SETTINGS_TEMPLATE.items()}
|
479
497
|
if isinstance(settings, dict):
|
480
498
|
for key, value in settings.items():
|
481
|
-
self.setValue(key, value)
|
499
|
+
self.setValue(RENAMED.get(key, key), value)
|
482
500
|
|
483
501
|
self._changed = False
|
484
502
|
|
485
503
|
return
|
486
504
|
|
505
|
+
@classmethod
|
506
|
+
def duplicate(cls, source: BuildSettings) -> BuildSettings:
|
507
|
+
"""Make a copy of another build."""
|
508
|
+
cls = BuildSettings()
|
509
|
+
cls.unpack(source.pack())
|
510
|
+
cls._uuid = str(uuid.uuid4())
|
511
|
+
cls._name = f"{source.name} 2"
|
512
|
+
return cls
|
513
|
+
|
487
514
|
|
488
515
|
class BuildCollection:
|
489
516
|
"""Core: Build Collection Class
|
novelwriter/core/coretools.py
CHANGED
@@ -27,6 +27,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
27
27
|
from __future__ import annotations
|
28
28
|
|
29
29
|
import logging
|
30
|
+
import re
|
30
31
|
import shutil
|
31
32
|
|
32
33
|
from collections.abc import Iterable
|
@@ -34,11 +35,11 @@ from functools import partial
|
|
34
35
|
from pathlib import Path
|
35
36
|
from zipfile import ZipFile, is_zipfile
|
36
37
|
|
37
|
-
from PyQt5.QtCore import QCoreApplication
|
38
|
+
from PyQt5.QtCore import QCoreApplication
|
38
39
|
|
39
40
|
from novelwriter import CONFIG, SHARED
|
40
41
|
from novelwriter.common import isHandle, minmax, simplified
|
41
|
-
from novelwriter.constants import nwConst, nwFiles, nwItemClass
|
42
|
+
from novelwriter.constants import nwConst, nwFiles, nwItemClass, nwStats
|
42
43
|
from novelwriter.core.item import NWItem
|
43
44
|
from novelwriter.core.project import NWProject
|
44
45
|
from novelwriter.core.storage import NWStorageCreate
|
@@ -55,10 +56,17 @@ class DocMerger:
|
|
55
56
|
def __init__(self, project: NWProject) -> None:
|
56
57
|
self._project = project
|
57
58
|
self._error = ""
|
58
|
-
self.
|
59
|
-
self.
|
59
|
+
self._target = None
|
60
|
+
self._text = []
|
60
61
|
return
|
61
62
|
|
63
|
+
@property
|
64
|
+
def targetHandle(self) -> str | None:
|
65
|
+
"""Get the handle of the target document."""
|
66
|
+
if self._target:
|
67
|
+
return self._target.itemHandle
|
68
|
+
return None
|
69
|
+
|
62
70
|
##
|
63
71
|
# Methods
|
64
72
|
##
|
@@ -71,63 +79,56 @@ class DocMerger:
|
|
71
79
|
"""Set the target document for the merging. Calling this
|
72
80
|
function resets the class.
|
73
81
|
"""
|
74
|
-
self.
|
75
|
-
self.
|
82
|
+
self._target = self._project.tree[tHandle]
|
83
|
+
self._text = []
|
76
84
|
return
|
77
85
|
|
78
|
-
def newTargetDoc(self,
|
86
|
+
def newTargetDoc(self, sHandle: str, label: str) -> None:
|
79
87
|
"""Create a brand new target document based on a source handle
|
80
88
|
and a new doc label. Calling this function resets the class.
|
81
89
|
"""
|
82
|
-
|
83
|
-
if
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
self._targetDoc = newHandle
|
94
|
-
self._targetText = []
|
95
|
-
|
96
|
-
return newHandle
|
90
|
+
sItem = self._project.tree[sHandle]
|
91
|
+
if sItem and sItem.itemParent:
|
92
|
+
tHandle = self._project.newFile(label, sItem.itemParent)
|
93
|
+
if nwItem := self._project.tree[tHandle]:
|
94
|
+
nwItem.setLayout(sItem.itemLayout)
|
95
|
+
nwItem.setStatus(sItem.itemStatus)
|
96
|
+
nwItem.setImport(sItem.itemImport)
|
97
|
+
nwItem.notifyToRefresh()
|
98
|
+
self._target = nwItem
|
99
|
+
self._text = []
|
100
|
+
return
|
97
101
|
|
98
|
-
def appendText(self,
|
102
|
+
def appendText(self, sHandle: str, addComment: bool, cmtPrefix: str) -> None:
|
99
103
|
"""Append text from an existing document to the text buffer."""
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
cmtLine = f"% {cmtPrefix} {docInfo}: {srcItem.itemName} [{docSt}]\n\n"
|
109
|
-
docText = cmtLine + docText
|
110
|
-
|
111
|
-
self._targetText.append(docText)
|
112
|
-
|
113
|
-
return True
|
104
|
+
if item := self._project.tree[sHandle]:
|
105
|
+
text = self._project.storage.getDocumentText(sHandle).rstrip("\n")
|
106
|
+
if addComment:
|
107
|
+
info = item.describeMe()
|
108
|
+
status, _ = item.getImportStatus()
|
109
|
+
text = f"% {cmtPrefix} {info}: {item.itemName} [{status}]\n\n{text}"
|
110
|
+
self._text.append(text)
|
111
|
+
return
|
114
112
|
|
115
113
|
def writeTargetDoc(self) -> bool:
|
116
114
|
"""Write the accumulated text into the designated target
|
117
115
|
document, appending any existing text.
|
118
116
|
"""
|
119
|
-
if self.
|
120
|
-
|
117
|
+
if self._target:
|
118
|
+
outDoc = self._project.storage.getDocument(self._target.itemHandle)
|
119
|
+
if text := (outDoc.readDocument() or "").rstrip("\n"):
|
120
|
+
self._text.insert(0, text)
|
121
|
+
|
122
|
+
status = outDoc.writeDocument("\n\n".join(self._text) + "\n\n")
|
123
|
+
if not status:
|
124
|
+
self._error = outDoc.getError()
|
121
125
|
|
122
|
-
|
123
|
-
|
124
|
-
self._targetText.insert(0, text)
|
126
|
+
self._project.index.reIndexHandle(self._target.itemHandle)
|
127
|
+
self._target.notifyToRefresh()
|
125
128
|
|
126
|
-
|
127
|
-
if not status:
|
128
|
-
self._error = outDoc.getError()
|
129
|
+
return status
|
129
130
|
|
130
|
-
return
|
131
|
+
return False
|
131
132
|
|
132
133
|
|
133
134
|
class DocSplitter:
|
@@ -171,23 +172,19 @@ class DocSplitter:
|
|
171
172
|
self._inFolder = False
|
172
173
|
return
|
173
174
|
|
174
|
-
def newParentFolder(self, pHandle: str, folderLabel: str) ->
|
175
|
+
def newParentFolder(self, pHandle: str, folderLabel: str) -> None:
|
175
176
|
"""Create a new folder that will be the top level parent item
|
176
177
|
for the new documents.
|
177
178
|
"""
|
178
|
-
if self._srcItem
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
self._parHandle = newHandle
|
188
|
-
self._inFolder = True
|
189
|
-
|
190
|
-
return newHandle
|
179
|
+
if self._srcItem:
|
180
|
+
nHandle = self._project.newFolder(folderLabel, pHandle)
|
181
|
+
if nwItem := self._project.tree[nHandle]:
|
182
|
+
nwItem.setStatus(self._srcItem.itemStatus)
|
183
|
+
nwItem.setImport(self._srcItem.itemImport)
|
184
|
+
nwItem.notifyToRefresh()
|
185
|
+
self._parHandle = nHandle
|
186
|
+
self._inFolder = True
|
187
|
+
return
|
191
188
|
|
192
189
|
def splitDocument(self, splitData: list, splitText: list[str]) -> None:
|
193
190
|
"""Loop through the split data record and perform the split job
|
@@ -201,58 +198,50 @@ class DocSplitter:
|
|
201
198
|
self._rawData.insert(0, (chunk, hLevel, hLabel))
|
202
199
|
return
|
203
200
|
|
204
|
-
def writeDocuments(self, docHierarchy: bool) -> Iterable[
|
201
|
+
def writeDocuments(self, docHierarchy: bool) -> Iterable[bool]:
|
205
202
|
"""An iterator that will write each document in the buffer, and
|
206
203
|
return its new handle, parent handle, and sibling handle.
|
207
204
|
"""
|
208
|
-
if self._srcHandle
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
self._error = outDoc.getError()
|
249
|
-
|
250
|
-
yield status, dHandle, nHandle
|
251
|
-
|
252
|
-
hHandle[hLevel] = dHandle
|
253
|
-
nHandle = dHandle
|
254
|
-
pLevel = hLevel
|
255
|
-
|
205
|
+
if self._srcHandle and self._srcItem and self._parHandle:
|
206
|
+
pHandle = self._parHandle
|
207
|
+
hHandle = [self._parHandle, None, None, None, None]
|
208
|
+
pLevel = 0
|
209
|
+
for docText, hLevel, docLabel in self._rawData:
|
210
|
+
|
211
|
+
hLevel = minmax(hLevel, 1, 4)
|
212
|
+
if pLevel == 0:
|
213
|
+
pLevel = hLevel
|
214
|
+
|
215
|
+
if docHierarchy:
|
216
|
+
if hLevel == 1:
|
217
|
+
pHandle = self._parHandle
|
218
|
+
elif hLevel == 2:
|
219
|
+
pHandle = hHandle[1] or hHandle[0]
|
220
|
+
elif hLevel == 3:
|
221
|
+
pHandle = hHandle[2] or hHandle[1] or hHandle[0]
|
222
|
+
elif hLevel == 4:
|
223
|
+
pHandle = hHandle[3] or hHandle[2] or hHandle[1] or hHandle[0]
|
224
|
+
|
225
|
+
if (
|
226
|
+
(dHandle := self._project.newFile(docLabel, pHandle))
|
227
|
+
and (nwItem := self._project.tree[dHandle])
|
228
|
+
):
|
229
|
+
hHandle[hLevel] = dHandle
|
230
|
+
nwItem.setStatus(self._srcItem.itemStatus)
|
231
|
+
nwItem.setImport(self._srcItem.itemImport)
|
232
|
+
|
233
|
+
outDoc = self._project.storage.getDocument(dHandle)
|
234
|
+
status = outDoc.writeDocument("\n".join(docText))
|
235
|
+
if not status:
|
236
|
+
self._error = outDoc.getError()
|
237
|
+
|
238
|
+
self._project.index.reIndexHandle(dHandle)
|
239
|
+
nwItem.notifyToRefresh()
|
240
|
+
|
241
|
+
yield status
|
242
|
+
|
243
|
+
hHandle[hLevel] = dHandle
|
244
|
+
pLevel = hLevel
|
256
245
|
return
|
257
246
|
|
258
247
|
|
@@ -269,36 +258,34 @@ class DocDuplicator:
|
|
269
258
|
# Methods
|
270
259
|
##
|
271
260
|
|
272
|
-
def duplicate(self, items: list[str]) ->
|
261
|
+
def duplicate(self, items: list[str]) -> list[str]:
|
273
262
|
"""Run through a list of items, duplicate them, and copy the
|
274
263
|
text content if they are documents.
|
275
264
|
"""
|
265
|
+
result = []
|
266
|
+
after = True
|
276
267
|
if items:
|
277
|
-
nHandle = items[0]
|
278
268
|
hMap: dict[str, str | None] = {t: None for t in items}
|
279
269
|
for tHandle in items:
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
yield newItem.itemHandle, nHandle
|
293
|
-
nHandle = None
|
294
|
-
return
|
270
|
+
if oldItem := self._project.tree[tHandle]:
|
271
|
+
pHandle = hMap.get(oldItem.itemParent or "") or oldItem.itemParent
|
272
|
+
if newItem := self._project.tree.duplicate(tHandle, pHandle, after):
|
273
|
+
hMap[tHandle] = newItem.itemHandle
|
274
|
+
if newItem.isFileType():
|
275
|
+
self._project.copyFileContent(newItem.itemHandle, tHandle)
|
276
|
+
newItem.notifyToRefresh()
|
277
|
+
result.append(newItem.itemHandle)
|
278
|
+
after = False
|
279
|
+
else:
|
280
|
+
break
|
281
|
+
return result
|
295
282
|
|
296
283
|
|
297
284
|
class DocSearch:
|
298
285
|
|
299
286
|
def __init__(self) -> None:
|
300
|
-
self._regEx =
|
301
|
-
self.
|
287
|
+
self._regEx = re.compile("")
|
288
|
+
self._opts = re.UNICODE | re.IGNORECASE
|
302
289
|
self._words = False
|
303
290
|
self._escape = True
|
304
291
|
return
|
@@ -309,10 +296,9 @@ class DocSearch:
|
|
309
296
|
|
310
297
|
def setCaseSensitive(self, state: bool) -> None:
|
311
298
|
"""Set the case sensitive search flag."""
|
312
|
-
|
299
|
+
self._opts = re.UNICODE
|
313
300
|
if not state:
|
314
|
-
|
315
|
-
self._regEx.setPatternOptions(opts)
|
301
|
+
self._opts |= re.IGNORECASE
|
316
302
|
return
|
317
303
|
|
318
304
|
def setWholeWords(self, state: bool) -> None:
|
@@ -329,8 +315,8 @@ class DocSearch:
|
|
329
315
|
self, project: NWProject, search: str
|
330
316
|
) -> Iterable[tuple[NWItem, list[tuple[int, int, str]], bool]]:
|
331
317
|
"""Iteratively search through documents in a project."""
|
332
|
-
self._regEx.
|
333
|
-
logger.debug("Searching with pattern '%s'", self._regEx.pattern
|
318
|
+
self._regEx = re.compile(self._buildPattern(search), self._opts)
|
319
|
+
logger.debug("Searching with pattern '%s'", self._regEx.pattern)
|
334
320
|
storage = project.storage
|
335
321
|
for item in project.tree:
|
336
322
|
if item.isFileType():
|
@@ -340,14 +326,12 @@ class DocSearch:
|
|
340
326
|
|
341
327
|
def searchText(self, text: str) -> tuple[list[tuple[int, int, str]], bool]:
|
342
328
|
"""Search a piece of text for RegEx matches."""
|
343
|
-
rxItt = self._regEx.globalMatch(text)
|
344
329
|
count = 0
|
345
330
|
capped = False
|
346
331
|
results = []
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
num = rxMatch.capturedLength()
|
332
|
+
for res in self._regEx.finditer(text):
|
333
|
+
pos = res.start(0)
|
334
|
+
num = len(res.group(0))
|
351
335
|
lim = text[:pos].rfind("\n") + 1
|
352
336
|
cut = text[lim:pos].rfind(" ") + lim + 1
|
353
337
|
context = text[cut:cut+100].partition("\n")[0]
|
@@ -366,7 +350,7 @@ class DocSearch:
|
|
366
350
|
def _buildPattern(self, search: str) -> str:
|
367
351
|
"""Build the search pattern string."""
|
368
352
|
if self._escape:
|
369
|
-
search =
|
353
|
+
search = re.escape(search)
|
370
354
|
if self._words:
|
371
355
|
search = f"(?:^|\\b){search}(?:$|\\b)"
|
372
356
|
return search
|
@@ -430,7 +414,6 @@ class ProjectBuilder:
|
|
430
414
|
|
431
415
|
lblNewProject = self.tr("New Project")
|
432
416
|
lblTitlePage = self.tr("Title Page")
|
433
|
-
lblByAuthors = self.tr("By")
|
434
417
|
|
435
418
|
# Settings
|
436
419
|
project.data.setUuid(None)
|
@@ -443,14 +426,29 @@ class ProjectBuilder:
|
|
443
426
|
# Add Root Folders
|
444
427
|
hNovelRoot = project.newRoot(nwItemClass.NOVEL)
|
445
428
|
hTitlePage = project.newFile(lblTitlePage, hNovelRoot)
|
446
|
-
novelTitle = project.data.name
|
447
|
-
|
448
|
-
titlePage = f"#! {novelTitle}\n\n"
|
449
|
-
if project.data.author:
|
450
|
-
titlePage += f">> {lblByAuthors} {project.data.author} <<\n\n"
|
451
429
|
|
430
|
+
# Generate Title Page
|
452
431
|
aDoc = project.storage.getDocument(hTitlePage)
|
453
|
-
aDoc.writeDocument(
|
432
|
+
aDoc.writeDocument((
|
433
|
+
"{author}[br]\n"
|
434
|
+
"{address} 1[br]\n"
|
435
|
+
"{address} 2 <<\n"
|
436
|
+
"\n"
|
437
|
+
"[vspace:5]\n"
|
438
|
+
"\n"
|
439
|
+
"#! {title}\n"
|
440
|
+
"\n"
|
441
|
+
">> **{by} {author}** <<\n"
|
442
|
+
"\n"
|
443
|
+
">> {count}: [field:{field}] <<\n"
|
444
|
+
).format(
|
445
|
+
author=project.data.author or "None",
|
446
|
+
address=self.tr("Address"),
|
447
|
+
title=project.data.name or "None",
|
448
|
+
by=self.tr("By"),
|
449
|
+
count=self.tr("Word Count"),
|
450
|
+
field=nwStats.WORDS_TEXT,
|
451
|
+
))
|
454
452
|
|
455
453
|
# Create a project structure based on selected root folders
|
456
454
|
# and a number of chapters and scenes selected in the
|
@@ -511,7 +509,7 @@ class ProjectBuilder:
|
|
511
509
|
|
512
510
|
# Also add the archive and trash folders
|
513
511
|
project.newRoot(nwItemClass.ARCHIVE)
|
514
|
-
project.
|
512
|
+
project.tree.trash # Triggers the creation of Trash
|
515
513
|
|
516
514
|
project.saveProject()
|
517
515
|
project.closeProject()
|