novelWriter 2.2.1__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.
- {novelWriter-2.2.1.dist-info → novelWriter-2.3b1.dist-info}/METADATA +1 -1
- {novelWriter-2.2.1.dist-info → novelWriter-2.3b1.dist-info}/RECORD +102 -92
- novelwriter/__init__.py +4 -4
- novelwriter/assets/icons/typicons_dark/icons.conf +6 -0
- novelwriter/assets/icons/typicons_dark/mixed_import.svg +5 -0
- novelwriter/assets/icons/typicons_dark/typ_document-add-col.svg +8 -0
- novelwriter/assets/icons/typicons_dark/typ_document-add.svg +4 -0
- novelwriter/assets/icons/typicons_dark/typ_document.svg +4 -0
- novelwriter/assets/icons/typicons_dark/typ_th-dot-more.svg +4 -0
- novelwriter/assets/icons/typicons_light/icons.conf +6 -0
- novelwriter/assets/icons/typicons_light/mixed_import.svg +5 -0
- novelwriter/assets/icons/typicons_light/typ_document-add-col.svg +8 -0
- novelwriter/assets/icons/typicons_light/typ_document-add.svg +4 -0
- novelwriter/assets/icons/typicons_light/typ_document.svg +4 -0
- novelwriter/assets/icons/typicons_light/typ_th-dot-more.svg +4 -0
- novelwriter/assets/images/novelwriter-text-dark.svg +4 -0
- novelwriter/assets/images/novelwriter-text-light.svg +4 -0
- novelwriter/assets/images/welcome-dark.jpg +0 -0
- novelwriter/assets/images/welcome-light.jpg +0 -0
- novelwriter/assets/manual.pdf +0 -0
- novelwriter/assets/sample.zip +0 -0
- novelwriter/assets/syntax/default_dark.conf +1 -0
- novelwriter/assets/syntax/default_light.conf +1 -0
- novelwriter/assets/syntax/grey_dark.conf +1 -0
- novelwriter/assets/syntax/grey_light.conf +1 -0
- novelwriter/assets/syntax/light_owl.conf +1 -0
- novelwriter/assets/syntax/night_owl.conf +1 -0
- novelwriter/assets/syntax/solarized_dark.conf +1 -0
- novelwriter/assets/syntax/solarized_light.conf +1 -0
- novelwriter/assets/syntax/tomorrow.conf +1 -0
- novelwriter/assets/syntax/tomorrow_night.conf +1 -0
- novelwriter/assets/syntax/tomorrow_night_blue.conf +1 -0
- novelwriter/assets/syntax/tomorrow_night_bright.conf +1 -0
- novelwriter/assets/syntax/tomorrow_night_eighties.conf +1 -0
- novelwriter/assets/text/credits_en.htm +4 -2
- novelwriter/assets/themes/default_dark.conf +2 -2
- novelwriter/assets/themes/default_light.conf +2 -2
- novelwriter/common.py +48 -37
- novelwriter/config.py +36 -41
- novelwriter/constants.py +38 -16
- novelwriter/core/buildsettings.py +7 -7
- novelwriter/core/coretools.py +192 -154
- novelwriter/core/docbuild.py +6 -3
- novelwriter/core/document.py +6 -6
- novelwriter/core/index.py +89 -56
- novelwriter/core/item.py +21 -3
- novelwriter/core/options.py +8 -7
- novelwriter/core/project.py +69 -44
- novelwriter/core/projectdata.py +1 -14
- novelwriter/core/projectxml.py +13 -41
- novelwriter/core/sessions.py +2 -1
- novelwriter/core/spellcheck.py +2 -1
- novelwriter/core/status.py +2 -1
- novelwriter/core/storage.py +178 -140
- novelwriter/core/tohtml.py +4 -2
- novelwriter/core/tokenizer.py +73 -45
- novelwriter/core/toodt.py +40 -30
- novelwriter/core/tree.py +3 -2
- novelwriter/dialogs/about.py +70 -160
- novelwriter/dialogs/docmerge.py +6 -5
- novelwriter/dialogs/docsplit.py +6 -6
- novelwriter/dialogs/editlabel.py +1 -1
- novelwriter/dialogs/preferences.py +553 -703
- novelwriter/dialogs/{projsettings.py → projectsettings.py} +288 -262
- novelwriter/dialogs/quotes.py +27 -23
- novelwriter/dialogs/wordlist.py +96 -40
- novelwriter/enum.py +20 -18
- novelwriter/error.py +1 -1
- novelwriter/extensions/circularprogress.py +11 -11
- novelwriter/extensions/configlayout.py +185 -134
- novelwriter/extensions/modified.py +81 -0
- novelwriter/extensions/novelselector.py +26 -12
- novelwriter/extensions/pagedsidebar.py +14 -16
- novelwriter/extensions/simpleprogress.py +5 -5
- novelwriter/extensions/statusled.py +8 -8
- novelwriter/extensions/switch.py +31 -63
- novelwriter/extensions/switchbox.py +1 -1
- novelwriter/extensions/versioninfo.py +153 -0
- novelwriter/gui/doceditor.py +178 -150
- novelwriter/gui/dochighlight.py +63 -92
- novelwriter/gui/docviewer.py +49 -51
- novelwriter/gui/docviewerpanel.py +72 -24
- novelwriter/gui/itemdetails.py +7 -7
- novelwriter/gui/mainmenu.py +14 -18
- novelwriter/gui/noveltree.py +9 -8
- novelwriter/gui/outline.py +98 -75
- novelwriter/gui/projtree.py +188 -61
- novelwriter/gui/sidebar.py +3 -4
- novelwriter/gui/statusbar.py +3 -4
- novelwriter/gui/theme.py +60 -68
- novelwriter/guimain.py +49 -156
- novelwriter/shared.py +15 -1
- novelwriter/tools/dictionaries.py +5 -6
- novelwriter/tools/manuscript.py +6 -6
- novelwriter/tools/manussettings.py +192 -221
- novelwriter/tools/noveldetails.py +525 -0
- novelwriter/tools/welcome.py +802 -0
- novelwriter/tools/writingstats.py +9 -9
- novelwriter/assets/images/wizard-back.jpg +0 -0
- novelwriter/assets/text/gplv3_en.htm +0 -641
- novelwriter/assets/text/release_notes.htm +0 -60
- novelwriter/dialogs/projdetails.py +0 -518
- novelwriter/dialogs/projload.py +0 -294
- novelwriter/dialogs/updates.py +0 -172
- novelwriter/extensions/pageddialog.py +0 -130
- novelwriter/tools/projwizard.py +0 -478
- {novelWriter-2.2.1.dist-info → novelWriter-2.3b1.dist-info}/LICENSE.md +0 -0
- {novelWriter-2.2.1.dist-info → novelWriter-2.3b1.dist-info}/WHEEL +0 -0
- {novelWriter-2.2.1.dist-info → novelWriter-2.3b1.dist-info}/entry_points.txt +0 -0
- {novelWriter-2.2.1.dist-info → novelWriter-2.3b1.dist-info}/top_level.txt +0 -0
@@ -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 =
|
25
|
-
linkvisited =
|
24
|
+
link = 102, 153, 204
|
25
|
+
linkvisited = 102, 153, 204
|
26
26
|
|
27
27
|
[GUI]
|
28
28
|
helptext = 164, 164, 164
|
novelwriter/common.py
CHANGED
@@ -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
|
@@ -41,7 +41,10 @@ from PyQt5.QtCore import QCoreApplication, QUrl
|
|
41
41
|
|
42
42
|
from novelwriter.enum import nwItemClass, nwItemType, nwItemLayout
|
43
43
|
from novelwriter.error import logException
|
44
|
-
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
|
45
48
|
|
46
49
|
logger = logging.getLogger(__name__)
|
47
50
|
|
@@ -104,15 +107,6 @@ def checkBool(value: Any, default: bool) -> bool:
|
|
104
107
|
return default
|
105
108
|
|
106
109
|
|
107
|
-
def checkHandle(value, default, allowNone=False):
|
108
|
-
"""Check if a value is a handle."""
|
109
|
-
if allowNone and (value is None or value == "None"):
|
110
|
-
return None
|
111
|
-
if isHandle(value):
|
112
|
-
return str(value)
|
113
|
-
return default
|
114
|
-
|
115
|
-
|
116
110
|
def checkUuid(value: Any, default: str) -> str:
|
117
111
|
"""Try to process a value as an UUID, or return a default."""
|
118
112
|
try:
|
@@ -135,7 +129,7 @@ def checkPath(value: Any, default: Path) -> Path:
|
|
135
129
|
# Validator Functions
|
136
130
|
##
|
137
131
|
|
138
|
-
def isHandle(value: Any) ->
|
132
|
+
def isHandle(value: Any) -> TypeGuard[str]:
|
139
133
|
"""Check if a string is a valid novelWriter handle.
|
140
134
|
Note: This is case sensitive. Must be lower case!
|
141
135
|
"""
|
@@ -149,7 +143,7 @@ def isHandle(value: Any) -> bool:
|
|
149
143
|
return True
|
150
144
|
|
151
145
|
|
152
|
-
def isTitleTag(value: Any) ->
|
146
|
+
def isTitleTag(value: Any) -> TypeGuard[str]:
|
153
147
|
"""Check if a string is a valid title tag string."""
|
154
148
|
if not isinstance(value, str):
|
155
149
|
return False
|
@@ -163,19 +157,19 @@ def isTitleTag(value: Any) -> bool:
|
|
163
157
|
return True
|
164
158
|
|
165
159
|
|
166
|
-
def isItemClass(value:
|
160
|
+
def isItemClass(value: Any) -> TypeGuard[str]:
|
167
161
|
"""Check if a string is a valid nwItemClass identifier."""
|
168
|
-
return value in nwItemClass.__members__
|
162
|
+
return isinstance(value, str) and value in nwItemClass.__members__
|
169
163
|
|
170
164
|
|
171
|
-
def isItemType(value:
|
165
|
+
def isItemType(value: Any) -> TypeGuard[str]:
|
172
166
|
"""Check if a string is a valid nwItemType identifier."""
|
173
|
-
return value in nwItemType.__members__
|
167
|
+
return isinstance(value, str) and value in nwItemType.__members__
|
174
168
|
|
175
169
|
|
176
|
-
def isItemLayout(value:
|
170
|
+
def isItemLayout(value: Any) -> TypeGuard[str]:
|
177
171
|
"""Check if a string is a valid nwItemLayout identifier."""
|
178
|
-
return value in nwItemLayout.__members__
|
172
|
+
return isinstance(value, str) and value in nwItemLayout.__members__
|
179
173
|
|
180
174
|
|
181
175
|
def hexToInt(value: Any, default: int = 0) -> int:
|
@@ -189,8 +183,7 @@ def hexToInt(value: Any, default: int = 0) -> int:
|
|
189
183
|
|
190
184
|
|
191
185
|
def minmax(value: int, minVal: int, maxVal: int) -> int:
|
192
|
-
"""
|
193
|
-
"""
|
186
|
+
"""Check that an value is between min and max value (inclusive)."""
|
194
187
|
return min(maxVal, max(minVal, value))
|
195
188
|
|
196
189
|
|
@@ -213,17 +206,17 @@ def formatInt(value: int) -> str:
|
|
213
206
|
if not isinstance(value, int):
|
214
207
|
return "ERR"
|
215
208
|
|
216
|
-
|
217
|
-
if
|
209
|
+
fVal = float(value)
|
210
|
+
if fVal > 1000.0:
|
218
211
|
for pF in ["k", "M", "G", "T", "P", "E"]:
|
219
|
-
|
220
|
-
if
|
221
|
-
if
|
222
|
-
return f"{
|
223
|
-
elif
|
224
|
-
return f"{
|
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}"
|
225
218
|
else:
|
226
|
-
return f"{
|
219
|
+
return f"{fVal:3.0f}{nwUnicode.U_THSP}{pF}"
|
227
220
|
|
228
221
|
return str(value)
|
229
222
|
|
@@ -250,6 +243,24 @@ def formatTime(t: int) -> str:
|
|
250
243
|
return "ERROR"
|
251
244
|
|
252
245
|
|
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
|
+
|
253
264
|
##
|
254
265
|
# String Functions
|
255
266
|
##
|
@@ -270,22 +281,22 @@ def transferCase(source: str, target: str) -> str:
|
|
270
281
|
"""Transfers the case of the source word to the target word. This
|
271
282
|
will consider all upper or lower, and first char capitalisation.
|
272
283
|
"""
|
273
|
-
|
284
|
+
result = target
|
274
285
|
|
275
286
|
if not isinstance(source, str) or not isinstance(target, str):
|
276
|
-
return
|
287
|
+
return result
|
277
288
|
if len(target) < 1 or len(source) < 1:
|
278
|
-
return
|
289
|
+
return result
|
279
290
|
|
280
291
|
if source.istitle():
|
281
|
-
|
292
|
+
result = target.title()
|
282
293
|
|
283
294
|
if source.isupper():
|
284
|
-
|
295
|
+
result = target.upper()
|
285
296
|
elif source.islower():
|
286
|
-
|
297
|
+
result = target.lower()
|
287
298
|
|
288
|
-
return
|
299
|
+
return result
|
289
300
|
|
290
301
|
|
291
302
|
def fuzzyTime(seconds: int) -> str:
|
novelwriter/config.py
CHANGED
@@ -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
|
@@ -250,12 +248,12 @@ class Config:
|
|
250
248
|
return [int(x*self.guiScale) for x in self._mainWinSize]
|
251
249
|
|
252
250
|
@property
|
253
|
-
def
|
254
|
-
return [int(x*self.guiScale) for x in self.
|
251
|
+
def welcomeWinSize(self) -> list[int]:
|
252
|
+
return [int(x*self.guiScale) for x in self._welcomeSize]
|
255
253
|
|
256
254
|
@property
|
257
|
-
def
|
258
|
-
return [int(x*self.guiScale) for x in self.
|
255
|
+
def preferencesWinSize(self) -> list[int]:
|
256
|
+
return [int(x*self.guiScale) for x in self._prefsWinSize]
|
259
257
|
|
260
258
|
@property
|
261
259
|
def mainPanePos(self) -> list[int]:
|
@@ -306,17 +304,18 @@ class Config:
|
|
306
304
|
self._mainWinSize[1] = height
|
307
305
|
return
|
308
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
|
+
|
309
313
|
def setPreferencesWinSize(self, width: int, height: int) -> None:
|
310
314
|
"""Set the size of the Preferences dialog window."""
|
311
315
|
self._prefsWinSize[0] = int(width/self.guiScale)
|
312
316
|
self._prefsWinSize[1] = int(height/self.guiScale)
|
313
317
|
return
|
314
318
|
|
315
|
-
def setProjLoadColWidths(self, widths: list[int]) -> None:
|
316
|
-
"""Set the column widths of the Load Project dialog."""
|
317
|
-
self._projLoadCols = [int(x/self.guiScale) for x in widths]
|
318
|
-
return
|
319
|
-
|
320
319
|
def setMainPanePos(self, pos: list[int]) -> None:
|
321
320
|
"""Set the position of the main GUI splitter."""
|
322
321
|
self._mainPanePos = [int(x/self.guiScale) for x in pos]
|
@@ -410,10 +409,10 @@ class Config:
|
|
410
409
|
"""Compile and return error messages from the initialisation of
|
411
410
|
the Config class, and clear the error buffer.
|
412
411
|
"""
|
413
|
-
|
412
|
+
message = "<br>".join(self._errData)
|
414
413
|
self._hasError = False
|
415
414
|
self._errData = []
|
416
|
-
return
|
415
|
+
return message
|
417
416
|
|
418
417
|
def listLanguages(self, lngSet: int) -> list[tuple[str, str]]:
|
419
418
|
"""List localisation files in the i18n folder. The default GUI
|
@@ -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":
|
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":
|
655
|
-
"
|
656
|
-
"
|
657
|
-
"mainpane":
|
658
|
-
"viewpane":
|
659
|
-
"outlinepane":
|
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
|
778
|
-
|
779
|
-
|
780
|
-
|
781
|
-
|
782
|
-
|
783
|
-
|
784
|
-
|
785
|
-
|
786
|
-
|
787
|
-
|
788
|
-
|
789
|
-
|
790
|
-
|
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
@@ -43,12 +43,12 @@ class nwConst:
|
|
43
43
|
FMT_DSTAMP = "%Y-%m-%d" # Date only format
|
44
44
|
|
45
45
|
# URLs
|
46
|
-
URL_WEB
|
47
|
-
URL_DOCS
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
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
|
302
|
-
TITLE
|
303
|
-
CH_NUM
|
304
|
-
CH_WORD
|
305
|
-
CH_ROMU
|
306
|
-
CH_ROML
|
307
|
-
SC_NUM
|
308
|
-
SC_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
|
-
|
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
|
|
@@ -29,8 +29,8 @@ import uuid
|
|
29
29
|
import logging
|
30
30
|
|
31
31
|
from enum import Enum
|
32
|
-
from typing import Iterable
|
33
32
|
from pathlib import Path
|
33
|
+
from collections.abc import Iterable
|
34
34
|
|
35
35
|
from PyQt5.QtCore import QT_TRANSLATE_NOOP, QCoreApplication
|
36
36
|
|
@@ -79,6 +79,8 @@ SETTINGS_TEMPLATE = {
|
|
79
79
|
"format.leftMargin": (float, 2.0),
|
80
80
|
"format.rightMargin": (float, 2.0),
|
81
81
|
"odt.addColours": (bool, True),
|
82
|
+
"odt.pageHeader": (str, nwHeadFmt.ODT_AUTO),
|
83
|
+
"odt.pageCountOffset": (int, 0),
|
82
84
|
"html.addStyles": (bool, True),
|
83
85
|
}
|
84
86
|
|
@@ -125,6 +127,8 @@ SETTINGS_LABELS = {
|
|
125
127
|
|
126
128
|
"odt": QT_TRANSLATE_NOOP("Builds", "Open Document (.odt)"),
|
127
129
|
"odt.addColours": QT_TRANSLATE_NOOP("Builds", "Add Highlight Colours"),
|
130
|
+
"odt.pageHeader": QT_TRANSLATE_NOOP("Builds", "Page Header"),
|
131
|
+
"odt.pageCountOffset": QT_TRANSLATE_NOOP("Builds", "Page Counter Offset"),
|
128
132
|
|
129
133
|
"html": QT_TRANSLATE_NOOP("Builds", "HTML (.html)"),
|
130
134
|
"html.addStyles": QT_TRANSLATE_NOOP("Builds", "Add CSS Styles"),
|
@@ -235,16 +239,12 @@ class BuildSettings:
|
|
235
239
|
def getInt(self, key: str) -> int:
|
236
240
|
"""Type safe value access for integers."""
|
237
241
|
value = self._settings.get(key, SETTINGS_TEMPLATE.get(key, (None, None))[1])
|
238
|
-
if isinstance(value, (int, float))
|
239
|
-
return int(value)
|
240
|
-
return 0
|
242
|
+
return int(value) if isinstance(value, (int, float)) else 0
|
241
243
|
|
242
244
|
def getFloat(self, key: str) -> float:
|
243
245
|
"""Type safe value access for floats."""
|
244
246
|
value = self._settings.get(key, SETTINGS_TEMPLATE.get(key, (None, None))[1])
|
245
|
-
if isinstance(value, (int, float))
|
246
|
-
return float(value)
|
247
|
-
return 0.0
|
247
|
+
return float(value) if isinstance(value, (int, float)) else 0.0
|
248
248
|
|
249
249
|
##
|
250
250
|
# Setters
|