Improve how the novel tree is refreshed

This commit is contained in:
Veronica Berglyd Olsen
2022-11-14 18:45:36 +01:00
parent 992ed4f36c
commit ebc6654069
7 changed files with 76 additions and 135 deletions
+35 -44
View File
@@ -528,13 +528,13 @@ class NWIndex:
for sTitle, hItem in self._itemIndex.iterItemHeaders(tHandle) for sTitle, hItem in self._itemIndex.iterItemHeaders(tHandle)
] ]
def getHandleHeaders(self, tHandle): def getHandleHeaderCount(self, tHandle):
"""Get all headers for a specific handle. """Get the number of headers in an item.
""" """
return [ tItem = self._itemIndex[tHandle]
(sTitle, hItem.level, hItem.title) if isinstance(tItem, IndexItem):
for sTitle, hItem in self._itemIndex.iterItemHeaders(tHandle) return len(tItem)
] return 0
def getTableOfContents(self, rootHandle, maxDepth, skipExcl=True): def getTableOfContents(self, rootHandle, maxDepth, skipExcl=True):
"""Generate a table of contents up to a maximum depth. """Generate a table of contents up to a maximum depth.
@@ -645,6 +645,16 @@ class TagsIndex:
self._tags = {} self._tags = {}
return 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 # Methods
## ##
@@ -655,22 +665,6 @@ class TagsIndex:
self._tags = {} self._tags = {}
return 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): def add(self, tagKey, tHandle, sTitle, itemClass):
"""Add a key to the index and set all values. """Add a key to the index and set all values.
""" """
@@ -753,6 +747,16 @@ class ItemIndex:
self._items = {} self._items = {}
return 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 # Methods
## ##
@@ -763,22 +767,6 @@ class ItemIndex:
self._items = {} self._items = {}
return 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): def add(self, tHandle, tItem):
"""Add a new item to the index. This will overwrite the item if """Add a new item to the index. This will overwrite the item if
it already exists. it already exists.
@@ -933,6 +921,15 @@ class IndexItem:
def __repr__(self): def __repr__(self):
return f"<IndexItem handle='{self._handle}'>" return f"<IndexItem handle='{self._handle}'>"
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 # Properties
## ##
@@ -987,12 +984,6 @@ class IndexItem:
# Data Methods # Data Methods
## ##
def __getitem__(self, sTitle):
return self._headings.get(sTitle, None)
def __contains__(self, sTitle):
return sTitle in self._headings
def items(self): def items(self):
return self._headings.items() return self._headings.items()
+7 -22
View File
@@ -90,7 +90,6 @@ class GuiDocEditor(QTextEdit):
self._docChanged = False # Flag for changed status of document self._docChanged = False # Flag for changed status of document
self._docHandle = None # The handle of the open file 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._spellCheck = False # Flag for spell checking enabled
self._nonWord = "\"'" # Characters to not include in spell checking self._nonWord = "\"'" # Characters to not include in spell checking
@@ -417,8 +416,8 @@ class GuiDocEditor(QTextEdit):
self._queuePos = self._nwItem.cursorPos self._queuePos = self._nwItem.cursorPos
else: else:
self.setCursorPosition(self._nwItem.cursorPos) self.setCursorPosition(self._nwItem.cursorPos)
else: elif isinstance(tLine, int):
self.setCursorLine(tLine) self.setCursorLine(tLine - 1)
if self.mainConf.scrollPastEnd > 0: if self.mainConf.scrollPastEnd > 0:
fSize = QFontMetrics(self.font()).lineSpacing() fSize = QFontMetrics(self.font()).lineSpacing()
@@ -427,7 +426,6 @@ class GuiDocEditor(QTextEdit):
self.document().rootFrame().setFrameFormat(docFrame) self.document().rootFrame().setFrameFormat(docFrame)
self.docFooter.updateLineCount() self.docFooter.updateLineCount()
self._docHeaders = self.theProject.index.getHandleHeaders(self._docHandle)
qApp.processEvents() qApp.processEvents()
self.document().clearUndoRedoStacks() self.document().clearUndoRedoStacks()
@@ -533,14 +531,16 @@ class GuiDocEditor(QTextEdit):
self.setDocumentChanged(False) self.setDocumentChanged(False)
oldHeader = self._nwItem.mainHeading oldHeader = self._nwItem.mainHeading
oldCount = self.theProject.index.getHandleHeaderCount(tHandle)
self.theProject.index.scanText(tHandle, docText) self.theProject.index.scanText(tHandle, docText)
newHeader = self._nwItem.mainHeading newHeader = self._nwItem.mainHeading
newCount = self.theProject.index.getHandleHeaderCount(tHandle)
if self._nwItem.itemClass == nwItemClass.NOVEL: if self._nwItem.itemClass == nwItemClass.NOVEL:
if self._updateHeaders(): if oldCount == newCount:
self.novelStructureChanged.emit()
else:
self.novelItemMetaChanged.emit(tHandle) self.novelItemMetaChanged.emit(tHandle)
else:
self.novelStructureChanged.emit()
# ToDo: This should be a signal # ToDo: This should be a signal
if oldHeader != newHeader: if oldHeader != newHeader:
@@ -2067,21 +2067,6 @@ class GuiDocEditor(QTextEdit):
return False return False
return True 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): def _checkDocSize(self, theSize):
"""Check if document size crosses the big document limit set in """Check if document size crosses the big document limit set in
config. If so, we will set the big document flag to True. config. If so, we will set the big document flag to True.
+27 -36
View File
@@ -477,7 +477,7 @@ class GuiNovelTree(QTreeWidget):
return return
def refreshTree(self, rootHandle=None, overRide=False): 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") logger.debug("Requesting refresh of the novel tree")
if rootHandle is None: if rootHandle is None:
@@ -509,31 +509,16 @@ class GuiNovelTree(QTreeWidget):
if idxData is None: if idxData is None:
return return
logger.debug("Refreshing meta data for item '%s'", tHandle)
for sTitle, tHeading in idxData.items(): for sTitle, tHeading in idxData.items():
sKey = f"{tHandle}:{sTitle}" sKey = f"{tHandle}:{sTitle}"
trItem = self._treeMap.get(sKey, None) trItem = self._treeMap.get(sKey, None)
if trItem is None: if trItem is None:
logger.debug("Heading '%s' not in novel tree", sKey) logger.debug("Heading '%s' not in novel tree", sKey)
continue self.refreshTree()
return
iLevel = nwHeaders.H_LEVEL.get(tHeading.level, 0) self._updateTreeItemValues(trItem, tHeading, tHandle, sTitle)
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 return
@@ -668,30 +653,16 @@ class GuiNovelTree(QTreeWidget):
novStruct = self.theProject.index.novelStructure(rootHandle=rootHandle, skipExcl=True) novStruct = self.theProject.index.novelStructure(rootHandle=rootHandle, skipExcl=True)
for tKey, tHandle, sTitle, novIdx in novStruct: for tKey, tHandle, sTitle, novIdx in novStruct:
if novIdx.level == "H0":
iLevel = nwHeaders.H_LEVEL.get(novIdx.level, 0)
if iLevel == 0:
continue continue
hDec = self.mainTheme.getHeaderDecoration(iLevel)
newItem = QTreeWidgetItem() 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_HANDLE, tHandle)
newItem.setData(self.C_TITLE, self.D_TITLE, sTitle) newItem.setData(self.C_TITLE, self.D_TITLE, sTitle)
newItem.setData(self.C_TITLE, self.D_KEY, tKey) 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.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._treeMap[tKey] = newItem
self.addTopLevelItem(newItem) self.addTopLevelItem(newItem)
@@ -702,6 +673,26 @@ class GuiNovelTree(QTreeWidget):
return 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): def _getLastColumnText(self, tHandle, sTitle):
"""Generate the text for the last column based on user settings. """Generate the text for the last column based on user settings.
""" """
+2 -17
View File
@@ -818,17 +818,8 @@ class GuiMain(QMainWindow):
"""Rebuild the project tree. """Rebuild the project tree.
""" """
self.projView.populateTree() self.projView.populateTree()
# self.novelView.refreshTree()
return 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): def rebuildIndex(self, beQuiet=False):
"""Rebuild the entire index. """Rebuild the entire index.
""" """
@@ -843,6 +834,7 @@ class GuiMain(QMainWindow):
self.projView.saveProjectTasks() self.projView.saveProjectTasks()
self.theProject.index.rebuildIndex() self.theProject.index.rebuildIndex()
self.projView.populateTree() self.projView.populateTree()
self.novelView.refreshTree()
tEnd = time() tEnd = time()
self.setStatus( self.setStatus(
@@ -1578,7 +1570,6 @@ class GuiMain(QMainWindow):
self.docEditor.closeSearch() self.docEditor.closeSearch()
elif self.isFocusMode: elif self.isFocusMode:
self.toggleFocusMode() self.toggleFocusMode()
return return
@pyqtSlot(int) @pyqtSlot(int)
@@ -1595,17 +1586,11 @@ class GuiMain(QMainWindow):
"""Activated when the project view tab is changed. """Activated when the project view tab is changed.
""" """
sHandle = None sHandle = None
if stIndex == self.idxProjView: if stIndex == self.idxProjView:
sHandle = self.projView.getSelectedHandle() sHandle = self.projView.getSelectedHandle()
elif stIndex == self.idxNovelView: elif stIndex == self.idxNovelView:
if self.hasProject: sHandle, _ = self.novelView.getSelectedHandle()
self.novelView.refreshTree()
sHandle, _ = self.novelView.getSelectedHandle()
self.itemDetails.updateViewBox(sHandle) self.itemDetails.updateViewBox(sHandle)
return return
# END Class GuiMain # END Class GuiMain
+3 -13
View File
@@ -580,12 +580,13 @@ def testCoreIndex_ExtractData(mockGUI, fncPath, mockRnd):
assert wC == 12 # Words in text and title only assert wC == 12 # Words in text and title only
assert pC == 2 # Paragraphs in text only assert pC == 2 # Paragraphs in text only
# getItemData # getItemData + getHandleHeaderCount
# =========== # ==================================
theItem = theIndex.getItemData(nHandle) theItem = theIndex.getItemData(nHandle)
assert isinstance(theItem, IndexItem) assert isinstance(theItem, IndexItem)
assert theItem.headings() == ["T0001"] assert theItem.headings() == ["T0001"]
assert theIndex.getHandleHeaderCount(nHandle) == 1
# getReferences # getReferences
# ============= # =============
@@ -786,17 +787,6 @@ def testCoreIndex_ExtractData(mockGUI, fncPath, mockRnd):
assert theIndex.saveIndex() is True assert theIndex.saveIndex() is True
assert theProject.saveProject() 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 assert theProject.closeProject() is True
# END Test testCoreIndex_ExtractData # END Test testCoreIndex_ExtractData
+2 -2
View File
@@ -118,10 +118,10 @@ def testGuiEditor_LoadText(qtbot, monkeypatch, caplog, nwGUI, projPath, ipsumTex
assert nwGUI.docEditor.loadText(C.hSceneDoc) is True assert nwGUI.docEditor.loadText(C.hSceneDoc) is True
assert nwGUI.docEditor._bigDoc 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 assert nwGUI.docEditor.loadText(C.hSceneDoc, tLine=4) is True
cursPos = nwGUI.docEditor.getCursorPosition() cursPos = nwGUI.docEditor.getCursorPosition()
assert nwGUI.docEditor.document().findBlock(cursPos).blockNumber() == 4 assert nwGUI.docEditor.document().findBlock(cursPos).blockNumber() == 3
# Load empty document # Load empty document
nwGUI.docEditor.replaceText("") nwGUI.docEditor.replaceText("")
-1
View File
@@ -57,7 +57,6 @@ def testGuiMain_ProjectBlocker(nwGUI):
assert nwGUI.importDocument() is False assert nwGUI.importDocument() is False
assert nwGUI.openSelectedItem() is False assert nwGUI.openSelectedItem() is False
assert nwGUI.editItemLabel() is False assert nwGUI.editItemLabel() is False
assert nwGUI.requestNovelTreeRefresh() is False
assert nwGUI.rebuildIndex() is False assert nwGUI.rebuildIndex() is False
assert nwGUI.showProjectSettingsDialog() is False assert nwGUI.showProjectSettingsDialog() is False
assert nwGUI.showProjectDetailsDialog() is False assert nwGUI.showProjectDetailsDialog() is False