novelWriter 2.6.3__py3-none-any.whl → 2.7__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 (348) hide show
  1. novelwriter/__init__.py +98 -75
  2. novelwriter/assets/i18n/nw_cs_CZ.qm +0 -0
  3. novelwriter/assets/i18n/nw_de_DE.qm +0 -0
  4. novelwriter/assets/i18n/nw_en_US.qm +0 -0
  5. novelwriter/assets/i18n/nw_es_419.qm +0 -0
  6. novelwriter/assets/i18n/nw_fr_FR.qm +0 -0
  7. novelwriter/assets/i18n/nw_it_IT.qm +0 -0
  8. novelwriter/assets/i18n/nw_ja_JP.qm +0 -0
  9. novelwriter/assets/i18n/nw_nb_NO.qm +0 -0
  10. novelwriter/assets/i18n/nw_nl_NL.qm +0 -0
  11. novelwriter/assets/i18n/nw_pl_PL.qm +0 -0
  12. novelwriter/assets/i18n/nw_pt_BR.qm +0 -0
  13. novelwriter/assets/i18n/nw_ru_RU.qm +0 -0
  14. novelwriter/assets/i18n/nw_zh_CN.qm +0 -0
  15. novelwriter/assets/i18n/project_de_DE.json +3 -1
  16. novelwriter/assets/i18n/project_en_GB.json +2 -0
  17. novelwriter/assets/i18n/project_en_US.json +2 -0
  18. novelwriter/assets/i18n/project_it_IT.json +2 -0
  19. novelwriter/assets/i18n/project_ja_JP.json +2 -0
  20. novelwriter/assets/i18n/project_nb_NO.json +2 -0
  21. novelwriter/assets/i18n/project_nn_NO.json +5 -0
  22. novelwriter/assets/i18n/project_pl_PL.json +2 -0
  23. novelwriter/assets/i18n/project_pt_BR.json +2 -0
  24. novelwriter/assets/i18n/project_ru_RU.json +2 -0
  25. novelwriter/assets/i18n/project_zh_CN.json +2 -0
  26. novelwriter/assets/icons/font_awesome.icons +109 -0
  27. novelwriter/assets/icons/material_filled_bold.icons +109 -0
  28. novelwriter/assets/icons/material_filled_normal.icons +109 -0
  29. novelwriter/assets/icons/material_filled_thin.icons +109 -0
  30. novelwriter/assets/icons/material_rounded_bold.icons +109 -0
  31. novelwriter/assets/icons/material_rounded_normal.icons +109 -0
  32. novelwriter/assets/icons/material_rounded_thin.icons +109 -0
  33. novelwriter/assets/icons/remix_filled.icons +109 -0
  34. novelwriter/assets/icons/remix_outline.icons +109 -0
  35. novelwriter/assets/images/splash.png +0 -0
  36. novelwriter/assets/manual.pdf +0 -0
  37. novelwriter/assets/{manual_fr_FR.pdf → manual_fr.pdf} +0 -0
  38. novelwriter/assets/sample.zip +0 -0
  39. novelwriter/assets/syntax/cyberpunk_night.conf +1 -1
  40. novelwriter/assets/syntax/snazzy.conf +3 -3
  41. novelwriter/assets/text/credits_en.htm +12 -6
  42. novelwriter/assets/themes/cyberpunk_night.conf +23 -7
  43. novelwriter/assets/themes/default_dark.conf +20 -4
  44. novelwriter/assets/themes/default_light.conf +21 -5
  45. novelwriter/assets/themes/dracula.conf +20 -4
  46. novelwriter/assets/themes/snazzy.conf +48 -0
  47. novelwriter/assets/themes/solarized_dark.conf +24 -8
  48. novelwriter/assets/themes/solarized_light.conf +22 -6
  49. novelwriter/common.py +43 -27
  50. novelwriter/config.py +201 -139
  51. novelwriter/constants.py +67 -36
  52. novelwriter/core/buildsettings.py +26 -17
  53. novelwriter/core/coretools.py +52 -41
  54. novelwriter/core/docbuild.py +20 -13
  55. novelwriter/core/document.py +2 -2
  56. novelwriter/core/index.py +166 -432
  57. novelwriter/core/indexdata.py +406 -0
  58. novelwriter/core/item.py +50 -32
  59. novelwriter/core/itemmodel.py +43 -38
  60. novelwriter/core/novelmodel.py +225 -0
  61. novelwriter/core/options.py +1 -1
  62. novelwriter/core/project.py +24 -25
  63. novelwriter/core/projectdata.py +47 -29
  64. novelwriter/core/projectxml.py +18 -8
  65. novelwriter/core/sessions.py +32 -15
  66. novelwriter/core/spellcheck.py +4 -3
  67. novelwriter/core/status.py +12 -15
  68. novelwriter/core/storage.py +1 -1
  69. novelwriter/core/tree.py +55 -13
  70. novelwriter/dialogs/about.py +19 -22
  71. novelwriter/dialogs/docmerge.py +23 -24
  72. novelwriter/dialogs/docsplit.py +26 -26
  73. novelwriter/dialogs/editlabel.py +19 -20
  74. novelwriter/dialogs/preferences.py +143 -69
  75. novelwriter/dialogs/projectsettings.py +51 -54
  76. novelwriter/dialogs/quotes.py +14 -19
  77. novelwriter/dialogs/wordlist.py +25 -30
  78. novelwriter/enum.py +8 -0
  79. novelwriter/error.py +16 -16
  80. novelwriter/extensions/configlayout.py +24 -20
  81. novelwriter/extensions/eventfilters.py +9 -5
  82. novelwriter/extensions/modified.py +34 -15
  83. novelwriter/extensions/novelselector.py +18 -5
  84. novelwriter/extensions/pagedsidebar.py +39 -49
  85. novelwriter/extensions/progressbars.py +10 -8
  86. novelwriter/extensions/statusled.py +6 -13
  87. novelwriter/extensions/switch.py +31 -41
  88. novelwriter/extensions/switchbox.py +8 -3
  89. novelwriter/extensions/versioninfo.py +4 -4
  90. novelwriter/formats/shared.py +1 -1
  91. novelwriter/formats/todocx.py +14 -10
  92. novelwriter/formats/tohtml.py +7 -5
  93. novelwriter/formats/tokenizer.py +37 -33
  94. novelwriter/formats/tomarkdown.py +6 -2
  95. novelwriter/formats/toodt.py +27 -22
  96. novelwriter/formats/toqdoc.py +19 -14
  97. novelwriter/formats/toraw.py +6 -2
  98. novelwriter/gui/doceditor.py +314 -305
  99. novelwriter/gui/dochighlight.py +46 -45
  100. novelwriter/gui/docviewer.py +102 -104
  101. novelwriter/gui/docviewerpanel.py +47 -51
  102. novelwriter/gui/editordocument.py +8 -5
  103. novelwriter/gui/itemdetails.py +11 -14
  104. novelwriter/gui/mainmenu.py +146 -145
  105. novelwriter/gui/noveltree.py +246 -406
  106. novelwriter/gui/outline.py +141 -72
  107. novelwriter/gui/projtree.py +139 -132
  108. novelwriter/gui/search.py +34 -31
  109. novelwriter/gui/sidebar.py +13 -18
  110. novelwriter/gui/statusbar.py +27 -21
  111. novelwriter/gui/theme.py +554 -436
  112. novelwriter/guimain.py +56 -38
  113. novelwriter/shared.py +32 -23
  114. novelwriter/splash.py +74 -0
  115. novelwriter/text/comments.py +70 -0
  116. novelwriter/text/patterns.py +4 -4
  117. novelwriter/tools/dictionaries.py +20 -29
  118. novelwriter/tools/lipsum.py +18 -18
  119. novelwriter/tools/manusbuild.py +39 -42
  120. novelwriter/tools/manuscript.py +102 -115
  121. novelwriter/tools/manussettings.py +129 -102
  122. novelwriter/tools/noveldetails.py +60 -72
  123. novelwriter/tools/welcome.py +57 -75
  124. novelwriter/tools/writingstats.py +112 -102
  125. novelwriter/types.py +5 -7
  126. {novelWriter-2.6.3.dist-info → novelwriter-2.7.dist-info}/METADATA +6 -6
  127. novelwriter-2.7.dist-info/RECORD +162 -0
  128. {novelWriter-2.6.3.dist-info → novelwriter-2.7.dist-info}/WHEEL +1 -1
  129. novelWriter-2.6.3.dist-info/RECORD +0 -363
  130. novelwriter/assets/icons/typicons_dark/README.md +0 -29
  131. novelwriter/assets/icons/typicons_dark/icons.conf +0 -134
  132. novelwriter/assets/icons/typicons_dark/mixed_copy.svg +0 -4
  133. novelwriter/assets/icons/typicons_dark/mixed_document-chapter.svg +0 -12
  134. novelwriter/assets/icons/typicons_dark/mixed_document-new.svg +0 -6
  135. novelwriter/assets/icons/typicons_dark/mixed_document-note.svg +0 -12
  136. novelwriter/assets/icons/typicons_dark/mixed_document-scene.svg +0 -12
  137. novelwriter/assets/icons/typicons_dark/mixed_document-section.svg +0 -12
  138. novelwriter/assets/icons/typicons_dark/mixed_document-title.svg +0 -12
  139. novelwriter/assets/icons/typicons_dark/mixed_edit.svg +0 -4
  140. novelwriter/assets/icons/typicons_dark/mixed_import.svg +0 -5
  141. novelwriter/assets/icons/typicons_dark/mixed_input-checked.svg +0 -5
  142. novelwriter/assets/icons/typicons_dark/mixed_input-none.svg +0 -5
  143. novelwriter/assets/icons/typicons_dark/mixed_input-unchecked.svg +0 -5
  144. novelwriter/assets/icons/typicons_dark/mixed_margin-bottom.svg +0 -6
  145. novelwriter/assets/icons/typicons_dark/mixed_margin-left.svg +0 -6
  146. novelwriter/assets/icons/typicons_dark/mixed_margin-right.svg +0 -6
  147. novelwriter/assets/icons/typicons_dark/mixed_margin-top.svg +0 -6
  148. novelwriter/assets/icons/typicons_dark/mixed_search-replace.svg +0 -6
  149. novelwriter/assets/icons/typicons_dark/mixed_size-height.svg +0 -6
  150. novelwriter/assets/icons/typicons_dark/mixed_size-width.svg +0 -6
  151. novelwriter/assets/icons/typicons_dark/nw_deco-h0.svg +0 -4
  152. novelwriter/assets/icons/typicons_dark/nw_deco-h1.svg +0 -4
  153. novelwriter/assets/icons/typicons_dark/nw_deco-h2-narrow.svg +0 -4
  154. novelwriter/assets/icons/typicons_dark/nw_deco-h2.svg +0 -4
  155. novelwriter/assets/icons/typicons_dark/nw_deco-h3-narrow.svg +0 -4
  156. novelwriter/assets/icons/typicons_dark/nw_deco-h3.svg +0 -4
  157. novelwriter/assets/icons/typicons_dark/nw_deco-h4-narrow.svg +0 -4
  158. novelwriter/assets/icons/typicons_dark/nw_deco-h4.svg +0 -4
  159. novelwriter/assets/icons/typicons_dark/nw_deco-note.svg +0 -4
  160. novelwriter/assets/icons/typicons_dark/nw_deco-noveltree-more.svg +0 -4
  161. novelwriter/assets/icons/typicons_dark/nw_font.svg +0 -4
  162. novelwriter/assets/icons/typicons_dark/nw_panel.svg +0 -4
  163. novelwriter/assets/icons/typicons_dark/nw_quote.svg +0 -4
  164. novelwriter/assets/icons/typicons_dark/nw_search-case.svg +0 -4
  165. novelwriter/assets/icons/typicons_dark/nw_search-preserve.svg +0 -4
  166. novelwriter/assets/icons/typicons_dark/nw_search-regex.svg +0 -4
  167. novelwriter/assets/icons/typicons_dark/nw_search-word.svg +0 -4
  168. novelwriter/assets/icons/typicons_dark/nw_tb-bold-md.svg +0 -4
  169. novelwriter/assets/icons/typicons_dark/nw_tb-bold.svg +0 -6
  170. novelwriter/assets/icons/typicons_dark/nw_tb-italic-md.svg +0 -4
  171. novelwriter/assets/icons/typicons_dark/nw_tb-italic.svg +0 -6
  172. novelwriter/assets/icons/typicons_dark/nw_tb-mark.svg +0 -7
  173. novelwriter/assets/icons/typicons_dark/nw_tb-strike-md.svg +0 -4
  174. novelwriter/assets/icons/typicons_dark/nw_tb-strike.svg +0 -6
  175. novelwriter/assets/icons/typicons_dark/nw_tb-subscript.svg +0 -7
  176. novelwriter/assets/icons/typicons_dark/nw_tb-superscript.svg +0 -7
  177. novelwriter/assets/icons/typicons_dark/nw_tb-underline.svg +0 -7
  178. novelwriter/assets/icons/typicons_dark/nw_toolbar.svg +0 -5
  179. novelwriter/assets/icons/typicons_dark/typ_arrow-down-thick-grey.svg +0 -4
  180. novelwriter/assets/icons/typicons_dark/typ_arrow-forward.svg +0 -4
  181. novelwriter/assets/icons/typicons_dark/typ_arrow-maximise.svg +0 -4
  182. novelwriter/assets/icons/typicons_dark/typ_arrow-minimise.svg +0 -4
  183. novelwriter/assets/icons/typicons_dark/typ_arrow-repeat-grey.svg +0 -4
  184. novelwriter/assets/icons/typicons_dark/typ_book-grey.svg +0 -4
  185. novelwriter/assets/icons/typicons_dark/typ_book.svg +0 -6
  186. novelwriter/assets/icons/typicons_dark/typ_bookmark.svg +0 -4
  187. novelwriter/assets/icons/typicons_dark/typ_calendar.svg +0 -4
  188. novelwriter/assets/icons/typicons_dark/typ_cancel-grey.svg +0 -4
  189. novelwriter/assets/icons/typicons_dark/typ_cancel.svg +0 -4
  190. novelwriter/assets/icons/typicons_dark/typ_chart-bar-grey.svg +0 -4
  191. novelwriter/assets/icons/typicons_dark/typ_chevron-down.svg +0 -4
  192. novelwriter/assets/icons/typicons_dark/typ_chevron-left.svg +0 -4
  193. novelwriter/assets/icons/typicons_dark/typ_chevron-right.svg +0 -4
  194. novelwriter/assets/icons/typicons_dark/typ_chevron-up.svg +0 -4
  195. novelwriter/assets/icons/typicons_dark/typ_cog.svg +0 -4
  196. novelwriter/assets/icons/typicons_dark/typ_delete-full.svg +0 -4
  197. novelwriter/assets/icons/typicons_dark/typ_delete.svg +0 -4
  198. novelwriter/assets/icons/typicons_dark/typ_directions-full.svg +0 -4
  199. novelwriter/assets/icons/typicons_dark/typ_document-add.svg +0 -4
  200. novelwriter/assets/icons/typicons_dark/typ_document-text.svg +0 -8
  201. novelwriter/assets/icons/typicons_dark/typ_document.svg +0 -4
  202. novelwriter/assets/icons/typicons_dark/typ_export-grey.svg +0 -4
  203. novelwriter/assets/icons/typicons_dark/typ_export.svg +0 -4
  204. novelwriter/assets/icons/typicons_dark/typ_eye.svg +0 -4
  205. novelwriter/assets/icons/typicons_dark/typ_flag.svg +0 -4
  206. novelwriter/assets/icons/typicons_dark/typ_folder-open.svg +0 -4
  207. novelwriter/assets/icons/typicons_dark/typ_folder.svg +0 -5
  208. novelwriter/assets/icons/typicons_dark/typ_globe-grey.svg +0 -4
  209. novelwriter/assets/icons/typicons_dark/typ_key.svg +0 -4
  210. novelwriter/assets/icons/typicons_dark/typ_lightbulb-full.svg +0 -4
  211. novelwriter/assets/icons/typicons_dark/typ_location.svg +0 -4
  212. novelwriter/assets/icons/typicons_dark/typ_media-pause-grey.svg +0 -4
  213. novelwriter/assets/icons/typicons_dark/typ_media-record-outline.svg +0 -4
  214. novelwriter/assets/icons/typicons_dark/typ_media-record.svg +0 -4
  215. novelwriter/assets/icons/typicons_dark/typ_minus.svg +0 -4
  216. novelwriter/assets/icons/typicons_dark/typ_pencil.svg +0 -5
  217. novelwriter/assets/icons/typicons_dark/typ_pin-outline.svg +0 -4
  218. novelwriter/assets/icons/typicons_dark/typ_pin.svg +0 -4
  219. novelwriter/assets/icons/typicons_dark/typ_plus.svg +0 -4
  220. novelwriter/assets/icons/typicons_dark/typ_puzzle-outline.svg +0 -4
  221. novelwriter/assets/icons/typicons_dark/typ_puzzle.svg +0 -4
  222. novelwriter/assets/icons/typicons_dark/typ_refresh-flipped.svg +0 -4
  223. novelwriter/assets/icons/typicons_dark/typ_refresh.svg +0 -4
  224. novelwriter/assets/icons/typicons_dark/typ_search-grey.svg +0 -4
  225. novelwriter/assets/icons/typicons_dark/typ_search.svg +0 -4
  226. novelwriter/assets/icons/typicons_dark/typ_star.svg +0 -4
  227. novelwriter/assets/icons/typicons_dark/typ_stopwatch-grey.svg +0 -4
  228. novelwriter/assets/icons/typicons_dark/typ_th-dot-menu.svg +0 -4
  229. novelwriter/assets/icons/typicons_dark/typ_th-dot-more.svg +0 -4
  230. novelwriter/assets/icons/typicons_dark/typ_th-list-grey.svg +0 -4
  231. novelwriter/assets/icons/typicons_dark/typ_th-list.svg +0 -9
  232. novelwriter/assets/icons/typicons_dark/typ_times.svg +0 -4
  233. novelwriter/assets/icons/typicons_dark/typ_trash.svg +0 -5
  234. novelwriter/assets/icons/typicons_dark/typ_unfold-hidden.svg +0 -4
  235. novelwriter/assets/icons/typicons_dark/typ_unfold-visible.svg +0 -4
  236. novelwriter/assets/icons/typicons_dark/typ_user.svg +0 -5
  237. novelwriter/assets/icons/typicons_dark/typ_warning-full.svg +0 -4
  238. novelwriter/assets/icons/typicons_light/README.md +0 -29
  239. novelwriter/assets/icons/typicons_light/icons.conf +0 -134
  240. novelwriter/assets/icons/typicons_light/mixed_copy.svg +0 -4
  241. novelwriter/assets/icons/typicons_light/mixed_document-chapter.svg +0 -12
  242. novelwriter/assets/icons/typicons_light/mixed_document-new.svg +0 -6
  243. novelwriter/assets/icons/typicons_light/mixed_document-note.svg +0 -12
  244. novelwriter/assets/icons/typicons_light/mixed_document-scene.svg +0 -12
  245. novelwriter/assets/icons/typicons_light/mixed_document-section.svg +0 -12
  246. novelwriter/assets/icons/typicons_light/mixed_document-title.svg +0 -12
  247. novelwriter/assets/icons/typicons_light/mixed_edit.svg +0 -4
  248. novelwriter/assets/icons/typicons_light/mixed_import.svg +0 -5
  249. novelwriter/assets/icons/typicons_light/mixed_input-checked.svg +0 -5
  250. novelwriter/assets/icons/typicons_light/mixed_input-none.svg +0 -5
  251. novelwriter/assets/icons/typicons_light/mixed_input-unchecked.svg +0 -5
  252. novelwriter/assets/icons/typicons_light/mixed_margin-bottom.svg +0 -6
  253. novelwriter/assets/icons/typicons_light/mixed_margin-left.svg +0 -6
  254. novelwriter/assets/icons/typicons_light/mixed_margin-right.svg +0 -6
  255. novelwriter/assets/icons/typicons_light/mixed_margin-top.svg +0 -6
  256. novelwriter/assets/icons/typicons_light/mixed_search-replace.svg +0 -6
  257. novelwriter/assets/icons/typicons_light/mixed_size-height.svg +0 -6
  258. novelwriter/assets/icons/typicons_light/mixed_size-width.svg +0 -6
  259. novelwriter/assets/icons/typicons_light/nw_deco-h0.svg +0 -4
  260. novelwriter/assets/icons/typicons_light/nw_deco-h1.svg +0 -4
  261. novelwriter/assets/icons/typicons_light/nw_deco-h2-narrow.svg +0 -4
  262. novelwriter/assets/icons/typicons_light/nw_deco-h2.svg +0 -4
  263. novelwriter/assets/icons/typicons_light/nw_deco-h3-narrow.svg +0 -4
  264. novelwriter/assets/icons/typicons_light/nw_deco-h3.svg +0 -4
  265. novelwriter/assets/icons/typicons_light/nw_deco-h4-narrow.svg +0 -4
  266. novelwriter/assets/icons/typicons_light/nw_deco-h4.svg +0 -4
  267. novelwriter/assets/icons/typicons_light/nw_deco-note.svg +0 -4
  268. novelwriter/assets/icons/typicons_light/nw_deco-noveltree-more.svg +0 -4
  269. novelwriter/assets/icons/typicons_light/nw_font.svg +0 -4
  270. novelwriter/assets/icons/typicons_light/nw_panel.svg +0 -4
  271. novelwriter/assets/icons/typicons_light/nw_quote.svg +0 -4
  272. novelwriter/assets/icons/typicons_light/nw_search-case.svg +0 -4
  273. novelwriter/assets/icons/typicons_light/nw_search-preserve.svg +0 -4
  274. novelwriter/assets/icons/typicons_light/nw_search-regex.svg +0 -4
  275. novelwriter/assets/icons/typicons_light/nw_search-word.svg +0 -4
  276. novelwriter/assets/icons/typicons_light/nw_tb-bold-md.svg +0 -4
  277. novelwriter/assets/icons/typicons_light/nw_tb-bold.svg +0 -6
  278. novelwriter/assets/icons/typicons_light/nw_tb-italic-md.svg +0 -4
  279. novelwriter/assets/icons/typicons_light/nw_tb-italic.svg +0 -6
  280. novelwriter/assets/icons/typicons_light/nw_tb-mark.svg +0 -7
  281. novelwriter/assets/icons/typicons_light/nw_tb-strike-md.svg +0 -4
  282. novelwriter/assets/icons/typicons_light/nw_tb-strike.svg +0 -6
  283. novelwriter/assets/icons/typicons_light/nw_tb-subscript.svg +0 -7
  284. novelwriter/assets/icons/typicons_light/nw_tb-superscript.svg +0 -7
  285. novelwriter/assets/icons/typicons_light/nw_tb-underline.svg +0 -7
  286. novelwriter/assets/icons/typicons_light/nw_toolbar.svg +0 -5
  287. novelwriter/assets/icons/typicons_light/typ_arrow-down-thick-grey.svg +0 -4
  288. novelwriter/assets/icons/typicons_light/typ_arrow-forward.svg +0 -4
  289. novelwriter/assets/icons/typicons_light/typ_arrow-maximise.svg +0 -4
  290. novelwriter/assets/icons/typicons_light/typ_arrow-minimise.svg +0 -4
  291. novelwriter/assets/icons/typicons_light/typ_arrow-repeat-grey.svg +0 -4
  292. novelwriter/assets/icons/typicons_light/typ_book-grey.svg +0 -4
  293. novelwriter/assets/icons/typicons_light/typ_book.svg +0 -6
  294. novelwriter/assets/icons/typicons_light/typ_bookmark.svg +0 -4
  295. novelwriter/assets/icons/typicons_light/typ_calendar.svg +0 -4
  296. novelwriter/assets/icons/typicons_light/typ_cancel-grey.svg +0 -4
  297. novelwriter/assets/icons/typicons_light/typ_cancel.svg +0 -4
  298. novelwriter/assets/icons/typicons_light/typ_chart-bar-grey.svg +0 -4
  299. novelwriter/assets/icons/typicons_light/typ_chevron-down.svg +0 -4
  300. novelwriter/assets/icons/typicons_light/typ_chevron-left.svg +0 -4
  301. novelwriter/assets/icons/typicons_light/typ_chevron-right.svg +0 -4
  302. novelwriter/assets/icons/typicons_light/typ_chevron-up.svg +0 -4
  303. novelwriter/assets/icons/typicons_light/typ_cog.svg +0 -4
  304. novelwriter/assets/icons/typicons_light/typ_delete-full.svg +0 -4
  305. novelwriter/assets/icons/typicons_light/typ_delete.svg +0 -4
  306. novelwriter/assets/icons/typicons_light/typ_directions-full.svg +0 -4
  307. novelwriter/assets/icons/typicons_light/typ_document-add.svg +0 -4
  308. novelwriter/assets/icons/typicons_light/typ_document-text.svg +0 -5
  309. novelwriter/assets/icons/typicons_light/typ_document.svg +0 -4
  310. novelwriter/assets/icons/typicons_light/typ_export-grey.svg +0 -4
  311. novelwriter/assets/icons/typicons_light/typ_export.svg +0 -4
  312. novelwriter/assets/icons/typicons_light/typ_eye.svg +0 -4
  313. novelwriter/assets/icons/typicons_light/typ_flag.svg +0 -4
  314. novelwriter/assets/icons/typicons_light/typ_folder-open.svg +0 -4
  315. novelwriter/assets/icons/typicons_light/typ_folder.svg +0 -5
  316. novelwriter/assets/icons/typicons_light/typ_globe-grey.svg +0 -4
  317. novelwriter/assets/icons/typicons_light/typ_key.svg +0 -4
  318. novelwriter/assets/icons/typicons_light/typ_lightbulb-full.svg +0 -4
  319. novelwriter/assets/icons/typicons_light/typ_location.svg +0 -4
  320. novelwriter/assets/icons/typicons_light/typ_media-pause-grey.svg +0 -4
  321. novelwriter/assets/icons/typicons_light/typ_media-record-outline.svg +0 -4
  322. novelwriter/assets/icons/typicons_light/typ_media-record.svg +0 -4
  323. novelwriter/assets/icons/typicons_light/typ_minus.svg +0 -4
  324. novelwriter/assets/icons/typicons_light/typ_pencil.svg +0 -5
  325. novelwriter/assets/icons/typicons_light/typ_pin-outline.svg +0 -4
  326. novelwriter/assets/icons/typicons_light/typ_pin.svg +0 -4
  327. novelwriter/assets/icons/typicons_light/typ_plus.svg +0 -4
  328. novelwriter/assets/icons/typicons_light/typ_puzzle-outline.svg +0 -4
  329. novelwriter/assets/icons/typicons_light/typ_puzzle.svg +0 -4
  330. novelwriter/assets/icons/typicons_light/typ_refresh-flipped.svg +0 -4
  331. novelwriter/assets/icons/typicons_light/typ_refresh.svg +0 -4
  332. novelwriter/assets/icons/typicons_light/typ_search-grey.svg +0 -4
  333. novelwriter/assets/icons/typicons_light/typ_search.svg +0 -4
  334. novelwriter/assets/icons/typicons_light/typ_star.svg +0 -4
  335. novelwriter/assets/icons/typicons_light/typ_stopwatch-grey.svg +0 -4
  336. novelwriter/assets/icons/typicons_light/typ_th-dot-menu.svg +0 -4
  337. novelwriter/assets/icons/typicons_light/typ_th-dot-more.svg +0 -4
  338. novelwriter/assets/icons/typicons_light/typ_th-list-grey.svg +0 -4
  339. novelwriter/assets/icons/typicons_light/typ_th-list.svg +0 -9
  340. novelwriter/assets/icons/typicons_light/typ_times.svg +0 -4
  341. novelwriter/assets/icons/typicons_light/typ_trash.svg +0 -5
  342. novelwriter/assets/icons/typicons_light/typ_unfold-hidden.svg +0 -4
  343. novelwriter/assets/icons/typicons_light/typ_unfold-visible.svg +0 -4
  344. novelwriter/assets/icons/typicons_light/typ_user.svg +0 -5
  345. novelwriter/assets/icons/typicons_light/typ_warning-full.svg +0 -4
  346. {novelWriter-2.6.3.dist-info → novelwriter-2.7.dist-info}/entry_points.txt +0 -0
  347. {novelWriter-2.6.3.dist-info → novelwriter-2.7.dist-info/licenses}/LICENSE.md +0 -0
  348. {novelWriter-2.6.3.dist-info → novelwriter-2.7.dist-info}/top_level.txt +0 -0
@@ -3,9 +3,11 @@ novelWriter – GUI Novel Tree
3
3
  ============================
4
4
 
5
5
  File History:
6
- Created: 2020-12-20 [1.1rc1] GuiNovelTree
7
- Created: 2022-06-12 [2.0rc1] GuiNovelView
8
- Created: 2022-06-12 [2.0rc1] GuiNovelToolBar
6
+ Created: 2020-12-20 [1.1rc1] GuiNovelTree
7
+ Created: 2022-06-12 [2.0rc1] GuiNovelView
8
+ Created: 2022-06-12 [2.0rc1] GuiNovelToolBar
9
+ Rewritten: 2025-02-22 [2.7b1] GuiNovelView
10
+ Rewritten: 2025-02-22 [2.7b1] GuiNovelToolBar
9
11
 
10
12
  This file is a part of novelWriter
11
13
  Copyright (C) 2020 Veronica Berglyd Olsen and novelWriter contributors
@@ -28,40 +30,30 @@ from __future__ import annotations
28
30
  import logging
29
31
 
30
32
  from enum import Enum
31
- from time import time
32
33
 
33
- from PyQt5.QtCore import QModelIndex, QPoint, Qt, pyqtSignal, pyqtSlot
34
- from PyQt5.QtGui import QFocusEvent, QFont, QMouseEvent, QPalette, QResizeEvent
35
- from PyQt5.QtWidgets import (
36
- QAbstractItemView, QActionGroup, QFrame, QHBoxLayout, QInputDialog, QMenu,
37
- QToolTip, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget
34
+ from PyQt6.QtCore import QModelIndex, QPoint, pyqtSignal, pyqtSlot
35
+ from PyQt6.QtGui import QActionGroup, QFont, QPainter, QPalette, QResizeEvent
36
+ from PyQt6.QtWidgets import (
37
+ QAbstractItemView, QFrame, QHBoxLayout, QInputDialog, QMenu,
38
+ QStyleOptionViewItem, QToolTip, QVBoxLayout, QWidget
38
39
  )
39
40
 
40
41
  from novelwriter import CONFIG, SHARED
41
- from novelwriter.common import minmax, qtLambda
42
- from novelwriter.constants import nwKeyWords, nwLabels, nwStyles, trConst
43
- from novelwriter.core.index import IndexHeading
44
- from novelwriter.enum import nwChange, nwDocMode, nwItemClass, nwOutline
45
- from novelwriter.extensions.modified import NIconToolButton
42
+ from novelwriter.common import minmax, qtAddAction, qtAddMenu, qtLambda
43
+ from novelwriter.constants import nwKeyWords, nwLabels, trConst
44
+ from novelwriter.core.novelmodel import NovelModel
45
+ from novelwriter.enum import nwChange, nwDocMode, nwNovelExtra, nwOutline
46
+ from novelwriter.extensions.modified import NIconToolButton, NTreeView
46
47
  from novelwriter.extensions.novelselector import NovelSelector
47
48
  from novelwriter.gui.theme import STYLES_MIN_TOOLBUTTON
48
49
  from novelwriter.types import (
49
- QtAlignRight, QtDecoration, QtHeaderStretch, QtHeaderToContents,
50
- QtMouseLeft, QtMouseMiddle, QtScrollAlwaysOff, QtScrollAsNeeded,
51
- QtSizeExpanding, QtUserRole
50
+ QtHeaderStretch, QtHeaderToContents, QtScrollAlwaysOff, QtScrollAsNeeded,
51
+ QtSizeExpanding
52
52
  )
53
53
 
54
54
  logger = logging.getLogger(__name__)
55
55
 
56
56
 
57
- class NovelTreeColumn(Enum):
58
-
59
- HIDDEN = 0
60
- POV = 1
61
- FOCUS = 2
62
- PLOT = 3
63
-
64
-
65
57
  class GuiNovelView(QWidget):
66
58
 
67
59
  # Signals for user interaction with the novel tree
@@ -86,7 +78,9 @@ class GuiNovelView(QWidget):
86
78
  self.setLayout(self.outerBox)
87
79
 
88
80
  # Function Mappings
81
+ self.setActive = self.novelBar.setActive
89
82
  self.getSelectedHandle = self.novelTree.getSelectedHandle
83
+ self.refreshCurrentTree = self.novelBar.forceRefreshNovelTree
90
84
 
91
85
  return
92
86
 
@@ -97,8 +91,6 @@ class GuiNovelView(QWidget):
97
91
  def updateTheme(self) -> None:
98
92
  """Update theme elements."""
99
93
  self.novelBar.updateTheme()
100
- self.novelTree.updateTheme()
101
- self.refreshTree()
102
94
  return
103
95
 
104
96
  def initSettings(self) -> None:
@@ -108,21 +100,18 @@ class GuiNovelView(QWidget):
108
100
 
109
101
  def clearNovelView(self) -> None:
110
102
  """Clear project-related GUI content."""
111
- self.novelTree.clearContent()
112
103
  self.novelBar.clearContent()
113
104
  self.novelBar.setEnabled(False)
105
+ self.novelTree.clearContent()
114
106
  return
115
107
 
116
108
  def openProjectTasks(self) -> None:
117
109
  """Run open project tasks."""
118
- lastNovel = SHARED.project.data.getLastHandle("novelTree")
119
- if lastNovel and lastNovel not in SHARED.project.tree:
120
- lastNovel = SHARED.project.tree.findRoot(nwItemClass.NOVEL)
121
-
110
+ lastNovel = SHARED.project.data.getLastHandle("novel")
122
111
  logger.debug("Setting novel tree to root item '%s'", lastNovel)
123
112
 
124
113
  lastCol = SHARED.project.options.getEnum(
125
- "GuiNovelView", "lastCol", NovelTreeColumn, NovelTreeColumn.HIDDEN
114
+ "GuiNovelView", "lastCol", nwNovelExtra, nwNovelExtra.HIDDEN
126
115
  )
127
116
  lastColSize = SHARED.project.options.getInt(
128
117
  "GuiNovelView", "lastColSize", 25
@@ -140,13 +129,17 @@ class GuiNovelView(QWidget):
140
129
 
141
130
  def closeProjectTasks(self) -> None:
142
131
  """Run closing project tasks."""
132
+ logger.debug("Saving State: GuiNovelView")
133
+
143
134
  lastColType = self.novelTree.lastColType
144
135
  lastColSize = self.novelTree.lastColSize
145
- logger.debug("Saving State: GuiNovelView")
146
- pOptions = SHARED.project.options
147
- pOptions.setValue("GuiNovelView", "lastCol", lastColType)
148
- pOptions.setValue("GuiNovelView", "lastColSize", lastColSize)
136
+
137
+ options = SHARED.project.options
138
+ options.setValue("GuiNovelView", "lastCol", lastColType)
139
+ options.setValue("GuiNovelView", "lastColSize", lastColSize)
140
+
149
141
  self.clearNovelView()
142
+
150
143
  return
151
144
 
152
145
  def setTreeFocus(self) -> None:
@@ -162,32 +155,24 @@ class GuiNovelView(QWidget):
162
155
  # Public Slots
163
156
  ##
164
157
 
158
+ @pyqtSlot(str)
159
+ def setCurrentNovel(self, rootHandle: str | None) -> None:
160
+ """Set the current novel to display."""
161
+ self.novelTree.setNovelModel(rootHandle)
162
+ return
163
+
165
164
  @pyqtSlot(str)
166
165
  def setActiveHandle(self, tHandle: str) -> None:
167
166
  """Highlight the rows associated with a given handle."""
168
167
  self.novelTree.setActiveHandle(tHandle)
169
168
  return
170
169
 
171
- @pyqtSlot()
172
- def refreshTree(self) -> None:
173
- """Refresh the current tree."""
174
- self.novelTree.refreshTree(rootHandle=SHARED.project.data.getLastHandle("novelTree"))
175
- return
176
-
177
170
  @pyqtSlot(str, Enum)
178
171
  def updateRootItem(self, tHandle: str, change: nwChange) -> None:
179
172
  """If any root item changes, rebuild the novel root menu."""
180
173
  self.novelBar.buildNovelRootMenu()
181
174
  return
182
175
 
183
- @pyqtSlot(str)
184
- def updateNovelItemMeta(self, tHandle: str) -> None:
185
- """The meta data of a novel item has changed, and the tree item
186
- needs to be refreshed.
187
- """
188
- self.novelTree.refreshHandle(tHandle)
189
- return
190
-
191
176
 
192
177
  class GuiNovelToolBar(QWidget):
193
178
 
@@ -198,10 +183,13 @@ class GuiNovelToolBar(QWidget):
198
183
 
199
184
  self.novelView = novelView
200
185
 
186
+ self._active = False
187
+ self._refresh: dict[str, bool] = {}
188
+
201
189
  iSz = SHARED.theme.baseIconSize
202
- mPx = CONFIG.pxInt(2)
203
190
 
204
191
  self.setContentsMargins(0, 0, 0, 0)
192
+ self.setBackgroundRole(QPalette.ColorRole.Base)
205
193
  self.setAutoFillBackground(True)
206
194
 
207
195
  # Novel Selector
@@ -211,7 +199,7 @@ class GuiNovelToolBar(QWidget):
211
199
  self.novelValue = NovelSelector(self)
212
200
  self.novelValue.setFont(selFont)
213
201
  self.novelValue.setListFormat(self.tr("Outline of {0}"))
214
- self.novelValue.setMinimumWidth(CONFIG.pxInt(150))
202
+ self.novelValue.setMinimumWidth(150)
215
203
  self.novelValue.setSizePolicy(QtSizeExpanding, QtSizeExpanding)
216
204
  self.novelValue.novelSelectionChanged.connect(self.setCurrentRoot)
217
205
 
@@ -222,21 +210,21 @@ class GuiNovelToolBar(QWidget):
222
210
  # Refresh Button
223
211
  self.tbRefresh = NIconToolButton(self, iSz)
224
212
  self.tbRefresh.setToolTip(self.tr("Refresh"))
225
- self.tbRefresh.clicked.connect(self._refreshNovelTree)
213
+ self.tbRefresh.clicked.connect(self.forceRefreshNovelTree)
226
214
 
227
215
  # More Options Menu
228
216
  self.mMore = QMenu(self)
229
217
 
230
- self.mLastCol = self.mMore.addMenu(self.tr("Last Column"))
218
+ self.mLastCol = qtAddMenu(self.mMore, self.tr("Last Column"))
231
219
  self.gLastCol = QActionGroup(self.mMore)
232
220
  self.aLastCol = {}
233
- self._addLastColAction(NovelTreeColumn.HIDDEN, self.tr("Hidden"))
234
- self._addLastColAction(NovelTreeColumn.POV, self.tr("Point of View Character"))
235
- self._addLastColAction(NovelTreeColumn.FOCUS, self.tr("Focus Character"))
236
- self._addLastColAction(NovelTreeColumn.PLOT, self.tr("Novel Plot"))
221
+ self._addLastColAction(nwNovelExtra.HIDDEN, self.tr("Hidden"))
222
+ self._addLastColAction(nwNovelExtra.POV, self.tr("Point of View Character"))
223
+ self._addLastColAction(nwNovelExtra.FOCUS, self.tr("Focus Character"))
224
+ self._addLastColAction(nwNovelExtra.PLOT, self.tr("Novel Plot"))
237
225
 
238
226
  self.mLastCol.addSeparator()
239
- self.aLastColSize = self.mLastCol.addAction(self.tr("Column Size"))
227
+ self.aLastColSize = qtAddAction(self.mLastCol, self.tr("Column Size"))
240
228
  self.aLastColSize.triggered.connect(self._selectLastColumnSize)
241
229
 
242
230
  self.tbMore = NIconToolButton(self, iSz)
@@ -249,13 +237,16 @@ class GuiNovelToolBar(QWidget):
249
237
  self.outerBox.addWidget(self.tbNovel)
250
238
  self.outerBox.addWidget(self.tbRefresh)
251
239
  self.outerBox.addWidget(self.tbMore)
252
- self.outerBox.setContentsMargins(mPx, mPx, 0, mPx)
240
+ self.outerBox.setContentsMargins(4, 2, 0, 2)
253
241
  self.outerBox.setSpacing(0)
254
242
 
255
243
  self.setLayout(self.outerBox)
256
244
 
257
245
  self.updateTheme()
258
246
 
247
+ # Connect Signals
248
+ SHARED.novelStructureChanged.connect(self._refreshNovelTree)
249
+
259
250
  logger.debug("Ready: GuiNovelToolBar")
260
251
 
261
252
  return
@@ -267,13 +258,9 @@ class GuiNovelToolBar(QWidget):
267
258
  def updateTheme(self) -> None:
268
259
  """Update theme elements."""
269
260
  # Icons
270
- self.tbNovel.setThemeIcon("cls_novel")
271
- self.tbRefresh.setThemeIcon("refresh")
272
- self.tbMore.setThemeIcon("menu")
273
-
274
- qPalette = self.palette()
275
- qPalette.setBrush(QPalette.ColorRole.Window, qPalette.base())
276
- self.setPalette(qPalette)
261
+ self.tbNovel.setThemeIcon("cls_novel", "red")
262
+ self.tbRefresh.setThemeIcon("refresh", "green")
263
+ self.tbMore.setThemeIcon("more_vertical")
277
264
 
278
265
  # StyleSheets
279
266
  buttonStyle = SHARED.theme.getStyleSheet(STYLES_MIN_TOOLBUTTON)
@@ -285,9 +272,11 @@ class GuiNovelToolBar(QWidget):
285
272
  "QComboBox {border-style: none; padding-left: 0;} "
286
273
  "QComboBox::drop-down {border-style: none}"
287
274
  )
288
- self.novelValue.refreshNovelList()
275
+ self.novelValue.updateTheme()
289
276
  self.tbNovel.setVisible(self.novelValue.count() > 1)
290
277
 
278
+ self.forceRefreshNovelTree()
279
+
291
280
  return
292
281
 
293
282
  def clearContent(self) -> None:
@@ -299,30 +288,67 @@ class GuiNovelToolBar(QWidget):
299
288
  def buildNovelRootMenu(self) -> None:
300
289
  """Build the novel root menu."""
301
290
  self.novelValue.refreshNovelList()
291
+ self.novelView.setCurrentNovel(self.novelValue.handle)
302
292
  self.tbNovel.setVisible(self.novelValue.count() > 1)
303
293
  return
304
294
 
305
295
  def setCurrentRoot(self, rootHandle: str | None) -> None:
306
296
  """Set the current active root handle."""
297
+ if rootHandle is None or rootHandle not in SHARED.project.tree:
298
+ rootHandle = self.novelValue.firstHandle
307
299
  self.novelValue.setHandle(rootHandle)
308
- self.novelView.novelTree.refreshTree(rootHandle=rootHandle, overRide=True)
300
+ SHARED.project.data.setLastHandle(rootHandle, "novel")
301
+ self.novelView.setCurrentNovel(rootHandle)
302
+ self.novelView.novelTree.setAccessibleName(self.novelValue.currentText())
309
303
  return
310
304
 
311
- def setLastColType(self, colType: NovelTreeColumn, doRefresh: bool = True) -> None:
305
+ def setLastColType(self, colType: nwNovelExtra, doRefresh: bool = True) -> None:
312
306
  """Set the last column type."""
313
307
  self.aLastCol[colType].setChecked(True)
314
- self.novelView.novelTree.setLastColType(colType, doRefresh=doRefresh)
308
+ self.novelView.novelTree.setLastColType(colType)
309
+ if doRefresh:
310
+ self.forceRefreshNovelTree()
311
+ self.novelView.novelTree.resizeColumns()
312
+ return
313
+
314
+ def setActive(self, state: bool) -> None:
315
+ """Set the widget active state, which enables automatic tree
316
+ refresh when content structure changes.
317
+ """
318
+ self._active = state
319
+ if (
320
+ self._active
321
+ and (handle := self.novelValue.handle)
322
+ and self._refresh.get(handle, False)
323
+ ):
324
+ self._refreshNovelTree(self.novelValue.handle)
315
325
  return
316
326
 
317
327
  ##
318
- # Private Slots
328
+ # Public Slots
319
329
  ##
320
330
 
321
331
  @pyqtSlot()
322
- def _refreshNovelTree(self) -> None:
332
+ def forceRefreshNovelTree(self) -> None:
323
333
  """Rebuild the current tree."""
324
- rootHandle = SHARED.project.data.getLastHandle("novelTree")
325
- self.novelView.novelTree.refreshTree(rootHandle=rootHandle, overRide=True)
334
+ if tHandle := self.novelValue.handle:
335
+ self.novelView.setCurrentNovel(tHandle)
336
+ SHARED.project.index.refreshNovelModel(tHandle)
337
+ self._refresh[tHandle] = False
338
+ return
339
+
340
+ ##
341
+ # Private Slots
342
+ ##
343
+
344
+ @pyqtSlot(str)
345
+ def _refreshNovelTree(self, tHandle: str) -> None:
346
+ """Refresh or schedule refresh of a novel tree."""
347
+ if self._active:
348
+ SHARED.project.index.refreshNovelModel(tHandle)
349
+ self._refresh[tHandle] = False
350
+ else:
351
+ self._refresh[tHandle] = True
326
352
  return
327
353
 
328
354
  @pyqtSlot()
@@ -334,16 +360,16 @@ class GuiNovelToolBar(QWidget):
334
360
  )
335
361
  if isOk:
336
362
  self.novelView.novelTree.setLastColSize(newSize)
337
- self._refreshNovelTree()
363
+ self.novelView.novelTree.resizeColumns()
338
364
  return
339
365
 
340
366
  ##
341
367
  # Internal Functions
342
368
  ##
343
369
 
344
- def _addLastColAction(self, colType: NovelTreeColumn, actionLabel: str) -> None:
370
+ def _addLastColAction(self, colType: nwNovelExtra, actionLabel: str) -> None:
345
371
  """Add a column selection entry to the last column menu."""
346
- aLast = self.mLastCol.addAction(actionLabel)
372
+ aLast = qtAddAction(self.mLastCol, actionLabel)
347
373
  aLast.setCheckable(True)
348
374
  aLast.setActionGroup(self.gLastCol)
349
375
  aLast.triggered.connect(qtLambda(self.setLastColType, colType))
@@ -351,18 +377,7 @@ class GuiNovelToolBar(QWidget):
351
377
  return
352
378
 
353
379
 
354
- class GuiNovelTree(QTreeWidget):
355
-
356
- C_DATA = 0
357
- C_TITLE = 0
358
- C_WORDS = 1
359
- C_EXTRA = 2
360
- C_MORE = 3
361
-
362
- D_HANDLE = QtUserRole
363
- D_TITLE = QtUserRole + 1
364
- D_KEY = QtUserRole + 2
365
- D_EXTRA = QtUserRole + 3
380
+ class GuiNovelTree(NTreeView):
366
381
 
367
382
  def __init__(self, novelView: GuiNovelView) -> None:
368
383
  super().__init__(parent=novelView)
@@ -372,63 +387,30 @@ class GuiNovelTree(QTreeWidget):
372
387
  self.novelView = novelView
373
388
 
374
389
  # Internal Variables
375
- self._lastBuild = 0
376
- self._lastCol = NovelTreeColumn.POV
377
- self._lastColSize = 0.25
378
390
  self._actHandle = None
379
- self._treeMap: dict[str, QTreeWidgetItem] = {}
380
-
381
- # Cached Strings
382
- self._povLabel = trConst(nwLabels.KEY_NAME[nwKeyWords.POV_KEY])
383
- self._focLabel = trConst(nwLabels.KEY_NAME[nwKeyWords.FOCUS_KEY])
384
- self._pltLabel = trConst(nwLabels.KEY_NAME[nwKeyWords.PLOT_KEY])
385
-
386
- # Build GUI
387
- # =========
388
-
389
- iPx = SHARED.theme.baseIconHeight
390
- iSz = SHARED.theme.baseIconSize
391
- cMg = CONFIG.pxInt(6)
391
+ self._lastColType = nwNovelExtra.POV
392
+ self._lastColSize = 0.25
392
393
 
393
- self.setIconSize(iSz)
394
+ # Widget Setup
395
+ self.setIconSize(SHARED.theme.baseIconSize)
394
396
  self.setFrameStyle(QFrame.Shape.NoFrame)
395
397
  self.setUniformRowHeights(True)
396
398
  self.setAllColumnsShowFocus(True)
397
399
  self.setHeaderHidden(True)
398
- self.setIndentation(0)
399
- self.setColumnCount(4)
400
- self.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
401
- self.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
402
- self.setExpandsOnDoubleClick(False)
400
+ self.setIndentation(2)
403
401
  self.setDragEnabled(False)
404
402
 
405
- # Lock the column sizes
406
- treeHeader = self.header()
407
- treeHeader.setStretchLastSection(False)
408
- treeHeader.setMinimumSectionSize(iPx + cMg)
409
- treeHeader.setSectionResizeMode(self.C_TITLE, QtHeaderStretch)
410
- treeHeader.setSectionResizeMode(self.C_WORDS, QtHeaderToContents)
411
- treeHeader.setSectionResizeMode(self.C_EXTRA, QtHeaderToContents)
412
- treeHeader.setSectionResizeMode(self.C_MORE, QtHeaderToContents)
413
-
414
- # Pre-Generate Tree Formatting
415
- fH1 = self.font()
416
- fH1.setBold(True)
417
- fH1.setUnderline(True)
418
-
419
- fH2 = self.font()
420
- fH2.setBold(True)
421
-
422
- self._hFonts = [self.font(), fH1, fH2, self.font(), self.font()]
403
+ # Set selection options
404
+ self.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
405
+ self.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
423
406
 
424
407
  # Connect signals
425
- self.clicked.connect(self._treeItemClicked)
426
- self.itemDoubleClicked.connect(self._treeDoubleClick)
427
- self.itemSelectionChanged.connect(self._treeSelectionChange)
408
+ self.clicked.connect(self._onSingleClick)
409
+ self.doubleClicked.connect(self._onDoubleClick)
410
+ self.middleClicked.connect(self._onMiddleClick)
428
411
 
429
412
  # Set custom settings
430
413
  self.initSettings()
431
- self.updateTheme()
432
414
 
433
415
  logger.debug("Ready: GuiNovelTree")
434
416
 
@@ -436,23 +418,14 @@ class GuiNovelTree(QTreeWidget):
436
418
 
437
419
  def initSettings(self) -> None:
438
420
  """Set or update tree widget settings."""
439
- # Scroll bars
440
421
  if CONFIG.hideVScroll:
441
422
  self.setVerticalScrollBarPolicy(QtScrollAlwaysOff)
442
423
  else:
443
424
  self.setVerticalScrollBarPolicy(QtScrollAsNeeded)
444
-
445
425
  if CONFIG.hideHScroll:
446
426
  self.setHorizontalScrollBarPolicy(QtScrollAlwaysOff)
447
427
  else:
448
428
  self.setHorizontalScrollBarPolicy(QtScrollAsNeeded)
449
-
450
- return
451
-
452
- def updateTheme(self) -> None:
453
- """Update theme elements."""
454
- iPx = SHARED.theme.baseIconHeight
455
- self._pMore = SHARED.theme.loadDecoration("deco_doc_more", h=iPx)
456
429
  return
457
430
 
458
431
  ##
@@ -460,313 +433,180 @@ class GuiNovelTree(QTreeWidget):
460
433
  ##
461
434
 
462
435
  @property
463
- def lastColType(self) -> NovelTreeColumn:
464
- return self._lastCol
436
+ def lastColType(self) -> nwNovelExtra:
437
+ """The data type of the extra column."""
438
+ return self._lastColType
465
439
 
466
440
  @property
467
441
  def lastColSize(self) -> int:
442
+ """Return the size of the extra column."""
468
443
  return int(self._lastColSize * 100)
469
444
 
470
445
  ##
471
- # Class Methods
446
+ # Getters
472
447
  ##
473
448
 
474
- def clearContent(self) -> None:
475
- """Clear the GUI content and the related maps."""
476
- self.clear()
477
- self._treeMap = {}
478
- self._lastBuild = 0
479
- return
480
-
481
- def refreshTree(self, rootHandle: str | None = None, overRide: bool = False) -> None:
482
- """Refresh the tree if it has been changed."""
483
- logger.debug("Requesting refresh of the novel tree")
484
- if rootHandle is None:
485
- rootHandle = SHARED.project.tree.findRoot(nwItemClass.NOVEL)
486
-
487
- titleKey = None
488
- if selItems := self.selectedItems():
489
- titleKey = selItems[0].data(self.C_DATA, self.D_KEY)
490
-
491
- self._populateTree(rootHandle)
492
- SHARED.project.data.setLastHandle(rootHandle, "novelTree")
493
-
494
- if titleKey is not None and titleKey in self._treeMap:
495
- self._treeMap[titleKey].setSelected(True)
496
-
497
- return
498
-
499
- def refreshHandle(self, tHandle: str) -> None:
500
- """Refresh the data for a given handle."""
501
- if idxData := SHARED.project.index.getItemData(tHandle):
502
- logger.debug("Refreshing meta data for item '%s'", tHandle)
503
- for sTitle, tHeading in idxData.items():
504
- sKey = f"{tHandle}:{sTitle}"
505
- if trItem := self._treeMap.get(sKey, None):
506
- self._updateTreeItemValues(trItem, tHeading, tHandle, sTitle)
507
- else:
508
- logger.debug("Heading '%s' not in novel tree", sKey)
509
- self.refreshTree()
510
- return
511
- return
512
-
513
449
  def getSelectedHandle(self) -> tuple[str | None, str | None]:
514
450
  """Get the currently selected or active handle. If multiple
515
451
  items are selected, return the first.
516
452
  """
517
- selList = self.selectedItems()
518
- trItem = selList[0] if selList else self.currentItem()
519
- if isinstance(trItem, QTreeWidgetItem):
520
- tHandle = trItem.data(self.C_DATA, self.D_HANDLE)
521
- sTitle = trItem.data(self.C_DATA, self.D_TITLE)
522
- return tHandle, sTitle
453
+ if (model := self._getModel()) and (index := self.currentIndex()).isValid():
454
+ return model.handle(index), model.key(index)
523
455
  return None, None
524
456
 
525
- def setLastColType(self, colType: NovelTreeColumn, doRefresh: bool = True) -> None:
526
- """Change the content type of the last column and rebuild."""
527
- if self._lastCol != colType:
528
- logger.debug("Changing last column to %s", colType.name)
529
- self._lastCol = colType
530
- self.setColumnHidden(self.C_EXTRA, colType == NovelTreeColumn.HIDDEN)
531
- if doRefresh:
532
- lastNovel = SHARED.project.data.getLastHandle("novelTree")
533
- self.refreshTree(rootHandle=lastNovel, overRide=True)
534
- return
457
+ ##
458
+ # Setters
459
+ ##
535
460
 
536
- def setLastColSize(self, colSize: int) -> None:
537
- """Set the column size in integer values between 15 and 75."""
538
- self._lastColSize = minmax(colSize, 15, 75)/100.0
461
+ def setNovelModel(self, tHandle: str | None) -> None:
462
+ """Set the current novel model."""
463
+ if tHandle and (model := SHARED.project.index.getNovelModel(tHandle)):
464
+ if model is not self.model():
465
+ self.setModel(model)
466
+ self.resizeColumns()
467
+ else:
468
+ self.clearContent()
539
469
  return
540
470
 
541
471
  def setActiveHandle(self, tHandle: str | None) -> None:
542
- """Highlight the rows associated with a given handle."""
543
- didScroll = False
544
- brushOn = self.palette().alternateBase()
545
- brushOff = self.palette().base()
546
- if pHandle := self._actHandle:
547
- for key, item in self._treeMap.items():
548
- if key.startswith(pHandle):
549
- for i in range(self.columnCount()):
550
- item.setBackground(i, brushOff)
551
- if tHandle:
552
- for key, item in self._treeMap.items():
553
- if key.startswith(tHandle):
554
- for i in range(self.columnCount()):
555
- item.setBackground(i, brushOn)
556
- if not didScroll:
557
- self.scrollToItem(item, QAbstractItemView.ScrollHint.PositionAtCenter)
558
- didScroll = True
559
- self._actHandle = tHandle or None
472
+ """Set the handle to be highlighted."""
473
+ self._actHandle = tHandle
474
+ if viewport := self.viewport():
475
+ viewport.repaint()
560
476
  return
561
477
 
562
- ##
563
- # Events
564
- ##
478
+ def setLastColType(self, colType: nwNovelExtra) -> None:
479
+ """Set the extra column type."""
480
+ self._lastColType = colType
481
+ SHARED.project.index.setNovelModelExtraColumn(colType)
482
+ return
565
483
 
566
- def mousePressEvent(self, event: QMouseEvent) -> None:
567
- """Overload mousePressEvent to clear selection if clicking the
568
- mouse in a blank area of the tree view, and to load a document
569
- for viewing if the user middle-clicked.
570
- """
571
- super().mousePressEvent(event)
484
+ def setLastColSize(self, colSize: int) -> None:
485
+ """Set the extra column size between 15% and 75%."""
486
+ self._lastColSize = minmax(colSize, 15, 75)/100.0
487
+ return
572
488
 
573
- if event.button() == QtMouseLeft:
574
- selItem = self.indexAt(event.pos())
575
- if not selItem.isValid():
576
- self.clearSelection()
489
+ ##
490
+ # Class Methods
491
+ ##
577
492
 
578
- elif event.button() == QtMouseMiddle:
579
- selItem = self.itemAt(event.pos())
580
- if not isinstance(selItem, QTreeWidgetItem):
581
- return
493
+ def clearContent(self) -> None:
494
+ """Clear the tree view."""
495
+ self.setModel(None)
496
+ return
582
497
 
583
- tHandle, sTitle = self.getSelectedHandle()
584
- if tHandle is None:
585
- return
498
+ def resizeColumns(self) -> None:
499
+ """Set the correct column sizes."""
500
+ if (header := self.header()) and (model := self._getModel()) and (vp := self.viewport()):
501
+ header.setStretchLastSection(False)
502
+ header.setMinimumSectionSize(SHARED.theme.baseIconHeight + 6)
503
+ header.setSectionResizeMode(0, QtHeaderStretch)
504
+ header.setSectionResizeMode(1, QtHeaderToContents)
505
+ header.setSectionResizeMode(2, QtHeaderToContents)
506
+ if model.columns == 4:
507
+ header.setSectionResizeMode(3, QtHeaderToContents)
508
+ header.setMaximumSectionSize(int(self._lastColSize * vp.width()))
509
+ return
586
510
 
587
- self.novelView.openDocumentRequest.emit(tHandle, nwDocMode.VIEW, sTitle or "", False)
511
+ ##
512
+ # Overloads
513
+ ##
588
514
 
515
+ def drawRow(self, painter: QPainter, opt: QStyleOptionViewItem, index: QModelIndex) -> None:
516
+ """Draw a box on the active row."""
517
+ if (model := self._getModel()) and model.handle(index) == self._actHandle:
518
+ painter.fillRect(opt.rect, self.palette().alternateBase())
519
+ super().drawRow(painter, opt, index)
589
520
  return
590
521
 
591
- def focusOutEvent(self, event: QFocusEvent) -> None:
592
- """Clear the selection when the tree no longer has focus."""
593
- super().focusOutEvent(event)
594
- self.clearSelection()
595
- return
522
+ ##
523
+ # Events
524
+ ##
596
525
 
597
526
  def resizeEvent(self, event: QResizeEvent) -> None:
598
- """Elide labels in the extra column."""
527
+ """Process size changed."""
599
528
  super().resizeEvent(event)
600
- newW = event.size().width()
601
- oldW = event.oldSize().width()
602
- if newW != oldW:
603
- eliW = int(self._lastColSize * newW)
604
- fMetric = self.fontMetrics()
605
- for i in range(self.topLevelItemCount()):
606
- trItem = self.topLevelItem(i)
607
- if isinstance(trItem, QTreeWidgetItem):
608
- lastText = trItem.data(self.C_DATA, self.D_EXTRA)
609
- trItem.setText(
610
- self.C_EXTRA,
611
- fMetric.elidedText(lastText, Qt.TextElideMode.ElideRight, eliW)
612
- )
529
+ self.resizeColumns()
613
530
  return
614
531
 
615
532
  ##
616
533
  # Private Slots
617
534
  ##
618
535
 
619
- @pyqtSlot("QModelIndex")
620
- def _treeItemClicked(self, index: QModelIndex) -> None:
621
- """The user clicked on an item in the tree."""
622
- if index.column() == self.C_MORE:
623
- tHandle = index.siblingAtColumn(self.C_DATA).data(self.D_HANDLE)
624
- sTitle = index.siblingAtColumn(self.C_DATA).data(self.D_TITLE)
625
- tipPos = self.mapToGlobal(self.visualRect(index).topRight())
626
- self._popMetaBox(tipPos, tHandle, sTitle)
627
- return
628
-
629
- @pyqtSlot()
630
- def _treeSelectionChange(self) -> None:
631
- """Extract the handle and line number of the currently selected
632
- title, and send it to the tree meta panel.
633
- """
634
- tHandle, _ = self.getSelectedHandle()
635
- if tHandle is not None:
636
- self.novelView.selectedItemChanged.emit(tHandle)
637
- return
638
-
639
- @pyqtSlot("QTreeWidgetItem*", int)
640
- def _treeDoubleClick(self, item: QTreeWidgetItem, column: int) -> None:
641
- """Extract the handle and line number of the title double-
642
- clicked, and send it to the main gui class for opening in the
643
- document editor.
644
- """
645
- tHandle, sTitle = self.getSelectedHandle()
646
- self.novelView.openDocumentRequest.emit(tHandle, nwDocMode.EDIT, sTitle or "", True)
536
+ @pyqtSlot(QModelIndex)
537
+ def _onSingleClick(self, index: QModelIndex) -> None:
538
+ """The user single-clicked an index."""
539
+ if index.isValid() and (model := self._getModel()):
540
+ if (tHandle := model.handle(index)) and (sTitle := model.key(index)):
541
+ self.novelView.selectedItemChanged.emit(tHandle)
542
+ if index.column() == model.columnCount(index) - 1:
543
+ pos = self.mapToGlobal(self.visualRect(index).topRight())
544
+ self._popMetaBox(pos, tHandle, sTitle)
545
+ return
546
+
547
+ @pyqtSlot(QModelIndex)
548
+ def _onDoubleClick(self, index: QModelIndex) -> None:
549
+ """The user double-clicked an index."""
550
+ if (
551
+ (model := self._getModel())
552
+ and (tHandle := model.handle(index))
553
+ and (sTitle := model.key(index))
554
+ ):
555
+ self.novelView.openDocumentRequest.emit(tHandle, nwDocMode.EDIT, sTitle, False)
556
+ return
557
+
558
+ @pyqtSlot(QModelIndex)
559
+ def _onMiddleClick(self, index: QModelIndex) -> None:
560
+ """The user middle-clicked an index."""
561
+ if (
562
+ (model := self._getModel())
563
+ and (tHandle := model.handle(index))
564
+ and (sTitle := model.key(index))
565
+ ):
566
+ self.novelView.openDocumentRequest.emit(tHandle, nwDocMode.VIEW, sTitle, False)
647
567
  return
648
568
 
649
569
  ##
650
570
  # Internal Functions
651
571
  ##
652
572
 
653
- def _populateTree(self, rootHandle: str | None) -> None:
654
- """Build the tree based on the project index."""
655
- self.clearContent()
656
- tStart = time()
657
- logger.debug("Building novel tree for root item '%s'", rootHandle)
658
-
659
- novStruct = SHARED.project.index.novelStructure(rootHandle=rootHandle, activeOnly=True)
660
- for tKey, tHandle, sTitle, novIdx in novStruct:
661
- if novIdx.level == "H0":
662
- continue
663
-
664
- newItem = QTreeWidgetItem()
665
- newItem.setData(self.C_DATA, self.D_HANDLE, tHandle)
666
- newItem.setData(self.C_DATA, self.D_TITLE, sTitle)
667
- newItem.setData(self.C_DATA, self.D_KEY, tKey)
668
- newItem.setTextAlignment(self.C_WORDS, QtAlignRight)
669
-
670
- self._updateTreeItemValues(newItem, novIdx, tHandle, sTitle)
671
- self._treeMap[tKey] = newItem
672
- self.addTopLevelItem(newItem)
673
-
674
- self.setActiveHandle(self._actHandle)
675
-
676
- logger.debug("Novel Tree built in %.3f ms", (time() - tStart)*1000)
677
- self._lastBuild = time()
678
-
679
- return
680
-
681
- def _updateTreeItemValues(self, trItem: QTreeWidgetItem, idxItem: IndexHeading,
682
- tHandle: str, sTitle: str) -> None:
683
- """Set the tree item values from the index entry."""
684
- iLevel = nwStyles.H_LEVEL.get(idxItem.level, 0)
685
- hDec = SHARED.theme.getHeaderDecoration(iLevel)
686
-
687
- trItem.setData(self.C_TITLE, QtDecoration, hDec)
688
- trItem.setText(self.C_TITLE, idxItem.title)
689
- trItem.setFont(self.C_TITLE, self._hFonts[iLevel])
690
- trItem.setText(self.C_WORDS, f"{idxItem.wordCount:n}")
691
- trItem.setData(self.C_MORE, QtDecoration, self._pMore)
692
-
693
- # Custom column
694
- mW = int(self._lastColSize * self.viewport().width())
695
- lastText, toolTip = self._getLastColumnText(tHandle, sTitle)
696
- elideText = self.fontMetrics().elidedText(lastText, Qt.TextElideMode.ElideRight, mW)
697
- trItem.setText(self.C_EXTRA, elideText)
698
- trItem.setData(self.C_DATA, self.D_EXTRA, lastText)
699
- trItem.setToolTip(self.C_EXTRA, toolTip)
700
-
701
- return
702
-
703
- def _getLastColumnText(self, tHandle: str, sTitle: str) -> tuple[str, str]:
704
- """Generate text for the last column based on user settings."""
705
- if self._lastCol == NovelTreeColumn.HIDDEN:
706
- return "", ""
707
-
708
- refData = []
709
- refName = ""
710
- refs = SHARED.project.index.getReferences(tHandle, sTitle)
711
- if self._lastCol == NovelTreeColumn.POV:
712
- refData = refs[nwKeyWords.POV_KEY]
713
- refName = self._povLabel
714
-
715
- elif self._lastCol == NovelTreeColumn.FOCUS:
716
- refData = refs[nwKeyWords.FOCUS_KEY]
717
- refName = self._focLabel
718
-
719
- elif self._lastCol == NovelTreeColumn.PLOT:
720
- refData = refs[nwKeyWords.PLOT_KEY]
721
- refName = self._pltLabel
722
-
723
- if refData:
724
- toolText = ", ".join(refData)
725
- return toolText, f"{refName}: {toolText}"
726
-
727
- return "", ""
573
+ def _getModel(self) -> NovelModel | None:
574
+ """Return the model, if it exists."""
575
+ if isinstance(model := self.model(), NovelModel):
576
+ return model
577
+ return None
728
578
 
729
579
  def _popMetaBox(self, qPos: QPoint, tHandle: str, sTitle: str) -> None:
730
580
  """Show the novel meta data box."""
731
- logger.debug("Generating meta data tooltip for '%s:%s'", tHandle, sTitle)
732
-
733
- pIndex = SHARED.project.index
734
- novIdx = pIndex.getItemHeading(tHandle, sTitle)
735
- refTags = pIndex.getReferences(tHandle, sTitle)
736
- if not novIdx:
581
+ def appendTags(refs: dict, key: str, lines: list[str]) -> None:
582
+ """Generate a reference list for a given reference key."""
583
+ if tags := ", ".join(refs.get(key, [])):
584
+ lines.append(f"<b>{trConst(nwLabels.KEY_NAME[key])}:</b> {tags}")
737
585
  return
738
586
 
739
- synopText = novIdx.synopsis
740
- if synopText:
741
- synopLabel = trConst(nwLabels.OUTLINE_COLS[nwOutline.SYNOP])
742
- synopText = f"<p><b>{synopLabel}</b>: {synopText}</p>"
743
-
744
- refLines = []
745
- refLines = self._appendMetaTag(refTags, nwKeyWords.POV_KEY, refLines)
746
- refLines = self._appendMetaTag(refTags, nwKeyWords.FOCUS_KEY, refLines)
747
- refLines = self._appendMetaTag(refTags, nwKeyWords.CHAR_KEY, refLines)
748
- refLines = self._appendMetaTag(refTags, nwKeyWords.PLOT_KEY, refLines)
749
- refLines = self._appendMetaTag(refTags, nwKeyWords.TIME_KEY, refLines)
750
- refLines = self._appendMetaTag(refTags, nwKeyWords.WORLD_KEY, refLines)
751
- refLines = self._appendMetaTag(refTags, nwKeyWords.OBJECT_KEY, refLines)
752
- refLines = self._appendMetaTag(refTags, nwKeyWords.ENTITY_KEY, refLines)
753
- refLines = self._appendMetaTag(refTags, nwKeyWords.CUSTOM_KEY, refLines)
754
-
755
- refText = ""
756
- if refLines:
757
- refList = "<br>".join(refLines)
758
- refText = f"<p>{refList}</p>"
759
-
760
- ttText = refText + synopText or self.tr("No meta data")
761
- if ttText:
762
- QToolTip.showText(qPos, ttText)
763
-
764
- return
765
-
766
- @staticmethod
767
- def _appendMetaTag(refs: dict, key: str, lines: list[str]) -> list[str]:
768
- """Generate a reference list for a given reference key."""
769
- tags = ", ".join(refs.get(key, []))
770
- if tags:
771
- lines.append(f"<b>{trConst(nwLabels.KEY_NAME[key])}</b>: {tags}")
772
- return lines
587
+ if head := SHARED.project.index.getItemHeading(tHandle, sTitle):
588
+ logger.debug("Generating meta data tooltip for '%s:%s'", tHandle, sTitle)
589
+ if synopsis := head.synopsis:
590
+ label = trConst(nwLabels.OUTLINE_COLS[nwOutline.SYNOP])
591
+ synopsis = f"<p><b>{label}:</b> {synopsis}</p>"
592
+
593
+ lines = []
594
+ if head := SHARED.project.index.getItemHeading(tHandle, sTitle):
595
+ tags = head.getReferences()
596
+ appendTags(tags, nwKeyWords.POV_KEY, lines)
597
+ appendTags(tags, nwKeyWords.FOCUS_KEY, lines)
598
+ appendTags(tags, nwKeyWords.CHAR_KEY, lines)
599
+ appendTags(tags, nwKeyWords.PLOT_KEY, lines)
600
+ appendTags(tags, nwKeyWords.TIME_KEY, lines)
601
+ appendTags(tags, nwKeyWords.WORLD_KEY, lines)
602
+ appendTags(tags, nwKeyWords.OBJECT_KEY, lines)
603
+ appendTags(tags, nwKeyWords.ENTITY_KEY, lines)
604
+ appendTags(tags, nwKeyWords.CUSTOM_KEY, lines)
605
+
606
+ text = ""
607
+ if lines:
608
+ refs = "<br>".join(lines)
609
+ text = f"<p>{refs}</p>"
610
+ if tooltip := (text + synopsis or self.tr("No meta data")):
611
+ QToolTip.showText(qPos, tooltip)
612
+ return