From ffe6cd0a6593a186c7a2aebddb08d27648ca719a Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Fri, 22 Nov 2024 00:15:12 +0100 Subject: [PATCH] Update item change signals --- novelwriter/core/index.py | 4 ---- novelwriter/core/itemmodel.py | 10 +++++++--- novelwriter/core/tree.py | 28 ++++++++++++++++++---------- novelwriter/enum.py | 7 +++++++ novelwriter/gui/doceditor.py | 10 +++++----- novelwriter/gui/docviewer.py | 8 ++++---- novelwriter/gui/docviewerpanel.py | 6 +++--- novelwriter/gui/itemdetails.py | 24 ++++++++++++++++++------ novelwriter/gui/projtree.py | 27 ++++++++++++++------------- novelwriter/guimain.py | 12 +++++------- novelwriter/shared.py | 18 +++++++++--------- 11 files changed, 90 insertions(+), 64 deletions(-) diff --git a/novelwriter/core/index.py b/novelwriter/core/index.py index 30d32413..a78e3b06 100644 --- a/novelwriter/core/index.py +++ b/novelwriter/core/index.py @@ -297,10 +297,6 @@ class NWIndex: self._rootChange[tItem.itemRoot] = nowTime if not blockSignal: tItem.notifyToRefresh() - SHARED.indexSignalProxy({ - "event": "scanText", - "handle": tHandle, - }) return True diff --git a/novelwriter/core/itemmodel.py b/novelwriter/core/itemmodel.py index 0766c905..aab17c61 100644 --- a/novelwriter/core/itemmodel.py +++ b/novelwriter/core/itemmodel.py @@ -78,6 +78,7 @@ class ProjectNode: self._row = 0 self._cache: dict[int, T_NodeData] = {} self._flags = NODE_FLAGS + self._count = 0 self.refresh() self.updateCount() return @@ -193,7 +194,6 @@ class ProjectNode: else: child._row = len(self._children) self._children.append(child) - self.updateCount() return def takeChild(self, pos: int) -> ProjectNode | None: @@ -450,8 +450,12 @@ class ProjectModel(QAbstractItemModel): for node in pruned: if node.item.itemParent not in handles: index = self.indexFromNode(node) - if child := self.removeChild(index.parent(), index.row()): - self.insertChild(child, target, pos) + if temp := self.removeChild(index.parent(), index.row()): + self.insertChild(temp, target, pos) + for child in reversed(node.allChildren()): + node._updateRelationships(child) + child.item.notifyToRefresh() + node.item.notifyToRefresh() return ## diff --git a/novelwriter/core/tree.py b/novelwriter/core/tree.py index 5231f90c..4a7a85d2 100644 --- a/novelwriter/core/tree.py +++ b/novelwriter/core/tree.py @@ -36,7 +36,7 @@ from novelwriter import SHARED from novelwriter.constants import nwFiles, nwLabels, trConst from novelwriter.core.item import NWItem from novelwriter.core.itemmodel import ProjectModel, ProjectNode -from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType +from novelwriter.enum import nwChange, nwItemClass, nwItemLayout, nwItemType from novelwriter.error import logException if TYPE_CHECKING: # pragma: no cover @@ -90,6 +90,8 @@ class NWTree: @property def trash(self) -> ProjectNode | None: """Return trash node, if it exists.""" + if self._trash: + return self._trash return self._getTrashNode() @property @@ -108,8 +110,8 @@ class NWTree: """Clear the item tree entirely.""" oldModel = self._model self._model = ProjectModel(self) - self._items = {} - self._nodes = {} + self._items.clear() + self._nodes.clear() self._trash = None oldModel.deleteLater() del oldModel @@ -124,7 +126,7 @@ class NWTree: self._model.insertChild(node, index, pos) self._nodes[item.itemHandle] = node self._items[item.itemHandle] = item - self._project.setProjectChanged(True) + self._itemChange(item.itemHandle, nwChange.CREATE) else: logger.error("Could not locate parent of '%s'", item.itemHandle) return False @@ -133,7 +135,7 @@ class NWTree: self._model.insertChild(node, QModelIndex(), pos) self._nodes[item.itemHandle] = node self._items[item.itemHandle] = item - self._project.setProjectChanged(True) + self._itemChange(item.itemHandle, nwChange.CREATE) else: logger.error("Invalid project item '%s'", item.itemHandle) return False @@ -144,6 +146,7 @@ class NWTree: if (node := self._nodes.get(tHandle)) and tHandle in self._items: index = self._model.indexFromNode(node) if index.isValid() and self._model.removeChild(index.parent(), index.row()): + self._itemChange(tHandle, nwChange.DELETE) del self._nodes[tHandle] del self._items[tHandle] return True @@ -234,18 +237,14 @@ class NWTree: """Refresh these items on the GUI. If they are an ordered range, also set the isRange flag to True. """ - change = False for tHandle in items: if node := self._nodes.get(tHandle): node.refresh() node.updateCount() - SHARED.projectSignalProxy({"event": "projectItem", "handle": tHandle}) 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) + self._itemChange(tHandle, nwChange.UPDATE) return def refreshAllItems(self) -> None: @@ -463,6 +462,13 @@ class NWTree: # Internal Functions ## + def _itemChange(self, tHandle: str, change: nwChange) -> None: + """Signal item change and notify project.""" + logger.debug("Item change: %s -> %s", tHandle, change.name) + self._project.setProjectChanged(True) + SHARED.projectSignalProxy({"event": "itemChanged", "handle": tHandle, "change": change}) + return + def _getTrashNode(self) -> ProjectNode | None: """Get the trash node. If it doesn't exist, create it.""" for node in self._model.root.children: @@ -483,6 +489,7 @@ class NWTree: if parent := self._nodes.get(pHandle): node = ProjectNode(item) parent.addChild(node) + parent.updateCount() self._items[handle] = item self._nodes[handle] = node elif pHandle in items: @@ -491,6 +498,7 @@ class NWTree: elif item.isRootType(): node = ProjectNode(item) self._model.root.addChild(node) + self._model.root.updateCount() self._items[handle] = item self._nodes[handle] = node return remains diff --git a/novelwriter/enum.py b/novelwriter/enum.py index 7441bc9b..c736b765 100644 --- a/novelwriter/enum.py +++ b/novelwriter/enum.py @@ -75,6 +75,13 @@ class nwTrinary(Enum): POSITIVE = 1 +class nwChange(Enum): + + CREATE = 0 + UPDATE = 1 + DELETE = 2 + + class nwDocMode(Enum): VIEW = 0 diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py index d6e326b8..5a5a6ba3 100644 --- a/novelwriter/gui/doceditor.py +++ b/novelwriter/gui/doceditor.py @@ -57,8 +57,8 @@ from novelwriter.common import decodeMimeHandles, minmax, qtLambda, transferCase from novelwriter.constants import nwConst, nwKeyWords, nwShortcode, nwUnicode from novelwriter.core.document import NWDocument from novelwriter.enum import ( - nwComment, nwDocAction, nwDocInsert, nwDocMode, nwItemClass, nwItemType, - nwTrinary + nwChange, nwComment, nwDocAction, nwDocInsert, nwDocMode, nwItemClass, + nwItemType, nwTrinary ) from novelwriter.extensions.configlayout import NColourLabel from novelwriter.extensions.eventfilters import WheelEventFilter @@ -1064,12 +1064,12 @@ class GuiDocEditor(QPlainTextEdit): # Public Slots ## - @pyqtSlot(str) - def updateDocInfo(self, tHandle: str) -> None: + @pyqtSlot(str, Enum) + def onProjectItemChanged(self, tHandle: str, change: nwChange) -> None: """Called when an item label is changed to check if the document title bar needs updating, """ - if tHandle and tHandle == self._docHandle: + if tHandle == self._docHandle and change == nwChange.UPDATE: self.docHeader.setHandle(tHandle) self.docFooter.updateInfo() self.updateDocMargins() diff --git a/novelwriter/gui/docviewer.py b/novelwriter/gui/docviewer.py index 2675eaa1..735c15e1 100644 --- a/novelwriter/gui/docviewer.py +++ b/novelwriter/gui/docviewer.py @@ -43,7 +43,7 @@ from PyQt5.QtWidgets import ( from novelwriter import CONFIG, SHARED from novelwriter.common import decodeMimeHandles, qtLambda from novelwriter.constants import nwConst, nwStyles, nwUnicode -from novelwriter.enum import nwDocAction, nwDocMode, nwItemType +from novelwriter.enum import nwChange, nwDocAction, nwDocMode, nwItemType from novelwriter.error import logException from novelwriter.extensions.configlayout import NColourLabel from novelwriter.extensions.eventfilters import WheelEventFilter @@ -346,10 +346,10 @@ class GuiDocViewer(QTextBrowser): # Public Slots ## - @pyqtSlot(str) - def updateDocInfo(self, tHandle: str) -> None: + @pyqtSlot(str, Enum) + def onProjectItemChanged(self, tHandle: str, change: nwChange) -> None: """Update the header title bar if needed.""" - if tHandle and tHandle == self._docHandle: + if tHandle == self._docHandle and change == nwChange.UPDATE: self.docHeader.setHandle(tHandle) self.updateDocMargins() return diff --git a/novelwriter/gui/docviewerpanel.py b/novelwriter/gui/docviewerpanel.py index 01c9d2f1..91c4eb61 100644 --- a/novelwriter/gui/docviewerpanel.py +++ b/novelwriter/gui/docviewerpanel.py @@ -37,7 +37,7 @@ from novelwriter import CONFIG, SHARED from novelwriter.common import checkInt from novelwriter.constants import nwLabels, nwLists, nwStyles, trConst from novelwriter.core.index import IndexHeading, IndexItem -from novelwriter.enum import nwDocMode, nwItemClass +from novelwriter.enum import nwChange, nwDocMode, nwItemClass from novelwriter.extensions.modified import NIconToolButton from novelwriter.gui.theme import STYLES_FLAT_TABS, STYLES_MIN_TOOLBUTTON from novelwriter.types import QtDecoration, QtHeaderFixed, QtHeaderToContents, QtUserRole @@ -151,8 +151,8 @@ class GuiDocViewerPanel(QWidget): self.updateHandle(self._lastHandle) return - @pyqtSlot(str) - def projectItemChanged(self, tHandle: str) -> None: + @pyqtSlot(str, Enum) + def onProjectItemChanged(self, tHandle: str, change: nwChange) -> None: """Update meta data for project item.""" self.tabBackRefs.refreshDocument(tHandle) activeOnly = self.aInactive.isChecked() diff --git a/novelwriter/gui/itemdetails.py b/novelwriter/gui/itemdetails.py index 2b98f730..2a90c7b5 100644 --- a/novelwriter/gui/itemdetails.py +++ b/novelwriter/gui/itemdetails.py @@ -25,12 +25,15 @@ from __future__ import annotations import logging +from enum import Enum + from PyQt5.QtCore import pyqtSlot from PyQt5.QtWidgets import QGridLayout, QLabel, QWidget from novelwriter import CONFIG, SHARED from novelwriter.common import elide from novelwriter.constants import nwLabels, nwStats, trConst +from novelwriter.enum import nwChange from novelwriter.types import ( QtAlignLeft, QtAlignLeftBase, QtAlignRight, QtAlignRightBase, QtAlignRightMiddle @@ -220,12 +223,7 @@ class GuiItemDetails(QWidget): self.updateViewBox(self._handle) return - ## - # Public Slots - ## - - @pyqtSlot(str) - def updateViewBox(self, tHandle: str) -> None: + def updateViewBox(self, tHandle: str | None) -> None: """Populate the details box from a given handle.""" if tHandle is None: self.clearDetails() @@ -286,6 +284,20 @@ class GuiItemDetails(QWidget): return + ## + # Public Slots + ## + + @pyqtSlot(str, Enum) + def onProjectItemChanged(self, tHandle: str, change: nwChange) -> None: + """Process project item change.""" + if tHandle == self._handle: + if change == nwChange.UPDATE: + self.updateViewBox(tHandle) + elif change == nwChange.DELETE: + self.updateViewBox(None) + return + @pyqtSlot(str, int, int, int) def updateCounts(self, tHandle: str, cC: int, wC: int, pC: int) -> None: """Update the counts if the handle is the same as the one we're diff --git a/novelwriter/gui/projtree.py b/novelwriter/gui/projtree.py index 793976be..373f9275 100644 --- a/novelwriter/gui/projtree.py +++ b/novelwriter/gui/projtree.py @@ -48,7 +48,7 @@ from novelwriter.dialogs.docmerge import GuiDocMerge from novelwriter.dialogs.docsplit import GuiDocSplit from novelwriter.dialogs.editlabel import GuiEditLabel from novelwriter.dialogs.projectsettings import GuiProjectSettings -from novelwriter.enum import nwDocMode, nwItemClass, nwItemLayout, nwItemType +from novelwriter.enum import nwChange, nwDocMode, nwItemClass, nwItemLayout, nwItemType from novelwriter.extensions.modified import NIconToolButton from novelwriter.gui.theme import STYLES_MIN_TOOLBUTTON from novelwriter.types import ( @@ -215,10 +215,10 @@ class GuiProjectView(QWidget): self.projTree.setActiveHandle(tHandle) return - @pyqtSlot(str) - def projectItemChanged(self, tHandle: str) -> None: + @pyqtSlot(str, Enum) + def onProjectItemChanged(self, tHandle: str, change: nwChange) -> None: """Refresh other content when project item changed.""" - self.projBar.processTemplateDocuments(tHandle) + self.projBar.processTemplateDocuments(tHandle, change) return @pyqtSlot(str) @@ -409,16 +409,19 @@ class GuiProjectToolBar(QWidget): """Build the templates menu.""" for tHandle, _ in SHARED.project.tree.iterRoots(nwItemClass.TEMPLATE): for dHandle in SHARED.project.tree.subTree(tHandle): - self.processTemplateDocuments(dHandle) + self.processTemplateDocuments(dHandle, nwChange.CREATE) return - def processTemplateDocuments(self, tHandle: str) -> None: + def processTemplateDocuments(self, tHandle: str, change: nwChange) -> None: """Process change in tree items to update menu content.""" - if item := SHARED.project.tree[tHandle]: - if item.isTemplateFile() and item.isActive: - self.mTemplates.addUpdate(tHandle, item.itemName, item.getMainIcon()) - elif tHandle in self.mTemplates: - self.mTemplates.remove(tHandle) + if change in (nwChange.CREATE, nwChange.UPDATE): + if item := SHARED.project.tree[tHandle]: + if item.isTemplateFile() and item.isActive: + self.mTemplates.addUpdate(tHandle, item.itemName, item.getMainIcon()) + elif tHandle in self.mTemplates: + self.mTemplates.remove(tHandle) + elif change == nwChange.DELETE and tHandle in self.mTemplates: + self.mTemplates.remove(tHandle) return ## @@ -916,9 +919,7 @@ class GuiProjectTree(QTreeView): if node := model.node(index): for child in reversed(node.allChildren()): SHARED.project.removeItem(child.item.itemHandle) - self.projView.projBar.processTemplateDocuments(child.item.itemHandle) SHARED.project.removeItem(node.item.itemHandle) - self.projView.projBar.processTemplateDocuments(node.item.itemHandle) elif trashNode := SHARED.project.tree.trash: if askFirst and not SHARED.question(self.tr("Move selected item(s) to Trash?")): diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index 6a724e4a..802b1791 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -213,14 +213,12 @@ class GuiMain(QMainWindow): SHARED.indexChangedTags.connect(self.docEditor.updateChangedTags) SHARED.indexChangedTags.connect(self.docViewerPanel.updateChangedTags) SHARED.indexCleared.connect(self.docViewerPanel.indexWasCleared) - SHARED.indexScannedText.connect(self.docViewerPanel.projectItemChanged) - SHARED.indexScannedText.connect(self.itemDetails.updateViewBox) SHARED.mainClockTick.connect(self._timeTick) - SHARED.projectItemChanged.connect(self.projView.projectItemChanged) - SHARED.projectItemChanged.connect(self.docEditor.updateDocInfo) - SHARED.projectItemChanged.connect(self.docViewer.updateDocInfo) - SHARED.projectItemChanged.connect(self.docViewerPanel.projectItemChanged) - SHARED.projectItemChanged.connect(self.itemDetails.updateViewBox) + SHARED.projectItemChanged.connect(self.docEditor.onProjectItemChanged) + SHARED.projectItemChanged.connect(self.docViewer.onProjectItemChanged) + SHARED.projectItemChanged.connect(self.docViewerPanel.onProjectItemChanged) + SHARED.projectItemChanged.connect(self.itemDetails.onProjectItemChanged) + SHARED.projectItemChanged.connect(self.projView.onProjectItemChanged) SHARED.projectStatusChanged.connect(self.mainStatus.updateProjectStatus) SHARED.projectStatusMessage.connect(self.mainStatus.setStatusMessage) SHARED.spellLanguageChanged.connect(self.mainStatus.setLanguage) diff --git a/novelwriter/shared.py b/novelwriter/shared.py index cdeb7614..148f84d0 100644 --- a/novelwriter/shared.py +++ b/novelwriter/shared.py @@ -26,6 +26,7 @@ from __future__ import annotations import logging +from enum import Enum from pathlib import Path from time import time from typing import TYPE_CHECKING, TypeVar @@ -37,7 +38,7 @@ from PyQt5.QtWidgets import QFileDialog, QFontDialog, QMessageBox, QWidget from novelwriter.common import formatFileFilter from novelwriter.constants import nwFiles from novelwriter.core.spellcheck import NWSpellEnchant -from novelwriter.enum import nwItemClass +from novelwriter.enum import nwChange, nwItemClass if TYPE_CHECKING: # pragma: no cover from novelwriter.core.project import NWProject @@ -60,9 +61,8 @@ class SharedData(QObject): indexAvailable = pyqtSignal() indexChangedTags = pyqtSignal(list, list) indexCleared = pyqtSignal() - indexScannedText = pyqtSignal(str) mainClockTick = pyqtSignal() - projectItemChanged = pyqtSignal(str) + projectItemChanged = pyqtSignal(str, Enum) projectStatusChanged = pyqtSignal(bool) projectStatusMessage = pyqtSignal(str) spellLanguageChanged = pyqtSignal(str, str) @@ -319,11 +319,9 @@ class SharedData(QObject): def indexSignalProxy(self, data: dict) -> None: """Emit signals on behalf of the index.""" event = data.get("event") - logger.debug("Received '%s' event from the index", event) + logger.debug("Signal Proxy: %s", event) if event == "updateTags": self.indexChangedTags.emit(data.get("updated", []), data.get("deleted", [])) - elif event == "scanText": - self.indexScannedText.emit(data.get("handle", "")) elif event == "clearIndex": self.indexCleared.emit() elif event == "buildIndex": @@ -333,11 +331,13 @@ class SharedData(QObject): def projectSignalProxy(self, data: dict) -> None: """Emit signals on project data change.""" event = data.get("event") - logger.debug("Received '%s' event from project data", event) + logger.debug("Signal Proxy: %s", event) if event == "statusLabels": self.statusLabelsChanged.emit(data.get("kind", "")) - elif event == "projectItem": - self.projectItemChanged.emit(data.get("handle", "")) + elif event == "itemChanged": + self.projectItemChanged.emit( + data.get("handle", ""), data.get("change", nwChange.UPDATE) + ) return ##