From e52d8d9ff7dbdd7982dbd2124dcbda6f1425a143 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Mon, 14 Nov 2022 16:59:04 +0100 Subject: [PATCH 1/5] Add novel tree method to refresh metadata for item --- novelwriter/core/index.py | 5 +++ novelwriter/gui/doceditor.py | 14 +++++---- novelwriter/gui/noveltree.py | 60 ++++++++++++++++++++++++++++-------- novelwriter/guimain.py | 2 ++ 4 files changed, 62 insertions(+), 19 deletions(-) diff --git a/novelwriter/core/index.py b/novelwriter/core/index.py index bdd49925..cf9cc002 100644 --- a/novelwriter/core/index.py +++ b/novelwriter/core/index.py @@ -501,6 +501,11 @@ class NWIndex: yield f"{tHandle}:{sTitle}", tHandle, sTitle, hItem return + def getItemData(self, tHandle): + """Get the index data for a given item. + """ + return self._itemIndex[tHandle] + def getNovelWordCount(self, skipExcl=True): """Count the number of words in the novel project. """ diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py index 1e2704f2..caa1b925 100644 --- a/novelwriter/gui/doceditor.py +++ b/novelwriter/gui/doceditor.py @@ -51,7 +51,7 @@ from PyQt5.QtWidgets import ( ) from novelwriter.core import NWSpellEnchant, countWords -from novelwriter.enum import nwAlert, nwDocAction, nwDocInsert, nwDocMode +from novelwriter.enum import nwAlert, nwDocAction, nwDocInsert, nwDocMode, nwItemClass from novelwriter.common import transferCase from novelwriter.constants import nwConst, nwFiles, nwKeyWords, nwUnicode from novelwriter.gui.dochighlight import GuiDocHighlighter @@ -71,6 +71,8 @@ class GuiDocEditor(QTextEdit): docEditedStatusChanged = pyqtSignal(bool) docCountsChanged = pyqtSignal(str, int, int, int) loadDocumentTagRequest = pyqtSignal(str, Enum) + novelStructureChanged = pyqtSignal() + novelItemMetaChanged = pyqtSignal(str) def __init__(self, mainGui): super().__init__(parent=mainGui) @@ -534,11 +536,11 @@ class GuiDocEditor(QTextEdit): self.theProject.index.scanText(tHandle, docText) newHeader = self._nwItem.mainHeading - # ToDo: This should be a signal - if self._updateHeaders(): - self.mainGui.requestNovelTreeRefresh() - else: - self.mainGui.novelView.updateWordCounts(tHandle) + if self._nwItem.itemClass == nwItemClass.NOVEL: + if self._updateHeaders(): + self.novelStructureChanged.emit() + else: + self.novelItemMetaChanged.emit(tHandle) # ToDo: This should be a signal if oldHeader != newHeader: diff --git a/novelwriter/gui/noveltree.py b/novelwriter/gui/noveltree.py index 02a7f567..e3134c8c 100644 --- a/novelwriter/gui/noveltree.py +++ b/novelwriter/gui/noveltree.py @@ -83,7 +83,6 @@ class GuiNovelView(QWidget): self.setLayout(self.outerBox) # Function Mappings - self.updateWordCounts = self.novelTree.updateWordCounts self.getSelectedHandle = self.novelTree.getSelectedHandle self.setActiveHandle = self.novelTree.setActiveHandle @@ -107,12 +106,6 @@ class GuiNovelView(QWidget): self.novelTree.initSettings() return - def refreshTree(self): - """Refresh the current tree. - """ - self.novelTree.refreshTree(rootHandle=self.theProject.data.getLastHandle("novelTree")) - return - def clearProject(self): """Clear project-related GUI content. """ @@ -164,6 +157,13 @@ class GuiNovelView(QWidget): # Public Slots ## + @pyqtSlot() + def refreshTree(self): + """Refresh the current tree. + """ + self.novelTree.refreshTree(rootHandle=self.theProject.data.getLastHandle("novelTree")) + return + @pyqtSlot(str) def updateRootItem(self, tHandle): """If any root item changes, rebuild the novel root menu. @@ -171,6 +171,14 @@ class GuiNovelView(QWidget): self.novelBar.buildNovelRootMenu() return + @pyqtSlot(str) + def updateNovelItemMeta(self, tHandle): + """The meta data of a novel item has changed, and the tree item + needs to be refreshed. + """ + self.novelTree.refreshHandle(tHandle) + return + # END Class GuiNovelView @@ -495,13 +503,39 @@ class GuiNovelTree(QTreeWidget): return - def updateWordCounts(self, tHandle): - """Update the word count for a given handle. + def refreshHandle(self, tHandle): + """Refresh the data for a given handle. """ - tHeaders = self.theProject.index.getHandleWordCounts(tHandle) - for titleKey, wCount in tHeaders: - if titleKey in self._treeMap: - self._treeMap[titleKey].setText(self.C_WORDS, f"{wCount:n}") + idxData = self.theProject.index.getItemData(tHandle) + if idxData is None: + return + + 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 + + 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) + return def getSelectedHandle(self): diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index 6a25e528..c4b569e6 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -224,6 +224,8 @@ class GuiMain(QMainWindow): self.docEditor.docCountsChanged.connect(self.itemDetails.updateCounts) self.docEditor.docCountsChanged.connect(self.projView.updateCounts) self.docEditor.loadDocumentTagRequest.connect(self._followTag) + self.docEditor.novelStructureChanged.connect(self.novelView.refreshTree) + self.docEditor.novelItemMetaChanged.connect(self.novelView.updateNovelItemMeta) self.docViewer.loadDocumentTagRequest.connect(self._followTag) From b0a0ca089f605ab2a5763b9c768f099ce79b7f52 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Mon, 14 Nov 2022 16:59:51 +0100 Subject: [PATCH 2/5] Rewrite index class to use sequential title keys rather than line numbers --- novelwriter/common.py | 4 +- novelwriter/constants.py | 1 - novelwriter/core/index.py | 135 +++++++++++++++++++++-------------- novelwriter/gui/noveltree.py | 18 ++--- novelwriter/gui/outline.py | 33 ++++----- novelwriter/gui/projtree.py | 10 +-- novelwriter/guimain.py | 28 +++++--- 7 files changed, 133 insertions(+), 96 deletions(-) diff --git a/novelwriter/common.py b/novelwriter/common.py index c712015a..5db4752d 100644 --- a/novelwriter/common.py +++ b/novelwriter/common.py @@ -154,11 +154,11 @@ def isHandle(value): def isTitleTag(value): - """Check if a string is a valid title string. + """Check if a string is a valid title tag string. """ if not isinstance(value, str): return False - if len(value) != 7: + if len(value) != 5: return False if not value.startswith("T"): return False diff --git a/novelwriter/constants.py b/novelwriter/constants.py index 87293a52..3d13d1f2 100644 --- a/novelwriter/constants.py +++ b/novelwriter/constants.py @@ -61,7 +61,6 @@ class nwHeaders: H_VALID = ("H0", "H1", "H2", "H3", "H4") H_LEVEL = {"H0": 0, "H1": 1, "H2": 2, "H3": 3, "H4": 4} - TT_NONE = "T000000" # END Class nwHeaders diff --git a/novelwriter/core/index.py b/novelwriter/core/index.py index cf9cc002..e2152ba3 100644 --- a/novelwriter/core/index.py +++ b/novelwriter/core/index.py @@ -41,6 +41,8 @@ from novelwriter.common import ( logger = logging.getLogger(__name__) +TT_NONE = "T0000" + class NWIndex: """This class holds the entire index for a given project. The index @@ -281,50 +283,58 @@ class NWIndex: def _scanActive(self, tHandle, theItem, theText, itemTags): """Scan an active document for meta data. """ - nTitle = 0 - findHeader = True - theLines = theText.splitlines() + nTitle = 0 # Line Number of the previous title + cTitle = TT_NONE # Tag of the current title + pTitle = TT_NONE # Tag of the previous title + firstHeader = True # First header has been seen + theLines = theText.splitlines() for nLine, aLine in enumerate(theLines, start=1): - if len(aLine.strip()) == 0: + if aLine.strip() == "": continue if aLine.startswith("#"): - if findHeader: - hDepth, _ = self._splitHeading(aLine) - if hDepth != "H0": - theItem.setMainHeading(hDepth) - findHeader = False + hDepth, hText = self._splitHeading(aLine) + if hDepth == "H0": + continue - isTitle = self._indexTitle(tHandle, aLine, nLine) - if isTitle and nLine > 0: + if firstHeader: + theItem.setMainHeading(hDepth) + firstHeader = False + + cTitle = self._itemIndex.addItemHeading(tHandle, nLine, hDepth, hText) + if cTitle != TT_NONE: if nTitle > 0: + # We have a new title, so we need to count the words of the previous one lastText = "\n".join(theLines[nTitle-1:nLine-1]) - self._indexWordCounts(tHandle, lastText, nTitle) + self._indexWordCounts(tHandle, lastText, pTitle) nTitle = nLine + pTitle = cTitle elif aLine.startswith("@"): - self._indexKeyword(tHandle, aLine, nTitle, theItem.itemClass, itemTags) + if cTitle != TT_NONE: + self._indexKeyword(tHandle, aLine, cTitle, theItem.itemClass, itemTags) elif aLine.startswith("%"): - if nTitle > 0: + if cTitle != TT_NONE: toCheck = aLine[1:].lstrip() synTag = toCheck[:9].lower() tLen = len(aLine) cLen = len(toCheck) cOff = tLen - cLen if synTag == "synopsis:": - self._indexSynopsis(tHandle, aLine[cOff+9:].strip(), nTitle) + sText = aLine[cOff+9:].strip() + self._itemIndex.setHeadingSynopsis(tHandle, cTitle, sText) # Count words for remaining text after last heading - if nTitle > 0: + if pTitle != TT_NONE: lastText = "\n".join(theLines[nTitle-1:]) - self._indexWordCounts(tHandle, lastText, nTitle) + self._indexWordCounts(tHandle, lastText, pTitle) # Also count words on a page with no titles - if nTitle == 0: - self._indexWordCounts(tHandle, theText, nTitle) + if cTitle == TT_NONE: + self._indexWordCounts(tHandle, theText, cTitle) # Prune no longer used tags for tTag, isActive in itemTags.items(): @@ -362,34 +372,14 @@ class NWIndex: return "H2", aLine[4:].strip() return "H0", "" - def _indexTitle(self, tHandle, aLine, nTitle): - """Save information about the title and its location in the - file to the index. - """ - hDepth, hText = self._splitHeading(aLine) - if hDepth == "H0": - return False - - sTitle = f"T{nTitle:06d}" - self._itemIndex.addItemHeading(tHandle, sTitle, hDepth, hText) - return True - - def _indexWordCounts(self, tHandle, theText, nTitle): + def _indexWordCounts(self, tHandle, theText, sTitle): """Count text stats and save the counts to the index. """ - sTitle = f"T{nTitle:06d}" cC, wC, pC = countWords(theText) - self._itemIndex.setHeadingCounts(tHandle, sTitle, cC, wC, pC) + self._itemIndex.setHeadingCounts(tHandle, sTitle, cC, wC, pC) return - def _indexSynopsis(self, tHandle, theText, nTitle): - """Save the synopsis to the index. - """ - sTitle = f"T{nTitle:06d}" - self._itemIndex.setHeadingSynopsis(tHandle, sTitle, theText) - return - - def _indexKeyword(self, tHandle, aLine, nTitle, itemClass, itemTags): + def _indexKeyword(self, tHandle, aLine, sTitle, itemClass, itemTags): """Validate and save the information about a reference to a tag in another file, or the setting of a tag in the file. A record of active tags is updated so that no longer used tags can be @@ -404,7 +394,6 @@ class NWIndex: logger.warning("Skipping invalid keyword '%s' in '%s'", theBits[0], tHandle) return - sTitle = f"T{nTitle:06d}" if theBits[0] == nwKeyWords.TAG_KEY: tagName = theBits[1] self._tagsIndex.add(tagName, tHandle, sTitle, itemClass) @@ -506,6 +495,14 @@ class NWIndex: """ return self._itemIndex[tHandle] + def getItemHeader(self, tHandle, sTitle): + """Get the header entry for a specific item and heading. + """ + tItem = self._itemIndex[tHandle] + if isinstance(tItem, IndexItem): + return tItem[sTitle] + return None + def getNovelWordCount(self, skipExcl=True): """Count the number of words in the novel project. """ @@ -649,6 +646,8 @@ class TagsIndex: control of the keys. """ + __slots__ = ("_tags") + def __init__(self): self._tags = {} return @@ -695,7 +694,7 @@ class TagsIndex: def tagHeading(self, tagKey): """Get the heading of a given tag. """ - return self._tags.get(tagKey, {}).get("heading", nwHeaders.TT_NONE) + return self._tags.get(tagKey, {}).get("heading", TT_NONE) def tagClass(self, tagKey): """Get the class of a given tag. @@ -754,6 +753,8 @@ class ItemIndex: IndexHeading object for each header of the text. """ + __slots__ = ("_project", "_items") + def __init__(self, project): self._project = project self._items = {} @@ -844,13 +845,15 @@ class ItemIndex: # Setters ## - def addItemHeading(self, tHandle, sTitle, hDepth, hText): - """Set the main heading level of an item. + def addItemHeading(self, tHandle, lineNo, hDepth, hText): + """Add a heading to an item. """ if tHandle in self._items: tItem = self._items[tHandle] - tItem.addHeading(IndexHeading(sTitle, hDepth, hText)) - return + sTitle = tItem.nextHeading() + tItem.addHeading(IndexHeading(sTitle, lineNo, hDepth, hText)) + return sTitle + return TT_NONE def setHeadingCounts(self, tHandle, sTitle, cC, wC, pC): """Set the character, word and paragraph counts of a heading @@ -921,14 +924,16 @@ class IndexItem: must be reset each time the item is re-indexed. """ + __slots__ = ("_handle", "_item", "_headings", "_headings", "_count") + def __init__(self, tHandle, tItem): self._handle = tHandle self._item = tItem self._headings = {} - self._index = 0 + self._count = 0 # Add a placeholder heading - self._headings[nwHeaders.TT_NONE] = IndexHeading(nwHeaders.TT_NONE) + self._headings[TT_NONE] = IndexHeading(TT_NONE) return @@ -951,8 +956,8 @@ class IndexItem: """Add a heading to the item. Also remove the placeholder entry if it exists. """ - if nwHeaders.TT_NONE in self._headings: - self._headings.pop(nwHeaders.TT_NONE) + if TT_NONE in self._headings: + self._headings.pop(TT_NONE) self._headings[tHeading.key] = tHeading return @@ -1011,6 +1016,12 @@ class IndexItem: tags.append(tag) return tags + def nextHeading(self): + """Return the next heading key to be used. + """ + self._count += 1 + return f"T{self._count:04d}" + ## # Pack/Unpack ## @@ -1056,8 +1067,14 @@ class IndexHeading: of all references made under each heading. """ - def __init__(self, key, level="H0", title=""): + __slots__ = ( + "_key", "_line", "_level", "_title", "_charCount", "_wordCount", + "_paraCount", "_synopsis", "_tag", "_refs", + ) + + def __init__(self, key, line=0, level="H0", title=""): self._key = key + self._line = line self._level = level self._title = title @@ -1082,6 +1099,10 @@ class IndexHeading: def key(self): return self._key + @property + def line(self): + return self._line + @property def level(self): return self._level @@ -1125,6 +1146,12 @@ class IndexHeading: self._level = level return + def setLine(self, line): + """Set the line number of a heading. + """ + self._line = max(0, checkInt(line, 0)) + return + def setCounts(self, charCount, wordCount, paraCount): """Set the character, word and paragraph count. Make sure the value is an integer and is not smaller than 0. @@ -1166,6 +1193,7 @@ class IndexHeading: return { "level": self._level, "title": self._title, + "line": self._line, "tag": self._tag, "cCount": self._charCount, "wCount": self._wordCount, @@ -1187,6 +1215,7 @@ class IndexHeading: self.setLevel(data.get("level", "H0")) self._title = str(data.get("title", "")) self._tag = str(data.get("tag", "")) + self.setLine(data.get("line", 0)) self.setCounts( data.get("cCount", 0), data.get("wCount", 0), diff --git a/novelwriter/gui/noveltree.py b/novelwriter/gui/noveltree.py index e3134c8c..bd796db7 100644 --- a/novelwriter/gui/noveltree.py +++ b/novelwriter/gui/noveltree.py @@ -40,7 +40,6 @@ from PyQt5.QtWidgets import ( ) from novelwriter.enum import nwDocMode, nwItemClass, nwOutline -from novelwriter.common import checkInt from novelwriter.constants import nwHeaders, nwKeyWords, nwLabels, trConst logger = logging.getLogger(__name__) @@ -60,7 +59,7 @@ class GuiNovelView(QWidget): # Signals for user interaction with the novel tree selectedItemChanged = pyqtSignal(str) - openDocumentRequest = pyqtSignal(str, Enum, int, str) + openDocumentRequest = pyqtSignal(str, Enum, str) def __init__(self, mainGui): super().__init__(parent=mainGui) @@ -543,14 +542,11 @@ class GuiNovelTree(QTreeWidget): selected, return the first. """ selItem = self.selectedItems() - tHandle = None - tLine = 0 if selItem: tHandle = selItem[0].data(self.C_TITLE, self.D_HANDLE) sTitle = selItem[0].data(self.C_TITLE, self.D_TITLE) - tLine = checkInt(sTitle[1:], 1) - 1 - - return tHandle, tLine + return tHandle, sTitle + return None, None def setLastColType(self, colType, doRefresh=True): """Change the content type of the last column and rebuild. @@ -609,11 +605,11 @@ class GuiNovelTree(QTreeWidget): if not isinstance(selItem, QTreeWidgetItem): return - tHandle, _ = self.getSelectedHandle() + tHandle, sTitle = self.getSelectedHandle() if tHandle is None: return - self.novelView.openDocumentRequest.emit(tHandle, nwDocMode.VIEW, -1, "") + self.novelView.openDocumentRequest.emit(tHandle, nwDocMode.VIEW, sTitle or "") return @@ -655,8 +651,8 @@ class GuiNovelTree(QTreeWidget): clicked, and send it to the main gui class for opening in the document editor. """ - tHandle, tLine = self.getSelectedHandle() - self.novelView.openDocumentRequest.emit(tHandle, nwDocMode.EDIT, tLine, "") + tHandle, sTitle = self.getSelectedHandle() + self.novelView.openDocumentRequest.emit(tHandle, nwDocMode.EDIT, sTitle or "") return ## diff --git a/novelwriter/gui/outline.py b/novelwriter/gui/outline.py index 3ccba658..dd0bb3d2 100644 --- a/novelwriter/gui/outline.py +++ b/novelwriter/gui/outline.py @@ -55,6 +55,7 @@ logger = logging.getLogger(__name__) class GuiOutlineView(QWidget): loadDocumentTagRequest = pyqtSignal(str, Enum) + openDocumentRequest = pyqtSignal(str, Enum, str) def __init__(self, mainGui): super().__init__(parent=mainGui) @@ -375,15 +376,16 @@ class GuiOutlineTree(QTreeWidget): hiddenStateChanged = pyqtSignal() activeItemChanged = pyqtSignal(str, str) - def __init__(self, theOutline): - super().__init__(parent=theOutline) + def __init__(self, outlineView): + super().__init__(parent=outlineView) logger.debug("Initialising GuiOutlineTree ...") - self.mainConf = novelwriter.CONFIG - self.mainGui = theOutline.mainGui - self.theProject = theOutline.mainGui.theProject - self.mainTheme = theOutline.mainGui.mainTheme + self.mainConf = novelwriter.CONFIG + self.outlineView = outlineView + self.mainGui = outlineView.mainGui + self.theProject = outlineView.mainGui.theProject + self.mainTheme = outlineView.mainGui.mainTheme self.setUniformRowHeights(True) self.setFrameStyle(QFrame.NoFrame) @@ -524,13 +526,11 @@ class GuiOutlineTree(QTreeWidget): selected, return the first. """ selItem = self.selectedItems() - tHandle = None - tLine = 0 if selItem: tHandle = selItem[0].data(self._colIdx[nwOutline.TITLE], self.D_HANDLE) - tLine = checkInt(selItem[0].text(self._colIdx[nwOutline.LINE]), 1) - 1 - - return tHandle, tLine + sTitle = selItem[0].data(self._colIdx[nwOutline.TITLE], self.D_TITLE) + return tHandle, sTitle + return None, None ## # Slots @@ -542,8 +542,10 @@ class GuiOutlineTree(QTreeWidget): clicked, and send it to the main gui class for opening in the document editor. """ - tHandle, tLine = self.getSelectedHandle() - self.mainGui.openDocument(tHandle, tLine=tLine - 1, doScroll=True) + tHandle, sTitle = self.getSelectedHandle() + if tHandle is None: + return + self.outlineView.openDocumentRequest.emit(tHandle, nwDocMode.EDIT, sTitle or "") return @pyqtSlot() @@ -554,9 +556,8 @@ class GuiOutlineTree(QTreeWidget): selItems = self.selectedItems() if selItems: tHandle = selItems[0].data(self._colIdx[nwOutline.TITLE], self.D_HANDLE) - sTitle = selItems[0].data(self._colIdx[nwOutline.TITLE], self.D_TITLE) + sTitle = selItems[0].data(self._colIdx[nwOutline.TITLE], self.D_TITLE) self.activeItemChanged.emit(tHandle, sTitle) - return @pyqtSlot(int, int, int) @@ -718,7 +719,7 @@ class GuiOutlineTree(QTreeWidget): trItem.setText(self._colIdx[nwOutline.LEVEL], novIdx.level) trItem.setIcon(self._colIdx[nwOutline.LABEL], self._dIcon[nwItem.mainHeading]) trItem.setText(self._colIdx[nwOutline.LABEL], nwItem.itemName) - trItem.setText(self._colIdx[nwOutline.LINE], sTitle[1:].lstrip("0")) + trItem.setText(self._colIdx[nwOutline.LINE], f"{novIdx.line:n}") trItem.setText(self._colIdx[nwOutline.SYNOP], novIdx.synopsis) trItem.setText(self._colIdx[nwOutline.CCOUNT], f"{novIdx.charCount:n}") trItem.setText(self._colIdx[nwOutline.WCOUNT], f"{novIdx.wordCount:n}") diff --git a/novelwriter/gui/projtree.py b/novelwriter/gui/projtree.py index 5ddeacba..92bdea27 100644 --- a/novelwriter/gui/projtree.py +++ b/novelwriter/gui/projtree.py @@ -60,7 +60,7 @@ class GuiProjectView(QWidget): # Signals for user interaction with the project tree selectedItemChanged = pyqtSignal(str) - openDocumentRequest = pyqtSignal(str, Enum, int, str) + openDocumentRequest = pyqtSignal(str, Enum, str) # Requests for the main GUI projectSettingsRequest = pyqtSignal(int) @@ -1144,7 +1144,7 @@ class GuiProjectTree(QTreeWidget): return if tItem.isFileType(): - self.projView.openDocumentRequest.emit(tHandle, nwDocMode.EDIT, -1, "") + self.projView.openDocumentRequest.emit(tHandle, nwDocMode.EDIT, "") else: trItem.setExpanded(not trItem.isExpanded()) @@ -1190,11 +1190,11 @@ class GuiProjectTree(QTreeWidget): if isFile: aOpenDoc = ctxMenu.addAction(self.tr("Open Document")) aOpenDoc.triggered.connect( - lambda: self.projView.openDocumentRequest.emit(tHandle, nwDocMode.EDIT, -1, "") + lambda: self.projView.openDocumentRequest.emit(tHandle, nwDocMode.EDIT, "") ) aViewDoc = ctxMenu.addAction(self.tr("View Document")) aViewDoc.triggered.connect( - lambda: self.projView.openDocumentRequest.emit(tHandle, nwDocMode.VIEW, -1, "") + lambda: self.projView.openDocumentRequest.emit(tHandle, nwDocMode.VIEW, "") ) ctxMenu.addSeparator() @@ -1324,7 +1324,7 @@ class GuiProjectTree(QTreeWidget): return if tItem.isFileType(): - self.projView.openDocumentRequest.emit(tHandle, nwDocMode.VIEW, -1, "") + self.projView.openDocumentRequest.emit(tHandle, nwDocMode.VIEW, "") return diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index c4b569e6..b32c3830 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -230,6 +230,7 @@ class GuiMain(QMainWindow): self.docViewer.loadDocumentTagRequest.connect(self._followTag) self.outlineView.loadDocumentTagRequest.connect(self._followTag) + self.outlineView.openDocumentRequest.connect(self._openDocument) # Finalise Initialisation # ======================= @@ -650,7 +651,7 @@ class GuiMain(QMainWindow): return True - def viewDocument(self, tHandle=None, tAnchor=None): + def viewDocument(self, tHandle=None, sTitle=None): """Load a document for viewing in the view panel. """ if not self.hasProject: @@ -689,7 +690,8 @@ class GuiMain(QMainWindow): self.splitDocs.setSizes(vPos) self.viewMeta.setVisible(self.mainConf.showRefPanel) - self.docViewer.navigateTo(tAnchor) + if sTitle: + self.docViewer.navigateTo(f"#{sTitle}") return True @@ -777,17 +779,23 @@ class GuiMain(QMainWindow): return False tHandle = None + sTitle = None tLine = None if self.projView.treeHasFocus(): tHandle = self.projView.getSelectedHandle() elif self.novelView.treeHasFocus(): - tHandle, tLine = self.novelView.getSelectedHandle() + tHandle, sTitle = self.novelView.getSelectedHandle() elif self.outlineView.treeHasFocus(): - tHandle, tLine = self.outlineView.getSelectedHandle() + tHandle, sTitle = self.outlineView.getSelectedHandle() else: logger.warning("No item selected") return False + if tHandle is not None and sTitle is not None: + hItem = self.theProject.index.getItemHeader(tHandle, sTitle) + if hItem is not None: + tLine = hItem.line + if tHandle is not None: self.openDocument(tHandle, tLine=tLine, changeFocus=False, doScroll=False) @@ -1473,18 +1481,22 @@ class GuiMain(QMainWindow): if tMode == nwDocMode.EDIT: self.openDocument(tHandle) elif tMode == nwDocMode.VIEW: - self.viewDocument(tHandle=tHandle, tAnchor=f"#{sTitle}") + self.viewDocument(tHandle=tHandle, sTitle=sTitle) return - @pyqtSlot(str, Enum, int, str) - def _openDocument(self, tHandle, tMode, tLine, tAnchor): + @pyqtSlot(str, Enum, str) + def _openDocument(self, tHandle, tMode, sTitle): """Handle an open document request from one of the tree views. """ if tHandle is not None: if tMode == nwDocMode.EDIT: + tLine = None + hItem = self.theProject.index.getItemHeader(tHandle, sTitle) + if hItem is not None: + tLine = hItem.line self.openDocument(tHandle, tLine=tLine, changeFocus=False) elif tMode == nwDocMode.VIEW: - self.viewDocument(tHandle=tHandle, tAnchor=(tAnchor or None)) + self.viewDocument(tHandle=tHandle, sTitle=sTitle) return @pyqtSlot(nwView) From 992ed4f36c9c925ab2be79cc9c3d1415924c749a Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Mon, 14 Nov 2022 17:56:31 +0100 Subject: [PATCH 3/5] Update tests --- novelwriter/core/index.py | 27 +- novelwriter/gui/noveltree.py | 2 +- novelwriter/gui/outline.py | 2 +- .../coreIndex_LoadSave_tagsIndex.json | 56 +-- tests/test_base/test_base_common.py | 10 +- tests/test_core/test_core_index.py | 419 +++++++++--------- tests/test_gui/test_gui_noveltree.py | 12 +- tests/test_gui/test_gui_outline.py | 8 +- 8 files changed, 275 insertions(+), 261 deletions(-) diff --git a/novelwriter/core/index.py b/novelwriter/core/index.py index e2152ba3..6005b280 100644 --- a/novelwriter/core/index.py +++ b/novelwriter/core/index.py @@ -480,16 +480,6 @@ class NWIndex: # Extract Data ## - def novelStructure(self, rootHandle=None, skipExcl=True): - """Iterate over all titles in the novel, in the correct order as - they appear in the tree view and in the respective document - files, but skipping all note files. - """ - novStruct = self._itemIndex.iterNovelStructure(rootHandle=rootHandle, skipExcl=skipExcl) - for tHandle, sTitle, hItem in novStruct: - yield f"{tHandle}:{sTitle}", tHandle, sTitle, hItem - return - def getItemData(self, tHandle): """Get the index data for a given item. """ @@ -503,6 +493,16 @@ class NWIndex: return tItem[sTitle] return None + def novelStructure(self, rootHandle=None, skipExcl=True): + """Iterate over all titles in the novel, in the correct order as + they appear in the tree view and in the respective document + files, but skipping all note files. + """ + novStruct = self._itemIndex.iterNovelStructure(rootHandle=rootHandle, skipExcl=skipExcl) + for tHandle, sTitle, hItem in novStruct: + yield f"{tHandle}:{sTitle}", tHandle, sTitle, hItem + return + def getNovelWordCount(self, skipExcl=True): """Count the number of words in the novel project. """ @@ -600,13 +600,6 @@ class NWIndex: return theRefs - def getNovelData(self, tHandle, sTitle): - """Return the novel data of a given handle and title. - """ - if tHandle in self._itemIndex: - return self._itemIndex[tHandle][sTitle] - return None - def getBackReferenceList(self, tHandle): """Build a list of files referring back to our file, specified by tHandle. diff --git a/novelwriter/gui/noveltree.py b/novelwriter/gui/noveltree.py index bd796db7..654dec2c 100644 --- a/novelwriter/gui/noveltree.py +++ b/novelwriter/gui/noveltree.py @@ -729,7 +729,7 @@ class GuiNovelTree(QTreeWidget): logger.debug("Generating meta data tooltip for '%s:%s'", tHandle, sTitle) pIndex = self.theProject.index - novIdx = pIndex.getNovelData(tHandle, sTitle) + novIdx = pIndex.getItemHeader(tHandle, sTitle) refTags = pIndex.getReferences(tHandle, sTitle) synopText = novIdx.synopsis diff --git a/novelwriter/gui/outline.py b/novelwriter/gui/outline.py index dd0bb3d2..bc2763d7 100644 --- a/novelwriter/gui/outline.py +++ b/novelwriter/gui/outline.py @@ -1049,7 +1049,7 @@ class GuiOutlineDetails(QScrollArea): """ pIndex = self.theProject.index nwItem = self.theProject.tree[tHandle] - novIdx = pIndex.getNovelData(tHandle, sTitle) + novIdx = pIndex.getItemHeader(tHandle, sTitle) theRefs = pIndex.getReferences(tHandle, sTitle) if nwItem is None or novIdx is None: return False diff --git a/tests/reference/coreIndex_LoadSave_tagsIndex.json b/tests/reference/coreIndex_LoadSave_tagsIndex.json index cba693dc..738e7916 100644 --- a/tests/reference/coreIndex_LoadSave_tagsIndex.json +++ b/tests/reference/coreIndex_LoadSave_tagsIndex.json @@ -1,109 +1,109 @@ { "tagsIndex": { - "Bod": {"handle": "4c4f28287af27", "heading": "T000001", "class": "CHARACTER"}, - "Main": {"handle": "2426c6f0ca922", "heading": "T000001", "class": "PLOT"}, - "Europe": {"handle": "04468803b92e1", "heading": "T000001", "class": "WORLD"} + "Bod": {"handle": "4c4f28287af27", "heading": "T0001", "class": "CHARACTER"}, + "Main": {"handle": "2426c6f0ca922", "heading": "T0001", "class": "PLOT"}, + "Europe": {"handle": "04468803b92e1", "heading": "T0001", "class": "WORLD"} }, "itemIndex": { "7a992350f3eb6": { "headings": { - "T000001": {"level": "H1", "title": "Lorem Ipsum", "tag": "", "cCount": 230, "wCount": 40, "pCount": 3, "synopsis": ""} + "T0001": {"level": "H1", "title": "Lorem Ipsum", "line": 1, "tag": "", "cCount": 230, "wCount": 40, "pCount": 3, "synopsis": ""} } }, "8c58a65414c23": { "headings": { - "T000000": {"level": "H0", "title": "", "tag": "", "cCount": 1058, "wCount": 176, "pCount": 2, "synopsis": ""} + "T0000": {"level": "H0", "title": "", "line": 0, "tag": "", "cCount": 1058, "wCount": 176, "pCount": 2, "synopsis": ""} } }, "88d59a277361b": { "headings": { - "T000001": {"level": "H2", "title": "Prologue", "tag": "", "cCount": 584, "wCount": 92, "pCount": 1, "synopsis": "Explanation from the lipsum.com website."} + "T0001": {"level": "H2", "title": "Prologue", "line": 1, "tag": "", "cCount": 584, "wCount": 92, "pCount": 1, "synopsis": "Explanation from the lipsum.com website."} } }, "db7e733775d4d": { "headings": { - "T000001": {"level": "H1", "title": "Act One", "tag": "", "cCount": 35, "wCount": 6, "pCount": 1, "synopsis": ""} + "T0001": {"level": "H1", "title": "Act One", "line": 1, "tag": "", "cCount": 35, "wCount": 6, "pCount": 1, "synopsis": ""} } }, "fb609cd8319dc": { "headings": { - "T000001": {"level": "H2", "title": "Chapter One", "tag": "", "cCount": 419, "wCount": 67, "pCount": 1, "synopsis": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque at aliquam quam."} + "T0001": {"level": "H2", "title": "Chapter One", "line": 1, "tag": "", "cCount": 419, "wCount": 67, "pCount": 1, "synopsis": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque at aliquam quam."} }, "references": { - "T000001": {"Bod": "@pov", "Main": "@plot", "Europe": "@location"} + "T0001": {"Bod": "@pov", "Main": "@plot", "Europe": "@location"} } }, "88243afbe5ed8": { "headings": { - "T000001": {"level": "H3", "title": "Scene One", "tag": "", "cCount": 1197, "wCount": 174, "pCount": 2, "synopsis": "Aenean ut placerat velit. Etiam laoreet ullamcorper risus, eget lobortis enim scelerisque non. Suspendisse id maximus nunc, et mollis sapien. Curabitur vel semper sapien, non pulvinar dolor. Etiam finibus nisi vel mi molestie consectetur."}, - "T000013": {"level": "H4", "title": "Scene One, Section Two", "tag": "", "cCount": 1561, "wCount": 230, "pCount": 2, "synopsis": ""} + "T0001": {"level": "H3", "title": "Scene One", "line": 1, "tag": "", "cCount": 1197, "wCount": 174, "pCount": 2, "synopsis": "Aenean ut placerat velit. Etiam laoreet ullamcorper risus, eget lobortis enim scelerisque non. Suspendisse id maximus nunc, et mollis sapien. Curabitur vel semper sapien, non pulvinar dolor. Etiam finibus nisi vel mi molestie consectetur."}, + "T0002": {"level": "H4", "title": "Scene One, Section Two", "line": 13, "tag": "", "cCount": 1561, "wCount": 230, "pCount": 2, "synopsis": ""} }, "references": { - "T000001": {"Bod": "@pov", "Main": "@plot", "Europe": "@location"} + "T0001": {"Bod": "@pov", "Main": "@plot", "Europe": "@location"} } }, "f96ec11c6a3da": { "headings": { - "T000001": {"level": "H3", "title": "Scene Two", "tag": "", "cCount": 2034, "wCount": 299, "pCount": 3, "synopsis": "Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Integer sapien nulla, dictum at lacus a, dignissim consectetur dolor. Nunc vel eleifend lacus, eu dapibus orci."}, - "T000015": {"level": "H4", "title": "Scene Two, Section Two", "tag": "", "cCount": 2009, "wCount": 301, "pCount": 3, "synopsis": ""} + "T0001": {"level": "H3", "title": "Scene Two", "line": 1, "tag": "", "cCount": 2034, "wCount": 299, "pCount": 3, "synopsis": "Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Integer sapien nulla, dictum at lacus a, dignissim consectetur dolor. Nunc vel eleifend lacus, eu dapibus orci."}, + "T0002": {"level": "H4", "title": "Scene Two, Section Two", "line": 15, "tag": "", "cCount": 2009, "wCount": 301, "pCount": 3, "synopsis": ""} }, "references": { - "T000001": {"Bod": "@pov", "Main": "@plot", "Europe": "@location"} + "T0001": {"Bod": "@pov", "Main": "@plot", "Europe": "@location"} } }, "846352075de7d": { "headings": { - "T000001": {"level": "H2", "title": "Why do we use it?", "tag": "", "cCount": 631, "wCount": 109, "pCount": 3, "synopsis": ""} + "T0001": {"level": "H2", "title": "Why do we use it?", "line": 1, "tag": "", "cCount": 631, "wCount": 109, "pCount": 3, "synopsis": ""} } }, "441420a886d82": { "headings": { - "T000001": {"level": "H2", "title": "Chapter Two", "tag": "", "cCount": 477, "wCount": 70, "pCount": 1, "synopsis": "Curabitur a elit posuere, varius ex et, convallis neque. Phasellus sagittis pharetra sem vitae dapibus. Curabitur varius lorem non pulvinar congue."} + "T0001": {"level": "H2", "title": "Chapter Two", "line": 1, "tag": "", "cCount": 477, "wCount": 70, "pCount": 1, "synopsis": "Curabitur a elit posuere, varius ex et, convallis neque. Phasellus sagittis pharetra sem vitae dapibus. Curabitur varius lorem non pulvinar congue."} }, "references": { - "T000001": {"Bod": "@pov", "Main": "@plot", "Europe": "@location"} + "T0001": {"Bod": "@pov", "Main": "@plot", "Europe": "@location"} } }, "eb103bc70c90c": { "headings": { - "T000001": {"level": "H3", "title": "Scene Three", "tag": "", "cCount": 3006, "wCount": 439, "pCount": 4, "synopsis": "Aenean ut libero ut lectus porttitor rhoncus vel et massa. Nam pretium, nibh et varius vehicula, urna metus blandit eros, euismod pharetra diam diam et libero. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos."} + "T0001": {"level": "H3", "title": "Scene Three", "line": 1, "tag": "", "cCount": 3006, "wCount": 439, "pCount": 4, "synopsis": "Aenean ut libero ut lectus porttitor rhoncus vel et massa. Nam pretium, nibh et varius vehicula, urna metus blandit eros, euismod pharetra diam diam et libero. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos."} }, "references": { - "T000001": {"Bod": "@pov", "Main": "@plot", "Europe": "@location"} + "T0001": {"Bod": "@pov", "Main": "@plot", "Europe": "@location"} } }, "f8c0562e50f1b": { "headings": { - "T000001": {"level": "H3", "title": "Scene Four", "tag": "", "cCount": 3839, "wCount": 563, "pCount": 6, "synopsis": "Nam tempor blandit magna laoreet aliquet. Vestibulum auctor posuere leo, ac gravida nisi rhoncus varius. Aenean posuere dolor vitae condimentum volutpat. Donec egestas volutpat risus, quis luctus justo."} + "T0001": {"level": "H3", "title": "Scene Four", "line": 1, "tag": "", "cCount": 3839, "wCount": 563, "pCount": 6, "synopsis": "Nam tempor blandit magna laoreet aliquet. Vestibulum auctor posuere leo, ac gravida nisi rhoncus varius. Aenean posuere dolor vitae condimentum volutpat. Donec egestas volutpat risus, quis luctus justo."} }, "references": { - "T000001": {"Bod": "@pov", "Main": "@plot", "Europe": "@location"} + "T0001": {"Bod": "@pov", "Main": "@plot", "Europe": "@location"} } }, "47666c91c7ccf": { "headings": { - "T000001": {"level": "H3", "title": "Scene Five", "tag": "", "cCount": 3644, "wCount": 543, "pCount": 5, "synopsis": "Praesent eget est porta, dictum ante in, egestas risus. Mauris risus mauris, consequat aliquam mauris et, feugiat iaculis ipsum. Aliquam arcu ipsum, fermentum ut arcu sed, lobortis euismod sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus."} + "T0001": {"level": "H3", "title": "Scene Five", "line": 1, "tag": "", "cCount": 3644, "wCount": 543, "pCount": 5, "synopsis": "Praesent eget est porta, dictum ante in, egestas risus. Mauris risus mauris, consequat aliquam mauris et, feugiat iaculis ipsum. Aliquam arcu ipsum, fermentum ut arcu sed, lobortis euismod sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus."} }, "references": { - "T000001": {"Bod": "@pov", "Main": "@plot", "Europe": "@location"} + "T0001": {"Bod": "@pov", "Main": "@plot", "Europe": "@location"} } }, "4c4f28287af27": { "headings": { - "T000001": {"level": "H1", "title": "Nobody Owens", "tag": "Bod", "cCount": 1864, "wCount": 284, "pCount": 3, "synopsis": ""} + "T0001": {"level": "H1", "title": "Nobody Owens", "line": 1, "tag": "Bod", "cCount": 1864, "wCount": 284, "pCount": 3, "synopsis": ""} }, "references": { - "T000001": {"Main": "@plot"} + "T0001": {"Main": "@plot"} } }, "2426c6f0ca922": { "headings": { - "T000001": {"level": "H1", "title": "Main Plot", "tag": "Main", "cCount": 1369, "wCount": 195, "pCount": 2, "synopsis": ""} + "T0001": {"level": "H1", "title": "Main Plot", "line": 1, "tag": "Main", "cCount": 1369, "wCount": 195, "pCount": 2, "synopsis": ""} } }, "04468803b92e1": { "headings": { - "T000001": {"level": "H1", "title": "Ancient Europe", "tag": "Europe", "cCount": 1770, "wCount": 259, "pCount": 3, "synopsis": ""} + "T0001": {"level": "H1", "title": "Ancient Europe", "line": 1, "tag": "Europe", "cCount": 1770, "wCount": 259, "pCount": 3, "synopsis": ""} } } } diff --git a/tests/test_base/test_base_common.py b/tests/test_base/test_base_common.py index e11895dc..a3d9d28e 100644 --- a/tests/test_base/test_base_common.py +++ b/tests/test_base/test_base_common.py @@ -208,12 +208,12 @@ def testBaseCommon_IsHandle(): def testBaseCommon_IsTitleTag(): """Test the isItemClass function. """ - assert isTitleTag("T123456") is True + assert isTitleTag("T1234") is True - assert isTitleTag("t123456") is False - assert isTitleTag("S123456") is False - assert isTitleTag("T12345A") is False - assert isTitleTag("T1234567") is False + assert isTitleTag("t1234") is False + assert isTitleTag("S1234") is False + assert isTitleTag("T123A") is False + assert isTitleTag("T12345") is False assert isTitleTag("None") is False assert isTitleTag(None) is False diff --git a/tests/test_core/test_core_index.py b/tests/test_core/test_core_index.py index 6d85dfb0..644c77cd 100644 --- a/tests/test_core/test_core_index.py +++ b/tests/test_core/test_core_index.py @@ -29,7 +29,7 @@ from tools import C, buildTestProject, cmpFiles, writeFile from novelwriter.enum import nwItemClass, nwItemLayout from novelwriter.constants import nwFiles -from novelwriter.core.index import NWIndex, countWords, TagsIndex +from novelwriter.core.index import IndexItem, NWIndex, countWords, TagsIndex from novelwriter.core.project import NWProject @@ -231,10 +231,10 @@ def testCoreIndex_CheckThese(mockGUI, fncPath, mockRnd): "@invalid: John\n" # Checks for issue #688 )) assert theIndex._tagsIndex.tagHandle("Jane") == cHandle - assert theIndex._tagsIndex.tagHeading("Jane") == "T000001" + assert theIndex._tagsIndex.tagHeading("Jane") == "T0001" assert theIndex._tagsIndex.tagClass("Jane") == "CHARACTER" - assert theIndex.getNovelData(nHandle, "T000001").title == "Hello World!" - assert theIndex.getReferences(nHandle, "T000001") == { + assert theIndex.getItemHeader(nHandle, "T0001").title == "Hello World!" + assert theIndex.getReferences(nHandle, "T0001") == { "@char": [], "@custom": [], "@entity": [], @@ -345,9 +345,9 @@ def testCoreIndex_ScanText(mockGUI, fncPath, mockRnd): "Well, not really.\n" )) assert theIndex._tagsIndex.tagHandle("Jane") == cHandle - assert theIndex._tagsIndex.tagHeading("Jane") == "T000001" + assert theIndex._tagsIndex.tagHeading("Jane") == "T0001" assert theIndex._tagsIndex.tagClass("Jane") == "CHARACTER" - assert theIndex.getNovelData(nHandle, "T000001").title == "Hello World!" + assert theIndex.getItemHeader(nHandle, "T0001").title == "Hello World!" # Title Indexing # ============== @@ -369,40 +369,45 @@ def testCoreIndex_ScanText(mockGUI, fncPath, mockRnd): "##### Title Five\n\n" # Not interpreted as a title, the hashes are counted as a word "Paragraph Five.\n\n" )) - assert theIndex._itemIndex[nHandle]["T000001"].references == {} - assert theIndex._itemIndex[nHandle]["T000007"].references == {} - assert theIndex._itemIndex[nHandle]["T000013"].references == {} - assert theIndex._itemIndex[nHandle]["T000019"].references == {} + assert theIndex._itemIndex[nHandle]["T0001"].references == {} + assert theIndex._itemIndex[nHandle]["T0002"].references == {} + assert theIndex._itemIndex[nHandle]["T0003"].references == {} + assert theIndex._itemIndex[nHandle]["T0004"].references == {} - assert theIndex._itemIndex[nHandle]["T000001"].level == "H1" - assert theIndex._itemIndex[nHandle]["T000007"].level == "H2" - assert theIndex._itemIndex[nHandle]["T000013"].level == "H3" - assert theIndex._itemIndex[nHandle]["T000019"].level == "H4" + assert theIndex._itemIndex[nHandle]["T0001"].level == "H1" + assert theIndex._itemIndex[nHandle]["T0002"].level == "H2" + assert theIndex._itemIndex[nHandle]["T0003"].level == "H3" + assert theIndex._itemIndex[nHandle]["T0004"].level == "H4" - assert theIndex._itemIndex[nHandle]["T000001"].title == "Title One" - assert theIndex._itemIndex[nHandle]["T000007"].title == "Title Two" - assert theIndex._itemIndex[nHandle]["T000013"].title == "Title Three" - assert theIndex._itemIndex[nHandle]["T000019"].title == "Title Four" + assert theIndex._itemIndex[nHandle]["T0001"].line == 1 + assert theIndex._itemIndex[nHandle]["T0002"].line == 7 + assert theIndex._itemIndex[nHandle]["T0003"].line == 13 + assert theIndex._itemIndex[nHandle]["T0004"].line == 19 - assert theIndex._itemIndex[nHandle]["T000001"].charCount == 23 - assert theIndex._itemIndex[nHandle]["T000007"].charCount == 23 - assert theIndex._itemIndex[nHandle]["T000013"].charCount == 27 - assert theIndex._itemIndex[nHandle]["T000019"].charCount == 56 + assert theIndex._itemIndex[nHandle]["T0001"].title == "Title One" + assert theIndex._itemIndex[nHandle]["T0002"].title == "Title Two" + assert theIndex._itemIndex[nHandle]["T0003"].title == "Title Three" + assert theIndex._itemIndex[nHandle]["T0004"].title == "Title Four" - assert theIndex._itemIndex[nHandle]["T000001"].wordCount == 4 - assert theIndex._itemIndex[nHandle]["T000007"].wordCount == 4 - assert theIndex._itemIndex[nHandle]["T000013"].wordCount == 4 - assert theIndex._itemIndex[nHandle]["T000019"].wordCount == 9 + assert theIndex._itemIndex[nHandle]["T0001"].charCount == 23 + assert theIndex._itemIndex[nHandle]["T0002"].charCount == 23 + assert theIndex._itemIndex[nHandle]["T0003"].charCount == 27 + assert theIndex._itemIndex[nHandle]["T0004"].charCount == 56 - assert theIndex._itemIndex[nHandle]["T000001"].paraCount == 1 - assert theIndex._itemIndex[nHandle]["T000007"].paraCount == 1 - assert theIndex._itemIndex[nHandle]["T000013"].paraCount == 1 - assert theIndex._itemIndex[nHandle]["T000019"].paraCount == 3 + assert theIndex._itemIndex[nHandle]["T0001"].wordCount == 4 + assert theIndex._itemIndex[nHandle]["T0002"].wordCount == 4 + assert theIndex._itemIndex[nHandle]["T0003"].wordCount == 4 + assert theIndex._itemIndex[nHandle]["T0004"].wordCount == 9 - assert theIndex._itemIndex[nHandle]["T000001"].synopsis == "Synopsis One." - assert theIndex._itemIndex[nHandle]["T000007"].synopsis == "Synopsis Two." - assert theIndex._itemIndex[nHandle]["T000013"].synopsis == "Synopsis Three." - assert theIndex._itemIndex[nHandle]["T000019"].synopsis == "Synopsis Four." + assert theIndex._itemIndex[nHandle]["T0001"].paraCount == 1 + assert theIndex._itemIndex[nHandle]["T0002"].paraCount == 1 + assert theIndex._itemIndex[nHandle]["T0003"].paraCount == 1 + assert theIndex._itemIndex[nHandle]["T0004"].paraCount == 3 + + assert theIndex._itemIndex[nHandle]["T0001"].synopsis == "Synopsis One." + assert theIndex._itemIndex[nHandle]["T0002"].synopsis == "Synopsis Two." + assert theIndex._itemIndex[nHandle]["T0003"].synopsis == "Synopsis Three." + assert theIndex._itemIndex[nHandle]["T0004"].synopsis == "Synopsis Four." # Note File assert theIndex.scanText(cHandle, ( @@ -411,13 +416,14 @@ def testCoreIndex_ScanText(mockGUI, fncPath, mockRnd): "% synopsis: Synopsis One.\n\n" "Paragraph One.\n\n" )) - assert theIndex._itemIndex[cHandle]["T000001"].references == {} - assert theIndex._itemIndex[cHandle]["T000001"].level == "H1" - assert theIndex._itemIndex[cHandle]["T000001"].title == "Title One" - assert theIndex._itemIndex[cHandle]["T000001"].charCount == 23 - assert theIndex._itemIndex[cHandle]["T000001"].wordCount == 4 - assert theIndex._itemIndex[cHandle]["T000001"].paraCount == 1 - assert theIndex._itemIndex[cHandle]["T000001"].synopsis == "Synopsis One." + assert theIndex._itemIndex[cHandle]["T0001"].references == {} + assert theIndex._itemIndex[cHandle]["T0001"].level == "H1" + assert theIndex._itemIndex[cHandle]["T0001"].line == 1 + assert theIndex._itemIndex[cHandle]["T0001"].title == "Title One" + assert theIndex._itemIndex[cHandle]["T0001"].charCount == 23 + assert theIndex._itemIndex[cHandle]["T0001"].wordCount == 4 + assert theIndex._itemIndex[cHandle]["T0001"].paraCount == 1 + assert theIndex._itemIndex[cHandle]["T0001"].synopsis == "Synopsis One." # Valid and Invalid References assert theIndex.scanText(sHandle, ( @@ -428,7 +434,7 @@ def testCoreIndex_ScanText(mockGUI, fncPath, mockRnd): "% synopsis: Synopsis One.\n\n" "Paragraph One.\n\n" )) - assert theIndex._itemIndex[sHandle]["T000001"].references == { + assert theIndex._itemIndex[sHandle]["T0001"].references == { "One": {"@pov"}, "Two": {"@char"} } @@ -439,25 +445,27 @@ def testCoreIndex_ScanText(mockGUI, fncPath, mockRnd): "#! My Project\n\n" ">> By Jane Doe <<\n\n" )) - assert theIndex._itemIndex[cHandle]["T000001"].references == {} - assert theIndex._itemIndex[tHandle]["T000001"].level == "H1" - assert theIndex._itemIndex[tHandle]["T000001"].title == "My Project" - assert theIndex._itemIndex[tHandle]["T000001"].charCount == 21 - assert theIndex._itemIndex[tHandle]["T000001"].wordCount == 5 - assert theIndex._itemIndex[tHandle]["T000001"].paraCount == 1 - assert theIndex._itemIndex[tHandle]["T000001"].synopsis == "" + assert theIndex._itemIndex[cHandle]["T0001"].references == {} + assert theIndex._itemIndex[tHandle]["T0001"].level == "H1" + assert theIndex._itemIndex[tHandle]["T0001"].line == 1 + assert theIndex._itemIndex[tHandle]["T0001"].title == "My Project" + assert theIndex._itemIndex[tHandle]["T0001"].charCount == 21 + assert theIndex._itemIndex[tHandle]["T0001"].wordCount == 5 + assert theIndex._itemIndex[tHandle]["T0001"].paraCount == 1 + assert theIndex._itemIndex[tHandle]["T0001"].synopsis == "" assert theIndex.scanText(tHandle, ( "##! Prologue\n\n" "In the beginning there was time ...\n\n" )) - assert theIndex._itemIndex[cHandle]["T000001"].references == {} - assert theIndex._itemIndex[tHandle]["T000001"].level == "H2" - assert theIndex._itemIndex[tHandle]["T000001"].title == "Prologue" - assert theIndex._itemIndex[tHandle]["T000001"].charCount == 43 - assert theIndex._itemIndex[tHandle]["T000001"].wordCount == 8 - assert theIndex._itemIndex[tHandle]["T000001"].paraCount == 1 - assert theIndex._itemIndex[tHandle]["T000001"].synopsis == "" + assert theIndex._itemIndex[cHandle]["T0001"].references == {} + assert theIndex._itemIndex[tHandle]["T0001"].level == "H2" + assert theIndex._itemIndex[tHandle]["T0001"].line == 1 + assert theIndex._itemIndex[tHandle]["T0001"].title == "Prologue" + assert theIndex._itemIndex[tHandle]["T0001"].charCount == 43 + assert theIndex._itemIndex[tHandle]["T0001"].wordCount == 8 + assert theIndex._itemIndex[tHandle]["T0001"].paraCount == 1 + assert theIndex._itemIndex[tHandle]["T0001"].synopsis == "" # Page wo/Title # ============= @@ -466,25 +474,27 @@ def testCoreIndex_ScanText(mockGUI, fncPath, mockRnd): assert theIndex.scanText(pHandle, ( "This is a page with some text on it.\n\n" )) - assert theIndex._itemIndex[pHandle]["T000000"].references == {} - assert theIndex._itemIndex[pHandle]["T000000"].level == "H0" - assert theIndex._itemIndex[pHandle]["T000000"].title == "" - assert theIndex._itemIndex[pHandle]["T000000"].charCount == 36 - assert theIndex._itemIndex[pHandle]["T000000"].wordCount == 9 - assert theIndex._itemIndex[pHandle]["T000000"].paraCount == 1 - assert theIndex._itemIndex[pHandle]["T000000"].synopsis == "" + assert theIndex._itemIndex[pHandle]["T0000"].references == {} + assert theIndex._itemIndex[pHandle]["T0000"].level == "H0" + assert theIndex._itemIndex[pHandle]["T0000"].line == 0 + assert theIndex._itemIndex[pHandle]["T0000"].title == "" + assert theIndex._itemIndex[pHandle]["T0000"].charCount == 36 + assert theIndex._itemIndex[pHandle]["T0000"].wordCount == 9 + assert theIndex._itemIndex[pHandle]["T0000"].paraCount == 1 + assert theIndex._itemIndex[pHandle]["T0000"].synopsis == "" theProject.tree[pHandle]._layout = nwItemLayout.NOTE assert theIndex.scanText(pHandle, ( "This is a page with some text on it.\n\n" )) - assert theIndex._itemIndex[pHandle]["T000000"].references == {} - assert theIndex._itemIndex[pHandle]["T000000"].level == "H0" - assert theIndex._itemIndex[pHandle]["T000000"].title == "" - assert theIndex._itemIndex[pHandle]["T000000"].charCount == 36 - assert theIndex._itemIndex[pHandle]["T000000"].wordCount == 9 - assert theIndex._itemIndex[pHandle]["T000000"].paraCount == 1 - assert theIndex._itemIndex[pHandle]["T000000"].synopsis == "" + assert theIndex._itemIndex[pHandle]["T0000"].references == {} + assert theIndex._itemIndex[pHandle]["T0000"].level == "H0" + assert theIndex._itemIndex[pHandle]["T0000"].line == 0 + assert theIndex._itemIndex[pHandle]["T0000"].title == "" + assert theIndex._itemIndex[pHandle]["T0000"].charCount == 36 + assert theIndex._itemIndex[pHandle]["T0000"].wordCount == 9 + assert theIndex._itemIndex[pHandle]["T0000"].paraCount == 1 + assert theIndex._itemIndex[pHandle]["T0000"].synopsis == "" assert theProject.closeProject() is True @@ -512,8 +522,8 @@ def testCoreIndex_ExtractData(mockGUI, fncPath, mockRnd): nHandle = theProject.newFile("Hello", C.hNovelRoot) cHandle = theProject.newFile("Jane", C.hCharRoot) - assert theIndex.getNovelData("", "") is None - assert theIndex.getNovelData(C.hNovelRoot, "") is None + assert theIndex.getItemHeader("", "") is None + assert theIndex.getItemHeader(C.hNovelRoot, "") is None assert theIndex.scanText(cHandle, ( "# Jane Smith\n" @@ -534,10 +544,10 @@ def testCoreIndex_ExtractData(mockGUI, fncPath, mockRnd): theKeys.append(aKey) assert theKeys == [ - f"{C.hTitlePage}:T000001", - f"{C.hChapterDoc}:T000001", - f"{C.hSceneDoc}:T000001", - f"{nHandle}:T000001", + f"{C.hTitlePage}:T0001", + f"{C.hChapterDoc}:T0001", + f"{C.hSceneDoc}:T0001", + f"{nHandle}:T0001", ] # Check that excluded files can be skipped @@ -548,10 +558,10 @@ def testCoreIndex_ExtractData(mockGUI, fncPath, mockRnd): theKeys.append(aKey) assert theKeys == [ - f"{C.hTitlePage}:T000001", - f"{C.hChapterDoc}:T000001", - f"{C.hSceneDoc}:T000001", - f"{nHandle}:T000001", + f"{C.hTitlePage}:T0001", + f"{C.hChapterDoc}:T0001", + f"{C.hSceneDoc}:T0001", + f"{nHandle}:T0001", ] theKeys = [] @@ -559,9 +569,9 @@ def testCoreIndex_ExtractData(mockGUI, fncPath, mockRnd): theKeys.append(aKey) assert theKeys == [ - f"{C.hTitlePage}:T000001", - f"{C.hChapterDoc}:T000001", - f"{C.hSceneDoc}:T000001", + f"{C.hTitlePage}:T0001", + f"{C.hChapterDoc}:T0001", + f"{C.hSceneDoc}:T0001", ] # The novel file should have the correct counts @@ -570,6 +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 + # =========== + + theItem = theIndex.getItemData(nHandle) + assert isinstance(theItem, IndexItem) + assert theItem.headings() == ["T0001"] + # getReferences # ============= @@ -594,13 +611,13 @@ def testCoreIndex_ExtractData(mockGUI, fncPath, mockRnd): # The character file should have a record of the reference from the novel file theRefs = theIndex.getBackReferenceList(cHandle) - assert theRefs == {nHandle: "T000001"} + assert theRefs == {nHandle: "T0001"} # getTagSource # ============ - assert theIndex.getTagSource("Jane") == (cHandle, "T000001") - assert theIndex.getTagSource("John") == (None, "T000000") + assert theIndex.getTagSource("Jane") == (cHandle, "T0001") + assert theIndex.getTagSource("John") == (None, "T0000") # getCounts # ========= @@ -632,13 +649,13 @@ def testCoreIndex_ExtractData(mockGUI, fncPath, mockRnd): assert pC == 4 # First part - cC, wC, pC = theIndex.getCounts(nHandle, "T000001") + cC, wC, pC = theIndex.getCounts(nHandle, "T0001") assert cC == 62 assert wC == 12 assert pC == 2 # Second part - cC, wC, pC = theIndex.getCounts(nHandle, "T000011") + cC, wC, pC = theIndex.getCounts(nHandle, "T0002") assert cC == 90 assert wC == 16 assert pC == 2 @@ -665,13 +682,13 @@ def testCoreIndex_ExtractData(mockGUI, fncPath, mockRnd): assert pC == 4 # First part - cC, wC, pC = theIndex.getCounts(cHandle, "T000001") + cC, wC, pC = theIndex.getCounts(cHandle, "T0001") assert cC == 62 assert wC == 12 assert pC == 2 # Second part - cC, wC, pC = theIndex.getCounts(cHandle, "T000011") + cC, wC, pC = theIndex.getCounts(cHandle, "T0002") assert cC == 90 assert wC == 16 assert pC == 2 @@ -692,36 +709,36 @@ def testCoreIndex_ExtractData(mockGUI, fncPath, mockRnd): assert theIndex.scanText(tHandle, "### Scene Two\n\n") assert [(h, t) for h, t, _ in theIndex._itemIndex.iterNovelStructure(skipExcl=False)] == [ - (C.hTitlePage, "T000001"), - (C.hChapterDoc, "T000001"), - (C.hSceneDoc, "T000001"), - (nHandle, "T000001"), - (nHandle, "T000011"), - (hHandle, "T000001"), - (sHandle, "T000001"), - (tHandle, "T000001"), + (C.hTitlePage, "T0001"), + (C.hChapterDoc, "T0001"), + (C.hSceneDoc, "T0001"), + (nHandle, "T0001"), + (nHandle, "T0002"), + (hHandle, "T0001"), + (sHandle, "T0001"), + (tHandle, "T0001"), ] assert [(h, t) for h, t, _ in theIndex._itemIndex.iterNovelStructure(skipExcl=True)] == [ - (C.hTitlePage, "T000001"), - (C.hChapterDoc, "T000001"), - (C.hSceneDoc, "T000001"), - (hHandle, "T000001"), - (sHandle, "T000001"), - (tHandle, "T000001"), + (C.hTitlePage, "T0001"), + (C.hChapterDoc, "T0001"), + (C.hSceneDoc, "T0001"), + (hHandle, "T0001"), + (sHandle, "T0001"), + (tHandle, "T0001"), ] # Add a fake handle to the tree and check that it's ignored theProject.tree._treeOrder.append("0000000000000") assert [(h, t) for h, t, _ in theIndex._itemIndex.iterNovelStructure(skipExcl=False)] == [ - (C.hTitlePage, "T000001"), - (C.hChapterDoc, "T000001"), - (C.hSceneDoc, "T000001"), - (nHandle, "T000001"), - (nHandle, "T000011"), - (hHandle, "T000001"), - (sHandle, "T000001"), - (tHandle, "T000001"), + (C.hTitlePage, "T0001"), + (C.hChapterDoc, "T0001"), + (C.hSceneDoc, "T0001"), + (nHandle, "T0001"), + (nHandle, "T0002"), + (hHandle, "T0001"), + (sHandle, "T0001"), + (tHandle, "T0001"), ] theProject.tree._treeOrder.remove("0000000000000") @@ -734,37 +751,37 @@ def testCoreIndex_ExtractData(mockGUI, fncPath, mockRnd): # Table of Contents assert theIndex.getTableOfContents(C.hNovelRoot, 0, skipExcl=True) == [] assert theIndex.getTableOfContents(C.hNovelRoot, 1, skipExcl=True) == [ - (f"{C.hTitlePage}:T000001", 1, "New Novel", 15), + (f"{C.hTitlePage}:T0001", 1, "New Novel", 15), ] assert theIndex.getTableOfContents(C.hNovelRoot, 2, skipExcl=True) == [ - (f"{C.hTitlePage}:T000001", 1, "New Novel", 5), - (f"{C.hChapterDoc}:T000001", 2, "New Chapter", 4), - (f"{hHandle}:T000001", 2, "Chapter One", 6), + (f"{C.hTitlePage}:T0001", 1, "New Novel", 5), + (f"{C.hChapterDoc}:T0001", 2, "New Chapter", 4), + (f"{hHandle}:T0001", 2, "Chapter One", 6), ] assert theIndex.getTableOfContents(C.hNovelRoot, 3, skipExcl=True) == [ - (f"{C.hTitlePage}:T000001", 1, "New Novel", 5), - (f"{C.hChapterDoc}:T000001", 2, "New Chapter", 2), - (f"{C.hSceneDoc}:T000001", 3, "New Scene", 2), - (f"{hHandle}:T000001", 2, "Chapter One", 2), - (f"{sHandle}:T000001", 3, "Scene One", 2), - (f"{tHandle}:T000001", 3, "Scene Two", 2), + (f"{C.hTitlePage}:T0001", 1, "New Novel", 5), + (f"{C.hChapterDoc}:T0001", 2, "New Chapter", 2), + (f"{C.hSceneDoc}:T0001", 3, "New Scene", 2), + (f"{hHandle}:T0001", 2, "Chapter One", 2), + (f"{sHandle}:T0001", 3, "Scene One", 2), + (f"{tHandle}:T0001", 3, "Scene Two", 2), ] assert theIndex.getTableOfContents(C.hNovelRoot, 0, skipExcl=False) == [] assert theIndex.getTableOfContents(C.hNovelRoot, 1, skipExcl=False) == [ - (f"{C.hTitlePage}:T000001", 1, "New Novel", 9), - (f"{nHandle}:T000001", 1, "Hello World!", 12), - (f"{nHandle}:T000011", 1, "Hello World!", 22), + (f"{C.hTitlePage}:T0001", 1, "New Novel", 9), + (f"{nHandle}:T0001", 1, "Hello World!", 12), + (f"{nHandle}:T0002", 1, "Hello World!", 22), ] # Header Word Counts bHandle = "0000000000000" assert theIndex.getHandleWordCounts(bHandle) == [] - assert theIndex.getHandleWordCounts(hHandle) == [("%s:T000001" % hHandle, 2)] - assert theIndex.getHandleWordCounts(sHandle) == [("%s:T000001" % sHandle, 2)] - assert theIndex.getHandleWordCounts(tHandle) == [("%s:T000001" % tHandle, 2)] + assert theIndex.getHandleWordCounts(hHandle) == [("%s:T0001" % hHandle, 2)] + assert theIndex.getHandleWordCounts(sHandle) == [("%s:T0001" % sHandle, 2)] + assert theIndex.getHandleWordCounts(tHandle) == [("%s:T0001" % tHandle, 2)] assert theIndex.getHandleWordCounts(nHandle) == [ - (f"{nHandle}:T000001", 12), (f"{nHandle}:T000011", 16) + (f"{nHandle}:T0001", 12), (f"{nHandle}:T0002", 16) ] assert theIndex.saveIndex() is True @@ -773,11 +790,11 @@ def testCoreIndex_ExtractData(mockGUI, fncPath, mockRnd): # Header Record bHandle = "0000000000000" assert theIndex.getHandleHeaders(bHandle) == [] - assert theIndex.getHandleHeaders(hHandle) == [("T000001", "H2", "Chapter One")] - assert theIndex.getHandleHeaders(sHandle) == [("T000001", "H3", "Scene One")] - assert theIndex.getHandleHeaders(tHandle) == [("T000001", "H3", "Scene Two")] + 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) == [ - ("T000001", "H1", "Hello World!"), ("T000011", "H1", "Hello World!") + ("T0001", "H1", "Hello World!"), ("T0002", "H1", "Hello World!") ] assert theProject.closeProject() is True @@ -796,25 +813,25 @@ def testCoreIndex_TagsIndex(): content = { "Tag1": { "handle": "0000000000001", - "heading": "T000001", + "heading": "T0001", "class": nwItemClass.NOVEL.name, }, "Tag2": { "handle": "0000000000002", - "heading": "T000002", + "heading": "T0002", "class": nwItemClass.CHARACTER.name, }, "Tag3": { "handle": "0000000000003", - "heading": "T000003", + "heading": "T0003", "class": nwItemClass.PLOT.name, }, } # Add data - tagsIndex.add("Tag1", "0000000000001", "T000001", nwItemClass.NOVEL) - tagsIndex.add("Tag2", "0000000000002", "T000002", nwItemClass.CHARACTER) - tagsIndex.add("Tag3", "0000000000003", "T000003", nwItemClass.PLOT) + tagsIndex.add("Tag1", "0000000000001", "T0001", nwItemClass.NOVEL) + tagsIndex.add("Tag2", "0000000000002", "T0002", nwItemClass.CHARACTER) + tagsIndex.add("Tag3", "0000000000003", "T0003", nwItemClass.PLOT) assert tagsIndex._tags == content # Get items @@ -836,10 +853,10 @@ def testCoreIndex_TagsIndex(): assert tagsIndex.tagHandle("Tag4") is None # Read back headings - assert tagsIndex.tagHeading("Tag1") == "T000001" - assert tagsIndex.tagHeading("Tag2") == "T000002" - assert tagsIndex.tagHeading("Tag3") == "T000003" - assert tagsIndex.tagHeading("Tag4") == "T000000" + assert tagsIndex.tagHeading("Tag1") == "T0001" + assert tagsIndex.tagHeading("Tag2") == "T0002" + assert tagsIndex.tagHeading("Tag3") == "T0003" + assert tagsIndex.tagHeading("Tag4") == "T0000" # Read back classes assert tagsIndex.tagClass("Tag1") == nwItemClass.NOVEL.name @@ -880,7 +897,7 @@ def testCoreIndex_TagsIndex(): tagsIndex.unpackData({ 1234: { "handle": "0000000000001", - "heading": "T000001", + "heading": "T0001", "class": "NOVEL", } }) @@ -889,7 +906,7 @@ def testCoreIndex_TagsIndex(): with pytest.raises(KeyError): tagsIndex.unpackData({ "Tag1": { - "heading": "T000001", + "heading": "T0001", "class": "NOVEL", } }) @@ -908,7 +925,7 @@ def testCoreIndex_TagsIndex(): tagsIndex.unpackData({ "Tag1": { "handle": "0000000000001", - "heading": "T000001", + "heading": "T0001", } }) @@ -917,7 +934,7 @@ def testCoreIndex_TagsIndex(): tagsIndex.unpackData({ "Tag1": { "handle": "blablabla", - "heading": "T000001", + "heading": "T0001", "class": "NOVEL", } }) @@ -937,7 +954,7 @@ def testCoreIndex_TagsIndex(): tagsIndex.unpackData({ "Tag1": { "handle": "0000000000001", - "heading": "T000001", + "heading": "T0001", "class": "blabla", } }) @@ -975,66 +992,70 @@ def testCoreIndex_ItemIndex(mockGUI, fncPath, mockRnd): assert cHandle in itemIndex assert itemIndex[cHandle].item == theProject.tree[cHandle] assert itemIndex.allItemTags(cHandle) == [] - assert list(itemIndex.iterItemHeaders(cHandle))[0][0] == "T000000" + assert list(itemIndex.iterItemHeaders(cHandle))[0][0] == "T0000" # Add a heading to the item, which should replace the T000000 heading - itemIndex.addItemHeading(cHandle, "T000001", "H2", "Chapter One") - assert list(itemIndex.iterItemHeaders(cHandle))[0][0] == "T000001" + assert itemIndex.addItemHeading(cHandle, 1, "H2", "Chapter One") == "T0001" + assert list(itemIndex.iterItemHeaders(cHandle))[0][0] == "T0001" + + # Add a heading to an invalid item + assert itemIndex.addItemHeading(C.hInvalid, 1, "H1", "Stuff") == "T0000" # Set the remainig data values - itemIndex.setHeadingCounts(cHandle, "T000001", 60, 10, 2) - itemIndex.setHeadingSynopsis(cHandle, "T000001", "In the beginning ...") - itemIndex.setHeadingTag(cHandle, "T000001", "One") - itemIndex.addHeadingReferences(cHandle, "T000001", ["Jane"], "@pov") - itemIndex.addHeadingReferences(cHandle, "T000001", ["Jane"], "@focus") - itemIndex.addHeadingReferences(cHandle, "T000001", ["Jane", "John"], "@char") + itemIndex.setHeadingCounts(cHandle, "T0001", 60, 10, 2) + itemIndex.setHeadingSynopsis(cHandle, "T0001", "In the beginning ...") + itemIndex.setHeadingTag(cHandle, "T0001", "One") + itemIndex.addHeadingReferences(cHandle, "T0001", ["Jane"], "@pov") + itemIndex.addHeadingReferences(cHandle, "T0001", ["Jane"], "@focus") + itemIndex.addHeadingReferences(cHandle, "T0001", ["Jane", "John"], "@char") idxData = itemIndex.packData() - assert idxData[cHandle]["headings"]["T000001"] == { - "level": "H2", "title": "Chapter One", "tag": "One", + assert idxData[cHandle]["headings"]["T0001"] == { + "level": "H2", "line": 1, "title": "Chapter One", "tag": "One", "cCount": 60, "wCount": 10, "pCount": 2, "synopsis": "In the beginning ...", } - assert "@pov" in idxData[cHandle]["references"]["T000001"]["Jane"] - assert "@focus" in idxData[cHandle]["references"]["T000001"]["Jane"] - assert "@char" in idxData[cHandle]["references"]["T000001"]["Jane"] - assert "@char" in idxData[cHandle]["references"]["T000001"]["John"] + assert "@pov" in idxData[cHandle]["references"]["T0001"]["Jane"] + assert "@focus" in idxData[cHandle]["references"]["T0001"]["Jane"] + assert "@char" in idxData[cHandle]["references"]["T0001"]["Jane"] + assert "@char" in idxData[cHandle]["references"]["T0001"]["John"] # Add the other two files itemIndex.add(nHandle, theProject.tree[nHandle]) itemIndex.add(sHandle, theProject.tree[sHandle]) - itemIndex.addItemHeading(nHandle, "T000001", "H1", "Novel") - itemIndex.addItemHeading(sHandle, "T000001", "H3", "Scene One") + itemIndex.addItemHeading(nHandle, 1, "H1", "Novel") + itemIndex.addItemHeading(sHandle, 1, "H3", "Scene One") # Check Item and Heading Direct Access # ==================================== # Check repr strings assert repr(itemIndex[nHandle]) == f"" - assert repr(itemIndex[nHandle]["T000001"]) == "" + assert repr(itemIndex[nHandle]["T0001"]) == "" # Check content of a single item - assert "T000001" in itemIndex[nHandle] + assert "T0001" in itemIndex[nHandle] assert itemIndex[cHandle].allTags() == ["One"] # Check the content of a single heading - assert itemIndex[cHandle]["T000001"].key == "T000001" - assert itemIndex[cHandle]["T000001"].level == "H2" - assert itemIndex[cHandle]["T000001"].title == "Chapter One" - assert itemIndex[cHandle]["T000001"].tag == "One" - assert itemIndex[cHandle]["T000001"].charCount == 60 - assert itemIndex[cHandle]["T000001"].wordCount == 10 - assert itemIndex[cHandle]["T000001"].paraCount == 2 - assert itemIndex[cHandle]["T000001"].synopsis == "In the beginning ..." - assert "Jane" in itemIndex[cHandle]["T000001"].references - assert "John" in itemIndex[cHandle]["T000001"].references + assert itemIndex[cHandle]["T0001"].key == "T0001" + assert itemIndex[cHandle]["T0001"].level == "H2" + assert itemIndex[cHandle]["T0001"].line == 1 + assert itemIndex[cHandle]["T0001"].title == "Chapter One" + assert itemIndex[cHandle]["T0001"].tag == "One" + assert itemIndex[cHandle]["T0001"].charCount == 60 + assert itemIndex[cHandle]["T0001"].wordCount == 10 + assert itemIndex[cHandle]["T0001"].paraCount == 2 + assert itemIndex[cHandle]["T0001"].synopsis == "In the beginning ..." + assert "Jane" in itemIndex[cHandle]["T0001"].references + assert "John" in itemIndex[cHandle]["T0001"].references # Check heading level setter - itemIndex[cHandle]["T000001"].setLevel("H3") # Change it - assert itemIndex[cHandle]["T000001"].level == "H3" - itemIndex[cHandle]["T000001"].setLevel("H2") # Set it back - assert itemIndex[cHandle]["T000001"].level == "H2" - itemIndex[cHandle]["T000001"].setLevel("H5") # Invalid level - assert itemIndex[cHandle]["T000001"].level == "H2" + itemIndex[cHandle]["T0001"].setLevel("H3") # Change it + assert itemIndex[cHandle]["T0001"].level == "H3" + itemIndex[cHandle]["T0001"].setLevel("H2") # Set it back + assert itemIndex[cHandle]["T0001"].level == "H2" + itemIndex[cHandle]["T0001"].setLevel("H5") # Invalid level + assert itemIndex[cHandle]["T0001"].level == "H2" # Data Extraction # =============== @@ -1044,9 +1065,9 @@ def testCoreIndex_ItemIndex(mockGUI, fncPath, mockRnd): assert allHeads[0][0] == cHandle assert allHeads[1][0] == nHandle assert allHeads[2][0] == sHandle - assert allHeads[0][1] == "T000001" - assert allHeads[1][1] == "T000001" - assert allHeads[2][1] == "T000001" + assert allHeads[0][1] == "T0001" + assert allHeads[1][1] == "T0001" + assert allHeads[2][1] == "T0001" # Ask for stuff that doesn't exist assert itemIndex.allItemTags("blablabla") == [] @@ -1058,7 +1079,7 @@ def testCoreIndex_ItemIndex(mockGUI, fncPath, mockRnd): mHandle = theProject.newRoot(nwItemClass.NOVEL) uHandle = theProject.newFile("Title Page", mHandle) itemIndex.add(uHandle, theProject.tree[uHandle]) - itemIndex.addItemHeading(uHandle, "T000001", "H1", "Novel 2") + itemIndex.addItemHeading(uHandle, "T0001", "H1", "Novel 2") assert uHandle in itemIndex # Structure of all novels @@ -1134,20 +1155,20 @@ def testCoreIndex_ItemIndex(mockGUI, fncPath, mockRnd): # Reference without a heading should be rejected itemIndex.unpackData({ cHandle: { - "headings": {"T000001": {}}, - "references": {"T000001": {}, "T000002": {}}, + "headings": {"T0001": {}}, + "references": {"T0001": {}, "T0002": {}}, } }) - assert "T000001" in itemIndex[cHandle] - assert "T000002" not in itemIndex[cHandle] + assert "T0001" in itemIndex[cHandle] + assert "T0002" not in itemIndex[cHandle] itemIndex.clear() # Tag keys must be strings with pytest.raises(ValueError): itemIndex.unpackData({ cHandle: { - "headings": {"T000001": {}}, - "references": {"T000001": {1234: "@pov"}}, + "headings": {"T0001": {}}, + "references": {"T0001": {1234: "@pov"}}, } }) @@ -1155,8 +1176,8 @@ def testCoreIndex_ItemIndex(mockGUI, fncPath, mockRnd): with pytest.raises(ValueError): itemIndex.unpackData({ cHandle: { - "headings": {"T000001": {}}, - "references": {"T000001": {"John": []}}, + "headings": {"T0001": {}}, + "references": {"T0001": {"John": []}}, } }) @@ -1164,16 +1185,16 @@ def testCoreIndex_ItemIndex(mockGUI, fncPath, mockRnd): with pytest.raises(ValueError): itemIndex.unpackData({ cHandle: { - "headings": {"T000001": {}}, - "references": {"T000001": {"John": "@pov,@char,@stuff"}}, + "headings": {"T0001": {}}, + "references": {"T0001": {"John": "@pov,@char,@stuff"}}, } }) # This should pass itemIndex.unpackData({ cHandle: { - "headings": {"T000001": {}}, - "references": {"T000001": {"John": "@pov,@char"}}, + "headings": {"T0001": {}}, + "references": {"T0001": {"John": "@pov,@char"}}, } }) diff --git a/tests/test_gui/test_gui_noveltree.py b/tests/test_gui/test_gui_noveltree.py index 20c38d03..15101972 100644 --- a/tests/test_gui/test_gui_noveltree.py +++ b/tests/test_gui/test_gui_noveltree.py @@ -92,7 +92,7 @@ def testGuiNovelTree_TreeItems(qtbot, monkeypatch, nwGUI, projPath, mockRnd): assert not topItem.isSelected() topItem.setSelected(True) assert novelTree.selectedItems()[0] == topItem - assert novelView.getSelectedHandle() == (C.hTitlePage, 0) + assert novelView.getSelectedHandle() == (C.hTitlePage, "T0001") # Refresh using the slot for the butoom novelBar._refreshNovelTree() @@ -142,31 +142,31 @@ def testGuiNovelTree_TreeItems(qtbot, monkeypatch, nwGUI, projPath, mockRnd): novelBar.setLastColType(NovelTreeColumn.HIDDEN) assert novelTree.isColumnHidden(novelTree.C_EXTRA) is True assert novelTree.lastColType == NovelTreeColumn.HIDDEN - assert novelTree._getLastColumnText(C.hSceneDoc, "T000001") == ("", "") + assert novelTree._getLastColumnText(C.hSceneDoc, "T0001") == ("", "") novelBar.setLastColType(NovelTreeColumn.POV) assert novelTree.isColumnHidden(novelTree.C_EXTRA) is False assert novelTree.lastColType == NovelTreeColumn.POV - assert novelTree._getLastColumnText(C.hSceneDoc, "T000001") == ( + assert novelTree._getLastColumnText(C.hSceneDoc, "T0001") == ( "Jane", "Point of View: Jane" ) novelBar.setLastColType(NovelTreeColumn.FOCUS) assert novelTree.isColumnHidden(novelTree.C_EXTRA) is False assert novelTree.lastColType == NovelTreeColumn.FOCUS - assert novelTree._getLastColumnText(C.hSceneDoc, "T000001") == ( + assert novelTree._getLastColumnText(C.hSceneDoc, "T0001") == ( "Jane", "Focus: Jane" ) novelBar.setLastColType(NovelTreeColumn.PLOT) assert novelTree.isColumnHidden(novelTree.C_EXTRA) is False assert novelTree.lastColType == NovelTreeColumn.PLOT - assert novelTree._getLastColumnText(C.hSceneDoc, "T000001") == ( + assert novelTree._getLastColumnText(C.hSceneDoc, "T0001") == ( "", "Plot: " ) novelTree._lastCol = None - assert novelTree._getLastColumnText("0000000000000", "T000000") == ("", "") + assert novelTree._getLastColumnText("0000000000000", "T0000") == ("", "") # Item Meta # ========= diff --git a/tests/test_gui/test_gui_outline.py b/tests/test_gui/test_gui_outline.py index 23f61c5b..707f497d 100644 --- a/tests/test_gui/test_gui_outline.py +++ b/tests/test_gui/test_gui_outline.py @@ -225,9 +225,9 @@ def testGuiOutline_Content(qtbot, nwGUI, nwLipsum): selItem = outlineTree.topLevelItem(4) outlineTree.setCurrentItem(selItem) - tHandle, tLine = outlineTree.getSelectedHandle() + tHandle, sTitle = outlineTree.getSelectedHandle() assert tHandle == "88243afbe5ed8" - assert tLine == 0 + assert sTitle == "T0001" assert outlineData.titleLabel.text() == "Scene" assert outlineData.titleValue.text() == "Scene One" @@ -243,9 +243,9 @@ def testGuiOutline_Content(qtbot, nwGUI, nwLipsum): selItem = outlineTree.topLevelItem(5) outlineTree.setCurrentItem(selItem) - tHandle, tLine = outlineTree.getSelectedHandle() + tHandle, sTitle = outlineTree.getSelectedHandle() assert tHandle == "88243afbe5ed8" - assert tLine == 12 + assert sTitle == "T0002" assert outlineData.titleLabel.text() == "Section" assert outlineData.titleValue.text() == "Scene One, Section Two" From ebc66540692195cb226d29ab992a6b73ee6ace42 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Mon, 14 Nov 2022 18:45:36 +0100 Subject: [PATCH 4/5] Improve how the novel tree is refreshed --- novelwriter/core/index.py | 79 ++++++++++++---------------- novelwriter/gui/doceditor.py | 29 +++------- novelwriter/gui/noveltree.py | 63 ++++++++++------------ novelwriter/guimain.py | 19 +------ tests/test_core/test_core_index.py | 16 ++---- tests/test_gui/test_gui_doceditor.py | 4 +- tests/test_gui/test_gui_guimain.py | 1 - 7 files changed, 76 insertions(+), 135 deletions(-) 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 From 8dbaa6bbd92af032872c00a26fdc94afb9bb066c Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Mon, 14 Nov 2022 18:59:47 +0100 Subject: [PATCH 5/5] Remove no longer needed index function --- novelwriter/core/index.py | 8 -------- tests/test_core/test_core_index.py | 10 ---------- 2 files changed, 18 deletions(-) diff --git a/novelwriter/core/index.py b/novelwriter/core/index.py index fb7c5f28..fdedb526 100644 --- a/novelwriter/core/index.py +++ b/novelwriter/core/index.py @@ -520,14 +520,6 @@ class NWIndex: hCount[iLevel] += 1 return hCount - def getHandleWordCounts(self, tHandle): - """Get all header word counts for a specific handle. - """ - return [ - (f"{tHandle}:{sTitle}", hItem.wordCount) - for sTitle, hItem in self._itemIndex.iterItemHeaders(tHandle) - ] - def getHandleHeaderCount(self, tHandle): """Get the number of headers in an item. """ diff --git a/tests/test_core/test_core_index.py b/tests/test_core/test_core_index.py index 7ea81ed2..dd94155f 100644 --- a/tests/test_core/test_core_index.py +++ b/tests/test_core/test_core_index.py @@ -775,16 +775,6 @@ def testCoreIndex_ExtractData(mockGUI, fncPath, mockRnd): (f"{nHandle}:T0002", 1, "Hello World!", 22), ] - # Header Word Counts - bHandle = "0000000000000" - assert theIndex.getHandleWordCounts(bHandle) == [] - assert theIndex.getHandleWordCounts(hHandle) == [("%s:T0001" % hHandle, 2)] - assert theIndex.getHandleWordCounts(sHandle) == [("%s:T0001" % sHandle, 2)] - assert theIndex.getHandleWordCounts(tHandle) == [("%s:T0001" % tHandle, 2)] - assert theIndex.getHandleWordCounts(nHandle) == [ - (f"{nHandle}:T0001", 12), (f"{nHandle}:T0002", 16) - ] - assert theIndex.saveIndex() is True assert theProject.saveProject() is True assert theProject.closeProject() is True