novelWriter 2.5b1__py3-none-any.whl → 2.5.1__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.5b1.dist-info → novelWriter-2.5.1.dist-info}/METADATA +1 -1
- {novelWriter-2.5b1.dist-info → novelWriter-2.5.1.dist-info}/RECORD +77 -75
- {novelWriter-2.5b1.dist-info → novelWriter-2.5.1.dist-info}/WHEEL +1 -1
- novelwriter/__init__.py +3 -3
- novelwriter/assets/i18n/nw_de_DE.qm +0 -0
- novelwriter/assets/i18n/nw_en_US.qm +0 -0
- novelwriter/assets/i18n/nw_es_419.qm +0 -0
- novelwriter/assets/i18n/nw_fr_FR.qm +0 -0
- novelwriter/assets/i18n/nw_it_IT.qm +0 -0
- novelwriter/assets/i18n/nw_ja_JP.qm +0 -0
- novelwriter/assets/i18n/nw_nb_NO.qm +0 -0
- novelwriter/assets/i18n/nw_nl_NL.qm +0 -0
- novelwriter/assets/i18n/nw_pl_PL.qm +0 -0
- novelwriter/assets/i18n/nw_pt_BR.qm +0 -0
- novelwriter/assets/i18n/nw_zh_CN.qm +0 -0
- novelwriter/assets/i18n/project_pl_PL.json +116 -0
- novelwriter/assets/i18n/project_pt_BR.json +74 -74
- novelwriter/assets/manual.pdf +0 -0
- novelwriter/assets/sample.zip +0 -0
- novelwriter/assets/text/credits_en.htm +52 -44
- novelwriter/assets/themes/cyberpunk_night.conf +1 -0
- novelwriter/assets/themes/default_dark.conf +1 -0
- novelwriter/assets/themes/default_light.conf +1 -0
- novelwriter/assets/themes/dracula.conf +1 -0
- novelwriter/assets/themes/solarized_dark.conf +1 -0
- novelwriter/assets/themes/solarized_light.conf +1 -0
- novelwriter/common.py +12 -3
- novelwriter/config.py +67 -15
- novelwriter/constants.py +8 -10
- novelwriter/core/buildsettings.py +5 -3
- novelwriter/core/coretools.py +3 -1
- novelwriter/core/docbuild.py +1 -0
- novelwriter/core/project.py +15 -4
- novelwriter/core/status.py +4 -1
- novelwriter/core/storage.py +6 -1
- novelwriter/core/tohtml.py +69 -29
- novelwriter/core/tokenizer.py +83 -14
- novelwriter/core/toodt.py +48 -21
- novelwriter/core/toqdoc.py +37 -21
- novelwriter/dialogs/about.py +10 -15
- novelwriter/dialogs/docmerge.py +16 -16
- novelwriter/dialogs/docsplit.py +16 -16
- novelwriter/dialogs/editlabel.py +6 -8
- novelwriter/dialogs/preferences.py +106 -93
- novelwriter/dialogs/projectsettings.py +16 -20
- novelwriter/dialogs/quotes.py +9 -5
- novelwriter/dialogs/wordlist.py +6 -6
- novelwriter/enum.py +4 -5
- novelwriter/extensions/configlayout.py +38 -4
- novelwriter/extensions/modified.py +22 -3
- novelwriter/extensions/{circularprogress.py → progressbars.py} +26 -3
- novelwriter/extensions/statusled.py +39 -23
- novelwriter/gui/doceditor.py +22 -13
- novelwriter/gui/dochighlight.py +30 -39
- novelwriter/gui/docviewer.py +24 -15
- novelwriter/gui/docviewerpanel.py +7 -0
- novelwriter/gui/mainmenu.py +11 -11
- novelwriter/gui/outline.py +4 -3
- novelwriter/gui/projtree.py +85 -77
- novelwriter/gui/search.py +10 -1
- novelwriter/gui/statusbar.py +25 -29
- novelwriter/gui/theme.py +3 -0
- novelwriter/guimain.py +139 -124
- novelwriter/shared.py +19 -8
- novelwriter/text/patterns.py +113 -0
- novelwriter/tools/dictionaries.py +2 -8
- novelwriter/tools/lipsum.py +8 -12
- novelwriter/tools/manusbuild.py +9 -9
- novelwriter/tools/manuscript.py +10 -5
- novelwriter/tools/manussettings.py +7 -3
- novelwriter/tools/noveldetails.py +10 -10
- novelwriter/tools/welcome.py +19 -10
- novelwriter/tools/writingstats.py +3 -3
- novelwriter/types.py +5 -2
- novelwriter/extensions/simpleprogress.py +0 -53
- {novelWriter-2.5b1.dist-info → novelWriter-2.5.1.dist-info}/LICENSE.md +0 -0
- {novelWriter-2.5b1.dist-info → novelWriter-2.5.1.dist-info}/entry_points.txt +0 -0
- {novelWriter-2.5b1.dist-info → novelWriter-2.5.1.dist-info}/top_level.txt +0 -0
@@ -1,84 +1,92 @@
|
|
1
|
-
<!DOCTYPE html public "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
|
2
1
|
<html>
|
3
2
|
<body>
|
4
3
|
|
5
4
|
<h3>Main Developer</h3>
|
6
|
-
|
5
|
+
|
6
|
+
<p>Veronica Berglyd Olsen</p>
|
7
7
|
|
8
8
|
<h3>Contributors</h3>
|
9
9
|
|
10
|
-
<
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
</
|
10
|
+
<ul>
|
11
|
+
<li><b>Early Concept:</b> Marian Lückhof</li>
|
12
|
+
<li><b>Internationalisation:</b> Bruno Meneguello</li>
|
13
|
+
<li><b>Setup and Packaging:</b> Rachel Powers</li>
|
14
|
+
</ul>
|
15
|
+
|
15
16
|
<p>For other contributions, see the project's
|
16
17
|
<a href="https://github.com/vkbo/novelWriter/graphs/contributors">Contributors</a> page.</p>
|
17
18
|
|
18
19
|
<h3>Artwork</h3>
|
19
20
|
|
20
|
-
<p>The artwork on the Welcome dialog was created by
|
21
|
+
<p>The artwork on the Welcome dialog was created by Louis Durrant.</p>
|
21
22
|
|
22
23
|
<h3>Translations</h3>
|
23
24
|
|
24
25
|
<p>The default language is English (UK) with English (US) as an option. These are the original
|
25
26
|
translators for the languages currently available:</p>
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
</
|
27
|
+
|
28
|
+
<ul>
|
29
|
+
<li><b>Dutch:</b> Martijn van der Kleijn (mvdkleijn)</li>
|
30
|
+
<li><b>French:</b> Jan Lüdke (jyhelle)</li>
|
31
|
+
<li><b>German:</b> Myian (HeyMyian)</li>
|
32
|
+
<li><b>Italian:</b> Riccardo Mangili</li>
|
33
|
+
<li><b>Japanese:</b> hebekeg</li>
|
34
|
+
<li><b>Latin American Spanish:</b> Tommy Marplatt (tmarplatt)</li>
|
35
|
+
<li><b>Norwegian:</b> Veronica Berglyd Olsen (vkbo)</li>
|
36
|
+
<li><b>Polish:</b> Anna Maria Polak (Nauthiz)</li>
|
37
|
+
<li><b>Portuguese:</b> Bruno Meneguello (bkmeneguello)</li>
|
38
|
+
<li><b>Simplified Chinese:</b> Qianzhi Long (longqzh)</li>
|
39
|
+
</ul>
|
37
40
|
|
38
41
|
<p>Additional larger translation contributions:</p>
|
39
|
-
|
40
|
-
|
41
|
-
</
|
42
|
+
|
43
|
+
<ul>
|
44
|
+
<li><b>French:</b> Albert Aribaud (aaribaud)</li>
|
45
|
+
<li><b>Portuguese:</b> Oli Maia (olimaia)</li>
|
46
|
+
</ul>
|
42
47
|
|
43
48
|
<p>Translations are managed on <a href="https://crowdin.com/project/novelwriter">Crowdin</a>, and
|
44
|
-
more contributions are listed on the project's
|
49
|
+
more contributions are listed on the project's Members page.</p>
|
45
50
|
|
46
51
|
<h3>Libraries</h3>
|
47
52
|
|
48
53
|
<p>The following libraries are dependencies of novelWriter:</p>
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
</
|
54
|
+
|
55
|
+
<ul>
|
56
|
+
<li><b>Qt5</b> by Qt Company</li>
|
57
|
+
<li><b>PyQt5</b> by Riverbank Computing</li>
|
58
|
+
<li><b>Enchant</b> by Dom Lachowicz</li>
|
59
|
+
<li><b>PyEnchant</b> by Dimitri Merejkowsky</li>
|
60
|
+
</ul>
|
55
61
|
|
56
62
|
<h3>Assets</h3>
|
57
63
|
|
58
64
|
<p>Some of the assets bundled with novelWriter were adapted from the following sources:</p>
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
</
|
65
|
+
|
66
|
+
<ul>
|
67
|
+
<li><b>Typicons</b> icons by Stephen Hutchings (CC BY-SA 4.0)</li>
|
68
|
+
<li><b>Tomorrow</b> syntax themes by Chris Kempson (MIT License)</li>
|
69
|
+
<li><b>Owl</b> syntax themes by Sarah Drasner (MIT License)</li>
|
70
|
+
<li><b>Solarized</b> themes by Ethan Schoonover (MIT License)</li>
|
71
|
+
<li><b>Cyberpunk Night</b> theme by Anders Lemvigh (CC BY-SA 4.0)</li>
|
72
|
+
<li><b>Dracula</b> theme by Zeno Rocha (MIT License)</li>
|
73
|
+
<li><b>Snazzy Light</b> theme by Florian Reuschel (MIT License)</li>
|
74
|
+
</ul>
|
68
75
|
|
69
76
|
<h3>Fonts</h3>
|
70
77
|
|
71
78
|
<p>The font used for the main novelWriter logo, mimetype and text banners is Pridi. Other fonts are
|
72
79
|
used on buttons and icons.</p>
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
</
|
80
|
+
|
81
|
+
<ul>
|
82
|
+
<li><b>Pridi</b> by Cadson Demak (Open Font License, Version 1.1)</li>
|
83
|
+
<li><b>Source Sans Pro</b> by Paul D. Hunt (SIL Open Font License)</li>
|
84
|
+
</ul>
|
77
85
|
|
78
86
|
<h3>Special Mentions</h3>
|
79
87
|
|
80
|
-
<p>Additional thanks to
|
81
|
-
|
88
|
+
<p>Additional thanks to John Blommers who was an early user and who has provided a lot of very
|
89
|
+
useful feedback over the years.</p>
|
82
90
|
|
83
91
|
</body>
|
84
92
|
</html>
|
novelwriter/common.py
CHANGED
@@ -279,6 +279,16 @@ def simplified(text: str) -> str:
|
|
279
279
|
return " ".join(str(text).strip().split())
|
280
280
|
|
281
281
|
|
282
|
+
def compact(text: str) -> str:
|
283
|
+
"""Compact a string by removing spaces."""
|
284
|
+
return "".join(str(text).split())
|
285
|
+
|
286
|
+
|
287
|
+
def uniqueCompact(text: str) -> str:
|
288
|
+
"""Return a unique, compact and sorted string."""
|
289
|
+
return "".join(sorted(set(compact(text))))
|
290
|
+
|
291
|
+
|
282
292
|
def elide(text: str, length: int) -> str:
|
283
293
|
"""Elide a piece of text to a maximum length."""
|
284
294
|
if len(text) > (cut := max(4, length)):
|
@@ -523,12 +533,11 @@ def readTextFile(path: str | Path) -> str:
|
|
523
533
|
|
524
534
|
|
525
535
|
def makeFileNameSafe(text: str) -> str:
|
526
|
-
"""Return a filename
|
536
|
+
"""Return a filename-safe string.
|
527
537
|
See: https://unicode.org/reports/tr15/#Norm_Forms
|
528
538
|
"""
|
529
539
|
text = unicodedata.normalize("NFKC", text).strip()
|
530
|
-
|
531
|
-
return "".join(c for c in text if c.isalnum() or c in allowed)
|
540
|
+
return "".join(c for c in text if c.isprintable() and c not in r'\/:*?"<>|')
|
532
541
|
|
533
542
|
|
534
543
|
def getFileSize(path: Path) -> int:
|
novelwriter/config.py
CHANGED
@@ -5,6 +5,7 @@ novelWriter – Config Class
|
|
5
5
|
File History:
|
6
6
|
Created: 2018-09-22 [0.0.1] Config
|
7
7
|
Created: 2022-11-09 [2.0rc2] RecentProjects
|
8
|
+
Created: 2024-06-16 [2.5rc1] RecentPaths
|
8
9
|
|
9
10
|
This file is a part of novelWriter
|
10
11
|
Copyright 2018–2024, Veronica Berglyd Olsen
|
@@ -103,7 +104,8 @@ class Config:
|
|
103
104
|
# User Settings
|
104
105
|
# =============
|
105
106
|
|
106
|
-
self.
|
107
|
+
self._recentProjects = RecentProjects(self)
|
108
|
+
self._recentPaths = RecentPaths(self)
|
107
109
|
|
108
110
|
# General GUI Settings
|
109
111
|
self.guiLocale = self._qLocale.name()
|
@@ -180,7 +182,6 @@ class Config:
|
|
180
182
|
self.fmtPadThin = False
|
181
183
|
|
182
184
|
# User Paths
|
183
|
-
self._lastPath = self._homePath # The user's last used path
|
184
185
|
self._backupPath = self._backPath # Backup path to use, can be none
|
185
186
|
|
186
187
|
# Spell Checking Settings
|
@@ -253,7 +254,7 @@ class Config:
|
|
253
254
|
|
254
255
|
@property
|
255
256
|
def recentProjects(self) -> RecentProjects:
|
256
|
-
return self.
|
257
|
+
return self._recentProjects
|
257
258
|
|
258
259
|
@property
|
259
260
|
def mainWinSize(self) -> list[int]:
|
@@ -343,7 +344,7 @@ class Config:
|
|
343
344
|
self._outlnPanePos = [int(x/self.guiScale) for x in pos]
|
344
345
|
return
|
345
346
|
|
346
|
-
def setLastPath(self, path: str | Path) -> None:
|
347
|
+
def setLastPath(self, key: str, path: str | Path) -> None:
|
347
348
|
"""Set the last used path. Only the folder is saved, so if the
|
348
349
|
path is not a folder, the parent of the path is used instead.
|
349
350
|
"""
|
@@ -352,8 +353,7 @@ class Config:
|
|
352
353
|
if not path.is_dir():
|
353
354
|
path = path.parent
|
354
355
|
if path.is_dir():
|
355
|
-
self.
|
356
|
-
logger.debug("Last path updated: %s" % self._lastPath)
|
356
|
+
self._recentPaths.setPath(key, path)
|
357
357
|
return
|
358
358
|
|
359
359
|
def setBackupPath(self, path: Path | str) -> None:
|
@@ -438,11 +438,12 @@ class Config:
|
|
438
438
|
return self._appPath / "assets" / target
|
439
439
|
return self._appPath / "assets"
|
440
440
|
|
441
|
-
def lastPath(self) -> Path:
|
441
|
+
def lastPath(self, key: str) -> Path:
|
442
442
|
"""Return the last path used by the user, if it exists."""
|
443
|
-
if
|
444
|
-
|
445
|
-
|
443
|
+
if path := self._recentPaths.getPath(key):
|
444
|
+
asPath = Path(path)
|
445
|
+
if asPath.is_dir():
|
446
|
+
return asPath
|
446
447
|
return self._homePath
|
447
448
|
|
448
449
|
def backupPath(self) -> Path:
|
@@ -516,7 +517,6 @@ class Config:
|
|
516
517
|
logger.debug("Data Path: %s", self._dataPath)
|
517
518
|
logger.debug("App Root: %s", self._appRoot)
|
518
519
|
logger.debug("App Path: %s", self._appPath)
|
519
|
-
logger.debug("Last Path: %s", self._lastPath)
|
520
520
|
logger.debug("PDF Manual: %s", self.pdfDocs)
|
521
521
|
|
522
522
|
# If the config and data folders don't exist, create them
|
@@ -531,7 +531,8 @@ class Config:
|
|
531
531
|
(self._dataPath / "syntax").mkdir(exist_ok=True)
|
532
532
|
(self._dataPath / "themes").mkdir(exist_ok=True)
|
533
533
|
|
534
|
-
self.
|
534
|
+
self._recentPaths.loadCache()
|
535
|
+
self._recentProjects.loadCache()
|
535
536
|
self._checkOptionalPackages()
|
536
537
|
|
537
538
|
logger.debug("Config instance initialised")
|
@@ -600,7 +601,6 @@ class Config:
|
|
600
601
|
self.hideHScroll = conf.rdBool(sec, "hidehscroll", self.hideHScroll)
|
601
602
|
self.lastNotes = conf.rdStr(sec, "lastnotes", self.lastNotes)
|
602
603
|
self.nativeFont = conf.rdBool(sec, "nativefont", self.nativeFont)
|
603
|
-
self._lastPath = conf.rdPath(sec, "lastpath", self._lastPath)
|
604
604
|
|
605
605
|
# Sizes
|
606
606
|
sec = "Sizes"
|
@@ -710,7 +710,6 @@ class Config:
|
|
710
710
|
"hidehscroll": str(self.hideHScroll),
|
711
711
|
"lastnotes": str(self.lastNotes),
|
712
712
|
"nativefont": str(self.nativeFont),
|
713
|
-
"lastpath": str(self._lastPath),
|
714
713
|
}
|
715
714
|
|
716
715
|
conf["Sizes"] = {
|
@@ -811,7 +810,7 @@ class Config:
|
|
811
810
|
"""Pack a list of items into a comma-separated string for saving
|
812
811
|
to the config file.
|
813
812
|
"""
|
814
|
-
return ", ".join(
|
813
|
+
return ", ".join(str(inVal) for inVal in data)
|
815
814
|
|
816
815
|
def _checkOptionalPackages(self) -> None:
|
817
816
|
"""Check optional packages used by some features."""
|
@@ -893,3 +892,56 @@ class RecentProjects:
|
|
893
892
|
logger.debug("Removed recent: %s", path)
|
894
893
|
self.saveCache()
|
895
894
|
return
|
895
|
+
|
896
|
+
|
897
|
+
class RecentPaths:
|
898
|
+
|
899
|
+
KEYS = ["default", "project", "import", "outline", "stats"]
|
900
|
+
|
901
|
+
def __init__(self, config: Config) -> None:
|
902
|
+
self._conf = config
|
903
|
+
self._data = {}
|
904
|
+
return
|
905
|
+
|
906
|
+
def setPath(self, key: str, path: Path | str) -> None:
|
907
|
+
"""Set a path for a given key, and save the cache."""
|
908
|
+
if key in self.KEYS:
|
909
|
+
self._data[key] = str(path)
|
910
|
+
self.saveCache()
|
911
|
+
return
|
912
|
+
|
913
|
+
def getPath(self, key: str) -> str | None:
|
914
|
+
"""Get a path for a given key, or return None."""
|
915
|
+
return self._data.get(key)
|
916
|
+
|
917
|
+
def loadCache(self) -> bool:
|
918
|
+
"""Load the cache file for recent paths."""
|
919
|
+
self._data = {}
|
920
|
+
cacheFile = self._conf.dataPath(nwFiles.RECENT_PATH)
|
921
|
+
if cacheFile.is_file():
|
922
|
+
try:
|
923
|
+
with open(cacheFile, mode="r", encoding="utf-8") as inFile:
|
924
|
+
data = json.load(inFile)
|
925
|
+
if isinstance(data, dict):
|
926
|
+
for key, path in data.items():
|
927
|
+
if key in self.KEYS and isinstance(path, str):
|
928
|
+
self._data[key] = path
|
929
|
+
except Exception:
|
930
|
+
logger.error("Could not load recent paths cache")
|
931
|
+
logException()
|
932
|
+
return False
|
933
|
+
return True
|
934
|
+
|
935
|
+
def saveCache(self) -> bool:
|
936
|
+
"""Save the cache dictionary of recent paths."""
|
937
|
+
cacheFile = self._conf.dataPath(nwFiles.RECENT_PATH)
|
938
|
+
cacheTemp = cacheFile.with_suffix(".tmp")
|
939
|
+
try:
|
940
|
+
with open(cacheTemp, mode="w+", encoding="utf-8") as outFile:
|
941
|
+
json.dump(self._data, outFile, indent=2)
|
942
|
+
cacheTemp.replace(cacheFile)
|
943
|
+
except Exception:
|
944
|
+
logger.error("Could not save recent paths cache")
|
945
|
+
logException()
|
946
|
+
return False
|
947
|
+
return True
|
novelwriter/constants.py
CHANGED
@@ -57,17 +57,14 @@ class nwConst:
|
|
57
57
|
STATUS_MSG_TIMEOUT = 15000 # milliseconds
|
58
58
|
MAX_SEARCH_RESULT = 1000
|
59
59
|
|
60
|
-
# Dialogs
|
61
|
-
DLG_FINISHED = 2
|
62
|
-
|
63
60
|
|
64
61
|
class nwRegEx:
|
65
62
|
|
66
63
|
FMT_EI = r"(?<![\w\\])(_)(?![\s_])(.+?)(?<![\s\\])(\1)(?!\w)"
|
67
|
-
FMT_EB = r"(?<![\w\\])(
|
68
|
-
FMT_ST = r"(?<![\w\\])(
|
69
|
-
FMT_SC = r"(?i)(?<!\\)(\[[\/\!]?(?:i|
|
70
|
-
FMT_SV = r"(?<!\\)(\[(
|
64
|
+
FMT_EB = r"(?<![\w\\])(\*{2})(?![\s\*])(.+?)(?<![\s\\])(\1)(?!\w)"
|
65
|
+
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):)(.+?)(?<!\\)(\])"
|
71
68
|
|
72
69
|
|
73
70
|
class nwShortcode:
|
@@ -107,6 +104,7 @@ class nwFiles:
|
|
107
104
|
# Config Files
|
108
105
|
CONF_FILE = "novelwriter.conf"
|
109
106
|
RECENT_FILE = "recentProjects.json"
|
107
|
+
RECENT_PATH = "recentPaths.json"
|
110
108
|
|
111
109
|
# Project Root Files
|
112
110
|
PROJ_FILE = "nwProject.nwx"
|
@@ -414,7 +412,7 @@ class nwUnicode:
|
|
414
412
|
U_EMDASH = "\u2014" # Long dash
|
415
413
|
U_HBAR = "\u2015" # Horizontal bar
|
416
414
|
U_HELLIP = "\u2026" # Ellipsis
|
417
|
-
|
415
|
+
U_MAPOS = "\u02bc" # Modifier letter single apostrophe
|
418
416
|
U_PRIME = "\u2032" # Prime
|
419
417
|
U_DPRIME = "\u2033" # Double prime
|
420
418
|
|
@@ -481,7 +479,7 @@ class nwUnicode:
|
|
481
479
|
H_EMDASH = "—"
|
482
480
|
H_HBAR = "―"
|
483
481
|
H_HELLIP = "…"
|
484
|
-
|
482
|
+
H_MAPOS = "ʼ"
|
485
483
|
H_PRIME = "′"
|
486
484
|
H_DPRIME = "″"
|
487
485
|
|
@@ -546,7 +544,7 @@ class nwHtmlUnicode():
|
|
546
544
|
nwUnicode.U_EMDASH: nwUnicode.H_EMDASH,
|
547
545
|
nwUnicode.U_HBAR: nwUnicode.H_HBAR,
|
548
546
|
nwUnicode.U_HELLIP: nwUnicode.H_HELLIP,
|
549
|
-
nwUnicode.
|
547
|
+
nwUnicode.U_MAPOS: nwUnicode.H_MAPOS,
|
550
548
|
nwUnicode.U_PRIME: nwUnicode.H_PRIME,
|
551
549
|
nwUnicode.U_DPRIME: nwUnicode.H_DPRIME,
|
552
550
|
|
@@ -82,6 +82,7 @@ SETTINGS_TEMPLATE = {
|
|
82
82
|
"format.stripUnicode": (bool, False),
|
83
83
|
"format.replaceTabs": (bool, False),
|
84
84
|
"format.keepBreaks": (bool, True),
|
85
|
+
"format.showDialogue": (bool, False),
|
85
86
|
"format.firstLineIndent": (bool, False),
|
86
87
|
"format.firstIndentWidth": (float, 1.4),
|
87
88
|
"format.indentFirstPar": (bool, False),
|
@@ -131,6 +132,7 @@ SETTINGS_LABELS = {
|
|
131
132
|
"format.stripUnicode": QT_TRANSLATE_NOOP("Builds", "Replace Unicode Characters"),
|
132
133
|
"format.replaceTabs": QT_TRANSLATE_NOOP("Builds", "Replace Tabs with Spaces"),
|
133
134
|
"format.keepBreaks": QT_TRANSLATE_NOOP("Builds", "Preserve Hard Line Breaks"),
|
135
|
+
"format.showDialogue": QT_TRANSLATE_NOOP("Builds", "Apply Dialogue Highlighting"),
|
134
136
|
|
135
137
|
"format.grpParIndent": QT_TRANSLATE_NOOP("Builds", "First Line Indent"),
|
136
138
|
"format.firstLineIndent": QT_TRANSLATE_NOOP("Builds", "Enable Indent"),
|
@@ -217,7 +219,7 @@ class BuildSettings:
|
|
217
219
|
return self._order
|
218
220
|
|
219
221
|
@property
|
220
|
-
def
|
222
|
+
def lastBuildPath(self) -> Path:
|
221
223
|
"""The last used build path."""
|
222
224
|
if self._path.is_dir():
|
223
225
|
return self._path
|
@@ -291,7 +293,7 @@ class BuildSettings:
|
|
291
293
|
self._order = value
|
292
294
|
return
|
293
295
|
|
294
|
-
def
|
296
|
+
def setLastBuildPath(self, path: Path | str | None) -> None:
|
295
297
|
"""Set the last used build path."""
|
296
298
|
if isinstance(path, str):
|
297
299
|
path = Path(path)
|
@@ -459,7 +461,7 @@ class BuildSettings:
|
|
459
461
|
self.setName(data.get("name", ""))
|
460
462
|
self.setBuildID(data.get("uuid", ""))
|
461
463
|
self.setOrder(data.get("order", 0))
|
462
|
-
self.
|
464
|
+
self.setLastBuildPath(data.get("path", None))
|
463
465
|
self.setLastBuildName(data.get("build", ""))
|
464
466
|
|
465
467
|
buildFmt = str(data.get("format", ""))
|
novelwriter/core/coretools.py
CHANGED
@@ -348,7 +348,9 @@ class DocSearch:
|
|
348
348
|
rxMatch = rxItt.next()
|
349
349
|
pos = rxMatch.capturedStart()
|
350
350
|
num = rxMatch.capturedLength()
|
351
|
-
|
351
|
+
lim = text[:pos].rfind("\n") + 1
|
352
|
+
cut = text[lim:pos].rfind(" ") + lim + 1
|
353
|
+
context = text[cut:cut+100].partition("\n")[0]
|
352
354
|
if context:
|
353
355
|
results.append((pos, num, context))
|
354
356
|
count += 1
|
novelwriter/core/docbuild.py
CHANGED
@@ -338,6 +338,7 @@ class NWBuildDocument:
|
|
338
338
|
bldObj.setJustify(self._build.getBool("format.justifyText"))
|
339
339
|
bldObj.setLineHeight(self._build.getFloat("format.lineHeight"))
|
340
340
|
bldObj.setKeepLineBreaks(self._build.getBool("format.keepBreaks"))
|
341
|
+
bldObj.setDialogueHighlight(self._build.getBool("format.showDialogue"))
|
341
342
|
bldObj.setFirstLineIndent(
|
342
343
|
self._build.getBool("format.firstLineIndent"),
|
343
344
|
self._build.getFloat("format.firstIndentWidth"),
|
novelwriter/core/project.py
CHANGED
@@ -254,13 +254,23 @@ class NWProject:
|
|
254
254
|
status = self._storage.initProjectStorage(projPath, clearLock)
|
255
255
|
if status != NWStorageOpen.READY:
|
256
256
|
if status == NWStorageOpen.UNKOWN:
|
257
|
-
SHARED.error(
|
257
|
+
SHARED.error(
|
258
|
+
self.tr("Not a known project file format."),
|
259
|
+
info=self.tr("Path: {0}").format(str(projPath))
|
260
|
+
)
|
258
261
|
elif status == NWStorageOpen.NOT_FOUND:
|
259
|
-
SHARED.error(
|
262
|
+
SHARED.error(
|
263
|
+
self.tr("Project file not found."),
|
264
|
+
info=self.tr("Path: {0}").format(str(projPath))
|
265
|
+
)
|
266
|
+
elif status == NWStorageOpen.FAILED:
|
267
|
+
SHARED.error(
|
268
|
+
self.tr("Failed to open project."),
|
269
|
+
info=self.tr("Path: {0}").format(str(projPath)),
|
270
|
+
exc=self._storage.exc
|
271
|
+
)
|
260
272
|
elif status == NWStorageOpen.LOCKED:
|
261
273
|
self._state = NWProjectState.LOCKED
|
262
|
-
elif status == NWStorageOpen.FAILED:
|
263
|
-
SHARED.error(self.tr("Failed to open project."), exc=self._storage.exc)
|
264
274
|
return False
|
265
275
|
|
266
276
|
# Read Project XML
|
@@ -346,6 +356,7 @@ class NWProject:
|
|
346
356
|
self.setProjectChanged(False)
|
347
357
|
self._valid = True
|
348
358
|
self._state = NWProjectState.READY
|
359
|
+
self._storage.lockSession() # Lock only after a successful open. See issue #1977.
|
349
360
|
|
350
361
|
SHARED.newStatusMessage(self.tr("Opened Project: {0}").format(self._data.name))
|
351
362
|
|
novelwriter/core/status.py
CHANGED
@@ -121,7 +121,7 @@ class NWStatus:
|
|
121
121
|
return key
|
122
122
|
|
123
123
|
def update(self, update: list[tuple[str | None, StatusEntry]]) -> None:
|
124
|
-
"""Update the list of statuses
|
124
|
+
"""Update the list of statuses."""
|
125
125
|
self._store.clear()
|
126
126
|
for key, entry in update:
|
127
127
|
self._store[self._checkKey(key)] = entry
|
@@ -130,6 +130,9 @@ class NWStatus:
|
|
130
130
|
if self._default not in self._store:
|
131
131
|
self._default = next(iter(self._store)) if self._store else None
|
132
132
|
|
133
|
+
# Emit the change signal
|
134
|
+
SHARED.projectSingalProxy({"event": "statusLabels", "kind": self._prefix})
|
135
|
+
|
133
136
|
return
|
134
137
|
|
135
138
|
def check(self, value: str) -> str:
|
novelwriter/core/storage.py
CHANGED
@@ -235,7 +235,6 @@ class NWStorage:
|
|
235
235
|
if child.is_dir() and child.name.startswith("data_"):
|
236
236
|
legacy.legacyDataFolder(basePath, child)
|
237
237
|
|
238
|
-
self._writeLockFile()
|
239
238
|
self._ready = True
|
240
239
|
|
241
240
|
return NWStorageOpen.READY
|
@@ -249,6 +248,12 @@ class NWStorage:
|
|
249
248
|
return True
|
250
249
|
return True
|
251
250
|
|
251
|
+
def lockSession(self) -> None:
|
252
|
+
"""Lock the session when the project is successfully opened."""
|
253
|
+
if self._ready:
|
254
|
+
self._writeLockFile()
|
255
|
+
return
|
256
|
+
|
252
257
|
def closeSession(self) -> None:
|
253
258
|
"""Run tasks related to closing the session."""
|
254
259
|
self._clearLockFile()
|