From 55eed706fbda2852311768c72c7e2623a3d330bf Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Thu, 21 Nov 2024 00:14:54 +0100 Subject: [PATCH] Re-implement word counters --- novelwriter/core/itemmodel.py | 19 +++++++++++--- novelwriter/core/project.py | 5 ++++ novelwriter/core/tree.py | 18 +++++++------- novelwriter/gui/doceditor.py | 3 +-- novelwriter/gui/projtree.py | 47 ----------------------------------- novelwriter/guimain.py | 34 ++++++++++++++----------- 6 files changed, 50 insertions(+), 76 deletions(-) diff --git a/novelwriter/core/itemmodel.py b/novelwriter/core/itemmodel.py index a26b15c4..fd6a365b 100644 --- a/novelwriter/core/itemmodel.py +++ b/novelwriter/core/itemmodel.py @@ -79,6 +79,7 @@ class ProjectNode: self._cache: dict[int, str | QIcon | Qt.AlignmentFlag] = {} self._flags = NODE_FLAGS self.refresh() + self.updateCount() return def __repr__(self) -> str: @@ -95,12 +96,19 @@ class ProjectNode: @property def item(self) -> NWItem: + """The project item of the node.""" return self._item @property def children(self) -> list[ProjectNode]: + """All children of the node.""" return self._children + @property + def count(self) -> int: + """The count of the node.""" + return self._count + ## # Data Maintenance ## @@ -127,11 +135,10 @@ class ProjectNode: self._cache[C_STATUS_TIP] = sText self._cache[C_STATUS_ICON] = sIcon - self.updateCount() - return def updateCount(self, propagate: bool = True) -> None: + """Update counts, and propagate upwards in the tree.""" self._count = self._item.wordCount + sum(c._count for c in self._children) self._cache[C_COUNT_TEXT] = f"{self._count:n}" if propagate and (parent := self._parent): @@ -143,9 +150,11 @@ class ProjectNode: ## def row(self) -> int: + """Return the node's row number.""" return self._row def childCount(self) -> int: + """Return the number of children of the node.""" return len(self._children) def data(self, column: int, role: Qt.ItemDataRole) -> T_NodeData: @@ -157,9 +166,11 @@ class ProjectNode: return self._flags def parent(self) -> ProjectNode | None: + """Return the parent of the node.""" return self._parent def child(self, row: int) -> ProjectNode | None: + """Return a child ofg the node.""" if 0 <= row < len(self._children): return self._children[row] return None @@ -366,9 +377,9 @@ class ProjectModel(QAbstractItemModel): return self.createIndex(node.row(), 0, node) return QModelIndex() - def indexFromNode(self, node: ProjectNode) -> QModelIndex: + def indexFromNode(self, node: ProjectNode, column: int = 0) -> QModelIndex: """Get the index representing a node in the model.""" - return self.createIndex(node.row(), 0, node) + return self.createIndex(node.row(), column, node) def rootIndex(self) -> QModelIndex: """Get the index representing the root.""" diff --git a/novelwriter/core/project.py b/novelwriter/core/project.py index cf6f03eb..be383d0e 100644 --- a/novelwriter/core/project.py +++ b/novelwriter/core/project.py @@ -145,6 +145,11 @@ class NWProject: """Return total edit time, including the current session.""" return self._data.editTime + round(time() - self._session.start) + @property + def currentTotalCount(self) -> int: + """Return the current total word count from the tree.""" + return self._tree.model.root.count + ## # Item Methods ## diff --git a/novelwriter/core/tree.py b/novelwriter/core/tree.py index 207365f4..cad53098 100644 --- a/novelwriter/core/tree.py +++ b/novelwriter/core/tree.py @@ -230,22 +230,22 @@ class NWTree: return - def refreshItems(self, items: list[str], isRange: bool = False) -> None: + def refreshItems(self, items: list[str]) -> None: """Refresh these items on the GUI. If they are an ordered range, also set the isRange flag to True. """ - indices = [] + change = False for tHandle in items: if node := self._nodes.get(tHandle): node.refresh() + node.updateCount() SHARED.projectSignalProxy({"event": "projectItem", "handle": tHandle}) - indices.append(self._model.indexFromNode(node)) - if isRange and len(indices) >= 2: - self._model.dataChanged.emit(indices[0], indices[-1]) - else: - for index in indices: - self._model.dataChanged.emit(index, index) - self._project.setProjectChanged(len(indices) > 0) + indexS = self._model.indexFromNode(node, 0) + indexE = self._model.indexFromNode(node, 3) + self._model.dataChanged.emit(indexS, indexE) + change = True + if change: + self._project.setProjectChanged(True) return def checkConsistency(self, prefix: str) -> tuple[int, int]: diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py index c0b9a4c6..d6e326b8 100644 --- a/novelwriter/gui/doceditor.py +++ b/novelwriter/gui/doceditor.py @@ -110,7 +110,6 @@ class GuiDocEditor(QPlainTextEdit): # Custom Signals closeEditorRequest = pyqtSignal() - docCountsChanged = pyqtSignal(str, int, int, int) docTextChanged = pyqtSignal(str, float) editedStatusChanged = pyqtSignal(bool) itemHandleChanged = pyqtSignal(str) @@ -1281,7 +1280,7 @@ class GuiDocEditor(QPlainTextEdit): self._nwItem.setCharCount(cCount) self._nwItem.setWordCount(wCount) self._nwItem.setParaCount(pCount) - self.docCountsChanged.emit(self._docHandle, cCount, wCount, pCount) + self._nwItem.notifyToRefresh() self.docFooter.updateWordCount(wCount, False) return diff --git a/novelwriter/gui/projtree.py b/novelwriter/gui/projtree.py index ee46fca7..93f31cc9 100644 --- a/novelwriter/gui/projtree.py +++ b/novelwriter/gui/projtree.py @@ -67,7 +67,6 @@ class GuiProjectView(QWidget): # Signals triggered when the meta data values of items change rootFolderChanged = pyqtSignal(str) - wordCountsChanged = pyqtSignal() # Signals for user interaction with the project tree selectedItemChanged = pyqtSignal(str) @@ -241,13 +240,6 @@ class GuiProjectView(QWidget): self.projTree.newTreeItem(nwItemType.FILE, copyDoc=tHandle) return - @pyqtSlot(str, int, int, int) - def updateCounts(self, tHandle: str, cCount: int, wCount: int, pCount: int) -> None: - """Slot for updating the word count of a specific item.""" - self.projTree.propagateCount(tHandle, wCount, countChildren=True) - self.wordCountsChanged.emit() - return - @pyqtSlot(str) def updateRootItem(self, tHandle: str) -> None: """Process root item changes.""" @@ -1067,45 +1059,6 @@ class GuiProjectTree(QTreeView): # self.setTreeItemValues(nwItem) return - def propagateCount(self, tHandle: str, newCount: int, countChildren: bool = False) -> None: - """Recursive function setting the word count for a given item, - and propagating that count upwards in the tree until reaching a - root item. This function is more efficient than recalculating - everything each time the word count is updated, but is also - prone to diverging from the true values if the counts are not - properly reported to the function. - """ - # tItem = self._getTreeItem(tHandle) - # if tItem is None: - # return - - # if countChildren: - # for i in range(tItem.childCount()): - # newCount += int(tItem.child(i).data(self.C_DATA, self.D_WORDS)) - - # tItem.setText(self.C_COUNT, f"{newCount:n}") - # tItem.setData(self.C_DATA, self.D_WORDS, int(newCount)) - - # pItem = tItem.parent() - # if pItem is None: - # return - - # pCount = 0 - # pHandle = None - # for i in range(pItem.childCount()): - # pCount += int(pItem.child(i).data(self.C_DATA, self.D_WORDS)) - # pHandle = pItem.data(self.C_DATA, self.D_HANDLE) - - # if pHandle: - # if SHARED.project.tree.checkType(pHandle, nwItemType.FILE): - # # A file has an internal word count we need to account - # # for, but a folder always has 0 words on its own. - # pCount += SHARED.project.index.getCounts(pHandle)[1] - - # self.propagateCount(pHandle, pCount, countChildren=False) - - return - class _UpdatableMenu(QMenu): diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index b4df7adb..93396f8d 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -242,7 +242,6 @@ class GuiMain(QMainWindow): self.projView.rootFolderChanged.connect(self.outlineView.updateRootItem) self.projView.rootFolderChanged.connect(self.projView.updateRootItem) self.projView.selectedItemChanged.connect(self.itemDetails.updateViewBox) - self.projView.wordCountsChanged.connect(self._updateStatusWordCount) self.novelView.openDocumentRequest.connect(self._openDocument) self.novelView.selectedItemChanged.connect(self.itemDetails.updateViewBox) @@ -251,8 +250,6 @@ class GuiMain(QMainWindow): self.projSearch.selectedItemChanged.connect(self.itemDetails.updateViewBox) self.docEditor.closeEditorRequest.connect(self.closeDocEditor) - self.docEditor.docCountsChanged.connect(self.itemDetails.updateCounts) - self.docEditor.docCountsChanged.connect(self.projView.updateCounts) self.docEditor.docTextChanged.connect(self.projSearch.textChanged) self.docEditor.editedStatusChanged.connect(self.mainStatus.updateDocumentStatus) self.docEditor.itemHandleChanged.connect(self.novelView.setActiveHandle) @@ -315,6 +312,9 @@ class GuiMain(QMainWindow): self.keyEscape.setKey("Esc") self.keyEscape.activated.connect(self._keyPressEscape) + # Internal Variables + self._lastTotalCount = 0 + # Initialise Main GUI self.initMain() self.asProjTimer.start() @@ -1226,8 +1226,10 @@ class GuiMain(QMainWindow): self.mainStatus.setUserIdle(editIdle or userIdle) SHARED.updateIdleTime(currTime, editIdle or userIdle) self.mainStatus.updateTime(idleTime=SHARED.projectIdleTime) - if CONFIG.memInfo and int(currTime) % 5 == 0: # pragma: no cover - self.mainStatus.memInfo() + if int(currTime) % 5 == 0: + self._updateStatusWordCount() + if CONFIG.memInfo: # pragma: no cover + self.mainStatus.memInfo() return @pyqtSlot() @@ -1255,15 +1257,19 @@ class GuiMain(QMainWindow): if not SHARED.hasProject: self.mainStatus.setProjectStats(0, 0) - SHARED.project.updateWordCounts() - if CONFIG.incNotesWCount: - iTotal = sum(SHARED.project.data.initCounts) - cTotal = sum(SHARED.project.data.currCounts) - self.mainStatus.setProjectStats(cTotal, cTotal - iTotal) - else: - iNovel, _ = SHARED.project.data.initCounts - cNovel, _ = SHARED.project.data.currCounts - self.mainStatus.setProjectStats(cNovel, cNovel - iNovel) + currentTotalCount = SHARED.project.currentTotalCount + if self._lastTotalCount != currentTotalCount: + self._lastTotalCount = currentTotalCount + + SHARED.project.updateWordCounts() + if CONFIG.incNotesWCount: + iTotal = sum(SHARED.project.data.initCounts) + cTotal = sum(SHARED.project.data.currCounts) + self.mainStatus.setProjectStats(cTotal, cTotal - iTotal) + else: + iNovel, _ = SHARED.project.data.initCounts + cNovel, _ = SHARED.project.data.currCounts + self.mainStatus.setProjectStats(cNovel, cNovel - iNovel) return