Added timestamp to project tree, and made some class variables internal in various classes

This commit is contained in:
Veronica K. B. Olsen
2021-01-02 16:57:12 +01:00
parent ad5f3f2450
commit 4d7f925c5a
8 changed files with 108 additions and 87 deletions
+32 -19
View File
@@ -51,18 +51,16 @@ class NWIndex():
self.indexBroken = False self.indexBroken = False
# Indices # Indices
self.tagIndex = None self.tagIndex = {}
self.refIndex = None self.refIndex = {}
self.novelIndex = None self.novelIndex = {}
self.noteIndex = None self.noteIndex = {}
self.textCounts = None self.textCounts = {}
# TimeStamps # TimeStamps
self.timeNovel = 0 self._timeNovel = 0
self.timeNote = 0 self._timeNotes = 0
self.timeIndex = 0 self._timeIndex = 0
self.clearIndex()
return return
@@ -78,9 +76,9 @@ class NWIndex():
self.novelIndex = {} self.novelIndex = {}
self.noteIndex = {} self.noteIndex = {}
self.textCounts = {} self.textCounts = {}
self.timeNovel = 0 self._timeNovel = 0
self.timeNote = 0 self._timeNotes = 0
self.timeIndex = 0 self._timeIndex = 0
return return
def deleteHandle(self, tHandle): def deleteHandle(self, tHandle):
@@ -123,6 +121,21 @@ class NWIndex():
return True return True
def novelChangedSince(self, checkTime):
"""Check if the novel index has changed since a given time.
"""
return self._timeNovel > checkTime
def notesChangedSince(self, checkTime):
"""Check if the notes index has changed since a given time.
"""
return self._timeNotes > checkTime
def indexChangedSince(self, checkTime):
"""Check if the index has changed since a given time.
"""
return self._timeIndex > checkTime
## ##
# Load and Save Index to/from File # Load and Save Index to/from File
## ##
@@ -155,9 +168,9 @@ class NWIndex():
self.textCounts = theData["textCounts"] self.textCounts = theData["textCounts"]
nowTime = round(time()) nowTime = round(time())
self.timeNovel = nowTime self._timeNovel = nowTime
self.timeNote = nowTime self._timeNotes = nowTime
self.timeIndex = nowTime self._timeIndex = nowTime
self.checkIndex() self.checkIndex()
@@ -334,11 +347,11 @@ class NWIndex():
# Update timestamps for index changes # Update timestamps for index changes
nowTime = round(time()) nowTime = round(time())
self.timeIndex = nowTime self._timeIndex = nowTime
if isNovel: if isNovel:
self.timeNovel = nowTime self._timeNovel = nowTime
else: else:
self.timeNote = nowTime self._timeNotes = nowTime
return True return True
+2 -2
View File
@@ -1025,7 +1025,7 @@ class NWProject():
by drag-and-drop. Forwarded to the NWTree class. by drag-and-drop. Forwarded to the NWTree class.
""" """
if len(self.projTree) != len(newOrder): if len(self.projTree) != len(newOrder):
logger.warning("Size of new and old tree order do not match") logger.warning("Sizes of new and old tree order do not match")
self.projTree.setOrder(newOrder) self.projTree.setOrder(newOrder)
self.setProjectChanged(True) self.setProjectChanged(True)
return True return True
@@ -1341,7 +1341,7 @@ class NWProject():
if oLayout is None: if oLayout is None:
oLayout = nwItemLayout.NOTE oLayout = nwItemLayout.NOTE
if oParent is None or not self.projTree.handleExists(oParent): if oParent is None or oParent not in self.projTree:
oParent = self.projTree.findRoot(oClass) oParent = self.projTree.findRoot(oClass)
if oParent is None: if oParent is None:
oParent = self.projTree.findRoot(nwItemClass.NOVEL) oParent = self.projTree.findRoot(nwItemClass.NOVEL)
-5
View File
@@ -294,11 +294,6 @@ class NWTree():
tTree.append(tHandle) tTree.append(tHandle)
return tTree return tTree
def handleExists(self, tHandle):
"""Check if a handle exists in the project.
"""
return tHandle in self._treeOrder
## ##
# Setters # Setters
## ##
+15 -11
View File
@@ -54,9 +54,9 @@ class GuiNovelTree(QTreeWidget):
self.theProject = theParent.theProject self.theProject = theParent.theProject
self.theIndex = theParent.theIndex self.theIndex = theParent.theIndex
# Tree State # Internal Variables
self.lastBuild = 0 self._treeMap = {}
self.treeMap = {} self._lastBuild = 0
# Build GUI # Build GUI
iPx = self.theTheme.baseIconSize iPx = self.theTheme.baseIconSize
@@ -124,14 +124,17 @@ class GuiNovelTree(QTreeWidget):
"""Clear the GUI content and the related maps. """Clear the GUI content and the related maps.
""" """
self.clear() self.clear()
self.treeMap = {} self._treeMap = {}
self._lastBuild = 0
return return
def refreshTree(self, overRide=False): def refreshTree(self, overRide=False):
"""Called whenever the Novel tab is activated. """Called whenever the Novel tab is activated.
""" """
if self.lastBuild >= self.theIndex.timeNovel: treeChanged = self.theParent.treeView.changedSince(self._lastBuild)
logger.verbose("Novel tree more recent than the novel index: not updating") indexChanged = self.theIndex.novelChangedSince(self._lastBuild)
if not (treeChanged or indexChanged):
logger.verbose("No changes made to the novel")
return return
selItem = self.selectedItems() selItem = self.selectedItems()
@@ -139,10 +142,11 @@ class GuiNovelTree(QTreeWidget):
if selItem: if selItem:
titleKey = selItem[0].data(self.C_TITLE, Qt.UserRole)[2] titleKey = selItem[0].data(self.C_TITLE, Qt.UserRole)[2]
self.theParent.treeView.flushTreeOrder()
self._populateTree() self._populateTree()
if titleKey is not None and titleKey in self.treeMap: if titleKey is not None and titleKey in self._treeMap:
self.treeMap[titleKey].setSelected(True) self._treeMap[titleKey].setSelected(True)
return return
@@ -233,7 +237,7 @@ class GuiNovelTree(QTreeWidget):
def _populateTree(self): def _populateTree(self):
"""Build the tree based on the project index. """Build the tree based on the project index.
""" """
self.clear() self.clearTree()
for titleKey in self.theIndex.getNovelStructure(skipExcluded=True): for titleKey in self.theIndex.getNovelStructure(skipExcluded=True):
@@ -250,7 +254,7 @@ class GuiNovelTree(QTreeWidget):
tLevel = self.theIndex.novelIndex[tHandle][sTitle]["level"] tLevel = self.theIndex.novelIndex[tHandle][sTitle]["level"]
tItem = self._createTreeItem(tHandle, sTitle, tLevel, titleKey) tItem = self._createTreeItem(tHandle, sTitle, tLevel, titleKey)
self.treeMap[titleKey] = tItem self._treeMap[titleKey] = tItem
if tLevel == "H1": if tLevel == "H1":
currTitle = tItem currTitle = tItem
@@ -284,7 +288,7 @@ class GuiNovelTree(QTreeWidget):
tItem.setExpanded(True) tItem.setExpanded(True)
self.lastBuild = time() self._lastBuild = time()
return return
+2 -5
View File
@@ -179,11 +179,8 @@ class GuiOutline(QTreeWidget):
# If the novel index has changed since the tree was last built, # If the novel index has changed since the tree was last built,
# we rebuild the tree from the updated index. # we rebuild the tree from the updated index.
lastChange = self.theParent.theIndex.timeNovel idxChanged = self.theParent.theIndex.novelChangedSince(self.lastBuild)
logger.verbose("Last outline build: %.3f" % self.lastBuild) doBuild = idxChanged and self.theProject.autoOutline
logger.verbose("Novel index change: %.3f" % lastChange)
doBuild = lastChange > self.lastBuild and self.theProject.autoOutline
if doBuild or overRide: if doBuild or overRide:
logger.debug("Rebuilding Project Outline") logger.debug("Rebuilding Project Outline")
self._populateTree() self._populateTree()
+49 -43
View File
@@ -29,6 +29,8 @@
import nw import nw
import logging import logging
from time import time
from PyQt5.QtCore import Qt, QSize from PyQt5.QtCore import Qt, QSize
from PyQt5.QtGui import QIcon from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
@@ -60,22 +62,27 @@ class GuiProjectTree(QTreeWidget):
self.theProject = theParent.theProject self.theProject = theParent.theProject
self.theIndex = theParent.theIndex self.theIndex = theParent.theIndex
# Tree Settings # Internal Variables
self.theMap = {} self._treeMap = {}
self.treeChanged = False self._treeChanged = False
self._timeChanged = 0
##
# Build GUI
##
# Context Menu
self.ctxMenu = GuiProjectTreeMenu(self) self.ctxMenu = GuiProjectTreeMenu(self)
self.clearTree() self.setContextMenuPolicy(Qt.CustomContextMenu)
self.customContextMenuRequested.connect(self._rightClickMenu)
# Build GUI # Tree Settings
iPx = self.theTheme.baseIconSize iPx = self.theTheme.baseIconSize
self.setIconSize(QSize(iPx, iPx)) self.setIconSize(QSize(iPx, iPx))
self.setExpandsOnDoubleClick(True) self.setExpandsOnDoubleClick(True)
self.setIndentation(iPx) self.setIndentation(iPx)
self.setColumnCount(4) self.setColumnCount(4)
self.setHeaderLabels(["Label", "Words", "Inc", "Flags"]) self.setHeaderLabels(["Label", "Words", "Inc", "Flags"])
self.setContextMenuPolicy(Qt.CustomContextMenu)
self.customContextMenuRequested.connect(self._rightClickMenu)
treeHeadItem = self.headerItem() treeHeadItem = self.headerItem()
treeHeadItem.setTextAlignment(self.C_COUNT, Qt.AlignRight) treeHeadItem.setTextAlignment(self.C_COUNT, Qt.AlignRight)
@@ -102,7 +109,7 @@ class GuiProjectTree(QTreeWidget):
# Set Multiple Selection by CTRL # Set Multiple Selection by CTRL
# Disabled for now, until the merge files option has been added # Disabled for now, until the merge files option has been added
# self.setSelectionMode(QAbstractItemView.ExtendedSelection) # self.setSelectionMode(QAbstractItemView.ExtendedSelection)
# self.setSelectionBehavior(QAbstractItemView.SelectRows) self.setSelectionBehavior(QAbstractItemView.SelectRows)
# Get user's column width preferences for NAME and COUNT # Get user's column width preferences for NAME and COUNT
treeColWidth = self.mainConf.getTreeColWidths() treeColWidth = self.mainConf.getTreeColWidths()
@@ -116,10 +123,11 @@ class GuiProjectTree(QTreeWidget):
# Set custom settings # Set custom settings
self.initTree() self.initTree()
logger.debug("GuiProjectTree initialisation complete") # Internal Function Mapping
self.makeAlert = self.theParent.makeAlert
self.askQuestion = self.theParent.askQuestion
# Internal Mapping logger.debug("GuiProjectTree initialisation complete")
self.makeAlert = self.theParent.makeAlert
return return
@@ -147,8 +155,9 @@ class GuiProjectTree(QTreeWidget):
"""Clear the GUI content and the related map. """Clear the GUI content and the related map.
""" """
self.clear() self.clear()
self.theMap = {} self._treeMap = {}
self.treeChanged = False self._treeChanged = False
self._timeChanged = 0
return return
def newTreeItem(self, itemType, itemClass): def newTreeItem(self, itemType, itemClass):
@@ -274,8 +283,8 @@ class GuiProjectTree(QTreeWidget):
return False return False
pHandle = nwItem.itemParent pHandle = nwItem.itemParent
if pHandle is not None and pHandle in self.theMap: if pHandle is not None and pHandle in self._treeMap:
self.theMap[pHandle].setExpanded(True) self._treeMap[pHandle].setExpanded(True)
self.clearSelection() self.clearSelection()
trItem.setSelected(True) trItem.setSelected(True)
return True return True
@@ -338,7 +347,7 @@ class GuiProjectTree(QTreeWidget):
"""Calls saveTreeOrder if there are unsaved changes, otherwise """Calls saveTreeOrder if there are unsaved changes, otherwise
does nothing. does nothing.
""" """
if self.treeChanged: if self._treeChanged:
logger.verbose("Flushing project tree to project class") logger.verbose("Flushing project tree to project class")
self.saveTreeOrder() self.saveTreeOrder()
self._setTreeChanged(False) self._setTreeChanged(False)
@@ -391,7 +400,7 @@ class GuiProjectTree(QTreeWidget):
self.makeAlert("The Trash folder is already empty.", nwAlert.INFO) self.makeAlert("The Trash folder is already empty.", nwAlert.INFO)
return False return False
msgYes = self.theParent.askQuestion( msgYes = self.askQuestion(
"Empty Trash", "Permanently delete %d file(s) from Trash?" % nTrash "Empty Trash", "Permanently delete %d file(s) from Trash?" % nTrash
) )
if not msgYes: if not msgYes:
@@ -446,7 +455,7 @@ class GuiProjectTree(QTreeWidget):
# user if they want to permanently delete the file. # user if they want to permanently delete the file.
doPermanent = False doPermanent = False
if not alreadyAsked: if not alreadyAsked:
msgYes = self.theParent.askQuestion( msgYes = self.askQuestion(
"Delete File", "Permanently delete file '%s'?" % nwItemS.itemName "Delete File", "Permanently delete file '%s'?" % nwItemS.itemName
) )
if msgYes: if msgYes:
@@ -474,7 +483,7 @@ class GuiProjectTree(QTreeWidget):
# move it there. # move it there.
doTrash = False doTrash = False
if askForTrash: if askForTrash:
msgYes = self.theParent.askQuestion( msgYes = self.askQuestion(
"Delete File", "Move file '%s' to Trash?" % nwItemS.itemName "Delete File", "Move file '%s' to Trash?" % nwItemS.itemName
) )
if msgYes: if msgYes:
@@ -533,7 +542,9 @@ class GuiProjectTree(QTreeWidget):
return True return True
def setTreeItemValues(self, tHandle): def setTreeItemValues(self, tHandle):
"""Set the name and flag values for a tree item. """Set the name and flag values for a tree item from a handle in
the project tree. Does not trigger a tree change as the data is
already coming from the project tree.
""" """
trItem = self._getTreeItem(tHandle) trItem = self._getTreeItem(tHandle)
nwItem = self.theProject.projTree[tHandle] nwItem = self.theProject.projTree[tHandle]
@@ -622,9 +633,9 @@ class GuiProjectTree(QTreeWidget):
sent first. sent first.
""" """
logger.debug("Building the project tree ...") logger.debug("Building the project tree ...")
self.clear() self.clearTree()
iCount = 0
iCount = 0
for nwItem in self.theProject.getProjectItems(): for nwItem in self.theProject.getProjectItems():
iCount += 1 iCount += 1
self._addTreeItem(nwItem) self._addTreeItem(nwItem)
@@ -642,21 +653,10 @@ class GuiProjectTree(QTreeWidget):
return None return None
def getSelectedHandles(self):
"""Return a list of all currently selected item handles.
"""
selItems = self.selectedItems()
selHandles = []
for n in range(len(selItems)):
if isinstance(selItems[n], QTreeWidgetItem):
selHandles.append(selItems[n].data(self.C_NAME, Qt.UserRole))
return selHandles
def setSelectedHandle(self, tHandle, doScroll=False): def setSelectedHandle(self, tHandle, doScroll=False):
"""Set a specific handle as the selected item. """Set a specific handle as the selected item.
""" """
if tHandle not in self.theMap: if tHandle not in self._treeMap:
return False return False
tItem = self._getTreeItem(tHandle) tItem = self._getTreeItem(tHandle)
@@ -664,7 +664,7 @@ class GuiProjectTree(QTreeWidget):
return False return False
self.clearSelection() self.clearSelection()
self.theMap[tHandle].setSelected(True) self._treeMap[tHandle].setSelected(True)
selItems = self.selectedIndexes() selItems = self.selectedIndexes()
if selItems and doScroll: if selItems and doScroll:
@@ -672,6 +672,11 @@ class GuiProjectTree(QTreeWidget):
return True return True
def changedSince(self, checkTime):
"""Check if the tree has changed since a given time.
"""
return self._timeChanged > checkTime
## ##
# Slots # Slots
## ##
@@ -797,7 +802,7 @@ class GuiProjectTree(QTreeWidget):
def _getTreeItem(self, tHandle): def _getTreeItem(self, tHandle):
"""Returns the QTreeWidgetItem of a given item handle. """Returns the QTreeWidgetItem of a given item handle.
""" """
return self.theMap.get(tHandle, None) return self._treeMap.get(tHandle, None)
def _scanChildren(self, theList, theItem, theIndex): def _scanChildren(self, theList, theItem, theIndex):
"""This is a recursive function returning all items in a tree """This is a recursive function returning all items in a tree
@@ -834,7 +839,7 @@ class GuiProjectTree(QTreeWidget):
newItem.setData(self.C_NAME, Qt.UserRole, tHandle) newItem.setData(self.C_NAME, Qt.UserRole, tHandle)
newItem.setData(self.C_COUNT, Qt.UserRole, 0) newItem.setData(self.C_COUNT, Qt.UserRole, 0)
self.theMap[tHandle] = newItem self._treeMap[tHandle] = newItem
if pHandle is None: if pHandle is None:
if nwItem.itemType == nwItemType.ROOT: if nwItem.itemType == nwItemType.ROOT:
self.addTopLevelItem(newItem) self.addTopLevelItem(newItem)
@@ -845,20 +850,20 @@ class GuiProjectTree(QTreeWidget):
self.makeAlert( self.makeAlert(
"There is nowhere to add item with name '%s'" % nwItem.itemName, nwAlert.ERROR "There is nowhere to add item with name '%s'" % nwItem.itemName, nwAlert.ERROR
) )
del self.theMap[tHandle] del self._treeMap[tHandle]
return None return None
else: else:
byIndex = -1 byIndex = -1
if nHandle is not None and nHandle in self.theMap: if nHandle is not None and nHandle in self._treeMap:
try: try:
byIndex = self.theMap[pHandle].indexOfChild(self.theMap[nHandle]) byIndex = self._treeMap[pHandle].indexOfChild(self._treeMap[nHandle])
except Exception: except Exception:
logger.error("Failed to get index of item with handle %s" % nHandle) logger.error("Failed to get index of item with handle %s" % nHandle)
if byIndex >= 0: if byIndex >= 0:
self.theMap[pHandle].insertChild(byIndex+1, newItem) self._treeMap[pHandle].insertChild(byIndex+1, newItem)
else: else:
self.theMap[pHandle].addChild(newItem) self._treeMap[pHandle].addChild(newItem)
self.propagateCount(tHandle, nwItem.wordCount) self.propagateCount(tHandle, nwItem.wordCount)
self.setTreeItemValues(tHandle) self.setTreeItemValues(tHandle)
@@ -919,8 +924,9 @@ class GuiProjectTree(QTreeWidget):
def _setTreeChanged(self, theState): def _setTreeChanged(self, theState):
"""Set the tree change flag, and propagate to the project. """Set the tree change flag, and propagate to the project.
""" """
self.treeChanged = theState self._treeChanged = theState
if theState: if theState:
self._timeChanged = time()
self.theProject.setProjectChanged(True) self.theProject.setProjectChanged(True)
return return
-2
View File
@@ -761,9 +761,7 @@ class GuiMain(QMainWindow):
""" """
self._makeStatusIcons() self._makeStatusIcons()
self._makeImportIcons() self._makeImportIcons()
self.treeView.clearTree()
self.treeView.buildTree() self.treeView.buildTree()
self.novelView.clearTree()
self.novelView.refreshTree() self.novelView.refreshTree()
return return
+8
View File
@@ -205,6 +205,10 @@ def testCoreIndex_CheckThese(nwMinimal, dummyGUI):
nItem = theProject.projTree[nHandle] nItem = theProject.projTree[nHandle]
cItem = theProject.projTree[cHandle] cItem = theProject.projTree[cHandle]
assert not theIndex.novelChangedSince(0)
assert not theIndex.notesChangedSince(0)
assert not theIndex.indexChangedSince(0)
assert theIndex.scanText(cHandle, ( assert theIndex.scanText(cHandle, (
"# Jane Smith\n" "# Jane Smith\n"
"@tag: Jane" "@tag: Jane"
@@ -216,6 +220,10 @@ def testCoreIndex_CheckThese(nwMinimal, dummyGUI):
assert theIndex.tagIndex == {"Jane": [2, cHandle, "CHARACTER", "T000001"]} assert theIndex.tagIndex == {"Jane": [2, cHandle, "CHARACTER", "T000001"]}
assert theIndex.novelIndex[nHandle]["T000001"]["title"] == "Hello World!" assert theIndex.novelIndex[nHandle]["T000001"]["title"] == "Hello World!"
assert theIndex.novelChangedSince(0)
assert theIndex.notesChangedSince(0)
assert theIndex.indexChangedSince(0)
assert theIndex.checkThese([], cItem) == [] assert theIndex.checkThese([], cItem) == []
assert theIndex.checkThese(["@tag", "Jane"], cItem) == [True, True] assert theIndex.checkThese(["@tag", "Jane"], cItem) == [True, True]
assert theIndex.checkThese(["@tag", "John"], cItem) == [True, True] assert theIndex.checkThese(["@tag", "John"], cItem) == [True, True]