Update item change signals

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