novelWriter 2.5.3__py3-none-any.whl → 2.6__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 (126) hide show
  1. {novelWriter-2.5.3.dist-info → novelWriter-2.6.dist-info}/METADATA +2 -2
  2. {novelWriter-2.5.3.dist-info → novelWriter-2.6.dist-info}/RECORD +123 -103
  3. {novelWriter-2.5.3.dist-info → novelWriter-2.6.dist-info}/WHEEL +1 -1
  4. novelwriter/__init__.py +50 -11
  5. novelwriter/assets/i18n/nw_de_DE.qm +0 -0
  6. novelwriter/assets/i18n/nw_en_US.qm +0 -0
  7. novelwriter/assets/i18n/nw_es_419.qm +0 -0
  8. novelwriter/assets/i18n/nw_fr_FR.qm +0 -0
  9. novelwriter/assets/i18n/nw_it_IT.qm +0 -0
  10. novelwriter/assets/i18n/nw_ja_JP.qm +0 -0
  11. novelwriter/assets/i18n/nw_nb_NO.qm +0 -0
  12. novelwriter/assets/i18n/nw_nl_NL.qm +0 -0
  13. novelwriter/assets/i18n/nw_pl_PL.qm +0 -0
  14. novelwriter/assets/i18n/nw_pt_BR.qm +0 -0
  15. novelwriter/assets/i18n/nw_ru_RU.qm +0 -0
  16. novelwriter/assets/i18n/nw_zh_CN.qm +0 -0
  17. novelwriter/assets/i18n/project_de_DE.json +2 -0
  18. novelwriter/assets/i18n/project_en_GB.json +1 -0
  19. novelwriter/assets/i18n/project_en_US.json +2 -0
  20. novelwriter/assets/i18n/project_it_IT.json +2 -0
  21. novelwriter/assets/i18n/project_ja_JP.json +2 -0
  22. novelwriter/assets/i18n/project_nb_NO.json +2 -0
  23. novelwriter/assets/i18n/project_nl_NL.json +2 -0
  24. novelwriter/assets/i18n/project_pl_PL.json +2 -0
  25. novelwriter/assets/i18n/project_pt_BR.json +2 -0
  26. novelwriter/assets/i18n/project_zh_CN.json +2 -0
  27. novelwriter/assets/icons/typicons_dark/icons.conf +8 -0
  28. novelwriter/assets/icons/typicons_dark/mixed_copy.svg +4 -0
  29. novelwriter/assets/icons/typicons_dark/mixed_margin-bottom.svg +6 -0
  30. novelwriter/assets/icons/typicons_dark/mixed_margin-left.svg +6 -0
  31. novelwriter/assets/icons/typicons_dark/mixed_margin-right.svg +6 -0
  32. novelwriter/assets/icons/typicons_dark/mixed_margin-top.svg +6 -0
  33. novelwriter/assets/icons/typicons_dark/mixed_size-height.svg +6 -0
  34. novelwriter/assets/icons/typicons_dark/mixed_size-width.svg +6 -0
  35. novelwriter/assets/icons/typicons_dark/nw_toolbar.svg +5 -0
  36. novelwriter/assets/icons/typicons_light/icons.conf +8 -0
  37. novelwriter/assets/icons/typicons_light/mixed_copy.svg +4 -0
  38. novelwriter/assets/icons/typicons_light/mixed_margin-bottom.svg +6 -0
  39. novelwriter/assets/icons/typicons_light/mixed_margin-left.svg +6 -0
  40. novelwriter/assets/icons/typicons_light/mixed_margin-right.svg +6 -0
  41. novelwriter/assets/icons/typicons_light/mixed_margin-top.svg +6 -0
  42. novelwriter/assets/icons/typicons_light/mixed_size-height.svg +6 -0
  43. novelwriter/assets/icons/typicons_light/mixed_size-width.svg +6 -0
  44. novelwriter/assets/icons/typicons_light/nw_toolbar.svg +5 -0
  45. novelwriter/assets/manual.pdf +0 -0
  46. novelwriter/assets/sample.zip +0 -0
  47. novelwriter/common.py +101 -3
  48. novelwriter/config.py +30 -17
  49. novelwriter/constants.py +189 -81
  50. novelwriter/core/buildsettings.py +74 -40
  51. novelwriter/core/coretools.py +146 -148
  52. novelwriter/core/docbuild.py +133 -171
  53. novelwriter/core/document.py +1 -1
  54. novelwriter/core/index.py +39 -38
  55. novelwriter/core/item.py +42 -9
  56. novelwriter/core/itemmodel.py +518 -0
  57. novelwriter/core/options.py +5 -2
  58. novelwriter/core/project.py +68 -90
  59. novelwriter/core/projectdata.py +8 -2
  60. novelwriter/core/projectxml.py +1 -1
  61. novelwriter/core/sessions.py +1 -1
  62. novelwriter/core/spellcheck.py +10 -15
  63. novelwriter/core/status.py +24 -8
  64. novelwriter/core/storage.py +1 -1
  65. novelwriter/core/tree.py +269 -288
  66. novelwriter/dialogs/about.py +1 -1
  67. novelwriter/dialogs/docmerge.py +8 -18
  68. novelwriter/dialogs/docsplit.py +1 -1
  69. novelwriter/dialogs/editlabel.py +1 -1
  70. novelwriter/dialogs/preferences.py +47 -34
  71. novelwriter/dialogs/projectsettings.py +149 -99
  72. novelwriter/dialogs/quotes.py +1 -1
  73. novelwriter/dialogs/wordlist.py +11 -10
  74. novelwriter/enum.py +37 -24
  75. novelwriter/error.py +2 -2
  76. novelwriter/extensions/configlayout.py +28 -13
  77. novelwriter/extensions/eventfilters.py +1 -1
  78. novelwriter/extensions/modified.py +30 -6
  79. novelwriter/extensions/novelselector.py +4 -3
  80. novelwriter/extensions/pagedsidebar.py +9 -9
  81. novelwriter/extensions/progressbars.py +4 -4
  82. novelwriter/extensions/statusled.py +3 -3
  83. novelwriter/extensions/switch.py +3 -3
  84. novelwriter/extensions/switchbox.py +1 -1
  85. novelwriter/extensions/versioninfo.py +1 -1
  86. novelwriter/formats/shared.py +156 -0
  87. novelwriter/formats/todocx.py +1191 -0
  88. novelwriter/formats/tohtml.py +454 -0
  89. novelwriter/{core → formats}/tokenizer.py +497 -495
  90. novelwriter/formats/tomarkdown.py +218 -0
  91. novelwriter/{core → formats}/toodt.py +312 -433
  92. novelwriter/formats/toqdoc.py +486 -0
  93. novelwriter/formats/toraw.py +91 -0
  94. novelwriter/gui/doceditor.py +347 -287
  95. novelwriter/gui/dochighlight.py +97 -85
  96. novelwriter/gui/docviewer.py +90 -33
  97. novelwriter/gui/docviewerpanel.py +18 -26
  98. novelwriter/gui/editordocument.py +18 -3
  99. novelwriter/gui/itemdetails.py +27 -29
  100. novelwriter/gui/mainmenu.py +130 -64
  101. novelwriter/gui/noveltree.py +46 -48
  102. novelwriter/gui/outline.py +202 -256
  103. novelwriter/gui/projtree.py +590 -1242
  104. novelwriter/gui/search.py +11 -19
  105. novelwriter/gui/sidebar.py +8 -7
  106. novelwriter/gui/statusbar.py +20 -3
  107. novelwriter/gui/theme.py +11 -6
  108. novelwriter/guimain.py +101 -201
  109. novelwriter/shared.py +67 -28
  110. novelwriter/text/counting.py +3 -1
  111. novelwriter/text/patterns.py +169 -61
  112. novelwriter/tools/dictionaries.py +3 -3
  113. novelwriter/tools/lipsum.py +1 -1
  114. novelwriter/tools/manusbuild.py +15 -13
  115. novelwriter/tools/manuscript.py +121 -79
  116. novelwriter/tools/manussettings.py +424 -291
  117. novelwriter/tools/noveldetails.py +1 -1
  118. novelwriter/tools/welcome.py +6 -6
  119. novelwriter/tools/writingstats.py +4 -4
  120. novelwriter/types.py +25 -9
  121. novelwriter/core/tohtml.py +0 -530
  122. novelwriter/core/tomarkdown.py +0 -252
  123. novelwriter/core/toqdoc.py +0 -419
  124. {novelWriter-2.5.3.dist-info → novelWriter-2.6.dist-info}/LICENSE.md +0 -0
  125. {novelWriter-2.5.3.dist-info → novelWriter-2.6.dist-info}/entry_points.txt +0 -0
  126. {novelWriter-2.5.3.dist-info → novelWriter-2.6.dist-info}/top_level.txt +0 -0
@@ -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": "二",
@@ -44,6 +44,7 @@ cls_template = mixed_document-new.svg
44
44
  cls_timeline = typ_calendar.svg
45
45
  cls_trash = typ_trash.svg
46
46
  cls_world = typ_location.svg
47
+ copy = mixed_copy.svg
47
48
  cross = typ_times.svg
48
49
  document = typ_document.svg
49
50
  down = typ_chevron-down.svg
@@ -63,6 +64,10 @@ font = nw_font.svg
63
64
  forward = typ_chevron-right.svg
64
65
  import = mixed_import.svg
65
66
  list = typ_th-list.svg
67
+ margin_bottom = mixed_margin-bottom.svg
68
+ margin_left = mixed_margin-left.svg
69
+ margin_right = mixed_margin-right.svg
70
+ margin_top = mixed_margin-top.svg
66
71
  maximise = typ_arrow-maximise.svg
67
72
  menu = typ_th-dot-menu.svg
68
73
  minimise = typ_arrow-minimise.svg
@@ -93,6 +98,8 @@ search_regex = nw_search-regex.svg
93
98
  search_replace = mixed_search-replace.svg
94
99
  search_word = nw_search-word.svg
95
100
  settings = typ_cog.svg
101
+ size_height = mixed_size-height.svg
102
+ size_width = mixed_size-width.svg
96
103
  star = typ_star.svg
97
104
  status_idle = typ_media-pause-grey.svg
98
105
  status_lang = typ_globe-grey.svg
@@ -101,6 +108,7 @@ status_stats = typ_chart-bar-grey.svg
101
108
  status_time = typ_stopwatch-grey.svg
102
109
  sticky-off = typ_pin-outline.svg
103
110
  sticky-on = typ_pin.svg
111
+ toolbar = nw_toolbar.svg
104
112
  unchecked = mixed_input-unchecked.svg
105
113
  unfold-hide = typ_unfold-hidden.svg
106
114
  unfold-show = typ_unfold-visible.svg
@@ -0,0 +1,4 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg width="24" height="24" version="1.2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
3
+ <path d="m7.8824 2c-1.2988 0-2.3529 1.0541-2.3529 2.3529v2.3529h-1.1765c-1.2988 0-2.3529 1.0541-2.3529 2.3529v10.588c0 1.2988 1.0541 2.3529 2.3529 2.3529h10.588c1.2988 0 2.3529-1.0541 2.3529-2.3529v-1.1765h2.3529c1.2988 0 2.3529-1.0541 2.3529-2.3529v-11.765c0-1.2988-1.0541-2.3529-2.3529-2.3529zm0 2.3529h11.765v11.765h-2.3529v-7.0588c0-1.2988-1.0541-2.3529-2.3529-2.3529h-7.0588zm-3.5294 4.7059h10.588c0.32353 0 0-0.32353 0 0v10.588h-10.588v-9.8943z" fill="#69c" stroke-width="1.1765"/>
4
+ </svg>
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg width="24" height="24" version="1.2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
3
+ <rect x="2" y="2" width="20" height="20" rx="1" ry="1" fill="#e6e6e6" fill-opacity=".14902" stroke-linecap="round" stroke-width="2"/>
4
+ <rect x="2" y="18" width="20" height="4" rx="1" ry="1" fill="#848484" stroke-linecap="round" stroke-width="2"/>
5
+ <path d="m18.414 8.5959c0.78104 0.79361 0.78104 2.0837 0 2.8773l-6.4141 6.5268-6.4141-6.5268c-0.391-0.3968-0.58601-0.91769-0.58601-1.4386 0-0.52092 0.195-1.0418 0.58601-1.4386 0.78104-0.79464 2.047-0.79464 2.8281 0l3.5861 3.6475 3.5861-3.6475c0.78104-0.79464 2.047-0.79464 2.8281 0z" fill="#69c" stroke-width="1.0087"/>
6
+ </svg>
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg width="24" height="24" version="1.2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
3
+ <rect x="2" y="2" width="20" height="20" rx="1" ry="1" fill="#e6e6e6" fill-opacity=".14902" stroke-linecap="round" stroke-width="2"/>
4
+ <rect x="2" y="2" width="4" height="20" rx="1" ry="1" fill="#848484" stroke-linecap="round" stroke-width="2"/>
5
+ <path d="m15.404 18.5c-0.79361 0.78104-2.0837 0.78104-2.8773 0l-6.5268-6.4141 6.5268-6.4141c0.3968-0.391 0.91769-0.58601 1.4386-0.58601 0.52092 0 1.0418 0.195 1.4386 0.58601 0.79464 0.78104 0.79464 2.047 0 2.8281l-3.6475 3.5861 3.6475 3.5861c0.79464 0.78104 0.79464 2.047 0 2.8281z" fill="#69c" stroke-width="1.0087"/>
6
+ </svg>
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg width="24" height="24" version="1.2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
3
+ <rect x="2" y="2" width="20" height="20" rx="1" ry="1" fill="#e6e6e6" fill-opacity=".14902" stroke-linecap="round" stroke-width="2"/>
4
+ <rect x="18" y="2" width="4" height="20" rx="1" ry="1" fill="#848484" stroke-linecap="round" stroke-width="2"/>
5
+ <path d="m8.5959 18.5c0.79361 0.78104 2.0837 0.78104 2.8773 0l6.5268-6.4141-6.5268-6.4141c-0.3968-0.391-0.91769-0.58601-1.4386-0.58601-0.52092 0-1.0418 0.195-1.4386 0.58601-0.79464 0.78104-0.79464 2.047 0 2.8281l3.6475 3.5861-3.6475 3.5861c-0.79464 0.78104-0.79464 2.047 0 2.8281z" fill="#69c" stroke-width="1.0087"/>
6
+ </svg>
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg width="24" height="24" version="1.2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
3
+ <rect x="2" y="2" width="20" height="20" rx="1" ry="1" fill="#e6e6e6" fill-opacity=".14902" stroke-linecap="round" stroke-width="2"/>
4
+ <rect x="2" y="2" width="20" height="4" rx="1" ry="1" fill="#848484" stroke-linecap="round" stroke-width="2"/>
5
+ <path d="m18.414 15.404c0.78104-0.79361 0.78104-2.0837 0-2.8773l-6.4141-6.5268-6.4141 6.5268c-0.391 0.3968-0.58601 0.91769-0.58601 1.4386 0 0.52092 0.195 1.0418 0.58601 1.4386 0.78104 0.79464 2.047 0.79464 2.8281 0l3.5861-3.6475 3.5861 3.6475c0.78104 0.79464 2.047 0.79464 2.8281 0z" fill="#69c" stroke-width="1.0087"/>
6
+ </svg>
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg width="24" height="24" version="1.2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
3
+ <rect x="2" y="2" width="20" height="20" rx="1" ry="1" fill="#e6e6e6" fill-opacity=".14902" stroke-linecap="round" stroke-width="2"/>
4
+ <path d="m18.414 10.404c0.78104-0.79361 0.78104-2.0837 0-2.8773l-6.4141-6.5268-6.4141 6.5268c-0.391 0.3968-0.58601 0.91769-0.58601 1.4386 0 0.52092 0.195 1.0418 0.58601 1.4386 0.78104 0.79464 2.047 0.79464 2.8281 0l3.5861-3.6475 3.5861 3.6475c0.78104 0.79464 2.047 0.79464 2.8281 0z" fill="#69c" stroke-width="1.0087"/>
5
+ <path d="m18.414 13.596c0.78104 0.79361 0.78104 2.0837 0 2.8773l-6.4141 6.5268-6.4141-6.5268c-0.391-0.3968-0.58601-0.91769-0.58601-1.4386 0-0.52092 0.195-1.0418 0.58601-1.4386 0.78104-0.79464 2.047-0.79464 2.8281 0l3.5861 3.6475 3.5861-3.6475c0.78104-0.79464 2.047-0.79464 2.8281 0z" fill="#69c" stroke-width="1.0087"/>
6
+ </svg>
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg width="24" height="24" version="1.2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
3
+ <rect x="2" y="2" width="20" height="20" rx="1" ry="1" fill="#e6e6e6" fill-opacity=".15" stroke-linecap="round" stroke-width="2"/>
4
+ <path d="m13.596 18.414c0.79361 0.78104 2.0837 0.78104 2.8773 0l6.5268-6.4141-6.5268-6.4141c-0.3968-0.391-0.91769-0.58601-1.4386-0.58601-0.52092 0-1.0418 0.195-1.4386 0.58601-0.79464 0.78104-0.79464 2.047 0 2.8281l3.6475 3.5861-3.6475 3.5861c-0.79464 0.78104-0.79464 2.047 0 2.8281z" fill="#69c" stroke-width="1.0087"/>
5
+ <path d="m10.404 18.414c-0.79361 0.78104-2.0837 0.78104-2.8773 0l-6.5268-6.4141 6.5268-6.4141c0.3968-0.391 0.91769-0.58601 1.4386-0.58601 0.52092 0 1.0418 0.195 1.4386 0.58601 0.79464 0.78104 0.79464 2.047 0 2.8281l-3.6475 3.5861 3.6475 3.5861c0.79464 0.78104 0.79464 2.047 0 2.8281z" fill="#69c" stroke-width="1.0087"/>
6
+ </svg>
@@ -0,0 +1,5 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg width="24" height="24" version="1.2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
3
+ <rect x="8" y="2" width="8" height="8" rx="1" ry="1" fill="none" stroke="#69c" stroke-linecap="round" stroke-width="2"/>
4
+ <rect x="8" y="14" width="8" height="8" rx="1" ry="1" fill="none" stroke="#69c" stroke-linecap="round" stroke-width="2"/>
5
+ </svg>
@@ -44,6 +44,7 @@ cls_template = mixed_document-new.svg
44
44
  cls_timeline = typ_calendar.svg
45
45
  cls_trash = typ_trash.svg
46
46
  cls_world = typ_location.svg
47
+ copy = mixed_copy.svg
47
48
  cross = typ_times.svg
48
49
  document = typ_document.svg
49
50
  down = typ_chevron-down.svg
@@ -63,6 +64,10 @@ font = nw_font.svg
63
64
  forward = typ_chevron-right.svg
64
65
  import = mixed_import.svg
65
66
  list = typ_th-list.svg
67
+ margin_bottom = mixed_margin-bottom.svg
68
+ margin_left = mixed_margin-left.svg
69
+ margin_right = mixed_margin-right.svg
70
+ margin_top = mixed_margin-top.svg
66
71
  maximise = typ_arrow-maximise.svg
67
72
  menu = typ_th-dot-menu.svg
68
73
  minimise = typ_arrow-minimise.svg
@@ -93,6 +98,8 @@ search_regex = nw_search-regex.svg
93
98
  search_replace = mixed_search-replace.svg
94
99
  search_word = nw_search-word.svg
95
100
  settings = typ_cog.svg
101
+ size_height = mixed_size-height.svg
102
+ size_width = mixed_size-width.svg
96
103
  star = typ_star.svg
97
104
  status_idle = typ_media-pause-grey.svg
98
105
  status_lang = typ_globe-grey.svg
@@ -101,6 +108,7 @@ status_stats = typ_chart-bar-grey.svg
101
108
  status_time = typ_stopwatch-grey.svg
102
109
  sticky-off = typ_pin-outline.svg
103
110
  sticky-on = typ_pin.svg
111
+ toolbar = nw_toolbar.svg
104
112
  unchecked = mixed_input-unchecked.svg
105
113
  unfold-hide = typ_unfold-hidden.svg
106
114
  unfold-show = typ_unfold-visible.svg
@@ -0,0 +1,4 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg width="24" height="24" version="1.2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
3
+ <path d="m7.8824 2c-1.2988 0-2.3529 1.0541-2.3529 2.3529v2.3529h-1.1765c-1.2988 0-2.3529 1.0541-2.3529 2.3529v10.588c0 1.2988 1.0541 2.3529 2.3529 2.3529h10.588c1.2988 0 2.3529-1.0541 2.3529-2.3529v-1.1765h2.3529c1.2988 0 2.3529-1.0541 2.3529-2.3529v-11.765c0-1.2988-1.0541-2.3529-2.3529-2.3529zm0 2.3529h11.765v11.765h-2.3529v-7.0588c0-1.2988-1.0541-2.3529-2.3529-2.3529h-7.0588zm-3.5294 4.7059h10.588c0.32353 0 0-0.32353 0 0v10.588h-10.588v-9.8943z" fill="#4271ae" stroke-width="1.1765"/>
4
+ </svg>
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg width="24" height="24" version="1.2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
3
+ <rect x="2" y="2" width="20" height="20" rx="1" ry="1" fill="#333" fill-opacity=".15" stroke-linecap="round" stroke-width="2"/>
4
+ <rect x="2" y="18" width="20" height="4" rx="1" ry="1" fill="#333" stroke-linecap="round" stroke-width="2"/>
5
+ <path d="m18.414 8.5959c0.78104 0.79361 0.78104 2.0837 0 2.8773l-6.4141 6.5268-6.4141-6.5268c-0.391-0.3968-0.58601-0.91769-0.58601-1.4386 0-0.52092 0.195-1.0418 0.58601-1.4386 0.78104-0.79464 2.047-0.79464 2.8281 0l3.5861 3.6475 3.5861-3.6475c0.78104-0.79464 2.047-0.79464 2.8281 0z" fill="#4271ae" stroke-width="1.0087"/>
6
+ </svg>
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg width="24" height="24" version="1.2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
3
+ <rect x="2" y="2" width="20" height="20" rx="1" ry="1" fill="#333" fill-opacity=".15" stroke-linecap="round" stroke-width="2"/>
4
+ <rect x="2" y="2" width="4" height="20" rx="1" ry="1" fill="#333" stroke-linecap="round" stroke-width="2"/>
5
+ <path d="m15.404 18.5c-0.79361 0.78104-2.0837 0.78104-2.8773 0l-6.5268-6.4141 6.5268-6.4141c0.3968-0.391 0.91769-0.58601 1.4386-0.58601 0.52092 0 1.0418 0.195 1.4386 0.58601 0.79464 0.78104 0.79464 2.047 0 2.8281l-3.6475 3.5861 3.6475 3.5861c0.79464 0.78104 0.79464 2.047 0 2.8281z" fill="#4271ae" stroke-width="1.0087"/>
6
+ </svg>
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg width="24" height="24" version="1.2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
3
+ <rect x="2" y="2" width="20" height="20" rx="1" ry="1" fill="#333" fill-opacity=".15" stroke-linecap="round" stroke-width="2"/>
4
+ <rect x="18" y="2" width="4" height="20" rx="1" ry="1" fill="#333" stroke-linecap="round" stroke-width="2"/>
5
+ <path d="m8.5959 18.5c0.79361 0.78104 2.0837 0.78104 2.8773 0l6.5268-6.4141-6.5268-6.4141c-0.3968-0.391-0.91769-0.58601-1.4386-0.58601-0.52092 0-1.0418 0.195-1.4386 0.58601-0.79464 0.78104-0.79464 2.047 0 2.8281l3.6475 3.5861-3.6475 3.5861c-0.79464 0.78104-0.79464 2.047 0 2.8281z" fill="#4271ae" stroke-width="1.0087"/>
6
+ </svg>
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg width="24" height="24" version="1.2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
3
+ <rect x="2" y="2" width="20" height="20" rx="1" ry="1" fill="#333" fill-opacity=".15" stroke-linecap="round" stroke-width="2"/>
4
+ <rect x="2" y="2" width="20" height="4" rx="1" ry="1" fill="#333" stroke-linecap="round" stroke-width="2"/>
5
+ <path d="m18.414 15.404c0.78104-0.79361 0.78104-2.0837 0-2.8773l-6.4141-6.5268-6.4141 6.5268c-0.391 0.3968-0.58601 0.91769-0.58601 1.4386 0 0.52092 0.195 1.0418 0.58601 1.4386 0.78104 0.79464 2.047 0.79464 2.8281 0l3.5861-3.6475 3.5861 3.6475c0.78104 0.79464 2.047 0.79464 2.8281 0z" fill="#4271ae" stroke-width="1.0087"/>
6
+ </svg>
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg width="24" height="24" version="1.2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
3
+ <rect x="2" y="2" width="20" height="20" rx="1" ry="1" fill="#333" fill-opacity=".15" stroke-linecap="round" stroke-width="2"/>
4
+ <path d="m18.414 10.404c0.78104-0.79361 0.78104-2.0837 0-2.8773l-6.4141-6.5268-6.4141 6.5268c-0.391 0.3968-0.58601 0.91769-0.58601 1.4386 0 0.52092 0.195 1.0418 0.58601 1.4386 0.78104 0.79464 2.047 0.79464 2.8281 0l3.5861-3.6475 3.5861 3.6475c0.78104 0.79464 2.047 0.79464 2.8281 0z" fill="#4271ae" stroke-width="1.0087"/>
5
+ <path d="m18.414 13.596c0.78104 0.79361 0.78104 2.0837 0 2.8773l-6.4141 6.5268-6.4141-6.5268c-0.391-0.3968-0.58601-0.91769-0.58601-1.4386 0-0.52092 0.195-1.0418 0.58601-1.4386 0.78104-0.79464 2.047-0.79464 2.8281 0l3.5861 3.6475 3.5861-3.6475c0.78104-0.79464 2.047-0.79464 2.8281 0z" fill="#4271ae" stroke-width="1.0087"/>
6
+ </svg>
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg width="24" height="24" version="1.2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
3
+ <rect x="2" y="2" width="20" height="20" rx="1" ry="1" fill="#333" fill-opacity=".15" stroke-linecap="round" stroke-width="2"/>
4
+ <path d="m13.596 18.414c0.79361 0.78104 2.0837 0.78104 2.8773 0l6.5268-6.4141-6.5268-6.4141c-0.3968-0.391-0.91769-0.58601-1.4386-0.58601-0.52092 0-1.0418 0.195-1.4386 0.58601-0.79464 0.78104-0.79464 2.047 0 2.8281l3.6475 3.5861-3.6475 3.5861c-0.79464 0.78104-0.79464 2.047 0 2.8281z" fill="#4271ae" stroke-width="1.0087"/>
5
+ <path d="m10.404 18.414c-0.79361 0.78104-2.0837 0.78104-2.8773 0l-6.5268-6.4141 6.5268-6.4141c0.3968-0.391 0.91769-0.58601 1.4386-0.58601 0.52092 0 1.0418 0.195 1.4386 0.58601 0.79464 0.78104 0.79464 2.047 0 2.8281l-3.6475 3.5861 3.6475 3.5861c0.79464 0.78104 0.79464 2.047 0 2.8281z" fill="#4271ae" stroke-width="1.0087"/>
6
+ </svg>
@@ -0,0 +1,5 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg width="24" height="24" version="1.2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
3
+ <rect x="8" y="2" width="8" height="8" rx="1" ry="1" fill="none" stroke="#4271ae" stroke-linecap="round" stroke-width="2"/>
4
+ <rect x="8" y="14" width="8" height="8" rx="1" ry="1" fill="none" stroke="#4271ae" stroke-linecap="round" stroke-width="2"/>
5
+ </svg>
Binary file
Binary file
novelwriter/common.py CHANGED
@@ -6,7 +6,7 @@ File History:
6
6
  Created: 2019-05-12 [0.1.0]
7
7
 
8
8
  This file is a part of novelWriter
9
- Copyright 2018–2024, Veronica Berglyd Olsen
9
+ Copyright (C) 2019 Veronica Berglyd Olsen and novelWriter contributors
10
10
 
11
11
  This program is free software: you can redistribute it and/or modify
12
12
  it under the terms of the GNU General Public License as published by
@@ -29,6 +29,7 @@ import unicodedata
29
29
  import uuid
30
30
  import xml.etree.ElementTree as ET
31
31
 
32
+ from collections.abc import Callable
32
33
  from configparser import ConfigParser
33
34
  from datetime import datetime
34
35
  from pathlib import Path
@@ -36,8 +37,8 @@ from typing import TYPE_CHECKING, Any, Literal, TypeVar
36
37
  from urllib.parse import urljoin
37
38
  from urllib.request import pathname2url
38
39
 
39
- from PyQt5.QtCore import QCoreApplication, QUrl
40
- from PyQt5.QtGui import QColor, QDesktopServices, QFont, QFontInfo
40
+ from PyQt5.QtCore import QCoreApplication, QMimeData, QUrl
41
+ from PyQt5.QtGui import QColor, QDesktopServices, QFont, QFontDatabase, QFontInfo
41
42
 
42
43
  from novelwriter.constants import nwConst, nwLabels, nwUnicode, trConst
43
44
  from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType
@@ -204,6 +205,14 @@ def checkIntTuple(value: int, valid: tuple | list | set, default: int) -> int:
204
205
  return default
205
206
 
206
207
 
208
+ def firstFloat(*args: Any) -> float:
209
+ """Return the first value that is a float."""
210
+ for arg in args:
211
+ if isinstance(arg, float):
212
+ return arg
213
+ return 0.0
214
+
215
+
207
216
  ##
208
217
  # Formatting Functions
209
218
  ##
@@ -406,6 +415,10 @@ def numberToRoman(value: int, toLower: bool = False) -> str:
406
415
  return roman.lower() if toLower else roman
407
416
 
408
417
 
418
+ ##
419
+ # Qt Helpers
420
+ ##
421
+
409
422
  def cssCol(col: QColor, alpha: int | None = None) -> str:
410
423
  """Convert a QColor object to an rgba entry to use in CSS."""
411
424
  return f"rgba({col.red()}, {col.green()}, {col.blue()}, {alpha or col.alpha()})"
@@ -421,6 +434,48 @@ def describeFont(font: QFont) -> str:
421
434
  return "Error"
422
435
 
423
436
 
437
+ def fontMatcher(font: QFont) -> QFont:
438
+ """Make sure the font is the correct family, if possible. This
439
+ ensures that Qt doesn't re-use another font under the hood. The
440
+ default Qt5 font matching algorithm doesn't handle well changing
441
+ application fonts at runtime.
442
+ """
443
+ info = QFontInfo(font)
444
+ if (famRequest := font.family()) != (famActual := info.family()):
445
+ logger.warning("Font mismatch: Requested '%s', but got '%s'", famRequest, famActual)
446
+ db = QFontDatabase()
447
+ if famRequest in db.families():
448
+ styleRequest, sizeRequest = font.styleName(), font.pointSize()
449
+ logger.info("Lookup: %s, %s, %d pt", famRequest, styleRequest, sizeRequest)
450
+ temp = db.font(famRequest, styleRequest, sizeRequest)
451
+ temp.setPointSize(sizeRequest) # Make sure it isn't changed
452
+ famFound, styleFound, sizeFound = temp.family(), temp.styleName(), temp.pointSize()
453
+ if famFound == famRequest:
454
+ logger.info("Found: %s, %s, %d pt", famFound, styleFound, sizeFound)
455
+ return temp
456
+ logger.warning("Could not find a font match in the font database")
457
+ logger.warning("If you just changed font, you may need to restart the application")
458
+ return font
459
+
460
+
461
+ def qtLambda(func: Callable, *args: Any, **kwargs: Any) -> Callable:
462
+ """A replacement for Python lambdas that works for Qt slots."""
463
+ def wrapper(*a_: Any) -> None:
464
+ func(*args, **kwargs)
465
+ return wrapper
466
+
467
+
468
+ def encodeMimeHandles(mimeData: QMimeData, handles: list[str]) -> None:
469
+ """Encode handles into a mime data object."""
470
+ mimeData.setData(nwConst.MIME_HANDLE, b"|".join(h.encode() for h in handles))
471
+ return
472
+
473
+
474
+ def decodeMimeHandles(mimeData: QMimeData) -> list[str]:
475
+ """Decode and split a mime data object with handles."""
476
+ return mimeData.data(nwConst.MIME_HANDLE).data().decode().split("|")
477
+
478
+
424
479
  ##
425
480
  # Encoder Functions
426
481
  ##
@@ -472,6 +527,10 @@ def jsonEncode(data: dict | list | tuple, n: int = 0, nmax: int = 0) -> str:
472
527
  return "".join(buffer)
473
528
 
474
529
 
530
+ ##
531
+ # XML Helpers
532
+ ##
533
+
475
534
  def xmlIndent(tree: ET.Element | ET.ElementTree) -> None:
476
535
  """A modified version of the XML indent function in the standard
477
536
  library. It behaves more closely to how the one from lxml does.
@@ -515,6 +574,45 @@ def xmlIndent(tree: ET.Element | ET.ElementTree) -> None:
515
574
  return
516
575
 
517
576
 
577
+ def xmlElement(
578
+ tag: str,
579
+ text: str | int | float | bool | None = None,
580
+ *,
581
+ attrib: dict | None = None,
582
+ tail: str | None = None,
583
+ ) -> ET.Element:
584
+ """A custom implementation of Element with more arguments."""
585
+ xSub = ET.Element(tag, attrib=attrib or {})
586
+ if text is not None:
587
+ if isinstance(text, bool):
588
+ xSub.text = str(text).lower()
589
+ else:
590
+ xSub.text = str(text)
591
+ if tail is not None:
592
+ xSub.tail = tail
593
+ return xSub
594
+
595
+
596
+ def xmlSubElem(
597
+ parent: ET.Element,
598
+ tag: str,
599
+ text: str | int | float | bool | None = None,
600
+ *,
601
+ attrib: dict | None = None,
602
+ tail: str | None = None,
603
+ ) -> ET.Element:
604
+ """A custom implementation of SubElement with more arguments."""
605
+ xSub = ET.SubElement(parent, tag, attrib=attrib or {})
606
+ if text is not None:
607
+ if isinstance(text, bool):
608
+ xSub.text = str(text).lower()
609
+ else:
610
+ xSub.text = str(text)
611
+ if tail is not None:
612
+ xSub.tail = tail
613
+ return xSub
614
+
615
+
518
616
  ##
519
617
  # File and File System Functions
520
618
  ##
novelwriter/config.py CHANGED
@@ -8,7 +8,7 @@ Created: 2022-11-09 [2.0rc2] RecentProjects
8
8
  Created: 2024-06-16 [2.5rc1] RecentPaths
9
9
 
10
10
  This file is a part of novelWriter
11
- Copyright 2018–2024, Veronica Berglyd Olsen
11
+ Copyright (C) 2018 Veronica Berglyd Olsen and novelWriter contributors
12
12
 
13
13
  This program is free software: you can redistribute it and/or modify
14
14
  it under the terms of the GNU General Public License as published by
@@ -40,7 +40,10 @@ from PyQt5.QtCore import (
40
40
  from PyQt5.QtGui import QFont, QFontDatabase
41
41
  from PyQt5.QtWidgets import QApplication
42
42
 
43
- from novelwriter.common import NWConfigParser, checkInt, checkPath, describeFont, formatTimeStamp
43
+ from novelwriter.common import (
44
+ NWConfigParser, checkInt, checkPath, describeFont, fontMatcher,
45
+ formatTimeStamp
46
+ )
44
47
  from novelwriter.constants import nwFiles, nwUnicode
45
48
  from novelwriter.error import formatException, logException
46
49
 
@@ -109,7 +112,7 @@ class Config:
109
112
 
110
113
  # General GUI Settings
111
114
  self.guiLocale = self._qLocale.name()
112
- self.guiTheme = "default" # GUI theme
115
+ self.guiTheme = "default_light" # GUI theme
113
116
  self.guiSyntax = "default_light" # Syntax theme
114
117
  self.guiFont = QFont() # Main GUI font
115
118
  self.guiScale = 1.0 # Set automatically by Theme class
@@ -161,8 +164,9 @@ class Config:
161
164
 
162
165
  self.dialogStyle = 2 # Quote type to use for dialogue
163
166
  self.allowOpenDial = True # Allow open-ended dialogue quotes
164
- self.narratorBreak = "" # Symbol to use for narrator break
165
167
  self.dialogLine = "" # Symbol to use for dialogue line
168
+ self.narratorBreak = "" # Symbol to use for narrator break
169
+ self.narratorDialog = "" # Symbol for alternating between dialogue and narrator
166
170
  self.altDialogOpen = "" # Alternative dialog symbol, open
167
171
  self.altDialogClose = "" # Alternative dialog symbol, close
168
172
  self.highlightEmph = True # Add colour to text emphasis
@@ -190,6 +194,7 @@ class Config:
190
194
  # State
191
195
  self.showViewerPanel = True # The panel for the viewer is visible
192
196
  self.showEditToolBar = False # The document editor toolbar visibility
197
+ self.showSessionTime = True # Show the session time in the status bar
193
198
  self.viewComments = True # Comments are shown in the viewer
194
199
  self.viewSynopsis = True # Synopsis is shown in the viewer
195
200
 
@@ -252,6 +257,10 @@ class Config:
252
257
  def hasError(self) -> bool:
253
258
  return self._hasError
254
259
 
260
+ @property
261
+ def locale(self) -> QLocale:
262
+ return self._dLocale
263
+
255
264
  @property
256
265
  def recentProjects(self) -> RecentProjects:
257
266
  return self._recentProjects
@@ -364,10 +373,11 @@ class Config:
364
373
  def setGuiFont(self, value: QFont | str | None) -> None:
365
374
  """Update the GUI's font style from settings."""
366
375
  if isinstance(value, QFont):
367
- self.guiFont = value
376
+ self.guiFont = fontMatcher(value)
368
377
  elif value and isinstance(value, str):
369
- self.guiFont = QFont()
370
- self.guiFont.fromString(value)
378
+ font = QFont()
379
+ font.fromString(value)
380
+ self.guiFont = fontMatcher(font)
371
381
  else:
372
382
  font = QFont()
373
383
  fontDB = QFontDatabase()
@@ -377,11 +387,9 @@ class Config:
377
387
  font.setPointSize(10)
378
388
  else:
379
389
  font = fontDB.systemFont(QFontDatabase.SystemFont.GeneralFont)
380
- self.guiFont = font
390
+ self.guiFont = fontMatcher(font)
381
391
  logger.debug("GUI font set to: %s", describeFont(font))
382
-
383
392
  QApplication.setFont(self.guiFont)
384
-
385
393
  return
386
394
 
387
395
  def setTextFont(self, value: QFont | str | None) -> None:
@@ -389,10 +397,11 @@ class Config:
389
397
  set to default font.
390
398
  """
391
399
  if isinstance(value, QFont):
392
- self.textFont = value
400
+ self.textFont = fontMatcher(value)
393
401
  elif value and isinstance(value, str):
394
- self.textFont = QFont()
395
- self.textFont.fromString(value)
402
+ font = QFont()
403
+ font.fromString(value)
404
+ self.textFont = fontMatcher(font)
396
405
  else:
397
406
  fontDB = QFontDatabase()
398
407
  fontFam = fontDB.families()
@@ -406,8 +415,8 @@ class Config:
406
415
  font.setPointSize(12)
407
416
  else:
408
417
  font = fontDB.systemFont(QFontDatabase.SystemFont.GeneralFont)
409
- self.textFont = font
410
- logger.debug("Text font set to: %s", describeFont(font))
418
+ self.textFont = fontMatcher(font)
419
+ logger.debug("Text font set to: %s", describeFont(self.textFont))
411
420
  return
412
421
 
413
422
  ##
@@ -653,10 +662,11 @@ class Config:
653
662
  self.showFullPath = conf.rdBool(sec, "showfullpath", self.showFullPath)
654
663
  self.dialogStyle = conf.rdInt(sec, "dialogstyle", self.dialogStyle)
655
664
  self.allowOpenDial = conf.rdBool(sec, "allowopendial", self.allowOpenDial)
665
+ self.dialogLine = conf.rdStr(sec, "dialogline", self.dialogLine)
656
666
  self.narratorBreak = conf.rdStr(sec, "narratorbreak", self.narratorBreak)
667
+ self.narratorDialog = conf.rdStr(sec, "narratordialog", self.narratorDialog)
657
668
  self.altDialogOpen = conf.rdStr(sec, "altdialogopen", self.altDialogOpen)
658
669
  self.altDialogClose = conf.rdStr(sec, "altdialogclose", self.altDialogClose)
659
- self.dialogLine = conf.rdStr(sec, "dialogline", self.dialogLine)
660
670
  self.highlightEmph = conf.rdBool(sec, "highlightemph", self.highlightEmph)
661
671
  self.stopWhenIdle = conf.rdBool(sec, "stopwhenidle", self.stopWhenIdle)
662
672
  self.userIdleTime = conf.rdInt(sec, "useridletime", self.userIdleTime)
@@ -665,6 +675,7 @@ class Config:
665
675
  sec = "State"
666
676
  self.showViewerPanel = conf.rdBool(sec, "showviewerpanel", self.showViewerPanel)
667
677
  self.showEditToolBar = conf.rdBool(sec, "showedittoolbar", self.showEditToolBar)
678
+ self.showSessionTime = conf.rdBool(sec, "showsessiontime", self.showSessionTime)
668
679
  self.viewComments = conf.rdBool(sec, "viewcomments", self.viewComments)
669
680
  self.viewSynopsis = conf.rdBool(sec, "viewsynopsis", self.viewSynopsis)
670
681
  self.searchCase = conf.rdBool(sec, "searchcase", self.searchCase)
@@ -762,10 +773,11 @@ class Config:
762
773
  "showfullpath": str(self.showFullPath),
763
774
  "dialogstyle": str(self.dialogStyle),
764
775
  "allowopendial": str(self.allowOpenDial),
776
+ "dialogline": str(self.dialogLine),
765
777
  "narratorbreak": str(self.narratorBreak),
778
+ "narratordialog": str(self.narratorDialog),
766
779
  "altdialogopen": str(self.altDialogOpen),
767
780
  "altdialogclose": str(self.altDialogClose),
768
- "dialogline": str(self.dialogLine),
769
781
  "highlightemph": str(self.highlightEmph),
770
782
  "stopwhenidle": str(self.stopWhenIdle),
771
783
  "useridletime": str(self.userIdleTime),
@@ -774,6 +786,7 @@ class Config:
774
786
  conf["State"] = {
775
787
  "showviewerpanel": str(self.showViewerPanel),
776
788
  "showedittoolbar": str(self.showEditToolBar),
789
+ "showsessiontime": str(self.showSessionTime),
777
790
  "viewcomments": str(self.viewComments),
778
791
  "viewsynopsis": str(self.viewSynopsis),
779
792
  "searchcase": str(self.searchCase),