Streamline how project item changes are reported

This commit is contained in:
Veronica Berglyd Olsen
2022-06-05 17:25:39 +02:00
parent 2873336dce
commit 88976d6800
9 changed files with 122 additions and 123 deletions
+16 -11
View File
@@ -569,16 +569,6 @@ class GuiDocEditor(QTextEdit):
return
def updateDocInfo(self, tHandle):
"""Called when an item label is changed to check if the document
title bar needs updating,
"""
if tHandle == self._docHandle:
self.docHeader.setTitleFromHandle(self._docHandle)
self.docFooter.updateInfo()
self.updateDocMargins()
return
##
# Properties
##
@@ -1068,7 +1058,22 @@ class GuiDocEditor(QTextEdit):
return
##
# Slots
# Public Slots
##
@pyqtSlot(str)
def updateDocInfo(self, tHandle):
"""Called when an item label is changed to check if the document
title bar needs updating,
"""
if tHandle == self._docHandle:
self.docHeader.setTitleFromHandle(self._docHandle)
self.docFooter.updateInfo()
self.updateDocMargins()
return
##
# Private Slots
##
@pyqtSlot(int, int, int)
+15 -10
View File
@@ -322,15 +322,6 @@ class GuiDocViewer(QTextBrowser):
return
def updateDocInfo(self, tHandle):
"""Called when an item label is changed to check if the document
title bar needs updating,
"""
if tHandle == self._docHandle:
self.docHeader.setTitleFromHandle(self._docHandle)
self.updateDocMargins()
return
##
# Properties
##
@@ -389,7 +380,21 @@ class GuiDocViewer(QTextBrowser):
return 0
##
# Slots
# Public Slots
##
@pyqtSlot(str)
def updateDocInfo(self, tHandle):
"""Called when an item label is changed to check if the document
title bar needs updating,
"""
if tHandle == self._docHandle:
self.docHeader.setTitleFromHandle(self._docHandle)
self.updateDocMargins()
return
##
# Private Slots
##
@pyqtSlot("QUrl")
+5 -4
View File
@@ -220,6 +220,11 @@ class GuiItemDetails(QWidget):
"""
self.updateViewBox(self._itemHandle)
##
# Public Slots
##
@pyqtSlot(str)
def updateViewBox(self, tHandle):
"""Populate the details box from a given handle.
"""
@@ -290,10 +295,6 @@ class GuiItemDetails(QWidget):
return
##
# Slots
##
@pyqtSlot(str, int, int, int)
def doUpdateCounts(self, tHandle, cC, wC, pC):
"""Update the counts if the handle is the same as the one we're
-1
View File
@@ -146,7 +146,6 @@ class GuiNovelTree(QTreeWidget):
if selItem:
titleKey = selItem[0].data(self.C_TITLE, Qt.UserRole)[2]
self.theParent.treeView.flushTreeOrder()
self._populateTree()
if titleKey is not None and titleKey in self._treeMap:
+8 -4
View File
@@ -126,17 +126,21 @@ class GuiOutline(QWidget):
return self.outlineView.setFocus()
##
# Slots
# Public Slots
##
@pyqtSlot()
def projectUpdated(self):
"""Should be called whenever the number of root folders change.
@pyqtSlot(str)
def updateRootItem(self, tHandle):
"""Should be called whenever a root folders changes.
"""
self.outlineBar.populateNovelList()
self.outlineData.updateClasses()
return
##
# Private Slots
##
@pyqtSlot()
def _updateMenuColumns(self):
"""Trigger an update of the toggled state of the column menu
+67 -57
View File
@@ -32,11 +32,13 @@ from time import time
from PyQt5.QtCore import Qt, QSize, pyqtSignal, pyqtSlot
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import (
QTreeWidget, QTreeWidgetItem, QAbstractItemView, QMenu, QAction, QFrame
QTreeWidget, QTreeWidgetItem, QAbstractItemView, QMenu, QAction, QFrame,
QDialog
)
from novelwriter.core import NWDoc
from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout, nwAlert
from novelwriter.dialogs.itemeditor import GuiItemEditor
logger = logging.getLogger(__name__)
@@ -48,10 +50,10 @@ class GuiProjectTree(QTreeWidget):
C_EXPORT = 2
C_STATUS = 3
novelItemChanged = pyqtSignal()
noteItemChanged = pyqtSignal()
treeItemChanged = pyqtSignal(str)
novelItemChanged = pyqtSignal(str)
rootFolderChanged = pyqtSignal(str)
wordCountsChanged = pyqtSignal()
rootFoldersChanged = pyqtSignal()
def __init__(self, theParent):
QTreeWidget.__init__(self, theParent)
@@ -64,10 +66,9 @@ class GuiProjectTree(QTreeWidget):
self.theProject = theParent.theProject
# Internal Variables
self._treeMap = {}
self._treeChanged = False
self._treeMap = {}
self._lastMove = {}
self._timeChanged = 0
self._lastMove = {}
##
# Build GUI
@@ -157,15 +158,15 @@ class GuiProjectTree(QTreeWidget):
"""
self.clear()
self._treeMap = {}
self._treeChanged = False
self._lastMove = {}
self._timeChanged = 0
return
def newTreeItem(self, itemType, itemClass=None):
"""Add new item to the tree, with a given itemType (and
itemClass if Root), and attach it to the selected handle. Also make
sure the item is added in a place it can be added, and that other
meta data is set correctly to ensure a valid project tree.
itemClass if Root), and attach it to the selected handle. Also
make sure the item is added in a place it can be added, and that
other meta data is set correctly to ensure a valid project tree.
"""
if not self.theParent.hasProject:
logger.error("No project open")
@@ -177,7 +178,6 @@ class GuiProjectTree(QTreeWidget):
if itemType == nwItemType.ROOT and isinstance(itemClass, nwItemClass):
tHandle = self.theProject.newRoot(itemClass)
self.rootFoldersChanged.emit()
elif itemType in (nwItemType.FILE, nwItemType.FOLDER):
@@ -223,9 +223,9 @@ class GuiProjectTree(QTreeWidget):
# Add the new item to the tree
self.revealNewTreeItem(tHandle, nHandle)
self.theParent.editItem(tHandle)
nwItem = self.theProject.tree[tHandle]
# If this is a folder, return here
# Handle new file creation
nwItem = self.theProject.tree[tHandle]
if nwItem.itemType != nwItemType.FILE:
return True
@@ -268,7 +268,7 @@ class GuiProjectTree(QTreeWidget):
if pHandle is not None and pHandle in self._treeMap:
self._treeMap[pHandle].setExpanded(True)
self._emitItemChange(tHandle)
self._alertTreeChange(tHandle=tHandle, flush=True)
self.clearSelection()
trItem.setSelected(True)
@@ -310,10 +310,31 @@ class GuiProjectTree(QTreeWidget):
pItem.insertChild(nIndex, cItem)
self._recordLastMove(cItem, pItem, tIndex)
self._alertTreeChange(tHandle=tHandle, flush=True)
self.clearSelection()
cItem.setSelected(True)
self._setTreeChanged(True)
self._emitItemChange(tHandle)
return True
def editTreeItem(self, tHandle=None):
"""Open the edit item dialog.
"""
if tHandle is None:
logger.warning("No item selected")
return False
tItem = self.theProject.tree[tHandle]
if tItem is None:
return False
if tItem.itemType == nwItemType.NO_TYPE:
return False
logger.verbose("Requesting change to item '%s'", tHandle)
dlgProj = GuiItemEditor(self, tHandle)
dlgProj.exec_()
if dlgProj.result() == QDialog.Accepted:
self.setTreeItemValues(tHandle)
self._alertTreeChange(tHandle=tHandle, flush=False)
return True
@@ -330,16 +351,6 @@ class GuiProjectTree(QTreeWidget):
self.theProject.setTreeOrder(theList)
return True
def flushTreeOrder(self):
"""Calls saveTreeOrder if there are unsaved changes, otherwise
does nothing.
"""
if self._treeChanged:
logger.verbose("Flushing project tree to project class")
self.saveTreeOrder()
self._setTreeChanged(False)
return
def getTreeFromHandle(self, tHandle):
"""Recursively return all the children items starting from a
given item handle.
@@ -411,7 +422,7 @@ class GuiProjectTree(QTreeWidget):
self.deleteItem(tHandle, alreadyAsked=True, bulkAction=True)
if nTrash > 0:
self._setTreeChanged(True)
self._alertTreeChange(tHandle=trashHandle, flush=True)
return True
@@ -445,6 +456,7 @@ class GuiProjectTree(QTreeWidget):
return False
wCount = self._getItemWordCount(tHandle)
autoFlush = not bulkAction
if nwItemS.itemType == nwItemType.ROOT:
# Only an empty ROOT folder can be deleted
logger.debug("User requested a root folder '%s' deleted", tHandle)
@@ -452,8 +464,7 @@ class GuiProjectTree(QTreeWidget):
if trItemS.childCount() == 0:
self.takeTopLevelItem(tIndex)
self._deleteTreeItem(tHandle)
self._setTreeChanged(True)
self.rootFoldersChanged.emit()
self._alertTreeChange(tHandle=tHandle, flush=True)
else:
self.theParent.makeAlert(self.tr(
"Cannot delete root folder. It is not empty. "
@@ -469,7 +480,7 @@ class GuiProjectTree(QTreeWidget):
tIndex = trItemP.indexOfChild(trItemS)
trItemP.takeChild(tIndex)
self._deleteTreeItem(tHandle)
self._setTreeChanged(True)
self._alertTreeChange(tHandle=tHandle, flush=autoFlush)
else:
# A populated FOLDER or a FILE requires confirmtation
@@ -505,7 +516,7 @@ class GuiProjectTree(QTreeWidget):
self.theParent.closeDocument()
self._deleteTreeItem(dHandle)
self._setTreeChanged(True)
self._alertTreeChange(tHandle=tHandle, flush=autoFlush)
self.wordCountsChanged.emit()
else:
@@ -524,7 +535,7 @@ class GuiProjectTree(QTreeWidget):
trItemT.addChild(trItemC)
self._postItemMove(tHandle, wCount)
self._recordLastMove(trItemS, trItemP, tIndex)
self._setTreeChanged(True)
self._alertTreeChange(tHandle=tHandle, flush=autoFlush)
return True
@@ -660,6 +671,7 @@ class GuiProjectTree(QTreeWidget):
dstItem.insertChild(dstIndex, movItem)
self._postItemMove(sHandle, wCount)
self._alertTreeChange(tHandle=sHandle, flush=True)
self.clearSelection()
movItem.setSelected(True)
@@ -788,6 +800,7 @@ class GuiProjectTree(QTreeWidget):
QTreeWidget.dropEvent(self, theEvent)
self._postItemMove(sHandle, wCount)
self._recordLastMove(sItem, pItem, pIndex)
self._alertTreeChange(tHandle=sHandle, flush=True)
sItem.setExpanded(isExpanded)
return
@@ -829,8 +842,6 @@ class GuiProjectTree(QTreeWidget):
# Trigger dependent updates
self.propagateCount(tHandle, wCount)
self._setTreeChanged(True)
self._emitItemChange(tHandle)
return True
@@ -931,8 +942,6 @@ class GuiProjectTree(QTreeWidget):
self.setTreeItemValues(tHandle)
newItem.setExpanded(nwItem.isExpanded)
self._setTreeChanged(True)
return newItem
def _addTrashRoot(self):
@@ -945,33 +954,34 @@ class GuiProjectTree(QTreeWidget):
trItem = self._getTreeItem(trashHandle)
if trItem is None:
trItem = self._addTreeItem(
self.theProject.tree[trashHandle]
)
trItem = self._addTreeItem(self.theProject.tree[trashHandle])
if trItem is not None:
trItem.setExpanded(True)
self._setTreeChanged(True)
self._alertTreeChange(tHandle=trashHandle, flush=True)
return trItem
def _setTreeChanged(self, theState):
"""Set the tree change flag, and propagate to the project.
def _alertTreeChange(self, tHandle=None, flush=True):
"""Update information on tree change state, and emit necessary
signals.
"""
self._treeChanged = theState
if theState:
self._timeChanged = time()
self.theProject.setProjectChanged(True)
return
self._timeChanged = time()
self.theProject.setProjectChanged(True)
if flush:
self.saveTreeOrder()
tItem = self.theProject.tree[tHandle]
if tItem is None:
return
itemType = tItem.itemType
if itemType == nwItemType.ROOT:
self.rootFolderChanged.emit(tHandle)
elif itemType == nwItemType.FILE and tItem.isNovelLike():
self.novelItemChanged.emit(tHandle)
self.treeItemChanged.emit(tHandle)
def _emitItemChange(self, tHandle):
"""Emit an item change signal for a given handle.
"""
if self.theProject.tree.checkType(tHandle, nwItemType.FILE):
nwItem = self.theProject.tree[tHandle]
if nwItem.isNovelLike():
self.novelItemChanged.emit()
else:
self.noteItemChanged.emit()
return
def _recordLastMove(self, srcItem, parItem, parIndex):
+11 -30
View File
@@ -44,9 +44,8 @@ from novelwriter.gui import (
GuiViewsBar
)
from novelwriter.dialogs import (
GuiAbout, GuiDocMerge, GuiDocSplit, GuiItemEditor, GuiPreferences,
GuiProjectDetails, GuiProjectLoad, GuiProjectSettings, GuiUpdates,
GuiWordList
GuiAbout, GuiDocMerge, GuiDocSplit, GuiPreferences, GuiProjectDetails,
GuiProjectLoad, GuiProjectSettings, GuiUpdates, GuiWordList
)
from novelwriter.tools import (
GuiBuildNovel, GuiLipsum, GuiProjectWizard, GuiWritingStats
@@ -123,7 +122,10 @@ class GuiMain(QMainWindow):
self.treeView.itemDoubleClicked.connect(self._treeDoubleClick)
self.treeView.novelItemChanged.connect(self._treeNovelItemChanged)
self.treeView.wordCountsChanged.connect(self._updateStatusWordCount)
self.treeView.rootFoldersChanged.connect(self.projView.projectUpdated)
self.treeView.treeItemChanged.connect(self.docEditor.updateDocInfo)
self.treeView.treeItemChanged.connect(self.docViewer.updateDocInfo)
self.treeView.treeItemChanged.connect(self.treeMeta.updateViewBox)
self.treeView.rootFolderChanged.connect(self.projView.updateRootItem)
self.docEditor.spellDictionaryChanged.connect(self.statusBar.setLanguage)
self.docEditor.docEditedStatusChanged.connect(self.statusBar.doUpdateDocumentStatus)
@@ -357,7 +359,7 @@ class GuiMain(QMainWindow):
self.rebuildTrees()
self.saveProject()
self.docEditor.setDictionaries()
self.projView.projectUpdated()
self.projView.updateRootItem(None)
self.rebuildIndex(beQuiet=True)
self.statusBar.setRefTime(self.theProject.projOpened)
self.statusBar.setProjectStatus(nwState.GOOD)
@@ -504,7 +506,7 @@ class GuiMain(QMainWindow):
self.docEditor.setDictionaries()
self.docEditor.toggleSpellCheck(self.theProject.spellCheck)
self.statusBar.setRefTime(self.theProject.projOpened)
self.projView.projectUpdated()
self.projView.updateRootItem(None)
self._updateStatusWordCount()
# Restore previously open documents, if any
@@ -596,7 +598,6 @@ class GuiMain(QMainWindow):
logger.error("No project open")
return False
self.treeView.flushTreeOrder()
nHandle = None # The next handle after tHandle
fHandle = None # The first file handle we encounter
foundIt = False # We've found tHandle, pick the next we see
@@ -813,27 +814,10 @@ class GuiMain(QMainWindow):
tHandle = self.docEditor.docHandle()
else:
tHandle = self.treeView.getSelectedHandle()
if tHandle:
return self.treeView.editTreeItem(tHandle)
if tHandle is None:
logger.warning("No item selected")
return False
tItem = self.theProject.tree[tHandle]
if tItem is None:
return False
if tItem.itemType == nwItemType.NO_TYPE:
return False
logger.verbose("Requesting change to item '%s'", tHandle)
dlgProj = GuiItemEditor(self, tHandle)
dlgProj.exec_()
if dlgProj.result() == QDialog.Accepted:
self.treeView.setTreeItemValues(tHandle)
self.treeMeta.updateViewBox(tHandle)
self.docEditor.updateDocInfo(tHandle)
self.docViewer.updateDocInfo(tHandle)
return True
return False
def rebuildTrees(self):
"""Rebuild the project tree.
@@ -966,8 +950,6 @@ class GuiMain(QMainWindow):
logger.error("No project open")
return False
self.treeView.flushTreeOrder()
dlgDetails = getGuiItem("GuiProjectDetails")
if dlgDetails is None:
dlgDetails = GuiProjectDetails(self)
@@ -1583,7 +1565,6 @@ class GuiMain(QMainWindow):
if self.mainStack.currentIndex() == self.idxOutlineView:
logger.verbose("Novel tree changed while Outline tab active")
if self.hasProject:
self.treeView.flushTreeOrder()
self.projView.refreshView(novelChanged=True)
return
-1
View File
@@ -709,7 +709,6 @@ class GuiBuildNovel(QDialog):
bldObj.initDocument()
# Make sure the project and document is up to date
self.theParent.treeView.flushTreeOrder()
self.theParent.saveDocument()
self.buildProgress.setMaximum(len(self.theProject.tree))
-5
View File
@@ -246,17 +246,14 @@ def testGuiProjTree_MoveItems(qtbot, monkeypatch, nwGUI, fncDir, mockRnd):
# Move novel folder up
assert nwTree.moveTreeItem(-1) is False
nwTree.flushTreeOrder()
assert nwGUI.theProject.tree._treeOrder.index("0000000000008") == 0
# Move novel folder down
assert nwTree.moveTreeItem(1) is True
nwTree.flushTreeOrder()
assert nwGUI.theProject.tree._treeOrder.index("0000000000008") == 1
# Move novel folder up again
assert nwTree.moveTreeItem(-1) is True
nwTree.flushTreeOrder()
assert nwGUI.theProject.tree._treeOrder.index("0000000000008") == 0
# Clean up
@@ -432,10 +429,8 @@ def testGuiProjTree_DeleteItems(qtbot, caplog, monkeypatch, nwGUI, fncDir, mockR
assert nwTree.emptyTrash() is False
# Empty the trash proper
nwTree._setTreeChanged(False)
assert nwTree.emptyTrash() is True
assert nwTree.getTreeFromHandle(trashHandle) == [trashHandle]
assert nwTree._treeChanged is True
# Try to delete a file, but block the underlying deletion of the file on disk
assert os.path.isfile(os.path.join(fncDir, "project", "content", "000000000000e.nwd"))