From d55f53be0a69936aa3c88455acf581b6ac41076d Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Thu, 21 Nov 2024 01:18:28 +0100 Subject: [PATCH] Clean up item notification and item main icon --- novelwriter/core/index.py | 1 + novelwriter/core/item.py | 4 ++ novelwriter/core/itemmodel.py | 6 +-- novelwriter/core/status.py | 3 +- novelwriter/core/tree.py | 8 ++++ novelwriter/dialogs/docmerge.py | 24 +++------- novelwriter/gui/docviewerpanel.py | 12 +---- novelwriter/gui/itemdetails.py | 5 +- novelwriter/gui/projtree.py | 77 +++++++++++------------------- novelwriter/gui/search.py | 6 +-- novelwriter/guimain.py | 16 +++---- novelwriter/tools/manusbuild.py | 6 +-- novelwriter/tools/manussettings.py | 12 +---- 13 files changed, 63 insertions(+), 117 deletions(-) diff --git a/novelwriter/core/index.py b/novelwriter/core/index.py index 812b15e2..30d32413 100644 --- a/novelwriter/core/index.py +++ b/novelwriter/core/index.py @@ -296,6 +296,7 @@ class NWIndex: self._indexChange = nowTime self._rootChange[tItem.itemRoot] = nowTime if not blockSignal: + tItem.notifyToRefresh() SHARED.indexSignalProxy({ "event": "scanText", "handle": tHandle, diff --git a/novelwriter/core/item.py b/novelwriter/core/item.py index 8265b299..2448e314 100644 --- a/novelwriter/core/item.py +++ b/novelwriter/core/item.py @@ -321,6 +321,10 @@ class NWItem: return trConst(nwLabels.ITEM_DESCRIPTION.get(descKey, "")) + def getMainIcon(self) -> QIcon: + """Get the main item icon.""" + return SHARED.theme.getItemIcon(self._type, self._class, self._layout, self._heading) + def getImportStatus(self) -> tuple[str, QIcon]: """Return the relevant importance or status label and icon for the current item based on its class. diff --git a/novelwriter/core/itemmodel.py b/novelwriter/core/itemmodel.py index fd6a365b..4f9828c0 100644 --- a/novelwriter/core/itemmodel.py +++ b/novelwriter/core/itemmodel.py @@ -31,7 +31,6 @@ from typing import TYPE_CHECKING from PyQt5.QtCore import QAbstractItemModel, QMimeData, QModelIndex, Qt from PyQt5.QtGui import QIcon -from novelwriter import SHARED from novelwriter.common import decodeMimeHandles, minmax from novelwriter.constants import nwConst from novelwriter.core.item import NWItem @@ -116,10 +115,7 @@ class ProjectNode: def refresh(self) -> None: """Refresh data values.""" # Label - self._cache[C_LABEL_ICON] = SHARED.theme.getItemIcon( - self._item.itemType, self._item.itemClass, - self._item.itemLayout, self._item.mainHeading - ) + self._cache[C_LABEL_ICON] = self._item.getMainIcon() self._cache[C_LABEL_TEXT] = self._item.itemName # Count diff --git a/novelwriter/core/status.py b/novelwriter/core/status.py index dc0ef397..a5e2e4d6 100644 --- a/novelwriter/core/status.py +++ b/novelwriter/core/status.py @@ -130,8 +130,9 @@ class NWStatus: if self._default not in self._store: self._default = next(iter(self._store)) if self._store else None - # Emit the change signal + # Emit the change signal and refresh tree SHARED.projectSignalProxy({"event": "statusLabels", "kind": self._prefix}) + SHARED.project.tree.refreshAllItems() return diff --git a/novelwriter/core/tree.py b/novelwriter/core/tree.py index cad53098..5231f90c 100644 --- a/novelwriter/core/tree.py +++ b/novelwriter/core/tree.py @@ -248,6 +248,14 @@ class NWTree: self._project.setProjectChanged(True) return + def refreshAllItems(self) -> None: + """Refresh all items in the tree.""" + for node in reversed(self._model.root.allChildren()): + node.refresh() + node.updateCount(propagate=False) + self._model.layoutChanged.emit() + return + def checkConsistency(self, prefix: str) -> tuple[int, int]: """Check the project tree consistency. Also check the content folder and add back files that were discovered but were not diff --git a/novelwriter/dialogs/docmerge.py b/novelwriter/dialogs/docmerge.py index d745dadb..02c06cb7 100644 --- a/novelwriter/dialogs/docmerge.py +++ b/novelwriter/dialogs/docmerge.py @@ -162,23 +162,13 @@ class GuiDocMerge(NDialog): self._data = {} self._data["sHandle"] = sHandle self._data["origItems"] = itemList - self.listBox.clear() for tHandle in itemList: - nwItem = SHARED.project.tree[tHandle] - if nwItem is None or not nwItem.isFileType(): - continue - - itemIcon = SHARED.theme.getItemIcon( - nwItem.itemType, nwItem.itemClass, nwItem.itemLayout, nwItem.mainHeading - ) - - newItem = QListWidgetItem() - newItem.setIcon(itemIcon) - newItem.setText(nwItem.itemName) - newItem.setData(self.D_HANDLE, tHandle) - newItem.setCheckState(Qt.CheckState.Checked) - - self.listBox.addItem(newItem) - + if (nwItem := SHARED.project.tree[tHandle]) and nwItem.isFileType(): + item = QListWidgetItem() + item.setIcon(nwItem.getMainIcon()) + item.setText(nwItem.itemName) + item.setData(self.D_HANDLE, tHandle) + item.setCheckState(Qt.CheckState.Checked) + self.listBox.addItem(item) return diff --git a/novelwriter/gui/docviewerpanel.py b/novelwriter/gui/docviewerpanel.py index 8529ceef..01c9d2f1 100644 --- a/novelwriter/gui/docviewerpanel.py +++ b/novelwriter/gui/docviewerpanel.py @@ -339,17 +339,13 @@ class _ViewPanelBackRefs(QTreeWidget): def _setTreeItemValues(self, tHandle: str, sTitle: str, hItem: IndexHeading) -> None: """Add or update a tree item.""" if nwItem := SHARED.project.tree[tHandle]: - docIcon = SHARED.theme.getItemIcon( - nwItem.itemType, nwItem.itemClass, - nwItem.itemLayout, nwItem.mainHeading - ) iLevel = nwStyles.H_LEVEL.get(hItem.level, 0) if nwItem.isDocumentLayout() else 5 hDec = SHARED.theme.getHeaderDecorationNarrow(iLevel) tKey = f"{tHandle}:{sTitle}" trItem = self._treeMap[tKey] if tKey in self._treeMap else QTreeWidgetItem() - trItem.setIcon(self.C_DOC, docIcon) + trItem.setIcon(self.C_DOC, nwItem.getMainIcon()) trItem.setText(self.C_DOC, nwItem.itemName) trItem.setToolTip(self.C_DOC, nwItem.itemName) trItem.setIcon(self.C_EDIT, self._editIcon) @@ -448,10 +444,6 @@ class _ViewPanelKeyWords(QTreeWidget): def addUpdateEntry(self, tag: str, name: str, iItem: IndexItem, hItem: IndexHeading) -> None: """Add a new entry, or update an existing one.""" nwItem = iItem.item - docIcon = SHARED.theme.getItemIcon( - nwItem.itemType, nwItem.itemClass, - nwItem.itemLayout, nwItem.mainHeading - ) impLabel, impIcon = nwItem.getImportStatus() iLevel = nwStyles.H_LEVEL.get(hItem.level, 0) if nwItem.isDocumentLayout() else 5 hDec = SHARED.theme.getHeaderDecorationNarrow(iLevel) @@ -468,7 +460,7 @@ class _ViewPanelKeyWords(QTreeWidget): trItem.setIcon(self.C_IMPORT, impIcon) trItem.setText(self.C_IMPORT, impLabel) trItem.setToolTip(self.C_IMPORT, impLabel) - trItem.setIcon(self.C_DOC, docIcon) + trItem.setIcon(self.C_DOC, nwItem.getMainIcon()) trItem.setText(self.C_DOC, nwItem.itemName) trItem.setToolTip(self.C_DOC, nwItem.itemName) trItem.setData(self.C_TITLE, QtDecoration, hDec) diff --git a/novelwriter/gui/itemdetails.py b/novelwriter/gui/itemdetails.py index 6d67d74c..2b98f730 100644 --- a/novelwriter/gui/itemdetails.py +++ b/novelwriter/gui/itemdetails.py @@ -269,10 +269,7 @@ class GuiItemDetails(QWidget): # Layout # ====== - usageIcon = SHARED.theme.getItemIcon( - nwItem.itemType, nwItem.itemClass, nwItem.itemLayout, nwItem.mainHeading - ) - self.usageIcon.setPixmap(usageIcon.pixmap(iPx, iPx)) + self.usageIcon.setPixmap(nwItem.getMainIcon().pixmap(iPx, iPx)) self.usageData.setText(nwItem.describeMe()) # Counts diff --git a/novelwriter/gui/projtree.py b/novelwriter/gui/projtree.py index 93f31cc9..793976be 100644 --- a/novelwriter/gui/projtree.py +++ b/novelwriter/gui/projtree.py @@ -130,7 +130,6 @@ class GuiProjectView(QWidget): # Signals self.selectedItemChanged.connect(self.projBar.treeSelectionChanged) - self.projTree.itemRefreshed.connect(self.projBar.treeItemRefreshed) self.projBar.newDocumentFromTemplate.connect(self.createFileFromTemplate) # Function Mappings @@ -145,7 +144,6 @@ class GuiProjectView(QWidget): def updateTheme(self) -> None: """Update theme elements.""" self.projBar.updateTheme() - self.populateTree() return def initSettings(self) -> None: @@ -162,21 +160,12 @@ class GuiProjectView(QWidget): def openProjectTasks(self) -> None: """Run open project tasks.""" - self.populateTree() + self.projTree.loadModel() + self.projBar.buildTemplatesMenu() self.projBar.buildQuickLinksMenu() self.projBar.setEnabled(True) return - def saveProjectTasks(self) -> None: - """Run save project tasks.""" - # self.projTree.saveTreeOrder() - return - - def populateTree(self) -> None: - """Build the tree structure from project data.""" - self.projTree.loadModel() - return - def setTreeFocus(self) -> None: """Forward the set focus call to the tree widget.""" self.projTree.setFocus() @@ -227,10 +216,9 @@ class GuiProjectView(QWidget): return @pyqtSlot(str) - def updateItemValues(self, tHandle: str) -> None: - """Update tree item.""" - # if nwItem := SHARED.project.tree[tHandle]: - # self.projTree.setTreeItemValues(nwItem) + def projectItemChanged(self, tHandle: str) -> None: + """Refresh other content when project item changed.""" + self.projBar.processTemplateDocuments(tHandle) return @pyqtSlot(str) @@ -246,12 +234,6 @@ class GuiProjectView(QWidget): self.projBar.buildQuickLinksMenu() return - @pyqtSlot(str) - def refreshUserLabels(self, kind: str) -> None: - """Refresh status or importance labels.""" - self.projTree.refreshUserLabels(kind) - return - class GuiProjectToolBar(QWidget): @@ -398,6 +380,7 @@ class GuiProjectToolBar(QWidget): self.aAddNote.setIcon(SHARED.theme.getIcon("proj_note")) self.aAddFolder.setIcon(SHARED.theme.getIcon("proj_folder")) + self.buildTemplatesMenu() self.buildQuickLinksMenu() self._buildRootMenu() @@ -422,19 +405,26 @@ class GuiProjectToolBar(QWidget): ) return + def buildTemplatesMenu(self) -> None: + """Build the templates menu.""" + for tHandle, _ in SHARED.project.tree.iterRoots(nwItemClass.TEMPLATE): + for dHandle in SHARED.project.tree.subTree(tHandle): + self.processTemplateDocuments(dHandle) + return + + def processTemplateDocuments(self, tHandle: str) -> 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) + return + ## # Public Slots ## - @pyqtSlot(str, NWItem, QIcon) - def treeItemRefreshed(self, tHandle: str, nwItem: NWItem, icon: QIcon) -> None: - """Process change in tree items to update menu content.""" - if nwItem.isTemplateFile() and nwItem.isActive: - self.mTemplates.addUpdate(tHandle, nwItem.itemName, icon) - elif tHandle in self.mTemplates: - self.mTemplates.remove(tHandle) - return - @pyqtSlot(str) def treeSelectionChanged(self, tHandle: str) -> None: """Toggle the visibility of the new item entries for novel @@ -482,8 +472,6 @@ class GuiProjectToolBar(QWidget): class GuiProjectTree(QTreeView): - itemRefreshed = pyqtSignal(str, NWItem, QIcon) - def __init__(self, projView: GuiProjectView) -> None: super().__init__(parent=projView) @@ -635,6 +623,7 @@ class GuiProjectTree(QTreeView): SHARED.project.newRoot(itemClass, pos) self.restoreExpandedState() + self.projView.rootFolderChanged.emit(tHandle) elif itemType in (nwItemType.FILE, nwItemType.FOLDER): @@ -927,7 +916,9 @@ 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?")): @@ -1043,22 +1034,6 @@ class GuiProjectTree(QTreeView): return node return None - # =========================================================================================== # - # Old Code - # =========================================================================================== # - - def refreshUserLabels(self, kind: str) -> None: - """Refresh status or importance labels.""" - # if kind == "s": - # for nwItem in SHARED.project.tree: - # if nwItem.isNovelLike(): - # self.setTreeItemValues(nwItem) - # elif kind == "i": - # for nwItem in SHARED.project.tree: - # if not nwItem.isNovelLike(): - # self.setTreeItemValues(nwItem) - return - class _UpdatableMenu(QMenu): @@ -1350,6 +1325,8 @@ class _TreeContextMenu(QMenu): """Add move to Trash action.""" if self._model.trashSelection(self._indices): text = self.tr("Delete Permanently") + elif len(self._indices) == 1 and self._item.isRootType(): + text = self.tr("Delete Permanently") else: text = self.tr("Move to Trash") action = self.addAction(text) diff --git a/novelwriter/gui/search.py b/novelwriter/gui/search.py index f18e98ee..cbe66442 100644 --- a/novelwriter/gui/search.py +++ b/novelwriter/gui/search.py @@ -334,15 +334,11 @@ class GuiProjectSearch(QWidget): """Populate the result tree.""" if results and nwItem: tHandle = nwItem.itemHandle - docIcon = SHARED.theme.getItemIcon( - nwItem.itemType, nwItem.itemClass, - nwItem.itemLayout, nwItem.mainHeading - ) ext = "+" if capped else "" tItem = QTreeWidgetItem() tItem.setText(self.C_NAME, nwItem.itemName) - tItem.setIcon(self.C_NAME, docIcon) + tItem.setIcon(self.C_NAME, nwItem.getMainIcon()) tItem.setData(self.C_NAME, self.D_HANDLE, tHandle) tItem.setText(self.C_COUNT, f"({len(results):n}{ext})") tItem.setTextAlignment(self.C_COUNT, QtAlignRight) diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index 93396f8d..6a724e4a 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -215,8 +215,8 @@ class GuiMain(QMainWindow): SHARED.indexCleared.connect(self.docViewerPanel.indexWasCleared) SHARED.indexScannedText.connect(self.docViewerPanel.projectItemChanged) SHARED.indexScannedText.connect(self.itemDetails.updateViewBox) - SHARED.indexScannedText.connect(self.projView.updateItemValues) 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) @@ -225,7 +225,6 @@ class GuiMain(QMainWindow): SHARED.projectStatusMessage.connect(self.mainStatus.setStatusMessage) SHARED.spellLanguageChanged.connect(self.mainStatus.setLanguage) SHARED.statusLabelsChanged.connect(self.docViewerPanel.updateStatusLabels) - SHARED.statusLabelsChanged.connect(self.projView.refreshUserLabels) self.mainMenu.requestDocAction.connect(self._passDocumentAction) self.mainMenu.requestDocInsert.connect(self._passDocumentInsert) @@ -512,11 +511,9 @@ class GuiMain(QMainWindow): def saveProject(self, autoSave: bool = False) -> bool: """Save the current project.""" - if not SHARED.hasProject: - logger.error("No project open") - return False - self.projView.saveProjectTasks() - return SHARED.saveProject(autoSave=autoSave) + if SHARED.hasProject: + return SHARED.saveProject(autoSave=autoSave) + return False ## # Document Actions @@ -743,9 +740,8 @@ class GuiMain(QMainWindow): QApplication.setOverrideCursor(QCursor(Qt.CursorShape.WaitCursor)) tStart = time() - self.projView.saveProjectTasks() SHARED.project.index.rebuildIndex() - self.projView.populateTree() + SHARED.project.tree.refreshAllItems() self.novelView.refreshTree() tEnd = time() @@ -1063,7 +1059,7 @@ class GuiMain(QMainWindow): )) if tree: - self.projView.populateTree() + SHARED.project.tree.refreshAllItems() if theme: # We are doing this manually instead of connecting to diff --git a/novelwriter/tools/manusbuild.py b/novelwriter/tools/manusbuild.py index 3eba76ac..6577290d 100644 --- a/novelwriter/tools/manusbuild.py +++ b/novelwriter/tools/manusbuild.py @@ -394,13 +394,9 @@ class GuiManuscriptBuild(NDialog): if isinstance(rItem, NWItem): rootMap[rHandle] = rItem.itemName - itemIcon = SHARED.theme.getItemIcon( - nwItem.itemType, nwItem.itemClass, - nwItem.itemLayout, nwItem.mainHeading - ) rootName = rootMap.get(rHandle, "??????") item = QListWidgetItem(f"{rootName}: {nwItem.itemName}") - item.setIcon(itemIcon) + item.setIcon(nwItem.getMainIcon()) self.listContent.addItem(item) return diff --git a/novelwriter/tools/manussettings.py b/novelwriter/tools/manussettings.py index bae2b19d..bca7cbaa 100644 --- a/novelwriter/tools/manussettings.py +++ b/novelwriter/tools/manussettings.py @@ -432,13 +432,8 @@ class _FilterTab(NFixedPage): if nwItem.isInactiveClass() or not self._build.isRootAllowed(rHandle): continue - hLevel = nwItem.mainHeading - itemIcon = SHARED.theme.getItemIcon( - nwItem.itemType, nwItem.itemClass, nwItem.itemLayout, hLevel - ) - trItem = QTreeWidgetItem() - trItem.setIcon(self.C_NAME, itemIcon) + trItem.setIcon(self.C_NAME, nwItem.getMainIcon()) trItem.setText(self.C_NAME, nwItem.itemName) trItem.setData(self.C_DATA, self.D_HANDLE, tHandle) trItem.setData(self.C_DATA, self.D_FILE, isFile) @@ -489,11 +484,8 @@ class _FilterTab(NFixedPage): self.filterOpt.addLabel(self.tr("Select Root Folders")) for tHandle, nwItem in SHARED.project.tree.iterRoots(None): if not nwItem.isInactiveClass(): - itemIcon = SHARED.theme.getItemIcon( - nwItem.itemType, nwItem.itemClass, nwItem.itemLayout - ) self.filterOpt.addItem( - itemIcon, nwItem.itemName, f"root:{tHandle}", + nwItem.getMainIcon(), nwItem.itemName, f"root:{tHandle}", default=self._build.isRootAllowed(tHandle) )