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
novelwriter/config.py CHANGED
@@ -29,33 +29,64 @@ import json
29
29
  import logging
30
30
  import sys
31
31
 
32
- from datetime import datetime
33
32
  from pathlib import Path
34
33
  from time import time
35
- from typing import TYPE_CHECKING
34
+ from typing import TYPE_CHECKING, Final
36
35
 
37
- from PyQt5.QtCore import (
38
- PYQT_VERSION, PYQT_VERSION_STR, QT_VERSION, QT_VERSION_STR, QLibraryInfo,
39
- QLocale, QStandardPaths, QSysInfo, QTranslator
36
+ from PyQt6.QtCore import (
37
+ PYQT_VERSION, PYQT_VERSION_STR, QT_VERSION, QT_VERSION_STR, QDate,
38
+ QDateTime, QLibraryInfo, QLocale, QStandardPaths, QSysInfo, QTranslator
40
39
  )
41
- from PyQt5.QtGui import QFont, QFontDatabase
42
- from PyQt5.QtWidgets import QApplication
40
+ from PyQt6.QtGui import QFont, QFontDatabase, QFontMetrics
41
+ from PyQt6.QtWidgets import QApplication
43
42
 
44
43
  from novelwriter.common import (
45
44
  NWConfigParser, checkInt, checkPath, describeFont, fontMatcher,
46
- formatTimeStamp
45
+ formatTimeStamp, processDialogSymbols, simplified
47
46
  )
48
- from novelwriter.constants import nwFiles, nwUnicode
47
+ from novelwriter.constants import nwFiles, nwHtmlUnicode, nwQuotes, nwUnicode
49
48
  from novelwriter.error import formatException, logException
50
49
 
51
- if TYPE_CHECKING: # pragma: no cover
50
+ if TYPE_CHECKING:
51
+ from datetime import datetime
52
+
52
53
  from novelwriter.core.projectdata import NWProjectData
54
+ from novelwriter.splash import NSplashScreen
53
55
 
54
56
  logger = logging.getLogger(__name__)
55
57
 
58
+ DEF_GUI = "default"
59
+ DEF_SYNTAX = "default_light"
60
+ DEF_ICONS = "material_rounded_normal"
61
+ DEF_TREECOL = "theme"
62
+
56
63
 
57
64
  class Config:
58
65
 
66
+ __slots__ = (
67
+ "_appPath", "_appRoot", "_backPath", "_backupPath", "_confPath", "_dLocale", "_dShortDate",
68
+ "_dShortDateTime", "_dataPath", "_errData", "_hasError", "_homePath", "_lastAuthor",
69
+ "_manuals", "_nwLangPath", "_qLocale", "_qtLangPath", "_qtTrans", "_recentPaths",
70
+ "_recentProjects", "_splash", "allowOpenDial", "altDialogClose", "altDialogOpen",
71
+ "appHandle", "appName", "askBeforeBackup", "askBeforeExit", "autoSaveDoc", "autoSaveProj",
72
+ "autoScroll", "autoScrollPos", "autoSelect", "backupOnClose", "cursorWidth", "dialogLine",
73
+ "dialogStyle", "doJustify", "doReplace", "doReplaceDQuote", "doReplaceDash",
74
+ "doReplaceDots", "doReplaceSQuote", "emphLabels", "fmtApostrophe", "fmtDQuoteClose",
75
+ "fmtDQuoteOpen", "fmtPadAfter", "fmtPadBefore", "fmtPadThin", "fmtSQuoteClose",
76
+ "fmtSQuoteOpen", "focusWidth", "guiFont", "guiLocale", "guiSyntax", "guiTheme",
77
+ "hasEnchant", "hideFocusFooter", "hideHScroll", "hideVScroll", "highlightEmph", "hostName",
78
+ "iconColDocs", "iconColTree", "iconTheme", "incNotesWCount", "isDebug", "kernelVer",
79
+ "lastNotes", "mainPanePos", "mainWinSize", "memInfo", "narratorBreak", "narratorDialog",
80
+ "nativeFont", "osDarwin", "osLinux", "osType", "osUnknown", "osWindows", "outlinePanePos",
81
+ "prefsWinSize", "scrollPastEnd", "searchCase", "searchLoop", "searchMatchCap",
82
+ "searchNextFile", "searchProjCase", "searchProjRegEx", "searchProjWord", "searchRegEx",
83
+ "searchWord", "showEditToolBar", "showFullPath", "showLineEndings", "showMultiSpaces",
84
+ "showSessionTime", "showTabsNSpaces", "showViewerPanel", "spellLanguage", "stopWhenIdle",
85
+ "tabWidth", "textFont", "textMargin", "textWidth", "useCharCount", "userIdleTime",
86
+ "verPyQtString", "verPyQtValue", "verPyString", "verQtString", "verQtValue",
87
+ "viewComments", "viewPanePos", "viewSynopsis", "welcomeWinSize",
88
+ )
89
+
59
90
  LANG_NW = 1
60
91
  LANG_PROJ = 2
61
92
 
@@ -64,6 +95,8 @@ class Config:
64
95
  # Initialisation
65
96
  # ==============
66
97
 
98
+ self._splash = None
99
+
67
100
  # Set Application Variables
68
101
  self.appName = "novelWriter"
69
102
  self.appHandle = "novelwriter"
@@ -83,10 +116,10 @@ class Config:
83
116
 
84
117
  self._appPath = Path(__file__).parent.absolute()
85
118
  self._appRoot = self._appPath.parent
86
- if self._appRoot.is_file():
87
- # novelWriter is packaged as a single file
88
- self._appRoot = self._appRoot.parent
89
- self._appPath = self._appRoot
119
+ if getattr(sys, "frozen", False): # pragma: no cover
120
+ # novelWriter is packaged as an exe
121
+ self._appPath = Path(__file__).parent.parent.absolute()
122
+ self._appRoot = self._appPath
90
123
 
91
124
  # Runtime Settings and Variables
92
125
  self._hasError = False # True if the config class encountered an error
@@ -95,7 +128,7 @@ class Config:
95
128
  # Localisation
96
129
  # Note that these paths must be strings
97
130
  self._nwLangPath = self._appPath / "assets" / "i18n"
98
- self._qtLangPath = QLibraryInfo.location(QLibraryInfo.LibraryLocation.TranslationsPath)
131
+ self._qtLangPath = QLibraryInfo.path(QLibraryInfo.LibraryPath.TranslationsPath)
99
132
 
100
133
  hasLocale = (self._nwLangPath / f"nw_{QLocale.system().name()}.qm").exists()
101
134
  self._qLocale = QLocale.system() if hasLocale else QLocale("en_GB")
@@ -116,25 +149,31 @@ class Config:
116
149
 
117
150
  self._recentProjects = RecentProjects(self)
118
151
  self._recentPaths = RecentPaths(self)
152
+ self._lastAuthor = ""
119
153
 
120
154
  # General GUI Settings
121
155
  self.guiLocale = self._qLocale.name()
122
- self.guiTheme = "default_light" # GUI theme
123
- self.guiSyntax = "default_light" # Syntax theme
124
- self.guiFont = QFont() # Main GUI font
125
- self.guiScale = 1.0 # Set automatically by Theme class
126
- self.hideVScroll = False # Hide vertical scroll bars on main widgets
127
- self.hideHScroll = False # Hide horizontal scroll bars on main widgets
128
- self.lastNotes = "0x0" # The latest release notes that have been shown
129
- self.nativeFont = True # Use native font dialog
156
+ self.guiTheme = DEF_GUI # GUI theme
157
+ self.guiSyntax = DEF_SYNTAX # Syntax theme
158
+ self.guiFont = QFont() # Main GUI font
159
+ self.hideVScroll = False # Hide vertical scroll bars on main widgets
160
+ self.hideHScroll = False # Hide horizontal scroll bars on main widgets
161
+ self.lastNotes = "0x0" # The latest release notes that have been shown
162
+ self.nativeFont = True # Use native font dialog
163
+ self.useCharCount = False # Use character count as primary count
164
+
165
+ # Icons
166
+ self.iconTheme = DEF_ICONS # Icons theme
167
+ self.iconColTree = DEF_TREECOL # Project tree icon colours
168
+ self.iconColDocs = False # Keep theme colours on documents
130
169
 
131
170
  # Size Settings
132
- self._mainWinSize = [1200, 650] # Last size of the main GUI window
133
- self._welcomeSize = [800, 550] # Last size of the welcome window
134
- self._prefsWinSize = [700, 615] # Last size of the Preferences dialog
135
- self._mainPanePos = [300, 800] # Last position of the main window splitter
136
- self._viewPanePos = [500, 150] # Last position of the document viewer splitter
137
- self._outlnPanePos = [500, 150] # Last position of the outline panel splitter
171
+ self.mainWinSize = [1200, 650] # Last size of the main GUI window
172
+ self.welcomeWinSize = [800, 550] # Last size of the welcome window
173
+ self.prefsWinSize = [700, 615] # Last size of the Preferences dialog
174
+ self.mainPanePos = [300, 800] # Last position of the main window splitter
175
+ self.viewPanePos = [500, 150] # Last position of the document viewer splitter
176
+ self.outlinePanePos = [500, 150] # Last position of the outline panel splitter
138
177
 
139
178
  # Project Settings
140
179
  self.autoSaveProj = 60 # Interval for auto-saving project, in seconds
@@ -149,6 +188,7 @@ class Config:
149
188
  self.textWidth = 700 # Editor text width
150
189
  self.textMargin = 40 # Editor/viewer text margin
151
190
  self.tabWidth = 40 # Editor tabulator width
191
+ self.cursorWidth = 1 # Editor cursor width
152
192
 
153
193
  self.focusWidth = 800 # Focus Mode text width
154
194
  self.hideFocusFooter = False # Hide document footer in Focus Mode
@@ -220,7 +260,7 @@ class Config:
220
260
  # System and App Information
221
261
  # ==========================
222
262
 
223
- # Check Qt5 Versions
263
+ # Check Qt Versions
224
264
  self.verQtString = QT_VERSION_STR
225
265
  self.verQtValue = QT_VERSION
226
266
  self.verPyQtString = PYQT_VERSION_STR
@@ -267,7 +307,12 @@ class Config:
267
307
 
268
308
  @property
269
309
  def pdfDocs(self) -> Path | None:
270
- return self._manuals.get(f"manual_{self.locale.name()}", self._manuals.get("manual"))
310
+ """Return the local manual PDF file, if any exist."""
311
+ return self._manuals.get(f"manual_{self.locale.bcp47Name()}", self._manuals.get("manual"))
312
+
313
+ @property
314
+ def nwLangPath(self) -> Path:
315
+ return self._nwLangPath
271
316
 
272
317
  @property
273
318
  def locale(self) -> QLocale:
@@ -278,28 +323,9 @@ class Config:
278
323
  return self._recentProjects
279
324
 
280
325
  @property
281
- def mainWinSize(self) -> list[int]:
282
- return [int(x*self.guiScale) for x in self._mainWinSize]
283
-
284
- @property
285
- def welcomeWinSize(self) -> list[int]:
286
- return [int(x*self.guiScale) for x in self._welcomeSize]
287
-
288
- @property
289
- def preferencesWinSize(self) -> list[int]:
290
- return [int(x*self.guiScale) for x in self._prefsWinSize]
291
-
292
- @property
293
- def mainPanePos(self) -> list[int]:
294
- return [int(x*self.guiScale) for x in self._mainPanePos]
295
-
296
- @property
297
- def viewPanePos(self) -> list[int]:
298
- return [int(x*self.guiScale) for x in self._viewPanePos]
299
-
300
- @property
301
- def outlinePanePos(self) -> list[int]:
302
- return [int(x*self.guiScale) for x in self._outlnPanePos]
326
+ def lastAuthor(self) -> str:
327
+ """Return the last author name used."""
328
+ return simplified(self._lastAuthor)
303
329
 
304
330
  ##
305
331
  # Getters
@@ -308,68 +334,46 @@ class Config:
308
334
  def getTextWidth(self, focusMode: bool = False) -> int:
309
335
  """Get the text with for the correct editor mode."""
310
336
  if focusMode:
311
- return self.pxInt(max(self.focusWidth, 200))
337
+ return max(self.focusWidth, 200)
312
338
  else:
313
- return self.pxInt(max(self.textWidth, 200))
314
-
315
- def getTextMargin(self) -> int:
316
- """Get the scaled text margin."""
317
- return self.pxInt(max(self.textMargin, 0))
318
-
319
- def getTabWidth(self) -> int:
320
- """Get the scaled tab width."""
321
- return self.pxInt(max(self.tabWidth, 0))
339
+ return max(self.textWidth, 200)
322
340
 
323
341
  ##
324
342
  # Setters
325
343
  ##
326
344
 
345
+ def setLastAuthor(self, value: str) -> None:
346
+ """Set tle last used author name."""
347
+ self._lastAuthor = simplified(value)
348
+ return
349
+
327
350
  def setMainWinSize(self, width: int, height: int) -> None:
328
351
  """Set the size of the main window, but only if the change is
329
352
  larger than 5 pixels. The OS window manager will sometimes
330
353
  adjust it a bit, and we don't want the main window to shrink or
331
354
  grow each time the app is opened.
332
355
  """
333
- width = int(width/self.guiScale)
334
- height = int(height/self.guiScale)
335
- if abs(self._mainWinSize[0] - width) > 5:
336
- self._mainWinSize[0] = width
337
- if abs(self._mainWinSize[1] - height) > 5:
338
- self._mainWinSize[1] = height
356
+ if abs(self.mainWinSize[0] - width) > 5:
357
+ self.mainWinSize[0] = width
358
+ if abs(self.mainWinSize[1] - height) > 5:
359
+ self.mainWinSize[1] = height
339
360
  return
340
361
 
341
362
  def setWelcomeWinSize(self, width: int, height: int) -> None:
342
363
  """Set the size of the Preferences dialog window."""
343
- self._welcomeSize[0] = int(width/self.guiScale)
344
- self._welcomeSize[1] = int(height/self.guiScale)
364
+ self.welcomeWinSize = [width, height]
345
365
  return
346
366
 
347
367
  def setPreferencesWinSize(self, width: int, height: int) -> None:
348
368
  """Set the size of the Preferences dialog window."""
349
- self._prefsWinSize[0] = int(width/self.guiScale)
350
- self._prefsWinSize[1] = int(height/self.guiScale)
351
- return
352
-
353
- def setMainPanePos(self, pos: list[int]) -> None:
354
- """Set the position of the main GUI splitter."""
355
- self._mainPanePos = [int(x/self.guiScale) for x in pos]
356
- return
357
-
358
- def setViewPanePos(self, pos: list[int]) -> None:
359
- """Set the position of the viewer meta data splitter."""
360
- self._viewPanePos = [int(x/self.guiScale) for x in pos]
361
- return
362
-
363
- def setOutlinePanePos(self, pos: list[int]) -> None:
364
- """Set the position of the outline details splitter."""
365
- self._outlnPanePos = [int(x/self.guiScale) for x in pos]
369
+ self.prefsWinSize = [width, height]
366
370
  return
367
371
 
368
372
  def setLastPath(self, key: str, path: str | Path) -> None:
369
373
  """Set the last used path. Only the folder is saved, so if the
370
374
  path is not a folder, the parent of the path is used instead.
371
375
  """
372
- if isinstance(path, (str, Path)):
376
+ if isinstance(path, str | Path):
373
377
  path = checkPath(path, self._homePath)
374
378
  if not path.is_dir():
375
379
  path = path.parent
@@ -392,13 +396,12 @@ class Config:
392
396
  self.guiFont = fontMatcher(font)
393
397
  else:
394
398
  font = QFont()
395
- fontDB = QFontDatabase()
396
- if self.osWindows and "Arial" in fontDB.families():
399
+ if self.osWindows and "Arial" in QFontDatabase.families():
397
400
  # On Windows we default to Arial if possible
398
401
  font.setFamily("Arial")
399
402
  font.setPointSize(10)
400
403
  else:
401
- font = fontDB.systemFont(QFontDatabase.SystemFont.GeneralFont)
404
+ font = QFontDatabase.systemFont(QFontDatabase.SystemFont.GeneralFont)
402
405
  self.guiFont = fontMatcher(font)
403
406
  logger.debug("GUI font set to: %s", describeFont(font))
404
407
  QApplication.setFont(self.guiFont)
@@ -415,8 +418,7 @@ class Config:
415
418
  font.fromString(value)
416
419
  self.textFont = fontMatcher(font)
417
420
  else:
418
- fontDB = QFontDatabase()
419
- fontFam = fontDB.families()
421
+ fontFam = QFontDatabase.families()
420
422
  if self.osWindows and "Arial" in fontFam:
421
423
  font = QFont()
422
424
  font.setFamily("Arial")
@@ -426,7 +428,7 @@ class Config:
426
428
  font.setFamily("Helvetica")
427
429
  font.setPointSize(12)
428
430
  else:
429
- font = fontDB.systemFont(QFontDatabase.SystemFont.GeneralFont)
431
+ font = QFontDatabase.systemFont(QFontDatabase.SystemFont.GeneralFont)
430
432
  self.textFont = fontMatcher(font)
431
433
  logger.debug("Text font set to: %s", describeFont(self.textFont))
432
434
  return
@@ -435,14 +437,6 @@ class Config:
435
437
  # Methods
436
438
  ##
437
439
 
438
- def pxInt(self, value: int) -> int:
439
- """Scale fixed gui sizes by the screen scale factor."""
440
- return int(value*self.guiScale)
441
-
442
- def rpxInt(self, value: int) -> int:
443
- """Un-scale fixed gui sizes by the screen scale factor."""
444
- return int(value/self.guiScale)
445
-
446
440
  def homePath(self) -> Path:
447
441
  """The user's home folder."""
448
442
  return self._homePath
@@ -484,11 +478,16 @@ class Config:
484
478
 
485
479
  def localDate(self, value: datetime) -> str:
486
480
  """Return a localised date format."""
487
- return self._dLocale.toString(value, self._dShortDate)
481
+ # Explicitly convert the date first, see bug #2325
482
+ return self._dLocale.toString(QDate(value.year, value.month, value.day), self._dShortDate)
488
483
 
489
484
  def localDateTime(self, value: datetime) -> str:
490
485
  """Return a localised datetime format."""
491
- return self._dLocale.toString(value, self._dShortDateTime)
486
+ # Explicitly convert the datetime first, see bug #2325
487
+ return self._dLocale.toString(
488
+ QDateTime(value.year, value.month, value.day, value.hour, value.minute, value.second),
489
+ self._dShortDateTime,
490
+ )
492
491
 
493
492
  def listLanguages(self, lngSet: int) -> list[tuple[str, str]]:
494
493
  """List localisation files in the i18n folder. The default GUI
@@ -517,20 +516,27 @@ class Config:
517
516
 
518
517
  return sorted(langList.items(), key=lambda x: x[0])
519
518
 
519
+ def splashMessage(self, message: str) -> None:
520
+ """Send a message to the splash screen."""
521
+ if self._splash:
522
+ self._splash.showStatus(message)
523
+ return
524
+
520
525
  ##
521
526
  # Config Actions
522
527
  ##
523
528
 
524
- def initConfig(self, confPath: str | Path | None = None,
525
- dataPath: str | Path | None = None) -> None:
529
+ def initConfig(
530
+ self, confPath: str | Path | None = None, dataPath: str | Path | None = None
531
+ ) -> None:
526
532
  """Initialise the config class. The manual setting of confPath
527
533
  and dataPath is mainly intended for the test suite.
528
534
  """
529
535
  logger.debug("Initialising Config ...")
530
- if isinstance(confPath, (str, Path)):
536
+ if isinstance(confPath, str | Path):
531
537
  logger.info("Setting alternative config path: %s", confPath)
532
538
  self._confPath = Path(confPath)
533
- if isinstance(dataPath, (str, Path)):
539
+ if isinstance(dataPath, str | Path):
534
540
  logger.info("Setting alternative data path: %s", dataPath)
535
541
  self._dataPath = Path(dataPath)
536
542
 
@@ -562,6 +568,8 @@ class Config:
562
568
 
563
569
  def initLocalisation(self, nwApp: QApplication) -> None:
564
570
  """Initialise the localisation of the GUI."""
571
+ self.splashMessage("Loading localisation ...")
572
+
565
573
  self._qLocale = QLocale(self.guiLocale)
566
574
  QLocale.setDefault(self._qLocale)
567
575
  self._qtTrans = {}
@@ -572,13 +580,13 @@ class Config:
572
580
  self._dShortDateTime = self._dLocale.dateTimeFormat(QLocale.FormatType.ShortFormat)
573
581
 
574
582
  langList = [
575
- (self._qtLangPath, "qtbase"), # Qt 5.x
583
+ (self._qtLangPath, "qtbase"), # Qt
576
584
  (str(self._nwLangPath), "nw"), # novelWriter
577
585
  ]
578
586
  for lngPath, lngBase in langList:
579
587
  for lngCode in self._qLocale.uiLanguages():
580
588
  qTrans = QTranslator()
581
- lngFile = "%s_%s" % (lngBase, lngCode.replace("-", "_"))
589
+ lngFile = "{0}_{1}".format(lngBase, lngCode.replace("-", "_"))
582
590
  if lngFile not in self._qtTrans:
583
591
  if qTrans.load(lngFile, lngPath):
584
592
  logger.debug("Loaded: %s.qm", lngFile)
@@ -587,8 +595,11 @@ class Config:
587
595
 
588
596
  return
589
597
 
590
- def loadConfig(self) -> bool:
598
+ def loadConfig(self, splash: NSplashScreen | None = None) -> bool:
591
599
  """Load preferences from file and replace default settings."""
600
+ self._splash = splash
601
+ self.splashMessage("Loading user configuration ...")
602
+
592
603
  logger.debug("Loading config file")
593
604
 
594
605
  conf = NWConfigParser()
@@ -615,22 +626,26 @@ class Config:
615
626
  # Main
616
627
  sec = "Main"
617
628
  self.setGuiFont(conf.rdStr(sec, "font", ""))
618
- self.guiTheme = conf.rdStr(sec, "theme", self.guiTheme)
619
- self.guiSyntax = conf.rdStr(sec, "syntax", self.guiSyntax)
620
- self.guiLocale = conf.rdStr(sec, "localisation", self.guiLocale)
621
- self.hideVScroll = conf.rdBool(sec, "hidevscroll", self.hideVScroll)
622
- self.hideHScroll = conf.rdBool(sec, "hidehscroll", self.hideHScroll)
623
- self.lastNotes = conf.rdStr(sec, "lastnotes", self.lastNotes)
624
- self.nativeFont = conf.rdBool(sec, "nativefont", self.nativeFont)
629
+ self.guiTheme = conf.rdStr(sec, "theme", self.guiTheme)
630
+ self.guiSyntax = conf.rdStr(sec, "syntax", self.guiSyntax)
631
+ self.iconTheme = conf.rdStr(sec, "icons", self.iconTheme)
632
+ self.iconColTree = conf.rdStr(sec, "iconcoltree", self.iconColTree)
633
+ self.iconColDocs = conf.rdBool(sec, "iconcoldocs", self.iconColDocs)
634
+ self.guiLocale = conf.rdStr(sec, "localisation", self.guiLocale)
635
+ self.hideVScroll = conf.rdBool(sec, "hidevscroll", self.hideVScroll)
636
+ self.hideHScroll = conf.rdBool(sec, "hidehscroll", self.hideHScroll)
637
+ self.lastNotes = conf.rdStr(sec, "lastnotes", self.lastNotes)
638
+ self.nativeFont = conf.rdBool(sec, "nativefont", self.nativeFont)
639
+ self.useCharCount = conf.rdBool(sec, "usecharcount", self.useCharCount)
625
640
 
626
641
  # Sizes
627
642
  sec = "Sizes"
628
- self._mainWinSize = conf.rdIntList(sec, "mainwindow", self._mainWinSize)
629
- self._welcomeSize = conf.rdIntList(sec, "welcome", self._welcomeSize)
630
- self._prefsWinSize = conf.rdIntList(sec, "preferences", self._prefsWinSize)
631
- self._mainPanePos = conf.rdIntList(sec, "mainpane", self._mainPanePos)
632
- self._viewPanePos = conf.rdIntList(sec, "viewpane", self._viewPanePos)
633
- self._outlnPanePos = conf.rdIntList(sec, "outlinepane", self._outlnPanePos)
643
+ self.mainWinSize = conf.rdIntList(sec, "mainwindow", self.mainWinSize)
644
+ self.welcomeWinSize = conf.rdIntList(sec, "welcome", self.welcomeWinSize)
645
+ self.prefsWinSize = conf.rdIntList(sec, "preferences", self.prefsWinSize)
646
+ self.mainPanePos = conf.rdIntList(sec, "mainpane", self.mainPanePos)
647
+ self.viewPanePos = conf.rdIntList(sec, "viewpane", self.viewPanePos)
648
+ self.outlinePanePos = conf.rdIntList(sec, "outlinepane", self.outlinePanePos)
634
649
 
635
650
  # Project
636
651
  sec = "Project"
@@ -641,6 +656,7 @@ class Config:
641
656
  self.backupOnClose = conf.rdBool(sec, "backuponclose", self.backupOnClose)
642
657
  self.askBeforeBackup = conf.rdBool(sec, "askbeforebackup", self.askBeforeBackup)
643
658
  self.askBeforeExit = conf.rdBool(sec, "askbeforeexit", self.askBeforeExit)
659
+ self._lastAuthor = conf.rdStr(sec, "lastauthor", self._lastAuthor)
644
660
 
645
661
  # Editor
646
662
  sec = "Editor"
@@ -648,6 +664,7 @@ class Config:
648
664
  self.textWidth = conf.rdInt(sec, "width", self.textWidth)
649
665
  self.textMargin = conf.rdInt(sec, "margin", self.textMargin)
650
666
  self.tabWidth = conf.rdInt(sec, "tabwidth", self.tabWidth)
667
+ self.cursorWidth = conf.rdInt(sec, "cursorwidth", self.cursorWidth)
651
668
  self.focusWidth = conf.rdInt(sec, "focuswidth", self.focusWidth)
652
669
  self.hideFocusFooter = conf.rdBool(sec, "hidefocusfooter", self.hideFocusFooter)
653
670
  self.doJustify = conf.rdBool(sec, "justify", self.doJustify)
@@ -675,9 +692,9 @@ class Config:
675
692
  self.showFullPath = conf.rdBool(sec, "showfullpath", self.showFullPath)
676
693
  self.dialogStyle = conf.rdInt(sec, "dialogstyle", self.dialogStyle)
677
694
  self.allowOpenDial = conf.rdBool(sec, "allowopendial", self.allowOpenDial)
678
- self.dialogLine = conf.rdStr(sec, "dialogline", self.dialogLine)
679
- self.narratorBreak = conf.rdStr(sec, "narratorbreak", self.narratorBreak)
680
- self.narratorDialog = conf.rdStr(sec, "narratordialog", self.narratorDialog)
695
+ dialogLine = conf.rdStr(sec, "dialogline", self.dialogLine)
696
+ narratorBreak = conf.rdStr(sec, "narratorbreak", self.narratorBreak)
697
+ narratorDialog = conf.rdStr(sec, "narratordialog", self.narratorDialog)
681
698
  self.altDialogOpen = conf.rdStr(sec, "altdialogopen", self.altDialogOpen)
682
699
  self.altDialogClose = conf.rdStr(sec, "altdialogclose", self.altDialogClose)
683
700
  self.highlightEmph = conf.rdBool(sec, "highlightemph", self.highlightEmph)
@@ -704,6 +721,9 @@ class Config:
704
721
  # Check Values
705
722
  # ============
706
723
 
724
+ self._prepareFont(self.guiFont, "GUI")
725
+ self._prepareFont(self.textFont, "document")
726
+
707
727
  # If we're using straight quotes, disable auto-replace
708
728
  if self.fmtSQuoteOpen == self.fmtSQuoteClose == "'" and self.doReplaceSQuote:
709
729
  logger.info("Using straight single quotes, so disabling auto-replace")
@@ -713,6 +733,10 @@ class Config:
713
733
  logger.info("Using straight double quotes, so disabling auto-replace")
714
734
  self.doReplaceDQuote = False
715
735
 
736
+ self.dialogLine = processDialogSymbols(dialogLine)
737
+ self.narratorBreak = narratorBreak if narratorBreak in nwQuotes.DASHES else ""
738
+ self.narratorDialog = narratorDialog if narratorDialog in nwQuotes.DASHES else ""
739
+
716
740
  return True
717
741
 
718
742
  def saveConfig(self) -> bool:
@@ -729,20 +753,24 @@ class Config:
729
753
  "font": self.guiFont.toString(),
730
754
  "theme": str(self.guiTheme),
731
755
  "syntax": str(self.guiSyntax),
756
+ "icons": str(self.iconTheme),
757
+ "iconcoltree": str(self.iconColTree),
758
+ "iconcoldocs": str(self.iconColDocs),
732
759
  "localisation": str(self.guiLocale),
733
760
  "hidevscroll": str(self.hideVScroll),
734
761
  "hidehscroll": str(self.hideHScroll),
735
762
  "lastnotes": str(self.lastNotes),
736
763
  "nativefont": str(self.nativeFont),
764
+ "usecharcount": str(self.useCharCount),
737
765
  }
738
766
 
739
767
  conf["Sizes"] = {
740
- "mainwindow": self._packList(self._mainWinSize),
741
- "welcome": self._packList(self._welcomeSize),
742
- "preferences": self._packList(self._prefsWinSize),
743
- "mainpane": self._packList(self._mainPanePos),
744
- "viewpane": self._packList(self._viewPanePos),
745
- "outlinepane": self._packList(self._outlnPanePos),
768
+ "mainwindow": self._packList(self.mainWinSize),
769
+ "welcome": self._packList(self.welcomeWinSize),
770
+ "preferences": self._packList(self.prefsWinSize),
771
+ "mainpane": self._packList(self.mainPanePos),
772
+ "viewpane": self._packList(self.viewPanePos),
773
+ "outlinepane": self._packList(self.outlinePanePos),
746
774
  }
747
775
 
748
776
  conf["Project"] = {
@@ -753,6 +781,7 @@ class Config:
753
781
  "backuponclose": str(self.backupOnClose),
754
782
  "askbeforebackup": str(self.askBeforeBackup),
755
783
  "askbeforeexit": str(self.askBeforeExit),
784
+ "lastauthor": str(self._lastAuthor),
756
785
  }
757
786
 
758
787
  conf["Editor"] = {
@@ -760,6 +789,7 @@ class Config:
760
789
  "width": str(self.textWidth),
761
790
  "margin": str(self.textMargin),
762
791
  "tabwidth": str(self.tabWidth),
792
+ "cursorwidth": str(self.cursorWidth),
763
793
  "focuswidth": str(self.focusWidth),
764
794
  "hidefocusfooter": str(self.hideFocusFooter),
765
795
  "justify": str(self.doJustify),
@@ -829,6 +859,11 @@ class Config:
829
859
 
830
860
  return True
831
861
 
862
+ def finishStartup(self) -> None:
863
+ """Call after startup is complete."""
864
+ self._splash = None
865
+ return
866
+
832
867
  ##
833
868
  # Internal Functions
834
869
  ##
@@ -851,6 +886,17 @@ class Config:
851
886
  logger.debug("Checking package 'pyenchant': OK")
852
887
  return
853
888
 
889
+ def _prepareFont(self, font: QFont, kind: str) -> None:
890
+ """Check Unicode availability in font. This also initialises any
891
+ alternative character used for missing glyphs. See #2315.
892
+ """
893
+ self.splashMessage(f"Initialising {kind} font: {font.family()}")
894
+ metrics = QFontMetrics(font)
895
+ for char in nwHtmlUnicode.U_TO_H.keys():
896
+ if not metrics.inFont(char): # type: ignore
897
+ logger.warning("No glyph U+%04x in font", ord(char)) # pragma: no cover
898
+ return
899
+
854
900
 
855
901
  class RecentProjects:
856
902
 
@@ -873,9 +919,10 @@ class RecentProjects:
873
919
  puuid = str(entry.get("uuid", ""))
874
920
  title = str(entry.get("title", ""))
875
921
  words = checkInt(entry.get("words", 0), 0)
922
+ chars = checkInt(entry.get("chars", 0), 0)
876
923
  saved = checkInt(entry.get("time", 0), 0)
877
924
  if path and title:
878
- self._setEntry(puuid, path, title, words, saved)
925
+ self._setEntry(puuid, path, title, words, chars, saved)
879
926
  except Exception:
880
927
  logger.error("Could not load recent project cache")
881
928
  logException()
@@ -908,7 +955,14 @@ class RecentProjects:
908
955
  try:
909
956
  if (remove := self._map.get(data.uuid)) and (remove != str(path)):
910
957
  self.remove(remove)
911
- self._setEntry(data.uuid, str(path), data.name, sum(data.currCounts), int(saved))
958
+ self._setEntry(
959
+ data.uuid,
960
+ str(path),
961
+ data.name,
962
+ sum(data.currCounts[:2]),
963
+ sum(data.currCounts[2:]),
964
+ int(saved),
965
+ )
912
966
  self.saveCache()
913
967
  except Exception:
914
968
  pass
@@ -921,9 +975,17 @@ class RecentProjects:
921
975
  self.saveCache()
922
976
  return
923
977
 
924
- def _setEntry(self, puuid: str, path: str, title: str, words: int, saved: int) -> None:
978
+ def _setEntry(
979
+ self, puuid: str, path: str, title: str, words: int, chars: int, saved: int
980
+ ) -> None:
925
981
  """Set an entry in the recent projects record."""
926
- self._data[path] = {"uuid": puuid, "title": title, "words": words, "time": saved}
982
+ self._data[path] = {
983
+ "uuid": puuid,
984
+ "title": title,
985
+ "words": words,
986
+ "chars": chars,
987
+ "time": saved,
988
+ }
927
989
  if puuid:
928
990
  self._map[puuid] = path
929
991
  return
@@ -931,7 +993,7 @@ class RecentProjects:
931
993
 
932
994
  class RecentPaths:
933
995
 
934
- KEYS = ["default", "project", "import", "outline", "stats"]
996
+ KEYS: Final[list[str]] = ["default", "project", "import", "outline", "stats"]
935
997
 
936
998
  def __init__(self, config: Config) -> None:
937
999
  self._conf = config