novelWriter 2.7b1__py3-none-any.whl → 2.7.1__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 (81) hide show
  1. novelwriter/__init__.py +17 -4
  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_cs_CZ.json +2 -0
  16. novelwriter/assets/i18n/project_de_DE.json +3 -1
  17. novelwriter/assets/i18n/project_en_GB.json +1 -0
  18. novelwriter/assets/i18n/project_en_US.json +2 -0
  19. novelwriter/assets/i18n/project_it_IT.json +2 -0
  20. novelwriter/assets/i18n/project_ja_JP.json +2 -0
  21. novelwriter/assets/i18n/project_nb_NO.json +2 -0
  22. novelwriter/assets/i18n/project_nn_NO.json +5 -0
  23. novelwriter/assets/i18n/project_pl_PL.json +2 -0
  24. novelwriter/assets/i18n/project_pt_BR.json +2 -0
  25. novelwriter/assets/i18n/project_ru_RU.json +2 -0
  26. novelwriter/assets/i18n/project_zh_CN.json +2 -0
  27. novelwriter/assets/icons/remix_filled.icons +1 -0
  28. novelwriter/assets/icons/remix_outline.icons +1 -0
  29. novelwriter/assets/images/splash.png +0 -0
  30. novelwriter/assets/manual.pdf +0 -0
  31. novelwriter/assets/manual_fr.pdf +0 -0
  32. novelwriter/assets/sample.zip +0 -0
  33. novelwriter/assets/syntax/snazzy.conf +3 -3
  34. novelwriter/assets/text/credits_en.htm +6 -0
  35. novelwriter/assets/themes/snazzy.conf +48 -0
  36. novelwriter/common.py +10 -1
  37. novelwriter/config.py +96 -25
  38. novelwriter/constants.py +21 -0
  39. novelwriter/core/buildsettings.py +3 -1
  40. novelwriter/core/coretools.py +41 -34
  41. novelwriter/core/docbuild.py +1 -0
  42. novelwriter/core/index.py +35 -5
  43. novelwriter/core/indexdata.py +4 -1
  44. novelwriter/core/item.py +35 -24
  45. novelwriter/core/itemmodel.py +17 -13
  46. novelwriter/core/novelmodel.py +2 -0
  47. novelwriter/core/project.py +14 -14
  48. novelwriter/core/projectdata.py +42 -24
  49. novelwriter/core/projectxml.py +17 -7
  50. novelwriter/core/sessions.py +29 -13
  51. novelwriter/core/tree.py +9 -5
  52. novelwriter/dialogs/docmerge.py +2 -1
  53. novelwriter/dialogs/docsplit.py +6 -3
  54. novelwriter/dialogs/editlabel.py +11 -8
  55. novelwriter/dialogs/preferences.py +37 -26
  56. novelwriter/dialogs/projectsettings.py +3 -0
  57. novelwriter/extensions/configlayout.py +6 -2
  58. novelwriter/extensions/switch.py +16 -15
  59. novelwriter/extensions/switchbox.py +1 -0
  60. novelwriter/formats/tokenizer.py +2 -1
  61. novelwriter/gui/doceditor.py +106 -47
  62. novelwriter/gui/noveltree.py +11 -5
  63. novelwriter/gui/outline.py +9 -1
  64. novelwriter/gui/projtree.py +1 -0
  65. novelwriter/gui/search.py +1 -0
  66. novelwriter/gui/statusbar.py +14 -6
  67. novelwriter/gui/theme.py +25 -10
  68. novelwriter/guimain.py +29 -9
  69. novelwriter/splash.py +74 -0
  70. novelwriter/tools/lipsum.py +2 -1
  71. novelwriter/tools/manuscript.py +1 -1
  72. novelwriter/tools/manussettings.py +52 -20
  73. novelwriter/tools/noveldetails.py +9 -8
  74. novelwriter/tools/welcome.py +1 -0
  75. novelwriter/tools/writingstats.py +68 -45
  76. {novelwriter-2.7b1.dist-info → novelwriter-2.7.1.dist-info}/METADATA +2 -2
  77. {novelwriter-2.7b1.dist-info → novelwriter-2.7.1.dist-info}/RECORD +81 -78
  78. {novelwriter-2.7b1.dist-info → novelwriter-2.7.1.dist-info}/WHEEL +1 -1
  79. {novelwriter-2.7b1.dist-info → novelwriter-2.7.1.dist-info}/entry_points.txt +0 -0
  80. {novelwriter-2.7b1.dist-info → novelwriter-2.7.1.dist-info}/licenses/LICENSE.md +0 -0
  81. {novelwriter-2.7b1.dist-info → novelwriter-2.7.1.dist-info}/top_level.txt +0 -0
novelwriter/__init__.py CHANGED
@@ -35,6 +35,7 @@ from PyQt6.QtWidgets import QApplication, QErrorMessage
35
35
  from novelwriter.config import Config
36
36
  from novelwriter.error import exceptionHandler
37
37
  from novelwriter.shared import SharedData
38
+ from novelwriter.splash import NSplashScreen
38
39
 
39
40
  if TYPE_CHECKING:
40
41
  from novelwriter.guimain import GuiMain
@@ -48,9 +49,9 @@ __license__ = "GPLv3"
48
49
  __author__ = "Veronica Berglyd Olsen"
49
50
  __maintainer__ = "Veronica Berglyd Olsen"
50
51
  __email__ = "code@vkbo.net"
51
- __version__ = "2.7b1"
52
- __hexversion__ = "0x020700b1"
53
- __date__ = "2025-04-20"
52
+ __version__ = "2.7.1"
53
+ __hexversion__ = "0x020701f0"
54
+ __date__ = "2025-06-09"
54
55
  __status__ = "Stable"
55
56
  __domain__ = "novelwriter.io"
56
57
 
@@ -267,13 +268,25 @@ def main(sysArgs: list | None = None) -> GuiMain | None:
267
268
  # Connect the exception handler before making the main GUI
268
269
  sys.excepthook = exceptionHandler
269
270
 
271
+ splash = NSplashScreen()
272
+ splash.show()
273
+
274
+ splash.showStatus("")
275
+ splash.showStatus("Starting novelWriter ...")
276
+
270
277
  # Run Config steps that require the QApplication
271
- CONFIG.loadConfig()
278
+ CONFIG.loadConfig(splash)
272
279
  CONFIG.initLocalisation(app)
273
280
  SHARED.initTheme(GuiTheme())
274
281
 
275
282
  # Launch main GUI
276
283
  nwGUI = GuiMain()
284
+ nwGUI.showNormal()
285
+ splash.finish(nwGUI)
286
+
287
+ CONFIG.finishStartup()
288
+ del splash
289
+
277
290
  nwGUI.postLaunchTasks(cmdOpen)
278
291
 
279
292
  sys.exit(app.exec())
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -3,6 +3,8 @@
3
3
  "Short Description": "Krátký popis",
4
4
  "Footnotes": "Poznámky pod čarou",
5
5
  "Comment": "Komentář",
6
+ "Story Structure": "Struktura příběhu",
7
+ "Note": "Poznámka",
6
8
  "Notes": "Poznámka",
7
9
  "Tag": "Štítek",
8
10
  "Point of View": "Úhel pohledu",
@@ -1,8 +1,10 @@
1
1
  {
2
- "Synopsis": "Synopsis",
2
+ "Synopsis": "Zusammenfassung",
3
3
  "Short Description": "Kurzbeschreibung",
4
4
  "Footnotes": "Fußnoten",
5
5
  "Comment": "Kommentar",
6
+ "Story Structure": "Struktur",
7
+ "Note": "Notiz",
6
8
  "Notes": "Notizen",
7
9
  "Tag": "Schlagwort",
8
10
  "Point of View": "Perspektive",
@@ -4,6 +4,7 @@
4
4
  "Footnotes": "Footnotes",
5
5
  "Comment": "Comment",
6
6
  "Story Structure": "Story Structure",
7
+ "Note": "Note",
7
8
  "Notes": "Notes",
8
9
  "Tag": "Tag",
9
10
  "Point of View": "Point of View",
@@ -3,6 +3,8 @@
3
3
  "Short Description": "Short Description",
4
4
  "Footnotes": "Footnotes",
5
5
  "Comment": "Comment",
6
+ "Story Structure": "Story Structure",
7
+ "Note": "Note",
6
8
  "Notes": "Notes",
7
9
  "Tag": "Tag",
8
10
  "Point of View": "Point of View",
@@ -3,6 +3,8 @@
3
3
  "Short Description": "Breve descrizione",
4
4
  "Footnotes": "Note a piè pagina",
5
5
  "Comment": "Commento",
6
+ "Story Structure": "Struttura della storia",
7
+ "Note": "Nota",
6
8
  "Notes": "Note",
7
9
  "Tag": "Etichetta",
8
10
  "Point of View": "Punto di vista",
@@ -3,6 +3,8 @@
3
3
  "Short Description": "短い説明",
4
4
  "Footnotes": "脚注",
5
5
  "Comment": "コメント ",
6
+ "Story Structure": "ストーリー構造",
7
+ "Note": "ノート",
6
8
  "Notes": "ノート",
7
9
  "Tag": "タグ",
8
10
  "Point of View": "視点",
@@ -3,6 +3,8 @@
3
3
  "Short Description": "Kort beskrivelse",
4
4
  "Footnotes": "Fotnoter",
5
5
  "Comment": "Kommentar",
6
+ "Story Structure": "Tekst-element",
7
+ "Note": "Notat",
6
8
  "Notes": "Notat",
7
9
  "Tag": "Knagg",
8
10
  "Point of View": "Perspektiv",
@@ -1,6 +1,10 @@
1
1
  {
2
2
  "Synopsis": "Samandrag",
3
+ "Short Description": "Kort beskriving",
4
+ "Footnotes": "Fotnotar",
3
5
  "Comment": "Kommentar",
6
+ "Story Structure": "Tekst-element",
7
+ "Note": "Notat",
4
8
  "Notes": "Notat",
5
9
  "Tag": "Knagg",
6
10
  "Point of View": "Perspektiv",
@@ -12,6 +16,7 @@
12
16
  "Objects": "Objekt",
13
17
  "Entities": "Eining",
14
18
  "Custom": "Anna",
19
+ "New Page": "Ny side",
15
20
  "0": "null",
16
21
  "1": "ein",
17
22
  "2": "to",
@@ -3,6 +3,8 @@
3
3
  "Short Description": "Krótki opis",
4
4
  "Footnotes": "Przypisy",
5
5
  "Comment": "Komentarz",
6
+ "Story Structure": "Struktura opowieści",
7
+ "Note": "Notatka",
6
8
  "Notes": "Notatki",
7
9
  "Tag": "Znacznik",
8
10
  "Point of View": "Punkt widzenia",
@@ -3,6 +3,8 @@
3
3
  "Short Description": "Descrição breve",
4
4
  "Footnotes": "Notas de rodapé",
5
5
  "Comment": "Comentário",
6
+ "Story Structure": "Estrutura narrativa",
7
+ "Note": "Nota",
6
8
  "Notes": "Notas",
7
9
  "Tag": "Etiqueta",
8
10
  "Point of View": "Ponto de vista",
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "Synopsis": "Сводка",
3
3
  "Short Description": "Краткое описание",
4
+ "Footnotes": "Примечания",
4
5
  "Comment": "Комментарий",
5
6
  "Notes": "Заметки",
6
7
  "Tag": "Тэг",
@@ -13,6 +14,7 @@
13
14
  "Objects": "Объекты",
14
15
  "Entities": "Сущности",
15
16
  "Custom": "Другое",
17
+ "New Page": "Новая страница",
16
18
  "0": "Ноль",
17
19
  "1": "Один",
18
20
  "2": "Два",
@@ -3,6 +3,8 @@
3
3
  "Short Description": "简短描述",
4
4
  "Footnotes": "注释",
5
5
  "Comment": "注释",
6
+ "Story Structure": "故事结构",
7
+ "Note": "笔记",
6
8
  "Notes": "笔记",
7
9
  "Tag": "标签",
8
10
  "Point of View": "视角",
@@ -94,6 +94,7 @@ icon:noncheckable = <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 2
94
94
  icon:open = <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#000000" height="128" width="128"><path d="M16 2L21 7V21.0082C21 21.556 20.5551 22 20.0066 22H3.9934C3.44476 22 3 21.5447 3 21.0082V2.9918C3 2.44405 3.44495 2 3.9934 2H16ZM13 12H16L12 8L8 12H11V16H13V12Z" /></svg>
95
95
  icon:panel = <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#000000" height="128" width="128"><path d="M22 16V20C22 20.5523 21.5523 21 21 21H3C2.44772 21 2 20.5523 2 20V16H22ZM21 3C21.5523 3 22 3.44772 22 4V14H2V4C2 3.44772 2.44772 3 3 3H21Z" /></svg>
96
96
  icon:pin = <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#000000" height="128" width="128"><path d="M22.3126 10.1753L20.8984 11.5895L20.1913 10.8824L15.9486 15.125L15.2415 18.6606L13.8273 20.0748L9.58466 15.8321L4.63492 20.7819L3.2207 19.3677L8.17045 14.4179L3.92781 10.1753L5.34202 8.76107L8.87756 8.05396L13.1202 3.81132L12.4131 3.10422L13.8273 1.69L22.3126 10.1753Z" /></svg>
97
+ icon:project_copy = <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#000000" height="128" width="128"><path d="M6.9998 6V3C6.9998 2.44772 7.44752 2 7.9998 2H19.9998C20.5521 2 20.9998 2.44772 20.9998 3V17C20.9998 17.5523 20.5521 18 19.9998 18H16.9998V20.9991C16.9998 21.5519 16.5499 22 15.993 22H4.00666C3.45059 22 3 21.5554 3 20.9991L3.0026 7.00087C3.0027 6.44811 3.45264 6 4.00942 6H6.9998ZM8.9998 6H16.9998V16H18.9998V4H8.9998V6ZM6.9998 11V13H12.9998V11H6.9998ZM6.9998 15V17H12.9998V15H6.9998Z" /></svg>
97
98
  icon:quote = <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#000000" height="128" width="128"><path d="M19.4167 6.67891C20.4469 7.77257 21.0001 9 21.0001 10.9897C21.0001 14.4891 18.5436 17.6263 14.9695 19.1768L14.0768 17.7992C17.4121 15.9946 18.0639 13.6539 18.3245 12.178C17.7875 12.4557 17.0845 12.5533 16.3954 12.4895C14.591 12.3222 13.1689 10.8409 13.1689 9C13.1689 7.067 14.7359 5.5 16.6689 5.5C17.742 5.5 18.7681 5.99045 19.4167 6.67891ZM9.41669 6.67891C10.4469 7.77257 11.0001 9 11.0001 10.9897C11.0001 14.4891 8.54359 17.6263 4.96951 19.1768L4.07682 17.7992C7.41206 15.9946 8.06392 13.6539 8.32447 12.178C7.78747 12.4557 7.08452 12.5533 6.39539 12.4895C4.59102 12.3222 3.16895 10.8409 3.16895 9C3.16895 7.067 4.73595 5.5 6.66895 5.5C7.742 5.5 8.76814 5.99045 9.41669 6.67891Z" /></svg>
98
99
  icon:refresh = <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#000000" height="128" width="128"><path d="M5.46257 4.43262C7.21556 2.91688 9.5007 2 12 2C17.5228 2 22 6.47715 22 12C22 14.1361 21.3302 16.1158 20.1892 17.7406L17 12H20C20 7.58172 16.4183 4 12 4C9.84982 4 7.89777 4.84827 6.46023 6.22842L5.46257 4.43262ZM18.5374 19.5674C16.7844 21.0831 14.4993 22 12 22C6.47715 22 2 17.5228 2 12C2 9.86386 2.66979 7.88416 3.8108 6.25944L7 12H4C4 16.4183 7.58172 20 12 20C14.1502 20 16.1022 19.1517 17.5398 17.7716L18.5374 19.5674Z" /></svg>
99
100
  icon:remove = <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#000000" height="128" width="128"><path d="M19 11H5V13H19V11Z" /></svg>
@@ -94,6 +94,7 @@ icon:noncheckable = <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 2
94
94
  icon:open = <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#000000" height="128" width="128"><path d="M15 4H5V20H19V8H15V4ZM3 2.9918C3 2.44405 3.44749 2 3.9985 2H16L20.9997 7L21 20.9925C21 21.5489 20.5551 22 20.0066 22H3.9934C3.44476 22 3 21.5447 3 21.0082V2.9918ZM13 12V16H11V12H8L12 8L16 12H13Z" /></svg>
95
95
  icon:panel = <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#000000" height="128" width="128"><path d="M21 3C21.5523 3 22 3.44772 22 4V20C22 20.5523 21.5523 21 21 21H3C2.44772 21 2 20.5523 2 20V4C2 3.44772 2.44772 3 3 3H21ZM4 16V19H20V16H4ZM4 14H20V5H4V14Z" /></svg>
96
96
  icon:pin = <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#000000" height="128" width="128"><path d="M13.8273 1.69L22.3126 10.1753L20.8984 11.5895L20.1913 10.8824L15.9486 15.125L15.2415 18.6606L13.8273 20.0748L9.58466 15.8321L4.63492 20.7819L3.2207 19.3677L8.17045 14.4179L3.92781 10.1753L5.34202 8.76107L8.87756 8.05396L13.1202 3.81132L12.4131 3.10422L13.8273 1.69ZM14.5344 5.22554L9.86358 9.89637L7.0417 10.4607L13.5418 16.9609L14.1062 14.139L18.7771 9.46818L14.5344 5.22554Z" /></svg>
97
+ icon:project_copy = <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#000000" height="128" width="128"><path d="M6.9998 6V3C6.9998 2.44772 7.44752 2 7.9998 2H19.9998C20.5521 2 20.9998 2.44772 20.9998 3V17C20.9998 17.5523 20.5521 18 19.9998 18H16.9998V20.9991C16.9998 21.5519 16.5499 22 15.993 22H4.00666C3.45059 22 3 21.5554 3 20.9991L3.0026 7.00087C3.0027 6.44811 3.45264 6 4.00942 6H6.9998ZM5.00242 8L5.00019 20H14.9998V8H5.00242ZM8.9998 6H16.9998V16H18.9998V4H8.9998V6ZM7 11H13V13H7V11ZM7 15H13V17H7V15Z" /></svg>
97
98
  icon:quote = <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#000000" height="128" width="128"><path d="M19.4167 6.67891C20.4469 7.77257 21.0001 9 21.0001 10.9897C21.0001 14.4891 18.5436 17.6263 14.9695 19.1768L14.0768 17.7992C17.4121 15.9946 18.0639 13.6539 18.3245 12.178C17.7875 12.4557 17.0845 12.5533 16.3954 12.4895C14.591 12.3222 13.1689 10.8409 13.1689 9C13.1689 7.067 14.7359 5.5 16.6689 5.5C17.742 5.5 18.7681 5.99045 19.4167 6.67891ZM9.41669 6.67891C10.4469 7.77257 11.0001 9 11.0001 10.9897C11.0001 14.4891 8.54359 17.6263 4.96951 19.1768L4.07682 17.7992C7.41206 15.9946 8.06392 13.6539 8.32447 12.178C7.78747 12.4557 7.08452 12.5533 6.39539 12.4895C4.59102 12.3222 3.16895 10.8409 3.16895 9C3.16895 7.067 4.73595 5.5 6.66895 5.5C7.742 5.5 8.76814 5.99045 9.41669 6.67891Z" /></svg>
98
99
  icon:refresh = <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#000000" height="128" width="128"><path d="M5.46257 4.43262C7.21556 2.91688 9.5007 2 12 2C17.5228 2 22 6.47715 22 12C22 14.1361 21.3302 16.1158 20.1892 17.7406L17 12H20C20 7.58172 16.4183 4 12 4C9.84982 4 7.89777 4.84827 6.46023 6.22842L5.46257 4.43262ZM18.5374 19.5674C16.7844 21.0831 14.4993 22 12 22C6.47715 22 2 17.5228 2 12C2 9.86386 2.66979 7.88416 3.8108 6.25944L7 12H4C4 16.4183 7.58172 20 12 20C14.1502 20 16.1022 19.1517 17.5398 17.7716L18.5374 19.5674Z" /></svg>
99
100
  icon:remove = <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#000000" height="128" width="128"><path d="M5 11V13H19V11H5Z" /></svg>
Binary file
Binary file
Binary file
Binary file
@@ -25,15 +25,15 @@ text = 86, 88, 105
25
25
  link = 9, 161, 237
26
26
  headertext = 45, 174, 88
27
27
  headertag = 45, 174, 88, 160
28
- emphasis = 19, 187, 183
29
- dialog = 207, 156, 0
28
+ emphasis = 247, 103, 187
29
+ dialog = 9, 161, 237
30
30
  altdialog = 207, 156, 0
31
31
  note = 120, 187, 185
32
32
  hidden = 145, 148, 162
33
33
  shortcode = 247, 103, 187
34
34
  keyword = 9, 161, 237
35
35
  tag = 45, 174, 88
36
- value = 9, 161, 237
36
+ value = 207, 156, 0
37
37
  optional = 207, 156, 0
38
38
  spellcheckline = 255, 92, 87
39
39
  errorline = 45, 174, 88
@@ -47,6 +47,12 @@ translators for the languages currently available:</p>
47
47
  <li><b>Portuguese:</b> Oli Maia (olimaia)</li>
48
48
  </ul>
49
49
 
50
+ <p>Translations of the documentation:</p>
51
+
52
+ <ul>
53
+ <li><b>French:</b> Jean-Michel Heras (Karduin)</li>
54
+ </ul>
55
+
50
56
  <p>Translations are managed on <a href="https://crowdin.com/project/novelwriter">Crowdin</a>, and
51
57
  more contributions are listed on the project's Members page.</p>
52
58
 
@@ -0,0 +1,48 @@
1
+ [Main]
2
+ name = Snazzy Light
3
+ author = Veronica Berglyd Olsen (adaptation)
4
+ credit = Florian Reuschel (color theme)
5
+ url = https://github.com/loilo/vscode-snazzy-light
6
+ license = MIT License
7
+ licenseurl = https://github.com/loilo/vscode-snazzy-light/blob/master/LICENSE
8
+
9
+ [Icons]
10
+ default = 86, 88, 105
11
+ faded = 84, 85, 84
12
+ red = 255, 92, 87
13
+ orange = 245, 185, 0
14
+ yellow = 207, 156, 0
15
+ green = 45, 174, 88
16
+ aqua = 19, 187, 183
17
+ blue = 9, 161, 237
18
+ purple = 247, 103, 187
19
+
20
+ [Project]
21
+ root = 9, 161, 237
22
+ folder = 207, 156, 0
23
+ file = 84, 85, 84
24
+ title = 45, 174, 88
25
+ chapter = 255, 92, 87
26
+ scene = 9, 161, 237
27
+ note = 207, 156, 0
28
+
29
+ [Palette]
30
+ window = 243, 244, 245
31
+ windowtext = 86, 88, 105
32
+ base = 250, 251, 252
33
+ alternatebase = 234, 234, 235
34
+ text = 86, 88, 105
35
+ tooltipbase = 245, 233, 194
36
+ tooltiptext = 86, 88, 105
37
+ button = 250, 251, 252
38
+ buttontext = 86, 88, 105
39
+ brighttext = 255, 255, 255
40
+ highlight = 9, 161, 237
41
+ highlightedtext = 255, 255, 255
42
+ link = 9, 161, 237
43
+ linkvisited = 9, 161, 237
44
+
45
+ [GUI]
46
+ helptext = 9, 161, 237
47
+ fadedtext = 84, 85, 84
48
+ errortext = 255, 92, 87
novelwriter/common.py CHANGED
@@ -40,7 +40,7 @@ from PyQt6.QtCore import QCoreApplication, QMimeData, QUrl
40
40
  from PyQt6.QtGui import QAction, QDesktopServices, QFont, QFontDatabase, QFontInfo
41
41
  from PyQt6.QtWidgets import QMenu, QMenuBar, QWidget
42
42
 
43
- from novelwriter.constants import nwConst, nwLabels, nwUnicode, trConst
43
+ from novelwriter.constants import nwConst, nwLabels, nwQuotes, nwUnicode, trConst
44
44
  from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType
45
45
  from novelwriter.error import logException
46
46
 
@@ -298,6 +298,15 @@ def uniqueCompact(text: str) -> str:
298
298
  return "".join(sorted(set(compact(text))))
299
299
 
300
300
 
301
+ def processDialogSymbols(symbols: str) -> str:
302
+ """Process dialogue line symbols."""
303
+ result = ""
304
+ for c in uniqueCompact(symbols):
305
+ if c in nwQuotes.ALLOWED:
306
+ result += c
307
+ return result
308
+
309
+
301
310
  def elide(text: str, length: int) -> str:
302
311
  """Elide a piece of text to a maximum length."""
303
312
  if len(text) > (cut := max(4, length)):
novelwriter/config.py CHANGED
@@ -34,23 +34,24 @@ from time import time
34
34
  from typing import TYPE_CHECKING, Final
35
35
 
36
36
  from PyQt6.QtCore import (
37
- PYQT_VERSION, PYQT_VERSION_STR, QT_VERSION, QT_VERSION_STR, QLibraryInfo,
38
- QLocale, QStandardPaths, QSysInfo, QTranslator
37
+ PYQT_VERSION, PYQT_VERSION_STR, QT_VERSION, QT_VERSION_STR, QDate,
38
+ QDateTime, QLibraryInfo, QLocale, QStandardPaths, QSysInfo, QTranslator
39
39
  )
40
- from PyQt6.QtGui import QFont, QFontDatabase
40
+ from PyQt6.QtGui import QFont, QFontDatabase, QFontMetrics
41
41
  from PyQt6.QtWidgets import QApplication
42
42
 
43
43
  from novelwriter.common import (
44
44
  NWConfigParser, checkInt, checkPath, describeFont, fontMatcher,
45
- formatTimeStamp
45
+ formatTimeStamp, processDialogSymbols, simplified
46
46
  )
47
- from novelwriter.constants import nwFiles, nwUnicode
47
+ from novelwriter.constants import nwFiles, nwHtmlUnicode, nwQuotes, nwUnicode
48
48
  from novelwriter.error import formatException, logException
49
49
 
50
50
  if TYPE_CHECKING:
51
51
  from datetime import datetime
52
52
 
53
53
  from novelwriter.core.projectdata import NWProjectData
54
+ from novelwriter.splash import NSplashScreen
54
55
 
55
56
  logger = logging.getLogger(__name__)
56
57
 
@@ -64,16 +65,16 @@ class Config:
64
65
 
65
66
  __slots__ = (
66
67
  "_appPath", "_appRoot", "_backPath", "_backupPath", "_confPath", "_dLocale", "_dShortDate",
67
- "_dShortDateTime", "_dataPath", "_errData", "_hasError", "_homePath", "_manuals",
68
- "_nwLangPath", "_qLocale", "_qtLangPath", "_qtTrans", "_recentPaths", "_recentProjects",
69
- "allowOpenDial", "altDialogClose", "altDialogOpen", "appHandle", "appName",
70
- "askBeforeBackup", "askBeforeExit", "autoSaveDoc", "autoSaveProj", "autoScroll",
71
- "autoScrollPos", "autoSelect", "backupOnClose", "cursorWidth", "dialogLine", "dialogStyle",
72
- "doJustify", "doReplace", "doReplaceDQuote", "doReplaceDash", "doReplaceDots",
73
- "doReplaceSQuote", "emphLabels", "fmtApostrophe", "fmtDQuoteClose", "fmtDQuoteOpen",
74
- "fmtPadAfter", "fmtPadBefore", "fmtPadThin", "fmtSQuoteClose", "fmtSQuoteOpen",
75
- "focusWidth", "guiFont", "guiLocale", "guiSyntax", "guiTheme", "hasEnchant",
76
- "hideFocusFooter", "hideHScroll", "hideVScroll", "highlightEmph", "hostName",
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",
77
78
  "iconColDocs", "iconColTree", "iconTheme", "incNotesWCount", "isDebug", "kernelVer",
78
79
  "lastNotes", "mainPanePos", "mainWinSize", "memInfo", "narratorBreak", "narratorDialog",
79
80
  "nativeFont", "osDarwin", "osLinux", "osType", "osUnknown", "osWindows", "outlinePanePos",
@@ -94,6 +95,8 @@ class Config:
94
95
  # Initialisation
95
96
  # ==============
96
97
 
98
+ self._splash = None
99
+
97
100
  # Set Application Variables
98
101
  self.appName = "novelWriter"
99
102
  self.appHandle = "novelwriter"
@@ -146,6 +149,7 @@ class Config:
146
149
 
147
150
  self._recentProjects = RecentProjects(self)
148
151
  self._recentPaths = RecentPaths(self)
152
+ self._lastAuthor = ""
149
153
 
150
154
  # General GUI Settings
151
155
  self.guiLocale = self._qLocale.name()
@@ -318,6 +322,11 @@ class Config:
318
322
  def recentProjects(self) -> RecentProjects:
319
323
  return self._recentProjects
320
324
 
325
+ @property
326
+ def lastAuthor(self) -> str:
327
+ """Return the last author name used."""
328
+ return simplified(self._lastAuthor)
329
+
321
330
  ##
322
331
  # Getters
323
332
  ##
@@ -333,6 +342,11 @@ class Config:
333
342
  # Setters
334
343
  ##
335
344
 
345
+ def setLastAuthor(self, value: str) -> None:
346
+ """Set tle last used author name."""
347
+ self._lastAuthor = simplified(value)
348
+ return
349
+
336
350
  def setMainWinSize(self, width: int, height: int) -> None:
337
351
  """Set the size of the main window, but only if the change is
338
352
  larger than 5 pixels. The OS window manager will sometimes
@@ -464,11 +478,16 @@ class Config:
464
478
 
465
479
  def localDate(self, value: datetime) -> str:
466
480
  """Return a localised date format."""
467
- 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)
468
483
 
469
484
  def localDateTime(self, value: datetime) -> str:
470
485
  """Return a localised datetime format."""
471
- 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
+ )
472
491
 
473
492
  def listLanguages(self, lngSet: int) -> list[tuple[str, str]]:
474
493
  """List localisation files in the i18n folder. The default GUI
@@ -497,6 +516,12 @@ class Config:
497
516
 
498
517
  return sorted(langList.items(), key=lambda x: x[0])
499
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
+
500
525
  ##
501
526
  # Config Actions
502
527
  ##
@@ -543,6 +568,8 @@ class Config:
543
568
 
544
569
  def initLocalisation(self, nwApp: QApplication) -> None:
545
570
  """Initialise the localisation of the GUI."""
571
+ self.splashMessage("Loading localisation ...")
572
+
546
573
  self._qLocale = QLocale(self.guiLocale)
547
574
  QLocale.setDefault(self._qLocale)
548
575
  self._qtTrans = {}
@@ -568,8 +595,11 @@ class Config:
568
595
 
569
596
  return
570
597
 
571
- def loadConfig(self) -> bool:
598
+ def loadConfig(self, splash: NSplashScreen | None = None) -> bool:
572
599
  """Load preferences from file and replace default settings."""
600
+ self._splash = splash
601
+ self.splashMessage("Loading user configuration ...")
602
+
573
603
  logger.debug("Loading config file")
574
604
 
575
605
  conf = NWConfigParser()
@@ -626,6 +656,7 @@ class Config:
626
656
  self.backupOnClose = conf.rdBool(sec, "backuponclose", self.backupOnClose)
627
657
  self.askBeforeBackup = conf.rdBool(sec, "askbeforebackup", self.askBeforeBackup)
628
658
  self.askBeforeExit = conf.rdBool(sec, "askbeforeexit", self.askBeforeExit)
659
+ self._lastAuthor = conf.rdStr(sec, "lastauthor", self._lastAuthor)
629
660
 
630
661
  # Editor
631
662
  sec = "Editor"
@@ -661,9 +692,9 @@ class Config:
661
692
  self.showFullPath = conf.rdBool(sec, "showfullpath", self.showFullPath)
662
693
  self.dialogStyle = conf.rdInt(sec, "dialogstyle", self.dialogStyle)
663
694
  self.allowOpenDial = conf.rdBool(sec, "allowopendial", self.allowOpenDial)
664
- self.dialogLine = conf.rdStr(sec, "dialogline", self.dialogLine)
665
- self.narratorBreak = conf.rdStr(sec, "narratorbreak", self.narratorBreak)
666
- 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)
667
698
  self.altDialogOpen = conf.rdStr(sec, "altdialogopen", self.altDialogOpen)
668
699
  self.altDialogClose = conf.rdStr(sec, "altdialogclose", self.altDialogClose)
669
700
  self.highlightEmph = conf.rdBool(sec, "highlightemph", self.highlightEmph)
@@ -690,6 +721,9 @@ class Config:
690
721
  # Check Values
691
722
  # ============
692
723
 
724
+ self._prepareFont(self.guiFont, "GUI")
725
+ self._prepareFont(self.textFont, "document")
726
+
693
727
  # If we're using straight quotes, disable auto-replace
694
728
  if self.fmtSQuoteOpen == self.fmtSQuoteClose == "'" and self.doReplaceSQuote:
695
729
  logger.info("Using straight single quotes, so disabling auto-replace")
@@ -699,6 +733,10 @@ class Config:
699
733
  logger.info("Using straight double quotes, so disabling auto-replace")
700
734
  self.doReplaceDQuote = False
701
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
+
702
740
  return True
703
741
 
704
742
  def saveConfig(self) -> bool:
@@ -743,6 +781,7 @@ class Config:
743
781
  "backuponclose": str(self.backupOnClose),
744
782
  "askbeforebackup": str(self.askBeforeBackup),
745
783
  "askbeforeexit": str(self.askBeforeExit),
784
+ "lastauthor": str(self._lastAuthor),
746
785
  }
747
786
 
748
787
  conf["Editor"] = {
@@ -820,6 +859,11 @@ class Config:
820
859
 
821
860
  return True
822
861
 
862
+ def finishStartup(self) -> None:
863
+ """Call after startup is complete."""
864
+ self._splash = None
865
+ return
866
+
823
867
  ##
824
868
  # Internal Functions
825
869
  ##
@@ -842,6 +886,17 @@ class Config:
842
886
  logger.debug("Checking package 'pyenchant': OK")
843
887
  return
844
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
+
845
900
 
846
901
  class RecentProjects:
847
902
 
@@ -864,9 +919,10 @@ class RecentProjects:
864
919
  puuid = str(entry.get("uuid", ""))
865
920
  title = str(entry.get("title", ""))
866
921
  words = checkInt(entry.get("words", 0), 0)
922
+ chars = checkInt(entry.get("chars", 0), 0)
867
923
  saved = checkInt(entry.get("time", 0), 0)
868
924
  if path and title:
869
- self._setEntry(puuid, path, title, words, saved)
925
+ self._setEntry(puuid, path, title, words, chars, saved)
870
926
  except Exception:
871
927
  logger.error("Could not load recent project cache")
872
928
  logException()
@@ -899,7 +955,14 @@ class RecentProjects:
899
955
  try:
900
956
  if (remove := self._map.get(data.uuid)) and (remove != str(path)):
901
957
  self.remove(remove)
902
- 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
+ )
903
966
  self.saveCache()
904
967
  except Exception:
905
968
  pass
@@ -912,9 +975,17 @@ class RecentProjects:
912
975
  self.saveCache()
913
976
  return
914
977
 
915
- 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:
916
981
  """Set an entry in the recent projects record."""
917
- 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
+ }
918
989
  if puuid:
919
990
  self._map[puuid] = path
920
991
  return