diff --git a/novelwriter/core/index.py b/novelwriter/core/index.py index 6005b280..fb7c5f28 100644 --- a/novelwriter/core/index.py +++ b/novelwriter/core/index.py @@ -528,13 +528,13 @@ class NWIndex: for sTitle, hItem in self._itemIndex.iterItemHeaders(tHandle) ] - def getHandleHeaders(self, tHandle): - """Get all headers for a specific handle. + def getHandleHeaderCount(self, tHandle): + """Get the number of headers in an item. """ - return [ - (sTitle, hItem.level, hItem.title) - for sTitle, hItem in self._itemIndex.iterItemHeaders(tHandle) - ] + tItem = self._itemIndex[tHandle] + if isinstance(tItem, IndexItem): + return len(tItem) + return 0 def getTableOfContents(self, rootHandle, maxDepth, skipExcl=True): """Generate a table of contents up to a maximum depth. @@ -645,6 +645,16 @@ class TagsIndex: self._tags = {} return + def __contains__(self, tagKey): + return tagKey in self._tags + + def __delitem__(self, tagKey): + self._tags.pop(tagKey, None) + return + + def __getitem__(self, tagKey): + return self._tags.get(tagKey, None) + ## # Methods ## @@ -655,22 +665,6 @@ class TagsIndex: self._tags = {} return - def __contains__(self, tagKey): - """Check if a tag exists in the index, - """ - return tagKey in self._tags - - def __delitem__(self, tagKey): - """Delete an entry in the index. - """ - self._tags.pop(tagKey, None) - return - - def __getitem__(self, tagKey): - """Return a tag, or return None if it isn't found. - """ - return self._tags.get(tagKey, None) - def add(self, tagKey, tHandle, sTitle, itemClass): """Add a key to the index and set all values. """ @@ -753,6 +747,16 @@ class ItemIndex: self._items = {} return + def __contains__(self, tHandle): + return tHandle in self._items + + def __delitem__(self, tHandle): + self._items.pop(tHandle, None) + return + + def __getitem__(self, tHandle): + return self._items.get(tHandle, None) + ## # Methods ## @@ -763,22 +767,6 @@ class ItemIndex: self._items = {} return - def __contains__(self, tHandle): - """Check if an item exists in the index, - """ - return tHandle in self._items - - def __delitem__(self, tHandle): - """Delete an entry in the index. - """ - self._items.pop(tHandle, None) - return - - def __getitem__(self, tHandle): - """Return an item, or return None if it isn't found. - """ - return self._items.get(tHandle, None) - def add(self, tHandle, tItem): """Add a new item to the index. This will overwrite the item if it already exists. @@ -933,6 +921,15 @@ class IndexItem: def __repr__(self): return f"" + def __len__(self): + return len(self._headings) + + def __getitem__(self, sTitle): + return self._headings.get(sTitle, None) + + def __contains__(self, sTitle): + return sTitle in self._headings + ## # Properties ## @@ -987,12 +984,6 @@ class IndexItem: # Data Methods ## - def __getitem__(self, sTitle): - return self._headings.get(sTitle, None) - - def __contains__(self, sTitle): - return sTitle in self._headings - def items(self): return self._headings.items() diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py index caa1b925..3b40b05a 100644 --- a/novelwriter/gui/doceditor.py +++ b/novelwriter/gui/doceditor.py @@ -90,7 +90,6 @@ class GuiDocEditor(QTextEdit): self._docChanged = False # Flag for changed status of document self._docHandle = None # The handle of the open file - self._docHeaders = [] # Record of headers in the file self._spellCheck = False # Flag for spell checking enabled self._nonWord = "\"'" # Characters to not include in spell checking @@ -417,8 +416,8 @@ class GuiDocEditor(QTextEdit): self._queuePos = self._nwItem.cursorPos else: self.setCursorPosition(self._nwItem.cursorPos) - else: - self.setCursorLine(tLine) + elif isinstance(tLine, int): + self.setCursorLine(tLine - 1) if self.mainConf.scrollPastEnd > 0: fSize = QFontMetrics(self.font()).lineSpacing() @@ -427,7 +426,6 @@ class GuiDocEditor(QTextEdit): self.document().rootFrame().setFrameFormat(docFrame) self.docFooter.updateLineCount() - self._docHeaders = self.theProject.index.getHandleHeaders(self._docHandle) qApp.processEvents() self.document().clearUndoRedoStacks() @@ -533,14 +531,16 @@ class GuiDocEditor(QTextEdit): self.setDocumentChanged(False) oldHeader = self._nwItem.mainHeading + oldCount = self.theProject.index.getHandleHeaderCount(tHandle) self.theProject.index.scanText(tHandle, docText) newHeader = self._nwItem.mainHeading + newCount = self.theProject.index.getHandleHeaderCount(tHandle) if self._nwItem.itemClass == nwItemClass.NOVEL: - if self._updateHeaders(): - self.novelStructureChanged.emit() - else: + if oldCount == newCount: self.novelItemMetaChanged.emit(tHandle) + else: + self.novelStructureChanged.emit() # ToDo: This should be a signal if oldHeader != newHeader: @@ -2067,21 +2067,6 @@ class GuiDocEditor(QTextEdit): return False return True - def _updateHeaders(self): - """Update the headers record and return True if anything - changed, if a check flag was provided. - """ - if self._docHandle is None: - return False - - newHeaders = self.theProject.index.getHandleHeaders(self._docHandle) - newLev = [x[1] for x in newHeaders] - oldLev = [x[1] for x in self._docHeaders] - - self._docHeaders = newHeaders - - return newLev != oldLev - def _checkDocSize(self, theSize): """Check if document size crosses the big document limit set in config. If so, we will set the big document flag to True. diff --git a/novelwriter/gui/noveltree.py b/novelwriter/gui/noveltree.py index 654dec2c..2a022bb0 100644 --- a/novelwriter/gui/noveltree.py +++ b/novelwriter/gui/noveltree.py @@ -477,7 +477,7 @@ class GuiNovelTree(QTreeWidget): return def refreshTree(self, rootHandle=None, overRide=False): - """Called whenever the Novel tab is activated. + """Refresh the tree if it has been changed. """ logger.debug("Requesting refresh of the novel tree") if rootHandle is None: @@ -509,31 +509,16 @@ class GuiNovelTree(QTreeWidget): if idxData is None: return + logger.debug("Refreshing meta data for item '%s'", tHandle) for sTitle, tHeading in idxData.items(): sKey = f"{tHandle}:{sTitle}" trItem = self._treeMap.get(sKey, None) if trItem is None: logger.debug("Heading '%s' not in novel tree", sKey) - continue + self.refreshTree() + return - iLevel = nwHeaders.H_LEVEL.get(tHeading.level, 0) - if iLevel == 0: - continue - - hDec = self.mainTheme.getHeaderDecoration(iLevel) - - trItem.setData(self.C_TITLE, Qt.DecorationRole, hDec) - trItem.setText(self.C_TITLE, tHeading.title) - trItem.setFont(self.C_TITLE, self._hFonts[iLevel]) - trItem.setText(self.C_WORDS, f"{tHeading.wordCount:n}") - trItem.setTextAlignment(self.C_WORDS, Qt.AlignRight) - trItem.setData(self.C_MORE, Qt.DecorationRole, self._pMore) - - # Custom column - lastText, toolTip = self._getLastColumnText(tHandle, sTitle) - trItem.setText(self.C_EXTRA, lastText) - if lastText: - trItem.setToolTip(self.C_EXTRA, toolTip) + self._updateTreeItemValues(trItem, tHeading, tHandle, sTitle) return @@ -668,30 +653,16 @@ class GuiNovelTree(QTreeWidget): novStruct = self.theProject.index.novelStructure(rootHandle=rootHandle, skipExcl=True) for tKey, tHandle, sTitle, novIdx in novStruct: - - iLevel = nwHeaders.H_LEVEL.get(novIdx.level, 0) - if iLevel == 0: + if novIdx.level == "H0": continue - hDec = self.mainTheme.getHeaderDecoration(iLevel) - newItem = QTreeWidgetItem() - newItem.setData(self.C_TITLE, Qt.DecorationRole, hDec) - newItem.setText(self.C_TITLE, novIdx.title) newItem.setData(self.C_TITLE, self.D_HANDLE, tHandle) newItem.setData(self.C_TITLE, self.D_TITLE, sTitle) newItem.setData(self.C_TITLE, self.D_KEY, tKey) - newItem.setFont(self.C_TITLE, self._hFonts[iLevel]) - newItem.setText(self.C_WORDS, f"{novIdx.wordCount:n}") newItem.setTextAlignment(self.C_WORDS, Qt.AlignRight) - newItem.setData(self.C_MORE, Qt.DecorationRole, self._pMore) - - # Custom column - lastText, toolTip = self._getLastColumnText(tHandle, sTitle) - newItem.setText(self.C_EXTRA, lastText) - if lastText: - newItem.setToolTip(self.C_EXTRA, toolTip) + self._updateTreeItemValues(newItem, novIdx, tHandle, sTitle) self._treeMap[tKey] = newItem self.addTopLevelItem(newItem) @@ -702,6 +673,26 @@ class GuiNovelTree(QTreeWidget): return + def _updateTreeItemValues(self, trItem, idxItem, tHandle, sTitle): + """Set the tree item values from the index entry. + """ + iLevel = nwHeaders.H_LEVEL.get(idxItem.level, 0) + hDec = self.mainTheme.getHeaderDecoration(iLevel) + + trItem.setData(self.C_TITLE, Qt.DecorationRole, hDec) + trItem.setText(self.C_TITLE, idxItem.title) + trItem.setFont(self.C_TITLE, self._hFonts[iLevel]) + trItem.setText(self.C_WORDS, f"{idxItem.wordCount:n}") + trItem.setData(self.C_MORE, Qt.DecorationRole, self._pMore) + + # Custom column + lastText, toolTip = self._getLastColumnText(tHandle, sTitle) + trItem.setText(self.C_EXTRA, lastText) + if lastText: + trItem.setToolTip(self.C_EXTRA, toolTip) + + return + def _getLastColumnText(self, tHandle, sTitle): """Generate the text for the last column based on user settings. """ diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index b32c3830..92b7c9cf 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -818,17 +818,8 @@ class GuiMain(QMainWindow): """Rebuild the project tree. """ self.projView.populateTree() - # self.novelView.refreshTree() return - def requestNovelTreeRefresh(self): - """Update the novel tree, but only if it is visible. - """ - if self.projStack.currentIndex() == self.idxNovelView and self.hasProject: - self.novelView.refreshTree() - return True - return False - def rebuildIndex(self, beQuiet=False): """Rebuild the entire index. """ @@ -843,6 +834,7 @@ class GuiMain(QMainWindow): self.projView.saveProjectTasks() self.theProject.index.rebuildIndex() self.projView.populateTree() + self.novelView.refreshTree() tEnd = time() self.setStatus( @@ -1578,7 +1570,6 @@ class GuiMain(QMainWindow): self.docEditor.closeSearch() elif self.isFocusMode: self.toggleFocusMode() - return @pyqtSlot(int) @@ -1595,17 +1586,11 @@ class GuiMain(QMainWindow): """Activated when the project view tab is changed. """ sHandle = None - if stIndex == self.idxProjView: sHandle = self.projView.getSelectedHandle() - elif stIndex == self.idxNovelView: - if self.hasProject: - self.novelView.refreshTree() - sHandle, _ = self.novelView.getSelectedHandle() - + sHandle, _ = self.novelView.getSelectedHandle() self.itemDetails.updateViewBox(sHandle) - return # END Class GuiMain diff --git a/tests/test_core/test_core_index.py b/tests/test_core/test_core_index.py index 644c77cd..7ea81ed2 100644 --- a/tests/test_core/test_core_index.py +++ b/tests/test_core/test_core_index.py @@ -580,12 +580,13 @@ def testCoreIndex_ExtractData(mockGUI, fncPath, mockRnd): assert wC == 12 # Words in text and title only assert pC == 2 # Paragraphs in text only - # getItemData - # =========== + # getItemData + getHandleHeaderCount + # ================================== theItem = theIndex.getItemData(nHandle) assert isinstance(theItem, IndexItem) assert theItem.headings() == ["T0001"] + assert theIndex.getHandleHeaderCount(nHandle) == 1 # getReferences # ============= @@ -786,17 +787,6 @@ def testCoreIndex_ExtractData(mockGUI, fncPath, mockRnd): assert theIndex.saveIndex() is True assert theProject.saveProject() is True - - # Header Record - bHandle = "0000000000000" - assert theIndex.getHandleHeaders(bHandle) == [] - assert theIndex.getHandleHeaders(hHandle) == [("T0001", "H2", "Chapter One")] - assert theIndex.getHandleHeaders(sHandle) == [("T0001", "H3", "Scene One")] - assert theIndex.getHandleHeaders(tHandle) == [("T0001", "H3", "Scene Two")] - assert theIndex.getHandleHeaders(nHandle) == [ - ("T0001", "H1", "Hello World!"), ("T0002", "H1", "Hello World!") - ] - assert theProject.closeProject() is True # END Test testCoreIndex_ExtractData diff --git a/tests/test_gui/test_gui_doceditor.py b/tests/test_gui/test_gui_doceditor.py index 7f47fd60..017c955c 100644 --- a/tests/test_gui/test_gui_doceditor.py +++ b/tests/test_gui/test_gui_doceditor.py @@ -118,10 +118,10 @@ def testGuiEditor_LoadText(qtbot, monkeypatch, caplog, nwGUI, projPath, ipsumTex assert nwGUI.docEditor.loadText(C.hSceneDoc) is True assert nwGUI.docEditor._bigDoc is True - # Regular open, with line number + # Regular open, with line number (1 indexed) assert nwGUI.docEditor.loadText(C.hSceneDoc, tLine=4) is True cursPos = nwGUI.docEditor.getCursorPosition() - assert nwGUI.docEditor.document().findBlock(cursPos).blockNumber() == 4 + assert nwGUI.docEditor.document().findBlock(cursPos).blockNumber() == 3 # Load empty document nwGUI.docEditor.replaceText("") diff --git a/tests/test_gui/test_gui_guimain.py b/tests/test_gui/test_gui_guimain.py index 9d4f2d02..43ac328f 100644 --- a/tests/test_gui/test_gui_guimain.py +++ b/tests/test_gui/test_gui_guimain.py @@ -57,7 +57,6 @@ def testGuiMain_ProjectBlocker(nwGUI): assert nwGUI.importDocument() is False assert nwGUI.openSelectedItem() is False assert nwGUI.editItemLabel() is False - assert nwGUI.requestNovelTreeRefresh() is False assert nwGUI.rebuildIndex() is False assert nwGUI.showProjectSettingsDialog() is False assert nwGUI.showProjectDetailsDialog() is False