novelWriter 2.7b1__py3-none-any.whl → 2.7.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/__init__.py +17 -4
- novelwriter/assets/i18n/nw_cs_CZ.qm +0 -0
- 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_ru_RU.qm +0 -0
- novelwriter/assets/i18n/nw_zh_CN.qm +0 -0
- novelwriter/assets/i18n/project_cs_CZ.json +2 -0
- novelwriter/assets/i18n/project_de_DE.json +3 -1
- novelwriter/assets/i18n/project_en_GB.json +1 -0
- novelwriter/assets/i18n/project_en_US.json +2 -0
- novelwriter/assets/i18n/project_it_IT.json +2 -0
- novelwriter/assets/i18n/project_ja_JP.json +2 -0
- novelwriter/assets/i18n/project_nb_NO.json +2 -0
- novelwriter/assets/i18n/project_nn_NO.json +5 -0
- novelwriter/assets/i18n/project_pl_PL.json +2 -0
- novelwriter/assets/i18n/project_pt_BR.json +2 -0
- novelwriter/assets/i18n/project_ru_RU.json +2 -0
- novelwriter/assets/i18n/project_zh_CN.json +2 -0
- novelwriter/assets/icons/remix_filled.icons +1 -0
- novelwriter/assets/icons/remix_outline.icons +1 -0
- novelwriter/assets/images/splash.png +0 -0
- novelwriter/assets/manual.pdf +0 -0
- novelwriter/assets/manual_fr.pdf +0 -0
- novelwriter/assets/sample.zip +0 -0
- novelwriter/assets/syntax/snazzy.conf +3 -3
- novelwriter/assets/text/credits_en.htm +6 -0
- novelwriter/assets/themes/snazzy.conf +48 -0
- novelwriter/common.py +10 -1
- novelwriter/config.py +96 -25
- novelwriter/constants.py +21 -0
- novelwriter/core/buildsettings.py +3 -1
- novelwriter/core/coretools.py +41 -34
- novelwriter/core/docbuild.py +1 -0
- novelwriter/core/index.py +35 -5
- novelwriter/core/indexdata.py +4 -1
- novelwriter/core/item.py +35 -24
- novelwriter/core/itemmodel.py +17 -13
- novelwriter/core/novelmodel.py +2 -0
- novelwriter/core/project.py +14 -14
- novelwriter/core/projectdata.py +42 -24
- novelwriter/core/projectxml.py +17 -7
- novelwriter/core/sessions.py +29 -13
- novelwriter/core/tree.py +9 -5
- novelwriter/dialogs/docmerge.py +2 -1
- novelwriter/dialogs/docsplit.py +6 -3
- novelwriter/dialogs/editlabel.py +11 -8
- novelwriter/dialogs/preferences.py +37 -26
- novelwriter/dialogs/projectsettings.py +3 -0
- novelwriter/extensions/configlayout.py +6 -2
- novelwriter/extensions/switch.py +16 -15
- novelwriter/extensions/switchbox.py +1 -0
- novelwriter/formats/tokenizer.py +2 -1
- novelwriter/gui/doceditor.py +106 -47
- novelwriter/gui/noveltree.py +11 -5
- novelwriter/gui/outline.py +9 -1
- novelwriter/gui/projtree.py +1 -0
- novelwriter/gui/search.py +1 -0
- novelwriter/gui/statusbar.py +14 -6
- novelwriter/gui/theme.py +25 -10
- novelwriter/guimain.py +29 -9
- novelwriter/splash.py +74 -0
- novelwriter/tools/lipsum.py +2 -1
- novelwriter/tools/manuscript.py +1 -1
- novelwriter/tools/manussettings.py +52 -20
- novelwriter/tools/noveldetails.py +9 -8
- novelwriter/tools/welcome.py +1 -0
- novelwriter/tools/writingstats.py +68 -45
- {novelwriter-2.7b1.dist-info → novelwriter-2.7.1.dist-info}/METADATA +2 -2
- {novelwriter-2.7b1.dist-info → novelwriter-2.7.1.dist-info}/RECORD +81 -78
- {novelwriter-2.7b1.dist-info → novelwriter-2.7.1.dist-info}/WHEEL +1 -1
- {novelwriter-2.7b1.dist-info → novelwriter-2.7.1.dist-info}/entry_points.txt +0 -0
- {novelwriter-2.7b1.dist-info → novelwriter-2.7.1.dist-info}/licenses/LICENSE.md +0 -0
- {novelwriter-2.7b1.dist-info → novelwriter-2.7.1.dist-info}/top_level.txt +0 -0
novelwriter/constants.py
CHANGED
@@ -184,6 +184,10 @@ class nwKeyWords:
|
|
184
184
|
POV_KEY, FOCUS_KEY, CHAR_KEY, PLOT_KEY, TIME_KEY, WORLD_KEY,
|
185
185
|
OBJECT_KEY, ENTITY_KEY, CUSTOM_KEY,
|
186
186
|
]
|
187
|
+
CAN_LOOKUP: Final[list[str]] = [
|
188
|
+
POV_KEY, FOCUS_KEY, CHAR_KEY, PLOT_KEY, TIME_KEY, WORLD_KEY,
|
189
|
+
OBJECT_KEY, ENTITY_KEY, CUSTOM_KEY, STORY_KEY, MENTION_KEY,
|
190
|
+
]
|
187
191
|
|
188
192
|
# Set of Valid Keys
|
189
193
|
VALID_KEYS: Final[set[str]] = set(ALL_KEYS)
|
@@ -352,6 +356,10 @@ class nwLabels:
|
|
352
356
|
nwStats.WORDS_TEXT: QT_TRANSLATE_NOOP("Stats", "Words in Text"),
|
353
357
|
nwStats.WORDS_TITLE: QT_TRANSLATE_NOOP("Stats", "Words in Headings"),
|
354
358
|
}
|
359
|
+
STATS_DISPLAY: Final[dict[str, str]] = {
|
360
|
+
nwStats.CHARS: QT_TRANSLATE_NOOP("Stats", "Characters: {0} ({1})"),
|
361
|
+
nwStats.WORDS: QT_TRANSLATE_NOOP("Stats", "Words: {0} ({1})"),
|
362
|
+
}
|
355
363
|
BUILD_FMT: Final[dict[nwBuildFmt, str]] = {
|
356
364
|
nwBuildFmt.ODT: QT_TRANSLATE_NOOP("Constant", "Open Document (.odt)"),
|
357
365
|
nwBuildFmt.FODT: QT_TRANSLATE_NOOP("Constant", "Flat Open Document (.fodt)"),
|
@@ -505,6 +513,19 @@ class nwQuotes:
|
|
505
513
|
"\u300f": QT_TRANSLATE_NOOP("Constant", "Right white corner bracket"),
|
506
514
|
}
|
507
515
|
|
516
|
+
DASHES: Final[dict[str, str]] = {
|
517
|
+
"": QT_TRANSLATE_NOOP("Constant", "None"),
|
518
|
+
"\u2013": QT_TRANSLATE_NOOP("Constant", "Short dash"),
|
519
|
+
"\u2014": QT_TRANSLATE_NOOP("Constant", "Long dash"),
|
520
|
+
"\u2015": QT_TRANSLATE_NOOP("Constant", "Horizontal bar"),
|
521
|
+
}
|
522
|
+
|
523
|
+
ALLOWED: Final[list[str]] = [
|
524
|
+
"\u0027", "\u0022", "\u2018", "\u2019", "\u201a", "\u201b", "\u201c", "\u201d", "\u201e",
|
525
|
+
"\u201f", "\u2e42", "\u2039", "\u203a", "\u00ab", "\u00bb", "\u300c", "\u300d", "\u300e",
|
526
|
+
"\u300f", "\u2013", "\u2014", "\u2015",
|
527
|
+
]
|
528
|
+
|
508
529
|
|
509
530
|
class nwUnicode:
|
510
531
|
"""Supported unicode character constants and their HTML equivalents."""
|
@@ -79,6 +79,7 @@ SETTINGS_TEMPLATE: dict[str, tuple[type, T_BuildValue]] = {
|
|
79
79
|
"text.includeSynopsis": (bool, False),
|
80
80
|
"text.includeComments": (bool, False),
|
81
81
|
"text.includeStory": (bool, False),
|
82
|
+
"text.includeNotes": (bool, False),
|
82
83
|
"text.includeKeywords": (bool, False),
|
83
84
|
"text.includeBodyText": (bool, True),
|
84
85
|
"text.ignoredKeywords": (str, ""),
|
@@ -146,6 +147,7 @@ SETTINGS_LABELS = {
|
|
146
147
|
"text.includeSynopsis": QT_TRANSLATE_NOOP("Builds", "Include Synopsis"),
|
147
148
|
"text.includeComments": QT_TRANSLATE_NOOP("Builds", "Include Comments"),
|
148
149
|
"text.includeStory": QT_TRANSLATE_NOOP("Builds", "Include Story Structure"),
|
150
|
+
"text.includeNotes": QT_TRANSLATE_NOOP("Builds", "Include Manuscript Notes"),
|
149
151
|
"text.includeKeywords": QT_TRANSLATE_NOOP("Builds", "Include Keywords"),
|
150
152
|
"text.includeBodyText": QT_TRANSLATE_NOOP("Builds", "Include Body Text"),
|
151
153
|
"text.ignoredKeywords": QT_TRANSLATE_NOOP("Builds", "Ignore These Keywords"),
|
@@ -388,7 +390,7 @@ class BuildSettings:
|
|
388
390
|
def setValue(self, key: str, value: T_BuildValue) -> None:
|
389
391
|
"""Set a specific value for a build setting."""
|
390
392
|
if (d := SETTINGS_TEMPLATE.get(key)) and len(d) == 2 and isinstance(value, d[0]):
|
391
|
-
self._changed
|
393
|
+
self._changed |= (value != self._settings[key])
|
392
394
|
self._settings[key] = value
|
393
395
|
return
|
394
396
|
|
novelwriter/core/coretools.py
CHANGED
@@ -383,10 +383,12 @@ class ProjectBuilder:
|
|
383
383
|
"""Build or copy a project from a data dictionary."""
|
384
384
|
if isinstance(data, dict):
|
385
385
|
path = data.get("path", None) or None
|
386
|
+
if author := data.get("author"):
|
387
|
+
CONFIG.setLastAuthor(author)
|
386
388
|
if isinstance(path, str | Path):
|
387
389
|
self._path = Path(path).resolve()
|
388
390
|
if data.get("sample"):
|
389
|
-
return self._extractSampleProject(self._path)
|
391
|
+
return self._extractSampleProject(self._path, data)
|
390
392
|
elif data.get("template"):
|
391
393
|
return self._copyProject(self._path, data)
|
392
394
|
else:
|
@@ -416,20 +418,21 @@ class ProjectBuilder:
|
|
416
418
|
|
417
419
|
self._path = project.storage.storagePath
|
418
420
|
|
419
|
-
|
420
|
-
|
421
|
+
trName = self.tr("New Project")
|
422
|
+
trAuthor = self.tr("Author Name")
|
423
|
+
trTitlePage = self.tr("Title Page")
|
421
424
|
|
422
425
|
# Settings
|
423
426
|
project.data.setUuid(None)
|
424
|
-
project.data.setName(data.get("name",
|
425
|
-
project.data.setAuthor(data.get("author",
|
427
|
+
project.data.setName(data.get("name", trName))
|
428
|
+
project.data.setAuthor(data.get("author", trAuthor))
|
426
429
|
project.data.setLanguage(CONFIG.guiLocale)
|
427
430
|
project.setDefaultStatusImport()
|
428
431
|
project.session.startSession()
|
429
432
|
|
430
433
|
# Add Root Folders
|
431
434
|
hNovelRoot = project.newRoot(nwItemClass.NOVEL)
|
432
|
-
hTitlePage = project.newFile(
|
435
|
+
hTitlePage = project.newFile(trTitlePage, hNovelRoot)
|
433
436
|
|
434
437
|
# Generate Title Page
|
435
438
|
aDoc = project.storage.getDocument(hTitlePage)
|
@@ -446,25 +449,21 @@ class ProjectBuilder:
|
|
446
449
|
"\n"
|
447
450
|
">> {count}: [field:{field}] <<\n"
|
448
451
|
).format(
|
449
|
-
author=project.data.author or
|
450
|
-
address=self.tr("Address"),
|
451
|
-
title=project.data.name or
|
452
|
+
author=project.data.author or trAuthor,
|
453
|
+
address=self.tr("Address Line"),
|
454
|
+
title=project.data.name or trName,
|
452
455
|
by=self.tr("By"),
|
453
456
|
count=self.tr("Word Count"),
|
454
457
|
field=nwStats.WORDS_TEXT,
|
455
458
|
))
|
456
459
|
|
457
|
-
# Create a project structure based on selected root folders
|
458
|
-
# and a number of chapters and scenes selected in the
|
459
|
-
# wizard's custom page.
|
460
|
-
|
461
460
|
# Create chapters and scenes
|
462
461
|
numChapters = data.get("chapters", 0)
|
463
462
|
numScenes = data.get("scenes", 0)
|
464
463
|
|
465
|
-
|
466
|
-
|
467
|
-
|
464
|
+
trChSynop = self.tr("Summary of the chapter.")
|
465
|
+
trScSynop = self.tr("Summary of the scene.")
|
466
|
+
trNoteDesc = self.tr("A short description.")
|
468
467
|
|
469
468
|
# Create chapters
|
470
469
|
if numChapters > 0:
|
@@ -472,7 +471,7 @@ class ProjectBuilder:
|
|
472
471
|
chTitle = self.tr("Chapter {0}").format(f"{ch+1:d}")
|
473
472
|
cHandle = project.newFile(chTitle, hNovelRoot)
|
474
473
|
aDoc = project.storage.getDocument(cHandle)
|
475
|
-
aDoc.writeDocument(f"## {chTitle}\n\n%Synopsis: {
|
474
|
+
aDoc.writeDocument(f"## {chTitle}\n\n%Synopsis: {trChSynop}\n\n")
|
476
475
|
|
477
476
|
# Create chapter scenes
|
478
477
|
if numScenes > 0 and cHandle:
|
@@ -480,7 +479,7 @@ class ProjectBuilder:
|
|
480
479
|
scTitle = self.tr("Scene {0}").format(f"{ch+1:d}.{sc+1:d}")
|
481
480
|
sHandle = project.newFile(scTitle, cHandle)
|
482
481
|
aDoc = project.storage.getDocument(sHandle)
|
483
|
-
aDoc.writeDocument(f"### {scTitle}\n\n%Synopsis: {
|
482
|
+
aDoc.writeDocument(f"### {scTitle}\n\n%Synopsis: {trScSynop}\n\n")
|
484
483
|
|
485
484
|
# Create scenes (no chapters)
|
486
485
|
elif numScenes > 0:
|
@@ -488,7 +487,7 @@ class ProjectBuilder:
|
|
488
487
|
scTitle = self.tr("Scene {0}").format(f"{sc+1:d}")
|
489
488
|
sHandle = project.newFile(scTitle, hNovelRoot)
|
490
489
|
aDoc = project.storage.getDocument(sHandle)
|
491
|
-
aDoc.writeDocument(f"### {scTitle}\n\n%Synopsis: {
|
490
|
+
aDoc.writeDocument(f"### {scTitle}\n\n%Synopsis: {trScSynop}\n\n")
|
492
491
|
|
493
492
|
# Create notes folders
|
494
493
|
noteTitles = {
|
@@ -508,7 +507,7 @@ class ProjectBuilder:
|
|
508
507
|
aDoc.writeDocument(
|
509
508
|
f"# {noteTitles[newRoot]}\n\n"
|
510
509
|
f"@tag: {ntTag}\n\n"
|
511
|
-
f"%Short: {
|
510
|
+
f"%Short: {trNoteDesc}\n\n"
|
512
511
|
)
|
513
512
|
|
514
513
|
# Also add the archive and trash folders
|
@@ -563,23 +562,11 @@ class ProjectBuilder:
|
|
563
562
|
return False
|
564
563
|
|
565
564
|
# Open the copied project and update settings
|
566
|
-
|
567
|
-
project.openProject(dstPath)
|
568
|
-
project.data.setUuid("") # Creates a fresh uuid
|
569
|
-
project.data.setName(data.get("name", "None"))
|
570
|
-
project.data.setAuthor(data.get("author", ""))
|
571
|
-
project.data.setSpellCheck(True)
|
572
|
-
project.data.setSpellLang(None)
|
573
|
-
project.data.setDoBackup(True)
|
574
|
-
project.data.setSaveCount(0)
|
575
|
-
project.data.setAutoCount(0)
|
576
|
-
project.data.setEditTime(0)
|
577
|
-
project.saveProject()
|
578
|
-
project.closeProject()
|
565
|
+
self._resetProject(dstPath, data)
|
579
566
|
|
580
567
|
return True
|
581
568
|
|
582
|
-
def _extractSampleProject(self, path: Path) -> bool:
|
569
|
+
def _extractSampleProject(self, path: Path, data: dict) -> bool:
|
583
570
|
"""Make a copy of the sample project by extracting the
|
584
571
|
sample.zip file to the new path.
|
585
572
|
"""
|
@@ -593,6 +580,7 @@ class ProjectBuilder:
|
|
593
580
|
if (sample := CONFIG.assetPath("sample.zip")).is_file():
|
594
581
|
try:
|
595
582
|
shutil.unpack_archive(sample, path)
|
583
|
+
self._resetProject(path, data)
|
596
584
|
except Exception as exc:
|
597
585
|
SHARED.error(self.tr("Failed to create a new example project."), exc=exc)
|
598
586
|
return False
|
@@ -605,3 +593,22 @@ class ProjectBuilder:
|
|
605
593
|
return False
|
606
594
|
|
607
595
|
return True
|
596
|
+
|
597
|
+
def _resetProject(self, path: Path, data: dict) -> None:
|
598
|
+
"""Open a project and reset/update its settings."""
|
599
|
+
project = NWProject()
|
600
|
+
project.openProject(path)
|
601
|
+
project.data.setUuid("") # Creates a fresh uuid
|
602
|
+
if name := data.get("name", ""):
|
603
|
+
project.data.setName(name)
|
604
|
+
if author := data.get("author", ""):
|
605
|
+
project.data.setAuthor(author)
|
606
|
+
project.data.setSpellCheck(True)
|
607
|
+
project.data.setSpellLang(None)
|
608
|
+
project.data.setDoBackup(True)
|
609
|
+
project.data.setSaveCount(0)
|
610
|
+
project.data.setAutoCount(0)
|
611
|
+
project.data.setEditTime(0)
|
612
|
+
project.saveProject()
|
613
|
+
project.closeProject()
|
614
|
+
return
|
novelwriter/core/docbuild.py
CHANGED
@@ -317,6 +317,7 @@ class NWBuildDocument:
|
|
317
317
|
bldObj.setCommentType(nwComment.SYNOPSIS, self._build.getBool("text.includeSynopsis"))
|
318
318
|
bldObj.setCommentType(nwComment.SHORT, self._build.getBool("text.includeSynopsis"))
|
319
319
|
bldObj.setCommentType(nwComment.STORY, self._build.getBool("text.includeStory"))
|
320
|
+
bldObj.setCommentType(nwComment.NOTE, self._build.getBool("text.includeNotes"))
|
320
321
|
|
321
322
|
if isinstance(bldObj, ToHtml):
|
322
323
|
bldObj.setStyles(self._build.getBool("html.addStyles"))
|
novelwriter/core/index.py
CHANGED
@@ -177,6 +177,16 @@ class Index:
|
|
177
177
|
self.scanText(tHandle, self._project.storage.getDocumentText(tHandle))
|
178
178
|
return
|
179
179
|
|
180
|
+
def refreshHandle(self, tHandle: str) -> None:
|
181
|
+
"""Update the class for all tags of a handle."""
|
182
|
+
if item := self._project.tree[tHandle]:
|
183
|
+
logger.info("Updating class for '%s'", tHandle)
|
184
|
+
if item.isInactiveClass():
|
185
|
+
self.deleteHandle(tHandle)
|
186
|
+
else:
|
187
|
+
self._tagsIndex.updateClass(tHandle, item.itemClass.name)
|
188
|
+
return
|
189
|
+
|
180
190
|
def indexChangedSince(self, checkTime: int | float) -> bool:
|
181
191
|
"""Check if the index has changed since a given time."""
|
182
192
|
return self._indexChange > float(checkTime)
|
@@ -612,6 +622,10 @@ class Index:
|
|
612
622
|
"""Return all story structure keys."""
|
613
623
|
return self._itemIndex.allStoryKeys()
|
614
624
|
|
625
|
+
def getNoteKeys(self) -> set[str]:
|
626
|
+
"""Return all note comment keys."""
|
627
|
+
return self._itemIndex.allNoteKeys()
|
628
|
+
|
615
629
|
def novelStructure(
|
616
630
|
self, rootHandle: str | None = None, activeOnly: bool = True
|
617
631
|
) -> Iterable[tuple[str, str, str, IndexHeading]]:
|
@@ -743,10 +757,12 @@ class Index:
|
|
743
757
|
"""Return all tags used by a specific document."""
|
744
758
|
return self._itemIndex.allItemTags(tHandle) if tHandle else []
|
745
759
|
|
746
|
-
def
|
747
|
-
"""Return all tags
|
748
|
-
|
749
|
-
|
760
|
+
def getKeyWordTags(self, keyWord: str) -> list[str]:
|
761
|
+
"""Return all tags usable for a specific keyword."""
|
762
|
+
if keyWord in nwKeyWords.CAN_LOOKUP:
|
763
|
+
itemClass = nwKeyWords.KEY_CLASS.get(keyWord)
|
764
|
+
return self._tagsIndex.filterTagNames(itemClass.name if itemClass else None)
|
765
|
+
return []
|
750
766
|
|
751
767
|
def getTagsData(
|
752
768
|
self, activeOnly: bool = True
|
@@ -854,6 +870,15 @@ class TagsIndex:
|
|
854
870
|
x.get("name", "") for x in self._tags.values() if x.get("class", "") == className
|
855
871
|
]
|
856
872
|
|
873
|
+
def updateClass(self, tHandle: str, className: str) -> None:
|
874
|
+
"""Update the class name of an item. This must be called when a
|
875
|
+
document moves to another class.
|
876
|
+
"""
|
877
|
+
for entry in self._tags.values():
|
878
|
+
if entry.get("handle") == tHandle:
|
879
|
+
entry["class"] = className
|
880
|
+
return
|
881
|
+
|
857
882
|
##
|
858
883
|
# Pack/Unpack
|
859
884
|
##
|
@@ -905,11 +930,12 @@ class IndexCache:
|
|
905
930
|
which provides lookup capabilities and caching for shared data.
|
906
931
|
"""
|
907
932
|
|
908
|
-
__slots__ = ("story", "tags")
|
933
|
+
__slots__ = ("note", "story", "tags")
|
909
934
|
|
910
935
|
def __init__(self, tagsIndex: TagsIndex) -> None:
|
911
936
|
self.tags: TagsIndex = tagsIndex
|
912
937
|
self.story: set[str] = set()
|
938
|
+
self.note: set[str] = set()
|
913
939
|
return
|
914
940
|
|
915
941
|
|
@@ -964,6 +990,10 @@ class ItemIndex:
|
|
964
990
|
"""Return all story structure keys."""
|
965
991
|
return self._cache.story.copy()
|
966
992
|
|
993
|
+
def allNoteKeys(self) -> set[str]:
|
994
|
+
"""Return all note comment keys."""
|
995
|
+
return self._cache.note.copy()
|
996
|
+
|
967
997
|
def allItemTags(self, tHandle: str) -> list[str]:
|
968
998
|
"""Get all tags set for headings of an item."""
|
969
999
|
if tHandle in self._items:
|
novelwriter/core/indexdata.py
CHANGED
@@ -316,6 +316,9 @@ class IndexHeading:
|
|
316
316
|
case "story" if key:
|
317
317
|
self._cache.story.add(key)
|
318
318
|
self._comments[f"story.{key}"] = str(text)
|
319
|
+
case "note" if key:
|
320
|
+
self._cache.note.add(key)
|
321
|
+
self._comments[f"note.{key}"] = str(text)
|
319
322
|
return
|
320
323
|
|
321
324
|
def setTag(self, tag: str) -> None:
|
@@ -395,7 +398,7 @@ class IndexHeading:
|
|
395
398
|
self.addReference(tag, keyword)
|
396
399
|
else:
|
397
400
|
raise ValueError("Heading reference contains an invalid keyword")
|
398
|
-
elif key == "summary" or key.startswith("story"):
|
401
|
+
elif key == "summary" or key.startswith(("story", "note")):
|
399
402
|
comment, _, kind = str(key).partition(".")
|
400
403
|
self.setComment(comment, compact(kind), str(entry))
|
401
404
|
else:
|
novelwriter/core/item.py
CHANGED
@@ -53,10 +53,10 @@ class NWItem:
|
|
53
53
|
"""
|
54
54
|
|
55
55
|
__slots__ = (
|
56
|
-
"_active", "_charCount", "
|
57
|
-
"
|
56
|
+
"_active", "_charCount", "_charInit", "_class", "_cursorPos",
|
57
|
+
"_expanded", "_handle", "_heading", "_import", "_layout", "_name",
|
58
58
|
"_order", "_paraCount", "_parent", "_project", "_root", "_status",
|
59
|
-
"_type", "_wordCount",
|
59
|
+
"_type", "_wordCount", "_wordInit",
|
60
60
|
)
|
61
61
|
|
62
62
|
def __init__(self, project: NWProject, handle: str) -> None:
|
@@ -81,7 +81,8 @@ class NWItem:
|
|
81
81
|
self._wordCount = 0 # Current word count
|
82
82
|
self._paraCount = 0 # Current paragraph count
|
83
83
|
self._cursorPos = 0 # Last cursor position
|
84
|
-
self.
|
84
|
+
self._wordInit = 0 # Initial character count
|
85
|
+
self._charInit = 0 # Initial word count
|
85
86
|
|
86
87
|
return
|
87
88
|
|
@@ -164,9 +165,13 @@ class NWItem:
|
|
164
165
|
def paraCount(self) -> int:
|
165
166
|
return self._paraCount
|
166
167
|
|
168
|
+
@property
|
169
|
+
def mainCount(self) -> int:
|
170
|
+
return self._charCount if CONFIG.useCharCount else self._wordCount
|
171
|
+
|
167
172
|
@property
|
168
173
|
def initCount(self) -> int:
|
169
|
-
return self.
|
174
|
+
return self._wordInit if CONFIG.useCharCount else self._charInit
|
170
175
|
|
171
176
|
@property
|
172
177
|
def cursorPos(self) -> int:
|
@@ -257,7 +262,8 @@ class NWItem:
|
|
257
262
|
self._paraCount = 0
|
258
263
|
self._cursorPos = 0
|
259
264
|
|
260
|
-
self.
|
265
|
+
self._wordInit = self._charCount
|
266
|
+
self._charInit = self._wordCount
|
261
267
|
|
262
268
|
return True
|
263
269
|
|
@@ -265,23 +271,24 @@ class NWItem:
|
|
265
271
|
def duplicate(cls, source: NWItem, handle: str) -> NWItem:
|
266
272
|
"""Make a copy of an item."""
|
267
273
|
new = cls(source._project, handle)
|
268
|
-
new._name
|
269
|
-
new._parent
|
270
|
-
new._root
|
271
|
-
new._order
|
272
|
-
new._type
|
273
|
-
new._class
|
274
|
-
new._layout
|
275
|
-
new._status
|
276
|
-
new._import
|
277
|
-
new._active
|
278
|
-
new._expanded
|
279
|
-
new._heading
|
280
|
-
new._charCount
|
281
|
-
new._wordCount
|
282
|
-
new._paraCount
|
283
|
-
new._cursorPos
|
284
|
-
new.
|
274
|
+
new._name = source._name
|
275
|
+
new._parent = source._parent
|
276
|
+
new._root = source._root
|
277
|
+
new._order = source._order
|
278
|
+
new._type = source._type
|
279
|
+
new._class = source._class
|
280
|
+
new._layout = source._layout
|
281
|
+
new._status = source._status
|
282
|
+
new._import = source._import
|
283
|
+
new._active = source._active
|
284
|
+
new._expanded = source._expanded
|
285
|
+
new._heading = source._heading
|
286
|
+
new._charCount = source._charCount
|
287
|
+
new._wordCount = source._wordCount
|
288
|
+
new._paraCount = source._paraCount
|
289
|
+
new._cursorPos = source._cursorPos
|
290
|
+
new._wordInit = source._wordInit
|
291
|
+
new._charInit = source._charInit
|
285
292
|
return new
|
286
293
|
|
287
294
|
##
|
@@ -428,7 +435,11 @@ class NWItem:
|
|
428
435
|
"""
|
429
436
|
if self._parent is not None:
|
430
437
|
# Only update for child items
|
431
|
-
self.
|
438
|
+
if itemClass != self._class:
|
439
|
+
self.setClass(itemClass)
|
440
|
+
if self._type == nwItemType.FILE:
|
441
|
+
# Notify the index of the class change
|
442
|
+
self._project.index.refreshHandle(self._handle)
|
432
443
|
|
433
444
|
if self._layout == nwItemLayout.NO_LAYOUT:
|
434
445
|
# If no layout is set, pick one
|
novelwriter/core/itemmodel.py
CHANGED
@@ -45,16 +45,18 @@ logger = logging.getLogger(__name__)
|
|
45
45
|
INV_ROOT = "invisibleRoot"
|
46
46
|
C_FACTOR = 0x0100
|
47
47
|
|
48
|
-
C_LABEL_TEXT
|
49
|
-
C_LABEL_ICON
|
50
|
-
C_LABEL_FONT
|
51
|
-
C_COUNT_TEXT
|
52
|
-
C_COUNT_ICON
|
53
|
-
C_COUNT_ALIGN
|
54
|
-
C_ACTIVE_ICON
|
55
|
-
C_ACTIVE_TIP
|
56
|
-
|
57
|
-
|
48
|
+
C_LABEL_TEXT = 0x0000 | Qt.ItemDataRole.DisplayRole
|
49
|
+
C_LABEL_ICON = 0x0000 | Qt.ItemDataRole.DecorationRole
|
50
|
+
C_LABEL_FONT = 0x0000 | Qt.ItemDataRole.FontRole
|
51
|
+
C_COUNT_TEXT = 0x0100 | Qt.ItemDataRole.DisplayRole
|
52
|
+
C_COUNT_ICON = 0x0100 | Qt.ItemDataRole.DecorationRole
|
53
|
+
C_COUNT_ALIGN = 0x0100 | Qt.ItemDataRole.TextAlignmentRole
|
54
|
+
C_ACTIVE_ICON = 0x0200 | Qt.ItemDataRole.DecorationRole
|
55
|
+
C_ACTIVE_TIP = 0x0200 | Qt.ItemDataRole.ToolTipRole
|
56
|
+
C_ACTIVE_ACCESS = 0x0200 | Qt.ItemDataRole.AccessibleTextRole
|
57
|
+
C_STATUS_ICON = 0x0300 | Qt.ItemDataRole.DecorationRole
|
58
|
+
C_STATUS_TIP = 0x0300 | Qt.ItemDataRole.ToolTipRole
|
59
|
+
C_STATUS_ACCESS = 0x0300 | Qt.ItemDataRole.AccessibleTextRole
|
58
60
|
|
59
61
|
NODE_FLAGS = Qt.ItemFlag.ItemIsEnabled
|
60
62
|
NODE_FLAGS |= Qt.ItemFlag.ItemIsSelectable
|
@@ -150,19 +152,21 @@ class ProjectNode:
|
|
150
152
|
|
151
153
|
# Active
|
152
154
|
aText, aIcon = self._item.getActiveStatus()
|
153
|
-
self._cache[C_ACTIVE_TIP] = aText
|
154
155
|
self._cache[C_ACTIVE_ICON] = aIcon
|
156
|
+
self._cache[C_ACTIVE_TIP] = aText
|
157
|
+
self._cache[C_ACTIVE_ACCESS] = aText
|
155
158
|
|
156
159
|
# Status
|
157
160
|
sText, sIcon = self._item.getImportStatus()
|
158
|
-
self._cache[C_STATUS_TIP] = sText
|
159
161
|
self._cache[C_STATUS_ICON] = sIcon
|
162
|
+
self._cache[C_STATUS_TIP] = sText
|
163
|
+
self._cache[C_STATUS_ACCESS] = sText
|
160
164
|
|
161
165
|
return
|
162
166
|
|
163
167
|
def updateCount(self, propagate: bool = True) -> None:
|
164
168
|
"""Update counts, and propagate upwards in the tree."""
|
165
|
-
self._count = self._item.
|
169
|
+
self._count = self._item.mainCount + sum(c._count for c in self._children) # noqa: SLF001
|
166
170
|
self._cache[C_COUNT_TEXT] = f"{self._count:n}"
|
167
171
|
if propagate and (parent := self._parent):
|
168
172
|
parent.updateCount()
|
novelwriter/core/novelmodel.py
CHANGED
@@ -46,6 +46,7 @@ R_TEXT = Qt.ItemDataRole.DisplayRole
|
|
46
46
|
R_ICON = Qt.ItemDataRole.DecorationRole
|
47
47
|
R_ALIGN = Qt.ItemDataRole.TextAlignmentRole
|
48
48
|
R_TIP = Qt.ItemDataRole.ToolTipRole
|
49
|
+
R_ACCESS = Qt.ItemDataRole.AccessibleTextRole
|
49
50
|
R_HANDLE = 0xff01
|
50
51
|
R_KEY = 0xff02
|
51
52
|
|
@@ -217,6 +218,7 @@ class NovelModel(QAbstractTableModel):
|
|
217
218
|
text = ", ".join(refs)
|
218
219
|
data[C_FACTOR*2 | R_TEXT] = text
|
219
220
|
data[C_FACTOR*2 | R_TIP] = f"<b>{self._extraLabel}:</b> {text}"
|
221
|
+
data[C_FACTOR*2 | R_ACCESS] = f"{self._extraLabel}: {text}"
|
220
222
|
data[C_FACTOR*3 | R_ICON] = self._more
|
221
223
|
data[R_HANDLE] = handle
|
222
224
|
data[R_KEY] = key
|
novelwriter/core/project.py
CHANGED
@@ -367,7 +367,7 @@ class NWProject:
|
|
367
367
|
# Often, the index needs to be rebuilt when updating format
|
368
368
|
self._index.rebuild()
|
369
369
|
|
370
|
-
self.
|
370
|
+
self.updateCounts()
|
371
371
|
self._session.startSession()
|
372
372
|
self.setProjectChanged(False)
|
373
373
|
self._valid = True
|
@@ -397,7 +397,7 @@ class NWProject:
|
|
397
397
|
else:
|
398
398
|
self._data.incSaveCount()
|
399
399
|
|
400
|
-
self.
|
400
|
+
self.updateCounts()
|
401
401
|
self.countStatus()
|
402
402
|
|
403
403
|
xmlWriter = self._storage.getXmlWriter()
|
@@ -483,14 +483,14 @@ class NWProject:
|
|
483
483
|
|
484
484
|
def setDefaultStatusImport(self) -> None:
|
485
485
|
"""Set the default status and importance values."""
|
486
|
-
self._data.itemStatus.add(None, self.tr("New"), (
|
487
|
-
self._data.itemStatus.add(None, self.tr("Note"), (
|
488
|
-
self._data.itemStatus.add(None, self.tr("Draft"), (
|
489
|
-
self._data.itemStatus.add(None, self.tr("Finished"), (
|
490
|
-
self._data.itemImport.add(None, self.tr("New"), (
|
491
|
-
self._data.itemImport.add(None, self.tr("Minor"), (
|
492
|
-
self._data.itemImport.add(None, self.tr("Major"), (
|
493
|
-
self._data.itemImport.add(None, self.tr("Main"), (
|
486
|
+
self._data.itemStatus.add(None, self.tr("New"), (120, 120, 120), "STAR", 0)
|
487
|
+
self._data.itemStatus.add(None, self.tr("Note"), (205, 171, 143), "TRIANGLE", 0)
|
488
|
+
self._data.itemStatus.add(None, self.tr("Draft"), (143, 240, 164), "CIRCLE_T", 0)
|
489
|
+
self._data.itemStatus.add(None, self.tr("Finished"), (249, 240, 107), "STAR", 0)
|
490
|
+
self._data.itemImport.add(None, self.tr("New"), (120, 120, 120), "SQUARE", 0)
|
491
|
+
self._data.itemImport.add(None, self.tr("Minor"), (220, 138, 221), "BLOCK_2", 0)
|
492
|
+
self._data.itemImport.add(None, self.tr("Major"), (220, 138, 221), "BLOCK_3", 0)
|
493
|
+
self._data.itemImport.add(None, self.tr("Main"), (220, 138, 221), "BLOCK_4", 0)
|
494
494
|
return
|
495
495
|
|
496
496
|
def setProjectLang(self, language: str | None) -> None:
|
@@ -515,10 +515,10 @@ class NWProject:
|
|
515
515
|
# Class Methods
|
516
516
|
##
|
517
517
|
|
518
|
-
def
|
519
|
-
"""Update the total word count values."""
|
520
|
-
|
521
|
-
self._data.setCurrCounts(
|
518
|
+
def updateCounts(self) -> None:
|
519
|
+
"""Update the total word and character count values."""
|
520
|
+
wNovel, wNotes, cNovel, cNotes = self._tree.sumCounts()
|
521
|
+
self._data.setCurrCounts(wNovel=wNovel, wNotes=wNotes, cNovel=cNovel, cNotes=cNotes)
|
522
522
|
return
|
523
523
|
|
524
524
|
def countStatus(self) -> None:
|