novelWriter 2.2rc1__py3-none-any.whl → 2.3b1__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 (153) hide show
  1. {novelWriter-2.2rc1.dist-info → novelWriter-2.3b1.dist-info}/METADATA +1 -1
  2. {novelWriter-2.2rc1.dist-info → novelWriter-2.3b1.dist-info}/RECORD +141 -129
  3. {novelWriter-2.2rc1.dist-info → novelWriter-2.3b1.dist-info}/WHEEL +1 -1
  4. novelwriter/__init__.py +11 -6
  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_zh_CN.qm +0 -0
  13. novelwriter/assets/i18n/project_de_DE.json +1 -0
  14. novelwriter/assets/i18n/project_en_US.json +1 -0
  15. novelwriter/assets/i18n/project_es_419.json +11 -0
  16. novelwriter/assets/i18n/project_fr_FR.json +11 -0
  17. novelwriter/assets/i18n/project_it_IT.json +11 -0
  18. novelwriter/assets/i18n/project_ja_JP.json +2 -1
  19. novelwriter/assets/i18n/project_nb_NO.json +1 -0
  20. novelwriter/assets/i18n/project_zh_CN.json +11 -0
  21. novelwriter/assets/icons/typicons_dark/icons.conf +9 -2
  22. novelwriter/assets/icons/typicons_dark/mixed_import.svg +5 -0
  23. novelwriter/assets/icons/typicons_dark/nw_tb-bold-md.svg +4 -0
  24. novelwriter/assets/icons/typicons_dark/nw_tb-bold.svg +3 -1
  25. novelwriter/assets/icons/typicons_dark/nw_tb-italic-md.svg +4 -0
  26. novelwriter/assets/icons/typicons_dark/nw_tb-italic.svg +3 -1
  27. novelwriter/assets/icons/typicons_dark/nw_tb-strike-md.svg +4 -0
  28. novelwriter/assets/icons/typicons_dark/nw_tb-strike.svg +3 -1
  29. novelwriter/assets/icons/typicons_dark/nw_tb-subscript.svg +4 -2
  30. novelwriter/assets/icons/typicons_dark/nw_tb-superscript.svg +4 -2
  31. novelwriter/assets/icons/typicons_dark/nw_tb-underline.svg +4 -2
  32. novelwriter/assets/icons/typicons_dark/typ_document-add-col.svg +8 -0
  33. novelwriter/assets/icons/typicons_dark/typ_document-add.svg +4 -0
  34. novelwriter/assets/icons/typicons_dark/typ_document.svg +4 -0
  35. novelwriter/assets/icons/typicons_dark/typ_th-dot-more.svg +4 -0
  36. novelwriter/assets/icons/typicons_light/icons.conf +9 -2
  37. novelwriter/assets/icons/typicons_light/mixed_import.svg +5 -0
  38. novelwriter/assets/icons/typicons_light/nw_tb-bold-md.svg +4 -0
  39. novelwriter/assets/icons/typicons_light/nw_tb-bold.svg +3 -1
  40. novelwriter/assets/icons/typicons_light/nw_tb-italic-md.svg +4 -0
  41. novelwriter/assets/icons/typicons_light/nw_tb-italic.svg +3 -1
  42. novelwriter/assets/icons/typicons_light/nw_tb-strike-md.svg +4 -0
  43. novelwriter/assets/icons/typicons_light/nw_tb-strike.svg +3 -1
  44. novelwriter/assets/icons/typicons_light/nw_tb-subscript.svg +4 -2
  45. novelwriter/assets/icons/typicons_light/nw_tb-superscript.svg +4 -2
  46. novelwriter/assets/icons/typicons_light/nw_tb-underline.svg +4 -2
  47. novelwriter/assets/icons/typicons_light/typ_document-add-col.svg +8 -0
  48. novelwriter/assets/icons/typicons_light/typ_document-add.svg +4 -0
  49. novelwriter/assets/icons/typicons_light/typ_document.svg +4 -0
  50. novelwriter/assets/icons/typicons_light/typ_th-dot-more.svg +4 -0
  51. novelwriter/assets/images/novelwriter-text-dark.svg +4 -0
  52. novelwriter/assets/images/novelwriter-text-light.svg +4 -0
  53. novelwriter/assets/images/welcome-dark.jpg +0 -0
  54. novelwriter/assets/images/welcome-light.jpg +0 -0
  55. novelwriter/assets/manual.pdf +0 -0
  56. novelwriter/assets/sample.zip +0 -0
  57. novelwriter/assets/syntax/default_dark.conf +1 -0
  58. novelwriter/assets/syntax/default_light.conf +1 -0
  59. novelwriter/assets/syntax/grey_dark.conf +1 -0
  60. novelwriter/assets/syntax/grey_light.conf +1 -0
  61. novelwriter/assets/syntax/light_owl.conf +1 -0
  62. novelwriter/assets/syntax/night_owl.conf +1 -0
  63. novelwriter/assets/syntax/solarized_dark.conf +1 -0
  64. novelwriter/assets/syntax/solarized_light.conf +1 -0
  65. novelwriter/assets/syntax/tomorrow.conf +1 -0
  66. novelwriter/assets/syntax/tomorrow_night.conf +1 -0
  67. novelwriter/assets/syntax/tomorrow_night_blue.conf +1 -0
  68. novelwriter/assets/syntax/tomorrow_night_bright.conf +1 -0
  69. novelwriter/assets/syntax/tomorrow_night_eighties.conf +1 -0
  70. novelwriter/assets/text/credits_en.htm +4 -2
  71. novelwriter/assets/themes/default_dark.conf +2 -2
  72. novelwriter/assets/themes/default_light.conf +2 -2
  73. novelwriter/common.py +64 -66
  74. novelwriter/config.py +39 -44
  75. novelwriter/constants.py +39 -17
  76. novelwriter/core/buildsettings.py +8 -8
  77. novelwriter/core/coretools.py +194 -155
  78. novelwriter/core/docbuild.py +7 -4
  79. novelwriter/core/document.py +7 -7
  80. novelwriter/core/index.py +90 -57
  81. novelwriter/core/item.py +23 -5
  82. novelwriter/core/options.py +11 -10
  83. novelwriter/core/project.py +72 -47
  84. novelwriter/core/projectdata.py +3 -16
  85. novelwriter/core/projectxml.py +14 -42
  86. novelwriter/core/sessions.py +4 -3
  87. novelwriter/core/spellcheck.py +6 -4
  88. novelwriter/core/status.py +5 -4
  89. novelwriter/core/storage.py +179 -141
  90. novelwriter/core/tohtml.py +6 -4
  91. novelwriter/core/tokenizer.py +74 -46
  92. novelwriter/core/tomd.py +2 -2
  93. novelwriter/core/toodt.py +41 -31
  94. novelwriter/core/tree.py +5 -4
  95. novelwriter/dialogs/about.py +88 -179
  96. novelwriter/dialogs/docmerge.py +30 -20
  97. novelwriter/dialogs/docsplit.py +33 -22
  98. novelwriter/dialogs/editlabel.py +20 -8
  99. novelwriter/dialogs/preferences.py +562 -725
  100. novelwriter/dialogs/{projsettings.py → projectsettings.py} +301 -270
  101. novelwriter/dialogs/quotes.py +47 -36
  102. novelwriter/dialogs/wordlist.py +128 -59
  103. novelwriter/enum.py +25 -22
  104. novelwriter/error.py +2 -2
  105. novelwriter/extensions/circularprogress.py +12 -12
  106. novelwriter/extensions/configlayout.py +185 -146
  107. novelwriter/extensions/{wheeleventfilter.py → eventfilters.py} +15 -5
  108. novelwriter/extensions/modified.py +81 -0
  109. novelwriter/extensions/novelselector.py +27 -13
  110. novelwriter/extensions/pagedsidebar.py +15 -20
  111. novelwriter/extensions/simpleprogress.py +8 -9
  112. novelwriter/extensions/statusled.py +9 -9
  113. novelwriter/extensions/switch.py +32 -64
  114. novelwriter/extensions/switchbox.py +2 -7
  115. novelwriter/extensions/versioninfo.py +153 -0
  116. novelwriter/gui/doceditor.py +250 -214
  117. novelwriter/gui/dochighlight.py +66 -94
  118. novelwriter/gui/docviewer.py +71 -98
  119. novelwriter/gui/docviewerpanel.py +140 -47
  120. novelwriter/gui/editordocument.py +3 -3
  121. novelwriter/gui/itemdetails.py +9 -9
  122. novelwriter/gui/mainmenu.py +47 -46
  123. novelwriter/gui/noveltree.py +53 -61
  124. novelwriter/gui/outline.py +100 -76
  125. novelwriter/gui/projtree.py +193 -67
  126. novelwriter/gui/sidebar.py +9 -8
  127. novelwriter/gui/statusbar.py +49 -7
  128. novelwriter/gui/theme.py +65 -74
  129. novelwriter/guimain.py +173 -330
  130. novelwriter/shared.py +68 -30
  131. novelwriter/tools/dictionaries.py +7 -8
  132. novelwriter/tools/lipsum.py +34 -28
  133. novelwriter/tools/manusbuild.py +3 -4
  134. novelwriter/tools/manuscript.py +25 -32
  135. novelwriter/tools/manussettings.py +194 -225
  136. novelwriter/tools/noveldetails.py +525 -0
  137. novelwriter/tools/welcome.py +802 -0
  138. novelwriter/tools/writingstats.py +26 -13
  139. novelwriter/assets/icons/typicons_dark/nw_tb-markdown.svg +0 -8
  140. novelwriter/assets/icons/typicons_dark/nw_tb-shortcode.svg +0 -8
  141. novelwriter/assets/icons/typicons_light/nw_tb-markdown.svg +0 -8
  142. novelwriter/assets/icons/typicons_light/nw_tb-shortcode.svg +0 -8
  143. novelwriter/assets/images/wizard-back.jpg +0 -0
  144. novelwriter/assets/text/gplv3_en.htm +0 -641
  145. novelwriter/assets/text/release_notes.htm +0 -17
  146. novelwriter/dialogs/projdetails.py +0 -525
  147. novelwriter/dialogs/projload.py +0 -298
  148. novelwriter/dialogs/updates.py +0 -182
  149. novelwriter/extensions/pageddialog.py +0 -130
  150. novelwriter/tools/projwizard.py +0 -478
  151. {novelWriter-2.2rc1.dist-info → novelWriter-2.3b1.dist-info}/LICENSE.md +0 -0
  152. {novelWriter-2.2rc1.dist-info → novelWriter-2.3b1.dist-info}/entry_points.txt +0 -0
  153. {novelWriter-2.2rc1.dist-info → novelWriter-2.3b1.dist-info}/top_level.txt +0 -0
@@ -40,6 +40,7 @@ hidden = 114, 133, 183
40
40
  shortcode = 187, 218, 255
41
41
  keyword = 255, 157, 164
42
42
  value = 235, 187, 255
43
+ optional = 187, 218, 255
43
44
  spellcheckline = 255, 157, 164
44
45
  errorline = 209, 241, 169
45
46
  replacetag = 153, 255, 255
@@ -40,6 +40,7 @@ hidden = 150, 152, 150
40
40
  shortcode = 122, 166, 218
41
41
  keyword = 213, 78, 83
42
42
  value = 195, 151, 216
43
+ optional = 122, 166, 218
43
44
  spellcheckline = 213, 78, 83
44
45
  errorline = 185, 202, 74
45
46
  replacetag = 112, 192, 177
@@ -40,6 +40,7 @@ hidden = 153, 153, 153
40
40
  shortcode = 102, 153, 204
41
41
  keyword = 242, 119, 122
42
42
  value = 204, 153, 204
43
+ optional = 102, 153, 204
43
44
  spellcheckline = 242, 119, 122
44
45
  errorline = 153, 204, 153
45
46
  replacetag = 102, 204, 204
@@ -2,8 +2,6 @@
2
2
  <html>
3
3
  <body>
4
4
 
5
- <h2>Credits</h2>
6
-
7
5
  <h3>Main Developer</h3>
8
6
  <p>Veronica Berglyd Olsen (<a href="https://github.com/vkbo">@vkbo</a>)</p>
9
7
 
@@ -17,6 +15,10 @@
17
15
  <p>For other contributions, see the project's
18
16
  <a href="https://github.com/vkbo/novelWriter/graphs/contributors">Contributors</a> page.</p>
19
17
 
18
+ <h3>Artwork</h3>
19
+
20
+ <p>The artwork on the Welcome dialog was created by <a href="https://louisdurrant.art">Louis Durrant</a>.</p>
21
+
20
22
  <h3>Translations</h3>
21
23
 
22
24
  <p>The default language is English (UK) with English (US) as an option. These are the original
@@ -21,8 +21,8 @@ buttontext = 204, 204, 204
21
21
  brighttext = 62, 62, 62
22
22
  highlight = 44, 152, 247
23
23
  highlightedtext = 255, 255, 255
24
- link = 44, 152, 247
25
- linkvisited = 44, 152, 247
24
+ link = 102, 153, 204
25
+ linkvisited = 102, 153, 204
26
26
 
27
27
  [GUI]
28
28
  helptext = 164, 164, 164
@@ -21,8 +21,8 @@ buttontext = 0, 0, 0
21
21
  brighttext = 255, 255, 255
22
22
  highlight = 48, 135, 198
23
23
  highlightedtext = 255, 255, 255
24
- link = 0, 84, 255
25
- linkvisited = 0, 84, 255
24
+ link = 66, 113, 174
25
+ linkvisited = 66, 113, 174
26
26
 
27
27
  [GUI]
28
28
  helptext = 92, 92, 92
novelwriter/common.py CHANGED
@@ -3,10 +3,10 @@ novelWriter – Common Functions
3
3
  ==============================
4
4
 
5
5
  File History:
6
- Created: 2019-05-12 [0.1]
6
+ Created: 2019-05-12 [0.1.0]
7
7
 
8
8
  This file is a part of novelWriter
9
- Copyright 2018–2023, Veronica Berglyd Olsen
9
+ Copyright 2018–2024, Veronica Berglyd Olsen
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,7 +29,7 @@ import logging
29
29
  import unicodedata
30
30
  import xml.etree.ElementTree as ET
31
31
 
32
- from typing import Any, Literal
32
+ from typing import TYPE_CHECKING, Any, Literal
33
33
  from pathlib import Path
34
34
  from datetime import datetime
35
35
  from configparser import ConfigParser
@@ -38,18 +38,20 @@ from urllib.request import pathname2url
38
38
 
39
39
  from PyQt5.QtGui import QDesktopServices
40
40
  from PyQt5.QtCore import QCoreApplication, QUrl
41
- from PyQt5.QtWidgets import QWidget, qApp
42
41
 
43
42
  from novelwriter.enum import nwItemClass, nwItemType, nwItemLayout
44
43
  from novelwriter.error import logException
45
- from novelwriter.constants import nwConst, nwUnicode
44
+ from novelwriter.constants import nwConst, nwLabels, nwUnicode, trConst
45
+
46
+ if TYPE_CHECKING: # pragma: no cover
47
+ from typing import TypeGuard # Requires Python 3.10
46
48
 
47
49
  logger = logging.getLogger(__name__)
48
50
 
49
51
 
50
- # =============================================================================================== #
52
+ ##
51
53
  # Checker Functions
52
- # =============================================================================================== #
54
+ ##
53
55
 
54
56
  def checkStringNone(value: Any, default: str | None) -> str | None:
55
57
  """Check if a variable is a string or a None."""
@@ -105,15 +107,6 @@ def checkBool(value: Any, default: bool) -> bool:
105
107
  return default
106
108
 
107
109
 
108
- def checkHandle(value, default, allowNone=False):
109
- """Check if a value is a handle."""
110
- if allowNone and (value is None or value == "None"):
111
- return None
112
- if isHandle(value):
113
- return str(value)
114
- return default
115
-
116
-
117
110
  def checkUuid(value: Any, default: str) -> str:
118
111
  """Try to process a value as an UUID, or return a default."""
119
112
  try:
@@ -132,11 +125,11 @@ def checkPath(value: Any, default: Path) -> Path:
132
125
  return default
133
126
 
134
127
 
135
- # =============================================================================================== #
128
+ ##
136
129
  # Validator Functions
137
- # =============================================================================================== #
130
+ ##
138
131
 
139
- def isHandle(value: Any) -> bool:
132
+ def isHandle(value: Any) -> TypeGuard[str]:
140
133
  """Check if a string is a valid novelWriter handle.
141
134
  Note: This is case sensitive. Must be lower case!
142
135
  """
@@ -150,7 +143,7 @@ def isHandle(value: Any) -> bool:
150
143
  return True
151
144
 
152
145
 
153
- def isTitleTag(value: Any) -> bool:
146
+ def isTitleTag(value: Any) -> TypeGuard[str]:
154
147
  """Check if a string is a valid title tag string."""
155
148
  if not isinstance(value, str):
156
149
  return False
@@ -164,19 +157,19 @@ def isTitleTag(value: Any) -> bool:
164
157
  return True
165
158
 
166
159
 
167
- def isItemClass(value: str) -> bool:
160
+ def isItemClass(value: Any) -> TypeGuard[str]:
168
161
  """Check if a string is a valid nwItemClass identifier."""
169
- return value in nwItemClass.__members__
162
+ return isinstance(value, str) and value in nwItemClass.__members__
170
163
 
171
164
 
172
- def isItemType(value: str) -> bool:
165
+ def isItemType(value: Any) -> TypeGuard[str]:
173
166
  """Check if a string is a valid nwItemType identifier."""
174
- return value in nwItemType.__members__
167
+ return isinstance(value, str) and value in nwItemType.__members__
175
168
 
176
169
 
177
- def isItemLayout(value: str) -> bool:
170
+ def isItemLayout(value: Any) -> TypeGuard[str]:
178
171
  """Check if a string is a valid nwItemLayout identifier."""
179
- return value in nwItemLayout.__members__
172
+ return isinstance(value, str) and value in nwItemLayout.__members__
180
173
 
181
174
 
182
175
  def hexToInt(value: Any, default: int = 0) -> int:
@@ -190,8 +183,7 @@ def hexToInt(value: Any, default: int = 0) -> int:
190
183
 
191
184
 
192
185
  def minmax(value: int, minVal: int, maxVal: int) -> int:
193
- """Make sure an integer is between min and max value (inclusive).
194
- """
186
+ """Check that an value is between min and max value (inclusive)."""
195
187
  return min(maxVal, max(minVal, value))
196
188
 
197
189
 
@@ -205,26 +197,26 @@ def checkIntTuple(value: int, valid: tuple | list | set, default: int) -> int:
205
197
  return default
206
198
 
207
199
 
208
- # =============================================================================================== #
200
+ ##
209
201
  # Formatting Functions
210
- # =============================================================================================== #
202
+ ##
211
203
 
212
204
  def formatInt(value: int) -> str:
213
205
  """Formats an integer with k, M, G etc."""
214
206
  if not isinstance(value, int):
215
207
  return "ERR"
216
208
 
217
- theVal = float(value)
218
- if theVal > 1000.0:
209
+ fVal = float(value)
210
+ if fVal > 1000.0:
219
211
  for pF in ["k", "M", "G", "T", "P", "E"]:
220
- theVal /= 1000.0
221
- if theVal < 1000.0:
222
- if theVal < 10.0:
223
- return f"{theVal:4.2f}{nwUnicode.U_THSP}{pF}"
224
- elif theVal < 100.0:
225
- return f"{theVal:4.1f}{nwUnicode.U_THSP}{pF}"
212
+ fVal /= 1000.0
213
+ if fVal < 1000.0:
214
+ if fVal < 10.0:
215
+ return f"{fVal:4.2f}{nwUnicode.U_THSP}{pF}"
216
+ elif fVal < 100.0:
217
+ return f"{fVal:4.1f}{nwUnicode.U_THSP}{pF}"
226
218
  else:
227
- return f"{theVal:3.0f}{nwUnicode.U_THSP}{pF}"
219
+ return f"{fVal:3.0f}{nwUnicode.U_THSP}{pF}"
228
220
 
229
221
  return str(value)
230
222
 
@@ -251,9 +243,27 @@ def formatTime(t: int) -> str:
251
243
  return "ERROR"
252
244
 
253
245
 
254
- # =============================================================================================== #
246
+ def formatVersion(value: str) -> str:
247
+ """Format a version number into a more human readable form."""
248
+ return value.lower().replace("a", " Alpha ").replace("b", " Beta ").replace("rc", " RC ")
249
+
250
+
251
+ def formatFileFilter(extensions: list[str | tuple[str, str]]) -> str:
252
+ """Format a list of extensions, or extension + label pairs into a
253
+ QFileDialog extensions filter.
254
+ """
255
+ result = []
256
+ for ext in extensions:
257
+ if isinstance(ext, str):
258
+ result.append(f"{trConst(nwLabels.FILE_FILTERS.get(ext))} ({ext})")
259
+ elif isinstance(ext, tuple) and len(ext) == 2:
260
+ result.append(f"{ext[0]} ({ext[1]})")
261
+ return ";;".join(result)
262
+
263
+
264
+ ##
255
265
  # String Functions
256
- # =============================================================================================== #
266
+ ##
257
267
 
258
268
  def simplified(text: str) -> str:
259
269
  """Take a string and strip leading and trailing whitespaces, and
@@ -271,22 +281,22 @@ def transferCase(source: str, target: str) -> str:
271
281
  """Transfers the case of the source word to the target word. This
272
282
  will consider all upper or lower, and first char capitalisation.
273
283
  """
274
- theResult = target
284
+ result = target
275
285
 
276
286
  if not isinstance(source, str) or not isinstance(target, str):
277
- return theResult
287
+ return result
278
288
  if len(target) < 1 or len(source) < 1:
279
- return theResult
289
+ return result
280
290
 
281
291
  if source.istitle():
282
- theResult = target.title()
292
+ result = target.title()
283
293
 
284
294
  if source.isupper():
285
- theResult = target.upper()
295
+ result = target.upper()
286
296
  elif source.islower():
287
- theResult = target.lower()
297
+ result = target.lower()
288
298
 
289
- return theResult
299
+ return result
290
300
 
291
301
 
292
302
  def fuzzyTime(seconds: int) -> str:
@@ -372,9 +382,9 @@ def numberToRoman(value: int, toLower: bool = False) -> str:
372
382
  return roman.lower() if toLower else roman
373
383
 
374
384
 
375
- # =============================================================================================== #
385
+ ##
376
386
  # Encoder Functions
377
- # =============================================================================================== #
387
+ ##
378
388
 
379
389
  def jsonEncode(data: dict | list | tuple, n: int = 0, nmax: int = 0) -> str:
380
390
  """Encode a dictionary, list or tuple as a json object or array, and
@@ -464,9 +474,9 @@ def xmlIndent(tree: ET.Element | ET.ElementTree) -> None:
464
474
  return
465
475
 
466
476
 
467
- # =============================================================================================== #
477
+ ##
468
478
  # File and File System Functions
469
- # =============================================================================================== #
479
+ ##
470
480
 
471
481
  def readTextFile(path: str | Path) -> str:
472
482
  """Read the content of a text file in a robust manner."""
@@ -508,21 +518,9 @@ def openExternalPath(path: Path) -> bool:
508
518
  return False
509
519
 
510
520
 
511
- # =============================================================================================== #
512
- # Other Functions
513
- # =============================================================================================== #
514
-
515
- def getGuiItem(objName: str) -> QWidget | None:
516
- """Returns a QtWidget based on its objectName."""
517
- for qWidget in qApp.topLevelWidgets():
518
- if qWidget.objectName() == objName:
519
- return qWidget
520
- return None
521
-
522
-
523
- # =============================================================================================== #
521
+ ##
524
522
  # Classes
525
- # =============================================================================================== #
523
+ ##
526
524
 
527
525
  class NWConfigParser(ConfigParser):
528
526
  """Common: Adapted Config Parser
novelwriter/config.py CHANGED
@@ -7,7 +7,7 @@ Created: 2018-09-22 [0.0.1] Config
7
7
  Created: 2022-11-09 [2.0rc2] RecentProjects
8
8
 
9
9
  This file is a part of novelWriter
10
- Copyright 2018–2023, Veronica Berglyd Olsen
10
+ Copyright 2018–2024, Veronica Berglyd Olsen
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
@@ -110,8 +110,8 @@ class Config:
110
110
 
111
111
  # Size Settings
112
112
  self._mainWinSize = [1200, 650] # Last size of the main GUI window
113
+ self._welcomeSize = [800, 550] # Last size of the welcome window
113
114
  self._prefsWinSize = [700, 615] # Last size of the Preferences dialog
114
- self._projLoadCols = [280, 60, 160] # Last columns widths of the Project Load dialog
115
115
  self._mainPanePos = [300, 800] # Last position of the main window splitter
116
116
  self._viewPanePos = [500, 150] # Last position of the document viewer splitter
117
117
  self._outlnPanePos = [500, 150] # Last position of the outline panel splitter
@@ -150,9 +150,6 @@ class Config:
150
150
  self.autoScrollPos = 30 # Start point for typewriter-like scrolling
151
151
  self.scrollPastEnd = True # Scroll past end of document, and centre cursor
152
152
 
153
- self.wordCountTimer = 5.0 # Interval for word count update in seconds
154
- self.incNotesWCount = True # The status bar word count includes notes
155
-
156
153
  self.highlightQuotes = True # Highlight text in quotes
157
154
  self.allowOpenSQuote = False # Allow open-ended single quotes
158
155
  self.allowOpenDQuote = True # Allow open-ended double quotes
@@ -160,6 +157,7 @@ class Config:
160
157
 
161
158
  self.stopWhenIdle = True # Stop the status bar clock when the user is idle
162
159
  self.userIdleTime = 300 # Time of inactivity to consider user idle
160
+ self.incNotesWCount = True # The status bar word count includes notes
163
161
 
164
162
  # User-Selected Symbol Settings
165
163
  self.fmtApostrophe = nwUnicode.U_RSQUO
@@ -225,7 +223,8 @@ class Config:
225
223
  # Other System Info
226
224
  self.hostName = QSysInfo.machineHostName()
227
225
  self.kernelVer = QSysInfo.kernelVersion()
228
- self.isDebug = False
226
+ self.isDebug = False # True if running in debug mode
227
+ self.memInfo = False # True if displaying mem info in status bar
229
228
 
230
229
  # Packages
231
230
  self.hasEnchant = False # The pyenchant package
@@ -249,12 +248,12 @@ class Config:
249
248
  return [int(x*self.guiScale) for x in self._mainWinSize]
250
249
 
251
250
  @property
252
- def preferencesWinSize(self) -> list[int]:
253
- return [int(x*self.guiScale) for x in self._prefsWinSize]
251
+ def welcomeWinSize(self) -> list[int]:
252
+ return [int(x*self.guiScale) for x in self._welcomeSize]
254
253
 
255
254
  @property
256
- def projLoadColWidths(self) -> list[int]:
257
- return [int(x*self.guiScale) for x in self._projLoadCols]
255
+ def preferencesWinSize(self) -> list[int]:
256
+ return [int(x*self.guiScale) for x in self._prefsWinSize]
258
257
 
259
258
  @property
260
259
  def mainPanePos(self) -> list[int]:
@@ -305,17 +304,18 @@ class Config:
305
304
  self._mainWinSize[1] = height
306
305
  return
307
306
 
307
+ def setWelcomeWinSize(self, width: int, height: int) -> None:
308
+ """Set the size of the Preferences dialog window."""
309
+ self._welcomeSize[0] = int(width/self.guiScale)
310
+ self._welcomeSize[1] = int(height/self.guiScale)
311
+ return
312
+
308
313
  def setPreferencesWinSize(self, width: int, height: int) -> None:
309
314
  """Set the size of the Preferences dialog window."""
310
315
  self._prefsWinSize[0] = int(width/self.guiScale)
311
316
  self._prefsWinSize[1] = int(height/self.guiScale)
312
317
  return
313
318
 
314
- def setProjLoadColWidths(self, widths: list[int]) -> None:
315
- """Set the column widths of the Load Project dialog."""
316
- self._projLoadCols = [int(x/self.guiScale) for x in widths]
317
- return
318
-
319
319
  def setMainPanePos(self, pos: list[int]) -> None:
320
320
  """Set the position of the main GUI splitter."""
321
321
  self._mainPanePos = [int(x/self.guiScale) for x in pos]
@@ -409,10 +409,10 @@ class Config:
409
409
  """Compile and return error messages from the initialisation of
410
410
  the Config class, and clear the error buffer.
411
411
  """
412
- errMessage = "<br>".join(self._errData)
412
+ message = "<br>".join(self._errData)
413
413
  self._hasError = False
414
414
  self._errData = []
415
- return errMessage
415
+ return message
416
416
 
417
417
  def listLanguages(self, lngSet: int) -> list[tuple[str, str]]:
418
418
  """List localisation files in the i18n folder. The default GUI
@@ -485,7 +485,6 @@ class Config:
485
485
 
486
486
  self._recentObj.loadCache()
487
487
  self._checkOptionalPackages()
488
- self.isDebug = logger.getEffectiveLevel() == logging.DEBUG
489
488
 
490
489
  logger.debug("Config instance initialised")
491
490
 
@@ -545,8 +544,8 @@ class Config:
545
544
  # Sizes
546
545
  sec = "Sizes"
547
546
  self._mainWinSize = conf.rdIntList(sec, "mainwindow", self._mainWinSize)
547
+ self._welcomeSize = conf.rdIntList(sec, "welcome", self._welcomeSize)
548
548
  self._prefsWinSize = conf.rdIntList(sec, "preferences", self._prefsWinSize)
549
- self._projLoadCols = conf.rdIntList(sec, "projloadcols", self._projLoadCols)
550
549
  self._mainPanePos = conf.rdIntList(sec, "mainpane", self._mainPanePos)
551
550
  self._viewPanePos = conf.rdIntList(sec, "viewpane", self._viewPanePos)
552
551
  self._outlnPanePos = conf.rdIntList(sec, "outlinepane", self._outlnPanePos)
@@ -590,7 +589,6 @@ class Config:
590
589
  self.showTabsNSpaces = conf.rdBool(sec, "showtabsnspaces", self.showTabsNSpaces)
591
590
  self.showLineEndings = conf.rdBool(sec, "showlineendings", self.showLineEndings)
592
591
  self.showMultiSpaces = conf.rdBool(sec, "showmultispaces", self.showMultiSpaces)
593
- self.wordCountTimer = conf.rdFlt(sec, "wordcounttimer", self.wordCountTimer)
594
592
  self.incNotesWCount = conf.rdBool(sec, "incnoteswcount", self.incNotesWCount)
595
593
  self.showFullPath = conf.rdBool(sec, "showfullpath", self.showFullPath)
596
594
  self.highlightQuotes = conf.rdBool(sec, "highlightquotes", self.highlightQuotes)
@@ -635,7 +633,7 @@ class Config:
635
633
  conf = NWConfigParser()
636
634
 
637
635
  conf["Meta"] = {
638
- "timestamp": formatTimeStamp(time()),
636
+ "timestamp": formatTimeStamp(time()),
639
637
  }
640
638
 
641
639
  conf["Main"] = {
@@ -651,12 +649,12 @@ class Config:
651
649
  }
652
650
 
653
651
  conf["Sizes"] = {
654
- "mainwindow": self._packList(self._mainWinSize),
655
- "preferences": self._packList(self._prefsWinSize),
656
- "projloadcols": self._packList(self._projLoadCols),
657
- "mainpane": self._packList(self._mainPanePos),
658
- "viewpane": self._packList(self._viewPanePos),
659
- "outlinepane": self._packList(self._outlnPanePos),
652
+ "mainwindow": self._packList(self._mainWinSize),
653
+ "welcome": self._packList(self._welcomeSize),
654
+ "preferences": self._packList(self._prefsWinSize),
655
+ "mainpane": self._packList(self._mainPanePos),
656
+ "viewpane": self._packList(self._viewPanePos),
657
+ "outlinepane": self._packList(self._outlnPanePos),
660
658
  }
661
659
 
662
660
  conf["Project"] = {
@@ -697,7 +695,6 @@ class Config:
697
695
  "showtabsnspaces": str(self.showTabsNSpaces),
698
696
  "showlineendings": str(self.showLineEndings),
699
697
  "showmultispaces": str(self.showMultiSpaces),
700
- "wordcounttimer": str(self.wordCountTimer),
701
698
  "incnoteswcount": str(self.incNotesWCount),
702
699
  "showfullpath": str(self.showFullPath),
703
700
  "highlightquotes": str(self.highlightQuotes),
@@ -774,22 +771,20 @@ class RecentProjects:
774
771
  self._data = {}
775
772
 
776
773
  cacheFile = self._conf.dataPath(nwFiles.RECENT_FILE)
777
- if not cacheFile.is_file():
778
- return True
779
-
780
- try:
781
- with open(cacheFile, mode="r", encoding="utf-8") as inFile:
782
- theData = json.load(inFile)
783
- for projPath, theEntry in theData.items():
784
- self._data[projPath] = {
785
- "title": theEntry.get("title", ""),
786
- "words": theEntry.get("words", 0),
787
- "time": theEntry.get("time", 0),
788
- }
789
- except Exception:
790
- logger.error("Could not load recent project cache")
791
- logException()
792
- return False
774
+ if cacheFile.is_file():
775
+ try:
776
+ with open(cacheFile, mode="r", encoding="utf-8") as inFile:
777
+ data = json.load(inFile)
778
+ for path, entry in data.items():
779
+ self._data[path] = {
780
+ "title": entry.get("title", ""),
781
+ "words": entry.get("words", 0),
782
+ "time": entry.get("time", 0),
783
+ }
784
+ except Exception:
785
+ logger.error("Could not load recent project cache")
786
+ logException()
787
+ return False
793
788
 
794
789
  return True
795
790
 
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–2023, Veronica Berglyd Olsen
9
+ Copyright 2018–2024, Veronica Berglyd Olsen
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
@@ -43,12 +43,12 @@ class nwConst:
43
43
  FMT_DSTAMP = "%Y-%m-%d" # Date only format
44
44
 
45
45
  # URLs
46
- URL_WEB = "https://novelwriter.io"
47
- URL_DOCS = "https://docs.novelwriter.io"
48
- URL_CODE = "https://github.com/vkbo/novelWriter"
49
- URL_REPORT = "https://github.com/vkbo/novelWriter/issues"
50
- URL_HELP = "https://github.com/vkbo/novelWriter/discussions"
51
- URL_RELEASE = "https://github.com/vkbo/novelWriter/releases/latest"
46
+ URL_WEB = "https://novelwriter.io"
47
+ URL_DOCS = "https://docs.novelwriter.io"
48
+ URL_RELEASES = "https://releases.novelwriter.io"
49
+ URL_CODE = "https://github.com/vkbo/novelWriter"
50
+ URL_REPORT = "https://github.com/vkbo/novelWriter/issues"
51
+ URL_HELP = "https://github.com/vkbo/novelWriter/discussions"
52
52
 
53
53
  # Requests
54
54
  USER_AGENT = "Mozilla/5.0 (compatible; novelWriter (Python))"
@@ -56,6 +56,9 @@ class nwConst:
56
56
  # Gui Settings
57
57
  STATUS_MSG_TIMEOUT = 15000 # milliseconds
58
58
 
59
+ # Dialogs
60
+ DLG_FINISHED = 2
61
+
59
62
  # END Class nwConst
60
63
 
61
64
 
@@ -107,7 +110,6 @@ class nwFiles:
107
110
 
108
111
  # Project Root Files
109
112
  PROJ_FILE = "nwProject.nwx"
110
- PROJ_BACKUP = "nwProject.bak"
111
113
  PROJ_LOCK = "nwProject.lock"
112
114
  TOC_TXT = "ToC.txt"
113
115
 
@@ -184,6 +186,7 @@ class nwLabels:
184
186
  nwItemClass.ENTITY: QT_TRANSLATE_NOOP("Constant", "Entities"),
185
187
  nwItemClass.CUSTOM: QT_TRANSLATE_NOOP("Constant", "Custom"),
186
188
  nwItemClass.ARCHIVE: QT_TRANSLATE_NOOP("Constant", "Archive"),
189
+ nwItemClass.TEMPLATE: QT_TRANSLATE_NOOP("Constant", "Templates"),
187
190
  nwItemClass.TRASH: QT_TRANSLATE_NOOP("Constant", "Trash"),
188
191
  }
189
192
  CLASS_ICON = {
@@ -197,6 +200,7 @@ class nwLabels:
197
200
  nwItemClass.ENTITY: "cls_entity",
198
201
  nwItemClass.CUSTOM: "cls_custom",
199
202
  nwItemClass.ARCHIVE: "cls_archive",
203
+ nwItemClass.TEMPLATE: "cls_template",
200
204
  nwItemClass.TRASH: "cls_trash",
201
205
  }
202
206
  LAYOUT_NAME = {
@@ -266,6 +270,13 @@ class nwLabels:
266
270
  nwBuildFmt.J_HTML: ".json",
267
271
  nwBuildFmt.J_NWD: ".json",
268
272
  }
273
+ FILE_FILTERS = {
274
+ "*.txt": QT_TRANSLATE_NOOP("Constant", "Text files"),
275
+ "*.md": QT_TRANSLATE_NOOP("Constant", "Markdown files"),
276
+ "*.nwd": QT_TRANSLATE_NOOP("Constant", "novelWriter files"),
277
+ "*.csv": QT_TRANSLATE_NOOP("Constant", "CSV files"),
278
+ "*": QT_TRANSLATE_NOOP("Constant", "All files"),
279
+ }
269
280
  UNIT_NAME = {
270
281
  "mm": QT_TRANSLATE_NOOP("Constant", "Millimetres"),
271
282
  "cm": QT_TRANSLATE_NOOP("Constant", "Centimetres"),
@@ -298,16 +309,27 @@ class nwLabels:
298
309
 
299
310
  class nwHeadFmt:
300
311
 
301
- BR = "{BR}"
302
- TITLE = "{Title}"
303
- CH_NUM = "{Chapter}"
304
- CH_WORD = "{Chapter:Word}"
305
- CH_ROMU = "{Chapter:URoman}"
306
- CH_ROML = "{Chapter:LRoman}"
307
- SC_NUM = "{Scene}"
308
- SC_ABS = "{Scene:Abs}"
312
+ BR = "{BR}"
313
+ TITLE = "{Title}"
314
+ CH_NUM = "{Chapter}"
315
+ CH_WORD = "{Chapter:Word}"
316
+ CH_ROMU = "{Chapter:URoman}"
317
+ CH_ROML = "{Chapter:LRoman}"
318
+ SC_NUM = "{Scene}"
319
+ SC_ABS = "{Scene:Abs}"
320
+ CHAR_POV = "{Char:POV}"
321
+ CHAR_FOCUS = "{Char:Focus}"
322
+
323
+ PAGE_HEADERS = [
324
+ TITLE, CH_NUM, CH_WORD, CH_ROMU, CH_ROML, SC_NUM, SC_ABS,
325
+ CHAR_POV, CHAR_FOCUS
326
+ ]
309
327
 
310
- ALL = [TITLE, CH_NUM, CH_WORD, CH_ROMU, CH_ROML, SC_NUM, SC_ABS]
328
+ # ODT Document Page Header
329
+ ODT_PROJECT = "{Project}"
330
+ ODT_AUTHOR = "{Author}"
331
+ ODT_PAGE = "{Page}"
332
+ ODT_AUTO = "{Project} / {Author} / {Page}"
311
333
 
312
334
  # END Class nwHeadFmt
313
335