novelWriter 2.5.2__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 (129) hide show
  1. {novelWriter-2.5.2.dist-info → novelWriter-2.6.dist-info}/METADATA +5 -4
  2. {novelWriter-2.5.2.dist-info → novelWriter-2.6.dist-info}/RECORD +126 -105
  3. {novelWriter-2.5.2.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 +4 -2
  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_ru_RU.json +11 -0
  27. novelwriter/assets/i18n/project_zh_CN.json +2 -0
  28. novelwriter/assets/icons/typicons_dark/icons.conf +8 -0
  29. novelwriter/assets/icons/typicons_dark/mixed_copy.svg +4 -0
  30. novelwriter/assets/icons/typicons_dark/mixed_margin-bottom.svg +6 -0
  31. novelwriter/assets/icons/typicons_dark/mixed_margin-left.svg +6 -0
  32. novelwriter/assets/icons/typicons_dark/mixed_margin-right.svg +6 -0
  33. novelwriter/assets/icons/typicons_dark/mixed_margin-top.svg +6 -0
  34. novelwriter/assets/icons/typicons_dark/mixed_size-height.svg +6 -0
  35. novelwriter/assets/icons/typicons_dark/mixed_size-width.svg +6 -0
  36. novelwriter/assets/icons/typicons_dark/nw_toolbar.svg +5 -0
  37. novelwriter/assets/icons/typicons_light/icons.conf +8 -0
  38. novelwriter/assets/icons/typicons_light/mixed_copy.svg +4 -0
  39. novelwriter/assets/icons/typicons_light/mixed_margin-bottom.svg +6 -0
  40. novelwriter/assets/icons/typicons_light/mixed_margin-left.svg +6 -0
  41. novelwriter/assets/icons/typicons_light/mixed_margin-right.svg +6 -0
  42. novelwriter/assets/icons/typicons_light/mixed_margin-top.svg +6 -0
  43. novelwriter/assets/icons/typicons_light/mixed_size-height.svg +6 -0
  44. novelwriter/assets/icons/typicons_light/mixed_size-width.svg +6 -0
  45. novelwriter/assets/icons/typicons_light/nw_toolbar.svg +5 -0
  46. novelwriter/assets/manual.pdf +0 -0
  47. novelwriter/assets/sample.zip +0 -0
  48. novelwriter/assets/text/credits_en.htm +1 -0
  49. novelwriter/assets/themes/default_light.conf +2 -2
  50. novelwriter/common.py +101 -3
  51. novelwriter/config.py +30 -17
  52. novelwriter/constants.py +189 -81
  53. novelwriter/core/buildsettings.py +74 -40
  54. novelwriter/core/coretools.py +146 -148
  55. novelwriter/core/docbuild.py +133 -171
  56. novelwriter/core/document.py +1 -1
  57. novelwriter/core/index.py +39 -38
  58. novelwriter/core/item.py +42 -9
  59. novelwriter/core/itemmodel.py +518 -0
  60. novelwriter/core/options.py +5 -2
  61. novelwriter/core/project.py +68 -90
  62. novelwriter/core/projectdata.py +8 -2
  63. novelwriter/core/projectxml.py +1 -1
  64. novelwriter/core/sessions.py +1 -1
  65. novelwriter/core/spellcheck.py +10 -15
  66. novelwriter/core/status.py +24 -8
  67. novelwriter/core/storage.py +1 -1
  68. novelwriter/core/tree.py +269 -288
  69. novelwriter/dialogs/about.py +1 -1
  70. novelwriter/dialogs/docmerge.py +8 -18
  71. novelwriter/dialogs/docsplit.py +1 -1
  72. novelwriter/dialogs/editlabel.py +1 -1
  73. novelwriter/dialogs/preferences.py +47 -34
  74. novelwriter/dialogs/projectsettings.py +149 -99
  75. novelwriter/dialogs/quotes.py +1 -1
  76. novelwriter/dialogs/wordlist.py +11 -10
  77. novelwriter/enum.py +37 -24
  78. novelwriter/error.py +2 -2
  79. novelwriter/extensions/configlayout.py +28 -13
  80. novelwriter/extensions/eventfilters.py +1 -1
  81. novelwriter/extensions/modified.py +30 -6
  82. novelwriter/extensions/novelselector.py +4 -3
  83. novelwriter/extensions/pagedsidebar.py +9 -9
  84. novelwriter/extensions/progressbars.py +4 -4
  85. novelwriter/extensions/statusled.py +3 -3
  86. novelwriter/extensions/switch.py +3 -3
  87. novelwriter/extensions/switchbox.py +1 -1
  88. novelwriter/extensions/versioninfo.py +1 -1
  89. novelwriter/formats/shared.py +156 -0
  90. novelwriter/formats/todocx.py +1191 -0
  91. novelwriter/formats/tohtml.py +454 -0
  92. novelwriter/{core → formats}/tokenizer.py +497 -495
  93. novelwriter/formats/tomarkdown.py +218 -0
  94. novelwriter/{core → formats}/toodt.py +312 -433
  95. novelwriter/formats/toqdoc.py +486 -0
  96. novelwriter/formats/toraw.py +91 -0
  97. novelwriter/gui/doceditor.py +347 -287
  98. novelwriter/gui/dochighlight.py +97 -85
  99. novelwriter/gui/docviewer.py +90 -33
  100. novelwriter/gui/docviewerpanel.py +18 -26
  101. novelwriter/gui/editordocument.py +18 -3
  102. novelwriter/gui/itemdetails.py +27 -29
  103. novelwriter/gui/mainmenu.py +130 -64
  104. novelwriter/gui/noveltree.py +46 -48
  105. novelwriter/gui/outline.py +202 -256
  106. novelwriter/gui/projtree.py +590 -1238
  107. novelwriter/gui/search.py +11 -19
  108. novelwriter/gui/sidebar.py +8 -7
  109. novelwriter/gui/statusbar.py +20 -3
  110. novelwriter/gui/theme.py +11 -6
  111. novelwriter/guimain.py +101 -201
  112. novelwriter/shared.py +67 -28
  113. novelwriter/text/counting.py +3 -1
  114. novelwriter/text/patterns.py +169 -61
  115. novelwriter/tools/dictionaries.py +3 -3
  116. novelwriter/tools/lipsum.py +1 -1
  117. novelwriter/tools/manusbuild.py +15 -13
  118. novelwriter/tools/manuscript.py +121 -79
  119. novelwriter/tools/manussettings.py +424 -291
  120. novelwriter/tools/noveldetails.py +1 -1
  121. novelwriter/tools/welcome.py +6 -6
  122. novelwriter/tools/writingstats.py +4 -4
  123. novelwriter/types.py +25 -9
  124. novelwriter/core/tohtml.py +0 -530
  125. novelwriter/core/tomarkdown.py +0 -252
  126. novelwriter/core/toqdoc.py +0 -419
  127. {novelWriter-2.5.2.dist-info → novelWriter-2.6.dist-info}/LICENSE.md +0 -0
  128. {novelWriter-2.5.2.dist-info → novelWriter-2.6.dist-info}/entry_points.txt +0 -0
  129. {novelWriter-2.5.2.dist-info → novelWriter-2.6.dist-info}/top_level.txt +0 -0
novelwriter/constants.py CHANGED
@@ -6,7 +6,7 @@ File History:
6
6
  Created: 2019-04-28 [0.0.1]
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
@@ -53,6 +53,9 @@ class nwConst:
53
53
  # Requests
54
54
  USER_AGENT = "Mozilla/5.0 (compatible; novelWriter (Python))"
55
55
 
56
+ # Mime Types
57
+ MIME_HANDLE = "text/vnd.novelwriter.handle"
58
+
56
59
  # Gui Settings
57
60
  STATUS_MSG_TIMEOUT = 15000 # milliseconds
58
61
  MAX_SEARCH_RESULT = 1000
@@ -60,11 +63,14 @@ class nwConst:
60
63
 
61
64
  class nwRegEx:
62
65
 
66
+ URL = r"https?://(?:www\.|(?!www))[\w/()@:%_\+-.~#?&=]+"
67
+ WORDS = r"\b[^\s\-\+\/–—\[\]:]+\b"
68
+ BREAK = r"(?i)(?<!\\)(\[br\]\n?)"
63
69
  FMT_EI = r"(?<![\w\\])(_)(?![\s_])(.+?)(?<![\s\\])(\1)(?!\w)"
64
70
  FMT_EB = r"(?<![\w\\])(\*{2})(?![\s\*])(.+?)(?<![\s\\])(\1)(?!\w)"
65
71
  FMT_ST = r"(?<![\w\\])(~{2})(?![\s~])(.+?)(?<![\s\\])(\1)(?!\w)"
66
- FMT_SC = r"(?i)(?<!\\)(\[[\/\!]?(?:b|i|s|u|m|sup|sub)\])"
67
- FMT_SV = r"(?i)(?<!\\)(\[(?:footnote):)(.+?)(?<!\\)(\])"
72
+ FMT_SC = r"(?i)(?<!\\)(\[(?:b|/b|i|/i|s|/s|u|/u|m|/m|sup|/sup|sub|/sub|br)\])"
73
+ FMT_SV = r"(?i)(?<!\\)(\[(?:footnote|field):)(.+?)(?<!\\)(\])"
68
74
 
69
75
 
70
76
  class nwShortcode:
@@ -83,21 +89,49 @@ class nwShortcode:
83
89
  SUP_C = "[/sup]"
84
90
  SUB_O = "[sub]"
85
91
  SUB_C = "[/sub]"
92
+ BREAK = "[br]"
86
93
 
87
94
  FOOTNOTE_B = "[footnote:"
95
+ FIELD_B = "[field:"
88
96
 
89
97
  COMMENT_STYLES = {
90
98
  nwComment.FOOTNOTE: "[footnote:{0}]",
91
99
  nwComment.COMMENT: "[comment:{0}]",
92
100
  }
93
101
 
102
+ FIELD_VALUE = "[field:{0}]"
103
+
94
104
 
95
- class nwHeaders:
105
+ class nwStyles:
96
106
 
97
107
  H_VALID = ("H0", "H1", "H2", "H3", "H4")
98
108
  H_LEVEL = {"H0": 0, "H1": 1, "H2": 2, "H3": 3, "H4": 4}
99
109
  H_SIZES = {0: 2.50, 1: 2.00, 2: 1.75, 3: 1.50, 4: 1.25}
100
110
 
111
+ T_NORMAL = 1.0
112
+ T_SMALL = 0.8
113
+
114
+ T_LABEL = {
115
+ "H0": QT_TRANSLATE_NOOP("Constant", "Title"),
116
+ "H1": QT_TRANSLATE_NOOP("Constant", "Heading 1 (Partition)"),
117
+ "H2": QT_TRANSLATE_NOOP("Constant", "Heading 2 (Chapter)"),
118
+ "H3": QT_TRANSLATE_NOOP("Constant", "Heading 3 (Scene)"),
119
+ "H4": QT_TRANSLATE_NOOP("Constant", "Heading 4 (Section)"),
120
+ "TT": QT_TRANSLATE_NOOP("Constant", "Text Paragraph"),
121
+ "SP": QT_TRANSLATE_NOOP("Constant", "Scene Separator"),
122
+ }
123
+ T_MARGIN = {
124
+ "H0": (1.50, 0.60), # Title margins (top, bottom)
125
+ "H1": (1.50, 0.60), # Heading 1 margins (top, bottom)
126
+ "H2": (1.50, 0.60), # Heading 2 margins (top, bottom)
127
+ "H3": (1.20, 0.60), # Heading 3 margins (top, bottom)
128
+ "H4": (1.20, 0.60), # Heading 4 margins (top, bottom)
129
+ "TT": (0.00, 0.60), # Text margins (top, bottom)
130
+ "SP": (1.20, 1.20), # Separator margins (top, bottom)
131
+ "MT": (0.00, 0.60), # Meta margins (top, bottom)
132
+ "FT": (1.40, 0.40), # Footnote margins (left, bottom)
133
+ }
134
+
101
135
 
102
136
  class nwFiles:
103
137
 
@@ -121,34 +155,40 @@ class nwFiles:
121
155
 
122
156
  class nwKeyWords:
123
157
 
124
- TAG_KEY = "@tag"
125
- POV_KEY = "@pov"
126
- FOCUS_KEY = "@focus"
127
- CHAR_KEY = "@char"
128
- PLOT_KEY = "@plot"
129
- TIME_KEY = "@time"
130
- WORLD_KEY = "@location"
131
- OBJECT_KEY = "@object"
132
- ENTITY_KEY = "@entity"
133
- CUSTOM_KEY = "@custom"
158
+ TAG_KEY = "@tag"
159
+ POV_KEY = "@pov"
160
+ FOCUS_KEY = "@focus"
161
+ CHAR_KEY = "@char"
162
+ PLOT_KEY = "@plot"
163
+ TIME_KEY = "@time"
164
+ WORLD_KEY = "@location"
165
+ OBJECT_KEY = "@object"
166
+ ENTITY_KEY = "@entity"
167
+ CUSTOM_KEY = "@custom"
168
+ STORY_KEY = "@story"
169
+ MENTION_KEY = "@mention"
170
+
171
+ # Note: The order here affects the order of menu entries
172
+ ALL_KEYS = [
173
+ TAG_KEY, POV_KEY, FOCUS_KEY, CHAR_KEY, PLOT_KEY, TIME_KEY, WORLD_KEY,
174
+ OBJECT_KEY, ENTITY_KEY, CUSTOM_KEY, STORY_KEY, MENTION_KEY,
175
+ ]
134
176
 
135
177
  # Set of Valid Keys
136
- VALID_KEYS = {
137
- TAG_KEY, POV_KEY, FOCUS_KEY, CHAR_KEY, PLOT_KEY, TIME_KEY,
138
- WORLD_KEY, OBJECT_KEY, ENTITY_KEY, CUSTOM_KEY
139
- }
178
+ VALID_KEYS = set(ALL_KEYS)
140
179
 
141
180
  # Map from Keys to Item Class
142
181
  KEY_CLASS = {
143
- POV_KEY: nwItemClass.CHARACTER,
144
- FOCUS_KEY: nwItemClass.CHARACTER,
145
- CHAR_KEY: nwItemClass.CHARACTER,
146
- PLOT_KEY: nwItemClass.PLOT,
147
- TIME_KEY: nwItemClass.TIMELINE,
148
- WORLD_KEY: nwItemClass.WORLD,
149
- OBJECT_KEY: nwItemClass.OBJECT,
150
- ENTITY_KEY: nwItemClass.ENTITY,
151
- CUSTOM_KEY: nwItemClass.CUSTOM,
182
+ POV_KEY: nwItemClass.CHARACTER,
183
+ FOCUS_KEY: nwItemClass.CHARACTER,
184
+ CHAR_KEY: nwItemClass.CHARACTER,
185
+ PLOT_KEY: nwItemClass.PLOT,
186
+ TIME_KEY: nwItemClass.TIMELINE,
187
+ WORLD_KEY: nwItemClass.WORLD,
188
+ OBJECT_KEY: nwItemClass.OBJECT,
189
+ ENTITY_KEY: nwItemClass.ENTITY,
190
+ CUSTOM_KEY: nwItemClass.CUSTOM,
191
+ STORY_KEY: nwItemClass.NOVEL,
152
192
  }
153
193
 
154
194
 
@@ -165,6 +205,29 @@ class nwLists:
165
205
  ]
166
206
 
167
207
 
208
+ class nwStats:
209
+
210
+ CHARS = "allChars"
211
+ CHARS_TEXT = "textChars"
212
+ CHARS_TITLE = "titleChars"
213
+ PARAGRAPHS = "paragraphCount"
214
+ TITLES = "titleCount"
215
+ WCHARS_ALL = "allWordChars"
216
+ WCHARS_TEXT = "textWordChars"
217
+ WCHARS_TITLE = "titleWordChars"
218
+ WORDS = "allWords"
219
+ WORDS_TEXT = "textWords"
220
+ WORDS_TITLE = "titleWords"
221
+
222
+ # Note: The order here affects the order of menu entries
223
+ ALL_FIELDS = [
224
+ WORDS, WORDS_TEXT, WORDS_TITLE,
225
+ CHARS, CHARS_TEXT, CHARS_TITLE,
226
+ WCHARS_ALL, WCHARS_TEXT, WCHARS_TITLE,
227
+ PARAGRAPHS, TITLES,
228
+ ]
229
+
230
+
168
231
  class nwLabels:
169
232
 
170
233
  CLASS_NAME = {
@@ -211,84 +274,124 @@ class nwLabels:
211
274
  "doc_h4": QT_TRANSLATE_NOOP("Constant", "Novel Section"),
212
275
  "note": QT_TRANSLATE_NOOP("Constant", "Project Note"),
213
276
  }
277
+ ACTIVE_NAME = {
278
+ "checked": QT_TRANSLATE_NOOP("Constant", "Active"),
279
+ "unchecked": QT_TRANSLATE_NOOP("Constant", "Inactive"),
280
+ }
214
281
  KEY_NAME = {
215
- nwKeyWords.TAG_KEY: QT_TRANSLATE_NOOP("Constant", "Tag"),
216
- nwKeyWords.POV_KEY: QT_TRANSLATE_NOOP("Constant", "Point of View"),
217
- nwKeyWords.FOCUS_KEY: QT_TRANSLATE_NOOP("Constant", "Focus"),
218
- nwKeyWords.CHAR_KEY: QT_TRANSLATE_NOOP("Constant", "Characters"),
219
- nwKeyWords.PLOT_KEY: QT_TRANSLATE_NOOP("Constant", "Plot"),
220
- nwKeyWords.TIME_KEY: QT_TRANSLATE_NOOP("Constant", "Timeline"),
221
- nwKeyWords.WORLD_KEY: QT_TRANSLATE_NOOP("Constant", "Locations"),
222
- nwKeyWords.OBJECT_KEY: QT_TRANSLATE_NOOP("Constant", "Objects"),
223
- nwKeyWords.ENTITY_KEY: QT_TRANSLATE_NOOP("Constant", "Entities"),
224
- nwKeyWords.CUSTOM_KEY: QT_TRANSLATE_NOOP("Constant", "Custom"),
282
+ nwKeyWords.TAG_KEY: QT_TRANSLATE_NOOP("Constant", "Tag"),
283
+ nwKeyWords.POV_KEY: QT_TRANSLATE_NOOP("Constant", "Point of View"),
284
+ nwKeyWords.FOCUS_KEY: QT_TRANSLATE_NOOP("Constant", "Focus"),
285
+ nwKeyWords.CHAR_KEY: QT_TRANSLATE_NOOP("Constant", "Characters"),
286
+ nwKeyWords.PLOT_KEY: QT_TRANSLATE_NOOP("Constant", "Plot"),
287
+ nwKeyWords.TIME_KEY: QT_TRANSLATE_NOOP("Constant", "Timeline"),
288
+ nwKeyWords.WORLD_KEY: QT_TRANSLATE_NOOP("Constant", "Locations"),
289
+ nwKeyWords.OBJECT_KEY: QT_TRANSLATE_NOOP("Constant", "Objects"),
290
+ nwKeyWords.ENTITY_KEY: QT_TRANSLATE_NOOP("Constant", "Entities"),
291
+ nwKeyWords.CUSTOM_KEY: QT_TRANSLATE_NOOP("Constant", "Custom"),
292
+ nwKeyWords.STORY_KEY: QT_TRANSLATE_NOOP("Constant", "Story"),
293
+ nwKeyWords.MENTION_KEY: QT_TRANSLATE_NOOP("Constant", "Mentions"),
294
+ }
295
+ KEY_SHORTCUT = {
296
+ nwKeyWords.TAG_KEY: "Ctrl+K, G",
297
+ nwKeyWords.POV_KEY: "Ctrl+K, V",
298
+ nwKeyWords.FOCUS_KEY: "Ctrl+K, F",
299
+ nwKeyWords.CHAR_KEY: "Ctrl+K, C",
300
+ nwKeyWords.PLOT_KEY: "Ctrl+K, P",
301
+ nwKeyWords.TIME_KEY: "Ctrl+K, T",
302
+ nwKeyWords.WORLD_KEY: "Ctrl+K, L",
303
+ nwKeyWords.OBJECT_KEY: "Ctrl+K, O",
304
+ nwKeyWords.ENTITY_KEY: "Ctrl+K, E",
305
+ nwKeyWords.CUSTOM_KEY: "Ctrl+K, X",
306
+ nwKeyWords.STORY_KEY: "Ctrl+K, N",
307
+ nwKeyWords.MENTION_KEY: "Ctrl+K, M",
225
308
  }
226
309
  OUTLINE_COLS = {
227
- nwOutline.TITLE: QT_TRANSLATE_NOOP("Constant", "Title"),
228
- nwOutline.LEVEL: QT_TRANSLATE_NOOP("Constant", "Level"),
229
- nwOutline.LABEL: QT_TRANSLATE_NOOP("Constant", "Document"),
230
- nwOutline.LINE: QT_TRANSLATE_NOOP("Constant", "Line"),
231
- nwOutline.CCOUNT: QT_TRANSLATE_NOOP("Constant", "Chars"),
232
- nwOutline.WCOUNT: QT_TRANSLATE_NOOP("Constant", "Words"),
233
- nwOutline.PCOUNT: QT_TRANSLATE_NOOP("Constant", "Pars"),
234
- nwOutline.POV: QT_TRANSLATE_NOOP("Constant", "POV"),
235
- nwOutline.FOCUS: QT_TRANSLATE_NOOP("Constant", "Focus"),
236
- nwOutline.CHAR: KEY_NAME[nwKeyWords.CHAR_KEY],
237
- nwOutline.PLOT: KEY_NAME[nwKeyWords.PLOT_KEY],
238
- nwOutline.WORLD: KEY_NAME[nwKeyWords.WORLD_KEY],
239
- nwOutline.TIME: KEY_NAME[nwKeyWords.TIME_KEY],
240
- nwOutline.OBJECT: KEY_NAME[nwKeyWords.OBJECT_KEY],
241
- nwOutline.ENTITY: KEY_NAME[nwKeyWords.ENTITY_KEY],
242
- nwOutline.CUSTOM: KEY_NAME[nwKeyWords.CUSTOM_KEY],
243
- nwOutline.SYNOP: QT_TRANSLATE_NOOP("Constant", "Synopsis"),
310
+ nwOutline.TITLE: QT_TRANSLATE_NOOP("Constant", "Title"),
311
+ nwOutline.LEVEL: QT_TRANSLATE_NOOP("Constant", "Level"),
312
+ nwOutline.LABEL: QT_TRANSLATE_NOOP("Constant", "Document"),
313
+ nwOutline.LINE: QT_TRANSLATE_NOOP("Constant", "Line"),
314
+ nwOutline.STATUS: QT_TRANSLATE_NOOP("Constant", "Status"),
315
+ nwOutline.CCOUNT: QT_TRANSLATE_NOOP("Constant", "Chars"),
316
+ nwOutline.WCOUNT: QT_TRANSLATE_NOOP("Constant", "Words"),
317
+ nwOutline.PCOUNT: QT_TRANSLATE_NOOP("Constant", "Pars"),
318
+ nwOutline.POV: QT_TRANSLATE_NOOP("Constant", "POV"),
319
+ nwOutline.FOCUS: QT_TRANSLATE_NOOP("Constant", "Focus"),
320
+ nwOutline.CHAR: KEY_NAME[nwKeyWords.CHAR_KEY],
321
+ nwOutline.PLOT: KEY_NAME[nwKeyWords.PLOT_KEY],
322
+ nwOutline.WORLD: KEY_NAME[nwKeyWords.WORLD_KEY],
323
+ nwOutline.TIME: KEY_NAME[nwKeyWords.TIME_KEY],
324
+ nwOutline.OBJECT: KEY_NAME[nwKeyWords.OBJECT_KEY],
325
+ nwOutline.ENTITY: KEY_NAME[nwKeyWords.ENTITY_KEY],
326
+ nwOutline.CUSTOM: KEY_NAME[nwKeyWords.CUSTOM_KEY],
327
+ nwOutline.STORY: KEY_NAME[nwKeyWords.STORY_KEY],
328
+ nwOutline.MENTION: KEY_NAME[nwKeyWords.MENTION_KEY],
329
+ nwOutline.SYNOP: QT_TRANSLATE_NOOP("Constant", "Synopsis"),
330
+ }
331
+ STATS_NAME = {
332
+ nwStats.CHARS: QT_TRANSLATE_NOOP("Stats", "Characters"),
333
+ nwStats.CHARS_TEXT: QT_TRANSLATE_NOOP("Stats", "Characters in Text"),
334
+ nwStats.CHARS_TITLE: QT_TRANSLATE_NOOP("Stats", "Characters in Headings"),
335
+ nwStats.PARAGRAPHS: QT_TRANSLATE_NOOP("Stats", "Paragraphs"),
336
+ nwStats.TITLES: QT_TRANSLATE_NOOP("Stats", "Headings"),
337
+ nwStats.WCHARS_ALL: QT_TRANSLATE_NOOP("Stats", "Characters, No Spaces"),
338
+ nwStats.WCHARS_TEXT: QT_TRANSLATE_NOOP("Stats", "Characters in Text, No Spaces"),
339
+ nwStats.WCHARS_TITLE: QT_TRANSLATE_NOOP("Stats", "Characters in Headings, No Spaces"),
340
+ nwStats.WORDS: QT_TRANSLATE_NOOP("Stats", "Words"),
341
+ nwStats.WORDS_TEXT: QT_TRANSLATE_NOOP("Stats", "Words in Text"),
342
+ nwStats.WORDS_TITLE: QT_TRANSLATE_NOOP("Stats", "Words in Headings"),
244
343
  }
245
344
  BUILD_FMT = {
246
345
  nwBuildFmt.ODT: QT_TRANSLATE_NOOP("Constant", "Open Document (.odt)"),
247
346
  nwBuildFmt.FODT: QT_TRANSLATE_NOOP("Constant", "Flat Open Document (.fodt)"),
248
- nwBuildFmt.HTML: QT_TRANSLATE_NOOP("Constant", "novelWriter HTML (.html)"),
347
+ nwBuildFmt.DOCX: QT_TRANSLATE_NOOP("Constant", "Microsoft Word Document (.docx)"),
348
+ nwBuildFmt.HTML: QT_TRANSLATE_NOOP("Constant", "HTML 5 (.html)"),
249
349
  nwBuildFmt.NWD: QT_TRANSLATE_NOOP("Constant", "novelWriter Markup (.txt)"),
250
350
  nwBuildFmt.STD_MD: QT_TRANSLATE_NOOP("Constant", "Standard Markdown (.md)"),
251
351
  nwBuildFmt.EXT_MD: QT_TRANSLATE_NOOP("Constant", "Extended Markdown (.md)"),
252
- nwBuildFmt.J_HTML: QT_TRANSLATE_NOOP("Constant", "JSON + novelWriter HTML (.json)"),
352
+ nwBuildFmt.PDF: QT_TRANSLATE_NOOP("Constant", "Portable Document Format (.pdf)"),
353
+ nwBuildFmt.J_HTML: QT_TRANSLATE_NOOP("Constant", "JSON + HTML 5 (.json)"),
253
354
  nwBuildFmt.J_NWD: QT_TRANSLATE_NOOP("Constant", "JSON + novelWriter Markup (.json)"),
254
355
  }
255
356
  BUILD_EXT = {
256
357
  nwBuildFmt.ODT: ".odt",
257
358
  nwBuildFmt.FODT: ".fodt",
359
+ nwBuildFmt.DOCX: ".docx",
258
360
  nwBuildFmt.HTML: ".html",
259
361
  nwBuildFmt.NWD: ".txt",
260
362
  nwBuildFmt.STD_MD: ".md",
261
363
  nwBuildFmt.EXT_MD: ".md",
364
+ nwBuildFmt.PDF: ".pdf",
262
365
  nwBuildFmt.J_HTML: ".json",
263
366
  nwBuildFmt.J_NWD: ".json",
264
367
  }
265
368
  SHAPES_PLAIN = {
266
- nwStatusShape.SQUARE: QT_TRANSLATE_NOOP("Constant", "Square"),
267
- nwStatusShape.TRIANGLE: QT_TRANSLATE_NOOP("Constant", "Triangle"),
268
- nwStatusShape.NABLA: QT_TRANSLATE_NOOP("Constant", "Nabla"),
269
- nwStatusShape.DIAMOND: QT_TRANSLATE_NOOP("Constant", "Diamond"),
270
- nwStatusShape.PENTAGON: QT_TRANSLATE_NOOP("Constant", "Pentagon"),
271
- nwStatusShape.HEXAGON: QT_TRANSLATE_NOOP("Constant", "Hexagon"),
272
- nwStatusShape.STAR: QT_TRANSLATE_NOOP("Constant", "Star"),
273
- nwStatusShape.PACMAN: QT_TRANSLATE_NOOP("Constant", "Pacman"),
369
+ nwStatusShape.SQUARE: QT_TRANSLATE_NOOP("Shape", "Square"),
370
+ nwStatusShape.TRIANGLE: QT_TRANSLATE_NOOP("Shape", "Triangle"),
371
+ nwStatusShape.NABLA: QT_TRANSLATE_NOOP("Shape", "Nabla"),
372
+ nwStatusShape.DIAMOND: QT_TRANSLATE_NOOP("Shape", "Diamond"),
373
+ nwStatusShape.PENTAGON: QT_TRANSLATE_NOOP("Shape", "Pentagon"),
374
+ nwStatusShape.HEXAGON: QT_TRANSLATE_NOOP("Shape", "Hexagon"),
375
+ nwStatusShape.STAR: QT_TRANSLATE_NOOP("Shape", "Star"),
376
+ nwStatusShape.PACMAN: QT_TRANSLATE_NOOP("Shape", "Pacman"),
274
377
  }
275
378
  SHAPES_CIRCLE = {
276
- nwStatusShape.CIRCLE_Q: QT_TRANSLATE_NOOP("Constant", "1/4 Circle"),
277
- nwStatusShape.CIRCLE_H: QT_TRANSLATE_NOOP("Constant", "Half Circle"),
278
- nwStatusShape.CIRCLE_T: QT_TRANSLATE_NOOP("Constant", "3/4 Circle"),
279
- nwStatusShape.CIRCLE: QT_TRANSLATE_NOOP("Constant", "Full Circle"),
379
+ nwStatusShape.CIRCLE_Q: QT_TRANSLATE_NOOP("Shape", "1/4 Circle"),
380
+ nwStatusShape.CIRCLE_H: QT_TRANSLATE_NOOP("Shape", "Half Circle"),
381
+ nwStatusShape.CIRCLE_T: QT_TRANSLATE_NOOP("Shape", "3/4 Circle"),
382
+ nwStatusShape.CIRCLE: QT_TRANSLATE_NOOP("Shape", "Full Circle"),
280
383
  }
281
384
  SHAPES_BARS = {
282
- nwStatusShape.BARS_1: QT_TRANSLATE_NOOP("Constant", "1 Bar"),
283
- nwStatusShape.BARS_2: QT_TRANSLATE_NOOP("Constant", "2 Bars"),
284
- nwStatusShape.BARS_3: QT_TRANSLATE_NOOP("Constant", "3 Bars"),
285
- nwStatusShape.BARS_4: QT_TRANSLATE_NOOP("Constant", "4 Bars"),
385
+ nwStatusShape.BARS_1: QT_TRANSLATE_NOOP("Shape", "1 Bar"),
386
+ nwStatusShape.BARS_2: QT_TRANSLATE_NOOP("Shape", "2 Bars"),
387
+ nwStatusShape.BARS_3: QT_TRANSLATE_NOOP("Shape", "3 Bars"),
388
+ nwStatusShape.BARS_4: QT_TRANSLATE_NOOP("Shape", "4 Bars"),
286
389
  }
287
390
  SHAPES_BLOCKS = {
288
- nwStatusShape.BLOCK_1: QT_TRANSLATE_NOOP("Constant", "1 Block"),
289
- nwStatusShape.BLOCK_2: QT_TRANSLATE_NOOP("Constant", "2 Blocks"),
290
- nwStatusShape.BLOCK_3: QT_TRANSLATE_NOOP("Constant", "3 Blocks"),
291
- nwStatusShape.BLOCK_4: QT_TRANSLATE_NOOP("Constant", "4 Blocks"),
391
+ nwStatusShape.BLOCK_1: QT_TRANSLATE_NOOP("Shape", "1 Block"),
392
+ nwStatusShape.BLOCK_2: QT_TRANSLATE_NOOP("Shape", "2 Blocks"),
393
+ nwStatusShape.BLOCK_3: QT_TRANSLATE_NOOP("Shape", "3 Blocks"),
394
+ nwStatusShape.BLOCK_4: QT_TRANSLATE_NOOP("Shape", "4 Blocks"),
292
395
  }
293
396
  FILE_FILTERS = {
294
397
  "*.txt": QT_TRANSLATE_NOOP("Constant", "Text files"),
@@ -343,11 +446,11 @@ class nwHeadFmt:
343
446
  CHAR_POV, CHAR_FOCUS
344
447
  ]
345
448
 
346
- # ODT Document Page Header
347
- ODT_PROJECT = "{Project}"
348
- ODT_AUTHOR = "{Author}"
349
- ODT_PAGE = "{Page}"
350
- ODT_AUTO = "{Project} / {Author} / {Page}"
449
+ # Document Page Header
450
+ DOC_PROJECT = "{Project}"
451
+ DOC_AUTHOR = "{Author}"
452
+ DOC_PAGE = "{Page}"
453
+ DOC_AUTO = "{Project} / {Author} / {Page}"
351
454
 
352
455
 
353
456
  class nwQuotes:
@@ -449,6 +552,11 @@ class nwUnicode:
449
552
  U_LTRI = "\u25c0" # Left-pointing triangle
450
553
  U_LTRIS = "\u25c2" # Left-pointing triangle, small
451
554
 
555
+ # Special
556
+ U_UNKN = "\ufffd" # Unknown character
557
+ U_NAC1 = "\ufffe" # Not a character
558
+ U_NAC2 = "\uffff" # Not a character
559
+
452
560
  # HTML Equivalents
453
561
  # ================
454
562
 
@@ -7,7 +7,7 @@ Created: 2023-02-14 [2.1b1] BuildSettings
7
7
  Created: 2023-05-22 [2.1b1] BuildCollection
8
8
 
9
9
  This file is a part of novelWriter
10
- Copyright 2018–2024, Veronica Berglyd Olsen
10
+ Copyright (C) 2023 Veronica Berglyd Olsen and novelWriter contributors
11
11
 
12
12
  This program is free software: you can redistribute it and/or modify
13
13
  it under the terms of the GNU General Public License as published by
@@ -36,7 +36,7 @@ from PyQt5.QtCore import QT_TRANSLATE_NOOP, QCoreApplication
36
36
 
37
37
  from novelwriter import CONFIG
38
38
  from novelwriter.common import checkUuid, isHandle, jsonEncode
39
- from novelwriter.constants import nwFiles, nwHeadFmt
39
+ from novelwriter.constants import nwFiles, nwHeadFmt, nwStyles
40
40
  from novelwriter.core.project import NWProject
41
41
  from novelwriter.enum import nwBuildFmt
42
42
  from novelwriter.error import logException
@@ -45,29 +45,30 @@ logger = logging.getLogger(__name__)
45
45
 
46
46
  # The Settings Template
47
47
  # =====================
48
- # Each entry contains a tuple on the form:
49
- # (type, default, [min value, max value])
48
+ # Each entry contains a tuple on the form: (type, default)
50
49
 
51
- SETTINGS_TEMPLATE = {
50
+ SETTINGS_TEMPLATE: dict[str, tuple[type, str | int | float | bool]] = {
52
51
  "filter.includeNovel": (bool, True),
53
52
  "filter.includeNotes": (bool, False),
54
53
  "filter.includeInactive": (bool, False),
55
- "headings.fmtTitle": (str, nwHeadFmt.TITLE),
54
+ "headings.fmtPart": (str, nwHeadFmt.TITLE),
56
55
  "headings.fmtChapter": (str, nwHeadFmt.TITLE),
57
56
  "headings.fmtUnnumbered": (str, nwHeadFmt.TITLE),
58
57
  "headings.fmtScene": (str, "* * *"),
59
58
  "headings.fmtAltScene": (str, ""),
60
59
  "headings.fmtSection": (str, ""),
61
- "headings.hideTitle": (bool, False),
60
+ "headings.hidePart": (bool, False),
62
61
  "headings.hideChapter": (bool, False),
63
62
  "headings.hideUnnumbered": (bool, False),
64
63
  "headings.hideScene": (bool, False),
65
64
  "headings.hideAltScene": (bool, False),
66
65
  "headings.hideSection": (bool, True),
67
66
  "headings.centerTitle": (bool, True),
67
+ "headings.centerPart": (bool, True),
68
68
  "headings.centerChapter": (bool, False),
69
69
  "headings.centerScene": (bool, False),
70
- "headings.breakTitle": (bool, True),
70
+ "headings.breakTitle": (bool, False),
71
+ "headings.breakPart": (bool, True),
71
72
  "headings.breakChapter": (bool, True),
72
73
  "headings.breakScene": (bool, False),
73
74
  "text.includeSynopsis": (bool, False),
@@ -77,7 +78,7 @@ SETTINGS_TEMPLATE = {
77
78
  "text.ignoredKeywords": (str, ""),
78
79
  "text.addNoteHeadings": (bool, True),
79
80
  "format.textFont": (str, CONFIG.textFont.toString()),
80
- "format.lineHeight": (float, 1.15, 0.75, 3.0),
81
+ "format.lineHeight": (float, 1.15),
81
82
  "format.justifyText": (bool, False),
82
83
  "format.stripUnicode": (bool, False),
83
84
  "format.replaceTabs": (bool, False),
@@ -86,6 +87,20 @@ SETTINGS_TEMPLATE = {
86
87
  "format.firstLineIndent": (bool, False),
87
88
  "format.firstIndentWidth": (float, 1.4),
88
89
  "format.indentFirstPar": (bool, False),
90
+ "format.titleMarginT": (float, nwStyles.T_MARGIN["H0"][0]),
91
+ "format.titleMarginB": (float, nwStyles.T_MARGIN["H0"][1]),
92
+ "format.h1MarginT": (float, nwStyles.T_MARGIN["H1"][0]),
93
+ "format.h1MarginB": (float, nwStyles.T_MARGIN["H1"][1]),
94
+ "format.h2MarginT": (float, nwStyles.T_MARGIN["H2"][0]),
95
+ "format.h2MarginB": (float, nwStyles.T_MARGIN["H2"][1]),
96
+ "format.h3MarginT": (float, nwStyles.T_MARGIN["H3"][0]),
97
+ "format.h3MarginB": (float, nwStyles.T_MARGIN["H3"][1]),
98
+ "format.h4MarginT": (float, nwStyles.T_MARGIN["H4"][0]),
99
+ "format.h4MarginB": (float, nwStyles.T_MARGIN["H4"][1]),
100
+ "format.textMarginT": (float, nwStyles.T_MARGIN["TT"][0]),
101
+ "format.textMarginB": (float, nwStyles.T_MARGIN["TT"][1]),
102
+ "format.sepMarginT": (float, nwStyles.T_MARGIN["SP"][0]),
103
+ "format.sepMarginB": (float, nwStyles.T_MARGIN["SP"][1]),
89
104
  "format.pageUnit": (str, "cm"),
90
105
  "format.pageSize": (str, "A4"),
91
106
  "format.pageWidth": (float, 21.0),
@@ -94,9 +109,11 @@ SETTINGS_TEMPLATE = {
94
109
  "format.bottomMargin": (float, 2.0),
95
110
  "format.leftMargin": (float, 2.0),
96
111
  "format.rightMargin": (float, 2.0),
97
- "odt.addColours": (bool, True),
98
- "odt.pageHeader": (str, nwHeadFmt.ODT_AUTO),
99
- "odt.pageCountOffset": (int, 0),
112
+ "doc.pageHeader": (str, nwHeadFmt.DOC_AUTO),
113
+ "doc.pageCountOffset": (int, 0),
114
+ "doc.colorHeadings": (bool, True),
115
+ "doc.scaleHeadings": (bool, True),
116
+ "doc.boldHeadings": (bool, True),
100
117
  "html.addStyles": (bool, True),
101
118
  "html.preserveTabs": (bool, False),
102
119
  }
@@ -108,12 +125,16 @@ SETTINGS_LABELS = {
108
125
  "filter.includeInactive": QT_TRANSLATE_NOOP("Builds", "Inactive Documents"),
109
126
 
110
127
  "headings": QT_TRANSLATE_NOOP("Builds", "Headings"),
111
- "headings.fmtTitle": QT_TRANSLATE_NOOP("Builds", "Partition Format"),
128
+ "headings.fmtPart": QT_TRANSLATE_NOOP("Builds", "Partition Format"),
112
129
  "headings.fmtChapter": QT_TRANSLATE_NOOP("Builds", "Chapter Format"),
113
130
  "headings.fmtUnnumbered": QT_TRANSLATE_NOOP("Builds", "Unnumbered Format"),
114
131
  "headings.fmtScene": QT_TRANSLATE_NOOP("Builds", "Scene Format"),
115
132
  "headings.fmtAltScene": QT_TRANSLATE_NOOP("Builds", "Alt. Scene Format"),
116
133
  "headings.fmtSection": QT_TRANSLATE_NOOP("Builds", "Section Format"),
134
+ "headings.styleTitle": QT_TRANSLATE_NOOP("Builds", "Title Styling"),
135
+ "headings.stylePart": QT_TRANSLATE_NOOP("Builds", "Partition Styling"),
136
+ "headings.styleChapter": QT_TRANSLATE_NOOP("Builds", "Chapter Styling"),
137
+ "headings.styleScene": QT_TRANSLATE_NOOP("Builds", "Scene Styling"),
117
138
 
118
139
  "text.grpContent": QT_TRANSLATE_NOOP("Builds", "Text Content"),
119
140
  "text.includeSynopsis": QT_TRANSLATE_NOOP("Builds", "Include Synopsis"),
@@ -121,13 +142,11 @@ SETTINGS_LABELS = {
121
142
  "text.includeKeywords": QT_TRANSLATE_NOOP("Builds", "Include Keywords"),
122
143
  "text.includeBodyText": QT_TRANSLATE_NOOP("Builds", "Include Body Text"),
123
144
  "text.ignoredKeywords": QT_TRANSLATE_NOOP("Builds", "Ignore These Keywords"),
124
- "text.grpInsert": QT_TRANSLATE_NOOP("Builds", "Insert Content"),
125
145
  "text.addNoteHeadings": QT_TRANSLATE_NOOP("Builds", "Add Titles for Notes"),
126
146
 
127
147
  "format.grpFormat": QT_TRANSLATE_NOOP("Builds", "Text Format"),
128
148
  "format.textFont": QT_TRANSLATE_NOOP("Builds", "Text Font"),
129
149
  "format.lineHeight": QT_TRANSLATE_NOOP("Builds", "Line Height"),
130
- "format.grpOptions": QT_TRANSLATE_NOOP("Builds", "Text Options"),
131
150
  "format.justifyText": QT_TRANSLATE_NOOP("Builds", "Justify Text Margins"),
132
151
  "format.stripUnicode": QT_TRANSLATE_NOOP("Builds", "Replace Unicode Characters"),
133
152
  "format.replaceTabs": QT_TRANSLATE_NOOP("Builds", "Replace Tabs with Spaces"),
@@ -139,26 +158,38 @@ SETTINGS_LABELS = {
139
158
  "format.firstIndentWidth": QT_TRANSLATE_NOOP("Builds", "Indent Width"),
140
159
  "format.indentFirstPar": QT_TRANSLATE_NOOP("Builds", "Indent First Paragraph"),
141
160
 
161
+ "format.grpMargins": QT_TRANSLATE_NOOP("Builds", "Text Margins"),
162
+ "format.titleMargin": QT_TRANSLATE_NOOP("Builds", "Title and Partition"),
163
+ "format.h1Margin": QT_TRANSLATE_NOOP("Builds", "Heading 1 and Chapter"),
164
+ "format.h2Margin": QT_TRANSLATE_NOOP("Builds", "Heading 2 and Scene"),
165
+ "format.h3Margin": QT_TRANSLATE_NOOP("Builds", "Heading 3 and Section"),
166
+ "format.h4Margin": QT_TRANSLATE_NOOP("Builds", "Heading 4"),
167
+ "format.textMargin": QT_TRANSLATE_NOOP("Builds", "Text Paragraph"),
168
+ "format.sepMargin": QT_TRANSLATE_NOOP("Builds", "Scene Separator"),
169
+
142
170
  "format.grpPage": QT_TRANSLATE_NOOP("Builds", "Page Layout"),
143
171
  "format.pageUnit": QT_TRANSLATE_NOOP("Builds", "Unit"),
144
172
  "format.pageSize": QT_TRANSLATE_NOOP("Builds", "Page Size"),
145
- "format.pageWidth": QT_TRANSLATE_NOOP("Builds", "Page Width"),
146
- "format.pageHeight": QT_TRANSLATE_NOOP("Builds", "Page Height"),
147
- "format.topMargin": QT_TRANSLATE_NOOP("Builds", "Top Margin"),
148
- "format.bottomMargin": QT_TRANSLATE_NOOP("Builds", "Bottom Margin"),
149
- "format.leftMargin": QT_TRANSLATE_NOOP("Builds", "Left Margin"),
150
- "format.rightMargin": QT_TRANSLATE_NOOP("Builds", "Right Margin"),
151
-
152
- "odt": QT_TRANSLATE_NOOP("Builds", "Open Document (.odt)"),
153
- "odt.addColours": QT_TRANSLATE_NOOP("Builds", "Add Highlight Colours"),
154
- "odt.pageHeader": QT_TRANSLATE_NOOP("Builds", "Page Header"),
155
- "odt.pageCountOffset": QT_TRANSLATE_NOOP("Builds", "Page Counter Offset"),
156
-
157
- "html": QT_TRANSLATE_NOOP("Builds", "HTML (.html)"),
173
+ "format.pageMargins": QT_TRANSLATE_NOOP("Builds", "Page Margins"),
174
+
175
+ "doc": QT_TRANSLATE_NOOP("Builds", "Document Style"),
176
+ "doc.pageHeader": QT_TRANSLATE_NOOP("Builds", "Page Header"),
177
+ "doc.pageCountOffset": QT_TRANSLATE_NOOP("Builds", "Page Counter Offset"),
178
+ "doc.colorHeadings": QT_TRANSLATE_NOOP("Builds", "Add Colours to Headings"),
179
+ "doc.scaleHeadings": QT_TRANSLATE_NOOP("Builds", "Increase Size of Headings"),
180
+ "doc.boldHeadings": QT_TRANSLATE_NOOP("Builds", "Bold Headings"),
181
+
182
+ "html": QT_TRANSLATE_NOOP("Builds", "HTML Options"),
158
183
  "html.addStyles": QT_TRANSLATE_NOOP("Builds", "Add CSS Styles"),
159
184
  "html.preserveTabs": QT_TRANSLATE_NOOP("Builds", "Preserve Tab Characters"),
160
185
  }
161
186
 
187
+ RENAMED = {
188
+ "odt.addColours": "doc.addColours",
189
+ "odt.pageHeader": "doc.pageHeader",
190
+ "odt.pageCountOffset": "doc.pageCountOffset",
191
+ }
192
+
162
193
 
163
194
  class FilterMode(Enum):
164
195
  """The decision reason for an item in a filtered project."""
@@ -348,18 +379,12 @@ class BuildSettings:
348
379
  self._changed = True
349
380
  return
350
381
 
351
- def setValue(self, key: str, value: str | int | bool | float) -> bool:
382
+ def setValue(self, key: str, value: str | int | float | bool) -> None:
352
383
  """Set a specific value for a build setting."""
353
- if key not in SETTINGS_TEMPLATE:
354
- return False
355
- definition = SETTINGS_TEMPLATE[key]
356
- if not isinstance(value, definition[0]):
357
- return False
358
- if len(definition) == 4 and isinstance(value, (int, float)):
359
- value = min(max(value, definition[2]), definition[3])
360
- self._changed = value != self._settings[key]
361
- self._settings[key] = value
362
- return True
384
+ if (d := SETTINGS_TEMPLATE.get(key)) and len(d) == 2 and isinstance(value, d[0]):
385
+ self._changed = value != self._settings[key]
386
+ self._settings[key] = value
387
+ return
363
388
 
364
389
  ##
365
390
  # Methods
@@ -478,12 +503,21 @@ class BuildSettings:
478
503
  self._settings = {k: v[1] for k, v in SETTINGS_TEMPLATE.items()}
479
504
  if isinstance(settings, dict):
480
505
  for key, value in settings.items():
481
- self.setValue(key, value)
506
+ self.setValue(RENAMED.get(key, key), value)
482
507
 
483
508
  self._changed = False
484
509
 
485
510
  return
486
511
 
512
+ @classmethod
513
+ def duplicate(cls, source: BuildSettings) -> BuildSettings:
514
+ """Make a copy of another build."""
515
+ cls = BuildSettings()
516
+ cls.unpack(source.pack())
517
+ cls._uuid = str(uuid.uuid4())
518
+ cls._name = f"{source.name} 2"
519
+ return cls
520
+
487
521
 
488
522
  class BuildCollection:
489
523
  """Core: Build Collection Class