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.
Files changed (64) hide show
  1. {novelWriter-2.5.1.dist-info → novelWriter-2.6b1.dist-info}/METADATA +2 -1
  2. {novelWriter-2.5.1.dist-info → novelWriter-2.6b1.dist-info}/RECORD +61 -56
  3. {novelWriter-2.5.1.dist-info → novelWriter-2.6b1.dist-info}/WHEEL +1 -1
  4. novelwriter/__init__.py +3 -3
  5. novelwriter/assets/i18n/project_en_GB.json +1 -0
  6. novelwriter/assets/icons/typicons_dark/icons.conf +1 -0
  7. novelwriter/assets/icons/typicons_dark/mixed_copy.svg +4 -0
  8. novelwriter/assets/icons/typicons_light/icons.conf +1 -0
  9. novelwriter/assets/icons/typicons_light/mixed_copy.svg +4 -0
  10. novelwriter/assets/manual.pdf +0 -0
  11. novelwriter/assets/sample.zip +0 -0
  12. novelwriter/assets/themes/default_light.conf +2 -2
  13. novelwriter/common.py +63 -0
  14. novelwriter/config.py +10 -3
  15. novelwriter/constants.py +153 -60
  16. novelwriter/core/buildsettings.py +66 -39
  17. novelwriter/core/coretools.py +34 -22
  18. novelwriter/core/docbuild.py +130 -169
  19. novelwriter/core/index.py +29 -18
  20. novelwriter/core/item.py +2 -2
  21. novelwriter/core/options.py +4 -1
  22. novelwriter/core/spellcheck.py +9 -14
  23. novelwriter/dialogs/preferences.py +45 -32
  24. novelwriter/dialogs/projectsettings.py +3 -3
  25. novelwriter/enum.py +29 -23
  26. novelwriter/extensions/configlayout.py +24 -11
  27. novelwriter/extensions/modified.py +13 -1
  28. novelwriter/extensions/pagedsidebar.py +5 -5
  29. novelwriter/formats/shared.py +155 -0
  30. novelwriter/formats/todocx.py +1195 -0
  31. novelwriter/formats/tohtml.py +452 -0
  32. novelwriter/{core → formats}/tokenizer.py +483 -485
  33. novelwriter/formats/tomarkdown.py +217 -0
  34. novelwriter/{core → formats}/toodt.py +270 -320
  35. novelwriter/formats/toqdoc.py +436 -0
  36. novelwriter/formats/toraw.py +91 -0
  37. novelwriter/gui/doceditor.py +240 -193
  38. novelwriter/gui/dochighlight.py +96 -84
  39. novelwriter/gui/docviewer.py +56 -30
  40. novelwriter/gui/docviewerpanel.py +3 -3
  41. novelwriter/gui/editordocument.py +17 -2
  42. novelwriter/gui/itemdetails.py +8 -4
  43. novelwriter/gui/mainmenu.py +121 -60
  44. novelwriter/gui/noveltree.py +35 -37
  45. novelwriter/gui/outline.py +186 -238
  46. novelwriter/gui/projtree.py +142 -131
  47. novelwriter/gui/sidebar.py +7 -6
  48. novelwriter/gui/theme.py +5 -4
  49. novelwriter/guimain.py +43 -155
  50. novelwriter/shared.py +14 -4
  51. novelwriter/text/counting.py +2 -0
  52. novelwriter/text/patterns.py +155 -59
  53. novelwriter/tools/manusbuild.py +1 -1
  54. novelwriter/tools/manuscript.py +121 -78
  55. novelwriter/tools/manussettings.py +403 -260
  56. novelwriter/tools/welcome.py +4 -4
  57. novelwriter/tools/writingstats.py +3 -3
  58. novelwriter/types.py +16 -6
  59. novelwriter/core/tohtml.py +0 -530
  60. novelwriter/core/tomarkdown.py +0 -252
  61. novelwriter/core/toqdoc.py +0 -419
  62. {novelWriter-2.5.1.dist-info → novelWriter-2.6b1.dist-info}/LICENSE.md +0 -0
  63. {novelWriter-2.5.1.dist-info → novelWriter-2.6b1.dist-info}/entry_points.txt +0 -0
  64. {novelWriter-2.5.1.dist-info → novelWriter-2.6b1.dist-info}/top_level.txt +0 -0
@@ -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, theme: TextDocumentTheme) -> Iterable[tuple[int, bool]]:
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
- for i, tHandle in enumerate(self._queue):
173
- self._error = None
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 iterBuildHTML(self, path: Path | None, asJson: bool = False) -> Iterable[tuple[int, bool]]:
196
- """Build an HTML file. If path is None, no file is saved. This
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 = makeObj
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
- return
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
- def iterBuildMarkdown(self, path: Path, extendedMd: bool) -> Iterable[tuple[int, bool]]:
230
- """Build a Markdown file."""
231
- makeObj = ToMarkdown(self._project)
232
- filtered = self._setupBuild(makeObj)
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
- makeObj.setExtendedMarkdown(extendedMd)
235
- if self._build.getBool("format.replaceTabs"):
236
- makeObj.replaceTabs(nSpaces=4, spaceChar=" ")
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
- for i, tHandle in enumerate(self._queue):
239
- self._error = None
240
- if filtered.get(tHandle, (False, 0))[0]:
241
- yield i, self._doBuild(makeObj, tHandle)
242
- else:
243
- yield i, False
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
- makeObj.appendFootnotes()
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.saveMarkdown(path)
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
- def iterBuildNWD(self, path: Path | None, asJson: bool = False) -> Iterable[tuple[int, bool]]:
259
- """Build a novelWriter Markdown file."""
260
- makeObj = ToMarkdown(self._project)
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, convert=False)
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.setTitleFormat(
302
- self._build.getStr("headings.fmtTitle"),
303
- self._build.getBool("headings.hideTitle")
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.setDialogueHighlight(self._build.getBool("format.showDialogue"))
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("odt.pageHeader"), self._build.getInt("odt.pageCountOffset")
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() and not tItem.isNovelLike():
388
- bldObj.addRootHeading(tHandle)
389
- if convert:
390
- bldObj.doConvert()
391
- if self._count:
392
- bldObj.countStats()
393
- if self._outline:
394
- bldObj.buildOutline()
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, nwHeaders, nwKeyWords
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 key is valid
491
- isGood[0] = tBits[0] in nwKeyWords.VALID_KEYS
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 tBits[0] == nwKeyWords.TAG_KEY and nBits > 1:
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
- refKey = nwKeyWords.KEY_CLASS[tBits[0]].name
507
- for n in range(1, nBits):
508
- if (aBit := tBits[n]) in self._tagsIndex:
509
- isGood[n] = self._tagsIndex.tagClass(aBit) == refKey and "|" not in aBit
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 = nwHeaders.H_LEVEL.get(hItem.level, 0)
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 = nwHeaders.H_LEVEL.get(hItem.level, 0)
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.KEY_CLASS}
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
- return self._tagsIndex.filterTagNames(itemClass.name)
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
- return [
786
- x.get("name", "") for x in self._tags.values() if x.get("class", "") == className
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 nwHeaders.H_VALID:
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 nwHeaders, nwLabels, trConst
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 nwHeaders.H_LEVEL:
521
+ if value in nwStyles.H_LEVEL:
522
522
  self._heading = value
523
523
  return
524
524
 
@@ -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
 
@@ -125,21 +125,16 @@ class NWSpellEnchant:
125
125
  except Exception:
126
126
  return []
127
127
 
128
- def addWord(self, word: str) -> bool:
128
+ def addWord(self, word: str, save: bool = True) -> None:
129
129
  """Add a word to the project dictionary."""
130
- word = word.strip()
131
- if not word:
132
- return False
133
- try:
134
- self._enchant.add_to_session(word)
135
- except Exception:
136
- return False
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."""