novelWriter 2.4.3__py3-none-any.whl → 2.5b1__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.4.3.dist-info → novelWriter-2.5b1.dist-info}/METADATA +4 -5
- {novelWriter-2.4.3.dist-info → novelWriter-2.5b1.dist-info}/RECORD +109 -101
- novelwriter/__init__.py +33 -39
- novelwriter/assets/i18n/project_en_GB.json +1 -0
- novelwriter/assets/icons/typicons_dark/icons.conf +2 -0
- novelwriter/assets/icons/typicons_dark/nw_font.svg +4 -0
- novelwriter/assets/icons/typicons_dark/nw_quote.svg +4 -0
- novelwriter/assets/icons/typicons_light/icons.conf +2 -0
- novelwriter/assets/icons/typicons_light/nw_font.svg +4 -0
- novelwriter/assets/icons/typicons_light/nw_quote.svg +4 -0
- novelwriter/assets/manual.pdf +0 -0
- novelwriter/assets/sample.zip +0 -0
- novelwriter/assets/syntax/cyberpunk_night.conf +5 -3
- novelwriter/assets/syntax/default_dark.conf +32 -18
- novelwriter/assets/syntax/default_light.conf +24 -10
- novelwriter/assets/syntax/dracula.conf +44 -0
- novelwriter/assets/syntax/grey_dark.conf +5 -4
- novelwriter/assets/syntax/grey_light.conf +5 -4
- novelwriter/assets/syntax/light_owl.conf +7 -6
- novelwriter/assets/syntax/night_owl.conf +7 -6
- novelwriter/assets/syntax/snazzy.conf +42 -0
- novelwriter/assets/syntax/solarized_dark.conf +4 -3
- novelwriter/assets/syntax/solarized_light.conf +4 -3
- novelwriter/assets/syntax/tango.conf +27 -11
- novelwriter/assets/syntax/tomorrow.conf +6 -5
- novelwriter/assets/syntax/tomorrow_night.conf +7 -6
- novelwriter/assets/syntax/tomorrow_night_blue.conf +6 -5
- novelwriter/assets/syntax/tomorrow_night_bright.conf +6 -5
- novelwriter/assets/syntax/tomorrow_night_eighties.conf +6 -5
- novelwriter/assets/text/credits_en.htm +4 -1
- novelwriter/assets/themes/cyberpunk_night.conf +2 -0
- novelwriter/assets/themes/default_dark.conf +1 -0
- novelwriter/assets/themes/default_light.conf +1 -0
- novelwriter/assets/themes/dracula.conf +47 -0
- novelwriter/assets/themes/solarized_dark.conf +1 -0
- novelwriter/assets/themes/solarized_light.conf +1 -0
- novelwriter/common.py +31 -9
- novelwriter/config.py +118 -84
- novelwriter/constants.py +40 -26
- novelwriter/core/buildsettings.py +63 -66
- novelwriter/core/coretools.py +2 -22
- novelwriter/core/docbuild.py +51 -40
- novelwriter/core/document.py +3 -5
- novelwriter/core/index.py +115 -45
- novelwriter/core/item.py +8 -19
- novelwriter/core/options.py +2 -4
- novelwriter/core/project.py +23 -57
- novelwriter/core/projectdata.py +1 -3
- novelwriter/core/projectxml.py +12 -15
- novelwriter/core/sessions.py +3 -5
- novelwriter/core/spellcheck.py +4 -9
- novelwriter/core/status.py +211 -164
- novelwriter/core/storage.py +0 -8
- novelwriter/core/tohtml.py +94 -100
- novelwriter/core/tokenizer.py +199 -112
- novelwriter/core/{tomd.py → tomarkdown.py} +97 -78
- novelwriter/core/toodt.py +212 -148
- novelwriter/core/toqdoc.py +403 -0
- novelwriter/core/tree.py +5 -7
- novelwriter/dialogs/about.py +3 -5
- novelwriter/dialogs/docmerge.py +1 -3
- novelwriter/dialogs/docsplit.py +1 -3
- novelwriter/dialogs/editlabel.py +0 -2
- novelwriter/dialogs/preferences.py +111 -88
- novelwriter/dialogs/projectsettings.py +216 -180
- novelwriter/dialogs/quotes.py +3 -4
- novelwriter/dialogs/wordlist.py +3 -9
- novelwriter/enum.py +31 -25
- novelwriter/error.py +8 -15
- novelwriter/extensions/circularprogress.py +5 -6
- novelwriter/extensions/configlayout.py +18 -18
- novelwriter/extensions/eventfilters.py +1 -5
- novelwriter/extensions/modified.py +50 -13
- novelwriter/extensions/novelselector.py +1 -3
- novelwriter/extensions/pagedsidebar.py +9 -12
- novelwriter/extensions/simpleprogress.py +1 -3
- novelwriter/extensions/statusled.py +1 -3
- novelwriter/extensions/switch.py +4 -6
- novelwriter/extensions/switchbox.py +7 -6
- novelwriter/extensions/versioninfo.py +3 -9
- novelwriter/gui/doceditor.py +98 -126
- novelwriter/gui/dochighlight.py +237 -183
- novelwriter/gui/docviewer.py +46 -94
- novelwriter/gui/docviewerpanel.py +3 -10
- novelwriter/gui/editordocument.py +1 -3
- novelwriter/gui/itemdetails.py +7 -11
- novelwriter/gui/mainmenu.py +11 -7
- novelwriter/gui/noveltree.py +11 -24
- novelwriter/gui/outline.py +11 -23
- novelwriter/gui/projtree.py +26 -43
- novelwriter/gui/search.py +1 -3
- novelwriter/gui/sidebar.py +2 -6
- novelwriter/gui/statusbar.py +6 -10
- novelwriter/gui/theme.py +23 -48
- novelwriter/guimain.py +50 -71
- novelwriter/shared.py +30 -15
- novelwriter/tools/dictionaries.py +8 -12
- novelwriter/tools/lipsum.py +2 -4
- novelwriter/tools/manusbuild.py +1 -3
- novelwriter/tools/manuscript.py +66 -145
- novelwriter/tools/manussettings.py +67 -73
- novelwriter/tools/noveldetails.py +6 -11
- novelwriter/tools/welcome.py +2 -16
- novelwriter/tools/writingstats.py +6 -9
- novelwriter/types.py +45 -3
- {novelWriter-2.4.3.dist-info → novelWriter-2.5b1.dist-info}/LICENSE.md +0 -0
- {novelWriter-2.4.3.dist-info → novelWriter-2.5b1.dist-info}/WHEEL +0 -0
- {novelWriter-2.4.3.dist-info → novelWriter-2.5b1.dist-info}/entry_points.txt +0 -0
- {novelWriter-2.4.3.dist-info → novelWriter-2.5b1.dist-info}/top_level.txt +0 -0
novelwriter/core/index.py
CHANGED
@@ -29,17 +29,20 @@ from __future__ import annotations
|
|
29
29
|
|
30
30
|
import json
|
31
31
|
import logging
|
32
|
+
import random
|
32
33
|
|
33
|
-
from time import time
|
34
|
-
from typing import TYPE_CHECKING
|
35
|
-
from pathlib import Path
|
36
34
|
from collections.abc import ItemsView, Iterable
|
35
|
+
from pathlib import Path
|
36
|
+
from time import time
|
37
|
+
from typing import TYPE_CHECKING, Literal
|
37
38
|
|
38
39
|
from novelwriter import SHARED
|
39
|
-
from novelwriter.
|
40
|
+
from novelwriter.common import (
|
41
|
+
checkInt, isHandle, isItemClass, isListInstance, isTitleTag, jsonEncode
|
42
|
+
)
|
43
|
+
from novelwriter.constants import nwFiles, nwHeaders, nwKeyWords
|
44
|
+
from novelwriter.enum import nwComment, nwItemClass, nwItemLayout, nwItemType
|
40
45
|
from novelwriter.error import logException
|
41
|
-
from novelwriter.common import checkInt, isHandle, isItemClass, isTitleTag, jsonEncode
|
42
|
-
from novelwriter.constants import nwFiles, nwKeyWords, nwHeaders
|
43
46
|
from novelwriter.text.counting import standardCounter
|
44
47
|
|
45
48
|
if TYPE_CHECKING: # pragma: no cover
|
@@ -48,7 +51,12 @@ if TYPE_CHECKING: # pragma: no cover
|
|
48
51
|
|
49
52
|
logger = logging.getLogger(__name__)
|
50
53
|
|
51
|
-
|
54
|
+
T_NoteTypes = Literal["footnotes", "comments"]
|
55
|
+
|
56
|
+
TT_NONE = "T0000" # Default title key
|
57
|
+
MAX_RETRY = 1000 # Key generator recursion limit
|
58
|
+
KEY_SOURCE = "0123456789bcdfghjklmnpqrstvwxz"
|
59
|
+
NOTE_TYPES: list[T_NoteTypes] = ["footnotes", "comments"]
|
52
60
|
|
53
61
|
|
54
62
|
class NWIndex:
|
@@ -301,9 +309,9 @@ class NWIndex:
|
|
301
309
|
|
302
310
|
def _scanActive(self, tHandle: str, nwItem: NWItem, text: str, tags: dict[str, bool]) -> None:
|
303
311
|
"""Scan an active document for meta data."""
|
304
|
-
nTitle = 0
|
305
|
-
cTitle = TT_NONE
|
306
|
-
pTitle = TT_NONE
|
312
|
+
nTitle = 0 # Line Number of the previous title
|
313
|
+
cTitle = TT_NONE # Tag of the current title
|
314
|
+
pTitle = TT_NONE # Tag of the previous title
|
307
315
|
canSetHead = True # First heading has not yet been set
|
308
316
|
|
309
317
|
lines = text.splitlines()
|
@@ -335,10 +343,11 @@ class NWIndex:
|
|
335
343
|
self._indexKeyword(tHandle, line, cTitle, nwItem.itemClass, tags)
|
336
344
|
|
337
345
|
elif line.startswith("%"):
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
346
|
+
cStyle, cKey, cText, _, _ = processComment(line)
|
347
|
+
if cStyle in (nwComment.SYNOPSIS, nwComment.SHORT):
|
348
|
+
self._itemIndex.setHeadingSynopsis(tHandle, cTitle, cText)
|
349
|
+
elif cStyle == nwComment.FOOTNOTE:
|
350
|
+
self._itemIndex.addNoteKey(tHandle, "footnotes", cKey)
|
342
351
|
|
343
352
|
# Count words for remaining text after last heading
|
344
353
|
if pTitle != TT_NONE:
|
@@ -506,6 +515,14 @@ class NWIndex:
|
|
506
515
|
name, _, display = text.partition("|")
|
507
516
|
return name.rstrip(), display.lstrip()
|
508
517
|
|
518
|
+
def newCommentKey(self, tHandle: str, style: nwComment) -> str:
|
519
|
+
"""Generate a new key for a comment style."""
|
520
|
+
if style == nwComment.FOOTNOTE:
|
521
|
+
return self._itemIndex.genNewNoteKey(tHandle, "footnotes")
|
522
|
+
elif style == nwComment.COMMENT:
|
523
|
+
return self._itemIndex.genNewNoteKey(tHandle, "comments")
|
524
|
+
return "err"
|
525
|
+
|
509
526
|
##
|
510
527
|
# Extract Data
|
511
528
|
##
|
@@ -690,12 +707,9 @@ class NWIndex:
|
|
690
707
|
return tName, tClass, iItem, None if iItem is None else iItem[tHeading]
|
691
708
|
return "", "", None, None
|
692
709
|
|
693
|
-
# END Class NWIndex
|
694
|
-
|
695
710
|
|
696
|
-
#
|
697
|
-
#
|
698
|
-
# =============================================================================================== #
|
711
|
+
# The Tags Index Object
|
712
|
+
# =====================
|
699
713
|
|
700
714
|
class TagsIndex:
|
701
715
|
"""Core: Tags Index Wrapper Class
|
@@ -790,7 +804,7 @@ class TagsIndex:
|
|
790
804
|
|
791
805
|
for key, entry in data.items():
|
792
806
|
if not isinstance(key, str):
|
793
|
-
raise ValueError("tagsIndex
|
807
|
+
raise ValueError("tagsIndex key must be a string")
|
794
808
|
if not isinstance(entry, dict):
|
795
809
|
raise ValueError("tagsIndex entry is not a dict")
|
796
810
|
|
@@ -815,12 +829,9 @@ class TagsIndex:
|
|
815
829
|
|
816
830
|
return
|
817
831
|
|
818
|
-
# END Class TagsIndex
|
819
|
-
|
820
832
|
|
821
|
-
#
|
822
|
-
#
|
823
|
-
# =============================================================================================== #
|
833
|
+
# The Item Index Objects
|
834
|
+
# ======================
|
824
835
|
|
825
836
|
class ItemIndex:
|
826
837
|
"""Core: Item Index Wrapper Class
|
@@ -950,6 +961,25 @@ class ItemIndex:
|
|
950
961
|
self._items[tHandle].addHeadingRef(sTitle, tagKeys, refType)
|
951
962
|
return
|
952
963
|
|
964
|
+
def addNoteKey(self, tHandle: str, style: T_NoteTypes, key: str) -> None:
|
965
|
+
"""Set notes key for a given item."""
|
966
|
+
if tHandle in self._items:
|
967
|
+
self._items[tHandle].addNoteKey(style, key)
|
968
|
+
return
|
969
|
+
|
970
|
+
def genNewNoteKey(self, tHandle: str, style: T_NoteTypes) -> str:
|
971
|
+
"""Set notes key for a given item."""
|
972
|
+
if style in NOTE_TYPES and (item := self._items.get(tHandle)):
|
973
|
+
keys = set()
|
974
|
+
for entry in self._items.values():
|
975
|
+
keys.update(entry.noteKeys(style))
|
976
|
+
for _ in range(MAX_RETRY):
|
977
|
+
key = style[:1] + "".join(random.choices(KEY_SOURCE, k=4))
|
978
|
+
if key not in keys:
|
979
|
+
item.addNoteKey(style, key)
|
980
|
+
return key
|
981
|
+
return "err"
|
982
|
+
|
953
983
|
##
|
954
984
|
# Pack/Unpack
|
955
985
|
##
|
@@ -978,8 +1008,6 @@ class ItemIndex:
|
|
978
1008
|
|
979
1009
|
return
|
980
1010
|
|
981
|
-
# END Class ItemIndex
|
982
|
-
|
983
1011
|
|
984
1012
|
class IndexItem:
|
985
1013
|
"""Core: Single Index Item Class
|
@@ -991,12 +1019,13 @@ class IndexItem:
|
|
991
1019
|
must be reset each time the item is re-indexed.
|
992
1020
|
"""
|
993
1021
|
|
994
|
-
__slots__ = ("_handle", "_item", "_headings", "_count")
|
1022
|
+
__slots__ = ("_handle", "_item", "_headings", "_count", "_notes")
|
995
1023
|
|
996
1024
|
def __init__(self, tHandle: str, nwItem: NWItem) -> None:
|
997
1025
|
self._handle = tHandle
|
998
1026
|
self._item = nwItem
|
999
1027
|
self._headings: dict[str, IndexHeading] = {TT_NONE: IndexHeading(TT_NONE)}
|
1028
|
+
self._notes: dict[str, set[str]] = {}
|
1000
1029
|
self._count = 0
|
1001
1030
|
return
|
1002
1031
|
|
@@ -1064,6 +1093,13 @@ class IndexItem:
|
|
1064
1093
|
self._headings[sTitle].addReference(tagKey, refType)
|
1065
1094
|
return
|
1066
1095
|
|
1096
|
+
def addNoteKey(self, style: T_NoteTypes, key: str) -> None:
|
1097
|
+
"""Add a note key to the index."""
|
1098
|
+
if style not in self._notes:
|
1099
|
+
self._notes[style] = set()
|
1100
|
+
self._notes[style].add(key)
|
1101
|
+
return
|
1102
|
+
|
1067
1103
|
##
|
1068
1104
|
# Data Methods
|
1069
1105
|
##
|
@@ -1085,6 +1121,10 @@ class IndexItem:
|
|
1085
1121
|
self._count += 1
|
1086
1122
|
return f"T{self._count:04d}"
|
1087
1123
|
|
1124
|
+
def noteKeys(self, style: T_NoteTypes) -> set[str]:
|
1125
|
+
"""Return a set of all note keys."""
|
1126
|
+
return self._notes.get(style, set())
|
1127
|
+
|
1088
1128
|
##
|
1089
1129
|
# Pack/Unpack
|
1090
1130
|
##
|
@@ -1103,6 +1143,8 @@ class IndexItem:
|
|
1103
1143
|
data["headings"] = heads
|
1104
1144
|
if refs:
|
1105
1145
|
data["references"] = refs
|
1146
|
+
if self._notes:
|
1147
|
+
data["notes"] = {style: list(keys) for style, keys in self._notes.items()}
|
1106
1148
|
|
1107
1149
|
return data
|
1108
1150
|
|
@@ -1116,9 +1158,15 @@ class IndexItem:
|
|
1116
1158
|
tHeading.unpackData(hData)
|
1117
1159
|
tHeading.unpackReferences(references.get(sTitle, {}))
|
1118
1160
|
self.addHeading(tHeading)
|
1119
|
-
return
|
1120
1161
|
|
1121
|
-
|
1162
|
+
for style, keys in data.get("notes", {}).items():
|
1163
|
+
if style not in NOTE_TYPES:
|
1164
|
+
raise ValueError("The notes style is invalid")
|
1165
|
+
if not isListInstance(keys, str):
|
1166
|
+
raise ValueError("The notes keys must be a list of strings")
|
1167
|
+
self._notes[style] = set(keys)
|
1168
|
+
|
1169
|
+
return
|
1122
1170
|
|
1123
1171
|
|
1124
1172
|
class IndexHeading:
|
@@ -1295,25 +1343,47 @@ class IndexHeading:
|
|
1295
1343
|
raise ValueError("The itemIndex contains an invalid reference type")
|
1296
1344
|
return
|
1297
1345
|
|
1298
|
-
# END Class IndexHeading
|
1299
|
-
|
1300
1346
|
|
1301
|
-
#
|
1302
|
-
#
|
1303
|
-
# =============================================================================================== #
|
1347
|
+
# Text Processing Functions
|
1348
|
+
# =========================
|
1304
1349
|
|
1305
|
-
|
1306
|
-
"short": nwComment.SHORT,
|
1350
|
+
MODIFIERS = {
|
1307
1351
|
"synopsis": nwComment.SYNOPSIS,
|
1352
|
+
"short": nwComment.SHORT,
|
1353
|
+
"note": nwComment.NOTE,
|
1354
|
+
"footnote": nwComment.FOOTNOTE,
|
1308
1355
|
}
|
1356
|
+
KEY_REQ = {
|
1357
|
+
"synopsis": 0, # Key not allowed
|
1358
|
+
"short": 0, # Key not allowed
|
1359
|
+
"note": 1, # Key optional
|
1360
|
+
"footnote": 2, # Key required
|
1361
|
+
}
|
1362
|
+
|
1363
|
+
|
1364
|
+
def _checkModKey(modifier: str, key: str) -> bool:
|
1365
|
+
"""Check if a modifier and key set are ok."""
|
1366
|
+
if modifier in MODIFIERS:
|
1367
|
+
if key == "":
|
1368
|
+
return KEY_REQ[modifier] < 2
|
1369
|
+
elif key.replace("_", "").isalnum():
|
1370
|
+
return KEY_REQ[modifier] > 0
|
1371
|
+
return False
|
1309
1372
|
|
1310
1373
|
|
1311
|
-
def processComment(text: str) -> tuple[nwComment, str, int]:
|
1312
|
-
"""Extract comment style and text. Should only be called on
|
1313
|
-
starting with a %.
|
1374
|
+
def processComment(text: str) -> tuple[nwComment, str, str, int, int]:
|
1375
|
+
"""Extract comment style, key and text. Should only be called on
|
1376
|
+
text starting with a %.
|
1314
1377
|
"""
|
1315
|
-
|
1316
|
-
|
1317
|
-
|
1318
|
-
|
1319
|
-
|
1378
|
+
if text[:2] == "%~":
|
1379
|
+
return nwComment.IGNORE, "", text[2:].lstrip(), 0, 0
|
1380
|
+
|
1381
|
+
check = text[1:].strip()
|
1382
|
+
start, _, content = check.partition(":")
|
1383
|
+
modifier, _, key = start.rstrip().partition(".")
|
1384
|
+
if content and (clean := modifier.lower()) and _checkModKey(clean, key):
|
1385
|
+
col = text.find(":") + 1
|
1386
|
+
dot = text.find(".", 0, col) + 1
|
1387
|
+
return MODIFIERS[clean], key, content.lstrip(), dot, col
|
1388
|
+
|
1389
|
+
return nwComment.PLAIN, "", check, 0, 0
|
novelwriter/core/item.py
CHANGED
@@ -25,15 +25,16 @@ from __future__ import annotations
|
|
25
25
|
|
26
26
|
import logging
|
27
27
|
|
28
|
-
from typing import TYPE_CHECKING, Any
|
28
|
+
from typing import TYPE_CHECKING, Any
|
29
29
|
|
30
30
|
from PyQt5.QtGui import QIcon
|
31
31
|
|
32
|
-
from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout
|
33
32
|
from novelwriter.common import (
|
34
|
-
checkInt, isHandle, isItemClass, isItemLayout, isItemType, simplified,
|
33
|
+
checkInt, isHandle, isItemClass, isItemLayout, isItemType, simplified,
|
34
|
+
yesNo
|
35
35
|
)
|
36
36
|
from novelwriter.constants import nwHeaders, nwLabels, trConst
|
37
|
+
from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType
|
37
38
|
|
38
39
|
if TYPE_CHECKING: # pragma: no cover
|
39
40
|
from novelwriter.core.project import NWProject
|
@@ -308,25 +309,15 @@ class NWItem:
|
|
308
309
|
|
309
310
|
return trConst(nwLabels.ITEM_DESCRIPTION.get(descKey, ""))
|
310
311
|
|
311
|
-
|
312
|
-
def getImportStatus(self, incIcon: Literal[True] = True) -> tuple[str, QIcon]:
|
313
|
-
pass
|
314
|
-
|
315
|
-
@overload # pragma: no cover
|
316
|
-
def getImportStatus(self, incIcon: Literal[False]) -> tuple[str, None]:
|
317
|
-
pass
|
318
|
-
|
319
|
-
def getImportStatus(self, incIcon=True):
|
312
|
+
def getImportStatus(self) -> tuple[str, QIcon]:
|
320
313
|
"""Return the relevant importance or status label and icon for
|
321
314
|
the current item based on its class.
|
322
315
|
"""
|
323
316
|
if self.isNovelLike():
|
324
|
-
|
325
|
-
stIcon = self._project.data.itemStatus.icon(self._status) if incIcon else None
|
317
|
+
entry = self._project.data.itemStatus[self._status]
|
326
318
|
else:
|
327
|
-
|
328
|
-
|
329
|
-
return stName, stIcon
|
319
|
+
entry = self._project.data.itemImport[self._import]
|
320
|
+
return entry.name, entry.icon
|
330
321
|
|
331
322
|
##
|
332
323
|
# Checker Methods
|
@@ -567,5 +558,3 @@ class NWItem:
|
|
567
558
|
"""Save the initial word count."""
|
568
559
|
self._initCount = self._wordCount
|
569
560
|
return
|
570
|
-
|
571
|
-
# END Class NWItem
|
novelwriter/core/options.py
CHANGED
@@ -28,12 +28,12 @@ import json
|
|
28
28
|
import logging
|
29
29
|
|
30
30
|
from enum import Enum
|
31
|
-
from typing import TYPE_CHECKING, Any, TypeVar
|
32
31
|
from pathlib import Path
|
32
|
+
from typing import TYPE_CHECKING, Any, TypeVar
|
33
33
|
|
34
|
-
from novelwriter.error import logException
|
35
34
|
from novelwriter.common import checkBool, checkFloat, checkInt, checkString, jsonEncode
|
36
35
|
from novelwriter.constants import nwFiles
|
36
|
+
from novelwriter.error import logException
|
37
37
|
|
38
38
|
if TYPE_CHECKING: # pragma: no cover
|
39
39
|
from novelwriter.core.project import NWProject
|
@@ -218,5 +218,3 @@ class OptionState:
|
|
218
218
|
if value in lookup.__members__:
|
219
219
|
return lookup[value]
|
220
220
|
return default
|
221
|
-
|
222
|
-
# END Class OptionState
|
novelwriter/core/project.py
CHANGED
@@ -26,33 +26,33 @@ from __future__ import annotations
|
|
26
26
|
import json
|
27
27
|
import logging
|
28
28
|
|
29
|
+
from collections.abc import Iterable
|
29
30
|
from enum import Enum
|
31
|
+
from functools import partial
|
32
|
+
from pathlib import Path
|
30
33
|
from time import time
|
31
34
|
from typing import TYPE_CHECKING
|
32
|
-
from pathlib import Path
|
33
|
-
from functools import partial
|
34
|
-
from collections.abc import Iterable
|
35
35
|
|
36
36
|
from PyQt5.QtCore import QCoreApplication
|
37
37
|
|
38
|
-
from novelwriter import CONFIG, SHARED,
|
39
|
-
from novelwriter.
|
40
|
-
|
41
|
-
|
42
|
-
|
38
|
+
from novelwriter import CONFIG, SHARED, __hexversion__, __version__
|
39
|
+
from novelwriter.common import (
|
40
|
+
checkStringNone, formatInt, formatTimeStamp, getFileSize, hexToInt,
|
41
|
+
makeFileNameSafe, minmax
|
42
|
+
)
|
43
|
+
from novelwriter.constants import nwLabels, trConst
|
43
44
|
from novelwriter.core.index import NWIndex
|
44
45
|
from novelwriter.core.options import OptionState
|
45
|
-
from novelwriter.core.storage import NWStorage, NWStorageOpen
|
46
|
-
from novelwriter.core.sessions import NWSessionLog
|
47
|
-
from novelwriter.core.projectxml import ProjectXMLReader, ProjectXMLWriter, XMLReadState
|
48
46
|
from novelwriter.core.projectdata import NWProjectData
|
49
|
-
from novelwriter.
|
50
|
-
|
51
|
-
|
47
|
+
from novelwriter.core.projectxml import ProjectXMLReader, ProjectXMLWriter, XMLReadState
|
48
|
+
from novelwriter.core.sessions import NWSessionLog
|
49
|
+
from novelwriter.core.storage import NWStorage, NWStorageOpen
|
50
|
+
from novelwriter.core.tree import NWTree
|
51
|
+
from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType
|
52
|
+
from novelwriter.error import logException
|
52
53
|
|
53
54
|
if TYPE_CHECKING: # pragma: no cover
|
54
55
|
from novelwriter.core.item import NWItem
|
55
|
-
from novelwriter.core.status import NWStatus
|
56
56
|
|
57
57
|
logger = logging.getLogger(__name__)
|
58
58
|
|
@@ -64,8 +64,6 @@ class NWProjectState(Enum):
|
|
64
64
|
RECOVERY = 2
|
65
65
|
READY = 3
|
66
66
|
|
67
|
-
# END Enum NWProjectState
|
68
|
-
|
69
67
|
|
70
68
|
class NWProject:
|
71
69
|
|
@@ -461,14 +459,14 @@ class NWProject:
|
|
461
459
|
|
462
460
|
def setDefaultStatusImport(self) -> None:
|
463
461
|
"""Set the default status and importance values."""
|
464
|
-
self._data.itemStatus.
|
465
|
-
self._data.itemStatus.
|
466
|
-
self._data.itemStatus.
|
467
|
-
self._data.itemStatus.
|
468
|
-
self._data.itemImport.
|
469
|
-
self._data.itemImport.
|
470
|
-
self._data.itemImport.
|
471
|
-
self._data.itemImport.
|
462
|
+
self._data.itemStatus.add(None, self.tr("New"), (100, 100, 100), "SQUARE", 0)
|
463
|
+
self._data.itemStatus.add(None, self.tr("Note"), (200, 50, 0), "SQUARE", 0)
|
464
|
+
self._data.itemStatus.add(None, self.tr("Draft"), (200, 150, 0), "SQUARE", 0)
|
465
|
+
self._data.itemStatus.add(None, self.tr("Finished"), (50, 200, 0), "SQUARE", 0)
|
466
|
+
self._data.itemImport.add(None, self.tr("New"), (100, 100, 100), "SQUARE", 0)
|
467
|
+
self._data.itemImport.add(None, self.tr("Minor"), (200, 50, 0), "SQUARE", 0)
|
468
|
+
self._data.itemImport.add(None, self.tr("Major"), (200, 150, 0), "SQUARE", 0)
|
469
|
+
self._data.itemImport.add(None, self.tr("Main"), (50, 200, 0), "SQUARE", 0)
|
472
470
|
return
|
473
471
|
|
474
472
|
def setProjectLang(self, language: str | None) -> None:
|
@@ -491,14 +489,6 @@ class NWProject:
|
|
491
489
|
self.setProjectChanged(True)
|
492
490
|
return
|
493
491
|
|
494
|
-
def setStatusColours(self, new: list[dict], deleted: list[str]) -> bool:
|
495
|
-
"""Update the list of novel file status flags."""
|
496
|
-
return self._setStatusImport(new, deleted, self._data.itemStatus)
|
497
|
-
|
498
|
-
def setImportColours(self, new: list[dict], deleted: list[str]) -> bool:
|
499
|
-
"""Update the list of note file importance flags."""
|
500
|
-
return self._setStatusImport(new, deleted, self._data.itemImport)
|
501
|
-
|
502
492
|
def setProjectChanged(self, status: bool) -> bool:
|
503
493
|
"""Toggle the project changed flag, and propagate the
|
504
494
|
information to the GUI statusbar.
|
@@ -584,28 +574,6 @@ class NWProject:
|
|
584
574
|
# Internal Functions
|
585
575
|
##
|
586
576
|
|
587
|
-
def _setStatusImport(self, new: list[dict], delete: list[str], target: NWStatus) -> bool:
|
588
|
-
"""Update the list of novel file status or importance flags, and
|
589
|
-
delete those that have been requested deleted.
|
590
|
-
"""
|
591
|
-
if not (new or delete):
|
592
|
-
return False
|
593
|
-
|
594
|
-
order = []
|
595
|
-
for entry in new:
|
596
|
-
key = entry.get("key", None)
|
597
|
-
name = entry.get("name", "")
|
598
|
-
cols = entry.get("cols", (100, 100, 100))
|
599
|
-
if name:
|
600
|
-
order.append(target.write(key, name, cols))
|
601
|
-
|
602
|
-
for key in delete:
|
603
|
-
target.remove(key)
|
604
|
-
|
605
|
-
target.reorder(order)
|
606
|
-
|
607
|
-
return True
|
608
|
-
|
609
577
|
def _loadProjectLocalisation(self) -> bool:
|
610
578
|
"""Load the language data for the current project language."""
|
611
579
|
if self._data.language is None or CONFIG._nwLangPath is None:
|
@@ -626,5 +594,3 @@ class NWProject:
|
|
626
594
|
return False
|
627
595
|
|
628
596
|
return True
|
629
|
-
|
630
|
-
# END Class NWProject
|
novelwriter/core/projectdata.py
CHANGED
@@ -23,8 +23,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
23
23
|
"""
|
24
24
|
from __future__ import annotations
|
25
25
|
|
26
|
-
import uuid
|
27
26
|
import logging
|
27
|
+
import uuid
|
28
28
|
|
29
29
|
from typing import TYPE_CHECKING, Any
|
30
30
|
|
@@ -322,5 +322,3 @@ class NWProjectData:
|
|
322
322
|
self._autoReplace[key] = simplified(entry)
|
323
323
|
self._project.setProjectChanged(True)
|
324
324
|
return
|
325
|
-
|
326
|
-
# END Class NWProjectData
|
novelwriter/core/projectxml.py
CHANGED
@@ -29,24 +29,24 @@ import logging
|
|
29
29
|
import xml.etree.ElementTree as ET
|
30
30
|
|
31
31
|
from enum import Enum
|
32
|
+
from pathlib import Path
|
32
33
|
from time import time
|
33
34
|
from typing import TYPE_CHECKING
|
34
|
-
from pathlib import Path
|
35
35
|
|
36
|
-
from novelwriter import
|
36
|
+
from novelwriter import __hexversion__, __version__
|
37
37
|
from novelwriter.common import (
|
38
38
|
checkBool, checkInt, checkString, checkStringNone, formatTimeStamp,
|
39
39
|
hexToInt, simplified, xmlIndent, yesNo
|
40
40
|
)
|
41
41
|
|
42
42
|
if TYPE_CHECKING: # pragma: no cover
|
43
|
-
from novelwriter.core.status import NWStatus
|
44
43
|
from novelwriter.core.projectdata import NWProjectData
|
44
|
+
from novelwriter.core.status import NWStatus
|
45
45
|
|
46
46
|
logger = logging.getLogger(__name__)
|
47
47
|
|
48
48
|
FILE_VERSION = "1.5" # The current project file format version
|
49
|
-
FILE_REVISION = "
|
49
|
+
FILE_REVISION = "4" # The current project file format revision
|
50
50
|
HEX_VERSION = 0x0105
|
51
51
|
|
52
52
|
NUM_VERSION = {
|
@@ -70,8 +70,6 @@ class XMLReadState(Enum):
|
|
70
70
|
PARSED_OK = 5
|
71
71
|
WAS_LEGACY = 6
|
72
72
|
|
73
|
-
# END Class XMLReadState
|
74
|
-
|
75
73
|
|
76
74
|
class ProjectXMLReader:
|
77
75
|
"""Core: Project XML Reader
|
@@ -109,6 +107,8 @@ class ProjectXMLReader:
|
|
109
107
|
Rev 2: Drops the title node from project and adds the TEMPLATE
|
110
108
|
class for items. 2.3 Beta 1.
|
111
109
|
Rev 3: Added TEMPLATE class. 2.3.
|
110
|
+
Rev 4: Added shape attribute to status and importance entry
|
111
|
+
nodes. 2.5.
|
112
112
|
"""
|
113
113
|
|
114
114
|
def __init__(self, path: str | Path) -> None:
|
@@ -356,8 +356,8 @@ class ProjectXMLReader:
|
|
356
356
|
logger.debug("Parsing <content> section (legacy format)")
|
357
357
|
|
358
358
|
# Create maps to look up name -> key for status and importance
|
359
|
-
|
360
|
-
|
359
|
+
sMap: dict[str | None, str] = {e.name: k for k, e in data.itemStatus.iterItems()}
|
360
|
+
iMap: dict[str | None, str] = {e.name: k for k, e in data.itemImport.iterItems()}
|
361
361
|
|
362
362
|
for xItem in xSection:
|
363
363
|
if xItem.tag != "item":
|
@@ -404,9 +404,9 @@ class ProjectXMLReader:
|
|
404
404
|
|
405
405
|
# Status was split into separate status/import with a key in 1.4
|
406
406
|
if item.get("class", "") in ("NOVEL", "ARCHIVE"):
|
407
|
-
name["status"] =
|
407
|
+
name["status"] = sMap.get(tmpStatus, None)
|
408
408
|
else:
|
409
|
-
name["import"] =
|
409
|
+
name["import"] = iMap.get(tmpStatus, None)
|
410
410
|
|
411
411
|
# A number of layouts were removed in 1.3
|
412
412
|
if item.get("layout", "") in (
|
@@ -436,7 +436,8 @@ class ProjectXMLReader:
|
|
436
436
|
green = checkInt(xEntry.attrib.get("green", 0), 0)
|
437
437
|
blue = checkInt(xEntry.attrib.get("blue", 0), 0)
|
438
438
|
count = checkInt(xEntry.attrib.get("count", 0), 0)
|
439
|
-
|
439
|
+
shape = xEntry.attrib.get("shape", "")
|
440
|
+
sObject.add(key, xEntry.text or "", (red, green, blue), shape, count)
|
440
441
|
return
|
441
442
|
|
442
443
|
def _parseDictKeyText(self, xItem: ET.Element) -> dict:
|
@@ -455,8 +456,6 @@ class ProjectXMLReader:
|
|
455
456
|
"""
|
456
457
|
return {xNode.tag: checkString(xNode.text, "") for xNode in xItem}
|
457
458
|
|
458
|
-
# END Class ProjectXMLReader
|
459
|
-
|
460
459
|
|
461
460
|
class ProjectXMLWriter:
|
462
461
|
"""Core: Project XML Writer
|
@@ -576,5 +575,3 @@ class ProjectXMLWriter:
|
|
576
575
|
xEntry = ET.SubElement(xItem, "entry", attrib={"key": key})
|
577
576
|
xEntry.text = str(value) or ""
|
578
577
|
return
|
579
|
-
|
580
|
-
# END Class ProjectXMLWriter
|
novelwriter/core/sessions.py
CHANGED
@@ -26,14 +26,14 @@ from __future__ import annotations
|
|
26
26
|
import json
|
27
27
|
import logging
|
28
28
|
|
29
|
+
from collections.abc import Iterable
|
30
|
+
from pathlib import Path
|
29
31
|
from time import time
|
30
32
|
from typing import TYPE_CHECKING
|
31
|
-
from pathlib import Path
|
32
|
-
from collections.abc import Iterable
|
33
33
|
|
34
|
-
from novelwriter.error import logException
|
35
34
|
from novelwriter.common import formatTimeStamp
|
36
35
|
from novelwriter.constants import nwFiles
|
36
|
+
from novelwriter.error import logException
|
37
37
|
|
38
38
|
if TYPE_CHECKING: # pragma: no cover
|
39
39
|
from novelwriter.core.project import NWProject
|
@@ -135,5 +135,3 @@ class NWSessionLog:
|
|
135
135
|
"novel": novel, "notes": notes, "idle": idle,
|
136
136
|
})
|
137
137
|
return f"{data}\n"
|
138
|
-
|
139
|
-
# END Class NWSessionLog
|
novelwriter/core/spellcheck.py
CHANGED
@@ -27,14 +27,14 @@ from __future__ import annotations
|
|
27
27
|
import json
|
28
28
|
import logging
|
29
29
|
|
30
|
-
from typing import TYPE_CHECKING
|
31
|
-
from pathlib import Path
|
32
30
|
from collections.abc import Iterator
|
31
|
+
from pathlib import Path
|
32
|
+
from typing import TYPE_CHECKING
|
33
33
|
|
34
34
|
from PyQt5.QtCore import QLocale
|
35
35
|
|
36
|
-
from novelwriter.error import logException
|
37
36
|
from novelwriter.constants import nwFiles
|
37
|
+
from novelwriter.error import logException
|
38
38
|
|
39
39
|
if TYPE_CHECKING: # pragma: no cover
|
40
40
|
from novelwriter.core.project import NWProject
|
@@ -164,11 +164,10 @@ class NWSpellEnchant:
|
|
164
164
|
name = ""
|
165
165
|
return tag, name
|
166
166
|
|
167
|
-
# END Class NWSpellEnchant
|
168
|
-
|
169
167
|
|
170
168
|
class FakeEnchant:
|
171
169
|
"""Fallback for when Enchant is selected, but not installed."""
|
170
|
+
|
172
171
|
def __init__(self) -> None:
|
173
172
|
|
174
173
|
class FakeProvider:
|
@@ -188,8 +187,6 @@ class FakeEnchant:
|
|
188
187
|
def add_to_session(self, word: str) -> None:
|
189
188
|
return
|
190
189
|
|
191
|
-
# END Class FakeEnchant
|
192
|
-
|
193
190
|
|
194
191
|
class UserDictionary:
|
195
192
|
|
@@ -240,5 +237,3 @@ class UserDictionary:
|
|
240
237
|
logger.error("Failed to save user dictionary")
|
241
238
|
logException()
|
242
239
|
return
|
243
|
-
|
244
|
-
# END Class UserDictionary
|