novelWriter 2.3rc1__py3-none-any.whl → 2.4__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.3rc1.dist-info → novelWriter-2.4.dist-info}/METADATA +5 -6
- {novelWriter-2.3rc1.dist-info → novelWriter-2.4.dist-info}/RECORD +119 -109
- {novelWriter-2.3rc1.dist-info → novelWriter-2.4.dist-info}/WHEEL +1 -1
- novelWriter-2.4.dist-info/entry_points.txt +2 -0
- novelwriter/__init__.py +17 -10
- 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_fr_FR.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/nw_pt_BR.qm +0 -0
- novelwriter/assets/i18n/nw_zh_CN.qm +0 -0
- novelwriter/assets/i18n/project_nl_NL.json +11 -0
- novelwriter/assets/i18n/project_pt_BR.json +11 -0
- novelwriter/assets/icons/none.svg +4 -0
- novelwriter/assets/icons/typicons_dark/icons.conf +4 -0
- novelwriter/assets/icons/typicons_dark/nw_tb-mark.svg +7 -0
- novelwriter/assets/icons/typicons_dark/typ_refresh-flipped.svg +1 -1
- novelwriter/assets/icons/typicons_dark/typ_refresh.svg +1 -1
- novelwriter/assets/icons/typicons_dark/typ_search-grey.svg +4 -0
- novelwriter/assets/icons/typicons_dark/typ_times.svg +1 -1
- novelwriter/assets/icons/typicons_dark/typ_unfold-hidden.svg +4 -0
- novelwriter/assets/icons/typicons_dark/typ_unfold-visible.svg +4 -0
- novelwriter/assets/icons/typicons_light/icons.conf +4 -0
- novelwriter/assets/icons/typicons_light/nw_tb-mark.svg +7 -0
- novelwriter/assets/icons/typicons_light/typ_refresh-flipped.svg +1 -1
- novelwriter/assets/icons/typicons_light/typ_refresh.svg +1 -1
- novelwriter/assets/icons/typicons_light/typ_search-grey.svg +4 -0
- novelwriter/assets/icons/typicons_light/typ_times.svg +1 -1
- novelwriter/assets/icons/typicons_light/typ_unfold-hidden.svg +4 -0
- novelwriter/assets/icons/typicons_light/typ_unfold-visible.svg +4 -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 +25 -23
- novelwriter/assets/themes/cyberpunk_night.conf +29 -0
- novelwriter/common.py +12 -4
- novelwriter/config.py +47 -16
- novelwriter/constants.py +5 -6
- novelwriter/core/buildsettings.py +64 -44
- novelwriter/core/coretools.py +97 -13
- novelwriter/core/docbuild.py +74 -7
- novelwriter/core/document.py +24 -3
- novelwriter/core/index.py +31 -112
- novelwriter/core/project.py +11 -15
- novelwriter/core/projectxml.py +3 -2
- novelwriter/core/sessions.py +2 -2
- novelwriter/core/spellcheck.py +3 -3
- novelwriter/core/status.py +6 -5
- novelwriter/core/storage.py +16 -6
- novelwriter/core/tohtml.py +22 -25
- novelwriter/core/tokenizer.py +417 -237
- novelwriter/core/tomd.py +17 -8
- novelwriter/core/toodt.py +386 -351
- novelwriter/core/tree.py +8 -8
- novelwriter/dialogs/about.py +10 -12
- novelwriter/dialogs/docmerge.py +17 -14
- novelwriter/dialogs/docsplit.py +20 -19
- novelwriter/dialogs/editlabel.py +5 -4
- novelwriter/dialogs/preferences.py +32 -40
- novelwriter/dialogs/projectsettings.py +31 -28
- novelwriter/dialogs/quotes.py +10 -9
- novelwriter/dialogs/wordlist.py +18 -15
- novelwriter/enum.py +17 -14
- novelwriter/error.py +14 -12
- novelwriter/extensions/circularprogress.py +12 -8
- novelwriter/extensions/configlayout.py +23 -3
- novelwriter/extensions/modified.py +51 -2
- novelwriter/extensions/pagedsidebar.py +16 -14
- novelwriter/extensions/simpleprogress.py +3 -1
- novelwriter/extensions/statusled.py +3 -1
- novelwriter/extensions/switch.py +10 -9
- novelwriter/extensions/switchbox.py +14 -13
- novelwriter/extensions/versioninfo.py +1 -1
- novelwriter/gui/doceditor.py +433 -496
- novelwriter/gui/dochighlight.py +54 -33
- novelwriter/gui/docviewer.py +162 -175
- novelwriter/gui/docviewerpanel.py +20 -37
- novelwriter/gui/editordocument.py +15 -4
- novelwriter/gui/itemdetails.py +51 -54
- novelwriter/gui/mainmenu.py +37 -17
- novelwriter/gui/noveltree.py +31 -37
- novelwriter/gui/outline.py +120 -98
- novelwriter/gui/projtree.py +114 -112
- novelwriter/gui/search.py +362 -0
- novelwriter/gui/sidebar.py +36 -45
- novelwriter/gui/statusbar.py +14 -14
- novelwriter/gui/theme.py +116 -34
- novelwriter/guimain.py +216 -207
- novelwriter/shared.py +31 -6
- novelwriter/text/counting.py +138 -0
- novelwriter/tools/dictionaries.py +15 -14
- novelwriter/tools/lipsum.py +20 -17
- novelwriter/tools/manusbuild.py +43 -35
- novelwriter/tools/manuscript.py +381 -104
- novelwriter/tools/manussettings.py +263 -125
- novelwriter/tools/noveldetails.py +21 -19
- novelwriter/tools/welcome.py +59 -57
- novelwriter/tools/writingstats.py +61 -55
- novelwriter/types.py +90 -0
- novelWriter-2.3rc1.dist-info/entry_points.txt +0 -5
- novelwriter/core/__init__.py +0 -3
- novelwriter/dialogs/__init__.py +0 -3
- novelwriter/extensions/__init__.py +0 -3
- novelwriter/gui/__init__.py +0 -3
- novelwriter/tools/__init__.py +0 -3
- {novelWriter-2.3rc1.dist-info → novelWriter-2.4.dist-info}/LICENSE.md +0 -0
- {novelWriter-2.3rc1.dist-info → novelWriter-2.4.dist-info}/top_level.txt +0 -0
@@ -49,39 +49,54 @@ logger = logging.getLogger(__name__)
|
|
49
49
|
# (type, default, [min value, max value])
|
50
50
|
|
51
51
|
SETTINGS_TEMPLATE = {
|
52
|
-
"filter.includeNovel":
|
53
|
-
"filter.includeNotes":
|
54
|
-
"filter.includeInactive":
|
55
|
-
"headings.fmtTitle":
|
56
|
-
"headings.fmtChapter":
|
57
|
-
"headings.fmtUnnumbered":
|
58
|
-
"headings.fmtScene":
|
59
|
-
"headings.
|
60
|
-
"headings.
|
61
|
-
"headings.
|
62
|
-
"
|
63
|
-
"
|
64
|
-
"
|
65
|
-
"
|
66
|
-
"
|
67
|
-
"
|
68
|
-
"
|
69
|
-
"
|
70
|
-
"
|
71
|
-
"
|
72
|
-
"
|
73
|
-
"
|
74
|
-
"
|
75
|
-
"
|
76
|
-
"
|
77
|
-
"
|
78
|
-
"
|
79
|
-
"format.
|
80
|
-
"format.
|
81
|
-
"
|
82
|
-
"
|
83
|
-
"
|
84
|
-
"
|
52
|
+
"filter.includeNovel": (bool, True),
|
53
|
+
"filter.includeNotes": (bool, False),
|
54
|
+
"filter.includeInactive": (bool, False),
|
55
|
+
"headings.fmtTitle": (str, nwHeadFmt.TITLE),
|
56
|
+
"headings.fmtChapter": (str, nwHeadFmt.TITLE),
|
57
|
+
"headings.fmtUnnumbered": (str, nwHeadFmt.TITLE),
|
58
|
+
"headings.fmtScene": (str, "* * *"),
|
59
|
+
"headings.fmtAltScene": (str, ""),
|
60
|
+
"headings.fmtSection": (str, ""),
|
61
|
+
"headings.hideTitle": (bool, False),
|
62
|
+
"headings.hideChapter": (bool, False),
|
63
|
+
"headings.hideUnnumbered": (bool, False),
|
64
|
+
"headings.hideScene": (bool, False),
|
65
|
+
"headings.hideAltScene": (bool, False),
|
66
|
+
"headings.hideSection": (bool, True),
|
67
|
+
"headings.centerTitle": (bool, True),
|
68
|
+
"headings.centerChapter": (bool, False),
|
69
|
+
"headings.centerScene": (bool, False),
|
70
|
+
"headings.breakTitle": (bool, True),
|
71
|
+
"headings.breakChapter": (bool, True),
|
72
|
+
"headings.breakScene": (bool, False),
|
73
|
+
"text.includeSynopsis": (bool, False),
|
74
|
+
"text.includeComments": (bool, False),
|
75
|
+
"text.includeKeywords": (bool, False),
|
76
|
+
"text.includeBodyText": (bool, True),
|
77
|
+
"text.ignoredKeywords": (str, ""),
|
78
|
+
"text.addNoteHeadings": (bool, True),
|
79
|
+
"format.textFont": (str, CONFIG.textFont),
|
80
|
+
"format.textSize": (int, 12),
|
81
|
+
"format.lineHeight": (float, 1.15, 0.75, 3.0),
|
82
|
+
"format.justifyText": (bool, False),
|
83
|
+
"format.stripUnicode": (bool, False),
|
84
|
+
"format.replaceTabs": (bool, False),
|
85
|
+
"format.pageUnit": (str, "cm"),
|
86
|
+
"format.pageSize": (str, "A4"),
|
87
|
+
"format.pageWidth": (float, 21.0),
|
88
|
+
"format.pageHeight": (float, 29.7),
|
89
|
+
"format.topMargin": (float, 2.0),
|
90
|
+
"format.bottomMargin": (float, 2.0),
|
91
|
+
"format.leftMargin": (float, 2.0),
|
92
|
+
"format.rightMargin": (float, 2.0),
|
93
|
+
"odt.addColours": (bool, True),
|
94
|
+
"odt.pageHeader": (str, nwHeadFmt.ODT_AUTO),
|
95
|
+
"odt.pageCountOffset": (int, 0),
|
96
|
+
"odt.firstLineIndent": (bool, False),
|
97
|
+
"md.preserveBreaks": (bool, True),
|
98
|
+
"html.addStyles": (bool, True),
|
99
|
+
"html.preserveTabs": (bool, False),
|
85
100
|
}
|
86
101
|
|
87
102
|
SETTINGS_LABELS = {
|
@@ -91,19 +106,19 @@ SETTINGS_LABELS = {
|
|
91
106
|
"filter.includeInactive": QT_TRANSLATE_NOOP("Builds", "Inactive Documents"),
|
92
107
|
|
93
108
|
"headings": QT_TRANSLATE_NOOP("Builds", "Headings"),
|
94
|
-
"headings.fmtTitle": QT_TRANSLATE_NOOP("Builds", "
|
95
|
-
"headings.fmtChapter": QT_TRANSLATE_NOOP("Builds", "Chapter
|
96
|
-
"headings.fmtUnnumbered": QT_TRANSLATE_NOOP("Builds", "Unnumbered
|
97
|
-
"headings.fmtScene": QT_TRANSLATE_NOOP("Builds", "Scene
|
98
|
-
"headings.
|
99
|
-
"headings.
|
100
|
-
"headings.hideSection": QT_TRANSLATE_NOOP("Builds", "Hide Section Headings"),
|
109
|
+
"headings.fmtTitle": QT_TRANSLATE_NOOP("Builds", "Partition Format"),
|
110
|
+
"headings.fmtChapter": QT_TRANSLATE_NOOP("Builds", "Chapter Format"),
|
111
|
+
"headings.fmtUnnumbered": QT_TRANSLATE_NOOP("Builds", "Unnumbered Format"),
|
112
|
+
"headings.fmtScene": QT_TRANSLATE_NOOP("Builds", "Scene Format"),
|
113
|
+
"headings.fmtAltScene": QT_TRANSLATE_NOOP("Builds", "Alt. Scene Format"),
|
114
|
+
"headings.fmtSection": QT_TRANSLATE_NOOP("Builds", "Section Format"),
|
101
115
|
|
102
116
|
"text.grpContent": QT_TRANSLATE_NOOP("Builds", "Text Content"),
|
103
117
|
"text.includeSynopsis": QT_TRANSLATE_NOOP("Builds", "Include Synopsis"),
|
104
118
|
"text.includeComments": QT_TRANSLATE_NOOP("Builds", "Include Comments"),
|
105
119
|
"text.includeKeywords": QT_TRANSLATE_NOOP("Builds", "Include Keywords"),
|
106
120
|
"text.includeBodyText": QT_TRANSLATE_NOOP("Builds", "Include Body Text"),
|
121
|
+
"text.ignoredKeywords": QT_TRANSLATE_NOOP("Builds", "Ignore These Keywords"),
|
107
122
|
"text.grpInsert": QT_TRANSLATE_NOOP("Builds", "Insert Content"),
|
108
123
|
"text.addNoteHeadings": QT_TRANSLATE_NOOP("Builds", "Add Titles for Notes"),
|
109
124
|
|
@@ -129,9 +144,14 @@ SETTINGS_LABELS = {
|
|
129
144
|
"odt.addColours": QT_TRANSLATE_NOOP("Builds", "Add Highlight Colours"),
|
130
145
|
"odt.pageHeader": QT_TRANSLATE_NOOP("Builds", "Page Header"),
|
131
146
|
"odt.pageCountOffset": QT_TRANSLATE_NOOP("Builds", "Page Counter Offset"),
|
147
|
+
"odt.firstLineIndent": QT_TRANSLATE_NOOP("Builds", "First Line Indent"),
|
148
|
+
|
149
|
+
"md": QT_TRANSLATE_NOOP("Builds", "Markdown (.md)"),
|
150
|
+
"md.preserveBreaks": QT_TRANSLATE_NOOP("Builds", "Preserve Hard Line Breaks"),
|
132
151
|
|
133
152
|
"html": QT_TRANSLATE_NOOP("Builds", "HTML (.html)"),
|
134
153
|
"html.addStyles": QT_TRANSLATE_NOOP("Builds", "Add CSS Styles"),
|
154
|
+
"html.preserveTabs": QT_TRANSLATE_NOOP("Builds", "Preserve Tab Characters"),
|
135
155
|
}
|
136
156
|
|
137
157
|
|
@@ -158,7 +178,7 @@ class BuildSettings:
|
|
158
178
|
def __init__(self) -> None:
|
159
179
|
self._name = ""
|
160
180
|
self._uuid = str(uuid.uuid4())
|
161
|
-
self._path =
|
181
|
+
self._path = CONFIG.homePath()
|
162
182
|
self._build = ""
|
163
183
|
self._order = 0
|
164
184
|
self._format = nwBuildFmt.ODT
|
@@ -200,7 +220,7 @@ class BuildSettings:
|
|
200
220
|
"""The last used build path."""
|
201
221
|
if self._path.is_dir():
|
202
222
|
return self._path
|
203
|
-
return
|
223
|
+
return CONFIG.homePath()
|
204
224
|
|
205
225
|
@property
|
206
226
|
def lastBuildName(self) -> str:
|
@@ -277,7 +297,7 @@ class BuildSettings:
|
|
277
297
|
if isinstance(path, Path) and path.is_dir():
|
278
298
|
self._path = path
|
279
299
|
else:
|
280
|
-
self._path =
|
300
|
+
self._path = CONFIG.homePath()
|
281
301
|
self._changed = True
|
282
302
|
return
|
283
303
|
|
@@ -362,7 +382,7 @@ class BuildSettings:
|
|
362
382
|
|
363
383
|
postponed = []
|
364
384
|
|
365
|
-
def allowRoot(rHandle):
|
385
|
+
def allowRoot(rHandle: str | None) -> None:
|
366
386
|
if rHandle in postponed and rHandle in result and rHandle is not None:
|
367
387
|
result[rHandle] = (True, FilterMode.ROOT)
|
368
388
|
postponed.remove(rHandle)
|
novelwriter/core/coretools.py
CHANGED
@@ -26,19 +26,19 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
26
26
|
"""
|
27
27
|
from __future__ import annotations
|
28
28
|
|
29
|
-
import shutil
|
30
29
|
import logging
|
30
|
+
import shutil
|
31
31
|
|
32
|
-
from
|
32
|
+
from collections.abc import Iterable
|
33
33
|
from functools import partial
|
34
|
+
from pathlib import Path
|
34
35
|
from zipfile import ZipFile, is_zipfile
|
35
|
-
from collections.abc import Iterable
|
36
36
|
|
37
|
-
from PyQt5.QtCore import QCoreApplication
|
37
|
+
from PyQt5.QtCore import QCoreApplication, QRegularExpression
|
38
38
|
|
39
39
|
from novelwriter import CONFIG, SHARED
|
40
40
|
from novelwriter.common import isHandle, minmax, simplified
|
41
|
-
from novelwriter.constants import nwFiles, nwItemClass
|
41
|
+
from novelwriter.constants import nwConst, nwFiles, nwItemClass
|
42
42
|
from novelwriter.core.item import NWItem
|
43
43
|
from novelwriter.core.project import NWProject
|
44
44
|
from novelwriter.core.storage import NWStorageCreate
|
@@ -101,9 +101,7 @@ class DocMerger:
|
|
101
101
|
if srcItem is None:
|
102
102
|
return False
|
103
103
|
|
104
|
-
|
105
|
-
docText = (inDoc.readDocument() or "").rstrip("\n")
|
106
|
-
|
104
|
+
docText = self._project.storage.getDocumentText(srcHandle).rstrip("\n")
|
107
105
|
if addComment:
|
108
106
|
docInfo = srcItem.describeMe()
|
109
107
|
docSt, _ = srcItem.getImportStatus(incIcon=False)
|
@@ -122,9 +120,8 @@ class DocMerger:
|
|
122
120
|
return False
|
123
121
|
|
124
122
|
outDoc = self._project.storage.getDocument(self._targetDoc)
|
125
|
-
|
126
|
-
|
127
|
-
self._targetText.insert(0, docText)
|
123
|
+
if text := (outDoc.readDocument() or "").rstrip("\n"):
|
124
|
+
self._targetText.insert(0, text)
|
128
125
|
|
129
126
|
status = outDoc.writeDocument("\n\n".join(self._targetText) + "\n\n")
|
130
127
|
if not status:
|
@@ -292,11 +289,10 @@ class DocDuplicator:
|
|
292
289
|
newItem.setParent(hMap[newItem.itemParent])
|
293
290
|
self._project.tree.updateItemData(newItem.itemHandle)
|
294
291
|
if newItem.isFileType():
|
295
|
-
oldDoc = self._project.storage.getDocument(tHandle)
|
296
292
|
newDoc = self._project.storage.getDocument(newItem.itemHandle)
|
297
293
|
if newDoc.fileExists():
|
298
294
|
return
|
299
|
-
newDoc.writeDocument(
|
295
|
+
newDoc.writeDocument(self._project.storage.getDocumentText(tHandle))
|
300
296
|
yield newItem.itemHandle, nHandle
|
301
297
|
nHandle = None
|
302
298
|
return
|
@@ -304,6 +300,94 @@ class DocDuplicator:
|
|
304
300
|
# END Class DocDuplicator
|
305
301
|
|
306
302
|
|
303
|
+
class DocSearch:
|
304
|
+
|
305
|
+
def __init__(self) -> None:
|
306
|
+
self._regEx = QRegularExpression()
|
307
|
+
self.setCaseSensitive(False)
|
308
|
+
self._words = False
|
309
|
+
self._escape = True
|
310
|
+
return
|
311
|
+
|
312
|
+
##
|
313
|
+
# Methods
|
314
|
+
##
|
315
|
+
|
316
|
+
def setCaseSensitive(self, state: bool) -> None:
|
317
|
+
"""Set the case sensitive search flag."""
|
318
|
+
opts = QRegularExpression.PatternOption.UseUnicodePropertiesOption
|
319
|
+
if not state:
|
320
|
+
opts |= QRegularExpression.PatternOption.CaseInsensitiveOption
|
321
|
+
self._regEx.setPatternOptions(opts)
|
322
|
+
return
|
323
|
+
|
324
|
+
def setWholeWords(self, state: bool) -> None:
|
325
|
+
"""Set the whole words search flag."""
|
326
|
+
self._words = state
|
327
|
+
return
|
328
|
+
|
329
|
+
def setUserRegEx(self, state: bool) -> None:
|
330
|
+
"""Set the escape flag to the opposite state."""
|
331
|
+
self._escape = not state
|
332
|
+
return
|
333
|
+
|
334
|
+
def iterSearch(
|
335
|
+
self, project: NWProject, search: str
|
336
|
+
) -> Iterable[tuple[NWItem, list[tuple[int, int, str]], bool]]:
|
337
|
+
"""Iteratively search through documents in a project."""
|
338
|
+
self._regEx.setPattern(self._buildPattern(search))
|
339
|
+
logger.debug("Searching with pattern '%s'", self._regEx.pattern())
|
340
|
+
storage = project.storage
|
341
|
+
for item in project.tree:
|
342
|
+
if item.isFileType():
|
343
|
+
results, capped = self.searchText(storage.getDocumentText(item.itemHandle))
|
344
|
+
yield item, results, capped
|
345
|
+
return
|
346
|
+
|
347
|
+
def searchText(self, text: str) -> tuple[list[tuple[int, int, str]], bool]:
|
348
|
+
"""Search a piece of text for RegEx matches."""
|
349
|
+
rxItt = self._regEx.globalMatch(text)
|
350
|
+
count = 0
|
351
|
+
capped = False
|
352
|
+
results = []
|
353
|
+
while rxItt.hasNext():
|
354
|
+
rxMatch = rxItt.next()
|
355
|
+
pos = rxMatch.capturedStart()
|
356
|
+
num = rxMatch.capturedLength()
|
357
|
+
context = text[pos:pos+100].partition("\n")[0]
|
358
|
+
if context:
|
359
|
+
results.append((pos, num, context))
|
360
|
+
count += 1
|
361
|
+
if count >= nwConst.MAX_SEARCH_RESULT:
|
362
|
+
capped = True
|
363
|
+
break
|
364
|
+
return results, capped
|
365
|
+
|
366
|
+
##
|
367
|
+
# Internal Functions
|
368
|
+
##
|
369
|
+
|
370
|
+
def _buildPattern(self, search: str) -> str:
|
371
|
+
"""Build the search pattern string."""
|
372
|
+
if self._escape:
|
373
|
+
if CONFIG.verQtValue >= 0x050f00:
|
374
|
+
search = QRegularExpression.escape(search)
|
375
|
+
else:
|
376
|
+
# For older Qt versions, we escape manually
|
377
|
+
escaped = ""
|
378
|
+
for c in search:
|
379
|
+
if c.isalnum() or c == "_":
|
380
|
+
escaped += c
|
381
|
+
else:
|
382
|
+
escaped += f"\\{c}"
|
383
|
+
search = escaped
|
384
|
+
if self._words:
|
385
|
+
search = f"(?:^|\\b){search}(?:$|\\b)"
|
386
|
+
return search
|
387
|
+
|
388
|
+
# END Class DocSearch
|
389
|
+
|
390
|
+
|
307
391
|
class ProjectBuilder:
|
308
392
|
"""A class to build a new project from a set of user-defined
|
309
393
|
parameter provided by the New Project Wizard.
|
novelwriter/core/docbuild.py
CHANGED
@@ -52,7 +52,10 @@ class NWBuildDocument:
|
|
52
52
|
manuscript, based on a build definition object (BuildSettings).
|
53
53
|
"""
|
54
54
|
|
55
|
-
__slots__ = (
|
55
|
+
__slots__ = (
|
56
|
+
"_project", "_build", "_queue", "_error", "_cache", "_count",
|
57
|
+
"_outline", "_preview"
|
58
|
+
)
|
56
59
|
|
57
60
|
def __init__(self, project: NWProject, build: BuildSettings) -> None:
|
58
61
|
self._project = project
|
@@ -60,6 +63,9 @@ class NWBuildDocument:
|
|
60
63
|
self._queue = []
|
61
64
|
self._error = None
|
62
65
|
self._cache = None
|
66
|
+
self._count = False
|
67
|
+
self._outline = False
|
68
|
+
self._preview = False
|
63
69
|
return
|
64
70
|
|
65
71
|
##
|
@@ -79,6 +85,29 @@ class NWBuildDocument:
|
|
79
85
|
"""
|
80
86
|
return self._cache
|
81
87
|
|
88
|
+
##
|
89
|
+
# Setters
|
90
|
+
##
|
91
|
+
|
92
|
+
def setCountEnabled(self, state: bool) -> None:
|
93
|
+
"""Turn on/off stats for builds."""
|
94
|
+
self._count = state
|
95
|
+
return
|
96
|
+
|
97
|
+
def setBuildOutline(self, state: bool) -> None:
|
98
|
+
"""Turn on/off outline for builds."""
|
99
|
+
self._outline = state
|
100
|
+
return
|
101
|
+
|
102
|
+
def setPreviewMode(self, state: bool) -> None:
|
103
|
+
"""Set the preview mode of the build. This also enables stats
|
104
|
+
count and outline mode.
|
105
|
+
"""
|
106
|
+
self._preview = state
|
107
|
+
self._outline = state
|
108
|
+
self._count = state
|
109
|
+
return
|
110
|
+
|
82
111
|
##
|
83
112
|
# Special Methods
|
84
113
|
##
|
@@ -153,6 +182,8 @@ class NWBuildDocument:
|
|
153
182
|
makeObj = ToHtml(self._project)
|
154
183
|
filtered = self._setupBuild(makeObj)
|
155
184
|
|
185
|
+
makeObj.setPreview(self._preview)
|
186
|
+
makeObj.setLinkHeadings(self._preview)
|
156
187
|
for i, tHandle in enumerate(self._queue):
|
157
188
|
self._error = None
|
158
189
|
if filtered.get(tHandle, (False, 0))[0]:
|
@@ -160,7 +191,7 @@ class NWBuildDocument:
|
|
160
191
|
else:
|
161
192
|
yield i, False
|
162
193
|
|
163
|
-
if self._build.getBool("
|
194
|
+
if not (self._build.getBool("html.preserveTabs") or self._preview):
|
164
195
|
makeObj.replaceTabs()
|
165
196
|
|
166
197
|
self._error = None
|
@@ -191,6 +222,8 @@ class NWBuildDocument:
|
|
191
222
|
if self._build.getBool("format.replaceTabs"):
|
192
223
|
makeObj.replaceTabs(nSpaces=4, spaceChar=" ")
|
193
224
|
|
225
|
+
makeObj.setPreserveBreaks(self._build.getBool("md.preserveBreaks"))
|
226
|
+
|
194
227
|
for i, tHandle in enumerate(self._queue):
|
195
228
|
self._error = None
|
196
229
|
if filtered.get(tHandle, (False, 0))[0]:
|
@@ -256,26 +289,52 @@ class NWBuildDocument:
|
|
256
289
|
fontInfo = QFontInfo(bldFont)
|
257
290
|
textFixed = fontInfo.fixedPitch()
|
258
291
|
|
259
|
-
bldObj.setTitleFormat(
|
260
|
-
|
261
|
-
|
292
|
+
bldObj.setTitleFormat(
|
293
|
+
self._build.getStr("headings.fmtTitle"),
|
294
|
+
self._build.getBool("headings.hideTitle")
|
295
|
+
)
|
296
|
+
bldObj.setChapterFormat(
|
297
|
+
self._build.getStr("headings.fmtChapter"),
|
298
|
+
self._build.getBool("headings.hideChapter")
|
299
|
+
)
|
300
|
+
bldObj.setUnNumberedFormat(
|
301
|
+
self._build.getStr("headings.fmtUnnumbered"),
|
302
|
+
self._build.getBool("headings.hideUnnumbered")
|
303
|
+
)
|
262
304
|
bldObj.setSceneFormat(
|
263
305
|
self._build.getStr("headings.fmtScene"),
|
264
306
|
self._build.getBool("headings.hideScene")
|
265
307
|
)
|
308
|
+
bldObj.setHardSceneFormat(
|
309
|
+
self._build.getStr("headings.fmtAltScene"),
|
310
|
+
self._build.getBool("headings.hideAltScene")
|
311
|
+
)
|
266
312
|
bldObj.setSectionFormat(
|
267
313
|
self._build.getStr("headings.fmtSection"),
|
268
314
|
self._build.getBool("headings.hideSection")
|
269
315
|
)
|
316
|
+
bldObj.setTitleStyle(
|
317
|
+
self._build.getBool("headings.centerTitle"),
|
318
|
+
self._build.getBool("headings.breakTitle")
|
319
|
+
)
|
320
|
+
bldObj.setChapterStyle(
|
321
|
+
self._build.getBool("headings.centerChapter"),
|
322
|
+
self._build.getBool("headings.breakChapter")
|
323
|
+
)
|
324
|
+
bldObj.setSceneStyle(
|
325
|
+
self._build.getBool("headings.centerScene"),
|
326
|
+
self._build.getBool("headings.breakScene")
|
327
|
+
)
|
270
328
|
|
271
329
|
bldObj.setFont(fontFamily, textSize, textFixed)
|
272
330
|
bldObj.setJustify(self._build.getBool("format.justifyText"))
|
273
331
|
bldObj.setLineHeight(self._build.getFloat("format.lineHeight"))
|
274
332
|
|
333
|
+
bldObj.setBodyText(self._build.getBool("text.includeBodyText"))
|
275
334
|
bldObj.setSynopsis(self._build.getBool("text.includeSynopsis"))
|
276
335
|
bldObj.setComments(self._build.getBool("text.includeComments"))
|
277
336
|
bldObj.setKeywords(self._build.getBool("text.includeKeywords"))
|
278
|
-
bldObj.
|
337
|
+
bldObj.setIgnoredKeywords(self._build.getStr("text.ignoredKeywords"))
|
279
338
|
|
280
339
|
if isinstance(bldObj, ToHtml):
|
281
340
|
bldObj.setStyles(self._build.getBool("html.addStyles"))
|
@@ -287,6 +346,7 @@ class NWBuildDocument:
|
|
287
346
|
bldObj.setHeaderFormat(
|
288
347
|
self._build.getStr("odt.pageHeader"), self._build.getInt("odt.pageCountOffset")
|
289
348
|
)
|
349
|
+
bldObj.setFirstLineIndent(self._build.getBool("odt.firstLineIndent"))
|
290
350
|
|
291
351
|
scale = nwLabels.UNIT_SCALE.get(self._build.getStr("format.pageUnit"), 1.0)
|
292
352
|
pW, pH = nwLabels.PAPER_SIZE.get(self._build.getStr("format.pageSize"), (-1.0, -1.0))
|
@@ -314,11 +374,18 @@ class NWBuildDocument:
|
|
314
374
|
bldObj.addRootHeading(tHandle)
|
315
375
|
if convert:
|
316
376
|
bldObj.doConvert()
|
377
|
+
if self._count:
|
378
|
+
bldObj.countStats()
|
379
|
+
if self._outline:
|
380
|
+
bldObj.buildOutline()
|
317
381
|
elif tItem.isFileType():
|
318
382
|
bldObj.setText(tHandle)
|
319
383
|
bldObj.doPreProcessing()
|
320
384
|
bldObj.tokenizeText()
|
321
|
-
|
385
|
+
if self._count:
|
386
|
+
bldObj.countStats()
|
387
|
+
if self._outline:
|
388
|
+
bldObj.buildOutline()
|
322
389
|
if convert:
|
323
390
|
bldObj.doConvert()
|
324
391
|
else:
|
novelwriter/core/document.py
CHANGED
@@ -31,7 +31,7 @@ from typing import TYPE_CHECKING
|
|
31
31
|
from pathlib import Path
|
32
32
|
|
33
33
|
from novelwriter.enum import nwItemLayout, nwItemClass
|
34
|
-
from novelwriter.error import formatException
|
34
|
+
from novelwriter.error import formatException, logException
|
35
35
|
from novelwriter.common import formatTimeStamp, isHandle
|
36
36
|
from novelwriter.core.item import NWItem
|
37
37
|
|
@@ -106,7 +106,28 @@ class NWDocument:
|
|
106
106
|
return self._item
|
107
107
|
|
108
108
|
##
|
109
|
-
#
|
109
|
+
# Static Methods
|
110
|
+
##
|
111
|
+
|
112
|
+
@staticmethod
|
113
|
+
def quickReadText(content: Path, tHandle: str) -> str:
|
114
|
+
"""Return the text of a document in a fast and efficient way."""
|
115
|
+
if (path := content / f"{tHandle}.nwd").is_file():
|
116
|
+
try:
|
117
|
+
with open(path, mode="r", encoding="utf-8") as inFile:
|
118
|
+
line = ""
|
119
|
+
for _ in range(10):
|
120
|
+
if not (line := inFile.readline()).startswith(r"%%~"):
|
121
|
+
break
|
122
|
+
return line + inFile.read()
|
123
|
+
except Exception:
|
124
|
+
logger.error("Cannot read document with handle '%s'", tHandle)
|
125
|
+
logException()
|
126
|
+
return ""
|
127
|
+
return ""
|
128
|
+
|
129
|
+
##
|
130
|
+
# Methods
|
110
131
|
##
|
111
132
|
|
112
133
|
def fileExists(self) -> bool:
|
@@ -155,7 +176,7 @@ class NWDocument:
|
|
155
176
|
try:
|
156
177
|
with open(docPath, mode="r", encoding="utf-8") as inFile:
|
157
178
|
# Check the first <= 10 lines for metadata
|
158
|
-
for
|
179
|
+
for _ in range(10):
|
159
180
|
line = inFile.readline()
|
160
181
|
if line.startswith(r"%%~"):
|
161
182
|
self._parseMeta(line)
|