From bd4a658bd90fb4c4edac451d2c3c55327c85bc4b Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Tue, 29 Apr 2025 22:00:53 +0200 Subject: [PATCH] Add project variables for character counts --- novelwriter/config.py | 24 +++++++++-- novelwriter/core/project.py | 12 +++--- novelwriter/core/projectdata.py | 66 ++++++++++++++++++++----------- novelwriter/core/projectxml.py | 24 +++++++---- novelwriter/core/sessions.py | 4 +- novelwriter/core/tree.py | 14 ++++--- novelwriter/guimain.py | 13 +++--- novelwriter/tools/noveldetails.py | 4 +- 8 files changed, 104 insertions(+), 57 deletions(-) diff --git a/novelwriter/config.py b/novelwriter/config.py index 78a4b843..4d90478b 100644 --- a/novelwriter/config.py +++ b/novelwriter/config.py @@ -897,9 +897,10 @@ class RecentProjects: puuid = str(entry.get("uuid", "")) title = str(entry.get("title", "")) words = checkInt(entry.get("words", 0), 0) + chars = checkInt(entry.get("chars", 0), 0) saved = checkInt(entry.get("time", 0), 0) if path and title: - self._setEntry(puuid, path, title, words, saved) + self._setEntry(puuid, path, title, words, chars, saved) except Exception: logger.error("Could not load recent project cache") logException() @@ -932,7 +933,14 @@ class RecentProjects: try: if (remove := self._map.get(data.uuid)) and (remove != str(path)): self.remove(remove) - self._setEntry(data.uuid, str(path), data.name, sum(data.currCounts), int(saved)) + self._setEntry( + data.uuid, + str(path), + data.name, + sum(data.currCounts[:2]), + sum(data.currCounts[2:]), + int(saved), + ) self.saveCache() except Exception: pass @@ -945,9 +953,17 @@ class RecentProjects: self.saveCache() return - def _setEntry(self, puuid: str, path: str, title: str, words: int, saved: int) -> None: + def _setEntry( + self, puuid: str, path: str, title: str, words: int, chars: int, saved: int + ) -> None: """Set an entry in the recent projects record.""" - self._data[path] = {"uuid": puuid, "title": title, "words": words, "time": saved} + self._data[path] = { + "uuid": puuid, + "title": title, + "words": words, + "chars": chars, + "time": saved, + } if puuid: self._map[puuid] = path return diff --git a/novelwriter/core/project.py b/novelwriter/core/project.py index 9ad46682..1466f984 100644 --- a/novelwriter/core/project.py +++ b/novelwriter/core/project.py @@ -367,7 +367,7 @@ class NWProject: # Often, the index needs to be rebuilt when updating format self._index.rebuild() - self.updateWordCounts() + self.updateCounts() self._session.startSession() self.setProjectChanged(False) self._valid = True @@ -397,7 +397,7 @@ class NWProject: else: self._data.incSaveCount() - self.updateWordCounts() + self.updateCounts() self.countStatus() xmlWriter = self._storage.getXmlWriter() @@ -515,10 +515,10 @@ class NWProject: # Class Methods ## - def updateWordCounts(self) -> None: - """Update the total word count values.""" - novel, notes = self._tree.sumWords() - self._data.setCurrCounts(novel=novel, notes=notes) + def updateCounts(self) -> None: + """Update the total word and character count values.""" + wNovel, wNotes, cNovel, cNotes = self._tree.sumWords() + self._data.setCurrCounts(wNovel=wNovel, wNotes=wNotes, cNovel=cNovel, cNotes=cNotes) return def countStatus(self) -> None: diff --git a/novelwriter/core/projectdata.py b/novelwriter/core/projectdata.py index 1c2c1f42..15201970 100644 --- a/novelwriter/core/projectdata.py +++ b/novelwriter/core/projectdata.py @@ -66,8 +66,8 @@ class NWProjectData: self._spellLang = None # Project Dictionaries - self._initCounts = [0, 0] - self._currCounts = [0, 0] + self._initCounts = [0, 0, 0, 0] + self._currCounts = [0, 0, 0, 0] self._lastHandle: dict[str, str | None] = { "editor": None, "viewer": None, @@ -148,18 +148,18 @@ class NWProjectData: return self._spellLang @property - def initCounts(self) -> tuple[int, int]: - """Return the initial count of words for novel and note - documents. + def initCounts(self) -> tuple[int, int, int, int]: + """Return the initial count of words and characters for novel + and note documents. """ - return self._initCounts[0], self._initCounts[1] + return self._initCounts[0], self._initCounts[1], self._initCounts[2], self._initCounts[3] @property - def currCounts(self) -> tuple[int, int]: - """Return the current count of words for novel and note - documents. + def currCounts(self) -> tuple[int, int, int, int]: + """Return the current count of words and characters for novel + and note documents. """ - return self._currCounts[0], self._currCounts[1] + return self._currCounts[0], self._currCounts[1], self._currCounts[2], self._currCounts[3] @property def lastHandle(self) -> dict[str, str | None]: @@ -301,22 +301,40 @@ class NWProjectData: self._project.setProjectChanged(True) return - def setInitCounts(self, novel: Any = None, notes: Any = None) -> None: - """Set the word count totals for novel and note files.""" - if novel is not None: - self._initCounts[0] = checkInt(novel, 0) - self._currCounts[0] = checkInt(novel, 0) - if notes is not None: - self._initCounts[1] = checkInt(notes, 0) - self._currCounts[1] = checkInt(notes, 0) + def setInitCounts( + self, wNovel: Any = None, wNotes: Any = None, cNovel: Any = None, cNotes: Any = None + ) -> None: + """Set the count totals for novel and note files.""" + if wNovel is not None: + count = checkInt(wNovel, 0) + self._initCounts[0] = count + self._currCounts[0] = count + if wNotes is not None: + count = checkInt(wNotes, 0) + self._initCounts[1] = count + self._currCounts[1] = count + if cNovel is not None: + count = checkInt(cNovel, 0) + self._initCounts[2] = count + self._currCounts[2] = count + if cNotes is not None: + count = checkInt(cNotes, 0) + self._initCounts[3] = count + self._currCounts[3] = count return - def setCurrCounts(self, novel: Any = None, notes: Any = None) -> None: - """Set the word count totals for novel and note files.""" - if novel is not None: - self._currCounts[0] = checkInt(novel, 0) - if notes is not None: - self._currCounts[1] = checkInt(notes, 0) + def setCurrCounts( + self, wNovel: Any = None, wNotes: Any = None, cNovel: Any = None, cNotes: Any = None + ) -> None: + """Set the count totals for novel and note files.""" + if wNovel is not None: + self._currCounts[0] = checkInt(wNovel, 0) + if wNotes is not None: + self._currCounts[1] = checkInt(wNotes, 0) + if cNovel is not None: + self._currCounts[2] = checkInt(cNovel, 0) + if cNotes is not None: + self._currCounts[3] = checkInt(cNotes, 0) return def setAutoReplace(self, value: dict) -> None: diff --git a/novelwriter/core/projectxml.py b/novelwriter/core/projectxml.py index 461d9a40..1e0e146b 100644 --- a/novelwriter/core/projectxml.py +++ b/novelwriter/core/projectxml.py @@ -46,7 +46,7 @@ if TYPE_CHECKING: logger = logging.getLogger(__name__) FILE_VERSION = "1.5" # The current project file format version -FILE_REVISION = "4" # The current project file format revision +FILE_REVISION = "5" # The current project file format revision HEX_VERSION = 0x0105 NUM_VERSION = { @@ -109,6 +109,8 @@ class ProjectXMLReader: Rev 3: Added TEMPLATE class. 2.3. Rev 4: Added shape attribute to status and importance entry nodes. 2.5. + Rev 5: Added novelChars and notesChars attributes to content + node. 2.7 RC 1. """ def __init__(self, path: str | Path) -> None: @@ -286,9 +288,9 @@ class ProjectXMLReader: elif xItem.tag == "spellLang": # Changed to spellChecking in 1.5 data.setSpellLang(xItem.text) elif xItem.tag == "novelWordCount": # Moved to content attribute in 1.5 - data.setInitCounts(novel=xItem.text) + data.setInitCounts(wNovel=xItem.text) elif xItem.tag == "notesWordCount": # Moved to content attribute in 1.5 - data.setInitCounts(notes=xItem.text) + data.setInitCounts(wNotes=xItem.text) return @@ -298,8 +300,13 @@ class ProjectXMLReader: """Parse the content section of the XML file.""" logger.debug("Parsing section") - data.setInitCounts(novel=xSection.attrib.get("novelWords", None)) # Moved in 1.5 - data.setInitCounts(notes=xSection.attrib.get("notesWords", None)) # Moved in 1.5 + # Moved in 1.5 + data.setInitCounts( + wNovel=xSection.attrib.get("novelWords", None), + wNotes=xSection.attrib.get("notesWords", None), + cNovel=xSection.attrib.get("novelChars", None), + cNotes=xSection.attrib.get("notesChars", None), + ) for xItem in xSection: if xItem.tag != "item": @@ -527,10 +534,13 @@ class ProjectXMLWriter: self._packSingleValue(xImport, "entry", label, attrib=attrib) # Save Tree Content + counts = data.currCounts contAttr = { "items": str(len(content)), - "novelWords": str(data.currCounts[0]), - "notesWords": str(data.currCounts[1]), + "novelWords": str(counts[0]), + "notesWords": str(counts[1]), + "novelChars": str(counts[2]), + "notesChars": str(counts[3]), } xContent = ET.SubElement(xRoot, "content", attrib=contAttr) diff --git a/novelwriter/core/sessions.py b/novelwriter/core/sessions.py index 6f7fcef4..659b6f9e 100644 --- a/novelwriter/core/sessions.py +++ b/novelwriter/core/sessions.py @@ -79,8 +79,8 @@ class NWSessionLog: return False now = time() - iNovel, iNotes = self._project.data.initCounts - cNovel, cNotes = self._project.data.currCounts + iNovel, iNotes, _, _ = self._project.data.initCounts + cNovel, cNotes, _, _ = self._project.data.currCounts iTotal = iNovel + iNotes wDiff = cNovel + cNotes - iTotal sTime = now - self._start diff --git a/novelwriter/core/tree.py b/novelwriter/core/tree.py index a3c93745..b9d569c9 100644 --- a/novelwriter/core/tree.py +++ b/novelwriter/core/tree.py @@ -410,16 +410,20 @@ class NWTree: return True - def sumWords(self) -> tuple[int, int]: - """Loop over all entries and add up the word counts.""" - noteWords = 0 + def sumWords(self) -> tuple[int, int, int, int]: + """Loop over all entries and add up the word and char counts.""" novelWords = 0 + notesWords = 0 + novelChars = 0 + notesChars = 0 for item in self._items.values(): if item.itemLayout == nwItemLayout.NOTE: - noteWords += item.wordCount + notesWords += item.wordCount + notesChars += item.charCount elif item.itemLayout == nwItemLayout.DOCUMENT: novelWords += item.wordCount - return novelWords, noteWords + novelChars += item.charCount + return novelWords, notesWords, novelChars, notesChars ## # Tree Item Methods diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index 7a03076f..ee32c547 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -1261,15 +1261,14 @@ class GuiMain(QMainWindow): if self._lastTotalCount != currentTotalCount: self._lastTotalCount = currentTotalCount - SHARED.project.updateWordCounts() + SHARED.project.updateCounts() if CONFIG.incNotesWCount: - iTotal = sum(SHARED.project.data.initCounts) - cTotal = sum(SHARED.project.data.currCounts) - self.mainStatus.setProjectStats(cTotal, cTotal - iTotal) + iTotal = sum(SHARED.project.data.initCounts[:2]) + cTotal = sum(SHARED.project.data.currCounts[:2]) else: - iNovel, _ = SHARED.project.data.initCounts - cNovel, _ = SHARED.project.data.currCounts - self.mainStatus.setProjectStats(cNovel, cNovel - iNovel) + iTotal = SHARED.project.data.initCounts[0] + cTotal = SHARED.project.data.currCounts[0] + self.mainStatus.setProjectStats(cTotal, cTotal - iTotal) return diff --git a/novelwriter/tools/noveldetails.py b/novelwriter/tools/noveldetails.py index ec42ccf1..d0628339 100644 --- a/novelwriter/tools/noveldetails.py +++ b/novelwriter/tools/noveldetails.py @@ -256,8 +256,8 @@ class _OverviewPage(NScrollablePage): def updateProjectData(self) -> None: """Load information about the project.""" project = SHARED.project - project.updateWordCounts() - wcNovel, wcNotes = project.data.currCounts + project.updateCounts() + wcNovel, wcNotes, _, _ = project.data.currCounts self.projName.setText(project.data.name) self.projRevisions.setText(f"{project.data.saveCount:n}")