novelWriter 2.6.3__py3-none-any.whl → 2.7b1__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 (321) hide show
  1. novelwriter/__init__.py +84 -74
  2. novelwriter/assets/i18n/project_en_GB.json +1 -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 +108 -0
  11. novelwriter/assets/icons/remix_outline.icons +108 -0
  12. novelwriter/assets/manual.pdf +0 -0
  13. novelwriter/assets/{manual_fr_FR.pdf → manual_fr.pdf} +0 -0
  14. novelwriter/assets/sample.zip +0 -0
  15. novelwriter/assets/syntax/cyberpunk_night.conf +1 -1
  16. novelwriter/assets/text/credits_en.htm +6 -6
  17. novelwriter/assets/themes/cyberpunk_night.conf +23 -7
  18. novelwriter/assets/themes/default_dark.conf +20 -4
  19. novelwriter/assets/themes/default_light.conf +21 -5
  20. novelwriter/assets/themes/dracula.conf +20 -4
  21. novelwriter/assets/themes/solarized_dark.conf +24 -8
  22. novelwriter/assets/themes/solarized_light.conf +22 -6
  23. novelwriter/common.py +33 -26
  24. novelwriter/config.py +118 -127
  25. novelwriter/constants.py +50 -36
  26. novelwriter/core/buildsettings.py +23 -16
  27. novelwriter/core/coretools.py +11 -7
  28. novelwriter/core/docbuild.py +19 -13
  29. novelwriter/core/document.py +2 -2
  30. novelwriter/core/index.py +142 -432
  31. novelwriter/core/indexdata.py +403 -0
  32. novelwriter/core/item.py +35 -28
  33. novelwriter/core/itemmodel.py +27 -26
  34. novelwriter/core/novelmodel.py +223 -0
  35. novelwriter/core/options.py +1 -1
  36. novelwriter/core/project.py +10 -11
  37. novelwriter/core/projectdata.py +5 -5
  38. novelwriter/core/projectxml.py +1 -1
  39. novelwriter/core/sessions.py +3 -2
  40. novelwriter/core/spellcheck.py +4 -3
  41. novelwriter/core/status.py +12 -15
  42. novelwriter/core/storage.py +1 -1
  43. novelwriter/core/tree.py +46 -8
  44. novelwriter/dialogs/about.py +19 -22
  45. novelwriter/dialogs/docmerge.py +21 -23
  46. novelwriter/dialogs/docsplit.py +20 -23
  47. novelwriter/dialogs/editlabel.py +9 -13
  48. novelwriter/dialogs/preferences.py +111 -48
  49. novelwriter/dialogs/projectsettings.py +48 -54
  50. novelwriter/dialogs/quotes.py +14 -19
  51. novelwriter/dialogs/wordlist.py +25 -30
  52. novelwriter/enum.py +8 -0
  53. novelwriter/error.py +16 -16
  54. novelwriter/extensions/configlayout.py +18 -18
  55. novelwriter/extensions/eventfilters.py +9 -5
  56. novelwriter/extensions/modified.py +34 -15
  57. novelwriter/extensions/novelselector.py +18 -5
  58. novelwriter/extensions/pagedsidebar.py +39 -49
  59. novelwriter/extensions/progressbars.py +10 -8
  60. novelwriter/extensions/statusled.py +6 -13
  61. novelwriter/extensions/switch.py +19 -30
  62. novelwriter/extensions/switchbox.py +7 -3
  63. novelwriter/extensions/versioninfo.py +4 -4
  64. novelwriter/formats/shared.py +1 -1
  65. novelwriter/formats/todocx.py +14 -10
  66. novelwriter/formats/tohtml.py +7 -5
  67. novelwriter/formats/tokenizer.py +36 -33
  68. novelwriter/formats/tomarkdown.py +6 -2
  69. novelwriter/formats/toodt.py +27 -22
  70. novelwriter/formats/toqdoc.py +19 -14
  71. novelwriter/formats/toraw.py +6 -2
  72. novelwriter/gui/doceditor.py +216 -265
  73. novelwriter/gui/dochighlight.py +46 -45
  74. novelwriter/gui/docviewer.py +102 -104
  75. novelwriter/gui/docviewerpanel.py +47 -51
  76. novelwriter/gui/editordocument.py +8 -5
  77. novelwriter/gui/itemdetails.py +11 -14
  78. novelwriter/gui/mainmenu.py +146 -145
  79. novelwriter/gui/noveltree.py +239 -405
  80. novelwriter/gui/outline.py +133 -72
  81. novelwriter/gui/projtree.py +138 -132
  82. novelwriter/gui/search.py +33 -31
  83. novelwriter/gui/sidebar.py +13 -18
  84. novelwriter/gui/statusbar.py +13 -15
  85. novelwriter/gui/theme.py +533 -430
  86. novelwriter/guimain.py +27 -29
  87. novelwriter/shared.py +32 -23
  88. novelwriter/text/comments.py +70 -0
  89. novelwriter/text/patterns.py +4 -4
  90. novelwriter/tools/dictionaries.py +20 -29
  91. novelwriter/tools/lipsum.py +16 -17
  92. novelwriter/tools/manusbuild.py +39 -42
  93. novelwriter/tools/manuscript.py +101 -114
  94. novelwriter/tools/manussettings.py +78 -83
  95. novelwriter/tools/noveldetails.py +51 -64
  96. novelwriter/tools/welcome.py +56 -75
  97. novelwriter/tools/writingstats.py +44 -57
  98. novelwriter/types.py +5 -7
  99. {novelWriter-2.6.3.dist-info → novelwriter-2.7b1.dist-info}/METADATA +6 -6
  100. novelwriter-2.7b1.dist-info/RECORD +159 -0
  101. {novelWriter-2.6.3.dist-info → novelwriter-2.7b1.dist-info}/WHEEL +1 -1
  102. novelWriter-2.6.3.dist-info/RECORD +0 -363
  103. novelwriter/assets/icons/typicons_dark/README.md +0 -29
  104. novelwriter/assets/icons/typicons_dark/icons.conf +0 -134
  105. novelwriter/assets/icons/typicons_dark/mixed_copy.svg +0 -4
  106. novelwriter/assets/icons/typicons_dark/mixed_document-chapter.svg +0 -12
  107. novelwriter/assets/icons/typicons_dark/mixed_document-new.svg +0 -6
  108. novelwriter/assets/icons/typicons_dark/mixed_document-note.svg +0 -12
  109. novelwriter/assets/icons/typicons_dark/mixed_document-scene.svg +0 -12
  110. novelwriter/assets/icons/typicons_dark/mixed_document-section.svg +0 -12
  111. novelwriter/assets/icons/typicons_dark/mixed_document-title.svg +0 -12
  112. novelwriter/assets/icons/typicons_dark/mixed_edit.svg +0 -4
  113. novelwriter/assets/icons/typicons_dark/mixed_import.svg +0 -5
  114. novelwriter/assets/icons/typicons_dark/mixed_input-checked.svg +0 -5
  115. novelwriter/assets/icons/typicons_dark/mixed_input-none.svg +0 -5
  116. novelwriter/assets/icons/typicons_dark/mixed_input-unchecked.svg +0 -5
  117. novelwriter/assets/icons/typicons_dark/mixed_margin-bottom.svg +0 -6
  118. novelwriter/assets/icons/typicons_dark/mixed_margin-left.svg +0 -6
  119. novelwriter/assets/icons/typicons_dark/mixed_margin-right.svg +0 -6
  120. novelwriter/assets/icons/typicons_dark/mixed_margin-top.svg +0 -6
  121. novelwriter/assets/icons/typicons_dark/mixed_search-replace.svg +0 -6
  122. novelwriter/assets/icons/typicons_dark/mixed_size-height.svg +0 -6
  123. novelwriter/assets/icons/typicons_dark/mixed_size-width.svg +0 -6
  124. novelwriter/assets/icons/typicons_dark/nw_deco-h0.svg +0 -4
  125. novelwriter/assets/icons/typicons_dark/nw_deco-h1.svg +0 -4
  126. novelwriter/assets/icons/typicons_dark/nw_deco-h2-narrow.svg +0 -4
  127. novelwriter/assets/icons/typicons_dark/nw_deco-h2.svg +0 -4
  128. novelwriter/assets/icons/typicons_dark/nw_deco-h3-narrow.svg +0 -4
  129. novelwriter/assets/icons/typicons_dark/nw_deco-h3.svg +0 -4
  130. novelwriter/assets/icons/typicons_dark/nw_deco-h4-narrow.svg +0 -4
  131. novelwriter/assets/icons/typicons_dark/nw_deco-h4.svg +0 -4
  132. novelwriter/assets/icons/typicons_dark/nw_deco-note.svg +0 -4
  133. novelwriter/assets/icons/typicons_dark/nw_deco-noveltree-more.svg +0 -4
  134. novelwriter/assets/icons/typicons_dark/nw_font.svg +0 -4
  135. novelwriter/assets/icons/typicons_dark/nw_panel.svg +0 -4
  136. novelwriter/assets/icons/typicons_dark/nw_quote.svg +0 -4
  137. novelwriter/assets/icons/typicons_dark/nw_search-case.svg +0 -4
  138. novelwriter/assets/icons/typicons_dark/nw_search-preserve.svg +0 -4
  139. novelwriter/assets/icons/typicons_dark/nw_search-regex.svg +0 -4
  140. novelwriter/assets/icons/typicons_dark/nw_search-word.svg +0 -4
  141. novelwriter/assets/icons/typicons_dark/nw_tb-bold-md.svg +0 -4
  142. novelwriter/assets/icons/typicons_dark/nw_tb-bold.svg +0 -6
  143. novelwriter/assets/icons/typicons_dark/nw_tb-italic-md.svg +0 -4
  144. novelwriter/assets/icons/typicons_dark/nw_tb-italic.svg +0 -6
  145. novelwriter/assets/icons/typicons_dark/nw_tb-mark.svg +0 -7
  146. novelwriter/assets/icons/typicons_dark/nw_tb-strike-md.svg +0 -4
  147. novelwriter/assets/icons/typicons_dark/nw_tb-strike.svg +0 -6
  148. novelwriter/assets/icons/typicons_dark/nw_tb-subscript.svg +0 -7
  149. novelwriter/assets/icons/typicons_dark/nw_tb-superscript.svg +0 -7
  150. novelwriter/assets/icons/typicons_dark/nw_tb-underline.svg +0 -7
  151. novelwriter/assets/icons/typicons_dark/nw_toolbar.svg +0 -5
  152. novelwriter/assets/icons/typicons_dark/typ_arrow-down-thick-grey.svg +0 -4
  153. novelwriter/assets/icons/typicons_dark/typ_arrow-forward.svg +0 -4
  154. novelwriter/assets/icons/typicons_dark/typ_arrow-maximise.svg +0 -4
  155. novelwriter/assets/icons/typicons_dark/typ_arrow-minimise.svg +0 -4
  156. novelwriter/assets/icons/typicons_dark/typ_arrow-repeat-grey.svg +0 -4
  157. novelwriter/assets/icons/typicons_dark/typ_book-grey.svg +0 -4
  158. novelwriter/assets/icons/typicons_dark/typ_book.svg +0 -6
  159. novelwriter/assets/icons/typicons_dark/typ_bookmark.svg +0 -4
  160. novelwriter/assets/icons/typicons_dark/typ_calendar.svg +0 -4
  161. novelwriter/assets/icons/typicons_dark/typ_cancel-grey.svg +0 -4
  162. novelwriter/assets/icons/typicons_dark/typ_cancel.svg +0 -4
  163. novelwriter/assets/icons/typicons_dark/typ_chart-bar-grey.svg +0 -4
  164. novelwriter/assets/icons/typicons_dark/typ_chevron-down.svg +0 -4
  165. novelwriter/assets/icons/typicons_dark/typ_chevron-left.svg +0 -4
  166. novelwriter/assets/icons/typicons_dark/typ_chevron-right.svg +0 -4
  167. novelwriter/assets/icons/typicons_dark/typ_chevron-up.svg +0 -4
  168. novelwriter/assets/icons/typicons_dark/typ_cog.svg +0 -4
  169. novelwriter/assets/icons/typicons_dark/typ_delete-full.svg +0 -4
  170. novelwriter/assets/icons/typicons_dark/typ_delete.svg +0 -4
  171. novelwriter/assets/icons/typicons_dark/typ_directions-full.svg +0 -4
  172. novelwriter/assets/icons/typicons_dark/typ_document-add.svg +0 -4
  173. novelwriter/assets/icons/typicons_dark/typ_document-text.svg +0 -8
  174. novelwriter/assets/icons/typicons_dark/typ_document.svg +0 -4
  175. novelwriter/assets/icons/typicons_dark/typ_export-grey.svg +0 -4
  176. novelwriter/assets/icons/typicons_dark/typ_export.svg +0 -4
  177. novelwriter/assets/icons/typicons_dark/typ_eye.svg +0 -4
  178. novelwriter/assets/icons/typicons_dark/typ_flag.svg +0 -4
  179. novelwriter/assets/icons/typicons_dark/typ_folder-open.svg +0 -4
  180. novelwriter/assets/icons/typicons_dark/typ_folder.svg +0 -5
  181. novelwriter/assets/icons/typicons_dark/typ_globe-grey.svg +0 -4
  182. novelwriter/assets/icons/typicons_dark/typ_key.svg +0 -4
  183. novelwriter/assets/icons/typicons_dark/typ_lightbulb-full.svg +0 -4
  184. novelwriter/assets/icons/typicons_dark/typ_location.svg +0 -4
  185. novelwriter/assets/icons/typicons_dark/typ_media-pause-grey.svg +0 -4
  186. novelwriter/assets/icons/typicons_dark/typ_media-record-outline.svg +0 -4
  187. novelwriter/assets/icons/typicons_dark/typ_media-record.svg +0 -4
  188. novelwriter/assets/icons/typicons_dark/typ_minus.svg +0 -4
  189. novelwriter/assets/icons/typicons_dark/typ_pencil.svg +0 -5
  190. novelwriter/assets/icons/typicons_dark/typ_pin-outline.svg +0 -4
  191. novelwriter/assets/icons/typicons_dark/typ_pin.svg +0 -4
  192. novelwriter/assets/icons/typicons_dark/typ_plus.svg +0 -4
  193. novelwriter/assets/icons/typicons_dark/typ_puzzle-outline.svg +0 -4
  194. novelwriter/assets/icons/typicons_dark/typ_puzzle.svg +0 -4
  195. novelwriter/assets/icons/typicons_dark/typ_refresh-flipped.svg +0 -4
  196. novelwriter/assets/icons/typicons_dark/typ_refresh.svg +0 -4
  197. novelwriter/assets/icons/typicons_dark/typ_search-grey.svg +0 -4
  198. novelwriter/assets/icons/typicons_dark/typ_search.svg +0 -4
  199. novelwriter/assets/icons/typicons_dark/typ_star.svg +0 -4
  200. novelwriter/assets/icons/typicons_dark/typ_stopwatch-grey.svg +0 -4
  201. novelwriter/assets/icons/typicons_dark/typ_th-dot-menu.svg +0 -4
  202. novelwriter/assets/icons/typicons_dark/typ_th-dot-more.svg +0 -4
  203. novelwriter/assets/icons/typicons_dark/typ_th-list-grey.svg +0 -4
  204. novelwriter/assets/icons/typicons_dark/typ_th-list.svg +0 -9
  205. novelwriter/assets/icons/typicons_dark/typ_times.svg +0 -4
  206. novelwriter/assets/icons/typicons_dark/typ_trash.svg +0 -5
  207. novelwriter/assets/icons/typicons_dark/typ_unfold-hidden.svg +0 -4
  208. novelwriter/assets/icons/typicons_dark/typ_unfold-visible.svg +0 -4
  209. novelwriter/assets/icons/typicons_dark/typ_user.svg +0 -5
  210. novelwriter/assets/icons/typicons_dark/typ_warning-full.svg +0 -4
  211. novelwriter/assets/icons/typicons_light/README.md +0 -29
  212. novelwriter/assets/icons/typicons_light/icons.conf +0 -134
  213. novelwriter/assets/icons/typicons_light/mixed_copy.svg +0 -4
  214. novelwriter/assets/icons/typicons_light/mixed_document-chapter.svg +0 -12
  215. novelwriter/assets/icons/typicons_light/mixed_document-new.svg +0 -6
  216. novelwriter/assets/icons/typicons_light/mixed_document-note.svg +0 -12
  217. novelwriter/assets/icons/typicons_light/mixed_document-scene.svg +0 -12
  218. novelwriter/assets/icons/typicons_light/mixed_document-section.svg +0 -12
  219. novelwriter/assets/icons/typicons_light/mixed_document-title.svg +0 -12
  220. novelwriter/assets/icons/typicons_light/mixed_edit.svg +0 -4
  221. novelwriter/assets/icons/typicons_light/mixed_import.svg +0 -5
  222. novelwriter/assets/icons/typicons_light/mixed_input-checked.svg +0 -5
  223. novelwriter/assets/icons/typicons_light/mixed_input-none.svg +0 -5
  224. novelwriter/assets/icons/typicons_light/mixed_input-unchecked.svg +0 -5
  225. novelwriter/assets/icons/typicons_light/mixed_margin-bottom.svg +0 -6
  226. novelwriter/assets/icons/typicons_light/mixed_margin-left.svg +0 -6
  227. novelwriter/assets/icons/typicons_light/mixed_margin-right.svg +0 -6
  228. novelwriter/assets/icons/typicons_light/mixed_margin-top.svg +0 -6
  229. novelwriter/assets/icons/typicons_light/mixed_search-replace.svg +0 -6
  230. novelwriter/assets/icons/typicons_light/mixed_size-height.svg +0 -6
  231. novelwriter/assets/icons/typicons_light/mixed_size-width.svg +0 -6
  232. novelwriter/assets/icons/typicons_light/nw_deco-h0.svg +0 -4
  233. novelwriter/assets/icons/typicons_light/nw_deco-h1.svg +0 -4
  234. novelwriter/assets/icons/typicons_light/nw_deco-h2-narrow.svg +0 -4
  235. novelwriter/assets/icons/typicons_light/nw_deco-h2.svg +0 -4
  236. novelwriter/assets/icons/typicons_light/nw_deco-h3-narrow.svg +0 -4
  237. novelwriter/assets/icons/typicons_light/nw_deco-h3.svg +0 -4
  238. novelwriter/assets/icons/typicons_light/nw_deco-h4-narrow.svg +0 -4
  239. novelwriter/assets/icons/typicons_light/nw_deco-h4.svg +0 -4
  240. novelwriter/assets/icons/typicons_light/nw_deco-note.svg +0 -4
  241. novelwriter/assets/icons/typicons_light/nw_deco-noveltree-more.svg +0 -4
  242. novelwriter/assets/icons/typicons_light/nw_font.svg +0 -4
  243. novelwriter/assets/icons/typicons_light/nw_panel.svg +0 -4
  244. novelwriter/assets/icons/typicons_light/nw_quote.svg +0 -4
  245. novelwriter/assets/icons/typicons_light/nw_search-case.svg +0 -4
  246. novelwriter/assets/icons/typicons_light/nw_search-preserve.svg +0 -4
  247. novelwriter/assets/icons/typicons_light/nw_search-regex.svg +0 -4
  248. novelwriter/assets/icons/typicons_light/nw_search-word.svg +0 -4
  249. novelwriter/assets/icons/typicons_light/nw_tb-bold-md.svg +0 -4
  250. novelwriter/assets/icons/typicons_light/nw_tb-bold.svg +0 -6
  251. novelwriter/assets/icons/typicons_light/nw_tb-italic-md.svg +0 -4
  252. novelwriter/assets/icons/typicons_light/nw_tb-italic.svg +0 -6
  253. novelwriter/assets/icons/typicons_light/nw_tb-mark.svg +0 -7
  254. novelwriter/assets/icons/typicons_light/nw_tb-strike-md.svg +0 -4
  255. novelwriter/assets/icons/typicons_light/nw_tb-strike.svg +0 -6
  256. novelwriter/assets/icons/typicons_light/nw_tb-subscript.svg +0 -7
  257. novelwriter/assets/icons/typicons_light/nw_tb-superscript.svg +0 -7
  258. novelwriter/assets/icons/typicons_light/nw_tb-underline.svg +0 -7
  259. novelwriter/assets/icons/typicons_light/nw_toolbar.svg +0 -5
  260. novelwriter/assets/icons/typicons_light/typ_arrow-down-thick-grey.svg +0 -4
  261. novelwriter/assets/icons/typicons_light/typ_arrow-forward.svg +0 -4
  262. novelwriter/assets/icons/typicons_light/typ_arrow-maximise.svg +0 -4
  263. novelwriter/assets/icons/typicons_light/typ_arrow-minimise.svg +0 -4
  264. novelwriter/assets/icons/typicons_light/typ_arrow-repeat-grey.svg +0 -4
  265. novelwriter/assets/icons/typicons_light/typ_book-grey.svg +0 -4
  266. novelwriter/assets/icons/typicons_light/typ_book.svg +0 -6
  267. novelwriter/assets/icons/typicons_light/typ_bookmark.svg +0 -4
  268. novelwriter/assets/icons/typicons_light/typ_calendar.svg +0 -4
  269. novelwriter/assets/icons/typicons_light/typ_cancel-grey.svg +0 -4
  270. novelwriter/assets/icons/typicons_light/typ_cancel.svg +0 -4
  271. novelwriter/assets/icons/typicons_light/typ_chart-bar-grey.svg +0 -4
  272. novelwriter/assets/icons/typicons_light/typ_chevron-down.svg +0 -4
  273. novelwriter/assets/icons/typicons_light/typ_chevron-left.svg +0 -4
  274. novelwriter/assets/icons/typicons_light/typ_chevron-right.svg +0 -4
  275. novelwriter/assets/icons/typicons_light/typ_chevron-up.svg +0 -4
  276. novelwriter/assets/icons/typicons_light/typ_cog.svg +0 -4
  277. novelwriter/assets/icons/typicons_light/typ_delete-full.svg +0 -4
  278. novelwriter/assets/icons/typicons_light/typ_delete.svg +0 -4
  279. novelwriter/assets/icons/typicons_light/typ_directions-full.svg +0 -4
  280. novelwriter/assets/icons/typicons_light/typ_document-add.svg +0 -4
  281. novelwriter/assets/icons/typicons_light/typ_document-text.svg +0 -5
  282. novelwriter/assets/icons/typicons_light/typ_document.svg +0 -4
  283. novelwriter/assets/icons/typicons_light/typ_export-grey.svg +0 -4
  284. novelwriter/assets/icons/typicons_light/typ_export.svg +0 -4
  285. novelwriter/assets/icons/typicons_light/typ_eye.svg +0 -4
  286. novelwriter/assets/icons/typicons_light/typ_flag.svg +0 -4
  287. novelwriter/assets/icons/typicons_light/typ_folder-open.svg +0 -4
  288. novelwriter/assets/icons/typicons_light/typ_folder.svg +0 -5
  289. novelwriter/assets/icons/typicons_light/typ_globe-grey.svg +0 -4
  290. novelwriter/assets/icons/typicons_light/typ_key.svg +0 -4
  291. novelwriter/assets/icons/typicons_light/typ_lightbulb-full.svg +0 -4
  292. novelwriter/assets/icons/typicons_light/typ_location.svg +0 -4
  293. novelwriter/assets/icons/typicons_light/typ_media-pause-grey.svg +0 -4
  294. novelwriter/assets/icons/typicons_light/typ_media-record-outline.svg +0 -4
  295. novelwriter/assets/icons/typicons_light/typ_media-record.svg +0 -4
  296. novelwriter/assets/icons/typicons_light/typ_minus.svg +0 -4
  297. novelwriter/assets/icons/typicons_light/typ_pencil.svg +0 -5
  298. novelwriter/assets/icons/typicons_light/typ_pin-outline.svg +0 -4
  299. novelwriter/assets/icons/typicons_light/typ_pin.svg +0 -4
  300. novelwriter/assets/icons/typicons_light/typ_plus.svg +0 -4
  301. novelwriter/assets/icons/typicons_light/typ_puzzle-outline.svg +0 -4
  302. novelwriter/assets/icons/typicons_light/typ_puzzle.svg +0 -4
  303. novelwriter/assets/icons/typicons_light/typ_refresh-flipped.svg +0 -4
  304. novelwriter/assets/icons/typicons_light/typ_refresh.svg +0 -4
  305. novelwriter/assets/icons/typicons_light/typ_search-grey.svg +0 -4
  306. novelwriter/assets/icons/typicons_light/typ_search.svg +0 -4
  307. novelwriter/assets/icons/typicons_light/typ_star.svg +0 -4
  308. novelwriter/assets/icons/typicons_light/typ_stopwatch-grey.svg +0 -4
  309. novelwriter/assets/icons/typicons_light/typ_th-dot-menu.svg +0 -4
  310. novelwriter/assets/icons/typicons_light/typ_th-dot-more.svg +0 -4
  311. novelwriter/assets/icons/typicons_light/typ_th-list-grey.svg +0 -4
  312. novelwriter/assets/icons/typicons_light/typ_th-list.svg +0 -9
  313. novelwriter/assets/icons/typicons_light/typ_times.svg +0 -4
  314. novelwriter/assets/icons/typicons_light/typ_trash.svg +0 -5
  315. novelwriter/assets/icons/typicons_light/typ_unfold-hidden.svg +0 -4
  316. novelwriter/assets/icons/typicons_light/typ_unfold-visible.svg +0 -4
  317. novelwriter/assets/icons/typicons_light/typ_user.svg +0 -5
  318. novelwriter/assets/icons/typicons_light/typ_warning-full.svg +0 -4
  319. {novelWriter-2.6.3.dist-info → novelwriter-2.7b1.dist-info}/entry_points.txt +0 -0
  320. {novelWriter-2.6.3.dist-info → novelwriter-2.7b1.dist-info/licenses}/LICENSE.md +0 -0
  321. {novelWriter-2.6.3.dist-info → novelwriter-2.7b1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,403 @@
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
+ return
320
+
321
+ def setTag(self, tag: str) -> None:
322
+ """Set the tag for references, and make sure it is a string."""
323
+ self._tag = str(tag).lower()
324
+ return
325
+
326
+ def addReference(self, tag: str, keyword: str) -> None:
327
+ """Add a record of a reference tag, and what keyword types it is
328
+ associated with.
329
+ """
330
+ if keyword in nwKeyWords.VALID_KEYS:
331
+ tag = tag.lower()
332
+ if tag not in self._refs:
333
+ self._refs[tag] = set()
334
+ self._refs[tag].add(keyword)
335
+ return
336
+
337
+ ##
338
+ # Getters
339
+ ##
340
+
341
+ def getReferences(self) -> dict[str, list[str]]:
342
+ """Extract all references for this heading."""
343
+ refs = {x: [] for x in nwKeyWords.VALID_KEYS}
344
+ for tag, types in self._refs.items():
345
+ for keyword in types:
346
+ if keyword in refs and (name := self._cache.tags.tagName(tag)):
347
+ refs[keyword].append(name)
348
+ return refs
349
+
350
+ def getReferencesByKeyword(self, keyword: str) -> list[str]:
351
+ """Extract all references for this heading."""
352
+ refs = []
353
+ for tag, types in self._refs.items():
354
+ if keyword in types and (name := self._cache.tags.tagName(tag)):
355
+ refs.append(name)
356
+ return refs
357
+
358
+ ##
359
+ # Data Methods
360
+ ##
361
+
362
+ def packData(self) -> dict:
363
+ """Pack the values into a dictionary for saving to cache."""
364
+ data = {}
365
+ data["meta"] = {
366
+ "level": self._level,
367
+ "title": self._title,
368
+ "line": self._line,
369
+ "tag": self._tag,
370
+ "counts": self._counts,
371
+ }
372
+ if self._refs:
373
+ data["refs"] = {k: ",".join(sorted(list(v))) for k, v in self._refs.items()}
374
+ if self._comments:
375
+ data.update(self._comments)
376
+ return data
377
+
378
+ def unpackData(self, data: dict) -> None:
379
+ """Unpack a heading entry from a dictionary."""
380
+ for key, entry in data.items():
381
+ if key == "meta":
382
+ self.setLevel(entry.get("level", "H0"))
383
+ self._title = str(entry.get("title", ""))
384
+ self._tag = str(entry.get("tag", ""))
385
+ self.setLine(entry.get("line", 0))
386
+ self.setCounts(entry.get("counts", [0, 0, 0]))
387
+ elif key == "refs":
388
+ for tag, value in entry.items():
389
+ if not isinstance(tag, str):
390
+ raise ValueError("Heading reference key must be a string")
391
+ if not isinstance(value, str):
392
+ raise ValueError("Heading reference value must be a string")
393
+ for keyword in value.split(","):
394
+ if keyword in nwKeyWords.VALID_KEYS:
395
+ self.addReference(tag, keyword)
396
+ else:
397
+ raise ValueError("Heading reference contains an invalid keyword")
398
+ elif key == "summary" or key.startswith("story"):
399
+ comment, _, kind = str(key).partition(".")
400
+ self.setComment(comment, compact(kind), str(entry))
401
+ else:
402
+ raise KeyError("Unknown key in heading entry")
403
+ 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", "_class", "_cursorPos", "_expanded",
57
+ "_handle", "_heading", "_import", "_initCount", "_layout", "_name",
58
+ "_order", "_paraCount", "_parent", "_project", "_root", "_status",
59
+ "_type", "_wordCount",
60
60
  )
61
61
 
62
62
  def __init__(self, project: NWProject, handle: str) -> None:
@@ -264,25 +264,25 @@ class NWItem:
264
264
  @classmethod
265
265
  def duplicate(cls, source: NWItem, handle: str) -> NWItem:
266
266
  """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
267
+ new = cls(source._project, handle)
268
+ new._name = source._name
269
+ new._parent = source._parent
270
+ new._root = source._root
271
+ new._order = source._order
272
+ new._type = source._type
273
+ new._class = source._class
274
+ new._layout = source._layout
275
+ new._status = source._status
276
+ new._import = source._import
277
+ new._active = source._active
278
+ new._expanded = source._expanded
279
+ new._heading = source._heading
280
+ new._charCount = source._charCount
281
+ new._wordCount = source._wordCount
282
+ new._paraCount = source._paraCount
283
+ new._cursorPos = source._cursorPos
284
+ new._initCount = source._initCount
285
+ return new
286
286
 
287
287
  ##
288
288
  # Action Methods
@@ -293,6 +293,12 @@ class NWItem:
293
293
  self._project.tree.refreshItems([self._handle])
294
294
  return
295
295
 
296
+ def notifyNovelStructureChange(self) -> None:
297
+ """Notify that the structure of a novel has changed."""
298
+ if self._root and self._class == nwItemClass.NOVEL:
299
+ self._project.tree.novelStructureChanged(self._root)
300
+ return
301
+
296
302
  ##
297
303
  # Lookup Methods
298
304
  ##
@@ -350,11 +356,12 @@ class NWItem:
350
356
  """
351
357
  if self.isFileType():
352
358
  key = "checked" if self._active else "unchecked"
359
+ color = "green" if self._active else "red"
353
360
  text = trConst(nwLabels.ACTIVE_NAME[key])
354
- icon = SHARED.theme.getIcon(key)
361
+ icon = SHARED.theme.getIcon(key, color)
355
362
  else:
356
363
  text = ""
357
- icon = SHARED.theme.getIcon("noncheckable")
364
+ icon = SHARED.theme.getIcon("noncheckable", "faded")
358
365
  return text, icon
359
366
 
360
367
  ##
@@ -28,8 +28,8 @@ import logging
28
28
 
29
29
  from typing import TYPE_CHECKING
30
30
 
31
- from PyQt5.QtCore import QAbstractItemModel, QMimeData, QModelIndex, Qt
32
- from PyQt5.QtGui import QFont, QIcon
31
+ from PyQt6.QtCore import QAbstractItemModel, QMimeData, QModelIndex, Qt
32
+ from PyQt6.QtGui import QFont, QIcon
33
33
 
34
34
  from novelwriter.common import decodeMimeHandles, encodeMimeHandles, minmax
35
35
  from novelwriter.constants import nwConst
@@ -37,7 +37,7 @@ from novelwriter.core.item import NWItem
37
37
  from novelwriter.enum import nwItemClass
38
38
  from novelwriter.types import QtAlignRight
39
39
 
40
- if TYPE_CHECKING: # pragma: no cover
40
+ if TYPE_CHECKING:
41
41
  from novelwriter.core.tree import NWTree
42
42
 
43
43
  logger = logging.getLogger(__name__)
@@ -60,9 +60,7 @@ NODE_FLAGS = Qt.ItemFlag.ItemIsEnabled
60
60
  NODE_FLAGS |= Qt.ItemFlag.ItemIsSelectable
61
61
  NODE_FLAGS |= Qt.ItemFlag.ItemIsDropEnabled
62
62
 
63
- if TYPE_CHECKING: # pragma: no cover
64
- # Requires Python 3.10
65
- T_NodeData = str | QIcon | QFont | Qt.AlignmentFlag | None
63
+ T_NodeData = str | QIcon | QFont | Qt.AlignmentFlag | None
66
64
 
67
65
 
68
66
  class ProjectNode:
@@ -91,7 +89,7 @@ class ProjectNode:
91
89
  C_ACTIVE = 2
92
90
  C_STATUS = 3
93
91
 
94
- __slots__ = ("_item", "_children", "_parent", "_row", "_cache", "_flags", "_count")
92
+ __slots__ = ("_cache", "_children", "_count", "_flags", "_item", "_parent", "_row")
95
93
 
96
94
  def __init__(self, item: NWItem) -> None:
97
95
  self._item = item
@@ -164,7 +162,7 @@ class ProjectNode:
164
162
 
165
163
  def updateCount(self, propagate: bool = True) -> None:
166
164
  """Update counts, and propagate upwards in the tree."""
167
- self._count = self._item.wordCount + sum(c._count for c in self._children)
165
+ self._count = self._item.wordCount + sum(c._count for c in self._children) # noqa: SLF001
168
166
  self._cache[C_COUNT_TEXT] = f"{self._count:n}"
169
167
  if propagate and (parent := self._parent):
170
168
  parent.updateCount()
@@ -195,7 +193,7 @@ class ProjectNode:
195
193
  return self._parent
196
194
 
197
195
  def child(self, row: int) -> ProjectNode | None:
198
- """Return a child ofg the node."""
196
+ """Return a child of the node."""
199
197
  if 0 <= row < len(self._children):
200
198
  return self._children[row]
201
199
  return None
@@ -220,6 +218,7 @@ class ProjectNode:
220
218
  child._row = len(self._children)
221
219
  self._children.append(child)
222
220
  self._refreshChildrenPos()
221
+ self._item.notifyNovelStructureChange()
223
222
  return
224
223
 
225
224
  def takeChild(self, pos: int) -> ProjectNode | None:
@@ -228,6 +227,7 @@ class ProjectNode:
228
227
  node = self._children.pop(pos)
229
228
  self._refreshChildrenPos()
230
229
  self.updateCount()
230
+ self._item.notifyNovelStructureChange()
231
231
  return node
232
232
  return None
233
233
 
@@ -238,6 +238,7 @@ class ProjectNode:
238
238
  node = self._children.pop(source)
239
239
  self._children.insert(target, node)
240
240
  self._refreshChildrenPos()
241
+ self._item.notifyNovelStructureChange()
241
242
  return
242
243
 
243
244
  def setExpanded(self, state: bool) -> None:
@@ -256,13 +257,13 @@ class ProjectNode:
256
257
  """Recursively add all nodes to a list."""
257
258
  for node in self._children:
258
259
  children.append(node)
259
- node._recursiveAppendChildren(children)
260
+ node._recursiveAppendChildren(children) # noqa: SLF001
260
261
  return
261
262
 
262
263
  def _refreshChildrenPos(self) -> None:
263
264
  """Update the row value on all children."""
264
265
  for n, child in enumerate(self._children):
265
- child._row = n
266
+ child._row = n # noqa: SLF001
266
267
  child.item.setOrder(n)
267
268
  return
268
269
 
@@ -289,12 +290,12 @@ class ProjectModel(QAbstractItemModel):
289
290
  methods needed primarily by the project tree GUI component.
290
291
  """
291
292
 
292
- __slots__ = ("_tree", "_root")
293
+ __slots__ = ("_root", "_tree")
293
294
 
294
295
  def __init__(self, tree: NWTree) -> None:
295
296
  super().__init__()
296
297
  self._tree = tree
297
- self._root = ProjectNode(NWItem(tree._project, INV_ROOT))
298
+ self._root = ProjectNode(NWItem(tree.project, INV_ROOT))
298
299
  self._root.item.setName("Invisible Root")
299
300
  logger.debug("Ready: ProjectModel")
300
301
  return
@@ -332,8 +333,9 @@ class ProjectModel(QAbstractItemModel):
332
333
  return self.createIndex(parent.row(), 0, parent)
333
334
  return QModelIndex()
334
335
 
335
- def index(self, row: int, column: int, parent: QModelIndex = QModelIndex()) -> QModelIndex:
336
- """get the index of a child item of a parent."""
336
+ def index(self, row: int, column: int, parent: QModelIndex | None = None) -> QModelIndex:
337
+ """Get the index of a child item of a parent."""
338
+ parent = parent or QModelIndex()
337
339
  if self.hasIndex(row, column, parent):
338
340
  node: ProjectNode = parent.internalPointer() if parent.isValid() else self._root
339
341
  if child := node.child(row):
@@ -389,10 +391,10 @@ class ProjectModel(QAbstractItemModel):
389
391
  ) -> bool:
390
392
  """Process mime data drop."""
391
393
  if self.canDropMimeData(data, action, row, column, parent):
392
- items = []
393
- for handle in decodeMimeHandles(data):
394
- if (index := self.indexFromHandle(handle)).isValid():
395
- items.append(index)
394
+ items = [
395
+ index for handle in decodeMimeHandles(data)
396
+ if (index := self.indexFromHandle(handle)).isValid()
397
+ ]
396
398
  self.multiMove(items, parent, row)
397
399
  return True
398
400
  return False
@@ -488,7 +490,7 @@ class ProjectModel(QAbstractItemModel):
488
490
  if temp := self.removeChild(index.parent(), index.row()):
489
491
  self.insertChild(temp, target, pos)
490
492
  for child in reversed(node.allChildren()):
491
- node._updateRelationships(child)
493
+ node._updateRelationships(child) # noqa: SLF001
492
494
  child.item.notifyToRefresh()
493
495
  node.item.notifyToRefresh()
494
496
  return
@@ -499,16 +501,15 @@ class ProjectModel(QAbstractItemModel):
499
501
 
500
502
  def clear(self) -> None:
501
503
  """Clear the project model."""
502
- self._root._children.clear()
504
+ self._root.children.clear()
503
505
  return
504
506
 
505
507
  def allExpanded(self) -> list[QModelIndex]:
506
508
  """Return a list of all expanded items."""
507
- expanded = []
508
- for node in self._root.allChildren():
509
- if node._item.isExpanded:
510
- expanded.append(self.createIndex(node.row(), 0, node))
511
- return expanded
509
+ return [
510
+ self.createIndex(node.row(), 0, node) for node in self._root.allChildren()
511
+ if node.item.isExpanded
512
+ ]
512
513
 
513
514
  def trashSelection(self, indices: list[QModelIndex]) -> bool:
514
515
  """Check if a selection of indices are all in trash or not."""