novelWriter 2.6.3__py3-none-any.whl → 2.7rc1__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 (325) hide show
  1. novelwriter/__init__.py +98 -75
  2. novelwriter/assets/i18n/project_en_GB.json +2 -0
  3. novelwriter/assets/icons/font_awesome.icons +109 -0
  4. novelwriter/assets/icons/material_filled_bold.icons +109 -0
  5. novelwriter/assets/icons/material_filled_normal.icons +109 -0
  6. novelwriter/assets/icons/material_filled_thin.icons +109 -0
  7. novelwriter/assets/icons/material_rounded_bold.icons +109 -0
  8. novelwriter/assets/icons/material_rounded_normal.icons +109 -0
  9. novelwriter/assets/icons/material_rounded_thin.icons +109 -0
  10. novelwriter/assets/icons/remix_filled.icons +109 -0
  11. novelwriter/assets/icons/remix_outline.icons +109 -0
  12. novelwriter/assets/images/splash.png +0 -0
  13. novelwriter/assets/manual.pdf +0 -0
  14. novelwriter/assets/{manual_fr_FR.pdf → manual_fr.pdf} +0 -0
  15. novelwriter/assets/sample.zip +0 -0
  16. novelwriter/assets/syntax/cyberpunk_night.conf +1 -1
  17. novelwriter/assets/syntax/snazzy.conf +3 -3
  18. novelwriter/assets/text/credits_en.htm +6 -6
  19. novelwriter/assets/themes/cyberpunk_night.conf +23 -7
  20. novelwriter/assets/themes/default_dark.conf +20 -4
  21. novelwriter/assets/themes/default_light.conf +21 -5
  22. novelwriter/assets/themes/dracula.conf +20 -4
  23. novelwriter/assets/themes/snazzy.conf +48 -0
  24. novelwriter/assets/themes/solarized_dark.conf +24 -8
  25. novelwriter/assets/themes/solarized_light.conf +22 -6
  26. novelwriter/common.py +43 -27
  27. novelwriter/config.py +201 -139
  28. novelwriter/constants.py +67 -36
  29. novelwriter/core/buildsettings.py +25 -16
  30. novelwriter/core/coretools.py +52 -41
  31. novelwriter/core/docbuild.py +20 -13
  32. novelwriter/core/document.py +2 -2
  33. novelwriter/core/index.py +166 -432
  34. novelwriter/core/indexdata.py +406 -0
  35. novelwriter/core/item.py +50 -32
  36. novelwriter/core/itemmodel.py +43 -38
  37. novelwriter/core/novelmodel.py +225 -0
  38. novelwriter/core/options.py +1 -1
  39. novelwriter/core/project.py +24 -25
  40. novelwriter/core/projectdata.py +47 -29
  41. novelwriter/core/projectxml.py +18 -8
  42. novelwriter/core/sessions.py +32 -15
  43. novelwriter/core/spellcheck.py +4 -3
  44. novelwriter/core/status.py +12 -15
  45. novelwriter/core/storage.py +1 -1
  46. novelwriter/core/tree.py +55 -13
  47. novelwriter/dialogs/about.py +19 -22
  48. novelwriter/dialogs/docmerge.py +23 -24
  49. novelwriter/dialogs/docsplit.py +26 -26
  50. novelwriter/dialogs/editlabel.py +19 -20
  51. novelwriter/dialogs/preferences.py +143 -69
  52. novelwriter/dialogs/projectsettings.py +51 -54
  53. novelwriter/dialogs/quotes.py +14 -19
  54. novelwriter/dialogs/wordlist.py +25 -30
  55. novelwriter/enum.py +8 -0
  56. novelwriter/error.py +16 -16
  57. novelwriter/extensions/configlayout.py +24 -20
  58. novelwriter/extensions/eventfilters.py +9 -5
  59. novelwriter/extensions/modified.py +34 -15
  60. novelwriter/extensions/novelselector.py +18 -5
  61. novelwriter/extensions/pagedsidebar.py +39 -49
  62. novelwriter/extensions/progressbars.py +10 -8
  63. novelwriter/extensions/statusled.py +6 -13
  64. novelwriter/extensions/switch.py +31 -41
  65. novelwriter/extensions/switchbox.py +8 -3
  66. novelwriter/extensions/versioninfo.py +4 -4
  67. novelwriter/formats/shared.py +1 -1
  68. novelwriter/formats/todocx.py +14 -10
  69. novelwriter/formats/tohtml.py +7 -5
  70. novelwriter/formats/tokenizer.py +37 -33
  71. novelwriter/formats/tomarkdown.py +6 -2
  72. novelwriter/formats/toodt.py +27 -22
  73. novelwriter/formats/toqdoc.py +19 -14
  74. novelwriter/formats/toraw.py +6 -2
  75. novelwriter/gui/doceditor.py +314 -305
  76. novelwriter/gui/dochighlight.py +46 -45
  77. novelwriter/gui/docviewer.py +102 -104
  78. novelwriter/gui/docviewerpanel.py +47 -51
  79. novelwriter/gui/editordocument.py +8 -5
  80. novelwriter/gui/itemdetails.py +11 -14
  81. novelwriter/gui/mainmenu.py +146 -145
  82. novelwriter/gui/noveltree.py +246 -406
  83. novelwriter/gui/outline.py +141 -72
  84. novelwriter/gui/projtree.py +139 -132
  85. novelwriter/gui/search.py +34 -31
  86. novelwriter/gui/sidebar.py +13 -18
  87. novelwriter/gui/statusbar.py +27 -21
  88. novelwriter/gui/theme.py +554 -436
  89. novelwriter/guimain.py +56 -38
  90. novelwriter/shared.py +32 -23
  91. novelwriter/splash.py +74 -0
  92. novelwriter/text/comments.py +70 -0
  93. novelwriter/text/patterns.py +4 -4
  94. novelwriter/tools/dictionaries.py +20 -29
  95. novelwriter/tools/lipsum.py +18 -18
  96. novelwriter/tools/manusbuild.py +39 -42
  97. novelwriter/tools/manuscript.py +102 -115
  98. novelwriter/tools/manussettings.py +116 -96
  99. novelwriter/tools/noveldetails.py +60 -72
  100. novelwriter/tools/welcome.py +57 -75
  101. novelwriter/tools/writingstats.py +112 -102
  102. novelwriter/types.py +5 -7
  103. {novelWriter-2.6.3.dist-info → novelwriter-2.7rc1.dist-info}/METADATA +6 -6
  104. novelwriter-2.7rc1.dist-info/RECORD +162 -0
  105. {novelWriter-2.6.3.dist-info → novelwriter-2.7rc1.dist-info}/WHEEL +1 -1
  106. novelWriter-2.6.3.dist-info/RECORD +0 -363
  107. novelwriter/assets/icons/typicons_dark/README.md +0 -29
  108. novelwriter/assets/icons/typicons_dark/icons.conf +0 -134
  109. novelwriter/assets/icons/typicons_dark/mixed_copy.svg +0 -4
  110. novelwriter/assets/icons/typicons_dark/mixed_document-chapter.svg +0 -12
  111. novelwriter/assets/icons/typicons_dark/mixed_document-new.svg +0 -6
  112. novelwriter/assets/icons/typicons_dark/mixed_document-note.svg +0 -12
  113. novelwriter/assets/icons/typicons_dark/mixed_document-scene.svg +0 -12
  114. novelwriter/assets/icons/typicons_dark/mixed_document-section.svg +0 -12
  115. novelwriter/assets/icons/typicons_dark/mixed_document-title.svg +0 -12
  116. novelwriter/assets/icons/typicons_dark/mixed_edit.svg +0 -4
  117. novelwriter/assets/icons/typicons_dark/mixed_import.svg +0 -5
  118. novelwriter/assets/icons/typicons_dark/mixed_input-checked.svg +0 -5
  119. novelwriter/assets/icons/typicons_dark/mixed_input-none.svg +0 -5
  120. novelwriter/assets/icons/typicons_dark/mixed_input-unchecked.svg +0 -5
  121. novelwriter/assets/icons/typicons_dark/mixed_margin-bottom.svg +0 -6
  122. novelwriter/assets/icons/typicons_dark/mixed_margin-left.svg +0 -6
  123. novelwriter/assets/icons/typicons_dark/mixed_margin-right.svg +0 -6
  124. novelwriter/assets/icons/typicons_dark/mixed_margin-top.svg +0 -6
  125. novelwriter/assets/icons/typicons_dark/mixed_search-replace.svg +0 -6
  126. novelwriter/assets/icons/typicons_dark/mixed_size-height.svg +0 -6
  127. novelwriter/assets/icons/typicons_dark/mixed_size-width.svg +0 -6
  128. novelwriter/assets/icons/typicons_dark/nw_deco-h0.svg +0 -4
  129. novelwriter/assets/icons/typicons_dark/nw_deco-h1.svg +0 -4
  130. novelwriter/assets/icons/typicons_dark/nw_deco-h2-narrow.svg +0 -4
  131. novelwriter/assets/icons/typicons_dark/nw_deco-h2.svg +0 -4
  132. novelwriter/assets/icons/typicons_dark/nw_deco-h3-narrow.svg +0 -4
  133. novelwriter/assets/icons/typicons_dark/nw_deco-h3.svg +0 -4
  134. novelwriter/assets/icons/typicons_dark/nw_deco-h4-narrow.svg +0 -4
  135. novelwriter/assets/icons/typicons_dark/nw_deco-h4.svg +0 -4
  136. novelwriter/assets/icons/typicons_dark/nw_deco-note.svg +0 -4
  137. novelwriter/assets/icons/typicons_dark/nw_deco-noveltree-more.svg +0 -4
  138. novelwriter/assets/icons/typicons_dark/nw_font.svg +0 -4
  139. novelwriter/assets/icons/typicons_dark/nw_panel.svg +0 -4
  140. novelwriter/assets/icons/typicons_dark/nw_quote.svg +0 -4
  141. novelwriter/assets/icons/typicons_dark/nw_search-case.svg +0 -4
  142. novelwriter/assets/icons/typicons_dark/nw_search-preserve.svg +0 -4
  143. novelwriter/assets/icons/typicons_dark/nw_search-regex.svg +0 -4
  144. novelwriter/assets/icons/typicons_dark/nw_search-word.svg +0 -4
  145. novelwriter/assets/icons/typicons_dark/nw_tb-bold-md.svg +0 -4
  146. novelwriter/assets/icons/typicons_dark/nw_tb-bold.svg +0 -6
  147. novelwriter/assets/icons/typicons_dark/nw_tb-italic-md.svg +0 -4
  148. novelwriter/assets/icons/typicons_dark/nw_tb-italic.svg +0 -6
  149. novelwriter/assets/icons/typicons_dark/nw_tb-mark.svg +0 -7
  150. novelwriter/assets/icons/typicons_dark/nw_tb-strike-md.svg +0 -4
  151. novelwriter/assets/icons/typicons_dark/nw_tb-strike.svg +0 -6
  152. novelwriter/assets/icons/typicons_dark/nw_tb-subscript.svg +0 -7
  153. novelwriter/assets/icons/typicons_dark/nw_tb-superscript.svg +0 -7
  154. novelwriter/assets/icons/typicons_dark/nw_tb-underline.svg +0 -7
  155. novelwriter/assets/icons/typicons_dark/nw_toolbar.svg +0 -5
  156. novelwriter/assets/icons/typicons_dark/typ_arrow-down-thick-grey.svg +0 -4
  157. novelwriter/assets/icons/typicons_dark/typ_arrow-forward.svg +0 -4
  158. novelwriter/assets/icons/typicons_dark/typ_arrow-maximise.svg +0 -4
  159. novelwriter/assets/icons/typicons_dark/typ_arrow-minimise.svg +0 -4
  160. novelwriter/assets/icons/typicons_dark/typ_arrow-repeat-grey.svg +0 -4
  161. novelwriter/assets/icons/typicons_dark/typ_book-grey.svg +0 -4
  162. novelwriter/assets/icons/typicons_dark/typ_book.svg +0 -6
  163. novelwriter/assets/icons/typicons_dark/typ_bookmark.svg +0 -4
  164. novelwriter/assets/icons/typicons_dark/typ_calendar.svg +0 -4
  165. novelwriter/assets/icons/typicons_dark/typ_cancel-grey.svg +0 -4
  166. novelwriter/assets/icons/typicons_dark/typ_cancel.svg +0 -4
  167. novelwriter/assets/icons/typicons_dark/typ_chart-bar-grey.svg +0 -4
  168. novelwriter/assets/icons/typicons_dark/typ_chevron-down.svg +0 -4
  169. novelwriter/assets/icons/typicons_dark/typ_chevron-left.svg +0 -4
  170. novelwriter/assets/icons/typicons_dark/typ_chevron-right.svg +0 -4
  171. novelwriter/assets/icons/typicons_dark/typ_chevron-up.svg +0 -4
  172. novelwriter/assets/icons/typicons_dark/typ_cog.svg +0 -4
  173. novelwriter/assets/icons/typicons_dark/typ_delete-full.svg +0 -4
  174. novelwriter/assets/icons/typicons_dark/typ_delete.svg +0 -4
  175. novelwriter/assets/icons/typicons_dark/typ_directions-full.svg +0 -4
  176. novelwriter/assets/icons/typicons_dark/typ_document-add.svg +0 -4
  177. novelwriter/assets/icons/typicons_dark/typ_document-text.svg +0 -8
  178. novelwriter/assets/icons/typicons_dark/typ_document.svg +0 -4
  179. novelwriter/assets/icons/typicons_dark/typ_export-grey.svg +0 -4
  180. novelwriter/assets/icons/typicons_dark/typ_export.svg +0 -4
  181. novelwriter/assets/icons/typicons_dark/typ_eye.svg +0 -4
  182. novelwriter/assets/icons/typicons_dark/typ_flag.svg +0 -4
  183. novelwriter/assets/icons/typicons_dark/typ_folder-open.svg +0 -4
  184. novelwriter/assets/icons/typicons_dark/typ_folder.svg +0 -5
  185. novelwriter/assets/icons/typicons_dark/typ_globe-grey.svg +0 -4
  186. novelwriter/assets/icons/typicons_dark/typ_key.svg +0 -4
  187. novelwriter/assets/icons/typicons_dark/typ_lightbulb-full.svg +0 -4
  188. novelwriter/assets/icons/typicons_dark/typ_location.svg +0 -4
  189. novelwriter/assets/icons/typicons_dark/typ_media-pause-grey.svg +0 -4
  190. novelwriter/assets/icons/typicons_dark/typ_media-record-outline.svg +0 -4
  191. novelwriter/assets/icons/typicons_dark/typ_media-record.svg +0 -4
  192. novelwriter/assets/icons/typicons_dark/typ_minus.svg +0 -4
  193. novelwriter/assets/icons/typicons_dark/typ_pencil.svg +0 -5
  194. novelwriter/assets/icons/typicons_dark/typ_pin-outline.svg +0 -4
  195. novelwriter/assets/icons/typicons_dark/typ_pin.svg +0 -4
  196. novelwriter/assets/icons/typicons_dark/typ_plus.svg +0 -4
  197. novelwriter/assets/icons/typicons_dark/typ_puzzle-outline.svg +0 -4
  198. novelwriter/assets/icons/typicons_dark/typ_puzzle.svg +0 -4
  199. novelwriter/assets/icons/typicons_dark/typ_refresh-flipped.svg +0 -4
  200. novelwriter/assets/icons/typicons_dark/typ_refresh.svg +0 -4
  201. novelwriter/assets/icons/typicons_dark/typ_search-grey.svg +0 -4
  202. novelwriter/assets/icons/typicons_dark/typ_search.svg +0 -4
  203. novelwriter/assets/icons/typicons_dark/typ_star.svg +0 -4
  204. novelwriter/assets/icons/typicons_dark/typ_stopwatch-grey.svg +0 -4
  205. novelwriter/assets/icons/typicons_dark/typ_th-dot-menu.svg +0 -4
  206. novelwriter/assets/icons/typicons_dark/typ_th-dot-more.svg +0 -4
  207. novelwriter/assets/icons/typicons_dark/typ_th-list-grey.svg +0 -4
  208. novelwriter/assets/icons/typicons_dark/typ_th-list.svg +0 -9
  209. novelwriter/assets/icons/typicons_dark/typ_times.svg +0 -4
  210. novelwriter/assets/icons/typicons_dark/typ_trash.svg +0 -5
  211. novelwriter/assets/icons/typicons_dark/typ_unfold-hidden.svg +0 -4
  212. novelwriter/assets/icons/typicons_dark/typ_unfold-visible.svg +0 -4
  213. novelwriter/assets/icons/typicons_dark/typ_user.svg +0 -5
  214. novelwriter/assets/icons/typicons_dark/typ_warning-full.svg +0 -4
  215. novelwriter/assets/icons/typicons_light/README.md +0 -29
  216. novelwriter/assets/icons/typicons_light/icons.conf +0 -134
  217. novelwriter/assets/icons/typicons_light/mixed_copy.svg +0 -4
  218. novelwriter/assets/icons/typicons_light/mixed_document-chapter.svg +0 -12
  219. novelwriter/assets/icons/typicons_light/mixed_document-new.svg +0 -6
  220. novelwriter/assets/icons/typicons_light/mixed_document-note.svg +0 -12
  221. novelwriter/assets/icons/typicons_light/mixed_document-scene.svg +0 -12
  222. novelwriter/assets/icons/typicons_light/mixed_document-section.svg +0 -12
  223. novelwriter/assets/icons/typicons_light/mixed_document-title.svg +0 -12
  224. novelwriter/assets/icons/typicons_light/mixed_edit.svg +0 -4
  225. novelwriter/assets/icons/typicons_light/mixed_import.svg +0 -5
  226. novelwriter/assets/icons/typicons_light/mixed_input-checked.svg +0 -5
  227. novelwriter/assets/icons/typicons_light/mixed_input-none.svg +0 -5
  228. novelwriter/assets/icons/typicons_light/mixed_input-unchecked.svg +0 -5
  229. novelwriter/assets/icons/typicons_light/mixed_margin-bottom.svg +0 -6
  230. novelwriter/assets/icons/typicons_light/mixed_margin-left.svg +0 -6
  231. novelwriter/assets/icons/typicons_light/mixed_margin-right.svg +0 -6
  232. novelwriter/assets/icons/typicons_light/mixed_margin-top.svg +0 -6
  233. novelwriter/assets/icons/typicons_light/mixed_search-replace.svg +0 -6
  234. novelwriter/assets/icons/typicons_light/mixed_size-height.svg +0 -6
  235. novelwriter/assets/icons/typicons_light/mixed_size-width.svg +0 -6
  236. novelwriter/assets/icons/typicons_light/nw_deco-h0.svg +0 -4
  237. novelwriter/assets/icons/typicons_light/nw_deco-h1.svg +0 -4
  238. novelwriter/assets/icons/typicons_light/nw_deco-h2-narrow.svg +0 -4
  239. novelwriter/assets/icons/typicons_light/nw_deco-h2.svg +0 -4
  240. novelwriter/assets/icons/typicons_light/nw_deco-h3-narrow.svg +0 -4
  241. novelwriter/assets/icons/typicons_light/nw_deco-h3.svg +0 -4
  242. novelwriter/assets/icons/typicons_light/nw_deco-h4-narrow.svg +0 -4
  243. novelwriter/assets/icons/typicons_light/nw_deco-h4.svg +0 -4
  244. novelwriter/assets/icons/typicons_light/nw_deco-note.svg +0 -4
  245. novelwriter/assets/icons/typicons_light/nw_deco-noveltree-more.svg +0 -4
  246. novelwriter/assets/icons/typicons_light/nw_font.svg +0 -4
  247. novelwriter/assets/icons/typicons_light/nw_panel.svg +0 -4
  248. novelwriter/assets/icons/typicons_light/nw_quote.svg +0 -4
  249. novelwriter/assets/icons/typicons_light/nw_search-case.svg +0 -4
  250. novelwriter/assets/icons/typicons_light/nw_search-preserve.svg +0 -4
  251. novelwriter/assets/icons/typicons_light/nw_search-regex.svg +0 -4
  252. novelwriter/assets/icons/typicons_light/nw_search-word.svg +0 -4
  253. novelwriter/assets/icons/typicons_light/nw_tb-bold-md.svg +0 -4
  254. novelwriter/assets/icons/typicons_light/nw_tb-bold.svg +0 -6
  255. novelwriter/assets/icons/typicons_light/nw_tb-italic-md.svg +0 -4
  256. novelwriter/assets/icons/typicons_light/nw_tb-italic.svg +0 -6
  257. novelwriter/assets/icons/typicons_light/nw_tb-mark.svg +0 -7
  258. novelwriter/assets/icons/typicons_light/nw_tb-strike-md.svg +0 -4
  259. novelwriter/assets/icons/typicons_light/nw_tb-strike.svg +0 -6
  260. novelwriter/assets/icons/typicons_light/nw_tb-subscript.svg +0 -7
  261. novelwriter/assets/icons/typicons_light/nw_tb-superscript.svg +0 -7
  262. novelwriter/assets/icons/typicons_light/nw_tb-underline.svg +0 -7
  263. novelwriter/assets/icons/typicons_light/nw_toolbar.svg +0 -5
  264. novelwriter/assets/icons/typicons_light/typ_arrow-down-thick-grey.svg +0 -4
  265. novelwriter/assets/icons/typicons_light/typ_arrow-forward.svg +0 -4
  266. novelwriter/assets/icons/typicons_light/typ_arrow-maximise.svg +0 -4
  267. novelwriter/assets/icons/typicons_light/typ_arrow-minimise.svg +0 -4
  268. novelwriter/assets/icons/typicons_light/typ_arrow-repeat-grey.svg +0 -4
  269. novelwriter/assets/icons/typicons_light/typ_book-grey.svg +0 -4
  270. novelwriter/assets/icons/typicons_light/typ_book.svg +0 -6
  271. novelwriter/assets/icons/typicons_light/typ_bookmark.svg +0 -4
  272. novelwriter/assets/icons/typicons_light/typ_calendar.svg +0 -4
  273. novelwriter/assets/icons/typicons_light/typ_cancel-grey.svg +0 -4
  274. novelwriter/assets/icons/typicons_light/typ_cancel.svg +0 -4
  275. novelwriter/assets/icons/typicons_light/typ_chart-bar-grey.svg +0 -4
  276. novelwriter/assets/icons/typicons_light/typ_chevron-down.svg +0 -4
  277. novelwriter/assets/icons/typicons_light/typ_chevron-left.svg +0 -4
  278. novelwriter/assets/icons/typicons_light/typ_chevron-right.svg +0 -4
  279. novelwriter/assets/icons/typicons_light/typ_chevron-up.svg +0 -4
  280. novelwriter/assets/icons/typicons_light/typ_cog.svg +0 -4
  281. novelwriter/assets/icons/typicons_light/typ_delete-full.svg +0 -4
  282. novelwriter/assets/icons/typicons_light/typ_delete.svg +0 -4
  283. novelwriter/assets/icons/typicons_light/typ_directions-full.svg +0 -4
  284. novelwriter/assets/icons/typicons_light/typ_document-add.svg +0 -4
  285. novelwriter/assets/icons/typicons_light/typ_document-text.svg +0 -5
  286. novelwriter/assets/icons/typicons_light/typ_document.svg +0 -4
  287. novelwriter/assets/icons/typicons_light/typ_export-grey.svg +0 -4
  288. novelwriter/assets/icons/typicons_light/typ_export.svg +0 -4
  289. novelwriter/assets/icons/typicons_light/typ_eye.svg +0 -4
  290. novelwriter/assets/icons/typicons_light/typ_flag.svg +0 -4
  291. novelwriter/assets/icons/typicons_light/typ_folder-open.svg +0 -4
  292. novelwriter/assets/icons/typicons_light/typ_folder.svg +0 -5
  293. novelwriter/assets/icons/typicons_light/typ_globe-grey.svg +0 -4
  294. novelwriter/assets/icons/typicons_light/typ_key.svg +0 -4
  295. novelwriter/assets/icons/typicons_light/typ_lightbulb-full.svg +0 -4
  296. novelwriter/assets/icons/typicons_light/typ_location.svg +0 -4
  297. novelwriter/assets/icons/typicons_light/typ_media-pause-grey.svg +0 -4
  298. novelwriter/assets/icons/typicons_light/typ_media-record-outline.svg +0 -4
  299. novelwriter/assets/icons/typicons_light/typ_media-record.svg +0 -4
  300. novelwriter/assets/icons/typicons_light/typ_minus.svg +0 -4
  301. novelwriter/assets/icons/typicons_light/typ_pencil.svg +0 -5
  302. novelwriter/assets/icons/typicons_light/typ_pin-outline.svg +0 -4
  303. novelwriter/assets/icons/typicons_light/typ_pin.svg +0 -4
  304. novelwriter/assets/icons/typicons_light/typ_plus.svg +0 -4
  305. novelwriter/assets/icons/typicons_light/typ_puzzle-outline.svg +0 -4
  306. novelwriter/assets/icons/typicons_light/typ_puzzle.svg +0 -4
  307. novelwriter/assets/icons/typicons_light/typ_refresh-flipped.svg +0 -4
  308. novelwriter/assets/icons/typicons_light/typ_refresh.svg +0 -4
  309. novelwriter/assets/icons/typicons_light/typ_search-grey.svg +0 -4
  310. novelwriter/assets/icons/typicons_light/typ_search.svg +0 -4
  311. novelwriter/assets/icons/typicons_light/typ_star.svg +0 -4
  312. novelwriter/assets/icons/typicons_light/typ_stopwatch-grey.svg +0 -4
  313. novelwriter/assets/icons/typicons_light/typ_th-dot-menu.svg +0 -4
  314. novelwriter/assets/icons/typicons_light/typ_th-dot-more.svg +0 -4
  315. novelwriter/assets/icons/typicons_light/typ_th-list-grey.svg +0 -4
  316. novelwriter/assets/icons/typicons_light/typ_th-list.svg +0 -9
  317. novelwriter/assets/icons/typicons_light/typ_times.svg +0 -4
  318. novelwriter/assets/icons/typicons_light/typ_trash.svg +0 -5
  319. novelwriter/assets/icons/typicons_light/typ_unfold-hidden.svg +0 -4
  320. novelwriter/assets/icons/typicons_light/typ_unfold-visible.svg +0 -4
  321. novelwriter/assets/icons/typicons_light/typ_user.svg +0 -5
  322. novelwriter/assets/icons/typicons_light/typ_warning-full.svg +0 -4
  323. {novelWriter-2.6.3.dist-info → novelwriter-2.7rc1.dist-info}/entry_points.txt +0 -0
  324. {novelWriter-2.6.3.dist-info → novelwriter-2.7rc1.dist-info/licenses}/LICENSE.md +0 -0
  325. {novelWriter-2.6.3.dist-info → novelwriter-2.7rc1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,406 @@
1
+ """
2
+ novelWriter – Project Index Data
3
+ ================================
4
+
5
+ File History:
6
+ Created: 2022-05-28 [2.0rc1] IndexNode
7
+ Created: 2022-05-28 [2.0rc1] IndexHeading
8
+ Moved: 2025-02-22 [2.7b1] IndexNode
9
+ Moved: 2025-02-22 [2.7b1] IndexHeading
10
+
11
+ This file is a part of novelWriter
12
+ Copyright (C) 2025 Veronica Berglyd Olsen and novelWriter contributors
13
+
14
+ This program is free software: you can redistribute it and/or modify
15
+ it under the terms of the GNU General Public License as published by
16
+ the Free Software Foundation, either version 3 of the License, or
17
+ (at your option) any later version.
18
+
19
+ This program is distributed in the hope that it will be useful, but
20
+ WITHOUT ANY WARRANTY; without even the implied warranty of
21
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
22
+ General Public License for more details.
23
+
24
+ You should have received a copy of the GNU General Public License
25
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
26
+ """
27
+ from __future__ import annotations
28
+
29
+ import logging
30
+
31
+ from typing import TYPE_CHECKING, Literal
32
+
33
+ from novelwriter import CONFIG
34
+ from novelwriter.common import checkInt, compact, isListInstance, isTitleTag
35
+ from novelwriter.constants import nwKeyWords, nwStyles
36
+
37
+ if TYPE_CHECKING:
38
+ from collections.abc import ItemsView, Sequence
39
+
40
+ from novelwriter.core.index import IndexCache
41
+ from novelwriter.core.item import NWItem
42
+ from novelwriter.enum import nwComment
43
+
44
+ logger = logging.getLogger(__name__)
45
+
46
+ T_NoteTypes = Literal["footnotes", "comments"]
47
+
48
+ TT_NONE = "T0000" # Default title key
49
+ NOTE_TYPES: list[T_NoteTypes] = ["footnotes", "comments"]
50
+
51
+
52
+ class IndexNode:
53
+ """Core: Single Index Item Node Class
54
+
55
+ This object represents the index data of a project item (NWItem).
56
+ It holds a record of all the headings in the text, and the meta data
57
+ associated with each heading. It also holds a pointer to the project
58
+ item. The main heading level of the item is also held here since it
59
+ must be reset each time the item is re-indexed.
60
+ """
61
+
62
+ __slots__ = ("_cache", "_count", "_handle", "_headings", "_item", "_notes")
63
+
64
+ def __init__(self, cache: IndexCache, tHandle: str, nwItem: NWItem) -> None:
65
+ self._cache = cache
66
+ self._handle = tHandle
67
+ self._item = nwItem
68
+ self._headings: dict[str, IndexHeading] = {TT_NONE: IndexHeading(self._cache, TT_NONE)}
69
+ self._notes: dict[str, set[str]] = {}
70
+ self._count = 0
71
+ return
72
+
73
+ def __repr__(self) -> str:
74
+ return f"<IndexNode handle='{self._handle}'>"
75
+
76
+ def __len__(self) -> int:
77
+ return len(self._headings)
78
+
79
+ def __getitem__(self, sTitle: str) -> IndexHeading | None:
80
+ return self._headings.get(sTitle, None)
81
+
82
+ def __contains__(self, sTitle: str) -> bool:
83
+ return sTitle in self._headings
84
+
85
+ ##
86
+ # Properties
87
+ ##
88
+
89
+ @property
90
+ def handle(self) -> str:
91
+ """Return the item handle of the index item."""
92
+ return self._handle
93
+
94
+ @property
95
+ def item(self) -> NWItem:
96
+ """Return the project item of the index item."""
97
+ return self._item
98
+
99
+ ##
100
+ # Setters
101
+ ##
102
+
103
+ def addHeading(self, tHeading: IndexHeading) -> None:
104
+ """Add a heading to the item. Also remove the placeholder entry
105
+ if it exists.
106
+ """
107
+ if TT_NONE in self._headings:
108
+ self._headings.pop(TT_NONE)
109
+ self._headings[tHeading.key] = tHeading
110
+ return
111
+
112
+ def setHeadingCounts(self, sTitle: str, cCount: int, wCount: int, pCount: int) -> None:
113
+ """Set the character, word and paragraph count of a heading."""
114
+ if sTitle in self._headings:
115
+ self._headings[sTitle].setCounts([cCount, wCount, pCount])
116
+ return
117
+
118
+ def setHeadingComment(self, sTitle: str, comment: nwComment, key: str, text: str) -> None:
119
+ """Set the comment text of a heading."""
120
+ if sTitle in self._headings:
121
+ self._headings[sTitle].setComment(comment.name, key, text)
122
+ return
123
+
124
+ def setHeadingTag(self, sTitle: str, tag: str) -> None:
125
+ """Set the tag of a heading."""
126
+ if sTitle in self._headings:
127
+ self._headings[sTitle].setTag(tag)
128
+ return
129
+
130
+ def addHeadingRef(self, sTitle: str, tags: list[str], keyword: str) -> None:
131
+ """Add a reference key and all its types to a heading."""
132
+ if sTitle in self._headings:
133
+ for tag in tags:
134
+ self._headings[sTitle].addReference(tag, keyword)
135
+ return
136
+
137
+ def addNoteKey(self, style: T_NoteTypes, key: str) -> None:
138
+ """Add a note key to the index."""
139
+ if style not in self._notes:
140
+ self._notes[style] = set()
141
+ self._notes[style].add(key)
142
+ return
143
+
144
+ ##
145
+ # Data Methods
146
+ ##
147
+
148
+ def items(self) -> ItemsView[str, IndexHeading]:
149
+ """Return IndexHeading items."""
150
+ return self._headings.items()
151
+
152
+ def headings(self) -> list[str]:
153
+ """Return heading keys in sorted order."""
154
+ return sorted(self._headings.keys())
155
+
156
+ def allTags(self) -> list[str]:
157
+ """Return a list of all tags in the current item."""
158
+ return [h.tag for h in self._headings.values() if h.tag]
159
+
160
+ def nextHeading(self) -> str:
161
+ """Return the next heading key to be used."""
162
+ self._count += 1
163
+ return f"T{self._count:04d}"
164
+
165
+ def noteKeys(self, style: T_NoteTypes) -> set[str]:
166
+ """Return a set of all note keys."""
167
+ return self._notes.get(style, set())
168
+
169
+ ##
170
+ # Pack/Unpack
171
+ ##
172
+
173
+ def packData(self) -> dict:
174
+ """Pack the indexed item's data into a dictionary."""
175
+ data = {}
176
+ for sTitle, hItem in self._headings.items():
177
+ data[sTitle] = hItem.packData()
178
+ if self._notes:
179
+ data["document"] = {style: list(keys) for style, keys in self._notes.items()}
180
+ return data
181
+
182
+ def unpackData(self, data: dict) -> None:
183
+ """Unpack an item entry from the data."""
184
+ for key, entry in data.items():
185
+ if isTitleTag(key):
186
+ heading = IndexHeading(self._cache, key)
187
+ heading.unpackData(entry)
188
+ self.addHeading(heading)
189
+ elif key == "document":
190
+ for style, keys in entry.items():
191
+ if style not in NOTE_TYPES:
192
+ raise ValueError("The notes style is invalid")
193
+ if not isListInstance(keys, str):
194
+ raise ValueError("The notes keys must be a list of strings")
195
+ self._notes[style] = set(keys)
196
+ else:
197
+ raise KeyError("Index node contains an invalid key")
198
+ return
199
+
200
+
201
+ class IndexHeading:
202
+ """Core: Single Index Heading Class
203
+
204
+ This object represents a section of text in a project item
205
+ associated with a single (valid) heading. It holds a separate record
206
+ of all references made under the heading.
207
+ """
208
+
209
+ __slots__ = (
210
+ "_cache", "_comments", "_counts", "_key", "_level", "_line", "_refs",
211
+ "_tag", "_title",
212
+ )
213
+
214
+ def __init__(
215
+ self, cache: IndexCache, key: str, line: int = 0,
216
+ level: str = "H0", title: str = "",
217
+ ) -> None:
218
+ self._cache = cache
219
+ self._key = key
220
+ self._line = line
221
+ self._level = level
222
+ self._title = title
223
+ self._counts: tuple[int, int, int] = (0, 0, 0)
224
+ self._tag = ""
225
+ self._refs: dict[str, set[str]] = {}
226
+ self._comments: dict[str, str] = {}
227
+ return
228
+
229
+ def __repr__(self) -> str:
230
+ return f"<IndexHeading key='{self._key}'>"
231
+
232
+ ##
233
+ # Properties
234
+ ##
235
+
236
+ @property
237
+ def key(self) -> str:
238
+ return self._key
239
+
240
+ @property
241
+ def line(self) -> int:
242
+ return self._line
243
+
244
+ @property
245
+ def level(self) -> str:
246
+ return self._level
247
+
248
+ @property
249
+ def title(self) -> str:
250
+ return self._title
251
+
252
+ @property
253
+ def mainCount(self) -> int:
254
+ return self._counts[0 if CONFIG.useCharCount else 1]
255
+
256
+ @property
257
+ def charCount(self) -> int:
258
+ return self._counts[0]
259
+
260
+ @property
261
+ def wordCount(self) -> int:
262
+ return self._counts[1]
263
+
264
+ @property
265
+ def paraCount(self) -> int:
266
+ return self._counts[2]
267
+
268
+ @property
269
+ def synopsis(self) -> str:
270
+ return self._comments.get("summary", "")
271
+
272
+ @property
273
+ def comments(self) -> dict[str, str]:
274
+ return self._comments
275
+
276
+ @property
277
+ def tag(self) -> str:
278
+ return self._tag
279
+
280
+ @property
281
+ def references(self) -> dict[str, set[str]]:
282
+ return self._refs
283
+
284
+ ##
285
+ # Setters
286
+ ##
287
+
288
+ def setLevel(self, level: str) -> None:
289
+ """Set the level of the heading if it's a valid value."""
290
+ if level in nwStyles.H_VALID:
291
+ self._level = level
292
+ return
293
+
294
+ def setLine(self, line: int) -> None:
295
+ """Set the line number of a heading."""
296
+ self._line = max(0, checkInt(line, 0))
297
+ return
298
+
299
+ def setCounts(self, counts: Sequence[int]) -> None:
300
+ """Set the character, word and paragraph count. Make sure the
301
+ value is an integer and is not smaller than 0.
302
+ """
303
+ if len(counts) == 3:
304
+ self._counts = (
305
+ max(0, checkInt(counts[0], 0)),
306
+ max(0, checkInt(counts[1], 0)),
307
+ max(0, checkInt(counts[2], 0)),
308
+ )
309
+ return
310
+
311
+ def setComment(self, comment: str, key: str, text: str) -> None:
312
+ """Set the text for a comment and make sure it is a string."""
313
+ match comment.lower():
314
+ case "short" | "synopsis" | "summary":
315
+ self._comments["summary"] = str(text)
316
+ case "story" if key:
317
+ self._cache.story.add(key)
318
+ self._comments[f"story.{key}"] = str(text)
319
+ case "note" if key:
320
+ self._cache.note.add(key)
321
+ self._comments[f"note.{key}"] = str(text)
322
+ return
323
+
324
+ def setTag(self, tag: str) -> None:
325
+ """Set the tag for references, and make sure it is a string."""
326
+ self._tag = str(tag).lower()
327
+ return
328
+
329
+ def addReference(self, tag: str, keyword: str) -> None:
330
+ """Add a record of a reference tag, and what keyword types it is
331
+ associated with.
332
+ """
333
+ if keyword in nwKeyWords.VALID_KEYS:
334
+ tag = tag.lower()
335
+ if tag not in self._refs:
336
+ self._refs[tag] = set()
337
+ self._refs[tag].add(keyword)
338
+ return
339
+
340
+ ##
341
+ # Getters
342
+ ##
343
+
344
+ def getReferences(self) -> dict[str, list[str]]:
345
+ """Extract all references for this heading."""
346
+ refs = {x: [] for x in nwKeyWords.VALID_KEYS}
347
+ for tag, types in self._refs.items():
348
+ for keyword in types:
349
+ if keyword in refs and (name := self._cache.tags.tagName(tag)):
350
+ refs[keyword].append(name)
351
+ return refs
352
+
353
+ def getReferencesByKeyword(self, keyword: str) -> list[str]:
354
+ """Extract all references for this heading."""
355
+ refs = []
356
+ for tag, types in self._refs.items():
357
+ if keyword in types and (name := self._cache.tags.tagName(tag)):
358
+ refs.append(name)
359
+ return refs
360
+
361
+ ##
362
+ # Data Methods
363
+ ##
364
+
365
+ def packData(self) -> dict:
366
+ """Pack the values into a dictionary for saving to cache."""
367
+ data = {}
368
+ data["meta"] = {
369
+ "level": self._level,
370
+ "title": self._title,
371
+ "line": self._line,
372
+ "tag": self._tag,
373
+ "counts": self._counts,
374
+ }
375
+ if self._refs:
376
+ data["refs"] = {k: ",".join(sorted(list(v))) for k, v in self._refs.items()}
377
+ if self._comments:
378
+ data.update(self._comments)
379
+ return data
380
+
381
+ def unpackData(self, data: dict) -> None:
382
+ """Unpack a heading entry from a dictionary."""
383
+ for key, entry in data.items():
384
+ if key == "meta":
385
+ self.setLevel(entry.get("level", "H0"))
386
+ self._title = str(entry.get("title", ""))
387
+ self._tag = str(entry.get("tag", ""))
388
+ self.setLine(entry.get("line", 0))
389
+ self.setCounts(entry.get("counts", [0, 0, 0]))
390
+ elif key == "refs":
391
+ for tag, value in entry.items():
392
+ if not isinstance(tag, str):
393
+ raise ValueError("Heading reference key must be a string")
394
+ if not isinstance(value, str):
395
+ raise ValueError("Heading reference value must be a string")
396
+ for keyword in value.split(","):
397
+ if keyword in nwKeyWords.VALID_KEYS:
398
+ self.addReference(tag, keyword)
399
+ else:
400
+ raise ValueError("Heading reference contains an invalid keyword")
401
+ elif key == "summary" or key.startswith(("story", "note")):
402
+ comment, _, kind = str(key).partition(".")
403
+ self.setComment(comment, compact(kind), str(entry))
404
+ else:
405
+ raise KeyError("Unknown key in heading entry")
406
+ return
novelwriter/core/item.py CHANGED
@@ -27,8 +27,6 @@ import logging
27
27
 
28
28
  from typing import TYPE_CHECKING, Any
29
29
 
30
- from PyQt5.QtGui import QFont, QIcon
31
-
32
30
  from novelwriter import CONFIG, SHARED
33
31
  from novelwriter.common import (
34
32
  checkInt, isHandle, isItemClass, isItemLayout, isItemType, simplified,
@@ -37,7 +35,9 @@ from novelwriter.common import (
37
35
  from novelwriter.constants import nwLabels, nwStyles, trConst
38
36
  from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType
39
37
 
40
- if TYPE_CHECKING: # pragma: no cover
38
+ if TYPE_CHECKING:
39
+ from PyQt6.QtGui import QFont, QIcon
40
+
41
41
  from novelwriter.core.project import NWProject
42
42
 
43
43
  logger = logging.getLogger(__name__)
@@ -53,10 +53,10 @@ class NWItem:
53
53
  """
54
54
 
55
55
  __slots__ = (
56
- "_project", "_name", "_handle", "_parent", "_root", "_order",
57
- "_type", "_class", "_layout", "_status", "_import", "_active",
58
- "_expanded", "_heading", "_charCount", "_wordCount",
59
- "_paraCount", "_cursorPos", "_initCount",
56
+ "_active", "_charCount", "_charInit", "_class", "_cursorPos",
57
+ "_expanded", "_handle", "_heading", "_import", "_layout", "_name",
58
+ "_order", "_paraCount", "_parent", "_project", "_root", "_status",
59
+ "_type", "_wordCount", "_wordInit",
60
60
  )
61
61
 
62
62
  def __init__(self, project: NWProject, handle: str) -> None:
@@ -81,7 +81,8 @@ class NWItem:
81
81
  self._wordCount = 0 # Current word count
82
82
  self._paraCount = 0 # Current paragraph count
83
83
  self._cursorPos = 0 # Last cursor position
84
- self._initCount = 0 # Initial word count
84
+ self._wordInit = 0 # Initial character count
85
+ self._charInit = 0 # Initial word count
85
86
 
86
87
  return
87
88
 
@@ -164,9 +165,13 @@ class NWItem:
164
165
  def paraCount(self) -> int:
165
166
  return self._paraCount
166
167
 
168
+ @property
169
+ def mainCount(self) -> int:
170
+ return self._charCount if CONFIG.useCharCount else self._wordCount
171
+
167
172
  @property
168
173
  def initCount(self) -> int:
169
- return self._initCount
174
+ return self._wordInit if CONFIG.useCharCount else self._charInit
170
175
 
171
176
  @property
172
177
  def cursorPos(self) -> int:
@@ -257,32 +262,34 @@ class NWItem:
257
262
  self._paraCount = 0
258
263
  self._cursorPos = 0
259
264
 
260
- self._initCount = self._wordCount
265
+ self._wordInit = self._charCount
266
+ self._charInit = self._wordCount
261
267
 
262
268
  return True
263
269
 
264
270
  @classmethod
265
271
  def duplicate(cls, source: NWItem, handle: str) -> NWItem:
266
272
  """Make a copy of an item."""
267
- cls = NWItem(source._project, handle)
268
- cls._name = source._name
269
- cls._parent = source._parent
270
- cls._root = source._root
271
- cls._order = source._order
272
- cls._type = source._type
273
- cls._class = source._class
274
- cls._layout = source._layout
275
- cls._status = source._status
276
- cls._import = source._import
277
- cls._active = source._active
278
- cls._expanded = source._expanded
279
- cls._heading = source._heading
280
- cls._charCount = source._charCount
281
- cls._wordCount = source._wordCount
282
- cls._paraCount = source._paraCount
283
- cls._cursorPos = source._cursorPos
284
- cls._initCount = source._initCount
285
- return cls
273
+ new = cls(source._project, handle)
274
+ new._name = source._name
275
+ new._parent = source._parent
276
+ new._root = source._root
277
+ new._order = source._order
278
+ new._type = source._type
279
+ new._class = source._class
280
+ new._layout = source._layout
281
+ new._status = source._status
282
+ new._import = source._import
283
+ new._active = source._active
284
+ new._expanded = source._expanded
285
+ new._heading = source._heading
286
+ new._charCount = source._charCount
287
+ new._wordCount = source._wordCount
288
+ new._paraCount = source._paraCount
289
+ new._cursorPos = source._cursorPos
290
+ new._wordInit = source._wordInit
291
+ new._charInit = source._charInit
292
+ return new
286
293
 
287
294
  ##
288
295
  # Action Methods
@@ -293,6 +300,12 @@ class NWItem:
293
300
  self._project.tree.refreshItems([self._handle])
294
301
  return
295
302
 
303
+ def notifyNovelStructureChange(self) -> None:
304
+ """Notify that the structure of a novel has changed."""
305
+ if self._root and self._class == nwItemClass.NOVEL:
306
+ self._project.tree.novelStructureChanged(self._root)
307
+ return
308
+
296
309
  ##
297
310
  # Lookup Methods
298
311
  ##
@@ -350,11 +363,12 @@ class NWItem:
350
363
  """
351
364
  if self.isFileType():
352
365
  key = "checked" if self._active else "unchecked"
366
+ color = "green" if self._active else "red"
353
367
  text = trConst(nwLabels.ACTIVE_NAME[key])
354
- icon = SHARED.theme.getIcon(key)
368
+ icon = SHARED.theme.getIcon(key, color)
355
369
  else:
356
370
  text = ""
357
- icon = SHARED.theme.getIcon("noncheckable")
371
+ icon = SHARED.theme.getIcon("noncheckable", "faded")
358
372
  return text, icon
359
373
 
360
374
  ##
@@ -421,7 +435,11 @@ class NWItem:
421
435
  """
422
436
  if self._parent is not None:
423
437
  # Only update for child items
424
- self.setClass(itemClass)
438
+ if itemClass != self._class:
439
+ self.setClass(itemClass)
440
+ if self._type == nwItemType.FILE:
441
+ # Notify the index of the class change
442
+ self._project.index.setItemClass(self._handle, itemClass)
425
443
 
426
444
  if self._layout == nwItemLayout.NO_LAYOUT:
427
445
  # If no layout is set, pick one