novelWriter 2.6.3__py3-none-any.whl → 2.7rc1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (325) hide show
  1. novelwriter/__init__.py +98 -75
  2. novelwriter/assets/i18n/project_en_GB.json +2 -0
  3. novelwriter/assets/icons/font_awesome.icons +109 -0
  4. novelwriter/assets/icons/material_filled_bold.icons +109 -0
  5. novelwriter/assets/icons/material_filled_normal.icons +109 -0
  6. novelwriter/assets/icons/material_filled_thin.icons +109 -0
  7. novelwriter/assets/icons/material_rounded_bold.icons +109 -0
  8. novelwriter/assets/icons/material_rounded_normal.icons +109 -0
  9. novelwriter/assets/icons/material_rounded_thin.icons +109 -0
  10. novelwriter/assets/icons/remix_filled.icons +109 -0
  11. novelwriter/assets/icons/remix_outline.icons +109 -0
  12. novelwriter/assets/images/splash.png +0 -0
  13. novelwriter/assets/manual.pdf +0 -0
  14. novelwriter/assets/{manual_fr_FR.pdf → manual_fr.pdf} +0 -0
  15. novelwriter/assets/sample.zip +0 -0
  16. novelwriter/assets/syntax/cyberpunk_night.conf +1 -1
  17. novelwriter/assets/syntax/snazzy.conf +3 -3
  18. novelwriter/assets/text/credits_en.htm +6 -6
  19. novelwriter/assets/themes/cyberpunk_night.conf +23 -7
  20. novelwriter/assets/themes/default_dark.conf +20 -4
  21. novelwriter/assets/themes/default_light.conf +21 -5
  22. novelwriter/assets/themes/dracula.conf +20 -4
  23. novelwriter/assets/themes/snazzy.conf +48 -0
  24. novelwriter/assets/themes/solarized_dark.conf +24 -8
  25. novelwriter/assets/themes/solarized_light.conf +22 -6
  26. novelwriter/common.py +43 -27
  27. novelwriter/config.py +201 -139
  28. novelwriter/constants.py +67 -36
  29. novelwriter/core/buildsettings.py +25 -16
  30. novelwriter/core/coretools.py +52 -41
  31. novelwriter/core/docbuild.py +20 -13
  32. novelwriter/core/document.py +2 -2
  33. novelwriter/core/index.py +166 -432
  34. novelwriter/core/indexdata.py +406 -0
  35. novelwriter/core/item.py +50 -32
  36. novelwriter/core/itemmodel.py +43 -38
  37. novelwriter/core/novelmodel.py +225 -0
  38. novelwriter/core/options.py +1 -1
  39. novelwriter/core/project.py +24 -25
  40. novelwriter/core/projectdata.py +47 -29
  41. novelwriter/core/projectxml.py +18 -8
  42. novelwriter/core/sessions.py +32 -15
  43. novelwriter/core/spellcheck.py +4 -3
  44. novelwriter/core/status.py +12 -15
  45. novelwriter/core/storage.py +1 -1
  46. novelwriter/core/tree.py +55 -13
  47. novelwriter/dialogs/about.py +19 -22
  48. novelwriter/dialogs/docmerge.py +23 -24
  49. novelwriter/dialogs/docsplit.py +26 -26
  50. novelwriter/dialogs/editlabel.py +19 -20
  51. novelwriter/dialogs/preferences.py +143 -69
  52. novelwriter/dialogs/projectsettings.py +51 -54
  53. novelwriter/dialogs/quotes.py +14 -19
  54. novelwriter/dialogs/wordlist.py +25 -30
  55. novelwriter/enum.py +8 -0
  56. novelwriter/error.py +16 -16
  57. novelwriter/extensions/configlayout.py +24 -20
  58. novelwriter/extensions/eventfilters.py +9 -5
  59. novelwriter/extensions/modified.py +34 -15
  60. novelwriter/extensions/novelselector.py +18 -5
  61. novelwriter/extensions/pagedsidebar.py +39 -49
  62. novelwriter/extensions/progressbars.py +10 -8
  63. novelwriter/extensions/statusled.py +6 -13
  64. novelwriter/extensions/switch.py +31 -41
  65. novelwriter/extensions/switchbox.py +8 -3
  66. novelwriter/extensions/versioninfo.py +4 -4
  67. novelwriter/formats/shared.py +1 -1
  68. novelwriter/formats/todocx.py +14 -10
  69. novelwriter/formats/tohtml.py +7 -5
  70. novelwriter/formats/tokenizer.py +37 -33
  71. novelwriter/formats/tomarkdown.py +6 -2
  72. novelwriter/formats/toodt.py +27 -22
  73. novelwriter/formats/toqdoc.py +19 -14
  74. novelwriter/formats/toraw.py +6 -2
  75. novelwriter/gui/doceditor.py +314 -305
  76. novelwriter/gui/dochighlight.py +46 -45
  77. novelwriter/gui/docviewer.py +102 -104
  78. novelwriter/gui/docviewerpanel.py +47 -51
  79. novelwriter/gui/editordocument.py +8 -5
  80. novelwriter/gui/itemdetails.py +11 -14
  81. novelwriter/gui/mainmenu.py +146 -145
  82. novelwriter/gui/noveltree.py +246 -406
  83. novelwriter/gui/outline.py +141 -72
  84. novelwriter/gui/projtree.py +139 -132
  85. novelwriter/gui/search.py +34 -31
  86. novelwriter/gui/sidebar.py +13 -18
  87. novelwriter/gui/statusbar.py +27 -21
  88. novelwriter/gui/theme.py +554 -436
  89. novelwriter/guimain.py +56 -38
  90. novelwriter/shared.py +32 -23
  91. novelwriter/splash.py +74 -0
  92. novelwriter/text/comments.py +70 -0
  93. novelwriter/text/patterns.py +4 -4
  94. novelwriter/tools/dictionaries.py +20 -29
  95. novelwriter/tools/lipsum.py +18 -18
  96. novelwriter/tools/manusbuild.py +39 -42
  97. novelwriter/tools/manuscript.py +102 -115
  98. novelwriter/tools/manussettings.py +116 -96
  99. novelwriter/tools/noveldetails.py +60 -72
  100. novelwriter/tools/welcome.py +57 -75
  101. novelwriter/tools/writingstats.py +112 -102
  102. novelwriter/types.py +5 -7
  103. {novelWriter-2.6.3.dist-info → novelwriter-2.7rc1.dist-info}/METADATA +6 -6
  104. novelwriter-2.7rc1.dist-info/RECORD +162 -0
  105. {novelWriter-2.6.3.dist-info → novelwriter-2.7rc1.dist-info}/WHEEL +1 -1
  106. novelWriter-2.6.3.dist-info/RECORD +0 -363
  107. novelwriter/assets/icons/typicons_dark/README.md +0 -29
  108. novelwriter/assets/icons/typicons_dark/icons.conf +0 -134
  109. novelwriter/assets/icons/typicons_dark/mixed_copy.svg +0 -4
  110. novelwriter/assets/icons/typicons_dark/mixed_document-chapter.svg +0 -12
  111. novelwriter/assets/icons/typicons_dark/mixed_document-new.svg +0 -6
  112. novelwriter/assets/icons/typicons_dark/mixed_document-note.svg +0 -12
  113. novelwriter/assets/icons/typicons_dark/mixed_document-scene.svg +0 -12
  114. novelwriter/assets/icons/typicons_dark/mixed_document-section.svg +0 -12
  115. novelwriter/assets/icons/typicons_dark/mixed_document-title.svg +0 -12
  116. novelwriter/assets/icons/typicons_dark/mixed_edit.svg +0 -4
  117. novelwriter/assets/icons/typicons_dark/mixed_import.svg +0 -5
  118. novelwriter/assets/icons/typicons_dark/mixed_input-checked.svg +0 -5
  119. novelwriter/assets/icons/typicons_dark/mixed_input-none.svg +0 -5
  120. novelwriter/assets/icons/typicons_dark/mixed_input-unchecked.svg +0 -5
  121. novelwriter/assets/icons/typicons_dark/mixed_margin-bottom.svg +0 -6
  122. novelwriter/assets/icons/typicons_dark/mixed_margin-left.svg +0 -6
  123. novelwriter/assets/icons/typicons_dark/mixed_margin-right.svg +0 -6
  124. novelwriter/assets/icons/typicons_dark/mixed_margin-top.svg +0 -6
  125. novelwriter/assets/icons/typicons_dark/mixed_search-replace.svg +0 -6
  126. novelwriter/assets/icons/typicons_dark/mixed_size-height.svg +0 -6
  127. novelwriter/assets/icons/typicons_dark/mixed_size-width.svg +0 -6
  128. novelwriter/assets/icons/typicons_dark/nw_deco-h0.svg +0 -4
  129. novelwriter/assets/icons/typicons_dark/nw_deco-h1.svg +0 -4
  130. novelwriter/assets/icons/typicons_dark/nw_deco-h2-narrow.svg +0 -4
  131. novelwriter/assets/icons/typicons_dark/nw_deco-h2.svg +0 -4
  132. novelwriter/assets/icons/typicons_dark/nw_deco-h3-narrow.svg +0 -4
  133. novelwriter/assets/icons/typicons_dark/nw_deco-h3.svg +0 -4
  134. novelwriter/assets/icons/typicons_dark/nw_deco-h4-narrow.svg +0 -4
  135. novelwriter/assets/icons/typicons_dark/nw_deco-h4.svg +0 -4
  136. novelwriter/assets/icons/typicons_dark/nw_deco-note.svg +0 -4
  137. novelwriter/assets/icons/typicons_dark/nw_deco-noveltree-more.svg +0 -4
  138. novelwriter/assets/icons/typicons_dark/nw_font.svg +0 -4
  139. novelwriter/assets/icons/typicons_dark/nw_panel.svg +0 -4
  140. novelwriter/assets/icons/typicons_dark/nw_quote.svg +0 -4
  141. novelwriter/assets/icons/typicons_dark/nw_search-case.svg +0 -4
  142. novelwriter/assets/icons/typicons_dark/nw_search-preserve.svg +0 -4
  143. novelwriter/assets/icons/typicons_dark/nw_search-regex.svg +0 -4
  144. novelwriter/assets/icons/typicons_dark/nw_search-word.svg +0 -4
  145. novelwriter/assets/icons/typicons_dark/nw_tb-bold-md.svg +0 -4
  146. novelwriter/assets/icons/typicons_dark/nw_tb-bold.svg +0 -6
  147. novelwriter/assets/icons/typicons_dark/nw_tb-italic-md.svg +0 -4
  148. novelwriter/assets/icons/typicons_dark/nw_tb-italic.svg +0 -6
  149. novelwriter/assets/icons/typicons_dark/nw_tb-mark.svg +0 -7
  150. novelwriter/assets/icons/typicons_dark/nw_tb-strike-md.svg +0 -4
  151. novelwriter/assets/icons/typicons_dark/nw_tb-strike.svg +0 -6
  152. novelwriter/assets/icons/typicons_dark/nw_tb-subscript.svg +0 -7
  153. novelwriter/assets/icons/typicons_dark/nw_tb-superscript.svg +0 -7
  154. novelwriter/assets/icons/typicons_dark/nw_tb-underline.svg +0 -7
  155. novelwriter/assets/icons/typicons_dark/nw_toolbar.svg +0 -5
  156. novelwriter/assets/icons/typicons_dark/typ_arrow-down-thick-grey.svg +0 -4
  157. novelwriter/assets/icons/typicons_dark/typ_arrow-forward.svg +0 -4
  158. novelwriter/assets/icons/typicons_dark/typ_arrow-maximise.svg +0 -4
  159. novelwriter/assets/icons/typicons_dark/typ_arrow-minimise.svg +0 -4
  160. novelwriter/assets/icons/typicons_dark/typ_arrow-repeat-grey.svg +0 -4
  161. novelwriter/assets/icons/typicons_dark/typ_book-grey.svg +0 -4
  162. novelwriter/assets/icons/typicons_dark/typ_book.svg +0 -6
  163. novelwriter/assets/icons/typicons_dark/typ_bookmark.svg +0 -4
  164. novelwriter/assets/icons/typicons_dark/typ_calendar.svg +0 -4
  165. novelwriter/assets/icons/typicons_dark/typ_cancel-grey.svg +0 -4
  166. novelwriter/assets/icons/typicons_dark/typ_cancel.svg +0 -4
  167. novelwriter/assets/icons/typicons_dark/typ_chart-bar-grey.svg +0 -4
  168. novelwriter/assets/icons/typicons_dark/typ_chevron-down.svg +0 -4
  169. novelwriter/assets/icons/typicons_dark/typ_chevron-left.svg +0 -4
  170. novelwriter/assets/icons/typicons_dark/typ_chevron-right.svg +0 -4
  171. novelwriter/assets/icons/typicons_dark/typ_chevron-up.svg +0 -4
  172. novelwriter/assets/icons/typicons_dark/typ_cog.svg +0 -4
  173. novelwriter/assets/icons/typicons_dark/typ_delete-full.svg +0 -4
  174. novelwriter/assets/icons/typicons_dark/typ_delete.svg +0 -4
  175. novelwriter/assets/icons/typicons_dark/typ_directions-full.svg +0 -4
  176. novelwriter/assets/icons/typicons_dark/typ_document-add.svg +0 -4
  177. novelwriter/assets/icons/typicons_dark/typ_document-text.svg +0 -8
  178. novelwriter/assets/icons/typicons_dark/typ_document.svg +0 -4
  179. novelwriter/assets/icons/typicons_dark/typ_export-grey.svg +0 -4
  180. novelwriter/assets/icons/typicons_dark/typ_export.svg +0 -4
  181. novelwriter/assets/icons/typicons_dark/typ_eye.svg +0 -4
  182. novelwriter/assets/icons/typicons_dark/typ_flag.svg +0 -4
  183. novelwriter/assets/icons/typicons_dark/typ_folder-open.svg +0 -4
  184. novelwriter/assets/icons/typicons_dark/typ_folder.svg +0 -5
  185. novelwriter/assets/icons/typicons_dark/typ_globe-grey.svg +0 -4
  186. novelwriter/assets/icons/typicons_dark/typ_key.svg +0 -4
  187. novelwriter/assets/icons/typicons_dark/typ_lightbulb-full.svg +0 -4
  188. novelwriter/assets/icons/typicons_dark/typ_location.svg +0 -4
  189. novelwriter/assets/icons/typicons_dark/typ_media-pause-grey.svg +0 -4
  190. novelwriter/assets/icons/typicons_dark/typ_media-record-outline.svg +0 -4
  191. novelwriter/assets/icons/typicons_dark/typ_media-record.svg +0 -4
  192. novelwriter/assets/icons/typicons_dark/typ_minus.svg +0 -4
  193. novelwriter/assets/icons/typicons_dark/typ_pencil.svg +0 -5
  194. novelwriter/assets/icons/typicons_dark/typ_pin-outline.svg +0 -4
  195. novelwriter/assets/icons/typicons_dark/typ_pin.svg +0 -4
  196. novelwriter/assets/icons/typicons_dark/typ_plus.svg +0 -4
  197. novelwriter/assets/icons/typicons_dark/typ_puzzle-outline.svg +0 -4
  198. novelwriter/assets/icons/typicons_dark/typ_puzzle.svg +0 -4
  199. novelwriter/assets/icons/typicons_dark/typ_refresh-flipped.svg +0 -4
  200. novelwriter/assets/icons/typicons_dark/typ_refresh.svg +0 -4
  201. novelwriter/assets/icons/typicons_dark/typ_search-grey.svg +0 -4
  202. novelwriter/assets/icons/typicons_dark/typ_search.svg +0 -4
  203. novelwriter/assets/icons/typicons_dark/typ_star.svg +0 -4
  204. novelwriter/assets/icons/typicons_dark/typ_stopwatch-grey.svg +0 -4
  205. novelwriter/assets/icons/typicons_dark/typ_th-dot-menu.svg +0 -4
  206. novelwriter/assets/icons/typicons_dark/typ_th-dot-more.svg +0 -4
  207. novelwriter/assets/icons/typicons_dark/typ_th-list-grey.svg +0 -4
  208. novelwriter/assets/icons/typicons_dark/typ_th-list.svg +0 -9
  209. novelwriter/assets/icons/typicons_dark/typ_times.svg +0 -4
  210. novelwriter/assets/icons/typicons_dark/typ_trash.svg +0 -5
  211. novelwriter/assets/icons/typicons_dark/typ_unfold-hidden.svg +0 -4
  212. novelwriter/assets/icons/typicons_dark/typ_unfold-visible.svg +0 -4
  213. novelwriter/assets/icons/typicons_dark/typ_user.svg +0 -5
  214. novelwriter/assets/icons/typicons_dark/typ_warning-full.svg +0 -4
  215. novelwriter/assets/icons/typicons_light/README.md +0 -29
  216. novelwriter/assets/icons/typicons_light/icons.conf +0 -134
  217. novelwriter/assets/icons/typicons_light/mixed_copy.svg +0 -4
  218. novelwriter/assets/icons/typicons_light/mixed_document-chapter.svg +0 -12
  219. novelwriter/assets/icons/typicons_light/mixed_document-new.svg +0 -6
  220. novelwriter/assets/icons/typicons_light/mixed_document-note.svg +0 -12
  221. novelwriter/assets/icons/typicons_light/mixed_document-scene.svg +0 -12
  222. novelwriter/assets/icons/typicons_light/mixed_document-section.svg +0 -12
  223. novelwriter/assets/icons/typicons_light/mixed_document-title.svg +0 -12
  224. novelwriter/assets/icons/typicons_light/mixed_edit.svg +0 -4
  225. novelwriter/assets/icons/typicons_light/mixed_import.svg +0 -5
  226. novelwriter/assets/icons/typicons_light/mixed_input-checked.svg +0 -5
  227. novelwriter/assets/icons/typicons_light/mixed_input-none.svg +0 -5
  228. novelwriter/assets/icons/typicons_light/mixed_input-unchecked.svg +0 -5
  229. novelwriter/assets/icons/typicons_light/mixed_margin-bottom.svg +0 -6
  230. novelwriter/assets/icons/typicons_light/mixed_margin-left.svg +0 -6
  231. novelwriter/assets/icons/typicons_light/mixed_margin-right.svg +0 -6
  232. novelwriter/assets/icons/typicons_light/mixed_margin-top.svg +0 -6
  233. novelwriter/assets/icons/typicons_light/mixed_search-replace.svg +0 -6
  234. novelwriter/assets/icons/typicons_light/mixed_size-height.svg +0 -6
  235. novelwriter/assets/icons/typicons_light/mixed_size-width.svg +0 -6
  236. novelwriter/assets/icons/typicons_light/nw_deco-h0.svg +0 -4
  237. novelwriter/assets/icons/typicons_light/nw_deco-h1.svg +0 -4
  238. novelwriter/assets/icons/typicons_light/nw_deco-h2-narrow.svg +0 -4
  239. novelwriter/assets/icons/typicons_light/nw_deco-h2.svg +0 -4
  240. novelwriter/assets/icons/typicons_light/nw_deco-h3-narrow.svg +0 -4
  241. novelwriter/assets/icons/typicons_light/nw_deco-h3.svg +0 -4
  242. novelwriter/assets/icons/typicons_light/nw_deco-h4-narrow.svg +0 -4
  243. novelwriter/assets/icons/typicons_light/nw_deco-h4.svg +0 -4
  244. novelwriter/assets/icons/typicons_light/nw_deco-note.svg +0 -4
  245. novelwriter/assets/icons/typicons_light/nw_deco-noveltree-more.svg +0 -4
  246. novelwriter/assets/icons/typicons_light/nw_font.svg +0 -4
  247. novelwriter/assets/icons/typicons_light/nw_panel.svg +0 -4
  248. novelwriter/assets/icons/typicons_light/nw_quote.svg +0 -4
  249. novelwriter/assets/icons/typicons_light/nw_search-case.svg +0 -4
  250. novelwriter/assets/icons/typicons_light/nw_search-preserve.svg +0 -4
  251. novelwriter/assets/icons/typicons_light/nw_search-regex.svg +0 -4
  252. novelwriter/assets/icons/typicons_light/nw_search-word.svg +0 -4
  253. novelwriter/assets/icons/typicons_light/nw_tb-bold-md.svg +0 -4
  254. novelwriter/assets/icons/typicons_light/nw_tb-bold.svg +0 -6
  255. novelwriter/assets/icons/typicons_light/nw_tb-italic-md.svg +0 -4
  256. novelwriter/assets/icons/typicons_light/nw_tb-italic.svg +0 -6
  257. novelwriter/assets/icons/typicons_light/nw_tb-mark.svg +0 -7
  258. novelwriter/assets/icons/typicons_light/nw_tb-strike-md.svg +0 -4
  259. novelwriter/assets/icons/typicons_light/nw_tb-strike.svg +0 -6
  260. novelwriter/assets/icons/typicons_light/nw_tb-subscript.svg +0 -7
  261. novelwriter/assets/icons/typicons_light/nw_tb-superscript.svg +0 -7
  262. novelwriter/assets/icons/typicons_light/nw_tb-underline.svg +0 -7
  263. novelwriter/assets/icons/typicons_light/nw_toolbar.svg +0 -5
  264. novelwriter/assets/icons/typicons_light/typ_arrow-down-thick-grey.svg +0 -4
  265. novelwriter/assets/icons/typicons_light/typ_arrow-forward.svg +0 -4
  266. novelwriter/assets/icons/typicons_light/typ_arrow-maximise.svg +0 -4
  267. novelwriter/assets/icons/typicons_light/typ_arrow-minimise.svg +0 -4
  268. novelwriter/assets/icons/typicons_light/typ_arrow-repeat-grey.svg +0 -4
  269. novelwriter/assets/icons/typicons_light/typ_book-grey.svg +0 -4
  270. novelwriter/assets/icons/typicons_light/typ_book.svg +0 -6
  271. novelwriter/assets/icons/typicons_light/typ_bookmark.svg +0 -4
  272. novelwriter/assets/icons/typicons_light/typ_calendar.svg +0 -4
  273. novelwriter/assets/icons/typicons_light/typ_cancel-grey.svg +0 -4
  274. novelwriter/assets/icons/typicons_light/typ_cancel.svg +0 -4
  275. novelwriter/assets/icons/typicons_light/typ_chart-bar-grey.svg +0 -4
  276. novelwriter/assets/icons/typicons_light/typ_chevron-down.svg +0 -4
  277. novelwriter/assets/icons/typicons_light/typ_chevron-left.svg +0 -4
  278. novelwriter/assets/icons/typicons_light/typ_chevron-right.svg +0 -4
  279. novelwriter/assets/icons/typicons_light/typ_chevron-up.svg +0 -4
  280. novelwriter/assets/icons/typicons_light/typ_cog.svg +0 -4
  281. novelwriter/assets/icons/typicons_light/typ_delete-full.svg +0 -4
  282. novelwriter/assets/icons/typicons_light/typ_delete.svg +0 -4
  283. novelwriter/assets/icons/typicons_light/typ_directions-full.svg +0 -4
  284. novelwriter/assets/icons/typicons_light/typ_document-add.svg +0 -4
  285. novelwriter/assets/icons/typicons_light/typ_document-text.svg +0 -5
  286. novelwriter/assets/icons/typicons_light/typ_document.svg +0 -4
  287. novelwriter/assets/icons/typicons_light/typ_export-grey.svg +0 -4
  288. novelwriter/assets/icons/typicons_light/typ_export.svg +0 -4
  289. novelwriter/assets/icons/typicons_light/typ_eye.svg +0 -4
  290. novelwriter/assets/icons/typicons_light/typ_flag.svg +0 -4
  291. novelwriter/assets/icons/typicons_light/typ_folder-open.svg +0 -4
  292. novelwriter/assets/icons/typicons_light/typ_folder.svg +0 -5
  293. novelwriter/assets/icons/typicons_light/typ_globe-grey.svg +0 -4
  294. novelwriter/assets/icons/typicons_light/typ_key.svg +0 -4
  295. novelwriter/assets/icons/typicons_light/typ_lightbulb-full.svg +0 -4
  296. novelwriter/assets/icons/typicons_light/typ_location.svg +0 -4
  297. novelwriter/assets/icons/typicons_light/typ_media-pause-grey.svg +0 -4
  298. novelwriter/assets/icons/typicons_light/typ_media-record-outline.svg +0 -4
  299. novelwriter/assets/icons/typicons_light/typ_media-record.svg +0 -4
  300. novelwriter/assets/icons/typicons_light/typ_minus.svg +0 -4
  301. novelwriter/assets/icons/typicons_light/typ_pencil.svg +0 -5
  302. novelwriter/assets/icons/typicons_light/typ_pin-outline.svg +0 -4
  303. novelwriter/assets/icons/typicons_light/typ_pin.svg +0 -4
  304. novelwriter/assets/icons/typicons_light/typ_plus.svg +0 -4
  305. novelwriter/assets/icons/typicons_light/typ_puzzle-outline.svg +0 -4
  306. novelwriter/assets/icons/typicons_light/typ_puzzle.svg +0 -4
  307. novelwriter/assets/icons/typicons_light/typ_refresh-flipped.svg +0 -4
  308. novelwriter/assets/icons/typicons_light/typ_refresh.svg +0 -4
  309. novelwriter/assets/icons/typicons_light/typ_search-grey.svg +0 -4
  310. novelwriter/assets/icons/typicons_light/typ_search.svg +0 -4
  311. novelwriter/assets/icons/typicons_light/typ_star.svg +0 -4
  312. novelwriter/assets/icons/typicons_light/typ_stopwatch-grey.svg +0 -4
  313. novelwriter/assets/icons/typicons_light/typ_th-dot-menu.svg +0 -4
  314. novelwriter/assets/icons/typicons_light/typ_th-dot-more.svg +0 -4
  315. novelwriter/assets/icons/typicons_light/typ_th-list-grey.svg +0 -4
  316. novelwriter/assets/icons/typicons_light/typ_th-list.svg +0 -9
  317. novelwriter/assets/icons/typicons_light/typ_times.svg +0 -4
  318. novelwriter/assets/icons/typicons_light/typ_trash.svg +0 -5
  319. novelwriter/assets/icons/typicons_light/typ_unfold-hidden.svg +0 -4
  320. novelwriter/assets/icons/typicons_light/typ_unfold-visible.svg +0 -4
  321. novelwriter/assets/icons/typicons_light/typ_user.svg +0 -5
  322. novelwriter/assets/icons/typicons_light/typ_warning-full.svg +0 -4
  323. {novelWriter-2.6.3.dist-info → novelwriter-2.7rc1.dist-info}/entry_points.txt +0 -0
  324. {novelWriter-2.6.3.dist-info → novelwriter-2.7rc1.dist-info/licenses}/LICENSE.md +0 -0
  325. {novelWriter-2.6.3.dist-info → novelwriter-2.7rc1.dist-info}/top_level.txt +0 -0
@@ -3,15 +3,16 @@ novelWriter – GUI Document Editor
3
3
  =================================
4
4
 
5
5
  File History:
6
- Created: 2018-09-29 [0.0.1] GuiDocEditor
7
- Created: 2019-04-22 [0.0.1] BackgroundWordCounter
8
- Created: 2019-09-29 [0.2.1] GuiDocEditSearch
9
- Created: 2020-04-25 [0.4.5] GuiDocEditHeader
10
- Rewritten: 2020-06-15 [0.9] GuiDocEditSearch
11
- Created: 2020-06-27 [0.10] GuiDocEditFooter
12
- Rewritten: 2020-10-07 [1.0b3] BackgroundWordCounter
13
- Created: 2023-11-06 [2.2b1] MetaCompleter
14
- Created: 2023-11-07 [2.2b1] GuiDocToolBar
6
+ Created: 2018-09-29 [0.0.1] GuiDocEditor
7
+ Created: 2019-04-22 [0.0.1] BackgroundWordCounter
8
+ Created: 2019-09-29 [0.2.1] GuiDocEditSearch
9
+ Created: 2020-04-25 [0.4.5] GuiDocEditHeader
10
+ Rewritten: 2020-06-15 [0.9] GuiDocEditSearch
11
+ Created: 2020-06-27 [0.10] GuiDocEditFooter
12
+ Rewritten: 2020-10-07 [1.0b3] BackgroundWordCounter
13
+ Created: 2023-11-06 [2.2b1] MetaCompleter
14
+ Created: 2023-11-07 [2.2b1] GuiDocToolBar
15
+ Extended: 2025-05-18 [2.7rc1] CommandCompleter
15
16
 
16
17
  This file is a part of novelWriter
17
18
  Copyright (C) 2018 Veronica Berglyd Olsen and novelWriter contributors
@@ -37,29 +38,33 @@ import logging
37
38
  from enum import Enum, IntFlag
38
39
  from time import time
39
40
 
40
- from PyQt5.QtCore import (
41
+ from PyQt6.QtCore import (
41
42
  QObject, QPoint, QRegularExpression, QRunnable, Qt, QTimer, pyqtSignal,
42
43
  pyqtSlot
43
44
  )
44
- from PyQt5.QtGui import (
45
- QColor, QCursor, QDragEnterEvent, QDragMoveEvent, QDropEvent, QKeyEvent,
46
- QKeySequence, QMouseEvent, QPalette, QPixmap, QResizeEvent, QTextBlock,
47
- QTextCursor, QTextDocument, QTextOption
45
+ from PyQt6.QtGui import (
46
+ QAction, QCursor, QDragEnterEvent, QDragMoveEvent, QDropEvent, QKeyEvent,
47
+ QKeySequence, QMouseEvent, QPalette, QPixmap, QResizeEvent, QShortcut,
48
+ QTextBlock, QTextCursor, QTextDocument, QTextOption
48
49
  )
49
- from PyQt5.QtWidgets import (
50
- QAction, QApplication, QFrame, QGridLayout, QHBoxLayout, QLabel, QLineEdit,
51
- QMenu, QPlainTextEdit, QShortcut, QToolBar, QVBoxLayout, QWidget
50
+ from PyQt6.QtWidgets import (
51
+ QApplication, QFrame, QGridLayout, QHBoxLayout, QLabel, QLineEdit, QMenu,
52
+ QPlainTextEdit, QToolBar, QVBoxLayout, QWidget
52
53
  )
53
54
 
54
55
  from novelwriter import CONFIG, SHARED
55
- from novelwriter.common import decodeMimeHandles, fontMatcher, minmax, qtLambda, transferCase
56
- from novelwriter.constants import nwConst, nwKeyWords, nwShortcode, nwUnicode
56
+ from novelwriter.common import (
57
+ decodeMimeHandles, fontMatcher, minmax, qtAddAction, qtLambda, transferCase
58
+ )
59
+ from novelwriter.constants import (
60
+ nwConst, nwKeyWords, nwLabels, nwShortcode, nwStats, nwUnicode, trStats
61
+ )
57
62
  from novelwriter.core.document import NWDocument
58
63
  from novelwriter.enum import (
59
64
  nwChange, nwComment, nwDocAction, nwDocInsert, nwDocMode, nwItemClass,
60
65
  nwItemType
61
66
  )
62
- from novelwriter.extensions.configlayout import NColourLabel
67
+ from novelwriter.extensions.configlayout import NColorLabel
63
68
  from novelwriter.extensions.eventfilters import WheelEventFilter
64
69
  from novelwriter.extensions.modified import NIconToggleButton, NIconToolButton
65
70
  from novelwriter.gui.dochighlight import BLOCK_META, BLOCK_TITLE
@@ -95,16 +100,17 @@ class GuiDocEditor(QPlainTextEdit):
95
100
  """Gui Widget: Main Document Editor"""
96
101
 
97
102
  __slots__ = (
98
- "_nwDocument", "_nwItem", "_docChanged", "_docHandle", "_vpMargin",
99
- "_lastEdit", "_lastActive", "_lastFind", "_doReplace", "_autoReplace",
100
- "_completer", "_qDocument", "_keyContext", "_followTag1", "_followTag2",
101
- "_timerDoc", "_wCounterDoc", "_timerSel", "_wCounterSel",
103
+ "_autoReplace", "_completer", "_doReplace", "_docChanged", "_docHandle", "_followTag1",
104
+ "_followTag2", "_keyContext", "_lastActive", "_lastEdit", "_lastFind", "_nwDocument",
105
+ "_nwItem", "_qDocument", "_timerDoc", "_timerSel", "_vpMargin", "_wCounterDoc",
106
+ "_wCounterSel",
102
107
  )
103
108
 
104
109
  MOVE_KEYS = (
105
110
  Qt.Key.Key_Left, Qt.Key.Key_Right, Qt.Key.Key_Up, Qt.Key.Key_Down,
106
111
  Qt.Key.Key_PageUp, Qt.Key.Key_PageDown
107
112
  )
113
+ ENTER_KEYS = (Qt.Key.Key_Return, Qt.Key.Key_Enter)
108
114
 
109
115
  # Custom Signals
110
116
  closeEditorRequest = pyqtSignal()
@@ -112,8 +118,6 @@ class GuiDocEditor(QPlainTextEdit):
112
118
  editedStatusChanged = pyqtSignal(bool)
113
119
  itemHandleChanged = pyqtSignal(str)
114
120
  loadDocumentTagRequest = pyqtSignal(str, Enum)
115
- novelItemMetaChanged = pyqtSignal(str)
116
- novelStructureChanged = pyqtSignal()
117
121
  openDocumentRequest = pyqtSignal(str, Enum, str, bool)
118
122
  requestNewNoteCreation = pyqtSignal(str, nwItemClass)
119
123
  requestNextDocument = pyqtSignal(str, bool)
@@ -146,7 +150,7 @@ class GuiDocEditor(QPlainTextEdit):
146
150
  self._autoReplace = TextAutoReplace()
147
151
 
148
152
  # Completer
149
- self._completer = MetaCompleter(self)
153
+ self._completer = CommandCompleter(self)
150
154
  self._completer.complete.connect(self._insertCompletion)
151
155
 
152
156
  # Create Custom Document
@@ -175,7 +179,7 @@ class GuiDocEditor(QPlainTextEdit):
175
179
  self.customContextMenuRequested.connect(self._openContextMenu)
176
180
 
177
181
  # Editor Settings
178
- self.setMinimumWidth(CONFIG.pxInt(300))
182
+ self.setMinimumWidth(300)
179
183
  self.setAutoFillBackground(True)
180
184
  self.setFrameStyle(QFrame.Shape.NoFrame)
181
185
  self.setAcceptDrops(True)
@@ -224,7 +228,7 @@ class GuiDocEditor(QPlainTextEdit):
224
228
  self.changeFocusState = self.docHeader.changeFocusState
225
229
 
226
230
  # Finalise
227
- self.updateSyntaxColours()
231
+ self.updateSyntaxColors()
228
232
  self.initEditor()
229
233
 
230
234
  logger.debug("Ready: GuiDocEditor")
@@ -292,21 +296,24 @@ class GuiDocEditor(QPlainTextEdit):
292
296
  self.docToolBar.updateTheme()
293
297
  return
294
298
 
295
- def updateSyntaxColours(self) -> None:
299
+ def updateSyntaxColors(self) -> None:
296
300
  """Update the syntax highlighting theme."""
297
- mainPalette = self.palette()
298
- mainPalette.setColor(QPalette.ColorRole.Window, SHARED.theme.colBack)
299
- mainPalette.setColor(QPalette.ColorRole.Base, SHARED.theme.colBack)
300
- mainPalette.setColor(QPalette.ColorRole.Text, SHARED.theme.colText)
301
- self.setPalette(mainPalette)
301
+ syntax = SHARED.theme.syntaxTheme
302
+
303
+ palette = self.palette()
304
+ palette.setColor(QPalette.ColorRole.Window, syntax.back)
305
+ palette.setColor(QPalette.ColorRole.Base, syntax.back)
306
+ palette.setColor(QPalette.ColorRole.Text, syntax.text)
307
+ self.setPalette(palette)
302
308
 
303
- docPalette = self.viewport().palette()
304
- docPalette.setColor(QPalette.ColorRole.Base, SHARED.theme.colBack)
305
- docPalette.setColor(QPalette.ColorRole.Text, SHARED.theme.colText)
306
- self.viewport().setPalette(docPalette)
309
+ if viewport := self.viewport():
310
+ palette = viewport.palette()
311
+ palette.setColor(QPalette.ColorRole.Base, syntax.back)
312
+ palette.setColor(QPalette.ColorRole.Text, syntax.text)
313
+ viewport.setPalette(palette)
307
314
 
308
- self.docHeader.matchColours()
309
- self.docFooter.matchColours()
315
+ self.docHeader.matchColors()
316
+ self.docFooter.matchColors()
310
317
 
311
318
  return
312
319
 
@@ -317,6 +324,7 @@ class GuiDocEditor(QPlainTextEdit):
317
324
  """
318
325
  # Auto-Replace
319
326
  self._autoReplace.initSettings()
327
+ self.docFooter.initSettings()
320
328
 
321
329
  # Reload spell check and dictionaries
322
330
  SHARED.updateSpellCheckLanguage()
@@ -336,19 +344,17 @@ class GuiDocEditor(QPlainTextEdit):
336
344
  # Due to cursor visibility, a part of the margin must be
337
345
  # allocated to the document itself. See issue #1112.
338
346
  self._qDocument.setDocumentMargin(4)
339
- self._vpMargin = max(CONFIG.getTextMargin() - 4, 0)
347
+ self._vpMargin = max(CONFIG.textMargin - 4, 0)
340
348
  self.setViewportMargins(self._vpMargin, self._vpMargin, self._vpMargin, self._vpMargin)
341
349
 
342
350
  # Also set the document text options for the document text flow
343
351
  options = QTextOption()
344
-
345
352
  if CONFIG.doJustify:
346
353
  options.setAlignment(QtAlignJustify)
347
354
  if CONFIG.showTabsNSpaces:
348
355
  options.setFlags(options.flags() | QTextOption.Flag.ShowTabsAndSpaces)
349
356
  if CONFIG.showLineEndings:
350
357
  options.setFlags(options.flags() | QTextOption.Flag.ShowLineAndParagraphSeparators)
351
-
352
358
  self._qDocument.setDefaultTextOption(options)
353
359
 
354
360
  # Scrolling
@@ -363,8 +369,9 @@ class GuiDocEditor(QPlainTextEdit):
363
369
  else:
364
370
  self.setHorizontalScrollBarPolicy(QtScrollAsNeeded)
365
371
 
366
- # Refresh the tab stops
367
- self.setTabStopDistance(CONFIG.getTabWidth())
372
+ # Refresh sizes
373
+ self.setTabStopDistance(CONFIG.tabWidth)
374
+ self.setCursorWidth(CONFIG.cursorWidth)
368
375
 
369
376
  # If we have a document open, we should refresh it in case the
370
377
  # font changed, otherwise we just clear the editor entirely,
@@ -381,19 +388,19 @@ class GuiDocEditor(QPlainTextEdit):
381
388
  """Load text from a document into the editor. If we have an I/O
382
389
  error, we must handle this and clear the editor so that we don't
383
390
  risk overwriting the file if it exists. This can for instance
384
- happen of the file contains binary elements or an encoding that
391
+ happen if the file contains binary elements or an encoding that
385
392
  novelWriter does not support. If loading is successful, or the
386
393
  document is new (empty string), we set up the editor for editing
387
394
  the file.
388
395
  """
389
396
  self._nwDocument = SHARED.project.storage.getDocument(tHandle)
390
397
  self._nwItem = self._nwDocument.nwItem
391
- if not ((nwItem := self._nwItem) and nwItem.itemType == nwItemType.FILE):
398
+ if not (self._nwItem and self._nwItem.itemType == nwItemType.FILE):
392
399
  logger.debug("Requested item '%s' is not a document", tHandle)
393
400
  self.clearEditor()
394
401
  return False
395
402
 
396
- if (docText := self._nwDocument.readDocument()) is None:
403
+ if (text := self._nwDocument.readDocument()) is None:
397
404
  # There was an I/O error
398
405
  self.clearEditor()
399
406
  return False
@@ -402,7 +409,7 @@ class GuiDocEditor(QPlainTextEdit):
402
409
  self._docHandle = tHandle
403
410
 
404
411
  self._allowAutoReplace(False)
405
- self._qDocument.setTextContent(docText, tHandle)
412
+ self._qDocument.setTextContent(text, tHandle)
406
413
  self._allowAutoReplace(True)
407
414
  QApplication.processEvents()
408
415
 
@@ -417,7 +424,7 @@ class GuiDocEditor(QPlainTextEdit):
417
424
  if isinstance(tLine, int):
418
425
  self.setCursorLine(tLine)
419
426
  else:
420
- self.setCursorPosition(nwItem.cursorPos)
427
+ self.setCursorPosition(self._nwItem.cursorPos)
421
428
 
422
429
  self.docHeader.setHandle(tHandle)
423
430
  self.docFooter.setHandle(tHandle)
@@ -440,7 +447,7 @@ class GuiDocEditor(QPlainTextEdit):
440
447
  # Finalise
441
448
  QApplication.restoreOverrideCursor()
442
449
  self.updateStatusMessage.emit(
443
- self.tr("Opened Document: {0}").format(nwItem.itemName)
450
+ self.tr("Opened Document: {0}").format(self._nwItem.itemName)
444
451
  )
445
452
 
446
453
  return True
@@ -471,19 +478,17 @@ class GuiDocEditor(QPlainTextEdit):
471
478
  )
472
479
  return False
473
480
 
474
- docText = self.getText()
475
- cC, wC, pC = standardCounter(docText)
481
+ text = self.getText()
482
+ cC, wC, pC = standardCounter(text)
476
483
  self._updateDocCounts(cC, wC, pC)
477
484
 
478
- if not self._nwDocument.writeDocument(docText):
485
+ if not self._nwDocument.writeDocument(text):
479
486
  saveOk = False
480
- if self._nwDocument.hashError:
481
- msgYes = SHARED.question(self.tr(
482
- "This document has been changed outside of novelWriter "
483
- "while it was open. Overwrite the file on disk?"
484
- ))
485
- if msgYes:
486
- saveOk = self._nwDocument.writeDocument(docText, forceWrite=True)
487
+ if self._nwDocument.hashError and SHARED.question(self.tr(
488
+ "This document has been changed outside of novelWriter "
489
+ "while it was open. Overwrite the file on disk?"
490
+ )):
491
+ saveOk = self._nwDocument.writeDocument(text, forceWrite=True)
487
492
 
488
493
  if not saveOk:
489
494
  SHARED.error(
@@ -495,52 +500,35 @@ class GuiDocEditor(QPlainTextEdit):
495
500
 
496
501
  self.setDocumentChanged(False)
497
502
  self.docTextChanged.emit(self._docHandle, self._lastEdit)
503
+ SHARED.project.index.scanText(tHandle, text)
498
504
 
499
- oldHeader = self._nwItem.mainHeading
500
- oldCount = SHARED.project.index.getHandleHeaderCount(tHandle)
501
- SHARED.project.index.scanText(tHandle, docText)
502
- newHeader = self._nwItem.mainHeading
503
- newCount = SHARED.project.index.getHandleHeaderCount(tHandle)
504
-
505
- if self._nwItem.itemClass == nwItemClass.NOVEL:
506
- if oldCount == newCount:
507
- self.novelItemMetaChanged.emit(tHandle)
508
- else:
509
- self.novelStructureChanged.emit()
510
-
511
- if oldHeader != newHeader:
512
- self.docFooter.updateInfo()
513
-
514
- # Update the status bar
515
505
  self.updateStatusMessage.emit(self.tr("Saved Document: {0}").format(self._nwItem.itemName))
516
506
 
517
507
  return True
518
508
 
519
509
  def cursorIsVisible(self) -> bool:
520
510
  """Check if the cursor is visible in the editor."""
521
- return (
522
- 0 < self.cursorRect().top()
523
- and self.cursorRect().bottom() < self.viewport().height()
524
- )
511
+ viewport = self.viewport()
512
+ height = viewport.height() if viewport else 0
513
+ return 0 < self.cursorRect().top() and self.cursorRect().bottom() < height
525
514
 
526
515
  def ensureCursorVisibleNoCentre(self) -> None:
527
516
  """Ensure cursor is visible, but don't force it to centre."""
528
- cT = self.cursorRect().top()
529
- cB = self.cursorRect().bottom()
530
- vH = self.viewport().height()
531
- if cT < 0:
532
- count = 0
533
- vBar = self.verticalScrollBar()
534
- while self.cursorRect().top() < 0 and count < 100000:
535
- vBar.setValue(vBar.value() - 1)
536
- count += 1
537
- elif cB > vH:
538
- count = 0
539
- vBar = self.verticalScrollBar()
540
- while self.cursorRect().bottom() > vH and count < 100000:
541
- vBar.setValue(vBar.value() + 1)
542
- count += 1
543
- QApplication.processEvents()
517
+ if (viewport := self.viewport()) and (vBar := self.verticalScrollBar()):
518
+ cT = self.cursorRect().top()
519
+ cB = self.cursorRect().bottom()
520
+ vH = viewport.height()
521
+ if cT < 0:
522
+ count = 0
523
+ while self.cursorRect().top() < 0 and count < 100000:
524
+ vBar.setValue(vBar.value() - 1)
525
+ count += 1
526
+ elif cB > vH:
527
+ count = 0
528
+ while self.cursorRect().bottom() > vH and count < 100000:
529
+ vBar.setValue(vBar.value() + 1)
530
+ count += 1
531
+ QApplication.processEvents()
544
532
  return
545
533
 
546
534
  def updateDocMargins(self) -> None:
@@ -552,10 +540,10 @@ class GuiDocEditor(QPlainTextEdit):
552
540
  wH = self.height()
553
541
 
554
542
  vBar = self.verticalScrollBar()
555
- sW = vBar.width() if vBar.isVisible() else 0
543
+ sW = vBar.width() if vBar and vBar.isVisible() else 0
556
544
 
557
545
  hBar = self.horizontalScrollBar()
558
- sH = hBar.height() if hBar.isVisible() else 0
546
+ sH = hBar.height() if hBar and hBar.isVisible() else 0
559
547
 
560
548
  tM = self._vpMargin
561
549
  if CONFIG.textWidth > 0 or SHARED.focusMode:
@@ -593,7 +581,7 @@ class GuiDocEditor(QPlainTextEdit):
593
581
  QTextDocument->toRawText instead of toPlainText. The former preserves
594
582
  non-breaking spaces, the latter does not. We still want to get rid of
595
583
  paragraph and line separators though.
596
- See: https://doc.qt.io/qt-5/qtextdocument.html#toPlainText
584
+ See: https://doc.qt.io/qt-6/qtextdocument.html#toPlainText
597
585
  """
598
586
  text = self._qDocument.toRawText()
599
587
  text = text.replace(nwUnicode.U_LSEP, "\n") # Line separators
@@ -631,10 +619,9 @@ class GuiDocEditor(QPlainTextEdit):
631
619
 
632
620
  def setCursorPosition(self, position: int) -> None:
633
621
  """Move the cursor to a given position in the document."""
634
- nChars = self._qDocument.characterCount()
635
- if nChars > 1 and isinstance(position, int):
622
+ if (chars := self._qDocument.characterCount()) > 1 and isinstance(position, int):
636
623
  cursor = self.textCursor()
637
- cursor.setPosition(minmax(position, 0, nChars-1))
624
+ cursor.setPosition(minmax(position, 0, chars-1))
638
625
  self.setTextCursor(cursor)
639
626
  self.centerCursor()
640
627
  return
@@ -697,12 +684,10 @@ class GuiDocEditor(QPlainTextEdit):
697
684
 
698
685
  def spellCheckDocument(self) -> None:
699
686
  """Rerun the highlighter to update spell checking status of the
700
- currently loaded text. The fastest way to do this, at least as
701
- of Qt 5.13, is to clear the text and put it back. This clears
702
- the undo stack, so we only do it for big documents.
687
+ currently loaded text.
703
688
  """
704
- logger.debug("Running spell checker")
705
689
  start = time()
690
+ logger.debug("Running spell checker")
706
691
  QApplication.setOverrideCursor(QCursor(Qt.CursorShape.WaitCursor))
707
692
  self._qDocument.syntaxHighlighter.rehighlight()
708
693
  QApplication.restoreOverrideCursor()
@@ -779,7 +764,7 @@ class GuiDocEditor(QPlainTextEdit):
779
764
  elif action == nwDocAction.REPL_SNG:
780
765
  self._replaceQuotes("'", CONFIG.fmtSQuoteOpen, CONFIG.fmtSQuoteClose)
781
766
  elif action == nwDocAction.REPL_DBL:
782
- self._replaceQuotes("\"", CONFIG.fmtDQuoteOpen, CONFIG.fmtDQuoteClose)
767
+ self._replaceQuotes('"', CONFIG.fmtDQuoteOpen, CONFIG.fmtDQuoteClose)
783
768
  elif action == nwDocAction.RM_BREAKS:
784
769
  self._removeInParLineBreaks()
785
770
  elif action == nwDocAction.ALIGN_L:
@@ -943,9 +928,7 @@ class GuiDocEditor(QPlainTextEdit):
943
928
  * We also handle automatic scrolling here.
944
929
  """
945
930
  self._lastActive = time()
946
- isReturn = event.key() == Qt.Key.Key_Return
947
- isReturn |= event.key() == Qt.Key.Key_Enter
948
- if isReturn and self.docSearch.anyFocus():
931
+ if self.docSearch.anyFocus() and event.key() in self.ENTER_KEYS:
949
932
  return
950
933
  elif event == QKeySequence.StandardKey.Redo:
951
934
  self.docAction(nwDocAction.REDO)
@@ -964,10 +947,9 @@ class GuiDocEditor(QPlainTextEdit):
964
947
  kMod = event.modifiers()
965
948
  okMod = kMod in (QtModNone, QtModShift)
966
949
  okKey = event.key() not in self.MOVE_KEYS
967
- if nPos != cPos and okMod and okKey:
968
- mPos = CONFIG.autoScrollPos*0.01 * self.viewport().height()
969
- if cPos > mPos:
970
- vBar = self.verticalScrollBar()
950
+ if nPos != cPos and okMod and okKey and (viewport := self.viewport()):
951
+ mPos = CONFIG.autoScrollPos*0.01 * viewport.height()
952
+ if cPos > mPos and (vBar := self.verticalScrollBar()):
971
953
  vBar.setValue(vBar.value() + (1 if nPos > cPos else -1))
972
954
  else:
973
955
  super().keyPressEvent(event)
@@ -976,7 +958,7 @@ class GuiDocEditor(QPlainTextEdit):
976
958
 
977
959
  def dragEnterEvent(self, event: QDragEnterEvent) -> None:
978
960
  """Overload drag enter event to handle dragged items."""
979
- if event.mimeData().hasFormat(nwConst.MIME_HANDLE):
961
+ if (data := event.mimeData()) and data.hasFormat(nwConst.MIME_HANDLE):
980
962
  event.acceptProposedAction()
981
963
  else:
982
964
  super().dragEnterEvent(event)
@@ -984,7 +966,7 @@ class GuiDocEditor(QPlainTextEdit):
984
966
 
985
967
  def dragMoveEvent(self, event: QDragMoveEvent) -> None:
986
968
  """Overload drag move event to handle dragged items."""
987
- if event.mimeData().hasFormat(nwConst.MIME_HANDLE):
969
+ if (data := event.mimeData()) and data.hasFormat(nwConst.MIME_HANDLE):
988
970
  event.acceptProposedAction()
989
971
  else:
990
972
  super().dragMoveEvent(event)
@@ -992,15 +974,15 @@ class GuiDocEditor(QPlainTextEdit):
992
974
 
993
975
  def dropEvent(self, event: QDropEvent) -> None:
994
976
  """Overload drop event to handle dragged items."""
995
- if event.mimeData().hasFormat(nwConst.MIME_HANDLE):
996
- if handles := decodeMimeHandles(event.mimeData()):
977
+ if (data := event.mimeData()) and data.hasFormat(nwConst.MIME_HANDLE):
978
+ if handles := decodeMimeHandles(data):
997
979
  if SHARED.project.tree.checkType(handles[0], nwItemType.FILE):
998
980
  self.openDocumentRequest.emit(handles[0], nwDocMode.EDIT, "", True)
999
981
  else:
1000
982
  super().dropEvent(event)
1001
983
  return
1002
984
 
1003
- def focusNextPrevChild(self, next: bool) -> bool:
985
+ def focusNextPrevChild(self, _next: bool) -> bool:
1004
986
  """Capture the focus request from the tab key on the text
1005
987
  editor. If the editor has focus, we do not change focus and
1006
988
  allow the editor to insert a tab. If the search bar has focus,
@@ -1059,7 +1041,7 @@ class GuiDocEditor(QPlainTextEdit):
1059
1041
  logger.error("Invalid keyword '%s'", keyword)
1060
1042
  return False
1061
1043
  logger.debug("Inserting keyword '%s'", keyword)
1062
- state = self.insertNewBlock("%s: " % keyword)
1044
+ state = self.insertNewBlock(f"{keyword}: ")
1063
1045
  return state
1064
1046
 
1065
1047
  @pyqtSlot()
@@ -1098,15 +1080,18 @@ class GuiDocEditor(QPlainTextEdit):
1098
1080
 
1099
1081
  if (block := self._qDocument.findBlock(pos)).isValid():
1100
1082
  text = block.text()
1101
- if text.startswith("@") and added + removed == 1:
1102
- # Only run on single keypresses, otherwise it will trigger
1083
+ if text and text[0] in "@%" and added + removed == 1:
1084
+ # Only run on single character changes, or it will trigger
1103
1085
  # at unwanted times when other changes are made to the document
1104
1086
  cursor = self.textCursor()
1105
1087
  bPos = cursor.positionInBlock()
1106
- if bPos > 0:
1107
- show = self._completer.updateText(text, bPos)
1088
+ if bPos > 0 and (viewport := self.viewport()):
1089
+ if text[0] == "@":
1090
+ show = self._completer.updateMetaText(text, bPos)
1091
+ else:
1092
+ show = self._completer.updateCommentText(text, bPos)
1108
1093
  point = self.cursorRect().bottomRight()
1109
- self._completer.move(self.viewport().mapToGlobal(point))
1094
+ self._completer.move(viewport.mapToGlobal(point))
1110
1095
  self._completer.setVisible(show)
1111
1096
  else:
1112
1097
  self._completer.setVisible(False)
@@ -1128,11 +1113,10 @@ class GuiDocEditor(QPlainTextEdit):
1128
1113
  def _insertCompletion(self, pos: int, length: int, text: str) -> None:
1129
1114
  """Insert choice from the completer menu."""
1130
1115
  cursor = self.textCursor()
1131
- block = cursor.block()
1132
- if block.isValid():
1133
- pos += block.position()
1134
- cursor.setPosition(pos, QtMoveAnchor)
1135
- cursor.setPosition(pos + length, QtKeepAnchor)
1116
+ if (block := cursor.block()).isValid():
1117
+ check = pos + block.position()
1118
+ cursor.setPosition(check, QtMoveAnchor)
1119
+ cursor.setPosition(check + length, QtKeepAnchor)
1136
1120
  cursor.insertText(text)
1137
1121
  self._completer.hide()
1138
1122
  return
@@ -1153,46 +1137,46 @@ class GuiDocEditor(QPlainTextEdit):
1153
1137
  ctxMenu = QMenu(self)
1154
1138
  ctxMenu.setObjectName("ContextMenu")
1155
1139
  if pBlock.userState() == BLOCK_TITLE:
1156
- action = ctxMenu.addAction(self.tr("Set as Document Name"))
1140
+ action = qtAddAction(ctxMenu, self.tr("Set as Document Name"))
1157
1141
  action.triggered.connect(qtLambda(self._emitRenameItem, pBlock))
1158
1142
 
1159
1143
  # URL
1160
1144
  (mData, mType) = self._qDocument.metaDataAtPos(pCursor.position())
1161
1145
  if mData and mType == "url":
1162
- action = ctxMenu.addAction(self.tr("Open URL"))
1146
+ action = qtAddAction(ctxMenu, self.tr("Open URL"))
1163
1147
  action.triggered.connect(qtLambda(SHARED.openWebsite, mData))
1164
1148
  ctxMenu.addSeparator()
1165
1149
 
1166
1150
  # Follow
1167
1151
  status = self._processTag(cursor=pCursor, follow=False)
1168
1152
  if status & _TagAction.FOLLOW:
1169
- action = ctxMenu.addAction(self.tr("Follow Tag"))
1153
+ action = qtAddAction(ctxMenu, self.tr("Follow Tag"))
1170
1154
  action.triggered.connect(qtLambda(self._processTag, cursor=pCursor, follow=True))
1171
1155
  ctxMenu.addSeparator()
1172
1156
  elif status & _TagAction.CREATE:
1173
- action = ctxMenu.addAction(self.tr("Create Note for Tag"))
1157
+ action = qtAddAction(ctxMenu, self.tr("Create Note for Tag"))
1174
1158
  action.triggered.connect(qtLambda(self._processTag, cursor=pCursor, create=True))
1175
1159
  ctxMenu.addSeparator()
1176
1160
 
1177
1161
  # Cut, Copy and Paste
1178
1162
  if uCursor.hasSelection():
1179
- action = ctxMenu.addAction(self.tr("Cut"))
1163
+ action = qtAddAction(ctxMenu, self.tr("Cut"))
1180
1164
  action.triggered.connect(qtLambda(self.docAction, nwDocAction.CUT))
1181
- action = ctxMenu.addAction(self.tr("Copy"))
1165
+ action = qtAddAction(ctxMenu, self.tr("Copy"))
1182
1166
  action.triggered.connect(qtLambda(self.docAction, nwDocAction.COPY))
1183
1167
 
1184
- action = ctxMenu.addAction(self.tr("Paste"))
1168
+ action = qtAddAction(ctxMenu, self.tr("Paste"))
1185
1169
  action.triggered.connect(qtLambda(self.docAction, nwDocAction.PASTE))
1186
1170
  ctxMenu.addSeparator()
1187
1171
 
1188
1172
  # Selections
1189
- action = ctxMenu.addAction(self.tr("Select All"))
1173
+ action = qtAddAction(ctxMenu, self.tr("Select All"))
1190
1174
  action.triggered.connect(qtLambda(self.docAction, nwDocAction.SEL_ALL))
1191
- action = ctxMenu.addAction(self.tr("Select Word"))
1175
+ action = qtAddAction(ctxMenu, self.tr("Select Word"))
1192
1176
  action.triggered.connect(qtLambda(
1193
1177
  self._makePosSelection, QTextCursor.SelectionType.WordUnderCursor, pos,
1194
1178
  ))
1195
- action = ctxMenu.addAction(self.tr("Select Paragraph"))
1179
+ action = qtAddAction(ctxMenu, self.tr("Select Paragraph"))
1196
1180
  action.triggered.connect(qtLambda(
1197
1181
  self._makePosSelection, QTextCursor.SelectionType.BlockUnderCursor, pos
1198
1182
  ))
@@ -1208,22 +1192,24 @@ class GuiDocEditor(QPlainTextEdit):
1208
1192
  sCursor.movePosition(QtMoveRight, QtKeepAnchor, cLen)
1209
1193
  if suggest:
1210
1194
  ctxMenu.addSeparator()
1211
- ctxMenu.addAction(self.tr("Spelling Suggestion(s)"))
1195
+ qtAddAction(ctxMenu, self.tr("Spelling Suggestion(s)"))
1212
1196
  for option in suggest[:15]:
1213
- action = ctxMenu.addAction(f"{nwUnicode.U_ENDASH} {option}")
1197
+ action = qtAddAction(ctxMenu, f"{nwUnicode.U_ENDASH} {option}")
1214
1198
  action.triggered.connect(qtLambda(self._correctWord, sCursor, option))
1215
1199
  else:
1216
1200
  trNone = self.tr("No Suggestions")
1217
- ctxMenu.addAction(f"{nwUnicode.U_ENDASH} {trNone}")
1201
+ qtAddAction(ctxMenu, f"{nwUnicode.U_ENDASH} {trNone}")
1218
1202
 
1219
1203
  ctxMenu.addSeparator()
1220
- action = ctxMenu.addAction(self.tr("Ignore Word"))
1204
+ action = qtAddAction(ctxMenu, self.tr("Ignore Word"))
1221
1205
  action.triggered.connect(qtLambda(self._addWord, word, block, False))
1222
- action = ctxMenu.addAction(self.tr("Add Word to Dictionary"))
1206
+ action = qtAddAction(ctxMenu, self.tr("Add Word to Dictionary"))
1223
1207
  action.triggered.connect(qtLambda(self._addWord, word, block, True))
1224
1208
 
1225
1209
  # Execute the context menu
1226
- ctxMenu.exec(self.viewport().mapToGlobal(pos))
1210
+ if viewport := self.viewport():
1211
+ ctxMenu.exec(viewport.mapToGlobal(pos))
1212
+
1227
1213
  ctxMenu.setParent(None)
1228
1214
 
1229
1215
  return
@@ -1254,7 +1240,8 @@ class GuiDocEditor(QPlainTextEdit):
1254
1240
  """Process the word counter's finished signal."""
1255
1241
  if self._docHandle and self._nwItem:
1256
1242
  logger.debug("Updating word count")
1257
- needsRefresh = wCount != self._nwItem.wordCount
1243
+ mCount = cCount if CONFIG.useCharCount else wCount
1244
+ needsRefresh = mCount != self._nwItem.mainCount
1258
1245
  self._nwItem.setCharCount(cCount)
1259
1246
  self._nwItem.setWordCount(wCount)
1260
1247
  self._nwItem.setParaCount(pCount)
@@ -1262,7 +1249,7 @@ class GuiDocEditor(QPlainTextEdit):
1262
1249
  self._nwItem.notifyToRefresh()
1263
1250
  if not self.textCursor().hasSelection():
1264
1251
  # Selection counter should take precedence (#2155)
1265
- self.docFooter.updateWordCount(wCount, False)
1252
+ self.docFooter.updateMainCount(mCount, False)
1266
1253
  return
1267
1254
 
1268
1255
  @pyqtSlot()
@@ -1275,29 +1262,24 @@ class GuiDocEditor(QPlainTextEdit):
1275
1262
  self._timerSel.start()
1276
1263
  else:
1277
1264
  self._timerSel.stop()
1278
- self.docFooter.updateWordCount(0, False)
1265
+ self.docFooter.updateMainCount(0, False)
1279
1266
  return
1280
1267
 
1281
1268
  @pyqtSlot()
1282
1269
  def _runSelCounter(self) -> None:
1283
1270
  """Update the selection word count."""
1284
- if self._docHandle is None:
1285
- return
1286
-
1287
- if self._wCounterSel.isRunning():
1288
- logger.debug("Selection word counter is busy")
1289
- return
1290
-
1291
- SHARED.runInThreadPool(self._wCounterSel)
1292
-
1271
+ if self._docHandle:
1272
+ if self._wCounterSel.isRunning():
1273
+ logger.debug("Selection word counter is busy")
1274
+ return
1275
+ SHARED.runInThreadPool(self._wCounterSel)
1293
1276
  return
1294
1277
 
1295
1278
  @pyqtSlot(int, int, int)
1296
1279
  def _updateSelCounts(self, cCount: int, wCount: int, pCount: int) -> None:
1297
1280
  """Update the counts on the counter's finished signal."""
1298
1281
  if self._docHandle and self._nwItem:
1299
- logger.debug("User selected %d words", wCount)
1300
- self.docFooter.updateWordCount(wCount, True)
1282
+ self.docFooter.updateMainCount(cCount if CONFIG.useCharCount else wCount, True)
1301
1283
  self._timerSel.stop()
1302
1284
  return
1303
1285
 
@@ -1347,6 +1329,7 @@ class GuiDocEditor(QPlainTextEdit):
1347
1329
  self.beginSearch()
1348
1330
  return
1349
1331
 
1332
+ prevFocus = QApplication.focusWidget() or self
1350
1333
  resS, resE = self.findAllOccurences()
1351
1334
  if len(resS) == 0 and self._docHandle:
1352
1335
  self.docSearch.setResultCount(0, 0)
@@ -1355,7 +1338,7 @@ class GuiDocEditor(QPlainTextEdit):
1355
1338
  self.requestNextDocument.emit(self._docHandle, CONFIG.searchLoop)
1356
1339
  QApplication.processEvents()
1357
1340
  self.beginSearch()
1358
- self.setFocus()
1341
+ prevFocus.setFocus()
1359
1342
  return
1360
1343
 
1361
1344
  cursor = self.textCursor()
@@ -1375,7 +1358,7 @@ class GuiDocEditor(QPlainTextEdit):
1375
1358
  self.requestNextDocument.emit(self._docHandle, CONFIG.searchLoop)
1376
1359
  QApplication.processEvents()
1377
1360
  self.beginSearch()
1378
- self.setFocus()
1361
+ prevFocus.setFocus()
1379
1362
  return
1380
1363
  else:
1381
1364
  resIdx = 0 if doLoop else maxIdx
@@ -1552,10 +1535,10 @@ class GuiDocEditor(QPlainTextEdit):
1552
1535
  if fLen == min(numA, numB):
1553
1536
  cursor.beginEditBlock()
1554
1537
  cursor.setPosition(posS)
1555
- for i in range(fLen):
1538
+ for _ in range(fLen):
1556
1539
  cursor.deletePreviousChar()
1557
1540
  cursor.setPosition(posE)
1558
- for i in range(fLen):
1541
+ for _ in range(fLen):
1559
1542
  cursor.deletePreviousChar()
1560
1543
  cursor.endEditBlock()
1561
1544
 
@@ -1569,8 +1552,10 @@ class GuiDocEditor(QPlainTextEdit):
1569
1552
 
1570
1553
  return
1571
1554
 
1572
- def _wrapSelection(self, before: str, after: str | None = None, pos: int | None = None,
1573
- select: _SelectAction = _SelectAction.NO_DECISION) -> None:
1555
+ def _wrapSelection(
1556
+ self, before: str, after: str | None = None, pos: int | None = None,
1557
+ select: _SelectAction = _SelectAction.NO_DECISION
1558
+ ) -> None:
1574
1559
  """Wrap the selected text in whatever is in tBefore and tAfter.
1575
1560
  If there is no selection, the autoSelect setting decides the
1576
1561
  action. AutoSelect will select the word under the cursor before
@@ -1965,7 +1950,9 @@ class GuiDocEditor(QPlainTextEdit):
1965
1950
  exist = False
1966
1951
  cPos = cursor.selectionStart() - block.position()
1967
1952
  tExist = SHARED.project.index.checkThese(tBits, self._docHandle)
1968
- for sTag, sPos, sExist in zip(reversed(tBits), reversed(tPos), reversed(tExist)):
1953
+ for sTag, sPos, sExist in zip(
1954
+ reversed(tBits), reversed(tPos), reversed(tExist), strict=False
1955
+ ):
1969
1956
  if cPos >= sPos:
1970
1957
  # The cursor is between the start of two tags
1971
1958
  if cPos <= sPos + len(sTag):
@@ -2021,9 +2008,9 @@ class GuiDocEditor(QPlainTextEdit):
2021
2008
  sPos = cPos
2022
2009
  for i in range(cPos - bPos):
2023
2010
  sPos = cPos - i - 1
2024
- cOne = self._qDocument.characterAt(sPos)
2025
- cTwo = self._qDocument.characterAt(sPos - 1)
2026
- if not (cOne.isalnum() or cOne in apos and cTwo.isalnum()):
2011
+ cOne = str(self._qDocument.characterAt(sPos))
2012
+ cTwo = str(self._qDocument.characterAt(sPos - 1))
2013
+ if not (cOne.isalnum() or (cOne in apos and cTwo.isalnum())):
2027
2014
  sPos += 1
2028
2015
  break
2029
2016
 
@@ -2031,9 +2018,9 @@ class GuiDocEditor(QPlainTextEdit):
2031
2018
  ePos = cPos
2032
2019
  for i in range(bPos + bLen - cPos):
2033
2020
  ePos = cPos + i
2034
- cOne = self._qDocument.characterAt(ePos)
2035
- cTwo = self._qDocument.characterAt(ePos + 1)
2036
- if not (cOne.isalnum() or cOne in apos and cTwo.isalnum()):
2021
+ cOne = str(self._qDocument.characterAt(ePos))
2022
+ cTwo = str(self._qDocument.characterAt(ePos + 1))
2023
+ if not (cOne.isalnum() or (cOne in apos and cTwo.isalnum())):
2037
2024
  break
2038
2025
 
2039
2026
  if ePos - sPos <= 0:
@@ -2048,8 +2035,9 @@ class GuiDocEditor(QPlainTextEdit):
2048
2035
 
2049
2036
  return cursor
2050
2037
 
2051
- def _makeSelection(self, mode: QTextCursor.SelectionType,
2052
- cursor: QTextCursor | None = None) -> None:
2038
+ def _makeSelection(
2039
+ self, mode: QTextCursor.SelectionType, cursor: QTextCursor | None = None
2040
+ ) -> None:
2053
2041
  """Select text based on selection mode."""
2054
2042
  if cursor is None:
2055
2043
  cursor = self.textCursor()
@@ -2089,13 +2077,13 @@ class GuiDocEditor(QPlainTextEdit):
2089
2077
  return
2090
2078
 
2091
2079
 
2092
- class MetaCompleter(QMenu):
2093
- """GuiWidget: Meta Completer Menu
2080
+ class CommandCompleter(QMenu):
2081
+ """GuiWidget: Command Completer Menu
2094
2082
 
2095
2083
  This is a context menu with options populated from the user's
2096
- defined tags. It also helps to type the meta data keyword on a new
2097
- line starting with an @. The updateText function should be called on
2098
- every keystroke on a line starting with @.
2084
+ defined tags and keys. It also helps to type the meta data keyword
2085
+ on a new line starting with @ or %. The update functions should be
2086
+ called on every keystroke on a line starting with @ or %.
2099
2087
  """
2100
2088
 
2101
2089
  complete = pyqtSignal(int, int, str)
@@ -2104,7 +2092,7 @@ class MetaCompleter(QMenu):
2104
2092
  super().__init__(parent=parent)
2105
2093
  return
2106
2094
 
2107
- def updateText(self, text: str, pos: int) -> bool:
2095
+ def updateMetaText(self, text: str, pos: int) -> bool:
2108
2096
  """Update the menu options based on the line of text."""
2109
2097
  self.clear()
2110
2098
  kw, sep, _ = text.partition(":")
@@ -2112,7 +2100,7 @@ class MetaCompleter(QMenu):
2112
2100
  offset = 0
2113
2101
  length = len(kw.rstrip())
2114
2102
  suffix = "" if sep else ":"
2115
- options = list(filter(
2103
+ options = sorted(filter(
2116
2104
  lambda x: x.startswith(kw.rstrip()), nwKeyWords.VALID_KEYS
2117
2105
  ))
2118
2106
  else:
@@ -2124,7 +2112,7 @@ class MetaCompleter(QMenu):
2124
2112
  offset = tPos[index] if lookup else pos
2125
2113
  length = len(lookup)
2126
2114
  suffix = ""
2127
- options = list(filter(
2115
+ options = sorted(filter(
2128
2116
  lambda x: lookup in x.lower(), SHARED.project.index.getClassTags(
2129
2117
  nwKeyWords.KEY_CLASS.get(kw.strip())
2130
2118
  )
@@ -2133,13 +2121,57 @@ class MetaCompleter(QMenu):
2133
2121
  if not options:
2134
2122
  return False
2135
2123
 
2136
- for value in sorted(options):
2124
+ for value in options:
2137
2125
  rep = value + suffix
2138
- action = self.addAction(value)
2126
+ action = qtAddAction(self, value)
2139
2127
  action.triggered.connect(qtLambda(self._emitComplete, offset, length, rep))
2140
2128
 
2141
2129
  return True
2142
2130
 
2131
+ def updateCommentText(self, text: str, pos: int) -> bool:
2132
+ """Update the menu options based on the line of text."""
2133
+ self.clear()
2134
+ cmd, sep, _ = text.partition(":")
2135
+ if pos <= len(cmd):
2136
+ clean = text[1:].lstrip()[:6].lower()
2137
+ if clean[:6] == "story.":
2138
+ pre, _, key = cmd.partition(".")
2139
+ offset = len(pre) + 1
2140
+ length = len(key)
2141
+ suffix = "" if sep else ": "
2142
+ options = sorted(filter(
2143
+ lambda x: x.startswith(key.rstrip()),
2144
+ SHARED.project.index.getStoryKeys(),
2145
+ ))
2146
+ elif clean[:5] == "note.":
2147
+ pre, _, key = cmd.partition(".")
2148
+ offset = len(pre) + 1
2149
+ length = len(key)
2150
+ suffix = "" if sep else ": "
2151
+ options = sorted(filter(
2152
+ lambda x: x.startswith(key.rstrip()),
2153
+ SHARED.project.index.getNoteKeys(),
2154
+ ))
2155
+ elif pos < 12:
2156
+ offset = 0
2157
+ length = len(cmd.rstrip())
2158
+ suffix = ""
2159
+ options = list(filter(
2160
+ lambda x: x.startswith(cmd.rstrip()),
2161
+ ["%Synopsis: ", "%Short: ", "%Story", "%Note"],
2162
+ ))
2163
+ else:
2164
+ return False
2165
+
2166
+ if options:
2167
+ for value in options:
2168
+ rep = value + suffix
2169
+ action = qtAddAction(self, rep.rstrip(":. "))
2170
+ action.triggered.connect(qtLambda(self._emitComplete, offset, length, rep))
2171
+ return True
2172
+
2173
+ return False
2174
+
2143
2175
  ##
2144
2176
  # Events
2145
2177
  ##
@@ -2175,13 +2207,10 @@ class BackgroundWordCounter(QRunnable):
2175
2207
 
2176
2208
  def __init__(self, docEditor: GuiDocEditor, forSelection: bool = False) -> None:
2177
2209
  super().__init__()
2178
-
2179
2210
  self._docEditor = docEditor
2180
2211
  self._forSelection = forSelection
2181
2212
  self._isRunning = False
2182
-
2183
2213
  self.signals = BackgroundWordCounterSignals()
2184
-
2185
2214
  return
2186
2215
 
2187
2216
  def isRunning(self) -> bool:
@@ -2215,9 +2244,9 @@ class BackgroundWordCounterSignals(QObject):
2215
2244
  class TextAutoReplace:
2216
2245
 
2217
2246
  __slots__ = (
2218
- "_quoteSO", "_quoteSC", "_quoteDO", "_quoteDC",
2219
- "_replaceSQuote", "_replaceDQuote", "_replaceDash", "_replaceDots",
2220
- "_padChar", "_padBefore", "_padAfter", "_doPadBefore", "_doPadAfter",
2247
+ "_doPadAfter", "_doPadBefore", "_padAfter", "_padBefore", "_padChar",
2248
+ "_quoteDC", "_quoteDO", "_quoteSC", "_quoteSO", "_replaceDQuote",
2249
+ "_replaceDash", "_replaceDots", "_replaceSQuote",
2221
2250
  )
2222
2251
 
2223
2252
  def __init__(self) -> None:
@@ -2352,7 +2381,6 @@ class GuiDocToolBar(QWidget):
2352
2381
  logger.debug("Create: GuiDocToolBar")
2353
2382
 
2354
2383
  iSz = SHARED.theme.baseIconSize
2355
- cM = CONFIG.pxInt(4)
2356
2384
  self.setContentsMargins(0, 0, 0, 0)
2357
2385
 
2358
2386
  # General Buttons
@@ -2425,7 +2453,7 @@ class GuiDocToolBar(QWidget):
2425
2453
  self.outerBox.addWidget(self.tbBoldMD)
2426
2454
  self.outerBox.addWidget(self.tbItalicMD)
2427
2455
  self.outerBox.addWidget(self.tbStrikeMD)
2428
- self.outerBox.addSpacing(cM)
2456
+ self.outerBox.addSpacing(4)
2429
2457
  self.outerBox.addWidget(self.tbBold)
2430
2458
  self.outerBox.addWidget(self.tbItalic)
2431
2459
  self.outerBox.addWidget(self.tbStrike)
@@ -2433,8 +2461,8 @@ class GuiDocToolBar(QWidget):
2433
2461
  self.outerBox.addWidget(self.tbMark)
2434
2462
  self.outerBox.addWidget(self.tbSuperscript)
2435
2463
  self.outerBox.addWidget(self.tbSubscript)
2436
- self.outerBox.setContentsMargins(cM, cM, cM, cM)
2437
- self.outerBox.setSpacing(cM)
2464
+ self.outerBox.setContentsMargins(4, 4, 4, 4)
2465
+ self.outerBox.setSpacing(4)
2438
2466
 
2439
2467
  self.setLayout(self.outerBox)
2440
2468
  self.updateTheme()
@@ -2448,15 +2476,17 @@ class GuiDocToolBar(QWidget):
2448
2476
 
2449
2477
  def updateTheme(self) -> None:
2450
2478
  """Initialise GUI elements that depend on specific settings."""
2451
- palette = QPalette()
2452
- palette.setColor(QPalette.ColorRole.Window, SHARED.theme.colBack)
2453
- palette.setColor(QPalette.ColorRole.WindowText, SHARED.theme.colText)
2454
- palette.setColor(QPalette.ColorRole.Text, SHARED.theme.colText)
2479
+ syntax = SHARED.theme.syntaxTheme
2480
+
2481
+ palette = self.palette()
2482
+ palette.setColor(QPalette.ColorRole.Window, syntax.back)
2483
+ palette.setColor(QPalette.ColorRole.WindowText, syntax.text)
2484
+ palette.setColor(QPalette.ColorRole.Text, syntax.text)
2455
2485
  self.setPalette(palette)
2456
2486
 
2457
- self.tbBoldMD.setThemeIcon("fmt_bold-md")
2458
- self.tbItalicMD.setThemeIcon("fmt_italic-md")
2459
- self.tbStrikeMD.setThemeIcon("fmt_strike-md")
2487
+ self.tbBoldMD.setThemeIcon("fmt_bold", "orange")
2488
+ self.tbItalicMD.setThemeIcon("fmt_italic", "orange")
2489
+ self.tbStrikeMD.setThemeIcon("fmt_strike", "orange")
2460
2490
  self.tbBold.setThemeIcon("fmt_bold")
2461
2491
  self.tbItalic.setThemeIcon("fmt_italic")
2462
2492
  self.tbStrike.setThemeIcon("fmt_strike")
@@ -2483,7 +2513,6 @@ class GuiDocEditSearch(QFrame):
2483
2513
  self.docEditor = docEditor
2484
2514
 
2485
2515
  iSz = SHARED.theme.baseIconSize
2486
- mPx = CONFIG.pxInt(6)
2487
2516
 
2488
2517
  self.setContentsMargins(0, 0, 0, 0)
2489
2518
  self.setAutoFillBackground(True)
@@ -2509,7 +2538,7 @@ class GuiDocEditSearch(QFrame):
2509
2538
  self.searchOpt.setContentsMargins(0, 0, 0, 0)
2510
2539
 
2511
2540
  self.searchLabel = QLabel(self.tr("Search"), self)
2512
- self.searchLabel.setIndent(CONFIG.pxInt(6))
2541
+ self.searchLabel.setIndent(6)
2513
2542
 
2514
2543
  self.resultLabel = QLabel("?/?", self)
2515
2544
 
@@ -2586,12 +2615,11 @@ class GuiDocEditSearch(QFrame):
2586
2615
  self.mainBox.setColumnStretch(3, 0)
2587
2616
  self.mainBox.setColumnStretch(4, 0)
2588
2617
  self.mainBox.setColumnStretch(5, 0)
2589
- self.mainBox.setSpacing(CONFIG.pxInt(2))
2590
- self.mainBox.setContentsMargins(mPx, mPx, mPx, mPx)
2618
+ self.mainBox.setSpacing(2)
2619
+ self.mainBox.setContentsMargins(6, 6, 6, 6)
2591
2620
 
2592
- boxWidth = CONFIG.pxInt(200)
2593
- self.searchBox.setFixedWidth(boxWidth)
2594
- self.replaceBox.setFixedWidth(boxWidth)
2621
+ self.searchBox.setFixedWidth(200)
2622
+ self.replaceBox.setFixedWidth(200)
2595
2623
  self.replaceBox.setVisible(False)
2596
2624
  self.replaceButton.setVisible(False)
2597
2625
  self.adjustSize()
@@ -2693,10 +2721,11 @@ class GuiDocEditSearch(QFrame):
2693
2721
 
2694
2722
  def updateTheme(self) -> None:
2695
2723
  """Update theme elements."""
2696
- qPalette = QApplication.palette()
2697
- self.setPalette(qPalette)
2698
- self.searchBox.setPalette(qPalette)
2699
- self.replaceBox.setPalette(qPalette)
2724
+ palette = QApplication.palette()
2725
+
2726
+ self.setPalette(palette)
2727
+ self.searchBox.setPalette(palette)
2728
+ self.replaceBox.setPalette(palette)
2700
2729
 
2701
2730
  # Set icons
2702
2731
  self.toggleCase.setIcon(SHARED.theme.getIcon("search_case"))
@@ -2706,31 +2735,13 @@ class GuiDocEditSearch(QFrame):
2706
2735
  self.toggleProject.setIcon(SHARED.theme.getIcon("search_project"))
2707
2736
  self.toggleMatchCap.setIcon(SHARED.theme.getIcon("search_preserve"))
2708
2737
  self.cancelSearch.setIcon(SHARED.theme.getIcon("search_cancel"))
2709
- self.searchButton.setThemeIcon("search")
2710
- self.replaceButton.setThemeIcon("search_replace")
2738
+ self.searchButton.setThemeIcon("search", "green")
2739
+ self.replaceButton.setThemeIcon("search_replace", "green")
2711
2740
 
2712
2741
  # Set stylesheets
2713
2742
  self.searchOpt.setStyleSheet("QToolBar {padding: 0;}")
2714
2743
  self.showReplace.setStyleSheet("QToolButton {border: none; background: transparent;}")
2715
2744
 
2716
- # Construct Box Colours
2717
- qPalette = self.searchBox.palette()
2718
- baseCol = qPalette.base().color()
2719
- rCol = baseCol.redF() + 0.1
2720
- gCol = baseCol.greenF() - 0.1
2721
- bCol = baseCol.blueF() - 0.1
2722
-
2723
- mCol = max(rCol, gCol, bCol, 1.0)
2724
- errCol = QColor()
2725
- errCol.setRedF(rCol/mCol)
2726
- errCol.setGreenF(gCol/mCol)
2727
- errCol.setBlueF(bCol/mCol)
2728
-
2729
- self.rxCol = {
2730
- True: baseCol,
2731
- False: errCol
2732
- }
2733
-
2734
2745
  return
2735
2746
 
2736
2747
  def cycleFocus(self) -> bool:
@@ -2747,7 +2758,7 @@ class GuiDocEditSearch(QFrame):
2747
2758
 
2748
2759
  def anyFocus(self) -> bool:
2749
2760
  """Return True if any of the input boxes have focus."""
2750
- return self.searchBox.hasFocus() or self.replaceBox.hasFocus()
2761
+ return self.hasFocus() or self.isAncestorOf(QApplication.focusWidget())
2751
2762
 
2752
2763
  ##
2753
2764
  # Public Slots
@@ -2831,9 +2842,12 @@ class GuiDocEditSearch(QFrame):
2831
2842
  """Highlight the search box to indicate the search string is or
2832
2843
  isn't valid. Take the colour from the replace box.
2833
2844
  """
2834
- qPalette = self.replaceBox.palette()
2835
- qPalette.setColor(QPalette.ColorRole.Base, self.rxCol[isValid])
2836
- self.searchBox.setPalette(qPalette)
2845
+ palette = self.replaceBox.palette()
2846
+ palette.setColor(
2847
+ QPalette.ColorRole.Text,
2848
+ palette.text().color() if isValid else SHARED.theme.errorText
2849
+ )
2850
+ self.searchBox.setPalette(palette)
2837
2851
  return
2838
2852
 
2839
2853
 
@@ -2859,13 +2873,12 @@ class GuiDocEditHeader(QWidget):
2859
2873
 
2860
2874
  iPx = SHARED.theme.baseIconHeight
2861
2875
  iSz = SHARED.theme.baseIconSize
2862
- mPx = CONFIG.pxInt(4)
2863
2876
 
2864
2877
  # Main Widget Settings
2865
2878
  self.setAutoFillBackground(True)
2866
2879
 
2867
2880
  # Title Label
2868
- self.itemTitle = NColourLabel("", self, faded=SHARED.theme.fadedText)
2881
+ self.itemTitle = NColorLabel("", self, faded=SHARED.theme.fadedText)
2869
2882
  self.itemTitle.setMargin(0)
2870
2883
  self.itemTitle.setContentsMargins(0, 0, 0, 0)
2871
2884
  self.itemTitle.setAutoFillBackground(True)
@@ -2906,13 +2919,13 @@ class GuiDocEditHeader(QWidget):
2906
2919
  self.outerBox.addWidget(self.tbButton, 0)
2907
2920
  self.outerBox.addWidget(self.outlineButton, 0)
2908
2921
  self.outerBox.addWidget(self.searchButton, 0)
2909
- self.outerBox.addSpacing(mPx)
2922
+ self.outerBox.addSpacing(4)
2910
2923
  self.outerBox.addWidget(self.itemTitle, 1)
2911
- self.outerBox.addSpacing(mPx)
2924
+ self.outerBox.addSpacing(4)
2912
2925
  self.outerBox.addSpacing(iPx)
2913
2926
  self.outerBox.addWidget(self.minmaxButton, 0)
2914
2927
  self.outerBox.addWidget(self.closeButton, 0)
2915
- self.outerBox.setContentsMargins(mPx, mPx, mPx, mPx)
2928
+ self.outerBox.setContentsMargins(4, 4, 4, 4)
2916
2929
  self.outerBox.setSpacing(0)
2917
2930
 
2918
2931
  self.setLayout(self.outerBox)
@@ -2923,7 +2936,7 @@ class GuiDocEditHeader(QWidget):
2923
2936
  # Fix Margins and Size
2924
2937
  # This is needed for high DPI systems. See issue #499.
2925
2938
  self.setContentsMargins(0, 0, 0, 0)
2926
- self.setMinimumHeight(iPx + 2*mPx)
2939
+ self.setMinimumHeight(iPx + 8)
2927
2940
 
2928
2941
  self.updateFont()
2929
2942
  self.updateTheme()
@@ -2956,7 +2969,7 @@ class GuiDocEditHeader(QWidget):
2956
2969
  tStart = time()
2957
2970
  self.outlineMenu.clear()
2958
2971
  for number, text in data.items():
2959
- action = self.outlineMenu.addAction(text)
2972
+ action = qtAddAction(self.outlineMenu, text)
2960
2973
  action.triggered.connect(qtLambda(self._gotoBlock, number))
2961
2974
  self._docOutline = data
2962
2975
  logger.debug("Document outline updated in %.3f ms", 1000*(time() - tStart))
@@ -2970,11 +2983,11 @@ class GuiDocEditHeader(QWidget):
2970
2983
 
2971
2984
  def updateTheme(self) -> None:
2972
2985
  """Update theme elements."""
2973
- self.tbButton.setThemeIcon("toolbar")
2974
- self.outlineButton.setThemeIcon("list")
2975
- self.searchButton.setThemeIcon("search")
2976
- self.minmaxButton.setThemeIcon("maximise")
2977
- self.closeButton.setThemeIcon("close")
2986
+ self.tbButton.setThemeIcon("fmt_toolbar", "blue")
2987
+ self.outlineButton.setThemeIcon("list", "blue")
2988
+ self.searchButton.setThemeIcon("search", "blue")
2989
+ self.minmaxButton.setThemeIcon("maximise", "blue")
2990
+ self.closeButton.setThemeIcon("close", "red")
2978
2991
 
2979
2992
  buttonStyle = SHARED.theme.getStyleSheet(STYLES_MIN_TOOLBUTTON)
2980
2993
  self.tbButton.setStyleSheet(buttonStyle)
@@ -2983,18 +2996,19 @@ class GuiDocEditHeader(QWidget):
2983
2996
  self.minmaxButton.setStyleSheet(buttonStyle)
2984
2997
  self.closeButton.setStyleSheet(buttonStyle)
2985
2998
 
2986
- self.matchColours()
2999
+ self.matchColors()
2987
3000
 
2988
3001
  return
2989
3002
 
2990
- def matchColours(self) -> None:
3003
+ def matchColors(self) -> None:
2991
3004
  """Update the colours of the widget to match those of the syntax
2992
3005
  theme rather than the main GUI.
2993
3006
  """
2994
- palette = QPalette()
2995
- palette.setColor(QPalette.ColorRole.Window, SHARED.theme.colBack)
2996
- palette.setColor(QPalette.ColorRole.WindowText, SHARED.theme.colText)
2997
- palette.setColor(QPalette.ColorRole.Text, SHARED.theme.colText)
3007
+ syntax = SHARED.theme.syntaxTheme
3008
+ palette = self.palette()
3009
+ palette.setColor(QPalette.ColorRole.Window, syntax.back)
3010
+ palette.setColor(QPalette.ColorRole.WindowText, syntax.text)
3011
+ palette.setColor(QPalette.ColorRole.Text, syntax.text)
2998
3012
  self.setPalette(palette)
2999
3013
  self.itemTitle.setTextColors(
3000
3014
  color=palette.windowText().color(), faded=SHARED.theme.fadedText
@@ -3047,7 +3061,7 @@ class GuiDocEditHeader(QWidget):
3047
3061
  @pyqtSlot(bool)
3048
3062
  def _focusModeChanged(self, focusMode: bool) -> None:
3049
3063
  """Update minimise/maximise icon of the Focus Mode button."""
3050
- self.minmaxButton.setThemeIcon("minimise" if focusMode else "maximise")
3064
+ self.minmaxButton.setThemeIcon("minimise" if focusMode else "maximise", "blue")
3051
3065
  return
3052
3066
 
3053
3067
  ##
@@ -3080,14 +3094,11 @@ class GuiDocEditFooter(QWidget):
3080
3094
 
3081
3095
  iPx = round(0.9*SHARED.theme.baseIconHeight)
3082
3096
  fPx = int(0.9*SHARED.theme.fontPixelSize)
3083
- mPx = CONFIG.pxInt(8)
3084
- bSp = CONFIG.pxInt(4)
3085
- hSp = CONFIG.pxInt(6)
3086
3097
 
3087
3098
  # Cached Translations
3099
+ self.initSettings()
3088
3100
  self._trLineCount = self.tr("Line: {0} ({1})")
3089
- self._trWordCount = self.tr("Words: {0} ({1})")
3090
- self._trSelectCount = self.tr("Words: {0} selected")
3101
+ self._trSelectCount = self.tr("Selected: {0}")
3091
3102
 
3092
3103
  # Main Widget Settings
3093
3104
  self.setContentsMargins(0, 0, 0, 0)
@@ -3095,72 +3106,60 @@ class GuiDocEditFooter(QWidget):
3095
3106
 
3096
3107
  # Status
3097
3108
  self.statusIcon = QLabel("", self)
3098
- self.statusIcon.setContentsMargins(0, 0, 0, 0)
3099
3109
  self.statusIcon.setFixedHeight(iPx)
3100
3110
  self.statusIcon.setAlignment(QtAlignLeftTop)
3101
3111
 
3102
- self.statusText = QLabel(self.tr("Status"), self)
3103
- self.statusText.setIndent(0)
3104
- self.statusText.setMargin(0)
3105
- self.statusText.setContentsMargins(0, 0, 0, 0)
3112
+ self.statusText = QLabel("", self)
3106
3113
  self.statusText.setAutoFillBackground(True)
3107
3114
  self.statusText.setFixedHeight(fPx)
3108
3115
  self.statusText.setAlignment(QtAlignLeftTop)
3109
3116
 
3110
3117
  # Lines
3111
3118
  self.linesIcon = QLabel("", self)
3112
- self.linesIcon.setContentsMargins(0, 0, 0, 0)
3113
3119
  self.linesIcon.setFixedHeight(iPx)
3114
3120
  self.linesIcon.setAlignment(QtAlignLeftTop)
3115
3121
 
3116
3122
  self.linesText = QLabel("", self)
3117
- self.linesText.setIndent(0)
3118
- self.linesText.setMargin(0)
3119
- self.linesText.setContentsMargins(0, 0, 0, 0)
3120
3123
  self.linesText.setAutoFillBackground(True)
3121
3124
  self.linesText.setFixedHeight(fPx)
3122
3125
  self.linesText.setAlignment(QtAlignLeftTop)
3123
3126
 
3124
3127
  # Words
3125
3128
  self.wordsIcon = QLabel("", self)
3126
- self.wordsIcon.setContentsMargins(0, 0, 0, 0)
3127
3129
  self.wordsIcon.setFixedHeight(iPx)
3128
3130
  self.wordsIcon.setAlignment(QtAlignLeftTop)
3129
3131
 
3130
3132
  self.wordsText = QLabel("", self)
3131
- self.wordsText.setIndent(0)
3132
- self.wordsText.setMargin(0)
3133
- self.wordsText.setContentsMargins(0, 0, 0, 0)
3134
3133
  self.wordsText.setAutoFillBackground(True)
3135
3134
  self.wordsText.setFixedHeight(fPx)
3136
3135
  self.wordsText.setAlignment(QtAlignLeftTop)
3137
3136
 
3138
3137
  # Assemble Layout
3139
3138
  self.outerBox = QHBoxLayout()
3140
- self.outerBox.setSpacing(bSp)
3139
+ self.outerBox.setSpacing(4)
3141
3140
  self.outerBox.addWidget(self.statusIcon)
3142
3141
  self.outerBox.addWidget(self.statusText)
3143
3142
  self.outerBox.addStretch(1)
3144
3143
  self.outerBox.addWidget(self.linesIcon)
3145
3144
  self.outerBox.addWidget(self.linesText)
3146
- self.outerBox.addSpacing(hSp)
3145
+ self.outerBox.addSpacing(6)
3147
3146
  self.outerBox.addWidget(self.wordsIcon)
3148
3147
  self.outerBox.addWidget(self.wordsText)
3149
- self.outerBox.setContentsMargins(mPx, mPx, mPx, mPx)
3148
+ self.outerBox.setContentsMargins(8, 8, 8, 8)
3150
3149
 
3151
3150
  self.setLayout(self.outerBox)
3152
3151
 
3153
3152
  # Fix Margins and Size
3154
3153
  # This is needed for high DPI systems. See issue #499.
3155
3154
  self.setContentsMargins(0, 0, 0, 0)
3156
- self.setMinimumHeight(fPx + 2*mPx)
3155
+ self.setMinimumHeight(fPx + 16)
3157
3156
 
3158
3157
  # Fix the Colours
3159
3158
  self.updateFont()
3160
3159
  self.updateTheme()
3161
3160
 
3162
3161
  # Initialise Info
3163
- self.updateWordCount(0, False)
3162
+ self.updateMainCount(0, False)
3164
3163
 
3165
3164
  logger.debug("Ready: GuiDocEditFooter")
3166
3165
 
@@ -3170,6 +3169,13 @@ class GuiDocEditFooter(QWidget):
3170
3169
  # Methods
3171
3170
  ##
3172
3171
 
3172
+ def initSettings(self) -> None:
3173
+ """Apply user settings."""
3174
+ self._trMainCount = trStats(nwLabels.STATS_DISPLAY[
3175
+ nwStats.CHARS if CONFIG.useCharCount else nwStats.WORDS
3176
+ ])
3177
+ return
3178
+
3173
3179
  def updateFont(self) -> None:
3174
3180
  """Update the font settings."""
3175
3181
  self.setFont(SHARED.theme.guiFont)
@@ -3181,19 +3187,21 @@ class GuiDocEditFooter(QWidget):
3181
3187
  def updateTheme(self) -> None:
3182
3188
  """Update theme elements."""
3183
3189
  iPx = round(0.9*SHARED.theme.baseIconHeight)
3184
- self.linesIcon.setPixmap(SHARED.theme.getPixmap("status_lines", (iPx, iPx)))
3185
- self.wordsIcon.setPixmap(SHARED.theme.getPixmap("status_stats", (iPx, iPx)))
3186
- self.matchColours()
3190
+ self.linesIcon.setPixmap(SHARED.theme.getPixmap("lines", (iPx, iPx)))
3191
+ self.wordsIcon.setPixmap(SHARED.theme.getPixmap("stats", (iPx, iPx)))
3192
+ self.matchColors()
3187
3193
  return
3188
3194
 
3189
- def matchColours(self) -> None:
3195
+ def matchColors(self) -> None:
3190
3196
  """Update the colours of the widget to match those of the syntax
3191
3197
  theme rather than the main GUI.
3192
3198
  """
3193
- palette = QPalette()
3194
- palette.setColor(QPalette.ColorRole.Window, SHARED.theme.colBack)
3195
- palette.setColor(QPalette.ColorRole.WindowText, SHARED.theme.colText)
3196
- palette.setColor(QPalette.ColorRole.Text, SHARED.theme.colText)
3199
+ syntax = SHARED.theme.syntaxTheme
3200
+
3201
+ palette = self.palette()
3202
+ palette.setColor(QPalette.ColorRole.Window, syntax.back)
3203
+ palette.setColor(QPalette.ColorRole.WindowText, syntax.text)
3204
+ palette.setColor(QPalette.ColorRole.Text, syntax.text)
3197
3205
 
3198
3206
  self.setPalette(palette)
3199
3207
  self.statusText.setPalette(palette)
@@ -3212,7 +3220,7 @@ class GuiDocEditFooter(QWidget):
3212
3220
  self._tItem = SHARED.project.tree[self._docHandle]
3213
3221
 
3214
3222
  self.updateInfo()
3215
- self.updateWordCount(0, False)
3223
+ self.updateMainCount(0, False)
3216
3224
 
3217
3225
  return
3218
3226
 
@@ -3234,23 +3242,24 @@ class GuiDocEditFooter(QWidget):
3234
3242
 
3235
3243
  def updateLineCount(self, cursor: QTextCursor) -> None:
3236
3244
  """Update the line and document position counter."""
3237
- cPos = cursor.position() + 1
3238
- cLine = cursor.blockNumber() + 1
3239
- cCount = max(cursor.document().characterCount(), 1)
3240
- self.linesText.setText(
3241
- self._trLineCount.format(f"{cLine:n}", f"{100*cPos//cCount:d} %")
3242
- )
3245
+ if document := cursor.document():
3246
+ cPos = cursor.position() + 1
3247
+ cLine = cursor.blockNumber() + 1
3248
+ cCount = max(document.characterCount(), 1)
3249
+ self.linesText.setText(
3250
+ self._trLineCount.format(f"{cLine:n}", f"{100*cPos//cCount:d} %")
3251
+ )
3243
3252
  return
3244
3253
 
3245
- def updateWordCount(self, wCount: int, selection: bool) -> None:
3246
- """Update word counter information."""
3247
- if selection and wCount:
3248
- wText = self._trSelectCount.format(f"{wCount:n}")
3254
+ def updateMainCount(self, count: int, selection: bool) -> None:
3255
+ """Update main counter information."""
3256
+ if selection and count:
3257
+ text = self._trSelectCount.format(f"{count:n}")
3249
3258
  elif self._tItem:
3250
- wCount = self._tItem.wordCount
3251
- wDiff = wCount - self._tItem.initCount
3252
- wText = self._trWordCount.format(f"{wCount:n}", f"{wDiff:+n}")
3259
+ count = self._tItem.mainCount
3260
+ diff = count - self._tItem.initCount
3261
+ text = self._trMainCount.format(f"{count:n}", f"{diff:+n}")
3253
3262
  else:
3254
- wText = self._trWordCount.format("0", "+0")
3255
- self.wordsText.setText(wText)
3263
+ text = self._trMainCount.format("0", "+0")
3264
+ self.wordsText.setText(text)
3256
3265
  return