novelWriter 2.7.4__py3-none-any.whl → 2.8b1__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 (196) hide show
  1. novelwriter/__init__.py +8 -7
  2. novelwriter/assets/icons/font_awesome.icons +22 -4
  3. novelwriter/assets/icons/material_filled_normal.icons +20 -2
  4. novelwriter/assets/icons/material_filled_thin.icons +20 -2
  5. novelwriter/assets/icons/material_rounded_normal.icons +20 -2
  6. novelwriter/assets/icons/material_rounded_thin.icons +20 -2
  7. novelwriter/assets/icons/material_sharp_normal.icons +20 -2
  8. novelwriter/assets/icons/material_sharp_thin.icons +20 -2
  9. novelwriter/assets/icons/remix_filled.icons +20 -2
  10. novelwriter/assets/icons/remix_outline.icons +20 -2
  11. novelwriter/assets/images/welcome.webp +0 -0
  12. novelwriter/assets/manual.pdf +0 -0
  13. novelwriter/assets/manual_fr.pdf +0 -0
  14. novelwriter/assets/sample.zip +0 -0
  15. novelwriter/assets/text/credits_en.htm +61 -11
  16. novelwriter/assets/themes/aura.conf +97 -0
  17. novelwriter/assets/themes/aura_bright.conf +95 -0
  18. novelwriter/assets/themes/aura_soft.conf +97 -0
  19. novelwriter/assets/themes/b2t_garden_dark.conf +97 -0
  20. novelwriter/assets/themes/b2t_garden_light.conf +97 -0
  21. novelwriter/assets/themes/b2t_suburb_dark.conf +97 -0
  22. novelwriter/assets/themes/b2t_suburb_light.conf +97 -0
  23. novelwriter/assets/themes/b4t_classic_o_dark.conf +97 -0
  24. novelwriter/assets/themes/b4t_classic_o_light.conf +97 -0
  25. novelwriter/assets/themes/b4t_modern_c_dark.conf +97 -0
  26. novelwriter/assets/themes/b4t_modern_c_light.conf +97 -0
  27. novelwriter/assets/themes/blue_streak_dark.conf +97 -0
  28. novelwriter/assets/themes/blue_streak_light.conf +97 -0
  29. novelwriter/assets/themes/castle_day.conf +95 -0
  30. novelwriter/assets/themes/castle_night.conf +95 -0
  31. novelwriter/assets/themes/catppuccin_latte.conf +97 -0
  32. novelwriter/assets/themes/catppuccin_mocha.conf +97 -0
  33. novelwriter/assets/themes/chalky_soil.conf +95 -0
  34. novelwriter/assets/themes/chernozem.conf +95 -0
  35. novelwriter/assets/themes/cyberpunk_night.conf +88 -40
  36. novelwriter/assets/themes/default_dark.conf +89 -41
  37. novelwriter/assets/themes/default_light.conf +89 -41
  38. novelwriter/assets/themes/dracula.conf +91 -42
  39. novelwriter/assets/themes/espresso.conf +97 -0
  40. novelwriter/assets/themes/everforest_dark.conf +97 -0
  41. novelwriter/assets/themes/everforest_light.conf +97 -0
  42. novelwriter/assets/themes/floral_daydream.conf +95 -0
  43. novelwriter/assets/themes/floral_midnight.conf +95 -0
  44. novelwriter/assets/themes/full_moon.conf +95 -0
  45. novelwriter/assets/themes/grey_dark.conf +97 -0
  46. novelwriter/assets/themes/grey_light.conf +97 -0
  47. novelwriter/assets/themes/horizon_dark.conf +97 -0
  48. novelwriter/assets/themes/horizon_light.conf +97 -0
  49. novelwriter/assets/themes/jewel_case_dark.conf +95 -0
  50. novelwriter/assets/themes/jewel_case_light.conf +95 -0
  51. novelwriter/assets/themes/lcars.conf +97 -0
  52. novelwriter/assets/themes/light_owl.conf +117 -0
  53. novelwriter/assets/themes/new_moon.conf +97 -0
  54. novelwriter/assets/themes/night_owl.conf +117 -0
  55. novelwriter/assets/themes/noctis.conf +129 -0
  56. novelwriter/assets/themes/noctis_lux.conf +129 -0
  57. novelwriter/assets/themes/nord.conf +97 -0
  58. novelwriter/assets/themes/nordlicht.conf +95 -0
  59. novelwriter/assets/themes/otium_dark.conf +95 -0
  60. novelwriter/assets/themes/otium_light.conf +95 -0
  61. novelwriter/assets/themes/paragon.conf +96 -0
  62. novelwriter/assets/themes/primer_light.conf +97 -0
  63. novelwriter/assets/themes/primer_night.conf +97 -0
  64. novelwriter/assets/themes/rose_pine.conf +97 -0
  65. novelwriter/assets/themes/rose_pine_dawn.conf +97 -0
  66. novelwriter/assets/themes/ruby_day.conf +95 -0
  67. novelwriter/assets/themes/ruby_night.conf +95 -0
  68. novelwriter/assets/themes/selenium_dark.conf +95 -0
  69. novelwriter/assets/themes/selenium_light.conf +95 -0
  70. novelwriter/assets/themes/sepia_dark.conf +95 -0
  71. novelwriter/assets/themes/sepia_light.conf +95 -0
  72. novelwriter/assets/themes/snazzy.conf +102 -40
  73. novelwriter/assets/themes/solarized_dark.conf +108 -40
  74. novelwriter/assets/themes/solarized_light.conf +108 -40
  75. novelwriter/assets/themes/sultana_light.conf +95 -0
  76. novelwriter/assets/themes/sultana_night.conf +95 -0
  77. novelwriter/assets/themes/tango_dark.conf +111 -0
  78. novelwriter/assets/themes/tango_light.conf +111 -0
  79. novelwriter/assets/themes/tomorrow.conf +117 -0
  80. novelwriter/assets/themes/tomorrow_night.conf +117 -0
  81. novelwriter/assets/themes/tomorrow_night_blue.conf +117 -0
  82. novelwriter/assets/themes/tomorrow_night_bright.conf +117 -0
  83. novelwriter/assets/themes/tomorrow_night_eighties.conf +117 -0
  84. novelwriter/assets/themes/vivid_black_green.conf +97 -0
  85. novelwriter/assets/themes/vivid_black_red.conf +97 -0
  86. novelwriter/assets/themes/vivid_white_green.conf +97 -0
  87. novelwriter/assets/themes/vivid_white_red.conf +97 -0
  88. novelwriter/assets/themes/warpgate.conf +96 -0
  89. novelwriter/assets/themes/waterlily_dark.conf +95 -0
  90. novelwriter/assets/themes/waterlily_light.conf +95 -0
  91. novelwriter/common.py +47 -17
  92. novelwriter/config.py +57 -62
  93. novelwriter/constants.py +32 -6
  94. novelwriter/core/buildsettings.py +3 -23
  95. novelwriter/core/coretools.py +21 -25
  96. novelwriter/core/docbuild.py +4 -9
  97. novelwriter/core/document.py +2 -6
  98. novelwriter/core/index.py +33 -53
  99. novelwriter/core/indexdata.py +17 -22
  100. novelwriter/core/item.py +11 -35
  101. novelwriter/core/itemmodel.py +5 -21
  102. novelwriter/core/novelmodel.py +3 -7
  103. novelwriter/core/options.py +3 -4
  104. novelwriter/core/project.py +31 -21
  105. novelwriter/core/projectdata.py +2 -21
  106. novelwriter/core/projectxml.py +13 -21
  107. novelwriter/core/sessions.py +2 -4
  108. novelwriter/core/spellcheck.py +12 -13
  109. novelwriter/core/status.py +27 -20
  110. novelwriter/core/storage.py +5 -10
  111. novelwriter/core/tree.py +6 -15
  112. novelwriter/dialogs/about.py +9 -10
  113. novelwriter/dialogs/docmerge.py +17 -14
  114. novelwriter/dialogs/docsplit.py +18 -14
  115. novelwriter/dialogs/editlabel.py +15 -9
  116. novelwriter/dialogs/preferences.py +69 -68
  117. novelwriter/dialogs/projectsettings.py +88 -67
  118. novelwriter/dialogs/quotes.py +15 -10
  119. novelwriter/dialogs/wordlist.py +18 -21
  120. novelwriter/enum.py +75 -30
  121. novelwriter/error.py +6 -11
  122. novelwriter/extensions/configlayout.py +8 -34
  123. novelwriter/extensions/eventfilters.py +3 -3
  124. novelwriter/extensions/modified.py +87 -32
  125. novelwriter/extensions/novelselector.py +13 -12
  126. novelwriter/extensions/pagedsidebar.py +10 -18
  127. novelwriter/extensions/progressbars.py +5 -11
  128. novelwriter/extensions/statusled.py +3 -6
  129. novelwriter/extensions/switch.py +8 -11
  130. novelwriter/extensions/switchbox.py +2 -11
  131. novelwriter/extensions/versioninfo.py +6 -7
  132. novelwriter/formats/shared.py +10 -2
  133. novelwriter/formats/todocx.py +15 -37
  134. novelwriter/formats/tohtml.py +52 -61
  135. novelwriter/formats/tokenizer.py +33 -64
  136. novelwriter/formats/tomarkdown.py +4 -11
  137. novelwriter/formats/toodt.py +12 -71
  138. novelwriter/formats/toqdoc.py +11 -21
  139. novelwriter/formats/toraw.py +2 -6
  140. novelwriter/gui/doceditor.py +207 -245
  141. novelwriter/gui/dochighlight.py +142 -101
  142. novelwriter/gui/docviewer.py +53 -84
  143. novelwriter/gui/docviewerpanel.py +18 -41
  144. novelwriter/gui/editordocument.py +12 -17
  145. novelwriter/gui/itemdetails.py +5 -14
  146. novelwriter/gui/mainmenu.py +24 -32
  147. novelwriter/gui/noveltree.py +13 -51
  148. novelwriter/gui/outline.py +20 -61
  149. novelwriter/gui/projtree.py +40 -96
  150. novelwriter/gui/search.py +9 -24
  151. novelwriter/gui/sidebar.py +54 -22
  152. novelwriter/gui/statusbar.py +7 -22
  153. novelwriter/gui/theme.py +482 -368
  154. novelwriter/guimain.py +87 -101
  155. novelwriter/shared.py +79 -48
  156. novelwriter/splash.py +9 -5
  157. novelwriter/text/comments.py +1 -1
  158. novelwriter/text/counting.py +9 -5
  159. novelwriter/text/patterns.py +20 -15
  160. novelwriter/tools/dictionaries.py +18 -16
  161. novelwriter/tools/lipsum.py +15 -17
  162. novelwriter/tools/manusbuild.py +25 -45
  163. novelwriter/tools/manuscript.py +94 -95
  164. novelwriter/tools/manussettings.py +149 -104
  165. novelwriter/tools/noveldetails.py +10 -24
  166. novelwriter/tools/welcome.py +24 -72
  167. novelwriter/tools/writingstats.py +17 -26
  168. novelwriter/types.py +25 -13
  169. {novelwriter-2.7.4.dist-info → novelwriter-2.8b1.dist-info}/METADATA +7 -7
  170. novelwriter-2.8b1.dist-info/RECORD +212 -0
  171. novelwriter/assets/images/welcome-dark.jpg +0 -0
  172. novelwriter/assets/images/welcome-light.jpg +0 -0
  173. novelwriter/assets/syntax/cyberpunk_night.conf +0 -28
  174. novelwriter/assets/syntax/default_dark.conf +0 -42
  175. novelwriter/assets/syntax/default_light.conf +0 -42
  176. novelwriter/assets/syntax/dracula.conf +0 -44
  177. novelwriter/assets/syntax/grey_dark.conf +0 -29
  178. novelwriter/assets/syntax/grey_light.conf +0 -29
  179. novelwriter/assets/syntax/light_owl.conf +0 -49
  180. novelwriter/assets/syntax/night_owl.conf +0 -49
  181. novelwriter/assets/syntax/snazzy.conf +0 -42
  182. novelwriter/assets/syntax/solarized_dark.conf +0 -29
  183. novelwriter/assets/syntax/solarized_light.conf +0 -29
  184. novelwriter/assets/syntax/tango.conf +0 -39
  185. novelwriter/assets/syntax/tomorrow.conf +0 -49
  186. novelwriter/assets/syntax/tomorrow_night.conf +0 -49
  187. novelwriter/assets/syntax/tomorrow_night_blue.conf +0 -49
  188. novelwriter/assets/syntax/tomorrow_night_bright.conf +0 -49
  189. novelwriter/assets/syntax/tomorrow_night_eighties.conf +0 -49
  190. novelwriter/assets/themes/default.conf +0 -3
  191. novelwriter-2.7.4.dist-info/RECORD +0 -163
  192. {novelwriter-2.7.4.dist-info → novelwriter-2.8b1.dist-info}/WHEEL +0 -0
  193. {novelwriter-2.7.4.dist-info → novelwriter-2.8b1.dist-info}/entry_points.txt +0 -0
  194. {novelwriter-2.7.4.dist-info → novelwriter-2.8b1.dist-info}/licenses/LICENSE.md +0 -0
  195. {novelwriter-2.7.4.dist-info → novelwriter-2.8b1.dist-info}/licenses/setup/LICENSE-Apache-2.0.txt +0 -0
  196. {novelwriter-2.7.4.dist-info → novelwriter-2.8b1.dist-info}/top_level.txt +0 -0
@@ -20,7 +20,7 @@ General Public License for more details.
20
20
 
21
21
  You should have received a copy of the GNU General Public License
22
22
  along with this program. If not, see <https://www.gnu.org/licenses/>.
23
- """
23
+ """ # noqa
24
24
  from __future__ import annotations
25
25
 
26
26
  import logging
@@ -41,7 +41,7 @@ logger = logging.getLogger(__name__)
41
41
 
42
42
 
43
43
  class NWProjectData:
44
- """Core: Project Data Class
44
+ """Core: Project Data Class.
45
45
 
46
46
  The class holds all project data from the main XML file, aside from
47
47
  the list of project items.
@@ -86,8 +86,6 @@ class NWProjectData:
86
86
  self._status = NWStatus(NWStatus.STATUS)
87
87
  self._import = NWStatus(NWStatus.IMPORT)
88
88
 
89
- return
90
-
91
89
  ##
92
90
  # Properties
93
91
  ##
@@ -191,13 +189,11 @@ class NWProjectData:
191
189
  """Increment the save count by one."""
192
190
  self._saveCount += 1
193
191
  self._project.setProjectChanged(True)
194
- return
195
192
 
196
193
  def incAutoCount(self) -> None:
197
194
  """Increment the auto save count by one."""
198
195
  self._autoCount += 1
199
196
  self._project.setProjectChanged(True)
200
- return
201
197
 
202
198
  ##
203
199
  # Getters
@@ -219,67 +215,57 @@ class NWProjectData:
219
215
  elif value != self._uuid:
220
216
  self._uuid = value
221
217
  self._project.setProjectChanged(True)
222
- return
223
218
 
224
219
  def setName(self, value: str | None) -> None:
225
220
  """Set a new project name."""
226
221
  if value != self._name:
227
222
  self._name = simplified(str(value or ""))
228
223
  self._project.setProjectChanged(True)
229
- return
230
224
 
231
225
  def setAuthor(self, value: str | None) -> None:
232
226
  """Set the author value."""
233
227
  if value != self._author:
234
228
  self._author = simplified(str(value or ""))
235
229
  self._project.setProjectChanged(True)
236
- return
237
230
 
238
231
  def setSaveCount(self, value: Any) -> None:
239
232
  """Set the save count from last session."""
240
233
  self._saveCount = checkInt(value, 0)
241
234
  self._project.setProjectChanged(True)
242
- return
243
235
 
244
236
  def setAutoCount(self, value: Any) -> None:
245
237
  """Set the auto save count from last session."""
246
238
  self._autoCount = checkInt(value, 0)
247
239
  self._project.setProjectChanged(True)
248
- return
249
240
 
250
241
  def setEditTime(self, value: Any) -> None:
251
242
  """Set the edit time from last session."""
252
243
  self._editTime = checkInt(value, 0)
253
244
  self._project.setProjectChanged(True)
254
- return
255
245
 
256
246
  def setDoBackup(self, value: Any) -> None:
257
247
  """Set the do write backup flag."""
258
248
  if value != self._doBackup:
259
249
  self._doBackup = checkBool(value, False)
260
250
  self._project.setProjectChanged(True)
261
- return
262
251
 
263
252
  def setLanguage(self, value: str | None) -> None:
264
253
  """Set the project language."""
265
254
  if value != self._language:
266
255
  self._language = checkStringNone(value, None)
267
256
  self._project.setProjectChanged(True)
268
- return
269
257
 
270
258
  def setSpellCheck(self, value: Any) -> None:
271
259
  """Set the spell check flag."""
272
260
  if value != self._spellCheck:
273
261
  self._spellCheck = checkBool(value, False)
274
262
  self._project.setProjectChanged(True)
275
- return
276
263
 
277
264
  def setSpellLang(self, value: str | None) -> None:
278
265
  """Set the spell check language."""
279
266
  if value != self._spellLang:
280
267
  self._spellLang = checkStringNone(value, None)
281
268
  self._project.setProjectChanged(True)
282
- return
283
269
 
284
270
  def setLastHandle(self, value: str | None, component: str) -> None:
285
271
  """Set a last used handle into the handle registry for a given
@@ -288,7 +274,6 @@ class NWProjectData:
288
274
  if isinstance(component, str):
289
275
  self._lastHandle[component] = checkStringNone(value, None)
290
276
  self._project.setProjectChanged(True)
291
- return
292
277
 
293
278
  def setLastHandles(self, value: dict) -> None:
294
279
  """Set the full last handles dictionary to a new set of values.
@@ -299,7 +284,6 @@ class NWProjectData:
299
284
  if key in self._lastHandle:
300
285
  self._lastHandle[key] = str(entry) if isHandle(entry) else None
301
286
  self._project.setProjectChanged(True)
302
- return
303
287
 
304
288
  def setInitCounts(
305
289
  self, wNovel: Any = None, wNotes: Any = None, cNovel: Any = None, cNotes: Any = None
@@ -321,7 +305,6 @@ class NWProjectData:
321
305
  count = checkInt(cNotes, 0)
322
306
  self._initCounts[3] = count
323
307
  self._currCounts[3] = count
324
- return
325
308
 
326
309
  def setCurrCounts(
327
310
  self, wNovel: Any = None, wNotes: Any = None, cNovel: Any = None, cNotes: Any = None
@@ -335,7 +318,6 @@ class NWProjectData:
335
318
  self._currCounts[2] = checkInt(cNovel, 0)
336
319
  if cNotes is not None:
337
320
  self._currCounts[3] = checkInt(cNotes, 0)
338
- return
339
321
 
340
322
  def setAutoReplace(self, value: dict) -> None:
341
323
  """Set the auto-replace dictionary."""
@@ -345,4 +327,3 @@ class NWProjectData:
345
327
  if isinstance(entry, str):
346
328
  self._autoReplace[key] = simplified(entry)
347
329
  self._project.setProjectChanged(True)
348
- return
@@ -22,7 +22,7 @@ General Public License for more details.
22
22
 
23
23
  You should have received a copy of the GNU General Public License
24
24
  along with this program. If not, see <https://www.gnu.org/licenses/>.
25
- """
25
+ """ # noqa
26
26
  from __future__ import annotations
27
27
 
28
28
  import logging
@@ -46,7 +46,7 @@ if TYPE_CHECKING:
46
46
  logger = logging.getLogger(__name__)
47
47
 
48
48
  FILE_VERSION = "1.5" # The current project file format version
49
- FILE_REVISION = "5" # The current project file format revision
49
+ FILE_REVISION = "6" # The current project file format revision
50
50
  HEX_VERSION = 0x0105
51
51
 
52
52
  NUM_VERSION = {
@@ -72,7 +72,7 @@ class XMLReadState(Enum):
72
72
 
73
73
 
74
74
  class ProjectXMLReader:
75
- """Core: Project XML Reader
75
+ """Core: Project XML Reader.
76
76
 
77
77
  All data is read into a NWProjectData instance, which must be
78
78
  provided.
@@ -111,6 +111,8 @@ class ProjectXMLReader:
111
111
  nodes. 2.5.
112
112
  Rev 5: Added novelChars and notesChars attributes to content
113
113
  node. 2.7 RC 1.
114
+ Rev 6: Replaced red, green and blue attributes with a single
115
+ color attribute. 2.8 Beta 1.
114
116
  """
115
117
 
116
118
  def __init__(self, path: str | Path) -> None:
@@ -122,7 +124,6 @@ class ProjectXMLReader:
122
124
  self._appVersion = ""
123
125
  self._hexVersion = 0x0
124
126
  self._timeStamp = ""
125
- return
126
127
 
127
128
  ##
128
129
  # Properties
@@ -252,8 +253,6 @@ class ProjectXMLReader:
252
253
  elif xItem.tag == "editTime": # Moved to attribute in 1.5
253
254
  data.setEditTime(xItem.text)
254
255
 
255
- return
256
-
257
256
  def _parseProjectSettings(self, xSection: ET.Element, data: NWProjectData) -> None:
258
257
  """Parse the settings section of the XML file."""
259
258
  logger.debug("Parsing <settings> section")
@@ -292,8 +291,6 @@ class ProjectXMLReader:
292
291
  elif xItem.tag == "notesWordCount": # Moved to content attribute in 1.5
293
292
  data.setInitCounts(wNotes=xItem.text)
294
293
 
295
- return
296
-
297
294
  def _parseProjectContent(
298
295
  self, xSection: ET.Element, data: NWProjectData, content: list
299
296
  ) -> None:
@@ -354,8 +351,6 @@ class ProjectXMLReader:
354
351
  "nameAttr": name,
355
352
  })
356
353
 
357
- return
358
-
359
354
  def _parseProjectContentLegacy(
360
355
  self, xSection: ET.Element, data: NWProjectData, content: list
361
356
  ) -> None:
@@ -432,20 +427,20 @@ class ProjectXMLReader:
432
427
  "nameAttr": name,
433
428
  })
434
429
 
435
- return
436
-
437
430
  def _parseStatusImport(self, xItem: ET.Element, sObject: NWStatus) -> None:
438
431
  """Parse a status or importance entry."""
439
432
  for xEntry in xItem:
440
433
  if xEntry.tag == "entry":
441
434
  key = xEntry.attrib.get("key", None)
442
- red = checkInt(xEntry.attrib.get("red", 0), 0)
443
- green = checkInt(xEntry.attrib.get("green", 0), 0)
444
- blue = checkInt(xEntry.attrib.get("blue", 0), 0)
435
+ red = checkInt(xEntry.attrib.get("red", 0), 0) # Removed in 1.5 R6
436
+ green = checkInt(xEntry.attrib.get("green", 0), 0) # Removed in 1.5 R6
437
+ blue = checkInt(xEntry.attrib.get("blue", 0), 0) # Removed in 1.5 R6
438
+ color = xEntry.attrib.get("color") # Added in 1.5 R6
445
439
  count = checkInt(xEntry.attrib.get("count", 0), 0)
446
440
  shape = xEntry.attrib.get("shape", "")
447
- sObject.add(key, xEntry.text or "", (red, green, blue), shape, count)
448
- return
441
+ if color is None:
442
+ color = f"{red}, {green}, {blue}"
443
+ sObject.add(key, xEntry.text or "", color, shape, count)
449
444
 
450
445
  def _parseDictKeyText(self, xItem: ET.Element) -> dict:
451
446
  """Parse a dictionary stored with key as an attribute and the
@@ -465,7 +460,7 @@ class ProjectXMLReader:
465
460
 
466
461
 
467
462
  class ProjectXMLWriter:
468
- """Core: Project XML Writer
463
+ """Core: Project XML Writer.
469
464
 
470
465
  The project writer class will only write a file according to the
471
466
  very latest spec.
@@ -474,7 +469,6 @@ class ProjectXMLWriter:
474
469
  def __init__(self, path: str | Path) -> None:
475
470
  self._path = Path(path)
476
471
  self._error = None
477
- return
478
472
 
479
473
  ##
480
474
  # Properties
@@ -575,7 +569,6 @@ class ProjectXMLWriter:
575
569
  """Pack a single value into an XML element."""
576
570
  xItem = ET.SubElement(xParent, name, attrib=attrib or {})
577
571
  xItem.text = str(value) or ""
578
- return
579
572
 
580
573
  def _packDictKeyValue(self, xParent: ET.Element, name: str, data: dict) -> None:
581
574
  """Pack the entries of a dictionary into an XML element."""
@@ -584,4 +577,3 @@ class ProjectXMLWriter:
584
577
  if len(key) > 0:
585
578
  xEntry = ET.SubElement(xItem, "entry", attrib={"key": key})
586
579
  xEntry.text = str(value) or ""
587
- return
@@ -20,7 +20,7 @@ General Public License for more details.
20
20
 
21
21
  You should have received a copy of the GNU General Public License
22
22
  along with this program. If not, see <https://www.gnu.org/licenses/>.
23
- """
23
+ """ # noqa
24
24
  from __future__ import annotations
25
25
 
26
26
  import json
@@ -43,7 +43,7 @@ logger = logging.getLogger(__name__)
43
43
 
44
44
 
45
45
  class NWSessionLog:
46
- """Core: Session JSON Lines Log File
46
+ """Core: Session JSON Lines Log File.
47
47
 
48
48
  The class that wraps the session log file, which is in JSON Lines
49
49
  format. That is, one JSON object per line.
@@ -52,7 +52,6 @@ class NWSessionLog:
52
52
  def __init__(self, project: NWProject) -> None:
53
53
  self._project = project
54
54
  self._start = 0.0
55
- return
56
55
 
57
56
  ##
58
57
  # Properties
@@ -70,7 +69,6 @@ class NWSessionLog:
70
69
  def startSession(self) -> None:
71
70
  """Start the writing session."""
72
71
  self._start = time()
73
- return
74
72
 
75
73
  def appendSession(self, idleTime: float) -> bool:
76
74
  """Append session statistics to the sessions log file."""
@@ -21,7 +21,7 @@ General Public License for more details.
21
21
 
22
22
  You should have received a copy of the GNU General Public License
23
23
  along with this program. If not, see <https://www.gnu.org/licenses/>.
24
- """
24
+ """ # noqa
25
25
  from __future__ import annotations
26
26
 
27
27
  import json
@@ -44,7 +44,7 @@ logger = logging.getLogger(__name__)
44
44
 
45
45
 
46
46
  class NWSpellEnchant:
47
- """Core: Enchant Spell Checking Wrapper
47
+ """Core: Enchant Spell Checking Wrapper.
48
48
 
49
49
  This is a rapper class for Enchant to keep the API consistent
50
50
  between spell check tools.
@@ -57,11 +57,9 @@ class NWSpellEnchant:
57
57
  self._language = None
58
58
  self._broker = None
59
59
  logger.debug("Ready: NWSpellEnchant")
60
- return
61
60
 
62
61
  def __del__(self) -> None: # pragma: no cover
63
62
  logger.debug("Delete: NWSpellEnchant")
64
- return
65
63
 
66
64
  ##
67
65
  # Properties
@@ -106,21 +104,19 @@ class NWSpellEnchant:
106
104
  for word in self._userDict:
107
105
  self._enchant.add_to_session(word)
108
106
 
109
- return
110
-
111
107
  ##
112
108
  # Methods
113
109
  ##
114
110
 
115
111
  def checkWord(self, word: str) -> bool:
116
- """Wrapper function for pyenchant."""
112
+ """Forward check to pyenchant."""
117
113
  try:
118
114
  return bool(self._enchant.check(word))
119
115
  except Exception:
120
116
  return True
121
117
 
122
118
  def suggestWords(self, word: str) -> list[str]:
123
- """Wrapper function for pyenchant."""
119
+ """Ask pyenchant for suggestions."""
124
120
  try:
125
121
  return self._enchant.suggest(word)
126
122
  except Exception:
@@ -172,24 +168,29 @@ class FakeEnchant:
172
168
  self.tag = ""
173
169
  self.provider = FakeProvider()
174
170
 
175
- return
176
-
177
171
  def check(self, word: str) -> bool:
172
+ """Return True for all words."""
178
173
  return True
179
174
 
180
175
  def suggest(self, word: str) -> list[str]:
176
+ """Return an empty suggestion list."""
181
177
  return []
182
178
 
183
179
  def add_to_session(self, word: str) -> None:
180
+ """Do nothing."""
184
181
  return
185
182
 
186
183
 
187
184
  class UserDictionary:
185
+ """Core: User Word Dictionary.
186
+
187
+ This class holds all the user's own words for spell checking
188
+ purposes. The dictionary is per-project.
189
+ """
188
190
 
189
191
  def __init__(self, project: NWProject) -> None:
190
192
  self._project = project
191
193
  self._words = set()
192
- return
193
194
 
194
195
  def __contains__(self, word: str) -> bool:
195
196
  return word in self._words
@@ -219,7 +220,6 @@ class UserDictionary:
219
220
  except Exception:
220
221
  logger.error("Failed to load user dictionary")
221
222
  logException()
222
- return
223
223
 
224
224
  def save(self) -> None:
225
225
  """Save the user's dictionary."""
@@ -232,4 +232,3 @@ class UserDictionary:
232
232
  except Exception:
233
233
  logger.error("Failed to save user dictionary")
234
234
  logException()
235
- return
@@ -21,7 +21,7 @@ General Public License for more details.
21
21
 
22
22
  You should have received a copy of the GNU General Public License
23
23
  along with this program. If not, see <https://www.gnu.org/licenses/>.
24
- """
24
+ """ # noqa
25
25
  from __future__ import annotations
26
26
 
27
27
  import dataclasses
@@ -35,6 +35,7 @@ from PyQt6.QtGui import QColor, QIcon, QPainter, QPainterPath, QPixmap, QPolygon
35
35
 
36
36
  from novelwriter import SHARED
37
37
  from novelwriter.common import simplified
38
+ from novelwriter.constants import nwLabels
38
39
  from novelwriter.enum import nwStatusShape
39
40
  from novelwriter.types import QtPaintAntiAlias, QtTransparent
40
41
 
@@ -43,12 +44,16 @@ if TYPE_CHECKING:
43
44
 
44
45
  logger = logging.getLogger(__name__)
45
46
 
47
+ CUSTOM_COL = "custom"
48
+
46
49
 
47
50
  @dataclasses.dataclass
48
51
  class StatusEntry:
52
+ """DataClass: Status Label Values."""
49
53
 
50
54
  name: str
51
55
  color: QColor
56
+ theme: str
52
57
  shape: nwStatusShape
53
58
  icon: QIcon
54
59
  count: int = 0
@@ -62,13 +67,14 @@ class StatusEntry:
62
67
  return status
63
68
 
64
69
 
65
- NO_ENTRY = StatusEntry("", QColor(0, 0, 0), nwStatusShape.SQUARE, QIcon(), 0)
70
+ NO_ENTRY = StatusEntry("", QColor(0, 0, 0), CUSTOM_COL, nwStatusShape.SQUARE, QIcon(), 0)
66
71
 
67
72
  T_UpdateEntry = list[tuple[str | None, StatusEntry]]
68
73
  T_StatusKind = Literal["s", "i"]
69
74
 
70
75
 
71
76
  class NWStatus:
77
+ """Core: Status/Importance Label Class."""
72
78
 
73
79
  STATUS = "s"
74
80
  IMPORT = "i"
@@ -80,7 +86,6 @@ class NWStatus:
80
86
  self._default = None
81
87
  self._prefix = prefix[:1]
82
88
  self._height = SHARED.theme.baseIconHeight
83
- return
84
89
 
85
90
  def __len__(self) -> int:
86
91
  return len(self._store)
@@ -97,15 +102,12 @@ class NWStatus:
97
102
  # Methods
98
103
  ##
99
104
 
100
- def add(self, key: str | None, name: str, color: tuple[int, int, int],
101
- shape: str, count: int) -> str:
105
+ def add(self, key: str | None, name: str, color: str, shape: str, count: int) -> str:
102
106
  """Add or update a status entry. If the key is invalid, a new
103
107
  key is generated.
104
108
  """
105
- if isinstance(color, tuple) and len(color) == 3:
106
- qColor = QColor(*color)
107
- else:
108
- qColor = QColor(100, 100, 100)
109
+ qColor = SHARED.theme.parseColor(color)
110
+ theme = color if color in nwLabels.THEME_COLORS else CUSTOM_COL
109
111
 
110
112
  try:
111
113
  iShape = nwStatusShape[shape]
@@ -115,7 +117,7 @@ class NWStatus:
115
117
  key = self._checkKey(key)
116
118
  name = simplified(name)
117
119
  icon = self.createIcon(self._height, qColor, iShape)
118
- self._store[key] = StatusEntry(name, qColor, iShape, icon, count)
120
+ self._store[key] = StatusEntry(name, qColor, theme, iShape, icon, count)
119
121
 
120
122
  if self._default is None:
121
123
  self._default = key
@@ -132,8 +134,6 @@ class NWStatus:
132
134
  if self._default not in self._store:
133
135
  self._default = next(iter(self._store)) if self._store else None
134
136
 
135
- return
136
-
137
137
  def check(self, value: str) -> str:
138
138
  """Check the key against the stored status names."""
139
139
  if self._isKey(value) and value in self._store:
@@ -146,23 +146,23 @@ class NWStatus:
146
146
  """Clear the counts of references to the status entries."""
147
147
  for entry in self._store.values():
148
148
  entry.count = 0
149
- return
150
149
 
151
150
  def increment(self, key: str | None) -> None:
152
151
  """Increment the counter for a given entry."""
153
152
  if key and key in self._store:
154
153
  self._store[key].count += 1
155
- return
156
154
 
157
155
  def pack(self) -> Iterable[tuple[str, dict]]:
158
156
  """Pack the status entries into a dictionary."""
159
157
  for key, entry in self._store.items():
158
+ if entry.theme == CUSTOM_COL:
159
+ color = entry.color.name(QColor.NameFormat.HexRgb)
160
+ else:
161
+ color = entry.theme
160
162
  yield (entry.name, {
161
163
  "key": key,
162
164
  "count": str(entry.count),
163
- "red": str(entry.color.red()),
164
- "green": str(entry.color.green()),
165
- "blue": str(entry.color.blue()),
165
+ "color": color,
166
166
  "shape": entry.shape.name,
167
167
  })
168
168
  return
@@ -179,12 +179,20 @@ class NWStatus:
179
179
  try:
180
180
  shape = nwStatusShape[str(data[0])]
181
181
  color = QColor(str(data[1]))
182
+ theme = CUSTOM_COL if data[1].startswith("#") else data[1]
182
183
  icon = NWStatus.createIcon(self._height, color, shape)
183
- return StatusEntry(simplified(data[2]), color, shape, icon)
184
+ return StatusEntry(simplified(data[2]), color, theme, shape, icon)
184
185
  except Exception:
185
- logger.error("Could not parse entry %s", str(data))
186
+ logger.error("Could not parse entry %s", data)
186
187
  return None
187
188
 
189
+ def refreshIcons(self) -> None:
190
+ """Refresh all icons."""
191
+ for entry in self._store.values():
192
+ if entry.theme != CUSTOM_COL:
193
+ entry.color = SHARED.theme.parseColor(entry.theme)
194
+ entry.icon = NWStatus.createIcon(self._height, entry.color, entry.shape)
195
+
188
196
  @staticmethod
189
197
  def createIcon(height: int, color: QColor, shape: nwStatusShape) -> QIcon:
190
198
  """Generate an icon for a status label."""
@@ -240,7 +248,6 @@ class _ShapeCache:
240
248
 
241
249
  def __init__(self) -> None:
242
250
  self._cache: dict[nwStatusShape, QPainterPath] = {}
243
- return
244
251
 
245
252
  def getShape(self, shape: nwStatusShape) -> QPainterPath:
246
253
  """Return a painter shape for an icon."""
@@ -20,7 +20,7 @@ General Public License for more details.
20
20
 
21
21
  You should have received a copy of the GNU General Public License
22
22
  along with this program. If not, see <https://www.gnu.org/licenses/>.
23
- """
23
+ """ # noqa
24
24
  from __future__ import annotations
25
25
 
26
26
  import json
@@ -47,6 +47,7 @@ logger = logging.getLogger(__name__)
47
47
 
48
48
 
49
49
  class NWStorageOpen(Enum):
50
+ """The status of a storage location."""
50
51
 
51
52
  UNKOWN = 0
52
53
  NOT_FOUND = 1
@@ -56,6 +57,7 @@ class NWStorageOpen(Enum):
56
57
 
57
58
 
58
59
  class NWStorageCreate(Enum):
60
+ """The status of a new storage location."""
59
61
 
60
62
  NOT_EMPTY = 0
61
63
  OS_ERROR = 1
@@ -63,7 +65,7 @@ class NWStorageCreate(Enum):
63
65
 
64
66
 
65
67
  class NWStorage:
66
- """Core: Project Storage Class
68
+ """Core: Project Storage Class.
67
69
 
68
70
  The class that handles all paths related to the project storage.
69
71
  """
@@ -81,7 +83,6 @@ class NWStorage:
81
83
  self._openMode = self.MODE_INACTIVE
82
84
  self._ready = False
83
85
  self._exception = None
84
- return
85
86
 
86
87
  def clear(self) -> None:
87
88
  """Reset internal variables."""
@@ -90,7 +91,6 @@ class NWStorage:
90
91
  self._lockFilePath = None
91
92
  self._openMode = self.MODE_INACTIVE
92
93
  self._ready = False
93
- return
94
94
 
95
95
  ##
96
96
  # Properties
@@ -252,13 +252,11 @@ class NWStorage:
252
252
  """Lock the session when the project is successfully opened."""
253
253
  if self._ready:
254
254
  self._writeLockFile()
255
- return
256
255
 
257
256
  def closeSession(self) -> None:
258
257
  """Run tasks related to closing the session."""
259
258
  self._clearLockFile()
260
259
  self.clear()
261
- return
262
260
 
263
261
  ##
264
262
  # Content Access Methods
@@ -394,7 +392,7 @@ class NWStorage:
394
392
 
395
393
 
396
394
  class _LegacyStorage:
397
- """Core: Legacy Storage Converter Utils
395
+ """Core: Legacy Storage Converter Utils.
398
396
 
399
397
  A class with various functions to convert old file formats and
400
398
  file/folder layouts to the current project format.
@@ -402,7 +400,6 @@ class _LegacyStorage:
402
400
 
403
401
  def __init__(self, project: NWProject) -> None:
404
402
  self._project = project
405
- return
406
403
 
407
404
  def legacyDataFolder(self, path: Path, child: Path) -> None:
408
405
  """Handle the content of a legacy data folder from a version 1.0
@@ -484,8 +481,6 @@ class _LegacyStorage:
484
481
  except Exception as exc:
485
482
  logger.warning("Failed to delete: %s", item, exc_info=exc)
486
483
 
487
- return
488
-
489
484
  ##
490
485
  # Internal Functions
491
486
  ##