Add test coverage of the gui project tree
This commit is contained in:
@@ -240,6 +240,14 @@ class ProjectNode:
|
|||||||
self._refreshChildrenPos()
|
self._refreshChildrenPos()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def setExpanded(self, state: bool) -> None:
|
||||||
|
"""Set the node's expanded state."""
|
||||||
|
if state and self._children:
|
||||||
|
self._item.setExpanded(True)
|
||||||
|
else:
|
||||||
|
self._item.setExpanded(False)
|
||||||
|
return
|
||||||
|
|
||||||
##
|
##
|
||||||
# Internal Functions
|
# Internal Functions
|
||||||
##
|
##
|
||||||
|
|||||||
+29
-21
@@ -101,22 +101,22 @@ class GuiProjectView(QWidget):
|
|||||||
self.keyGoPrev = QShortcut(self.projTree)
|
self.keyGoPrev = QShortcut(self.projTree)
|
||||||
self.keyGoPrev.setKey("Alt+Up")
|
self.keyGoPrev.setKey("Alt+Up")
|
||||||
self.keyGoPrev.setContext(Qt.ShortcutContext.WidgetShortcut)
|
self.keyGoPrev.setContext(Qt.ShortcutContext.WidgetShortcut)
|
||||||
self.keyGoPrev.activated.connect(self.projTree.moveSiblingUp)
|
self.keyGoPrev.activated.connect(self.projTree.goToSiblingUp)
|
||||||
|
|
||||||
self.keyGoNext = QShortcut(self.projTree)
|
self.keyGoNext = QShortcut(self.projTree)
|
||||||
self.keyGoNext.setKey("Alt+Down")
|
self.keyGoNext.setKey("Alt+Down")
|
||||||
self.keyGoNext.setContext(Qt.ShortcutContext.WidgetShortcut)
|
self.keyGoNext.setContext(Qt.ShortcutContext.WidgetShortcut)
|
||||||
self.keyGoNext.activated.connect(self.projTree.moveSiblingDown)
|
self.keyGoNext.activated.connect(self.projTree.goToSiblingDown)
|
||||||
|
|
||||||
self.keyGoUp = QShortcut(self.projTree)
|
self.keyGoUp = QShortcut(self.projTree)
|
||||||
self.keyGoUp.setKey("Alt+Left")
|
self.keyGoUp.setKey("Alt+Left")
|
||||||
self.keyGoUp.setContext(Qt.ShortcutContext.WidgetShortcut)
|
self.keyGoUp.setContext(Qt.ShortcutContext.WidgetShortcut)
|
||||||
self.keyGoUp.activated.connect(self.projTree.moveToParent)
|
self.keyGoUp.activated.connect(self.projTree.goToParent)
|
||||||
|
|
||||||
self.keyGoDown = QShortcut(self.projTree)
|
self.keyGoDown = QShortcut(self.projTree)
|
||||||
self.keyGoDown.setKey("Alt+Right")
|
self.keyGoDown.setKey("Alt+Right")
|
||||||
self.keyGoDown.setContext(Qt.ShortcutContext.WidgetShortcut)
|
self.keyGoDown.setContext(Qt.ShortcutContext.WidgetShortcut)
|
||||||
self.keyGoDown.activated.connect(self.projTree.moveToFirstChild)
|
self.keyGoDown.activated.connect(self.projTree.goToFirstChild)
|
||||||
|
|
||||||
self.keyContext = QShortcut(self.projTree)
|
self.keyContext = QShortcut(self.projTree)
|
||||||
self.keyContext.setKey("Ctrl+.")
|
self.keyContext.setKey("Ctrl+.")
|
||||||
@@ -415,8 +415,6 @@ class GuiProjectToolBar(QWidget):
|
|||||||
self.mTemplates.addUpdate(tHandle, item.itemName, item.getMainIcon())
|
self.mTemplates.addUpdate(tHandle, item.itemName, item.getMainIcon())
|
||||||
elif tHandle in self.mTemplates:
|
elif tHandle in self.mTemplates:
|
||||||
self.mTemplates.remove(tHandle)
|
self.mTemplates.remove(tHandle)
|
||||||
elif change == nwChange.DELETE and tHandle in self.mTemplates:
|
|
||||||
self.mTemplates.remove(tHandle)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
##
|
##
|
||||||
@@ -680,7 +678,8 @@ class GuiProjectTree(QTreeView):
|
|||||||
tHandle = SHARED.project.newFolder(newLabel, sHandle, pos)
|
tHandle = SHARED.project.newFolder(newLabel, sHandle, pos)
|
||||||
|
|
||||||
# Select the new item automatically
|
# Select the new item automatically
|
||||||
self.setSelectedHandle(tHandle)
|
if tHandle:
|
||||||
|
self.setSelectedHandle(tHandle)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -793,6 +792,7 @@ class GuiProjectTree(QTreeView):
|
|||||||
dHandles = docDup.duplicate(itemTree)
|
dHandles = docDup.duplicate(itemTree)
|
||||||
if len(dHandles) != len(itemTree):
|
if len(dHandles) != len(itemTree):
|
||||||
SHARED.warn(self.tr("Could not duplicate all items."))
|
SHARED.warn(self.tr("Could not duplicate all items."))
|
||||||
|
self.restoreExpandedState()
|
||||||
return
|
return
|
||||||
|
|
||||||
##
|
##
|
||||||
@@ -807,7 +807,7 @@ class GuiProjectTree(QTreeView):
|
|||||||
super().mousePressEvent(event)
|
super().mousePressEvent(event)
|
||||||
if event.button() == QtMouseLeft:
|
if event.button() == QtMouseLeft:
|
||||||
if not self.indexAt(event.pos()).isValid():
|
if not self.indexAt(event.pos()).isValid():
|
||||||
self.selectionModel().clearCurrentIndex()
|
self._clearSelection()
|
||||||
elif event.button() == QtMouseMiddle:
|
elif event.button() == QtMouseMiddle:
|
||||||
if (node := self._getNode(self.indexAt(event.pos()))) and node.item.isFileType():
|
if (node := self._getNode(self.indexAt(event.pos()))) and node.item.isFileType():
|
||||||
self.projView.openDocumentRequest.emit(
|
self.projView.openDocumentRequest.emit(
|
||||||
@@ -841,7 +841,7 @@ class GuiProjectTree(QTreeView):
|
|||||||
return
|
return
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def moveSiblingUp(self) -> None:
|
def goToSiblingUp(self) -> None:
|
||||||
"""Skip to the previous sibling."""
|
"""Skip to the previous sibling."""
|
||||||
if (node := self._getNode(self.currentIndex())) and (parent := node.parent()):
|
if (node := self._getNode(self.currentIndex())) and (parent := node.parent()):
|
||||||
if (move := parent.child(node.row() - 1)) and (model := self._getModel()):
|
if (move := parent.child(node.row() - 1)) and (model := self._getModel()):
|
||||||
@@ -849,7 +849,7 @@ class GuiProjectTree(QTreeView):
|
|||||||
return
|
return
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def moveSiblingDown(self) -> None:
|
def goToSiblingDown(self) -> None:
|
||||||
"""Skip to the next sibling."""
|
"""Skip to the next sibling."""
|
||||||
if (node := self._getNode(self.currentIndex())) and (parent := node.parent()):
|
if (node := self._getNode(self.currentIndex())) and (parent := node.parent()):
|
||||||
if (move := parent.child(node.row() + 1)) and (model := self._getModel()):
|
if (move := parent.child(node.row() + 1)) and (model := self._getModel()):
|
||||||
@@ -857,7 +857,7 @@ class GuiProjectTree(QTreeView):
|
|||||||
return
|
return
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def moveToParent(self) -> None:
|
def goToParent(self) -> None:
|
||||||
"""Move to parent item."""
|
"""Move to parent item."""
|
||||||
if (
|
if (
|
||||||
(model := self._getModel())
|
(model := self._getModel())
|
||||||
@@ -868,7 +868,7 @@ class GuiProjectTree(QTreeView):
|
|||||||
return
|
return
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def moveToFirstChild(self) -> None:
|
def goToFirstChild(self) -> None:
|
||||||
"""Move to first child item."""
|
"""Move to first child item."""
|
||||||
if (
|
if (
|
||||||
(model := self._getModel())
|
(model := self._getModel())
|
||||||
@@ -893,7 +893,9 @@ class GuiProjectTree(QTreeView):
|
|||||||
return
|
return
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def processDeleteRequest(self, handles: list[str] = [], askFirst: bool = True) -> None:
|
def processDeleteRequest(
|
||||||
|
self, handles: list[str] | None = None, askFirst: bool = True
|
||||||
|
) -> None:
|
||||||
"""Move selected items to Trash."""
|
"""Move selected items to Trash."""
|
||||||
if handles and (model := self._getModel()):
|
if handles and (model := self._getModel()):
|
||||||
indices = [model.indexFromHandle(handle) for handle in handles]
|
indices = [model.indexFromHandle(handle) for handle in handles]
|
||||||
@@ -1002,20 +1004,26 @@ class GuiProjectTree(QTreeView):
|
|||||||
def _onNodeCollapsed(self, index: QModelIndex) -> None:
|
def _onNodeCollapsed(self, index: QModelIndex) -> None:
|
||||||
"""Capture a node collapse, and pass it to the model."""
|
"""Capture a node collapse, and pass it to the model."""
|
||||||
if node := self._getNode(index):
|
if node := self._getNode(index):
|
||||||
node.item.setExpanded(False)
|
node.setExpanded(False)
|
||||||
return
|
return
|
||||||
|
|
||||||
@pyqtSlot(QModelIndex)
|
@pyqtSlot(QModelIndex)
|
||||||
def _onNodeExpanded(self, index: QModelIndex) -> None:
|
def _onNodeExpanded(self, index: QModelIndex) -> None:
|
||||||
"""Capture a node expand, and pass it to the model."""
|
"""Capture a node expand, and pass it to the model."""
|
||||||
if node := self._getNode(index):
|
if node := self._getNode(index):
|
||||||
node.item.setExpanded(True)
|
node.setExpanded(True)
|
||||||
return
|
return
|
||||||
|
|
||||||
##
|
##
|
||||||
# Internal Functions
|
# Internal Functions
|
||||||
##
|
##
|
||||||
|
|
||||||
|
def _clearSelection(self) -> None:
|
||||||
|
"""Clear the currently selected items."""
|
||||||
|
self.clearSelection()
|
||||||
|
self.selectionModel().clearCurrentIndex()
|
||||||
|
return
|
||||||
|
|
||||||
def _selectedRows(self) -> list[QModelIndex]:
|
def _selectedRows(self) -> list[QModelIndex]:
|
||||||
"""Return all column 0 indexes."""
|
"""Return all column 0 indexes."""
|
||||||
return [i for i in self.selectedIndexes() if i.column() == 0]
|
return [i for i in self.selectedIndexes() if i.column() == 0]
|
||||||
@@ -1162,9 +1170,8 @@ class _TreeContextMenu(QMenu):
|
|||||||
# Process Item
|
# Process Item
|
||||||
if self._children:
|
if self._children:
|
||||||
self._expandCollapse()
|
self._expandCollapse()
|
||||||
if isFile:
|
action = self.addAction(self.tr("Duplicate"))
|
||||||
action = self.addAction(self.tr("Duplicate"))
|
action.triggered.connect(qtLambda(self._tree.duplicateFromHandle, self._handle))
|
||||||
action.triggered.connect(qtLambda(self._tree.duplicateFromHandle, self._handle))
|
|
||||||
self._deleteOrTrash()
|
self._deleteOrTrash()
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -1321,9 +1328,10 @@ class _TreeContextMenu(QMenu):
|
|||||||
|
|
||||||
def _deleteOrTrash(self) -> None:
|
def _deleteOrTrash(self) -> None:
|
||||||
"""Add move to Trash action."""
|
"""Add move to Trash action."""
|
||||||
if self._model.trashSelection(self._indices):
|
if (
|
||||||
text = self.tr("Delete Permanently")
|
self._model.trashSelection(self._indices)
|
||||||
elif len(self._indices) == 1 and self._item.isRootType():
|
or len(self._indices) == 1 and self._item.isRootType()
|
||||||
|
):
|
||||||
text = self.tr("Delete Permanently")
|
text = self.tr("Delete Permanently")
|
||||||
else:
|
else:
|
||||||
text = self.tr("Move to Trash")
|
text = self.tr("Move to Trash")
|
||||||
|
|||||||
@@ -419,8 +419,7 @@ class _FilterTab(NFixedPage):
|
|||||||
logger.debug("Building project tree")
|
logger.debug("Building project tree")
|
||||||
self._treeMap = {}
|
self._treeMap = {}
|
||||||
self.optTree.clear()
|
self.optTree.clear()
|
||||||
for node in SHARED.project.tree.model.root.allChildren():
|
for nwItem in SHARED.project.tree:
|
||||||
nwItem = node.item
|
|
||||||
tHandle = nwItem.itemHandle
|
tHandle = nwItem.itemHandle
|
||||||
pHandle = nwItem.itemParent
|
pHandle = nwItem.itemParent
|
||||||
rHandle = nwItem.itemRoot
|
rHandle = nwItem.itemRoot
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
<name status="s000000" import="i000004">Novel</name>
|
<name status="s000000" import="i000004">Novel</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="000000000000c" parent="0000000000008" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="000000000000c" parent="0000000000008" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta expanded="yes" heading="H1" charCount="20" wordCount="5" paraCount="1" cursorPos="0" />
|
<meta expanded="no" heading="H1" charCount="20" wordCount="5" paraCount="1" cursorPos="0" />
|
||||||
<name status="s000000" import="i000004" active="yes">Title Page</name>
|
<name status="s000000" import="i000004" active="yes">Title Page</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="000000000000d" parent="0000000000008" root="0000000000008" order="1" type="FOLDER" class="NOVEL">
|
<item handle="000000000000d" parent="0000000000008" root="0000000000008" order="1" type="FOLDER" class="NOVEL">
|
||||||
@@ -42,39 +42,39 @@
|
|||||||
<name status="s000000" import="i000004">New Folder</name>
|
<name status="s000000" import="i000004">New Folder</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="000000000000e" parent="000000000000d" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="000000000000e" parent="000000000000d" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta expanded="yes" heading="H2" charCount="11" wordCount="2" paraCount="0" cursorPos="0" />
|
<meta expanded="no" heading="H2" charCount="11" wordCount="2" paraCount="0" cursorPos="0" />
|
||||||
<name status="s000000" import="i000004" active="yes">New Chapter</name>
|
<name status="s000000" import="i000004" active="yes">New Chapter</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="000000000000f" parent="000000000000d" root="0000000000008" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="000000000000f" parent="000000000000d" root="0000000000008" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta expanded="yes" heading="H1" charCount="1003" wordCount="172" paraCount="17" cursorPos="1259" />
|
<meta expanded="no" heading="H1" charCount="1003" wordCount="172" paraCount="17" cursorPos="1259" />
|
||||||
<name status="s000000" import="i000004" active="yes">New Scene</name>
|
<name status="s000000" import="i000004" active="yes">New Scene</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="0000000000009" parent="None" root="0000000000009" order="1" type="ROOT" class="PLOT">
|
<item handle="0000000000009" parent="None" root="0000000000009" order="1" type="ROOT" class="PLOT">
|
||||||
<meta expanded="yes" />
|
<meta expanded="no" />
|
||||||
<name status="s000000" import="i000004">Plot</name>
|
<name status="s000000" import="i000004">Plot</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="0000000000012" parent="0000000000009" root="0000000000009" order="0" type="FILE" class="PLOT" layout="NOTE">
|
<item handle="0000000000012" parent="0000000000009" root="0000000000009" order="0" type="FILE" class="PLOT" layout="NOTE">
|
||||||
<meta expanded="yes" heading="H1" charCount="48" wordCount="10" paraCount="1" cursorPos="76" />
|
<meta expanded="no" heading="H1" charCount="48" wordCount="10" paraCount="1" cursorPos="76" />
|
||||||
<name status="s000000" import="i000004" active="yes">New Note</name>
|
<name status="s000000" import="i000004" active="yes">New Note</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="000000000000a" parent="None" root="000000000000a" order="2" type="ROOT" class="CHARACTER">
|
<item handle="000000000000a" parent="None" root="000000000000a" order="2" type="ROOT" class="CHARACTER">
|
||||||
<meta expanded="yes" />
|
<meta expanded="no" />
|
||||||
<name status="s000000" import="i000004">Characters</name>
|
<name status="s000000" import="i000004">Characters</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="0000000000011" parent="000000000000a" root="000000000000a" order="0" type="FILE" class="CHARACTER" layout="NOTE">
|
<item handle="0000000000011" parent="000000000000a" root="000000000000a" order="0" type="FILE" class="CHARACTER" layout="NOTE">
|
||||||
<meta expanded="yes" heading="H1" charCount="34" wordCount="8" paraCount="1" cursorPos="51" />
|
<meta expanded="no" heading="H1" charCount="34" wordCount="8" paraCount="1" cursorPos="51" />
|
||||||
<name status="s000000" import="i000004" active="yes">New Note</name>
|
<name status="s000000" import="i000004" active="yes">New Note</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="000000000000b" parent="None" root="000000000000b" order="3" type="ROOT" class="WORLD">
|
<item handle="000000000000b" parent="None" root="000000000000b" order="3" type="ROOT" class="WORLD">
|
||||||
<meta expanded="yes" />
|
<meta expanded="no" />
|
||||||
<name status="s000000" import="i000004">Locations</name>
|
<name status="s000000" import="i000004">Locations</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="0000000000013" parent="000000000000b" root="000000000000b" order="0" type="FILE" class="WORLD" layout="NOTE">
|
<item handle="0000000000013" parent="000000000000b" root="000000000000b" order="0" type="FILE" class="WORLD" layout="NOTE">
|
||||||
<meta expanded="yes" heading="H1" charCount="51" wordCount="9" paraCount="1" cursorPos="68" />
|
<meta expanded="no" heading="H1" charCount="51" wordCount="9" paraCount="1" cursorPos="68" />
|
||||||
<name status="s000000" import="i000004" active="yes">New Note</name>
|
<name status="s000000" import="i000004" active="yes">New Note</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="0000000000010" parent="None" root="0000000000010" order="4" type="ROOT" class="TRASH">
|
<item handle="0000000000010" parent="None" root="0000000000010" order="4" type="ROOT" class="TRASH">
|
||||||
<meta expanded="yes" />
|
<meta expanded="no" />
|
||||||
<name status="s000000" import="i000004">Trash</name>
|
<name status="s000000" import="i000004">Trash</name>
|
||||||
</item>
|
</item>
|
||||||
</content>
|
</content>
|
||||||
|
|||||||
@@ -116,6 +116,16 @@ def testCoreItemModel_ProjectNode_Children(mockGUI, mockRnd, fncPath):
|
|||||||
assert child010.parent() is child01
|
assert child010.parent() is child01
|
||||||
assert child011.parent() is child01
|
assert child011.parent() is child01
|
||||||
|
|
||||||
|
# Expand
|
||||||
|
child0.setExpanded(True)
|
||||||
|
child1.setExpanded(True)
|
||||||
|
child2.setExpanded(True)
|
||||||
|
child3.setExpanded(True)
|
||||||
|
assert child0.item.isExpanded is True # Only one with children
|
||||||
|
assert child1.item.isExpanded is False
|
||||||
|
assert child2.item.isExpanded is False
|
||||||
|
assert child3.item.isExpanded is False
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.core
|
@pytest.mark.core
|
||||||
def testCoreItemModel_ProjectNode_Modify(mockGUI, mockRnd, fncPath):
|
def testCoreItemModel_ProjectNode_Modify(mockGUI, mockRnd, fncPath):
|
||||||
|
|||||||
+852
-932
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user