From ef652355bf8c902fa4de18b8e48c937c79613645 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 22 Mar 2025 21:10:01 +0100 Subject: [PATCH] Refresh model when document heading count changes --- novelwriter/core/index.py | 77 ++++++++++++++++-------------- novelwriter/gui/doceditor.py | 11 ----- novelwriter/gui/noveltree.py | 29 +++++------ novelwriter/gui/theme.py | 2 +- novelwriter/guimain.py | 12 ++--- tests/test_core/test_core_index.py | 9 ---- 6 files changed, 59 insertions(+), 81 deletions(-) diff --git a/novelwriter/core/index.py b/novelwriter/core/index.py index 398a49b2..550fe3f6 100644 --- a/novelwriter/core/index.py +++ b/novelwriter/core/index.py @@ -120,35 +120,6 @@ class Index: self._generateNovelModel(tHandle) return self._novelModels.get(tHandle) - def refreshNovelModel(self, tHandle: str) -> None: - """Refresh a novel model.""" - if model := self.getNovelModel(tHandle): - logger.debug("Refreshing novel model '%s'", tHandle) - model.beginResetModel() - model.clear() - self._appendSubTreeToModel(tHandle, model) - model.endResetModel() - return - - def _generateNovelModel(self, tHandle: str) -> None: - """Generate a novel model for a specific handle.""" - if (item := SHARED.project.tree[tHandle]) and item.isRootType() and item.isNovelLike(): - model = NovelModel() - self._appendSubTreeToModel(tHandle, model) - self._novelModels[tHandle] = model - return - - def _appendSubTreeToModel(self, tHandle: str, model: NovelModel) -> None: - """Append all active novel documents to a novel model.""" - for handle in SHARED.project.tree.subTree(tHandle): - if ( - (node := self._itemIndex[handle]) - and node.item.isDocumentLayout() - and node.item.isActive - ): - model.append(node) - return - ## # Public Methods ## @@ -207,6 +178,16 @@ class Index: return self._rootChange.get(rootHandle, self._indexChange) > float(checkTime) return False + def refreshNovelModel(self, tHandle: str) -> None: + """Refresh a novel model.""" + if model := self.getNovelModel(tHandle): + logger.info("Refreshing novel model '%s'", tHandle) + model.beginResetModel() + model.clear() + self._appendSubTreeToModel(tHandle, model) + model.endResetModel() + return + ## # Load and Save Index to/from File ## @@ -304,6 +285,7 @@ class Index: # Keep a record of existing tags, and create a new item entry itemTags = dict.fromkeys(self._itemIndex.allItemTags(tHandle), False) + itemHeadings = self._itemIndex.headingCount(tHandle) self._itemIndex.add(tHandle, tItem) # Run word counter for the whole text @@ -327,6 +309,11 @@ class Index: else: self._scanActive(tHandle, tItem, text, itemTags) + if itemHeadings == self._itemIndex.headingCount(tHandle): + pass + else: + tItem.notifyNovelStructureChange() + # Update timestamps for index changes nowTime = time() self._indexChange = nowTime @@ -467,6 +454,25 @@ class Index: return + def _generateNovelModel(self, tHandle: str) -> None: + """Generate a novel model for a specific handle.""" + if (item := SHARED.project.tree[tHandle]) and item.isRootType() and item.isNovelLike(): + model = NovelModel() + self._appendSubTreeToModel(tHandle, model) + self._novelModels[tHandle] = model + return + + def _appendSubTreeToModel(self, tHandle: str, model: NovelModel) -> None: + """Append all active novel documents to a novel model.""" + for handle in SHARED.project.tree.subTree(tHandle): + if ( + (node := self._itemIndex[handle]) + and node.item.isDocumentLayout() + and node.item.isActive + ): + model.append(node) + return + ## # Check @ Lines ## @@ -607,13 +613,6 @@ class Index: hCount[iLevel] += 1 return hCount - def getHandleHeaderCount(self, tHandle: str) -> int: - """Get the number of headers in an item.""" - tItem = self._itemIndex[tHandle] - if isinstance(tItem, IndexNode): - return len(tItem) - return 0 - def getTableOfContents( self, rHandle: str | None, maxDepth: int, activeOnly: bool = True ) -> list[tuple[str, int, str, int]]: @@ -916,6 +915,12 @@ class ItemIndex: self._items[tHandle] = IndexNode(tHandle, nwItem) return + def headingCount(self, tHandle: str) -> int: + """Return the number of headings in this item.""" + if tHandle in self._items: + return len(self._items[tHandle]) + return 0 + def allItemTags(self, tHandle: str) -> list[str]: """Get all tags set for headings of an item.""" if tHandle in self._items: diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py index 58230c7d..69e24763 100644 --- a/novelwriter/gui/doceditor.py +++ b/novelwriter/gui/doceditor.py @@ -115,7 +115,6 @@ class GuiDocEditor(QPlainTextEdit): editedStatusChanged = pyqtSignal(bool) itemHandleChanged = pyqtSignal(str) loadDocumentTagRequest = pyqtSignal(str, Enum) - novelItemMetaChanged = pyqtSignal(str) novelStructureChanged = pyqtSignal() openDocumentRequest = pyqtSignal(str, Enum, str, bool) requestNewNoteCreation = pyqtSignal(str, nwItemClass) @@ -498,18 +497,8 @@ class GuiDocEditor(QPlainTextEdit): self.setDocumentChanged(False) self.docTextChanged.emit(self._docHandle, self._lastEdit) - - oldCount = SHARED.project.index.getHandleHeaderCount(tHandle) SHARED.project.index.scanText(tHandle, text) - newCount = SHARED.project.index.getHandleHeaderCount(tHandle) - if self._nwItem.itemClass == nwItemClass.NOVEL: - if oldCount == newCount: - self.novelItemMetaChanged.emit(tHandle) - else: - self.novelStructureChanged.emit() - - # Update the status bar self.updateStatusMessage.emit(self.tr("Saved Document: {0}").format(self._nwItem.itemName)) return True diff --git a/novelwriter/gui/noveltree.py b/novelwriter/gui/noveltree.py index 3e02d750..5710cb35 100644 --- a/novelwriter/gui/noveltree.py +++ b/novelwriter/gui/noveltree.py @@ -96,7 +96,6 @@ class GuiNovelView(QWidget): def updateTheme(self) -> None: """Update theme elements.""" self.novelBar.updateTheme() - self.refreshTree() return def initSettings(self) -> None: @@ -174,12 +173,6 @@ class GuiNovelView(QWidget): self.novelTree.setActiveHandle(tHandle) return - @pyqtSlot() - def refreshTree(self) -> None: - """Refresh the current tree.""" - # self.novelTree.refreshTree(rootHandle=SHARED.project.data.getLastHandle("novelTree")) - return - @pyqtSlot(str, Enum) def updateRootItem(self, tHandle: str, change: nwChange) -> None: """If any root item changes, rebuild the novel root menu.""" @@ -296,6 +289,8 @@ class GuiNovelToolBar(QWidget): self.novelValue.refreshNovelList() self.tbNovel.setVisible(self.novelValue.count() > 1) + self._forceRefreshNovelTree() + return def clearContent(self) -> None: @@ -328,7 +323,7 @@ class GuiNovelToolBar(QWidget): refresh when content structure changes. """ self._active = state - if self._active: + if self._active and self._refresh.get(self.novelValue.handle, False): self._refreshNovelTree(self.novelValue.handle) return @@ -339,21 +334,19 @@ class GuiNovelToolBar(QWidget): @pyqtSlot() def _forceRefreshNovelTree(self) -> None: """Rebuild the current tree.""" - self._refresh[self.novelValue.handle] = True - self._refreshNovelTree(self.novelValue.handle) + if tHandle := self.novelValue.handle: + SHARED.project.index.refreshNovelModel(tHandle) + self._refresh[tHandle] = False return @pyqtSlot(str) def _refreshNovelTree(self, tHandle: str) -> None: """Refresh or schedule refresh of a novel tree.""" - if tHandle: - if self._active: - if self._refresh.get(tHandle, False): - SHARED.project.index.refreshNovelModel(tHandle) - self._refresh[tHandle] = False - else: - self._refresh[tHandle] = True - print(self._refresh) + if self._active: + SHARED.project.index.refreshNovelModel(tHandle) + self._refresh[tHandle] = False + else: + self._refresh[tHandle] = True return @pyqtSlot() diff --git a/novelwriter/gui/theme.py b/novelwriter/gui/theme.py index d7578389..729db7ee 100644 --- a/novelwriter/gui/theme.py +++ b/novelwriter/gui/theme.py @@ -694,7 +694,7 @@ class GuiIcons: else: icon = self._loadIcon(name, color, w, h) self._qIcons[key] = icon - logger.info("Icon: %s", key) + logger.debug("Icon: %s", key) return icon def getToggleIcon(self, name: str, size: tuple[int, int], color: str | None = None) -> QIcon: diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index 98b2c7ef..3f5ca87d 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -247,8 +247,6 @@ class GuiMain(QMainWindow): self.docEditor.itemHandleChanged.connect(self.novelView.setActiveHandle) self.docEditor.itemHandleChanged.connect(self.projView.setActiveHandle) self.docEditor.loadDocumentTagRequest.connect(self._followTag) - self.docEditor.novelItemMetaChanged.connect(self.novelView.updateNovelItemMeta) - self.docEditor.novelStructureChanged.connect(self.novelView.refreshTree) self.docEditor.openDocumentRequest.connect(self._openDocument) self.docEditor.requestNewNoteCreation.connect(SHARED.createNewNote) self.docEditor.requestNextDocument.connect(self.openNextDocument) @@ -735,7 +733,6 @@ class GuiMain(QMainWindow): SHARED.project.index.rebuild() SHARED.project.tree.refreshAllItems() - self.novelView.refreshTree() tEnd = time() self.mainStatus.setStatusMessage( @@ -1163,20 +1160,23 @@ class GuiMain(QMainWindow): elif view == nwView.PROJECT: self.mainStack.setCurrentWidget(self.splitMain) self.projStack.setCurrentWidget(self.projView) - self.novelView.setActive(False) elif view == nwView.NOVEL: self.mainStack.setCurrentWidget(self.splitMain) self.projStack.setCurrentWidget(self.novelView) - self.novelView.setActive(True) elif view == nwView.SEARCH: self.mainStack.setCurrentWidget(self.splitMain) self.projStack.setCurrentWidget(self.projSearch) self.projSearch.beginSearch( self.docEditor.getSelectedText() if self.docEditor.anyFocus() else "" ) - self.novelView.setActive(False) elif view == nwView.OUTLINE: self.mainStack.setCurrentWidget(self.outlineView) + + # Set active status + isMain = self.mainStack.currentWidget() == self.splitMain + isNovel = self.projStack.currentWidget() == self.novelView + self.novelView.setActive(isMain and isNovel) + return @pyqtSlot(nwDocAction) diff --git a/tests/test_core/test_core_index.py b/tests/test_core/test_core_index.py index 0b24a788..d22fba9f 100644 --- a/tests/test_core/test_core_index.py +++ b/tests/test_core/test_core_index.py @@ -708,15 +708,6 @@ def testCoreIndex_ExtractData(mockGUI, fncPath, mockRnd): assert wC == 12 # Words in text and title only assert pC == 2 # Paragraphs in text only - # getItemData + getHandleHeaderCount - # ================================== - - item = index.getItemData(nHandle) - assert isinstance(item, IndexNode) - assert item.headings() == ["T0001"] - assert index.getHandleHeaderCount(nHandle) == 1 - assert index.getHandleHeaderCount("foo") == 0 - # getReferences # =============