Clean up project tree class

This commit is contained in:
Veronica Berglyd Olsen
2024-10-31 17:30:49 +01:00
parent b529d5b125
commit 7f49979bc9
4 changed files with 134 additions and 137 deletions
+2 -9
View File
@@ -484,16 +484,9 @@ class GuiNovelTree(QTreeWidget):
if rootHandle is None: if rootHandle is None:
rootHandle = SHARED.project.tree.findRoot(nwItemClass.NOVEL) rootHandle = SHARED.project.tree.findRoot(nwItemClass.NOVEL)
treeChanged = SHARED.mainGui.projView.changedSince(self._lastBuild)
indexChanged = SHARED.project.index.rootChangedSince(rootHandle, self._lastBuild)
if not (treeChanged or indexChanged or overRide):
logger.debug("No changes have been made to the novel index")
return
selItem = self.selectedItems()
titleKey = None titleKey = None
if selItem: if selItems := self.selectedItems():
titleKey = selItem[0].data(self.C_DATA, self.D_KEY) titleKey = selItems[0].data(self.C_DATA, self.D_KEY)
self._populateTree(rootHandle) self._populateTree(rootHandle)
SHARED.project.data.setLastHandle(rootHandle, "novelTree") SHARED.project.data.setLastHandle(rootHandle, "novelTree")
+115 -119
View File
@@ -29,7 +29,6 @@ from __future__ import annotations
import logging import logging
from enum import Enum from enum import Enum
from time import time
from PyQt5.QtCore import QPoint, Qt, QTimer, pyqtSignal, pyqtSlot from PyQt5.QtCore import QPoint, Qt, QTimer, pyqtSignal, pyqtSlot
from PyQt5.QtGui import QDragEnterEvent, QDragMoveEvent, QDropEvent, QIcon, QMouseEvent, QPalette from PyQt5.QtGui import QDragEnterEvent, QDragMoveEvent, QDropEvent, QIcon, QMouseEvent, QPalette
@@ -39,7 +38,7 @@ from PyQt5.QtWidgets import (
) )
from novelwriter import CONFIG, SHARED from novelwriter import CONFIG, SHARED
from novelwriter.common import minmax from novelwriter.common import minmax, qtLambda
from novelwriter.constants import nwLabels, nwStyles, nwUnicode, trConst from novelwriter.constants import nwLabels, nwStyles, nwUnicode, trConst
from novelwriter.core.coretools import DocDuplicator, DocMerger, DocSplitter from novelwriter.core.coretools import DocDuplicator, DocMerger, DocSplitter
from novelwriter.core.item import NWItem from novelwriter.core.item import NWItem
@@ -97,37 +96,37 @@ class GuiProjectView(QWidget):
self.keyMoveUp = QShortcut(self.projTree) self.keyMoveUp = QShortcut(self.projTree)
self.keyMoveUp.setKey("Ctrl+Up") self.keyMoveUp.setKey("Ctrl+Up")
self.keyMoveUp.setContext(Qt.ShortcutContext.WidgetShortcut) self.keyMoveUp.setContext(Qt.ShortcutContext.WidgetShortcut)
self.keyMoveUp.activated.connect(lambda: self.projTree.moveTreeItem(-1)) self.keyMoveUp.activated.connect(qtLambda(self.projTree.moveTreeItem, -1))
self.keyMoveDn = QShortcut(self.projTree) self.keyMoveDn = QShortcut(self.projTree)
self.keyMoveDn.setKey("Ctrl+Down") self.keyMoveDn.setKey("Ctrl+Down")
self.keyMoveDn.setContext(Qt.ShortcutContext.WidgetShortcut) self.keyMoveDn.setContext(Qt.ShortcutContext.WidgetShortcut)
self.keyMoveDn.activated.connect(lambda: self.projTree.moveTreeItem(1)) self.keyMoveDn.activated.connect(qtLambda(self.projTree.moveTreeItem, 1))
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(lambda: self.projTree.moveToNextItem(-1)) self.keyGoPrev.activated.connect(qtLambda(self.projTree.moveToNextItem, -1))
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(lambda: self.projTree.moveToNextItem(1)) self.keyGoNext.activated.connect(qtLambda(self.projTree.moveToNextItem, 1))
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(lambda: self.projTree.moveToLevel(-1)) self.keyGoUp.activated.connect(qtLambda(self.projTree.moveToLevel, -1))
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(lambda: self.projTree.moveToLevel(1)) self.keyGoDown.activated.connect(qtLambda(self.projTree.moveToLevel, 1))
self.keyContext = QShortcut(self.projTree) self.keyContext = QShortcut(self.projTree)
self.keyContext.setKey("Ctrl+.") self.keyContext.setKey("Ctrl+.")
self.keyContext.setContext(Qt.ShortcutContext.WidgetShortcut) self.keyContext.setContext(Qt.ShortcutContext.WidgetShortcut)
self.keyContext.activated.connect(lambda: self.projTree.openContextOnSelected()) self.keyContext.activated.connect(self.projTree.openContextMenu)
# Signals # Signals
self.selectedItemChanged.connect(self.projBar.treeSelectionChanged) self.selectedItemChanged.connect(self.projBar.treeSelectionChanged)
@@ -138,7 +137,6 @@ class GuiProjectView(QWidget):
self.emptyTrash = self.projTree.emptyTrash self.emptyTrash = self.projTree.emptyTrash
self.requestDeleteItem = self.projTree.requestDeleteItem self.requestDeleteItem = self.projTree.requestDeleteItem
self.getSelectedHandle = self.projTree.getSelectedHandle self.getSelectedHandle = self.projTree.getSelectedHandle
self.changedSince = self.projTree.changedSince
return return
@@ -292,38 +290,38 @@ class GuiProjectToolBar(QWidget):
# Move Buttons # Move Buttons
self.tbMoveU = NIconToolButton(self, iSz) self.tbMoveU = NIconToolButton(self, iSz)
self.tbMoveU.setToolTip("%s [Ctrl+Up]" % self.tr("Move Up")) self.tbMoveU.setToolTip("%s [Ctrl+Up]" % self.tr("Move Up"))
self.tbMoveU.clicked.connect(lambda: self.projTree.moveTreeItem(-1)) self.tbMoveU.clicked.connect(qtLambda(self.projTree.moveTreeItem, -1))
self.tbMoveD = NIconToolButton(self, iSz) self.tbMoveD = NIconToolButton(self, iSz)
self.tbMoveD.setToolTip("%s [Ctrl+Down]" % self.tr("Move Down")) self.tbMoveD.setToolTip("%s [Ctrl+Down]" % self.tr("Move Down"))
self.tbMoveD.clicked.connect(lambda: self.projTree.moveTreeItem(1)) self.tbMoveD.clicked.connect(qtLambda(self.projTree.moveTreeItem, 1))
# Add Item Menu # Add Item Menu
self.mAdd = QMenu(self) self.mAdd = QMenu(self)
self.aAddEmpty = self.mAdd.addAction(trConst(nwLabels.ITEM_DESCRIPTION["document"])) self.aAddEmpty = self.mAdd.addAction(trConst(nwLabels.ITEM_DESCRIPTION["document"]))
self.aAddEmpty.triggered.connect( self.aAddEmpty.triggered.connect(
lambda: self.projTree.newTreeItem(nwItemType.FILE, hLevel=0, isNote=False) qtLambda(self.projTree.newTreeItem, nwItemType.FILE, hLevel=0, isNote=False)
) )
self.aAddChap = self.mAdd.addAction(trConst(nwLabels.ITEM_DESCRIPTION["doc_h2"])) self.aAddChap = self.mAdd.addAction(trConst(nwLabels.ITEM_DESCRIPTION["doc_h2"]))
self.aAddChap.triggered.connect( self.aAddChap.triggered.connect(
lambda: self.projTree.newTreeItem(nwItemType.FILE, hLevel=2, isNote=False) qtLambda(self.projTree.newTreeItem, nwItemType.FILE, hLevel=2, isNote=False)
) )
self.aAddScene = self.mAdd.addAction(trConst(nwLabels.ITEM_DESCRIPTION["doc_h3"])) self.aAddScene = self.mAdd.addAction(trConst(nwLabels.ITEM_DESCRIPTION["doc_h3"]))
self.aAddScene.triggered.connect( self.aAddScene.triggered.connect(
lambda: self.projTree.newTreeItem(nwItemType.FILE, hLevel=3, isNote=False) qtLambda(self.projTree.newTreeItem, nwItemType.FILE, hLevel=3, isNote=False)
) )
self.aAddNote = self.mAdd.addAction(trConst(nwLabels.ITEM_DESCRIPTION["note"])) self.aAddNote = self.mAdd.addAction(trConst(nwLabels.ITEM_DESCRIPTION["note"]))
self.aAddNote.triggered.connect( self.aAddNote.triggered.connect(
lambda: self.projTree.newTreeItem(nwItemType.FILE, hLevel=1, isNote=True) qtLambda(self.projTree.newTreeItem, nwItemType.FILE, hLevel=1, isNote=True)
) )
self.aAddFolder = self.mAdd.addAction(trConst(nwLabels.ITEM_DESCRIPTION["folder"])) self.aAddFolder = self.mAdd.addAction(trConst(nwLabels.ITEM_DESCRIPTION["folder"]))
self.aAddFolder.triggered.connect( self.aAddFolder.triggered.connect(
lambda: self.projTree.newTreeItem(nwItemType.FOLDER) qtLambda(self.projTree.newTreeItem, nwItemType.FOLDER)
) )
self.mTemplates = _UpdatableMenu(self.mAdd) self.mTemplates = _UpdatableMenu(self.mAdd)
@@ -343,13 +341,17 @@ class GuiProjectToolBar(QWidget):
self.mMore = QMenu(self) self.mMore = QMenu(self)
self.aExpand = self.mMore.addAction(self.tr("Expand All")) self.aExpand = self.mMore.addAction(self.tr("Expand All"))
self.aExpand.triggered.connect(lambda: self.projTree.setExpandedFromHandle(None, True)) self.aExpand.triggered.connect(
qtLambda(self.projTree.setExpandedFromHandle, None, True)
)
self.aCollapse = self.mMore.addAction(self.tr("Collapse All")) self.aCollapse = self.mMore.addAction(self.tr("Collapse All"))
self.aCollapse.triggered.connect(lambda: self.projTree.setExpandedFromHandle(None, False)) self.aCollapse.triggered.connect(
qtLambda(self.projTree.setExpandedFromHandle, None, False)
)
self.aEmptyTrash = self.mMore.addAction(self.tr("Empty Trash")) self.aEmptyTrash = self.mMore.addAction(self.tr("Empty Trash"))
self.aEmptyTrash.triggered.connect(lambda: self.projTree.emptyTrash()) self.aEmptyTrash.triggered.connect(qtLambda(self.projTree.emptyTrash))
self.tbMore = NIconToolButton(self, iSz) self.tbMore = NIconToolButton(self, iSz)
self.tbMore.setToolTip(self.tr("More Options")) self.tbMore.setToolTip(self.tr("More Options"))
@@ -422,7 +424,7 @@ class GuiProjectToolBar(QWidget):
action.setData(tHandle) action.setData(tHandle)
action.setIcon(SHARED.theme.getIcon(nwLabels.CLASS_ICON[nwItem.itemClass])) action.setIcon(SHARED.theme.getIcon(nwLabels.CLASS_ICON[nwItem.itemClass]))
action.triggered.connect( action.triggered.connect(
lambda _, tHandle=tHandle: self.projView.setSelectedHandle(tHandle, doScroll=True) qtLambda(self.projView.setSelectedHandle, tHandle, doScroll=True)
) )
return return
@@ -461,7 +463,9 @@ class GuiProjectToolBar(QWidget):
def addClass(itemClass: nwItemClass) -> None: def addClass(itemClass: nwItemClass) -> None:
aNew = self.mAddRoot.addAction(trConst(nwLabels.CLASS_NAME[itemClass])) aNew = self.mAddRoot.addAction(trConst(nwLabels.CLASS_NAME[itemClass]))
aNew.setIcon(SHARED.theme.getIcon(nwLabels.CLASS_ICON[itemClass])) aNew.setIcon(SHARED.theme.getIcon(nwLabels.CLASS_ICON[itemClass]))
aNew.triggered.connect(lambda: self.projTree.newTreeItem(nwItemType.ROOT, itemClass)) aNew.triggered.connect(
qtLambda(self.projTree.newTreeItem, nwItemType.ROOT, itemClass)
)
self.mAddRoot.addAction(aNew) self.mAddRoot.addAction(aNew)
return return
@@ -504,7 +508,6 @@ class GuiProjectTree(QTreeWidget):
# Internal Variables # Internal Variables
self._treeMap: dict[str, QTreeWidgetItem] = {} self._treeMap: dict[str, QTreeWidgetItem] = {}
self._timeChanged = 0.0
self._popAlert = None self._popAlert = None
self._actHandle = None self._actHandle = None
@@ -518,7 +521,7 @@ class GuiProjectTree(QTreeWidget):
# Context Menu # Context Menu
self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
self.customContextMenuRequested.connect(self._openContextMenu) self.customContextMenuRequested.connect(self.openContextMenu)
# Tree Settings # Tree Settings
iPx = SHARED.theme.baseIconHeight iPx = SHARED.theme.baseIconHeight
@@ -602,7 +605,6 @@ class GuiProjectTree(QTreeWidget):
"""Clear the GUI content and the related map.""" """Clear the GUI content and the related map."""
self.clear() self.clear()
self._treeMap = {} self._treeMap = {}
self._timeChanged = 0.0
return return
def createNewNote(self, tag: str, itemClass: nwItemClass) -> None: def createNewNote(self, tag: str, itemClass: nwItemClass) -> None:
@@ -1121,12 +1123,10 @@ class GuiProjectTree(QTreeWidget):
""" """
logger.debug("Building the project tree ...") logger.debug("Building the project tree ...")
self.clearTree() self.clearTree()
count = 0
for nwItem in SHARED.project.iterProjectItems(): for nwItem in SHARED.project.iterProjectItems():
count += 1
self._addTreeItem(nwItem) self._addTreeItem(nwItem)
if count > 0: self.setActiveHandle(self._actHandle)
logger.info("%d item(s) added to the project tree", count) logger.info("%d item(s) added to the project tree", len(self._treeMap))
return return
def getSelectedHandle(self) -> str | None: def getSelectedHandle(self) -> str | None:
@@ -1137,19 +1137,13 @@ class GuiProjectTree(QTreeWidget):
return items[0].data(self.C_DATA, self.D_HANDLE) return items[0].data(self.C_DATA, self.D_HANDLE)
return None return None
def setSelectedHandle(self, tHandle: str | None, doScroll: bool = False) -> bool: def setSelectedHandle(self, tHandle: str | None, doScroll: bool = False) -> None:
"""Set a specific handle as the selected item.""" """Set a specific handle as the selected item."""
tItem = self._getTreeItem(tHandle)
if tItem is None:
return False
if tHandle in self._treeMap: if tHandle in self._treeMap:
self.setCurrentItem(self._treeMap[tHandle]) self.setCurrentItem(self._treeMap[tHandle])
if (indexes := self.selectedIndexes()) and doScroll:
if (indexes := self.selectedIndexes()) and doScroll: self.scrollTo(indexes[0], QAbstractItemView.ScrollHint.PositionAtCenter)
self.scrollTo(indexes[0], QAbstractItemView.ScrollHint.PositionAtCenter) return
return True
def setActiveHandle(self, tHandle: str | None) -> None: def setActiveHandle(self, tHandle: str | None) -> None:
"""Highlight the rows associated with a given handle.""" """Highlight the rows associated with a given handle."""
@@ -1169,19 +1163,52 @@ class GuiProjectTree(QTreeWidget):
status for all child items. If tHandle is None, it affects the status for all child items. If tHandle is None, it affects the
entire tree. entire tree.
""" """
trItem = self._getTreeItem(tHandle) or self.invisibleRootItem() item = self._getTreeItem(tHandle) or self.invisibleRootItem()
self._recursiveSetExpanded(trItem, isExpanded) self._recursiveSetExpanded(item, isExpanded)
return return
def openContextOnSelected(self) -> bool: ##
"""Open the context menu on the current selected item.""" # Public Slots
if items := self.selectedItems(): ##
return self._openContextMenu(self.visualItemRect(items[0]).center())
return False
def changedSince(self, checkTime: float) -> bool: @pyqtSlot()
"""Check if the tree has changed since a given time.""" @pyqtSlot("QPoint")
return self._timeChanged > checkTime def openContextMenu(self, clickPos: QPoint | None = None) -> None:
"""The user right clicked an element in the project tree, so we
open a context menu in-place.
"""
if clickPos is None and (items := self.selectedItems()):
clickPos = self.visualItemRect(items[0]).center()
if clickPos is not None:
tItem = None
tHandle = None
hasChild = False
sItem = self.itemAt(clickPos)
sItems = self.selectedItems()
if isinstance(sItem, QTreeWidgetItem):
tHandle = sItem.data(self.C_DATA, self.D_HANDLE)
tItem = SHARED.project.tree[tHandle]
hasChild = sItem.childCount() > 0
if tItem is None or tHandle is None:
logger.debug("No item found")
return
ctxMenu = _TreeContextMenu(self, tItem)
trashHandle = SHARED.project.tree.trashRoot
if trashHandle and tHandle == trashHandle:
ctxMenu.buildTrashMenu()
elif len(sItems) > 1:
handles = [str(x.data(self.C_DATA, self.D_HANDLE)) for x in sItems]
ctxMenu.buildMultiSelectMenu(handles)
else:
ctxMenu.buildSingleSelectMenu(hasChild)
ctxMenu.exec(self.viewport().mapToGlobal(clickPos))
ctxMenu.deleteLater()
return
## ##
# Private Slots # Private Slots
@@ -1224,40 +1251,6 @@ class GuiProjectTree(QTreeWidget):
return return
@pyqtSlot("QPoint")
def _openContextMenu(self, clickPos: QPoint) -> bool:
"""The user right clicked an element in the project tree, so we
open a context menu in-place.
"""
tItem = None
tHandle = None
hasChild = False
sItem = self.itemAt(clickPos)
sItems = self.selectedItems()
if isinstance(sItem, QTreeWidgetItem):
tHandle = sItem.data(self.C_DATA, self.D_HANDLE)
tItem = SHARED.project.tree[tHandle]
hasChild = sItem.childCount() > 0
if tItem is None or tHandle is None:
logger.debug("No item found")
return False
ctxMenu = _TreeContextMenu(self, tItem)
trashHandle = SHARED.project.tree.trashRoot
if trashHandle and tHandle == trashHandle:
ctxMenu.buildTrashMenu()
elif len(sItems) > 1:
handles = [str(x.data(self.C_DATA, self.D_HANDLE)) for x in sItems]
ctxMenu.buildMultiSelectMenu(handles)
else:
ctxMenu.buildSingleSelectMenu(hasChild)
ctxMenu.exec(self.viewport().mapToGlobal(clickPos))
ctxMenu.deleteLater()
return True
@pyqtSlot() @pyqtSlot()
def _doAutoScroll(self) -> None: def _doAutoScroll(self) -> None:
"""Scroll one item up or down based on direction value.""" """Scroll one item up or down based on direction value."""
@@ -1658,7 +1651,6 @@ class GuiProjectTree(QTreeWidget):
signals. A flush is only needed if an item is moved, created or signals. A flush is only needed if an item is moved, created or
deleted. deleted.
""" """
self._timeChanged = time()
SHARED.project.setProjectChanged(True) SHARED.project.setProjectChanged(True)
if flush: if flush:
self.saveTreeOrder() self.saveTreeOrder()
@@ -1785,7 +1777,7 @@ class _TreeContextMenu(QMenu):
# Edit Item Settings # Edit Item Settings
action = self.addAction(self.tr("Rename")) action = self.addAction(self.tr("Rename"))
action.triggered.connect(lambda: self.projTree.renameTreeItem(self._handle)) action.triggered.connect(qtLambda(self.projTree.renameTreeItem, self._handle))
if isFile: if isFile:
self._itemHeader() self._itemHeader()
self._itemActive(False) self._itemActive(False)
@@ -1821,13 +1813,15 @@ class _TreeContextMenu(QMenu):
def _docActions(self) -> None: def _docActions(self) -> None:
"""Add document actions.""" """Add document actions."""
action = self.addAction(self.tr("Open Document")) action = self.addAction(self.tr("Open Document"))
action.triggered.connect( action.triggered.connect(qtLambda(
lambda: self.projView.openDocumentRequest.emit(self._handle, nwDocMode.EDIT, "", True) self.projView.openDocumentRequest.emit,
) self._handle, nwDocMode.EDIT, "", True
))
action = self.addAction(self.tr("View Document")) action = self.addAction(self.tr("View Document"))
action.triggered.connect( action.triggered.connect(qtLambda(
lambda: self.projView.openDocumentRequest.emit(self._handle, nwDocMode.VIEW, "", False) self.projView.openDocumentRequest.emit,
) self._handle, nwDocMode.VIEW, "", False
))
return return
def _itemCreation(self) -> None: def _itemCreation(self) -> None:
@@ -1846,7 +1840,7 @@ class _TreeContextMenu(QMenu):
if hItem := SHARED.project.index.getItemHeading(self._handle, "T0001"): if hItem := SHARED.project.index.getItemHeading(self._handle, "T0001"):
action = self.addAction(self.tr("Rename to Heading")) action = self.addAction(self.tr("Rename to Heading"))
action.triggered.connect( action.triggered.connect(
lambda: self.projTree.renameTreeItem(self._handle, hItem.title) qtLambda(self.projTree.renameTreeItem, self._handle, hItem.title)
) )
return return
@@ -1855,9 +1849,9 @@ class _TreeContextMenu(QMenu):
if multi: if multi:
mSub = self.addMenu(self.tr("Set Active to ...")) mSub = self.addMenu(self.tr("Set Active to ..."))
aOne = mSub.addAction(SHARED.theme.getIcon("checked"), self.projTree.trActive) aOne = mSub.addAction(SHARED.theme.getIcon("checked"), self.projTree.trActive)
aOne.triggered.connect(lambda: self._iterItemActive(True)) aOne.triggered.connect(qtLambda(self._iterItemActive, True))
aTwo = mSub.addAction(SHARED.theme.getIcon("unchecked"), self.projTree.trInactive) aTwo = mSub.addAction(SHARED.theme.getIcon("unchecked"), self.projTree.trInactive)
aTwo.triggered.connect(lambda: self._iterItemActive(False)) aTwo.triggered.connect(qtLambda(self._iterItemActive, False))
else: else:
action = self.addAction(self.tr("Toggle Active")) action = self.addAction(self.tr("Toggle Active"))
action.triggered.connect(self._toggleItemActive) action.triggered.connect(self._toggleItemActive)
@@ -1868,37 +1862,39 @@ class _TreeContextMenu(QMenu):
if self._item.isNovelLike(): if self._item.isNovelLike():
menu = self.addMenu(self.tr("Set Status to ...")) menu = self.addMenu(self.tr("Set Status to ..."))
current = self._item.itemStatus current = self._item.itemStatus
for n, (key, entry) in enumerate(SHARED.project.data.itemStatus.iterItems()): for key, entry in SHARED.project.data.itemStatus.iterItems():
name = entry.name name = entry.name
if not multi and current == key: if not multi and current == key:
name += f" ({nwUnicode.U_CHECK})" name += f" ({nwUnicode.U_CHECK})"
action = menu.addAction(entry.icon, name) action = menu.addAction(entry.icon, name)
if multi: if multi:
action.triggered.connect(lambda n, key=key: self._iterSetItemStatus(key)) action.triggered.connect(qtLambda(self._iterSetItemStatus, key))
else: else:
action.triggered.connect(lambda n, key=key: self._changeItemStatus(key)) action.triggered.connect(qtLambda(self._changeItemStatus, key))
menu.addSeparator() menu.addSeparator()
action = menu.addAction(self.tr("Manage Labels ...")) action = menu.addAction(self.tr("Manage Labels ..."))
action.triggered.connect( action.triggered.connect(qtLambda(
lambda: self.projView.projectSettingsRequest.emit(GuiProjectSettings.PAGE_STATUS) self.projView.projectSettingsRequest.emit,
) GuiProjectSettings.PAGE_STATUS
))
else: else:
menu = self.addMenu(self.tr("Set Importance to ...")) menu = self.addMenu(self.tr("Set Importance to ..."))
current = self._item.itemImport current = self._item.itemImport
for n, (key, entry) in enumerate(SHARED.project.data.itemImport.iterItems()): for key, entry in SHARED.project.data.itemImport.iterItems():
name = entry.name name = entry.name
if not multi and current == key: if not multi and current == key:
name += f" ({nwUnicode.U_CHECK})" name += f" ({nwUnicode.U_CHECK})"
action = menu.addAction(entry.icon, name) action = menu.addAction(entry.icon, name)
if multi: if multi:
action.triggered.connect(lambda n, key=key: self._iterSetItemImport(key)) action.triggered.connect(qtLambda(self._iterSetItemImport, key))
else: else:
action.triggered.connect(lambda n, key=key: self._changeItemImport(key)) action.triggered.connect(qtLambda(self._changeItemImport, key))
menu.addSeparator() menu.addSeparator()
action = menu.addAction(self.tr("Manage Labels ...")) action = menu.addAction(self.tr("Manage Labels ..."))
action.triggered.connect( action.triggered.connect(qtLambda(
lambda: self.projView.projectSettingsRequest.emit(GuiProjectSettings.PAGE_IMPORT) self.projView.projectSettingsRequest.emit,
) GuiProjectSettings.PAGE_IMPORT
))
return return
def _itemTransform(self, isFile: bool, isFolder: bool, hasChild: bool) -> None: def _itemTransform(self, isFile: bool, isFolder: bool, hasChild: bool) -> None:
@@ -1917,33 +1913,33 @@ class _TreeContextMenu(QMenu):
if isNoteFile and self._item.documentAllowed(): if isNoteFile and self._item.documentAllowed():
action = menu.addAction(self.tr("Convert to {0}").format(trDoc)) action = menu.addAction(self.tr("Convert to {0}").format(trDoc))
action.triggered.connect(lambda: self._changeItemLayout(loDoc)) action.triggered.connect(qtLambda(self._changeItemLayout, loDoc))
if isDocFile: if isDocFile:
action = menu.addAction(self.tr("Convert to {0}").format(trNote)) action = menu.addAction(self.tr("Convert to {0}").format(trNote))
action.triggered.connect(lambda: self._changeItemLayout(loNote)) action.triggered.connect(qtLambda(self._changeItemLayout, loNote))
if isFolder and self._item.documentAllowed(): if isFolder and self._item.documentAllowed():
action = menu.addAction(self.tr("Convert to {0}").format(trDoc)) action = menu.addAction(self.tr("Convert to {0}").format(trDoc))
action.triggered.connect(lambda: self._covertFolderToFile(loDoc)) action.triggered.connect(qtLambda(self._covertFolderToFile, loDoc))
if isFolder: if isFolder:
action = menu.addAction(self.tr("Convert to {0}").format(trNote)) action = menu.addAction(self.tr("Convert to {0}").format(trNote))
action.triggered.connect(lambda: self._covertFolderToFile(loNote)) action.triggered.connect(qtLambda(self._covertFolderToFile, loNote))
if hasChild and isFile: if hasChild and isFile:
action = menu.addAction(self.tr("Merge Child Items into Self")) action = menu.addAction(self.tr("Merge Child Items into Self"))
action.triggered.connect(lambda: tree._mergeDocuments(tHandle, False)) action.triggered.connect(qtLambda(tree._mergeDocuments, tHandle, False))
action = menu.addAction(self.tr("Merge Child Items into New")) action = menu.addAction(self.tr("Merge Child Items into New"))
action.triggered.connect(lambda: tree._mergeDocuments(tHandle, True)) action.triggered.connect(qtLambda(tree._mergeDocuments, tHandle, True))
if hasChild and isFolder: if hasChild and isFolder:
action = menu.addAction(self.tr("Merge Documents in Folder")) action = menu.addAction(self.tr("Merge Documents in Folder"))
action.triggered.connect(lambda: tree._mergeDocuments(tHandle, True)) action.triggered.connect(qtLambda(tree._mergeDocuments, tHandle, True))
if isFile: if isFile:
action = menu.addAction(self.tr("Split Document by Headings")) action = menu.addAction(self.tr("Split Document by Headings"))
action.triggered.connect(lambda: tree._splitDocument(tHandle)) action.triggered.connect(qtLambda(tree._splitDocument, tHandle))
return return
@@ -1953,19 +1949,19 @@ class _TreeContextMenu(QMenu):
tHandle = self._handle tHandle = self._handle
if hasChild: if hasChild:
action = self.addAction(self.tr("Expand All")) action = self.addAction(self.tr("Expand All"))
action.triggered.connect(lambda: tree.setExpandedFromHandle(tHandle, True)) action.triggered.connect(qtLambda(tree.setExpandedFromHandle, tHandle, True))
action = self.addAction(self.tr("Collapse All")) action = self.addAction(self.tr("Collapse All"))
action.triggered.connect(lambda: tree.setExpandedFromHandle(tHandle, False)) action.triggered.connect(qtLambda(tree.setExpandedFromHandle, tHandle, False))
action = self.addAction(self.tr("Duplicate")) action = self.addAction(self.tr("Duplicate"))
action.triggered.connect(lambda: tree._duplicateFromHandle(tHandle)) action.triggered.connect(qtLambda(tree._duplicateFromHandle, tHandle))
if self._item.itemClass == nwItemClass.TRASH or isRoot or (isFolder and not hasChild): if self._item.itemClass == nwItemClass.TRASH or isRoot or (isFolder and not hasChild):
action = self.addAction(self.tr("Delete Permanently")) action = self.addAction(self.tr("Delete Permanently"))
action.triggered.connect(lambda: tree.permDeleteItem(tHandle)) action.triggered.connect(qtLambda(tree.permDeleteItem, tHandle))
else: else:
action = self.addAction(self.tr("Move to Trash")) action = self.addAction(self.tr("Move to Trash"))
action.triggered.connect(lambda: tree.moveItemToTrash(tHandle)) action.triggered.connect(qtLambda(tree.moveItemToTrash, tHandle))
return return
+1 -1
View File
@@ -134,7 +134,7 @@ def testGuiViewer_Main(qtbot, monkeypatch, nwGUI, prjLipsum):
assert docViewer.docAction(nwDocAction.COPY) is False assert docViewer.docAction(nwDocAction.COPY) is False
# Open again via menu # Open again via menu
assert nwGUI.projView.projTree.setSelectedHandle("88243afbe5ed8") nwGUI.projView.projTree.setSelectedHandle("88243afbe5ed8")
nwGUI.mainMenu.aViewDoc.activate(QAction.ActionEvent.Trigger) nwGUI.mainMenu.aViewDoc.activate(QAction.ActionEvent.Trigger)
# Open context menu # Open context menu
+16 -8
View File
@@ -21,6 +21,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
from __future__ import annotations from __future__ import annotations
from pathlib import Path from pathlib import Path
from unittest.mock import MagicMock
import pytest import pytest
@@ -1146,30 +1147,37 @@ def testGuiProjTree_ContextMenu(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
def itemPos(tHandle): def itemPos(tHandle):
return projTree.visualItemRect(projTree._getTreeItem(tHandle)).center() return projTree.visualItemRect(projTree._getTreeItem(tHandle)).center()
# Pop the menu # Pop the menu in various positions and check for success
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setattr(QMenu, "exec", lambda *a: None) mockMenu = MagicMock()
mp.setattr(QMenu, "exec", mockMenu)
projTree.clearSelection() projTree.clearSelection()
# No item under menu # No item under menu
assert projTree._openContextMenu(projTree.viewport().rect().bottomRight()) is False projTree.openContextMenu(projTree.viewport().rect().bottomRight())
assert mockMenu.call_count == 0
# Open Trash Menu # Open Trash Menu
assert projTree._openContextMenu(itemPos(hTrashRoot)) is True projTree.openContextMenu(itemPos(hTrashRoot))
assert mockMenu.call_count == 1
# Open Single Select Menu # Open Single Select Menu
assert projTree._openContextMenu(itemPos(C.hNovelRoot)) is True projTree.openContextMenu(itemPos(C.hNovelRoot))
assert mockMenu.call_count == 2
# Open Multi-Select Menu # Open Multi-Select Menu
projTree._getTreeItem(hNovelNote).setSelected(True) projTree._getTreeItem(hNovelNote).setSelected(True)
projTree._getTreeItem(hSubNote).setSelected(True) projTree._getTreeItem(hSubNote).setSelected(True)
assert projTree._openContextMenu(itemPos(hCharNote)) is True projTree.openContextMenu(itemPos(hCharNote))
assert mockMenu.call_count == 3
# Check the keyboard shortcut handler as well # Check the keyboard shortcut handler as well
projTree.setSelectedHandle(C.hNovelRoot) projTree.setSelectedHandle(C.hNovelRoot)
assert projTree.openContextOnSelected() is True projTree.openContextMenu(None)
assert mockMenu.call_count == 4
projTree.clearSelection() projTree.clearSelection()
assert projTree.openContextOnSelected() is False projTree.openContextMenu(None)
assert mockMenu.call_count == 4
# Menu Builders # Menu Builders
# ============= # =============