From df0244095f5d698b9cfc77c80ad01b7aa73f5cc3 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Mon, 18 Nov 2024 17:51:05 +0100 Subject: [PATCH] Rewrite how project items are refreshed on the GUI --- novelwriter/core/item.py | 31 ++++++++++--------------------- novelwriter/core/itemmodel.py | 21 +++++++++++++++------ novelwriter/core/tree.py | 22 +++++++++++++++------- novelwriter/gui/projtree.py | 1 + tests/test_core/test_core_item.py | 5 ----- 5 files changed, 41 insertions(+), 39 deletions(-) diff --git a/novelwriter/core/item.py b/novelwriter/core/item.py index b5915c1c..cdfd4a38 100644 --- a/novelwriter/core/item.py +++ b/novelwriter/core/item.py @@ -55,7 +55,7 @@ class NWItem: "_project", "_name", "_handle", "_parent", "_root", "_order", "_type", "_class", "_layout", "_status", "_import", "_active", "_expanded", "_heading", "_charCount", "_wordCount", - "_paraCount", "_cursorPos", "_initCount", "_blocked", + "_paraCount", "_cursorPos", "_initCount", ) def __init__(self, project: NWProject, handle: str) -> None: @@ -82,8 +82,6 @@ class NWItem: self._cursorPos = 0 # Last cursor position self._initCount = 0 # Initial word count - self._blocked = True - return def __repr__(self) -> str: @@ -259,7 +257,6 @@ class NWItem: self._cursorPos = 0 self._initCount = self._wordCount - self._blocked = False return True @@ -284,9 +281,17 @@ class NWItem: cls._paraCount = source._paraCount cls._cursorPos = source._cursorPos cls._initCount = source._initCount - cls._blocked = source._blocked return cls + ## + # Action Methods + ## + + def notifyToRefresh(self) -> None: + """Notify GUI that item info needs to be refreshed.""" + self._project.tree.refreshItems([self._handle]) + return + ## # Lookup Methods ## @@ -419,7 +424,6 @@ class NWItem: self._name = simplified(name) else: self._name = "" - self._notifyChange() return def setParent(self, handle: Any) -> None: @@ -560,18 +564,3 @@ class NWItem: else: self._cursorPos = 0 return - - def saveInitialCount(self) -> None: - """Save the initial word count.""" - self._initCount = self._wordCount - return - - ## - # Internal Functions - ## - - def _notifyChange(self) -> None: - """Notify project tree on user changes to the item.""" - if not self._blocked: - self._project.tree.refreshNode(self._handle) - return diff --git a/novelwriter/core/itemmodel.py b/novelwriter/core/itemmodel.py index 17763ff0..dd14c46b 100644 --- a/novelwriter/core/itemmodel.py +++ b/novelwriter/core/itemmodel.py @@ -26,7 +26,7 @@ from __future__ import annotations import logging -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING from PyQt5.QtCore import QAbstractItemModel, QModelIndex, Qt from PyQt5.QtGui import QIcon @@ -53,6 +53,8 @@ C_ACTIVE_TIP = 0x0200 | Qt.ItemDataRole.ToolTipRole C_STATUS_ICON = 0x0300 | Qt.ItemDataRole.DecorationRole C_STATUS_TIP = 0x0300 | Qt.ItemDataRole.ToolTipRole +T_NodeData = str | QIcon | Qt.AlignmentFlag | None + class ProjectNode: @@ -125,7 +127,7 @@ class ProjectNode: def childCount(self) -> int: return len(self._children) - def data(self, column: int, role: Qt.ItemDataRole) -> str | QIcon | Qt.AlignmentFlag | None: + def data(self, column: int, role: Qt.ItemDataRole) -> T_NodeData: """""" return self._cache.get(COL_MASK*column | role) @@ -168,7 +170,7 @@ class ProjectModel(QAbstractItemModel): super().__init__(None) logger.debug("Create: ProjectModel") self._tree = tree - self._root = ProjectNode(NWItem(tree._project, "")) + self._root = ProjectNode(NWItem(tree._project, "invisibleRoot")) return def __del__(self) -> None: @@ -177,6 +179,7 @@ class ProjectModel(QAbstractItemModel): @property def root(self) -> ProjectNode: + """Return the model root item.""" return self._root ## @@ -184,22 +187,26 @@ class ProjectModel(QAbstractItemModel): ## def rowCount(self, index: QModelIndex) -> int: + """Return the number of rows for an entry.""" if index.isValid(): return index.internalPointer().childCount() return self._root.childCount() def columnCount(self, index: QModelIndex) -> int: + """Return the number of columns for an entry.""" return 4 def parent(self, index: QModelIndex) -> QModelIndex: + """Get the parent model index of another index.""" if index.isValid(): if parent := index.internalPointer().parent(): return self.createIndex(parent.row(), 0, parent) return QModelIndex() def index(self, row: int, column: int, parent: QModelIndex = QModelIndex()) -> QModelIndex: + """get the index of a child item of a parent.""" if parent.isValid(): - item = parent.internalPointer() + item: ProjectNode = parent.internalPointer() else: item = self._root @@ -211,10 +218,11 @@ class ProjectModel(QAbstractItemModel): return QModelIndex() - def data(self, index: QModelIndex, role: Qt.ItemDataRole) -> Any: + def data(self, index: QModelIndex, role: Qt.ItemDataRole) -> T_NodeData: + """Return display data for a project node.""" if not index.isValid(): return None - node = index.internalPointer() + node: ProjectNode = index.internalPointer() return node.data(index.column(), role) # def addChild(self, node: ProjectNode, parent: QModelIndex) -> None: @@ -230,6 +238,7 @@ class ProjectModel(QAbstractItemModel): ## def node(self, index: QModelIndex) -> ProjectNode | None: + """Return the node for a given model index.""" if index.isValid(): return index.internalPointer() return None diff --git a/novelwriter/core/tree.py b/novelwriter/core/tree.py index 36d1dbfc..728a3000 100644 --- a/novelwriter/core/tree.py +++ b/novelwriter/core/tree.py @@ -225,13 +225,21 @@ class NWTree: return - def refreshNode(self, tHandle: str) -> None: - """Refresh node data on item change.""" - if node := self._nodes.get(tHandle): - node.refresh() - index = self._model.indexFromNode(node) - SHARED.projectSignalProxy({"event": "projectItem", "handle": tHandle}) - self._model.dataChanged.emit(index, index) + def refreshItems(self, items: list[str], isRange: bool = False) -> None: + """Refresh these items on the GUI. If they are an ordered range, + also set the isRange flag to True. + """ + indices = [] + for tHandle in items: + if node := self._nodes.get(tHandle): + node.refresh() + 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) return def _buildTree(self, items: dict[str, NWItem]) -> dict[str, NWItem]: diff --git a/novelwriter/gui/projtree.py b/novelwriter/gui/projtree.py index 4017b3f5..7e2643a7 100644 --- a/novelwriter/gui/projtree.py +++ b/novelwriter/gui/projtree.py @@ -198,6 +198,7 @@ class GuiProjectView(QWidget): newLabel, dlgOk = GuiEditLabel.getLabel(self, text=name or nwItem.itemName) if dlgOk: nwItem.setName(newLabel) + nwItem.notifyToRefresh() return @pyqtSlot(str, bool) diff --git a/tests/test_core/test_core_item.py b/tests/test_core/test_core_item.py index 3e0fd0bb..b27ab593 100644 --- a/tests/test_core/test_core_item.py +++ b/tests/test_core/test_core_item.py @@ -176,11 +176,6 @@ def testCoreItem_Setters(mockGUI, mockRnd, fncPath): item.setCursorPos(1) assert item.cursorPos == 1 - # Initial Count - item.setWordCount(234) - item.saveInitialCount() - assert item.initCount == 234 - @pytest.mark.core def testCoreItem_Methods(mockGUI, mockRnd, fncPath):