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.
Files changed (83) hide show
  1. {novelWriter-2.5.3.dist-info → novelWriter-2.6b2.dist-info}/METADATA +1 -1
  2. {novelWriter-2.5.3.dist-info → novelWriter-2.6b2.dist-info}/RECORD +80 -60
  3. novelwriter/__init__.py +49 -10
  4. novelwriter/assets/i18n/project_en_GB.json +1 -0
  5. novelwriter/assets/icons/typicons_dark/icons.conf +8 -0
  6. novelwriter/assets/icons/typicons_dark/mixed_copy.svg +4 -0
  7. novelwriter/assets/icons/typicons_dark/mixed_margin-bottom.svg +6 -0
  8. novelwriter/assets/icons/typicons_dark/mixed_margin-left.svg +6 -0
  9. novelwriter/assets/icons/typicons_dark/mixed_margin-right.svg +6 -0
  10. novelwriter/assets/icons/typicons_dark/mixed_margin-top.svg +6 -0
  11. novelwriter/assets/icons/typicons_dark/mixed_size-height.svg +6 -0
  12. novelwriter/assets/icons/typicons_dark/mixed_size-width.svg +6 -0
  13. novelwriter/assets/icons/typicons_dark/nw_toolbar.svg +5 -0
  14. novelwriter/assets/icons/typicons_light/icons.conf +8 -0
  15. novelwriter/assets/icons/typicons_light/mixed_copy.svg +4 -0
  16. novelwriter/assets/icons/typicons_light/mixed_margin-bottom.svg +6 -0
  17. novelwriter/assets/icons/typicons_light/mixed_margin-left.svg +6 -0
  18. novelwriter/assets/icons/typicons_light/mixed_margin-right.svg +6 -0
  19. novelwriter/assets/icons/typicons_light/mixed_margin-top.svg +6 -0
  20. novelwriter/assets/icons/typicons_light/mixed_size-height.svg +6 -0
  21. novelwriter/assets/icons/typicons_light/mixed_size-width.svg +6 -0
  22. novelwriter/assets/icons/typicons_light/nw_toolbar.svg +5 -0
  23. novelwriter/assets/manual.pdf +0 -0
  24. novelwriter/assets/sample.zip +0 -0
  25. novelwriter/common.py +100 -2
  26. novelwriter/config.py +25 -15
  27. novelwriter/constants.py +168 -60
  28. novelwriter/core/buildsettings.py +66 -39
  29. novelwriter/core/coretools.py +145 -147
  30. novelwriter/core/docbuild.py +132 -170
  31. novelwriter/core/index.py +38 -37
  32. novelwriter/core/item.py +41 -8
  33. novelwriter/core/itemmodel.py +518 -0
  34. novelwriter/core/options.py +4 -1
  35. novelwriter/core/project.py +67 -89
  36. novelwriter/core/spellcheck.py +9 -14
  37. novelwriter/core/status.py +7 -5
  38. novelwriter/core/tree.py +268 -287
  39. novelwriter/dialogs/docmerge.py +7 -17
  40. novelwriter/dialogs/preferences.py +46 -33
  41. novelwriter/dialogs/projectsettings.py +5 -5
  42. novelwriter/enum.py +36 -23
  43. novelwriter/extensions/configlayout.py +27 -12
  44. novelwriter/extensions/modified.py +13 -1
  45. novelwriter/extensions/pagedsidebar.py +5 -5
  46. novelwriter/formats/shared.py +155 -0
  47. novelwriter/formats/todocx.py +1191 -0
  48. novelwriter/formats/tohtml.py +451 -0
  49. novelwriter/{core → formats}/tokenizer.py +487 -491
  50. novelwriter/formats/tomarkdown.py +217 -0
  51. novelwriter/{core → formats}/toodt.py +311 -432
  52. novelwriter/formats/toqdoc.py +484 -0
  53. novelwriter/formats/toraw.py +91 -0
  54. novelwriter/gui/doceditor.py +342 -284
  55. novelwriter/gui/dochighlight.py +96 -84
  56. novelwriter/gui/docviewer.py +88 -31
  57. novelwriter/gui/docviewerpanel.py +17 -25
  58. novelwriter/gui/editordocument.py +17 -2
  59. novelwriter/gui/itemdetails.py +25 -28
  60. novelwriter/gui/mainmenu.py +129 -63
  61. novelwriter/gui/noveltree.py +45 -47
  62. novelwriter/gui/outline.py +196 -249
  63. novelwriter/gui/projtree.py +594 -1241
  64. novelwriter/gui/search.py +9 -10
  65. novelwriter/gui/sidebar.py +7 -6
  66. novelwriter/gui/theme.py +10 -5
  67. novelwriter/guimain.py +100 -196
  68. novelwriter/shared.py +66 -27
  69. novelwriter/text/counting.py +2 -0
  70. novelwriter/text/patterns.py +168 -60
  71. novelwriter/tools/manusbuild.py +14 -12
  72. novelwriter/tools/manuscript.py +120 -78
  73. novelwriter/tools/manussettings.py +424 -291
  74. novelwriter/tools/welcome.py +4 -4
  75. novelwriter/tools/writingstats.py +3 -3
  76. novelwriter/types.py +23 -7
  77. novelwriter/core/tohtml.py +0 -530
  78. novelwriter/core/tomarkdown.py +0 -252
  79. novelwriter/core/toqdoc.py +0 -419
  80. {novelWriter-2.5.3.dist-info → novelWriter-2.6b2.dist-info}/LICENSE.md +0 -0
  81. {novelWriter-2.5.3.dist-info → novelWriter-2.6b2.dist-info}/WHEEL +0 -0
  82. {novelWriter-2.5.3.dist-info → novelWriter-2.6b2.dist-info}/entry_points.txt +0 -0
  83. {novelWriter-2.5.3.dist-info → novelWriter-2.6b2.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,117 @@ 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
+ makeObj.disableAnchors()
180
+ filtered = self._setupBuild(makeObj)
181
+ makeObj.initDocument(pdf=True)
182
+ yield from self._iterBuild(makeObj, filtered)
183
+ makeObj.closeDocument()
184
+
185
+ else:
186
+ logger.error("Unsupported document format")
187
+ return
246
188
 
247
189
  self._error = None
248
190
  self._cache = makeObj
249
191
 
250
192
  try:
251
- makeObj.saveMarkdown(path)
193
+ makeObj.saveDocument(path)
252
194
  except Exception as exc:
253
195
  logException()
254
196
  self._error = formatException(exc)
255
197
 
256
198
  return
257
199
 
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)
200
+ ##
201
+ # Internal Functions
202
+ ##
264
203
 
204
+ def _iterBuild(self, makeObj: Tokenizer, filtered: dict) -> Iterable[tuple[int, bool]]:
205
+ """Iterate over buildable documents."""
206
+ self._count = True
265
207
  for i, tHandle in enumerate(self._queue):
266
208
  self._error = None
267
209
  if filtered.get(tHandle, (False, 0))[0]:
268
- yield i, self._doBuild(makeObj, tHandle, convert=False)
210
+ yield i, self._doBuild(makeObj, tHandle)
269
211
  else:
270
212
  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
213
  return
289
214
 
290
- ##
291
- # Internal Functions
292
- ##
293
-
294
215
  def _setupBuild(self, bldObj: Tokenizer) -> dict:
295
216
  """Configure the build object."""
296
217
  # Get Settings
297
218
  textFont = QFont(CONFIG.textFont)
298
219
  textFont.fromString(self._build.getStr("format.textFont"))
299
- bldObj.setFont(textFont)
300
220
 
301
- bldObj.setTitleFormat(
302
- self._build.getStr("headings.fmtTitle"),
303
- self._build.getBool("headings.hideTitle")
221
+ bldObj.setTextFont(textFont)
222
+ bldObj.setLanguage(self._project.data.language)
223
+
224
+ bldObj.setPartitionFormat(
225
+ self._build.getStr("headings.fmtPart"),
226
+ self._build.getBool("headings.hidePart")
304
227
  )
305
228
  bldObj.setChapterFormat(
306
229
  self._build.getStr("headings.fmtChapter"),
@@ -326,6 +249,10 @@ class NWBuildDocument:
326
249
  self._build.getBool("headings.centerTitle"),
327
250
  self._build.getBool("headings.breakTitle")
328
251
  )
252
+ bldObj.setPartitionStyle(
253
+ self._build.getBool("headings.centerPart"),
254
+ self._build.getBool("headings.breakPart")
255
+ )
329
256
  bldObj.setChapterStyle(
330
257
  self._build.getBool("headings.centerChapter"),
331
258
  self._build.getBool("headings.breakChapter")
@@ -338,12 +265,46 @@ class NWBuildDocument:
338
265
  bldObj.setJustify(self._build.getBool("format.justifyText"))
339
266
  bldObj.setLineHeight(self._build.getFloat("format.lineHeight"))
340
267
  bldObj.setKeepLineBreaks(self._build.getBool("format.keepBreaks"))
341
- bldObj.setDialogueHighlight(self._build.getBool("format.showDialogue"))
268
+ bldObj.setDialogHighlight(self._build.getBool("format.showDialogue"))
342
269
  bldObj.setFirstLineIndent(
343
270
  self._build.getBool("format.firstLineIndent"),
344
271
  self._build.getFloat("format.firstIndentWidth"),
345
272
  self._build.getBool("format.indentFirstPar"),
346
273
  )
274
+ bldObj.setHeadingStyles(
275
+ self._build.getBool("doc.colorHeadings"),
276
+ self._build.getBool("doc.scaleHeadings"),
277
+ self._build.getBool("doc.boldHeadings"),
278
+ )
279
+
280
+ bldObj.setTitleMargins(
281
+ self._build.getFloat("format.titleMarginT"),
282
+ self._build.getFloat("format.titleMarginB"),
283
+ )
284
+ bldObj.setHead1Margins(
285
+ self._build.getFloat("format.h1MarginT"),
286
+ self._build.getFloat("format.h1MarginB"),
287
+ )
288
+ bldObj.setHead2Margins(
289
+ self._build.getFloat("format.h2MarginT"),
290
+ self._build.getFloat("format.h2MarginB"),
291
+ )
292
+ bldObj.setHead3Margins(
293
+ self._build.getFloat("format.h3MarginT"),
294
+ self._build.getFloat("format.h3MarginB"),
295
+ )
296
+ bldObj.setHead4Margins(
297
+ self._build.getFloat("format.h4MarginT"),
298
+ self._build.getFloat("format.h4MarginB"),
299
+ )
300
+ bldObj.setTextMargins(
301
+ self._build.getFloat("format.textMarginT"),
302
+ self._build.getFloat("format.textMarginB"),
303
+ )
304
+ bldObj.setSeparatorMargins(
305
+ self._build.getFloat("format.sepMarginT"),
306
+ self._build.getFloat("format.sepMarginB"),
307
+ )
347
308
 
348
309
  bldObj.setBodyText(self._build.getBool("text.includeBodyText"))
349
310
  bldObj.setSynopsis(self._build.getBool("text.includeSynopsis"))
@@ -355,13 +316,13 @@ class NWBuildDocument:
355
316
  bldObj.setStyles(self._build.getBool("html.addStyles"))
356
317
  bldObj.setReplaceUnicode(self._build.getBool("format.stripUnicode"))
357
318
 
358
- if isinstance(bldObj, ToOdt):
359
- bldObj.setColourHeaders(self._build.getBool("odt.addColours"))
360
- bldObj.setLanguage(self._project.data.language)
319
+ if isinstance(bldObj, (ToOdt, ToDocX)):
361
320
  bldObj.setHeaderFormat(
362
- self._build.getStr("odt.pageHeader"), self._build.getInt("odt.pageCountOffset")
321
+ self._build.getStr("doc.pageHeader"),
322
+ self._build.getInt("doc.pageCountOffset"),
363
323
  )
364
324
 
325
+ if isinstance(bldObj, (ToOdt, ToDocX, ToQTextDocument)):
365
326
  scale = nwLabels.UNIT_SCALE.get(self._build.getStr("format.pageUnit"), 1.0)
366
327
  pW, pH = nwLabels.PAPER_SIZE.get(self._build.getStr("format.pageSize"), (-1.0, -1.0))
367
328
  bldObj.setPageLayout(
@@ -384,14 +345,17 @@ class NWBuildDocument:
384
345
  tItem = self._project.tree[tHandle]
385
346
  if isinstance(tItem, NWItem):
386
347
  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()
348
+ if tItem.isRootType():
349
+ if tItem.isNovelLike():
350
+ bldObj.setBreakNext()
351
+ else:
352
+ bldObj.addRootHeading(tHandle)
353
+ if convert:
354
+ bldObj.doConvert()
355
+ if self._count:
356
+ bldObj.countStats()
357
+ if self._outline:
358
+ bldObj.buildOutline()
395
359
  elif tItem.isFileType():
396
360
  bldObj.setText(tHandle)
397
361
  bldObj.doPreProcessing()
@@ -402,8 +366,6 @@ class NWBuildDocument:
402
366
  bldObj.buildOutline()
403
367
  if convert:
404
368
  bldObj.doConvert()
405
- else:
406
- logger.info(f"Build: Skipping '{tHandle}'")
407
369
 
408
370
  except Exception:
409
371
  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
@@ -116,24 +116,24 @@ class NWIndex:
116
116
  # Public Methods
117
117
  ##
118
118
 
119
- def clearIndex(self) -> None:
119
+ def clear(self) -> None:
120
120
  """Clear the index dictionaries and time stamps."""
121
121
  self._tagsIndex.clear()
122
122
  self._itemIndex.clear()
123
123
  self._indexChange = 0.0
124
124
  self._rootChange = {}
125
- SHARED.indexSignalProxy({"event": "clearIndex"})
125
+ SHARED.emitIndexCleared(self._project)
126
126
  return
127
127
 
128
- def rebuildIndex(self) -> None:
128
+ def rebuild(self) -> None:
129
129
  """Rebuild the entire index from scratch."""
130
- self.clearIndex()
130
+ self.clear()
131
131
  for nwItem in self._project.tree:
132
132
  if nwItem.isFileType():
133
133
  text = self._project.storage.getDocumentText(nwItem.itemHandle)
134
134
  self.scanText(nwItem.itemHandle, text, blockSignal=True)
135
135
  self._indexBroken = False
136
- SHARED.indexSignalProxy({"event": "buildIndex"})
136
+ SHARED.emitIndexAvailable(self._project)
137
137
  return
138
138
 
139
139
  def deleteHandle(self, tHandle: str) -> None:
@@ -143,10 +143,7 @@ class NWIndex:
143
143
  for tTag in delTags:
144
144
  del self._tagsIndex[tTag]
145
145
  del self._itemIndex[tHandle]
146
- SHARED.indexSignalProxy({
147
- "event": "updateTags",
148
- "deleted": delTags,
149
- })
146
+ SHARED.emitIndexChangedTags(self._project, [], delTags)
150
147
  return
151
148
 
152
149
  def reIndexHandle(self, tHandle: str | None) -> None:
@@ -212,7 +209,7 @@ class NWIndex:
212
209
  self.reIndexHandle(fHandle)
213
210
 
214
211
  self._indexChange = time()
215
- SHARED.indexSignalProxy({"event": "buildIndex"})
212
+ SHARED.emitIndexAvailable(self._project)
216
213
 
217
214
  logger.debug("Index loaded in %.3f ms", (time() - tStart)*1000)
218
215
 
@@ -296,10 +293,7 @@ class NWIndex:
296
293
  self._indexChange = nowTime
297
294
  self._rootChange[tItem.itemRoot] = nowTime
298
295
  if not blockSignal:
299
- SHARED.indexSignalProxy({
300
- "event": "scanText",
301
- "handle": tHandle,
302
- })
296
+ tItem.notifyToRefresh()
303
297
 
304
298
  return True
305
299
 
@@ -370,11 +364,7 @@ class NWIndex:
370
364
  del self._tagsIndex[tTag]
371
365
  deleted.append(tTag)
372
366
  if updated or deleted:
373
- SHARED.indexSignalProxy({
374
- "event": "updateTags",
375
- "updated": updated,
376
- "deleted": deleted,
377
- })
367
+ SHARED.emitIndexChangedTags(self._project, updated, deleted)
378
368
 
379
369
  return
380
370
 
@@ -487,13 +477,14 @@ class NWIndex:
487
477
  if nBits == 0:
488
478
  return []
489
479
 
490
- # Check that the key is valid
491
- isGood[0] = tBits[0] in nwKeyWords.VALID_KEYS
480
+ # Check that the keyword is valid
481
+ kBit = tBits[0]
482
+ isGood[0] = kBit in nwKeyWords.VALID_KEYS
492
483
  if not isGood[0] or nBits == 1:
493
484
  return isGood
494
485
 
495
486
  # For a tag, only the first value is accepted, the rest are ignored
496
- if tBits[0] == nwKeyWords.TAG_KEY and nBits > 1:
487
+ if kBit == nwKeyWords.TAG_KEY and nBits > 1:
497
488
  check, _ = self.parseValue(tBits[1])
498
489
  if check in self._tagsIndex:
499
490
  isGood[1] = self._tagsIndex.tagHandle(check) == tHandle
@@ -501,12 +492,16 @@ class NWIndex:
501
492
  isGood[1] = True
502
493
  return isGood
503
494
 
495
+ if kBit == nwKeyWords.MENTION_KEY and nBits > 1:
496
+ isGood[1:nBits] = [aBit in self._tagsIndex for aBit in tBits[1:nBits]]
497
+ return isGood
498
+
504
499
  # If we're still here, we check that the references exist
505
500
  # 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
501
+ if rClass := nwKeyWords.KEY_CLASS.get(kBit):
502
+ for n in range(1, nBits):
503
+ if (aBit := tBits[n]) in self._tagsIndex:
504
+ isGood[n] = self._tagsIndex.tagClass(aBit) == rClass.name and "|" not in aBit
510
505
 
511
506
  return isGood
512
507
 
@@ -569,7 +564,7 @@ class NWIndex:
569
564
  for _, _, hItem in self._itemIndex.iterNovelStructure(
570
565
  rHandle=rootHandle, activeOnly=activeOnly
571
566
  ):
572
- iLevel = nwHeaders.H_LEVEL.get(hItem.level, 0)
567
+ iLevel = nwStyles.H_LEVEL.get(hItem.level, 0)
573
568
  hCount[iLevel] += 1
574
569
  return hCount
575
570
 
@@ -591,7 +586,7 @@ class NWIndex:
591
586
  rHandle=rHandle, activeOnly=activeOnly
592
587
  ):
593
588
  tKey = f"{tHandle}:{sTitle}"
594
- iLevel = nwHeaders.H_LEVEL.get(hItem.level, 0)
589
+ iLevel = nwStyles.H_LEVEL.get(hItem.level, 0)
595
590
  if iLevel > maxDepth:
596
591
  if pKey in tData:
597
592
  tData[pKey]["words"] += hItem.wordCount
@@ -635,7 +630,7 @@ class NWIndex:
635
630
  """Extract all references made in a file, and optionally title
636
631
  section.
637
632
  """
638
- tRefs = {x: [] for x in nwKeyWords.KEY_CLASS}
633
+ tRefs = {x: [] for x in nwKeyWords.VALID_KEYS}
639
634
  for rTitle, hItem in self._itemIndex.iterItemHeaders(tHandle):
640
635
  if sTitle is None or sTitle == rTitle:
641
636
  for aTag, refTypes in hItem.references.items():
@@ -681,9 +676,10 @@ class NWIndex:
681
676
  """Return all tags used by a specific document."""
682
677
  return self._itemIndex.allItemTags(tHandle) if tHandle else []
683
678
 
684
- def getClassTags(self, itemClass: nwItemClass) -> list[str]:
679
+ def getClassTags(self, itemClass: nwItemClass | None) -> list[str]:
685
680
  """Return all tags based on itemClass."""
686
- return self._tagsIndex.filterTagNames(itemClass.name)
681
+ name = None if itemClass is None else itemClass.name
682
+ return self._tagsIndex.filterTagNames(name)
687
683
 
688
684
  def getTagsData(
689
685
  self, activeOnly: bool = True
@@ -780,11 +776,16 @@ class TagsIndex:
780
776
  """Get the class of a given tag."""
781
777
  return self._tags.get(tagKey.lower(), {}).get("class", None)
782
778
 
783
- def filterTagNames(self, className: str) -> list[str]:
779
+ def filterTagNames(self, className: str | None) -> list[str]:
784
780
  """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
- ]
781
+ if className is None:
782
+ return [
783
+ x.get("name", "") for x in self._tags.values()
784
+ ]
785
+ else:
786
+ return [
787
+ x.get("name", "") for x in self._tags.values() if x.get("class", "") == className
788
+ ]
788
789
 
789
790
  ##
790
791
  # Pack/Unpack
@@ -1251,7 +1252,7 @@ class IndexHeading:
1251
1252
 
1252
1253
  def setLevel(self, level: str) -> None:
1253
1254
  """Set the level of the heading if it's a valid value."""
1254
- if level in nwHeaders.H_VALID:
1255
+ if level in nwStyles.H_VALID:
1255
1256
  self._level = level
1256
1257
  return
1257
1258