novelWriter 2.5.1__py3-none-any.whl → 2.6b1__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 (64) hide show
  1. {novelWriter-2.5.1.dist-info → novelWriter-2.6b1.dist-info}/METADATA +2 -1
  2. {novelWriter-2.5.1.dist-info → novelWriter-2.6b1.dist-info}/RECORD +61 -56
  3. {novelWriter-2.5.1.dist-info → novelWriter-2.6b1.dist-info}/WHEEL +1 -1
  4. novelwriter/__init__.py +3 -3
  5. novelwriter/assets/i18n/project_en_GB.json +1 -0
  6. novelwriter/assets/icons/typicons_dark/icons.conf +1 -0
  7. novelwriter/assets/icons/typicons_dark/mixed_copy.svg +4 -0
  8. novelwriter/assets/icons/typicons_light/icons.conf +1 -0
  9. novelwriter/assets/icons/typicons_light/mixed_copy.svg +4 -0
  10. novelwriter/assets/manual.pdf +0 -0
  11. novelwriter/assets/sample.zip +0 -0
  12. novelwriter/assets/themes/default_light.conf +2 -2
  13. novelwriter/common.py +63 -0
  14. novelwriter/config.py +10 -3
  15. novelwriter/constants.py +153 -60
  16. novelwriter/core/buildsettings.py +66 -39
  17. novelwriter/core/coretools.py +34 -22
  18. novelwriter/core/docbuild.py +130 -169
  19. novelwriter/core/index.py +29 -18
  20. novelwriter/core/item.py +2 -2
  21. novelwriter/core/options.py +4 -1
  22. novelwriter/core/spellcheck.py +9 -14
  23. novelwriter/dialogs/preferences.py +45 -32
  24. novelwriter/dialogs/projectsettings.py +3 -3
  25. novelwriter/enum.py +29 -23
  26. novelwriter/extensions/configlayout.py +24 -11
  27. novelwriter/extensions/modified.py +13 -1
  28. novelwriter/extensions/pagedsidebar.py +5 -5
  29. novelwriter/formats/shared.py +155 -0
  30. novelwriter/formats/todocx.py +1195 -0
  31. novelwriter/formats/tohtml.py +452 -0
  32. novelwriter/{core → formats}/tokenizer.py +483 -485
  33. novelwriter/formats/tomarkdown.py +217 -0
  34. novelwriter/{core → formats}/toodt.py +270 -320
  35. novelwriter/formats/toqdoc.py +436 -0
  36. novelwriter/formats/toraw.py +91 -0
  37. novelwriter/gui/doceditor.py +240 -193
  38. novelwriter/gui/dochighlight.py +96 -84
  39. novelwriter/gui/docviewer.py +56 -30
  40. novelwriter/gui/docviewerpanel.py +3 -3
  41. novelwriter/gui/editordocument.py +17 -2
  42. novelwriter/gui/itemdetails.py +8 -4
  43. novelwriter/gui/mainmenu.py +121 -60
  44. novelwriter/gui/noveltree.py +35 -37
  45. novelwriter/gui/outline.py +186 -238
  46. novelwriter/gui/projtree.py +142 -131
  47. novelwriter/gui/sidebar.py +7 -6
  48. novelwriter/gui/theme.py +5 -4
  49. novelwriter/guimain.py +43 -155
  50. novelwriter/shared.py +14 -4
  51. novelwriter/text/counting.py +2 -0
  52. novelwriter/text/patterns.py +155 -59
  53. novelwriter/tools/manusbuild.py +1 -1
  54. novelwriter/tools/manuscript.py +121 -78
  55. novelwriter/tools/manussettings.py +403 -260
  56. novelwriter/tools/welcome.py +4 -4
  57. novelwriter/tools/writingstats.py +3 -3
  58. novelwriter/types.py +16 -6
  59. novelwriter/core/tohtml.py +0 -530
  60. novelwriter/core/tomarkdown.py +0 -252
  61. novelwriter/core/toqdoc.py +0 -419
  62. {novelWriter-2.5.1.dist-info → novelWriter-2.6b1.dist-info}/LICENSE.md +0 -0
  63. {novelWriter-2.5.1.dist-info → novelWriter-2.6b1.dist-info}/entry_points.txt +0 -0
  64. {novelWriter-2.5.1.dist-info → novelWriter-2.6b1.dist-info}/top_level.txt +0 -0
novelwriter/constants.py CHANGED
@@ -60,11 +60,14 @@ class nwConst:
60
60
 
61
61
  class nwRegEx:
62
62
 
63
+ URL = r"https?://(?:www\.|(?!www))[\w/()@:%_\+-.~#?&=]+"
64
+ WORDS = r"\b[^\s\-\+\/–—\[\]:]+\b"
65
+ BREAK = r"(?i)(?<!\\)(\[br\]\n?)"
63
66
  FMT_EI = r"(?<![\w\\])(_)(?![\s_])(.+?)(?<![\s\\])(\1)(?!\w)"
64
67
  FMT_EB = r"(?<![\w\\])(\*{2})(?![\s\*])(.+?)(?<![\s\\])(\1)(?!\w)"
65
68
  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):)(.+?)(?<!\\)(\])"
69
+ FMT_SC = r"(?i)(?<!\\)(\[(?:b|/b|i|/i|s|/s|u|/u|m|/m|sup|/sup|sub|/sub|br)\])"
70
+ FMT_SV = r"(?i)(?<!\\)(\[(?:footnote|field):)(.+?)(?<!\\)(\])"
68
71
 
69
72
 
70
73
  class nwShortcode:
@@ -83,21 +86,46 @@ class nwShortcode:
83
86
  SUP_C = "[/sup]"
84
87
  SUB_O = "[sub]"
85
88
  SUB_C = "[/sub]"
89
+ BREAK = "[br]"
86
90
 
87
91
  FOOTNOTE_B = "[footnote:"
92
+ FIELD_B = "[field:"
88
93
 
89
94
  COMMENT_STYLES = {
90
95
  nwComment.FOOTNOTE: "[footnote:{0}]",
91
96
  nwComment.COMMENT: "[comment:{0}]",
92
97
  }
93
98
 
99
+ FIELD_VALUE = "[field:{0}]"
94
100
 
95
- class nwHeaders:
101
+
102
+ class nwStyles:
96
103
 
97
104
  H_VALID = ("H0", "H1", "H2", "H3", "H4")
98
105
  H_LEVEL = {"H0": 0, "H1": 1, "H2": 2, "H3": 3, "H4": 4}
99
106
  H_SIZES = {0: 2.50, 1: 2.00, 2: 1.75, 3: 1.50, 4: 1.25}
100
107
 
108
+ T_LABEL = {
109
+ "H0": QT_TRANSLATE_NOOP("Constant", "Title"),
110
+ "H1": QT_TRANSLATE_NOOP("Constant", "Heading 1 (Partition)"),
111
+ "H2": QT_TRANSLATE_NOOP("Constant", "Heading 2 (Chapter)"),
112
+ "H3": QT_TRANSLATE_NOOP("Constant", "Heading 3 (Scene)"),
113
+ "H4": QT_TRANSLATE_NOOP("Constant", "Heading 4 (Section)"),
114
+ "TT": QT_TRANSLATE_NOOP("Constant", "Text Paragraph"),
115
+ "SP": QT_TRANSLATE_NOOP("Constant", "Scene Separator"),
116
+ }
117
+ T_MARGIN = {
118
+ "H0": (1.42, 0.50), # Title margins
119
+ "H1": (1.42, 0.50), # Heading 1 margins
120
+ "H2": (1.67, 0.50), # Heading 2 margins
121
+ "H3": (1.17, 0.50), # Heading 3 margins
122
+ "H4": (1.17, 0.50), # Heading 4 margins
123
+ "TT": (0.00, 0.58), # Text margins
124
+ "SP": (1.17, 1.17), # Separator margins
125
+ "MT": (0.00, 0.58), # Meta margins
126
+ "FT": (1.42, 0.47), # Footnote margins
127
+ }
128
+
101
129
 
102
130
  class nwFiles:
103
131
 
@@ -121,34 +149,40 @@ class nwFiles:
121
149
 
122
150
  class nwKeyWords:
123
151
 
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"
152
+ TAG_KEY = "@tag"
153
+ POV_KEY = "@pov"
154
+ FOCUS_KEY = "@focus"
155
+ CHAR_KEY = "@char"
156
+ PLOT_KEY = "@plot"
157
+ TIME_KEY = "@time"
158
+ WORLD_KEY = "@location"
159
+ OBJECT_KEY = "@object"
160
+ ENTITY_KEY = "@entity"
161
+ CUSTOM_KEY = "@custom"
162
+ STORY_KEY = "@story"
163
+ MENTION_KEY = "@mention"
164
+
165
+ # Note: The order here affects the order of menu entries
166
+ ALL_KEYS = [
167
+ TAG_KEY, POV_KEY, FOCUS_KEY, CHAR_KEY, PLOT_KEY, TIME_KEY, WORLD_KEY,
168
+ OBJECT_KEY, ENTITY_KEY, CUSTOM_KEY, STORY_KEY, MENTION_KEY,
169
+ ]
134
170
 
135
171
  # 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
- }
172
+ VALID_KEYS = set(ALL_KEYS)
140
173
 
141
174
  # Map from Keys to Item Class
142
175
  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,
176
+ POV_KEY: nwItemClass.CHARACTER,
177
+ FOCUS_KEY: nwItemClass.CHARACTER,
178
+ CHAR_KEY: nwItemClass.CHARACTER,
179
+ PLOT_KEY: nwItemClass.PLOT,
180
+ TIME_KEY: nwItemClass.TIMELINE,
181
+ WORLD_KEY: nwItemClass.WORLD,
182
+ OBJECT_KEY: nwItemClass.OBJECT,
183
+ ENTITY_KEY: nwItemClass.ENTITY,
184
+ CUSTOM_KEY: nwItemClass.CUSTOM,
185
+ STORY_KEY: nwItemClass.NOVEL,
152
186
  }
153
187
 
154
188
 
@@ -165,6 +199,29 @@ class nwLists:
165
199
  ]
166
200
 
167
201
 
202
+ class nwStats:
203
+
204
+ CHARS_ALL = "allChars"
205
+ CHARS_TEXT = "textChars"
206
+ CHARS_TITLE = "titleChars"
207
+ PARAGRAPHS = "paragraphCount"
208
+ TITLES = "titleCount"
209
+ WCHARS_ALL = "allWordChars"
210
+ WCHARS_TEXT = "textWordChars"
211
+ WCHARS_TITLE = "titleWordChars"
212
+ WORDS_ALL = "allWords"
213
+ WORDS_TEXT = "textWords"
214
+ WORDS_TITLE = "titleWords"
215
+
216
+ # Note: The order here affects the order of menu entries
217
+ ALL_FIELDS = [
218
+ WORDS_ALL, WORDS_TEXT, WORDS_TITLE,
219
+ CHARS_ALL, CHARS_TEXT, CHARS_TITLE,
220
+ WCHARS_ALL, WCHARS_TEXT, WCHARS_TITLE,
221
+ PARAGRAPHS, TITLES,
222
+ ]
223
+
224
+
168
225
  class nwLabels:
169
226
 
170
227
  CLASS_NAME = {
@@ -212,53 +269,89 @@ class nwLabels:
212
269
  "note": QT_TRANSLATE_NOOP("Constant", "Project Note"),
213
270
  }
214
271
  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"),
272
+ nwKeyWords.TAG_KEY: QT_TRANSLATE_NOOP("Constant", "Tag"),
273
+ nwKeyWords.POV_KEY: QT_TRANSLATE_NOOP("Constant", "Point of View"),
274
+ nwKeyWords.FOCUS_KEY: QT_TRANSLATE_NOOP("Constant", "Focus"),
275
+ nwKeyWords.CHAR_KEY: QT_TRANSLATE_NOOP("Constant", "Characters"),
276
+ nwKeyWords.PLOT_KEY: QT_TRANSLATE_NOOP("Constant", "Plot"),
277
+ nwKeyWords.TIME_KEY: QT_TRANSLATE_NOOP("Constant", "Timeline"),
278
+ nwKeyWords.WORLD_KEY: QT_TRANSLATE_NOOP("Constant", "Locations"),
279
+ nwKeyWords.OBJECT_KEY: QT_TRANSLATE_NOOP("Constant", "Objects"),
280
+ nwKeyWords.ENTITY_KEY: QT_TRANSLATE_NOOP("Constant", "Entities"),
281
+ nwKeyWords.CUSTOM_KEY: QT_TRANSLATE_NOOP("Constant", "Custom"),
282
+ nwKeyWords.STORY_KEY: QT_TRANSLATE_NOOP("Constant", "Story"),
283
+ nwKeyWords.MENTION_KEY: QT_TRANSLATE_NOOP("Constant", "Mentions"),
284
+ }
285
+ KEY_SHORTCUT = {
286
+ nwKeyWords.TAG_KEY: "Ctrl+K, G",
287
+ nwKeyWords.POV_KEY: "Ctrl+K, V",
288
+ nwKeyWords.FOCUS_KEY: "Ctrl+K, F",
289
+ nwKeyWords.CHAR_KEY: "Ctrl+K, C",
290
+ nwKeyWords.PLOT_KEY: "Ctrl+K, P",
291
+ nwKeyWords.TIME_KEY: "Ctrl+K, T",
292
+ nwKeyWords.WORLD_KEY: "Ctrl+K, L",
293
+ nwKeyWords.OBJECT_KEY: "Ctrl+K, O",
294
+ nwKeyWords.ENTITY_KEY: "Ctrl+K, E",
295
+ nwKeyWords.CUSTOM_KEY: "Ctrl+K, X",
296
+ nwKeyWords.STORY_KEY: "Ctrl+K, N",
297
+ nwKeyWords.MENTION_KEY: "Ctrl+K, M",
225
298
  }
226
299
  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"),
300
+ nwOutline.TITLE: QT_TRANSLATE_NOOP("Constant", "Title"),
301
+ nwOutline.LEVEL: QT_TRANSLATE_NOOP("Constant", "Level"),
302
+ nwOutline.LABEL: QT_TRANSLATE_NOOP("Constant", "Document"),
303
+ nwOutline.LINE: QT_TRANSLATE_NOOP("Constant", "Line"),
304
+ nwOutline.STATUS: QT_TRANSLATE_NOOP("Constant", "Status"),
305
+ nwOutline.CCOUNT: QT_TRANSLATE_NOOP("Constant", "Chars"),
306
+ nwOutline.WCOUNT: QT_TRANSLATE_NOOP("Constant", "Words"),
307
+ nwOutline.PCOUNT: QT_TRANSLATE_NOOP("Constant", "Pars"),
308
+ nwOutline.POV: QT_TRANSLATE_NOOP("Constant", "POV"),
309
+ nwOutline.FOCUS: QT_TRANSLATE_NOOP("Constant", "Focus"),
310
+ nwOutline.CHAR: KEY_NAME[nwKeyWords.CHAR_KEY],
311
+ nwOutline.PLOT: KEY_NAME[nwKeyWords.PLOT_KEY],
312
+ nwOutline.WORLD: KEY_NAME[nwKeyWords.WORLD_KEY],
313
+ nwOutline.TIME: KEY_NAME[nwKeyWords.TIME_KEY],
314
+ nwOutline.OBJECT: KEY_NAME[nwKeyWords.OBJECT_KEY],
315
+ nwOutline.ENTITY: KEY_NAME[nwKeyWords.ENTITY_KEY],
316
+ nwOutline.CUSTOM: KEY_NAME[nwKeyWords.CUSTOM_KEY],
317
+ nwOutline.STORY: KEY_NAME[nwKeyWords.STORY_KEY],
318
+ nwOutline.MENTION: KEY_NAME[nwKeyWords.MENTION_KEY],
319
+ nwOutline.SYNOP: QT_TRANSLATE_NOOP("Constant", "Synopsis"),
320
+ }
321
+ STATS_NAME = {
322
+ nwStats.CHARS_ALL: QT_TRANSLATE_NOOP("Constant", "Characters"),
323
+ nwStats.CHARS_TEXT: QT_TRANSLATE_NOOP("Constant", "Characters in Text"),
324
+ nwStats.CHARS_TITLE: QT_TRANSLATE_NOOP("Constant", "Characters in Headings"),
325
+ nwStats.PARAGRAPHS: QT_TRANSLATE_NOOP("Constant", "Paragraphs"),
326
+ nwStats.TITLES: QT_TRANSLATE_NOOP("Constant", "Headings"),
327
+ nwStats.WCHARS_ALL: QT_TRANSLATE_NOOP("Constant", "Characters, No Spaces"),
328
+ nwStats.WCHARS_TEXT: QT_TRANSLATE_NOOP("Constant", "Characters in Text, No Spaces"),
329
+ nwStats.WCHARS_TITLE: QT_TRANSLATE_NOOP("Constant", "Characters in Headings, No Spaces"),
330
+ nwStats.WORDS_ALL: QT_TRANSLATE_NOOP("Constant", "Words"),
331
+ nwStats.WORDS_TEXT: QT_TRANSLATE_NOOP("Constant", "Words in Text"),
332
+ nwStats.WORDS_TITLE: QT_TRANSLATE_NOOP("Constant", "Words in Headings"),
244
333
  }
245
334
  BUILD_FMT = {
246
335
  nwBuildFmt.ODT: QT_TRANSLATE_NOOP("Constant", "Open Document (.odt)"),
247
336
  nwBuildFmt.FODT: QT_TRANSLATE_NOOP("Constant", "Flat Open Document (.fodt)"),
248
- nwBuildFmt.HTML: QT_TRANSLATE_NOOP("Constant", "novelWriter HTML (.html)"),
337
+ nwBuildFmt.DOCX: QT_TRANSLATE_NOOP("Constant", "Microsoft Word Document (.docx)"),
338
+ nwBuildFmt.HTML: QT_TRANSLATE_NOOP("Constant", "HTML 5 (.html)"),
249
339
  nwBuildFmt.NWD: QT_TRANSLATE_NOOP("Constant", "novelWriter Markup (.txt)"),
250
340
  nwBuildFmt.STD_MD: QT_TRANSLATE_NOOP("Constant", "Standard Markdown (.md)"),
251
341
  nwBuildFmt.EXT_MD: QT_TRANSLATE_NOOP("Constant", "Extended Markdown (.md)"),
252
- nwBuildFmt.J_HTML: QT_TRANSLATE_NOOP("Constant", "JSON + novelWriter HTML (.json)"),
342
+ nwBuildFmt.PDF: QT_TRANSLATE_NOOP("Constant", "Portable Document Format (.pdf)"),
343
+ nwBuildFmt.J_HTML: QT_TRANSLATE_NOOP("Constant", "JSON + HTML 5 (.json)"),
253
344
  nwBuildFmt.J_NWD: QT_TRANSLATE_NOOP("Constant", "JSON + novelWriter Markup (.json)"),
254
345
  }
255
346
  BUILD_EXT = {
256
347
  nwBuildFmt.ODT: ".odt",
257
348
  nwBuildFmt.FODT: ".fodt",
349
+ nwBuildFmt.DOCX: ".docx",
258
350
  nwBuildFmt.HTML: ".html",
259
351
  nwBuildFmt.NWD: ".txt",
260
352
  nwBuildFmt.STD_MD: ".md",
261
353
  nwBuildFmt.EXT_MD: ".md",
354
+ nwBuildFmt.PDF: ".pdf",
262
355
  nwBuildFmt.J_HTML: ".json",
263
356
  nwBuildFmt.J_NWD: ".json",
264
357
  }
@@ -343,11 +436,11 @@ class nwHeadFmt:
343
436
  CHAR_POV, CHAR_FOCUS
344
437
  ]
345
438
 
346
- # ODT Document Page Header
347
- ODT_PROJECT = "{Project}"
348
- ODT_AUTHOR = "{Author}"
349
- ODT_PAGE = "{Page}"
350
- ODT_AUTO = "{Project} / {Author} / {Page}"
439
+ # Document Page Header
440
+ DOC_PROJECT = "{Project}"
441
+ DOC_AUTHOR = "{Author}"
442
+ DOC_PAGE = "{Page}"
443
+ DOC_AUTO = "{Project} / {Author} / {Page}"
351
444
 
352
445
 
353
446
  class nwQuotes:
@@ -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,31 @@ 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
+
142
163
  "format.grpPage": QT_TRANSLATE_NOOP("Builds", "Page Layout"),
143
164
  "format.pageUnit": QT_TRANSLATE_NOOP("Builds", "Unit"),
144
165
  "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)"),
166
+ "format.pageMargins": QT_TRANSLATE_NOOP("Builds", "Page Margins"),
167
+
168
+ "doc": QT_TRANSLATE_NOOP("Builds", "Document Style"),
169
+ "doc.pageHeader": QT_TRANSLATE_NOOP("Builds", "Page Header"),
170
+ "doc.pageCountOffset": QT_TRANSLATE_NOOP("Builds", "Page Counter Offset"),
171
+ "doc.colorHeadings": QT_TRANSLATE_NOOP("Builds", "Add Colours to Headings"),
172
+ "doc.scaleHeadings": QT_TRANSLATE_NOOP("Builds", "Increase Size of Headings"),
173
+ "doc.boldHeadings": QT_TRANSLATE_NOOP("Builds", "Bold Headings"),
174
+
175
+ "html": QT_TRANSLATE_NOOP("Builds", "HTML Options"),
158
176
  "html.addStyles": QT_TRANSLATE_NOOP("Builds", "Add CSS Styles"),
159
177
  "html.preserveTabs": QT_TRANSLATE_NOOP("Builds", "Preserve Tab Characters"),
160
178
  }
161
179
 
180
+ RENAMED = {
181
+ "odt.addColours": "doc.addColours",
182
+ "odt.pageHeader": "doc.pageHeader",
183
+ "odt.pageCountOffset": "doc.pageCountOffset",
184
+ }
185
+
162
186
 
163
187
  class FilterMode(Enum):
164
188
  """The decision reason for an item in a filtered project."""
@@ -348,18 +372,12 @@ class BuildSettings:
348
372
  self._changed = True
349
373
  return
350
374
 
351
- def setValue(self, key: str, value: str | int | bool | float) -> bool:
375
+ def setValue(self, key: str, value: str | int | float | bool) -> None:
352
376
  """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
377
+ if (d := SETTINGS_TEMPLATE.get(key)) and len(d) == 2 and isinstance(value, d[0]):
378
+ self._changed = value != self._settings[key]
379
+ self._settings[key] = value
380
+ return
363
381
 
364
382
  ##
365
383
  # Methods
@@ -478,12 +496,21 @@ class BuildSettings:
478
496
  self._settings = {k: v[1] for k, v in SETTINGS_TEMPLATE.items()}
479
497
  if isinstance(settings, dict):
480
498
  for key, value in settings.items():
481
- self.setValue(key, value)
499
+ self.setValue(RENAMED.get(key, key), value)
482
500
 
483
501
  self._changed = False
484
502
 
485
503
  return
486
504
 
505
+ @classmethod
506
+ def duplicate(cls, source: BuildSettings) -> BuildSettings:
507
+ """Make a copy of another build."""
508
+ cls = BuildSettings()
509
+ cls.unpack(source.pack())
510
+ cls._uuid = str(uuid.uuid4())
511
+ cls._name = f"{source.name} 2"
512
+ return cls
513
+
487
514
 
488
515
  class BuildCollection:
489
516
  """Core: Build Collection Class
@@ -27,6 +27,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
27
27
  from __future__ import annotations
28
28
 
29
29
  import logging
30
+ import re
30
31
  import shutil
31
32
 
32
33
  from collections.abc import Iterable
@@ -34,11 +35,11 @@ from functools import partial
34
35
  from pathlib import Path
35
36
  from zipfile import ZipFile, is_zipfile
36
37
 
37
- from PyQt5.QtCore import QCoreApplication, QRegularExpression
38
+ from PyQt5.QtCore import QCoreApplication
38
39
 
39
40
  from novelwriter import CONFIG, SHARED
40
41
  from novelwriter.common import isHandle, minmax, simplified
41
- from novelwriter.constants import nwConst, nwFiles, nwItemClass
42
+ from novelwriter.constants import nwConst, nwFiles, nwItemClass, nwStats
42
43
  from novelwriter.core.item import NWItem
43
44
  from novelwriter.core.project import NWProject
44
45
  from novelwriter.core.storage import NWStorageCreate
@@ -297,8 +298,8 @@ class DocDuplicator:
297
298
  class DocSearch:
298
299
 
299
300
  def __init__(self) -> None:
300
- self._regEx = QRegularExpression()
301
- self.setCaseSensitive(False)
301
+ self._regEx = re.compile("")
302
+ self._opts = re.UNICODE | re.IGNORECASE
302
303
  self._words = False
303
304
  self._escape = True
304
305
  return
@@ -309,10 +310,9 @@ class DocSearch:
309
310
 
310
311
  def setCaseSensitive(self, state: bool) -> None:
311
312
  """Set the case sensitive search flag."""
312
- opts = QRegularExpression.PatternOption.UseUnicodePropertiesOption
313
+ self._opts = re.UNICODE
313
314
  if not state:
314
- opts |= QRegularExpression.PatternOption.CaseInsensitiveOption
315
- self._regEx.setPatternOptions(opts)
315
+ self._opts |= re.IGNORECASE
316
316
  return
317
317
 
318
318
  def setWholeWords(self, state: bool) -> None:
@@ -329,8 +329,8 @@ class DocSearch:
329
329
  self, project: NWProject, search: str
330
330
  ) -> Iterable[tuple[NWItem, list[tuple[int, int, str]], bool]]:
331
331
  """Iteratively search through documents in a project."""
332
- self._regEx.setPattern(self._buildPattern(search))
333
- logger.debug("Searching with pattern '%s'", self._regEx.pattern())
332
+ self._regEx = re.compile(self._buildPattern(search), self._opts)
333
+ logger.debug("Searching with pattern '%s'", self._regEx.pattern)
334
334
  storage = project.storage
335
335
  for item in project.tree:
336
336
  if item.isFileType():
@@ -340,14 +340,12 @@ class DocSearch:
340
340
 
341
341
  def searchText(self, text: str) -> tuple[list[tuple[int, int, str]], bool]:
342
342
  """Search a piece of text for RegEx matches."""
343
- rxItt = self._regEx.globalMatch(text)
344
343
  count = 0
345
344
  capped = False
346
345
  results = []
347
- while rxItt.hasNext():
348
- rxMatch = rxItt.next()
349
- pos = rxMatch.capturedStart()
350
- num = rxMatch.capturedLength()
346
+ for res in self._regEx.finditer(text):
347
+ pos = res.start(0)
348
+ num = len(res.group(0))
351
349
  lim = text[:pos].rfind("\n") + 1
352
350
  cut = text[lim:pos].rfind(" ") + lim + 1
353
351
  context = text[cut:cut+100].partition("\n")[0]
@@ -366,7 +364,7 @@ class DocSearch:
366
364
  def _buildPattern(self, search: str) -> str:
367
365
  """Build the search pattern string."""
368
366
  if self._escape:
369
- search = QRegularExpression.escape(search)
367
+ search = re.escape(search)
370
368
  if self._words:
371
369
  search = f"(?:^|\\b){search}(?:$|\\b)"
372
370
  return search
@@ -430,7 +428,6 @@ class ProjectBuilder:
430
428
 
431
429
  lblNewProject = self.tr("New Project")
432
430
  lblTitlePage = self.tr("Title Page")
433
- lblByAuthors = self.tr("By")
434
431
 
435
432
  # Settings
436
433
  project.data.setUuid(None)
@@ -443,14 +440,29 @@ class ProjectBuilder:
443
440
  # Add Root Folders
444
441
  hNovelRoot = project.newRoot(nwItemClass.NOVEL)
445
442
  hTitlePage = project.newFile(lblTitlePage, hNovelRoot)
446
- novelTitle = project.data.name
447
-
448
- titlePage = f"#! {novelTitle}\n\n"
449
- if project.data.author:
450
- titlePage += f">> {lblByAuthors} {project.data.author} <<\n\n"
451
443
 
444
+ # Generate Title Page
452
445
  aDoc = project.storage.getDocument(hTitlePage)
453
- aDoc.writeDocument(titlePage)
446
+ aDoc.writeDocument((
447
+ "{author}[br]\n"
448
+ "{address} 1[br]\n"
449
+ "{address} 2 <<\n"
450
+ "\n"
451
+ "[vspace:5]\n"
452
+ "\n"
453
+ "#! {title}\n"
454
+ "\n"
455
+ ">> **{by} {author}** <<\n"
456
+ "\n"
457
+ ">> {count}: [field:{field}] <<\n"
458
+ ).format(
459
+ author=project.data.author or "None",
460
+ address=self.tr("Address"),
461
+ title=project.data.name or "None",
462
+ by=self.tr("By"),
463
+ count=self.tr("Word Count"),
464
+ field=nwStats.WORDS_TEXT,
465
+ ))
454
466
 
455
467
  # Create a project structure based on selected root folders
456
468
  # and a number of chapters and scenes selected in the