novelWriter 2.5.1__py3-none-any.whl → 2.6b1__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.1.dist-info → novelWriter-2.6b1.dist-info}/METADATA +2 -1
- {novelWriter-2.5.1.dist-info → novelWriter-2.6b1.dist-info}/RECORD +61 -56
- {novelWriter-2.5.1.dist-info → novelWriter-2.6b1.dist-info}/WHEEL +1 -1
- novelwriter/__init__.py +3 -3
- novelwriter/assets/i18n/project_en_GB.json +1 -0
- novelwriter/assets/icons/typicons_dark/icons.conf +1 -0
- novelwriter/assets/icons/typicons_dark/mixed_copy.svg +4 -0
- novelwriter/assets/icons/typicons_light/icons.conf +1 -0
- novelwriter/assets/icons/typicons_light/mixed_copy.svg +4 -0
- novelwriter/assets/manual.pdf +0 -0
- novelwriter/assets/sample.zip +0 -0
- novelwriter/assets/themes/default_light.conf +2 -2
- novelwriter/common.py +63 -0
- novelwriter/config.py +10 -3
- novelwriter/constants.py +153 -60
- novelwriter/core/buildsettings.py +66 -39
- novelwriter/core/coretools.py +34 -22
- novelwriter/core/docbuild.py +130 -169
- novelwriter/core/index.py +29 -18
- novelwriter/core/item.py +2 -2
- novelwriter/core/options.py +4 -1
- novelwriter/core/spellcheck.py +9 -14
- novelwriter/dialogs/preferences.py +45 -32
- novelwriter/dialogs/projectsettings.py +3 -3
- novelwriter/enum.py +29 -23
- novelwriter/extensions/configlayout.py +24 -11
- novelwriter/extensions/modified.py +13 -1
- novelwriter/extensions/pagedsidebar.py +5 -5
- novelwriter/formats/shared.py +155 -0
- novelwriter/formats/todocx.py +1195 -0
- novelwriter/formats/tohtml.py +452 -0
- novelwriter/{core → formats}/tokenizer.py +483 -485
- novelwriter/formats/tomarkdown.py +217 -0
- novelwriter/{core → formats}/toodt.py +270 -320
- novelwriter/formats/toqdoc.py +436 -0
- novelwriter/formats/toraw.py +91 -0
- novelwriter/gui/doceditor.py +240 -193
- novelwriter/gui/dochighlight.py +96 -84
- novelwriter/gui/docviewer.py +56 -30
- novelwriter/gui/docviewerpanel.py +3 -3
- novelwriter/gui/editordocument.py +17 -2
- novelwriter/gui/itemdetails.py +8 -4
- novelwriter/gui/mainmenu.py +121 -60
- novelwriter/gui/noveltree.py +35 -37
- novelwriter/gui/outline.py +186 -238
- novelwriter/gui/projtree.py +142 -131
- novelwriter/gui/sidebar.py +7 -6
- novelwriter/gui/theme.py +5 -4
- novelwriter/guimain.py +43 -155
- novelwriter/shared.py +14 -4
- novelwriter/text/counting.py +2 -0
- novelwriter/text/patterns.py +155 -59
- novelwriter/tools/manusbuild.py +1 -1
- novelwriter/tools/manuscript.py +121 -78
- novelwriter/tools/manussettings.py +403 -260
- novelwriter/tools/welcome.py +4 -4
- novelwriter/tools/writingstats.py +3 -3
- novelwriter/types.py +16 -6
- novelwriter/core/tohtml.py +0 -530
- novelwriter/core/tomarkdown.py +0 -252
- novelwriter/core/toqdoc.py +0 -419
- {novelWriter-2.5.1.dist-info → novelWriter-2.6b1.dist-info}/LICENSE.md +0 -0
- {novelWriter-2.5.1.dist-info → novelWriter-2.6b1.dist-info}/entry_points.txt +0 -0
- {novelWriter-2.5.1.dist-info → novelWriter-2.6b1.dist-info}/top_level.txt +0 -0
novelwriter/core/docbuild.py
CHANGED
@@ -35,13 +35,15 @@ from novelwriter.constants import nwLabels
|
|
35
35
|
from novelwriter.core.buildsettings import BuildSettings
|
36
36
|
from novelwriter.core.item import NWItem
|
37
37
|
from novelwriter.core.project import NWProject
|
38
|
-
from novelwriter.core.tohtml import ToHtml
|
39
|
-
from novelwriter.core.tokenizer import Tokenizer
|
40
|
-
from novelwriter.core.tomarkdown import ToMarkdown
|
41
|
-
from novelwriter.core.toodt import ToOdt
|
42
|
-
from novelwriter.core.toqdoc import TextDocumentTheme, ToQTextDocument
|
43
38
|
from novelwriter.enum import nwBuildFmt
|
44
39
|
from novelwriter.error import formatException, logException
|
40
|
+
from novelwriter.formats.todocx import ToDocX
|
41
|
+
from novelwriter.formats.tohtml import ToHtml
|
42
|
+
from novelwriter.formats.tokenizer import Tokenizer
|
43
|
+
from novelwriter.formats.tomarkdown import ToMarkdown
|
44
|
+
from novelwriter.formats.toodt import ToOdt
|
45
|
+
from novelwriter.formats.toqdoc import ToQTextDocument
|
46
|
+
from novelwriter.formats.toraw import ToRaw
|
45
47
|
|
46
48
|
logger = logging.getLogger(__name__)
|
47
49
|
|
@@ -85,20 +87,6 @@ class NWBuildDocument:
|
|
85
87
|
"""
|
86
88
|
return self._cache
|
87
89
|
|
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
90
|
##
|
103
91
|
# Special Methods
|
104
92
|
##
|
@@ -125,182 +113,116 @@ class NWBuildDocument:
|
|
125
113
|
self._queue.append(item.itemHandle)
|
126
114
|
return
|
127
115
|
|
128
|
-
def iterBuildPreview(self,
|
116
|
+
def iterBuildPreview(self, newPage: bool) -> Iterable[tuple[int, bool]]:
|
129
117
|
"""Build a preview QTextDocument."""
|
130
118
|
makeObj = ToQTextDocument(self._project)
|
131
119
|
filtered = self._setupBuild(makeObj)
|
132
|
-
|
133
|
-
self._outline = True
|
134
|
-
self._count = True
|
135
|
-
|
136
|
-
font = QFont()
|
137
|
-
font.fromString(self._build.getStr("format.textFont"))
|
138
|
-
|
139
|
-
makeObj.initDocument(font, theme)
|
140
|
-
for i, tHandle in enumerate(self._queue):
|
141
|
-
self._error = None
|
142
|
-
if filtered.get(tHandle, (False, 0))[0]:
|
143
|
-
yield i, self._doBuild(makeObj, tHandle)
|
144
|
-
else:
|
145
|
-
yield i, False
|
146
|
-
|
147
|
-
makeObj.appendFootnotes()
|
148
|
-
|
149
|
-
self._error = None
|
150
|
-
self._cache = makeObj
|
151
|
-
|
152
|
-
return
|
153
|
-
|
154
|
-
def iterBuild(self, path: Path, bFormat: nwBuildFmt) -> Iterable[tuple[int, bool]]:
|
155
|
-
"""Wrapper for builders based on format."""
|
156
|
-
if bFormat in (nwBuildFmt.ODT, nwBuildFmt.FODT):
|
157
|
-
yield from self.iterBuildOpenDocument(path, bFormat == nwBuildFmt.FODT)
|
158
|
-
elif bFormat in (nwBuildFmt.HTML, nwBuildFmt.J_HTML):
|
159
|
-
yield from self.iterBuildHTML(path, asJson=bFormat == nwBuildFmt.J_HTML)
|
160
|
-
elif bFormat in (nwBuildFmt.STD_MD, nwBuildFmt.EXT_MD):
|
161
|
-
yield from self.iterBuildMarkdown(path, bFormat == nwBuildFmt.EXT_MD)
|
162
|
-
elif bFormat in (nwBuildFmt.NWD, nwBuildFmt.J_NWD):
|
163
|
-
yield from self.iterBuildNWD(path, asJson=bFormat == nwBuildFmt.J_NWD)
|
164
|
-
return
|
165
|
-
|
166
|
-
def iterBuildOpenDocument(self, path: Path, isFlat: bool) -> Iterable[tuple[int, bool]]:
|
167
|
-
"""Build an Open Document file."""
|
168
|
-
makeObj = ToOdt(self._project, isFlat=isFlat)
|
169
|
-
filtered = self._setupBuild(makeObj)
|
170
120
|
makeObj.initDocument()
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
if filtered.get(tHandle, (False, 0))[0]:
|
175
|
-
yield i, self._doBuild(makeObj, tHandle)
|
176
|
-
else:
|
177
|
-
yield i, False
|
178
|
-
|
121
|
+
makeObj.setShowNewPage(newPage)
|
122
|
+
self._outline = True
|
123
|
+
yield from self._iterBuild(makeObj, filtered)
|
179
124
|
makeObj.closeDocument()
|
180
|
-
|
181
125
|
self._error = None
|
182
126
|
self._cache = makeObj
|
183
|
-
|
184
|
-
try:
|
185
|
-
if isFlat:
|
186
|
-
makeObj.saveFlatXML(path)
|
187
|
-
else:
|
188
|
-
makeObj.saveOpenDocText(path)
|
189
|
-
except Exception as exc:
|
190
|
-
logException()
|
191
|
-
self._error = formatException(exc)
|
192
|
-
|
193
127
|
return
|
194
128
|
|
195
|
-
def
|
196
|
-
"""
|
197
|
-
is used for generating build previews.
|
198
|
-
"""
|
199
|
-
makeObj = ToHtml(self._project)
|
200
|
-
filtered = self._setupBuild(makeObj)
|
201
|
-
|
202
|
-
for i, tHandle in enumerate(self._queue):
|
203
|
-
self._error = None
|
204
|
-
if filtered.get(tHandle, (False, 0))[0]:
|
205
|
-
yield i, self._doBuild(makeObj, tHandle)
|
206
|
-
else:
|
207
|
-
yield i, False
|
208
|
-
|
209
|
-
makeObj.appendFootnotes()
|
210
|
-
|
211
|
-
if not self._build.getBool("html.preserveTabs"):
|
212
|
-
makeObj.replaceTabs()
|
213
|
-
|
129
|
+
def iterBuildDocument(self, path: Path, bFormat: nwBuildFmt) -> Iterable[tuple[int, bool]]:
|
130
|
+
"""Wrapper for builders based on format."""
|
214
131
|
self._error = None
|
215
|
-
self._cache =
|
216
|
-
|
217
|
-
if isinstance(path, Path):
|
218
|
-
try:
|
219
|
-
if asJson:
|
220
|
-
makeObj.saveHtmlJson(path)
|
221
|
-
else:
|
222
|
-
makeObj.saveHtml5(path)
|
223
|
-
except Exception as exc:
|
224
|
-
logException()
|
225
|
-
self._error = formatException(exc)
|
132
|
+
self._cache = None
|
226
133
|
|
227
|
-
|
134
|
+
if bFormat in (nwBuildFmt.J_HTML, nwBuildFmt.J_NWD):
|
135
|
+
# Ensure that JSON output has the correct extension
|
136
|
+
path = path.with_suffix(".json")
|
228
137
|
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
138
|
+
if bFormat in (nwBuildFmt.ODT, nwBuildFmt.FODT):
|
139
|
+
makeObj = ToOdt(self._project, bFormat == nwBuildFmt.FODT)
|
140
|
+
filtered = self._setupBuild(makeObj)
|
141
|
+
makeObj.initDocument()
|
142
|
+
yield from self._iterBuild(makeObj, filtered)
|
143
|
+
makeObj.closeDocument()
|
233
144
|
|
234
|
-
|
235
|
-
|
236
|
-
|
145
|
+
elif bFormat in (nwBuildFmt.HTML, nwBuildFmt.J_HTML):
|
146
|
+
makeObj = ToHtml(self._project)
|
147
|
+
filtered = self._setupBuild(makeObj)
|
148
|
+
makeObj.initDocument()
|
149
|
+
yield from self._iterBuild(makeObj, filtered)
|
150
|
+
makeObj.closeDocument()
|
151
|
+
if not self._build.getBool("html.preserveTabs"):
|
152
|
+
makeObj.replaceTabs()
|
237
153
|
|
238
|
-
|
239
|
-
self.
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
154
|
+
elif bFormat in (nwBuildFmt.STD_MD, nwBuildFmt.EXT_MD):
|
155
|
+
makeObj = ToMarkdown(self._project, bFormat == nwBuildFmt.EXT_MD)
|
156
|
+
filtered = self._setupBuild(makeObj)
|
157
|
+
yield from self._iterBuild(makeObj, filtered)
|
158
|
+
makeObj.closeDocument()
|
159
|
+
if self._build.getBool("format.replaceTabs"):
|
160
|
+
makeObj.replaceTabs(nSpaces=4, spaceChar=" ")
|
244
161
|
|
245
|
-
|
162
|
+
elif bFormat in (nwBuildFmt.NWD, nwBuildFmt.J_NWD):
|
163
|
+
makeObj = ToRaw(self._project)
|
164
|
+
filtered = self._setupBuild(makeObj)
|
165
|
+
yield from self._iterBuild(makeObj, filtered)
|
166
|
+
makeObj.closeDocument()
|
167
|
+
if self._build.getBool("format.replaceTabs"):
|
168
|
+
makeObj.replaceTabs(nSpaces=4, spaceChar=" ")
|
169
|
+
|
170
|
+
elif bFormat == nwBuildFmt.DOCX:
|
171
|
+
makeObj = ToDocX(self._project)
|
172
|
+
filtered = self._setupBuild(makeObj)
|
173
|
+
makeObj.initDocument()
|
174
|
+
yield from self._iterBuild(makeObj, filtered)
|
175
|
+
makeObj.closeDocument()
|
176
|
+
|
177
|
+
elif bFormat == nwBuildFmt.PDF:
|
178
|
+
makeObj = ToQTextDocument(self._project)
|
179
|
+
filtered = self._setupBuild(makeObj)
|
180
|
+
makeObj.initDocument()
|
181
|
+
yield from self._iterBuild(makeObj, filtered)
|
182
|
+
makeObj.closeDocument()
|
183
|
+
|
184
|
+
else:
|
185
|
+
logger.error("Unsupported document format")
|
186
|
+
return
|
246
187
|
|
247
188
|
self._error = None
|
248
189
|
self._cache = makeObj
|
249
190
|
|
250
191
|
try:
|
251
|
-
makeObj.
|
192
|
+
makeObj.saveDocument(path)
|
252
193
|
except Exception as exc:
|
253
194
|
logException()
|
254
195
|
self._error = formatException(exc)
|
255
196
|
|
256
197
|
return
|
257
198
|
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
filtered = self._setupBuild(makeObj)
|
262
|
-
|
263
|
-
makeObj.setKeepMarkdown(True)
|
199
|
+
##
|
200
|
+
# Internal Functions
|
201
|
+
##
|
264
202
|
|
203
|
+
def _iterBuild(self, makeObj: Tokenizer, filtered: dict) -> Iterable[tuple[int, bool]]:
|
204
|
+
"""Iterate over buildable documents."""
|
205
|
+
self._count = True
|
265
206
|
for i, tHandle in enumerate(self._queue):
|
266
207
|
self._error = None
|
267
208
|
if filtered.get(tHandle, (False, 0))[0]:
|
268
|
-
yield i, self._doBuild(makeObj, tHandle
|
209
|
+
yield i, self._doBuild(makeObj, tHandle)
|
269
210
|
else:
|
270
211
|
yield i, False
|
271
|
-
|
272
|
-
if self._build.getBool("format.replaceTabs"):
|
273
|
-
makeObj.replaceTabs(nSpaces=4, spaceChar=" ")
|
274
|
-
|
275
|
-
self._error = None
|
276
|
-
self._cache = makeObj
|
277
|
-
|
278
|
-
if isinstance(path, Path):
|
279
|
-
try:
|
280
|
-
if asJson:
|
281
|
-
makeObj.saveRawMarkdownJSON(path)
|
282
|
-
else:
|
283
|
-
makeObj.saveRawMarkdown(path)
|
284
|
-
except Exception as exc:
|
285
|
-
logException()
|
286
|
-
self._error = formatException(exc)
|
287
|
-
|
288
212
|
return
|
289
213
|
|
290
|
-
##
|
291
|
-
# Internal Functions
|
292
|
-
##
|
293
|
-
|
294
214
|
def _setupBuild(self, bldObj: Tokenizer) -> dict:
|
295
215
|
"""Configure the build object."""
|
296
216
|
# Get Settings
|
297
217
|
textFont = QFont(CONFIG.textFont)
|
298
218
|
textFont.fromString(self._build.getStr("format.textFont"))
|
219
|
+
|
299
220
|
bldObj.setFont(textFont)
|
221
|
+
bldObj.setLanguage(self._project.data.language)
|
300
222
|
|
301
|
-
bldObj.
|
302
|
-
self._build.getStr("headings.
|
303
|
-
self._build.getBool("headings.
|
223
|
+
bldObj.setPartitionFormat(
|
224
|
+
self._build.getStr("headings.fmtPart"),
|
225
|
+
self._build.getBool("headings.hidePart")
|
304
226
|
)
|
305
227
|
bldObj.setChapterFormat(
|
306
228
|
self._build.getStr("headings.fmtChapter"),
|
@@ -326,6 +248,10 @@ class NWBuildDocument:
|
|
326
248
|
self._build.getBool("headings.centerTitle"),
|
327
249
|
self._build.getBool("headings.breakTitle")
|
328
250
|
)
|
251
|
+
bldObj.setPartitionStyle(
|
252
|
+
self._build.getBool("headings.centerPart"),
|
253
|
+
self._build.getBool("headings.breakPart")
|
254
|
+
)
|
329
255
|
bldObj.setChapterStyle(
|
330
256
|
self._build.getBool("headings.centerChapter"),
|
331
257
|
self._build.getBool("headings.breakChapter")
|
@@ -338,12 +264,46 @@ class NWBuildDocument:
|
|
338
264
|
bldObj.setJustify(self._build.getBool("format.justifyText"))
|
339
265
|
bldObj.setLineHeight(self._build.getFloat("format.lineHeight"))
|
340
266
|
bldObj.setKeepLineBreaks(self._build.getBool("format.keepBreaks"))
|
341
|
-
bldObj.
|
267
|
+
bldObj.setDialogHighlight(self._build.getBool("format.showDialogue"))
|
342
268
|
bldObj.setFirstLineIndent(
|
343
269
|
self._build.getBool("format.firstLineIndent"),
|
344
270
|
self._build.getFloat("format.firstIndentWidth"),
|
345
271
|
self._build.getBool("format.indentFirstPar"),
|
346
272
|
)
|
273
|
+
bldObj.setHeadingStyles(
|
274
|
+
self._build.getBool("doc.colorHeadings"),
|
275
|
+
self._build.getBool("doc.scaleHeadings"),
|
276
|
+
self._build.getBool("doc.boldHeadings"),
|
277
|
+
)
|
278
|
+
|
279
|
+
bldObj.setTitleMargins(
|
280
|
+
self._build.getFloat("format.titleMarginT"),
|
281
|
+
self._build.getFloat("format.titleMarginB"),
|
282
|
+
)
|
283
|
+
bldObj.setHead1Margins(
|
284
|
+
self._build.getFloat("format.h1MarginT"),
|
285
|
+
self._build.getFloat("format.h1MarginB"),
|
286
|
+
)
|
287
|
+
bldObj.setHead2Margins(
|
288
|
+
self._build.getFloat("format.h2MarginT"),
|
289
|
+
self._build.getFloat("format.h2MarginB"),
|
290
|
+
)
|
291
|
+
bldObj.setHead3Margins(
|
292
|
+
self._build.getFloat("format.h3MarginT"),
|
293
|
+
self._build.getFloat("format.h3MarginB"),
|
294
|
+
)
|
295
|
+
bldObj.setHead4Margins(
|
296
|
+
self._build.getFloat("format.h4MarginT"),
|
297
|
+
self._build.getFloat("format.h4MarginB"),
|
298
|
+
)
|
299
|
+
bldObj.setTextMargins(
|
300
|
+
self._build.getFloat("format.textMarginT"),
|
301
|
+
self._build.getFloat("format.textMarginB"),
|
302
|
+
)
|
303
|
+
bldObj.setSeparatorMargins(
|
304
|
+
self._build.getFloat("format.sepMarginT"),
|
305
|
+
self._build.getFloat("format.sepMarginB"),
|
306
|
+
)
|
347
307
|
|
348
308
|
bldObj.setBodyText(self._build.getBool("text.includeBodyText"))
|
349
309
|
bldObj.setSynopsis(self._build.getBool("text.includeSynopsis"))
|
@@ -355,13 +315,13 @@ class NWBuildDocument:
|
|
355
315
|
bldObj.setStyles(self._build.getBool("html.addStyles"))
|
356
316
|
bldObj.setReplaceUnicode(self._build.getBool("format.stripUnicode"))
|
357
317
|
|
358
|
-
if isinstance(bldObj, ToOdt):
|
359
|
-
bldObj.setColourHeaders(self._build.getBool("odt.addColours"))
|
360
|
-
bldObj.setLanguage(self._project.data.language)
|
318
|
+
if isinstance(bldObj, (ToOdt, ToDocX)):
|
361
319
|
bldObj.setHeaderFormat(
|
362
|
-
self._build.getStr("
|
320
|
+
self._build.getStr("doc.pageHeader"),
|
321
|
+
self._build.getInt("doc.pageCountOffset"),
|
363
322
|
)
|
364
323
|
|
324
|
+
if isinstance(bldObj, (ToOdt, ToDocX, ToQTextDocument)):
|
365
325
|
scale = nwLabels.UNIT_SCALE.get(self._build.getStr("format.pageUnit"), 1.0)
|
366
326
|
pW, pH = nwLabels.PAPER_SIZE.get(self._build.getStr("format.pageSize"), (-1.0, -1.0))
|
367
327
|
bldObj.setPageLayout(
|
@@ -384,14 +344,17 @@ class NWBuildDocument:
|
|
384
344
|
tItem = self._project.tree[tHandle]
|
385
345
|
if isinstance(tItem, NWItem):
|
386
346
|
try:
|
387
|
-
if tItem.isRootType()
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
347
|
+
if tItem.isRootType():
|
348
|
+
if tItem.isNovelLike():
|
349
|
+
bldObj.setBreakNext()
|
350
|
+
else:
|
351
|
+
bldObj.addRootHeading(tHandle)
|
352
|
+
if convert:
|
353
|
+
bldObj.doConvert()
|
354
|
+
if self._count:
|
355
|
+
bldObj.countStats()
|
356
|
+
if self._outline:
|
357
|
+
bldObj.buildOutline()
|
395
358
|
elif tItem.isFileType():
|
396
359
|
bldObj.setText(tHandle)
|
397
360
|
bldObj.doPreProcessing()
|
@@ -402,8 +365,6 @@ class NWBuildDocument:
|
|
402
365
|
bldObj.buildOutline()
|
403
366
|
if convert:
|
404
367
|
bldObj.doConvert()
|
405
|
-
else:
|
406
|
-
logger.info(f"Build: Skipping '{tHandle}'")
|
407
368
|
|
408
369
|
except Exception:
|
409
370
|
self._error = f"Build: Failed to build '{tHandle}'"
|
novelwriter/core/index.py
CHANGED
@@ -40,7 +40,7 @@ from novelwriter import SHARED
|
|
40
40
|
from novelwriter.common import (
|
41
41
|
checkInt, isHandle, isItemClass, isListInstance, isTitleTag, jsonEncode
|
42
42
|
)
|
43
|
-
from novelwriter.constants import nwFiles,
|
43
|
+
from novelwriter.constants import nwFiles, nwKeyWords, nwStyles
|
44
44
|
from novelwriter.enum import nwComment, nwItemClass, nwItemLayout, nwItemType
|
45
45
|
from novelwriter.error import logException
|
46
46
|
from novelwriter.text.counting import standardCounter
|
@@ -487,13 +487,14 @@ class NWIndex:
|
|
487
487
|
if nBits == 0:
|
488
488
|
return []
|
489
489
|
|
490
|
-
# Check that the
|
491
|
-
|
490
|
+
# Check that the keyword is valid
|
491
|
+
kBit = tBits[0]
|
492
|
+
isGood[0] = kBit in nwKeyWords.VALID_KEYS
|
492
493
|
if not isGood[0] or nBits == 1:
|
493
494
|
return isGood
|
494
495
|
|
495
496
|
# For a tag, only the first value is accepted, the rest are ignored
|
496
|
-
if
|
497
|
+
if kBit == nwKeyWords.TAG_KEY and nBits > 1:
|
497
498
|
check, _ = self.parseValue(tBits[1])
|
498
499
|
if check in self._tagsIndex:
|
499
500
|
isGood[1] = self._tagsIndex.tagHandle(check) == tHandle
|
@@ -501,12 +502,16 @@ class NWIndex:
|
|
501
502
|
isGood[1] = True
|
502
503
|
return isGood
|
503
504
|
|
505
|
+
if kBit == nwKeyWords.MENTION_KEY and nBits > 1:
|
506
|
+
isGood[1:nBits] = [aBit in self._tagsIndex for aBit in tBits[1:nBits]]
|
507
|
+
return isGood
|
508
|
+
|
504
509
|
# If we're still here, we check that the references exist
|
505
510
|
# Class references cannot have the | symbol in them
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
511
|
+
if rClass := nwKeyWords.KEY_CLASS.get(kBit):
|
512
|
+
for n in range(1, nBits):
|
513
|
+
if (aBit := tBits[n]) in self._tagsIndex:
|
514
|
+
isGood[n] = self._tagsIndex.tagClass(aBit) == rClass.name and "|" not in aBit
|
510
515
|
|
511
516
|
return isGood
|
512
517
|
|
@@ -569,7 +574,7 @@ class NWIndex:
|
|
569
574
|
for _, _, hItem in self._itemIndex.iterNovelStructure(
|
570
575
|
rHandle=rootHandle, activeOnly=activeOnly
|
571
576
|
):
|
572
|
-
iLevel =
|
577
|
+
iLevel = nwStyles.H_LEVEL.get(hItem.level, 0)
|
573
578
|
hCount[iLevel] += 1
|
574
579
|
return hCount
|
575
580
|
|
@@ -591,7 +596,7 @@ class NWIndex:
|
|
591
596
|
rHandle=rHandle, activeOnly=activeOnly
|
592
597
|
):
|
593
598
|
tKey = f"{tHandle}:{sTitle}"
|
594
|
-
iLevel =
|
599
|
+
iLevel = nwStyles.H_LEVEL.get(hItem.level, 0)
|
595
600
|
if iLevel > maxDepth:
|
596
601
|
if pKey in tData:
|
597
602
|
tData[pKey]["words"] += hItem.wordCount
|
@@ -635,7 +640,7 @@ class NWIndex:
|
|
635
640
|
"""Extract all references made in a file, and optionally title
|
636
641
|
section.
|
637
642
|
"""
|
638
|
-
tRefs = {x: [] for x in nwKeyWords.
|
643
|
+
tRefs = {x: [] for x in nwKeyWords.VALID_KEYS}
|
639
644
|
for rTitle, hItem in self._itemIndex.iterItemHeaders(tHandle):
|
640
645
|
if sTitle is None or sTitle == rTitle:
|
641
646
|
for aTag, refTypes in hItem.references.items():
|
@@ -681,9 +686,10 @@ class NWIndex:
|
|
681
686
|
"""Return all tags used by a specific document."""
|
682
687
|
return self._itemIndex.allItemTags(tHandle) if tHandle else []
|
683
688
|
|
684
|
-
def getClassTags(self, itemClass: nwItemClass) -> list[str]:
|
689
|
+
def getClassTags(self, itemClass: nwItemClass | None) -> list[str]:
|
685
690
|
"""Return all tags based on itemClass."""
|
686
|
-
|
691
|
+
name = None if itemClass is None else itemClass.name
|
692
|
+
return self._tagsIndex.filterTagNames(name)
|
687
693
|
|
688
694
|
def getTagsData(
|
689
695
|
self, activeOnly: bool = True
|
@@ -780,11 +786,16 @@ class TagsIndex:
|
|
780
786
|
"""Get the class of a given tag."""
|
781
787
|
return self._tags.get(tagKey.lower(), {}).get("class", None)
|
782
788
|
|
783
|
-
def filterTagNames(self, className: str) -> list[str]:
|
789
|
+
def filterTagNames(self, className: str | None) -> list[str]:
|
784
790
|
"""Get a list of tag names for a given class."""
|
785
|
-
|
786
|
-
|
787
|
-
|
791
|
+
if className is None:
|
792
|
+
return [
|
793
|
+
x.get("name", "") for x in self._tags.values()
|
794
|
+
]
|
795
|
+
else:
|
796
|
+
return [
|
797
|
+
x.get("name", "") for x in self._tags.values() if x.get("class", "") == className
|
798
|
+
]
|
788
799
|
|
789
800
|
##
|
790
801
|
# Pack/Unpack
|
@@ -1251,7 +1262,7 @@ class IndexHeading:
|
|
1251
1262
|
|
1252
1263
|
def setLevel(self, level: str) -> None:
|
1253
1264
|
"""Set the level of the heading if it's a valid value."""
|
1254
|
-
if level in
|
1265
|
+
if level in nwStyles.H_VALID:
|
1255
1266
|
self._level = level
|
1256
1267
|
return
|
1257
1268
|
|
novelwriter/core/item.py
CHANGED
@@ -33,7 +33,7 @@ from novelwriter.common import (
|
|
33
33
|
checkInt, isHandle, isItemClass, isItemLayout, isItemType, simplified,
|
34
34
|
yesNo
|
35
35
|
)
|
36
|
-
from novelwriter.constants import
|
36
|
+
from novelwriter.constants import nwLabels, nwStyles, trConst
|
37
37
|
from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType
|
38
38
|
|
39
39
|
if TYPE_CHECKING: # pragma: no cover
|
@@ -518,7 +518,7 @@ class NWItem:
|
|
518
518
|
|
519
519
|
def setMainHeading(self, value: str) -> None:
|
520
520
|
"""Set the main heading level."""
|
521
|
-
if value in
|
521
|
+
if value in nwStyles.H_LEVEL:
|
522
522
|
self._heading = value
|
523
523
|
return
|
524
524
|
|
novelwriter/core/options.py
CHANGED
@@ -60,7 +60,7 @@ VALID_MAP: dict[str, set[str]] = {
|
|
60
60
|
},
|
61
61
|
"GuiManuscript": {
|
62
62
|
"winWidth", "winHeight", "optsWidth", "viewWidth", "listHeight",
|
63
|
-
"detailsHeight", "detailsWidth", "detailsExpanded",
|
63
|
+
"detailsHeight", "detailsWidth", "detailsExpanded", "showNewPage",
|
64
64
|
},
|
65
65
|
"GuiManuscriptBuild": {
|
66
66
|
"winWidth", "winHeight", "fmtWidth", "sumWidth",
|
@@ -73,6 +73,9 @@ VALID_MAP: dict[str, set[str]] = {
|
|
73
73
|
"widthCol3", "widthCol4", "wordsPerPage", "countFrom", "clearDouble",
|
74
74
|
"novelRoot",
|
75
75
|
},
|
76
|
+
"GuiOutlineDetails": {
|
77
|
+
"detailsWidth", "tagsWidth",
|
78
|
+
}
|
76
79
|
}
|
77
80
|
|
78
81
|
|
novelwriter/core/spellcheck.py
CHANGED
@@ -125,21 +125,16 @@ class NWSpellEnchant:
|
|
125
125
|
except Exception:
|
126
126
|
return []
|
127
127
|
|
128
|
-
def addWord(self, word: str) ->
|
128
|
+
def addWord(self, word: str, save: bool = True) -> None:
|
129
129
|
"""Add a word to the project dictionary."""
|
130
|
-
word
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
added = self._userDict.add(word)
|
139
|
-
if added:
|
140
|
-
self._userDict.save()
|
141
|
-
|
142
|
-
return added
|
130
|
+
if word := word.strip():
|
131
|
+
try:
|
132
|
+
self._enchant.add_to_session(word)
|
133
|
+
except Exception:
|
134
|
+
return
|
135
|
+
if save and self._userDict.add(word):
|
136
|
+
self._userDict.save()
|
137
|
+
return
|
143
138
|
|
144
139
|
def listDictionaries(self) -> list[tuple[str, str]]:
|
145
140
|
"""List available dictionaries."""
|