From 06aa2e6c074202906af5ee69af2f42130173fdee Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Thu, 25 Jul 2024 20:39:32 +0200 Subject: [PATCH] Only refresh project tree values when status labels change --- novelwriter/dialogs/projectsettings.py | 16 +-- novelwriter/gui/projtree.py | 114 ++++++++++-------- novelwriter/guimain.py | 7 +- .../test_dialogs/test_dlg_projectsettings.py | 6 +- tests/test_gui/test_gui_docviewerpanel.py | 2 +- tests/test_gui/test_gui_projtree.py | 4 + 6 files changed, 82 insertions(+), 67 deletions(-) diff --git a/novelwriter/dialogs/projectsettings.py b/novelwriter/dialogs/projectsettings.py index e718634f..7e7b4f4a 100644 --- a/novelwriter/dialogs/projectsettings.py +++ b/novelwriter/dialogs/projectsettings.py @@ -58,7 +58,7 @@ class GuiProjectSettings(NDialog): PAGE_IMPORT = 2 PAGE_REPLACE = 3 - newProjectSettingsReady = pyqtSignal(bool) + newProjectSettingsReady = pyqtSignal() def __init__(self, parent: QWidget, gotoPage: int = PAGE_SETTINGS) -> None: super().__init__(parent=parent) @@ -175,7 +175,7 @@ class GuiProjectSettings(NDialog): projAuthor = self.settingsPage.projAuthor.text() projLang = self.settingsPage.projLang.currentData() spellLang = self.settingsPage.spellLang.currentData() - doBackup = not self.settingsPage.doBackup.isChecked() + doBackup = not self.settingsPage.noBackup.isChecked() project.data.setName(projName) project.data.setAuthor(projAuthor) @@ -183,23 +183,19 @@ class GuiProjectSettings(NDialog): project.data.setSpellLang(spellLang) project.setProjectLang(projLang) - rebuildTrees = False - if self.statusPage.changed: logger.debug("Updating status labels") project.data.itemStatus.update(self.statusPage.getNewList()) - rebuildTrees = True if self.importPage.changed: logger.debug("Updating importance labels") project.data.itemImport.update(self.importPage.getNewList()) - rebuildTrees = True if self.replacePage.changed: logger.debug("Updating auto-replace settings") project.data.setAutoReplace(self.replacePage.getNewList()) - self.newProjectSettingsReady.emit(rebuildTrees) + self.newProjectSettingsReady.emit() QApplication.processEvents() self.close() @@ -289,10 +285,10 @@ class _SettingsPage(NScrollableForm): self.spellLang.setCurrentIndex(idx) # Backup on Close - self.doBackup = NSwitch(self) - self.doBackup.setChecked(not data.doBackup) + self.noBackup = NSwitch(self) + self.noBackup.setChecked(not data.doBackup) self.addRow( - self.tr("Disable backup on close"), self.doBackup, + self.tr("Disable backup on close"), self.noBackup, self.tr("Overrides main preferences.") ) diff --git a/novelwriter/gui/projtree.py b/novelwriter/gui/projtree.py index f6e897cb..b6a9fdc0 100644 --- a/novelwriter/gui/projtree.py +++ b/novelwriter/gui/projtree.py @@ -213,7 +213,8 @@ class GuiProjectView(QWidget): @pyqtSlot(str) def updateItemValues(self, tHandle: str) -> None: """Update tree item.""" - self.projTree.setTreeItemValues(tHandle) + if nwItem := SHARED.project.tree[tHandle]: + self.projTree.setTreeItemValues(nwItem) return @pyqtSlot(str) @@ -242,6 +243,12 @@ class GuiProjectView(QWidget): self.projTree.createNewNote(tag, itemClass) return + @pyqtSlot(str) + def refreshUserLabels(self, kind: str) -> None: + """Refresh status or importance labels.""" + self.projTree.refreshUserLabels(kind) + return + class GuiProjectToolBar(QWidget): @@ -792,11 +799,11 @@ class GuiProjectTree(QTreeWidget): def renameTreeItem(self, tHandle: str, name: str = "") -> None: """Open a dialog to edit the label of an item.""" - if tItem := SHARED.project.tree[tHandle]: - newLabel, dlgOk = GuiEditLabel.getLabel(self, text=name or tItem.itemName) + if nwItem := SHARED.project.tree[tHandle]: + newLabel, dlgOk = GuiEditLabel.getLabel(self, text=name or nwItem.itemName) if dlgOk: - tItem.setName(newLabel) - self.setTreeItemValues(tHandle) + nwItem.setName(newLabel) + self.setTreeItemValues(nwItem) self._alertTreeChange(tHandle, flush=False) return @@ -1010,44 +1017,52 @@ class GuiProjectTree(QTreeWidget): return True - def setTreeItemValues(self, tHandle: str) -> None: - """Set the name and flag values for a tree item from a handle in - the project tree. Does not trigger a tree change as the data is - already coming from the project tree. + 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 + + def setTreeItemValues(self, nwItem: NWItem | None) -> None: + """Set the name and flag values for a tree item in the project + tree. Does not trigger a tree change as the data is already + coming from project data. """ - trItem = self._getTreeItem(tHandle) - nwItem = SHARED.project.tree[tHandle] - if trItem is None or nwItem is None: - return + if isinstance(nwItem, NWItem) and (trItem := self._getTreeItem(nwItem.itemHandle)): + itemStatus, statusIcon = nwItem.getImportStatus() + hLevel = nwItem.mainHeading + itemIcon = SHARED.theme.getItemIcon( + nwItem.itemType, nwItem.itemClass, nwItem.itemLayout, hLevel + ) - itemStatus, statusIcon = nwItem.getImportStatus() - hLevel = nwItem.mainHeading - itemIcon = SHARED.theme.getItemIcon( - nwItem.itemType, nwItem.itemClass, nwItem.itemLayout, hLevel - ) + trItem.setIcon(self.C_NAME, itemIcon) + trItem.setText(self.C_NAME, nwItem.itemName) + trItem.setIcon(self.C_STATUS, statusIcon) + trItem.setToolTip(self.C_STATUS, itemStatus) - trItem.setIcon(self.C_NAME, itemIcon) - trItem.setText(self.C_NAME, nwItem.itemName) - trItem.setIcon(self.C_STATUS, statusIcon) - trItem.setToolTip(self.C_STATUS, itemStatus) + if nwItem.isFileType(): + iconName = "checked" if nwItem.isActive else "unchecked" + toolTip = self.trActive if nwItem.isActive else self.trInactive + trItem.setToolTip(self.C_ACTIVE, toolTip) + else: + iconName = "noncheckable" - if nwItem.isFileType(): - iconName = "checked" if nwItem.isActive else "unchecked" - toolTip = self.trActive if nwItem.isActive else self.trInactive - trItem.setToolTip(self.C_ACTIVE, toolTip) - else: - iconName = "noncheckable" + trItem.setIcon(self.C_ACTIVE, SHARED.theme.getIcon(iconName)) - trItem.setIcon(self.C_ACTIVE, SHARED.theme.getIcon(iconName)) + if CONFIG.emphLabels and nwItem.isDocumentLayout(): + trFont = trItem.font(self.C_NAME) + trFont.setBold(hLevel == "H1" or hLevel == "H2") + trFont.setUnderline(hLevel == "H1") + trItem.setFont(self.C_NAME, trFont) - if CONFIG.emphLabels and nwItem.isDocumentLayout(): - trFont = trItem.font(self.C_NAME) - trFont.setBold(hLevel == "H1" or hLevel == "H2") - trFont.setUnderline(hLevel == "H1") - trItem.setFont(self.C_NAME, trFont) - - # Emit Refresh Signal - self.itemRefreshed.emit(tHandle, nwItem, itemIcon) + # Emit Refresh Signal + self.itemRefreshed.emit(nwItem.itemHandle, nwItem, itemIcon) return @@ -1353,7 +1368,8 @@ class GuiProjectTree(QTreeWidget): SHARED.project.index.deleteHandle(mHandle) else: SHARED.project.index.reIndexHandle(mHandle) - self.setTreeItemValues(mHandle) + if mItem := SHARED.project.tree[mHandle]: + self.setTreeItemValues(mItem) # Update word count self.propagateCount(tHandle, nwItemS.wordCount, countChildren=True) @@ -1594,7 +1610,7 @@ class GuiProjectTree(QTreeWidget): self._treeMap[tHandle] = newItem self.propagateCount(tHandle, nwItem.wordCount, countChildren=True) - self.setTreeItemValues(tHandle) + self.setTreeItemValues(nwItem) newItem.setExpanded(nwItem.isExpanded) return newItem @@ -1971,7 +1987,7 @@ class _TreeContextMenu(QMenu): def _toggleItemActive(self) -> None: """Toggle the active status of an item.""" self._item.setActive(not self._item.isActive) - self.projTree.setTreeItemValues(self._handle) + self.projTree.setTreeItemValues(self._item) self.projTree._alertTreeChange(self._handle, flush=False) return @@ -1984,14 +2000,14 @@ class _TreeContextMenu(QMenu): for tItem in self._items: if tItem and tItem.isFileType(): tItem.setActive(isActive) - self.projTree.setTreeItemValues(tItem.itemHandle) + self.projTree.setTreeItemValues(tItem) self.projTree._alertTreeChange(tItem.itemHandle, flush=False) return def _changeItemStatus(self, key: str) -> None: """Set a new status value of an item.""" self._item.setStatus(key) - self.projTree.setTreeItemValues(self._handle) + self.projTree.setTreeItemValues(self._item) self.projTree._alertTreeChange(self._handle, flush=False) return @@ -2000,14 +2016,14 @@ class _TreeContextMenu(QMenu): for tItem in self._items: if tItem and tItem.isNovelLike(): tItem.setStatus(key) - self.projTree.setTreeItemValues(tItem.itemHandle) + self.projTree.setTreeItemValues(tItem) self.projTree._alertTreeChange(tItem.itemHandle, flush=False) return def _changeItemImport(self, key: str) -> None: """Set a new importance value of an item.""" self._item.setImport(key) - self.projTree.setTreeItemValues(self._handle) + self.projTree.setTreeItemValues(self._item) self.projTree._alertTreeChange(self._handle, flush=False) return @@ -2016,7 +2032,7 @@ class _TreeContextMenu(QMenu): for tItem in self._items: if tItem and not tItem.isNovelLike(): tItem.setImport(key) - self.projTree.setTreeItemValues(tItem.itemHandle) + self.projTree.setTreeItemValues(tItem) self.projTree._alertTreeChange(tItem.itemHandle, flush=False) return @@ -2024,11 +2040,11 @@ class _TreeContextMenu(QMenu): """Set a new item layout value of an item.""" if itemLayout == nwItemLayout.DOCUMENT and self._item.documentAllowed(): self._item.setLayout(nwItemLayout.DOCUMENT) - self.projTree.setTreeItemValues(self._handle) + self.projTree.setTreeItemValues(self._item) self.projTree._alertTreeChange(self._handle, flush=False) elif itemLayout == nwItemLayout.NOTE: self._item.setLayout(nwItemLayout.NOTE) - self.projTree.setTreeItemValues(self._handle) + self.projTree.setTreeItemValues(self._item) self.projTree._alertTreeChange(self._handle, flush=False) return @@ -2042,12 +2058,12 @@ class _TreeContextMenu(QMenu): if msgYes and itemLayout == nwItemLayout.DOCUMENT and self._item.documentAllowed(): self._item.setType(nwItemType.FILE) self._item.setLayout(nwItemLayout.DOCUMENT) - self.projTree.setTreeItemValues(self._handle) + self.projTree.setTreeItemValues(self._item) self.projTree._alertTreeChange(self._handle, flush=False) elif msgYes and itemLayout == nwItemLayout.NOTE: self._item.setType(nwItemType.FILE) self._item.setLayout(nwItemLayout.NOTE) - self.projTree.setTreeItemValues(self._handle) + self.projTree.setTreeItemValues(self._item) self.projTree._alertTreeChange(self._handle, flush=False) else: logger.info("Folder conversion cancelled") diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index 8e800ae4..a43524c9 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -223,6 +223,7 @@ 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) @@ -1099,15 +1100,13 @@ class GuiMain(QMainWindow): return - @pyqtSlot(bool) - def _processProjectSettingsChanges(self, rebuildTrees: bool) -> None: + @pyqtSlot() + def _processProjectSettingsChanges(self) -> None: """Refresh data dependent on project settings.""" logger.debug("Applying new project settings") SHARED.updateSpellCheckLanguage() self.itemDetails.refreshDetails() self._updateWindowTitle(SHARED.project.data.name) - if rebuildTrees: - self.rebuildTrees() return @pyqtSlot() diff --git a/tests/test_dialogs/test_dlg_projectsettings.py b/tests/test_dialogs/test_dlg_projectsettings.py index 36fba384..ace5bcae 100644 --- a/tests/test_dialogs/test_dlg_projectsettings.py +++ b/tests/test_dialogs/test_dlg_projectsettings.py @@ -113,13 +113,13 @@ def testDlgProjSettings_SettingsPage(qtbot, monkeypatch, nwGUI, fncPath, projPat assert settings.projAuthor.text() == "Jane Smith" assert settings.projLang.currentData() == "en" assert settings.spellLang.currentData() == "en" - assert settings.doBackup.isChecked() is False + assert settings.noBackup.isChecked() is False settings.projName.setText("Project Name") settings.projAuthor.setText("Jane Doe") settings.projLang.setCurrentIndex(settings.projLang.findData("de")) settings.spellLang.setCurrentIndex(settings.spellLang.findData("de")) - settings.doBackup.setChecked(True) + settings.noBackup.setChecked(True) projSettings._doSave() assert project.data.name == "Project Name" @@ -128,7 +128,7 @@ def testDlgProjSettings_SettingsPage(qtbot, monkeypatch, nwGUI, fncPath, projPat assert project.data.spellLang == "de" assert project.data.doBackup is False - nwGUI._processProjectSettingsChanges(False) + nwGUI._processProjectSettingsChanges() assert nwGUI.windowTitle() == "Project Name - novelWriter" # qtbot.stop() diff --git a/tests/test_gui/test_gui_docviewerpanel.py b/tests/test_gui/test_gui_docviewerpanel.py index b53cb626..7fab1376 100644 --- a/tests/test_gui/test_gui_docviewerpanel.py +++ b/tests/test_gui/test_gui_docviewerpanel.py @@ -221,7 +221,7 @@ def testGuiViewerPanel_Tags(qtbot, monkeypatch, caplog, nwGUI, projPath, mockRnd nwJohn = SHARED.project.tree[hJohn] assert isinstance(nwJohn, NWItem) nwJohn.setActive(False) - projTree.setTreeItemValues(hJohn) + projTree.setTreeItemValues(nwJohn) projTree._alertTreeChange(hJohn, flush=False) assert charTab.topLevelItemCount() == 1 diff --git a/tests/test_gui/test_gui_projtree.py b/tests/test_gui/test_gui_projtree.py index 29a945cf..5ddb8490 100644 --- a/tests/test_gui/test_gui_projtree.py +++ b/tests/test_gui/test_gui_projtree.py @@ -186,6 +186,10 @@ def testGuiProjTree_NewItems(qtbot, caplog, monkeypatch, nwGUI, projPath, mockRn # Adding an invalid item directly to the tree should also fail assert projTree._addTreeItem(None) is None + # Setting values for a non-existing tree item should be handled + projTree.setTreeItemValues(None) + projTree.setTreeItemValues(C.hInvalid) # The function used to take handles + # Clean up # qtbot.stop() nwGUI.closeProject()