Move project tree context menu to a separate class
This commit is contained in:
+308
-269
@@ -836,6 +836,7 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
|
|
||||||
return status
|
return status
|
||||||
|
|
||||||
|
@pyqtSlot()
|
||||||
def emptyTrash(self) -> bool:
|
def emptyTrash(self) -> bool:
|
||||||
"""Permanently delete all documents in the Trash folder. This
|
"""Permanently delete all documents in the Trash folder. This
|
||||||
function only asks for confirmation once, and calls the regular
|
function only asks for confirmation once, and calls the regular
|
||||||
@@ -1175,163 +1176,29 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
tItem = None
|
tItem = None
|
||||||
tHandle = None
|
tHandle = None
|
||||||
hasChild = False
|
hasChild = False
|
||||||
selItem = self.itemAt(clickPos)
|
sItem = self.itemAt(clickPos)
|
||||||
if isinstance(selItem, QTreeWidgetItem):
|
sItems = self.selectedItems()
|
||||||
tHandle = selItem.data(self.C_DATA, self.D_HANDLE)
|
if isinstance(sItem, QTreeWidgetItem):
|
||||||
|
tHandle = sItem.data(self.C_DATA, self.D_HANDLE)
|
||||||
tItem = SHARED.project.tree[tHandle]
|
tItem = SHARED.project.tree[tHandle]
|
||||||
hasChild = selItem.childCount() > 0
|
hasChild = sItem.childCount() > 0
|
||||||
|
|
||||||
if tItem is None or tHandle is None:
|
if tItem is None or tHandle is None:
|
||||||
logger.debug("No item found")
|
logger.debug("No item found")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if len(self.selectedItems()) > 1:
|
ctxMenu = _TreeContextMenu(self, tItem)
|
||||||
return self._openMultiSelectContextMenu(clickPos, tItem)
|
|
||||||
|
|
||||||
ctxMenu = QMenu(self)
|
|
||||||
|
|
||||||
# Trash Folder
|
|
||||||
# ============
|
|
||||||
|
|
||||||
trashHandle = SHARED.project.tree.trashRoot
|
trashHandle = SHARED.project.tree.trashRoot
|
||||||
if tItem.itemHandle == trashHandle and trashHandle is not None:
|
if trashHandle and tHandle == trashHandle:
|
||||||
# The trash folder only has one option
|
ctxMenu.buildTrashMenu()
|
||||||
aEmptyTrash = ctxMenu.addAction(self.tr("Empty Trash"))
|
elif len(sItems) > 1:
|
||||||
aEmptyTrash.triggered.connect(lambda: self.emptyTrash())
|
handles = [str(x.data(self.C_DATA, self.D_HANDLE)) for x in sItems]
|
||||||
ctxMenu.exec_(self.viewport().mapToGlobal(clickPos))
|
ctxMenu.buildMultiSelectMenu(handles)
|
||||||
return True
|
|
||||||
|
|
||||||
# Document Actions
|
|
||||||
# ================
|
|
||||||
|
|
||||||
isRoot = tItem.isRootType()
|
|
||||||
isFolder = tItem.isFolderType()
|
|
||||||
isFile = tItem.isFileType()
|
|
||||||
|
|
||||||
if isFile:
|
|
||||||
aOpenDoc = ctxMenu.addAction(self.tr("Open Document"))
|
|
||||||
aOpenDoc.triggered.connect(
|
|
||||||
lambda: self.projView.openDocumentRequest.emit(tHandle, nwDocMode.EDIT, "", True)
|
|
||||||
)
|
|
||||||
aViewDoc = ctxMenu.addAction(self.tr("View Document"))
|
|
||||||
aViewDoc.triggered.connect(
|
|
||||||
lambda: self.projView.openDocumentRequest.emit(tHandle, nwDocMode.VIEW, "", False)
|
|
||||||
)
|
|
||||||
ctxMenu.addSeparator()
|
|
||||||
|
|
||||||
# Edit Item Settings
|
|
||||||
# ==================
|
|
||||||
|
|
||||||
aLabel = ctxMenu.addAction(self.tr("Rename"))
|
|
||||||
aLabel.triggered.connect(lambda: self.renameTreeItem(tHandle))
|
|
||||||
|
|
||||||
if isFile:
|
|
||||||
aActive = ctxMenu.addAction(self.tr("Toggle Active"))
|
|
||||||
aActive.triggered.connect(lambda: self._toggleItemActive(tHandle))
|
|
||||||
|
|
||||||
checkMark = f" ({nwUnicode.U_CHECK})"
|
|
||||||
if tItem.isNovelLike():
|
|
||||||
mStatus = ctxMenu.addMenu(self.tr("Set Status to ..."))
|
|
||||||
for n, (key, entry) in enumerate(SHARED.project.data.itemStatus.items()):
|
|
||||||
entryName = entry["name"] + (checkMark if tItem.itemStatus == key else "")
|
|
||||||
aStatus = mStatus.addAction(entry["icon"], entryName)
|
|
||||||
aStatus.triggered.connect(
|
|
||||||
lambda n, key=key: self._changeItemStatus(tHandle, key)
|
|
||||||
)
|
|
||||||
mStatus.addSeparator()
|
|
||||||
aManage1 = mStatus.addAction(self.tr("Manage Labels ..."))
|
|
||||||
aManage1.triggered.connect(
|
|
||||||
lambda: self.projView.projectSettingsRequest.emit(GuiProjectSettings.TAB_STATUS)
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
mImport = ctxMenu.addMenu(self.tr("Set Importance to ..."))
|
ctxMenu.buildSingleSelectMenu(hasChild)
|
||||||
for n, (key, entry) in enumerate(SHARED.project.data.itemImport.items()):
|
|
||||||
entryName = entry["name"] + (checkMark if tItem.itemImport == key else "")
|
|
||||||
aImport = mImport.addAction(entry["icon"], entryName)
|
|
||||||
aImport.triggered.connect(
|
|
||||||
lambda n, key=key: self._changeItemImport(tHandle, key)
|
|
||||||
)
|
|
||||||
mImport.addSeparator()
|
|
||||||
aManage2 = mImport.addAction(self.tr("Manage Labels ..."))
|
|
||||||
aManage2.triggered.connect(
|
|
||||||
lambda: self.projView.projectSettingsRequest.emit(GuiProjectSettings.TAB_IMPORT)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Transform Item
|
|
||||||
# ==============
|
|
||||||
|
|
||||||
if not isRoot:
|
|
||||||
mTrans = ctxMenu.addMenu(self.tr("Transform"))
|
|
||||||
|
|
||||||
trDoc = trConst(nwLabels.LAYOUT_NAME[nwItemLayout.DOCUMENT])
|
|
||||||
trNote = trConst(nwLabels.LAYOUT_NAME[nwItemLayout.NOTE])
|
|
||||||
|
|
||||||
isDocFile = isFile and tItem.isDocumentLayout()
|
|
||||||
isNoteFile = isFile and tItem.isNoteLayout()
|
|
||||||
|
|
||||||
if isNoteFile and tItem.documentAllowed():
|
|
||||||
aConvert1 = mTrans.addAction(self.tr("Convert to {0}").format(trDoc))
|
|
||||||
aConvert1.triggered.connect(
|
|
||||||
lambda: self._changeItemLayout(tHandle, nwItemLayout.DOCUMENT)
|
|
||||||
)
|
|
||||||
|
|
||||||
if isDocFile:
|
|
||||||
aConvert2 = mTrans.addAction(self.tr("Convert to {0}").format(trNote))
|
|
||||||
aConvert2.triggered.connect(
|
|
||||||
lambda: self._changeItemLayout(tHandle, nwItemLayout.NOTE)
|
|
||||||
)
|
|
||||||
|
|
||||||
if isFolder and tItem.documentAllowed():
|
|
||||||
aConvert3 = mTrans.addAction(self.tr("Convert to {0}").format(trDoc))
|
|
||||||
aConvert3.triggered.connect(
|
|
||||||
lambda: self._covertFolderToFile(tHandle, nwItemLayout.DOCUMENT)
|
|
||||||
)
|
|
||||||
|
|
||||||
if isFolder:
|
|
||||||
aConvert4 = mTrans.addAction(self.tr("Convert to {0}").format(trNote))
|
|
||||||
aConvert4.triggered.connect(
|
|
||||||
lambda: self._covertFolderToFile(tHandle, nwItemLayout.NOTE)
|
|
||||||
)
|
|
||||||
|
|
||||||
if hasChild and isFile:
|
|
||||||
aMerge1 = mTrans.addAction(self.tr("Merge Child Items into Self"))
|
|
||||||
aMerge1.triggered.connect(lambda: self._mergeDocuments(tHandle, False))
|
|
||||||
aMerge2 = mTrans.addAction(self.tr("Merge Child Items into New"))
|
|
||||||
aMerge2.triggered.connect(lambda: self._mergeDocuments(tHandle, True))
|
|
||||||
|
|
||||||
if hasChild and isFolder:
|
|
||||||
aMerge3 = mTrans.addAction(self.tr("Merge Documents in Folder"))
|
|
||||||
aMerge3.triggered.connect(lambda: self._mergeDocuments(tHandle, True))
|
|
||||||
|
|
||||||
if isFile:
|
|
||||||
aSplit1 = mTrans.addAction(self.tr("Split Document by Headers"))
|
|
||||||
aSplit1.triggered.connect(lambda: self._splitDocument(tHandle))
|
|
||||||
|
|
||||||
# Expand/Collapse/Delete/Duplicate
|
|
||||||
# ================================
|
|
||||||
|
|
||||||
ctxMenu.addSeparator()
|
|
||||||
|
|
||||||
if hasChild:
|
|
||||||
aExpand = ctxMenu.addAction(self.tr("Expand All"))
|
|
||||||
aExpand.triggered.connect(lambda: self.setExpandedFromHandle(tHandle, True))
|
|
||||||
aCollapse = ctxMenu.addAction(self.tr("Collapse All"))
|
|
||||||
aCollapse.triggered.connect(lambda: self.setExpandedFromHandle(tHandle, False))
|
|
||||||
aDuplicate = ctxMenu.addAction(self.tr("Duplicate from Here"))
|
|
||||||
aDuplicate.triggered.connect(lambda: self._duplicateFromHandle(tHandle))
|
|
||||||
elif isFile:
|
|
||||||
aDuplicate = ctxMenu.addAction(self.tr("Duplicate Document"))
|
|
||||||
aDuplicate.triggered.connect(lambda: self._duplicateFromHandle(tHandle))
|
|
||||||
|
|
||||||
if tItem.itemClass == nwItemClass.TRASH or isRoot or (isFolder and not hasChild):
|
|
||||||
aDelete = ctxMenu.addAction(self.tr("Delete Permanently"))
|
|
||||||
aDelete.triggered.connect(lambda: self.permDeleteItem(tHandle))
|
|
||||||
else:
|
|
||||||
aMoveTrash = ctxMenu.addAction(self.tr("Move to Trash"))
|
|
||||||
aMoveTrash.triggered.connect(lambda: self.moveItemToTrash(tHandle))
|
|
||||||
|
|
||||||
# Show Context Menu
|
|
||||||
ctxMenu.exec_(self.viewport().mapToGlobal(clickPos))
|
ctxMenu.exec_(self.viewport().mapToGlobal(clickPos))
|
||||||
|
ctxMenu.deleteLater()
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -1482,15 +1349,6 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
"""Return the QTreeWidgetItem of a given item handle."""
|
"""Return the QTreeWidgetItem of a given item handle."""
|
||||||
return self._treeMap.get(tHandle, None) if tHandle else None
|
return self._treeMap.get(tHandle, None) if tHandle else None
|
||||||
|
|
||||||
def _toggleItemActive(self, tHandle: str) -> None:
|
|
||||||
"""Toggle the active status of an item."""
|
|
||||||
tItem = SHARED.project.tree[tHandle]
|
|
||||||
if tItem is not None:
|
|
||||||
tItem.setActive(not tItem.isActive)
|
|
||||||
self.setTreeItemValues(tItem.itemHandle)
|
|
||||||
self._alertTreeChange(tHandle, flush=False)
|
|
||||||
return
|
|
||||||
|
|
||||||
def _recursiveSetExpanded(self, trItem: QTreeWidgetItem, isExpanded: bool) -> None:
|
def _recursiveSetExpanded(self, trItem: QTreeWidgetItem, isExpanded: bool) -> None:
|
||||||
"""Recursive function to set expanded status starting from (and
|
"""Recursive function to set expanded status starting from (and
|
||||||
not including) a given item.
|
not including) a given item.
|
||||||
@@ -1503,60 +1361,6 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
self._recursiveSetExpanded(chItem, isExpanded)
|
self._recursiveSetExpanded(chItem, isExpanded)
|
||||||
return
|
return
|
||||||
|
|
||||||
def _changeItemStatus(self, tHandle: str, tStatus: str) -> None:
|
|
||||||
"""Set a new status value of an item."""
|
|
||||||
tItem = SHARED.project.tree[tHandle]
|
|
||||||
if tItem and tItem.isDocumentLayout():
|
|
||||||
tItem.setStatus(tStatus)
|
|
||||||
self.setTreeItemValues(tItem.itemHandle)
|
|
||||||
self._alertTreeChange(tHandle, flush=False)
|
|
||||||
return
|
|
||||||
|
|
||||||
def _changeItemImport(self, tHandle: str, tImport: str) -> None:
|
|
||||||
"""Set a new importance value of an item."""
|
|
||||||
tItem = SHARED.project.tree[tHandle]
|
|
||||||
if tItem and tItem.isNoteLayout():
|
|
||||||
tItem.setImport(tImport)
|
|
||||||
self.setTreeItemValues(tItem.itemHandle)
|
|
||||||
self._alertTreeChange(tHandle, flush=False)
|
|
||||||
return
|
|
||||||
|
|
||||||
def _changeItemLayout(self, tHandle: str, itemLayout: nwItemLayout) -> None:
|
|
||||||
"""Set a new item layout value of an item."""
|
|
||||||
tItem = SHARED.project.tree[tHandle]
|
|
||||||
if tItem is not None:
|
|
||||||
if itemLayout == nwItemLayout.DOCUMENT and tItem.documentAllowed():
|
|
||||||
tItem.setLayout(nwItemLayout.DOCUMENT)
|
|
||||||
self.setTreeItemValues(tHandle)
|
|
||||||
self._alertTreeChange(tHandle, flush=False)
|
|
||||||
elif itemLayout == nwItemLayout.NOTE:
|
|
||||||
tItem.setLayout(nwItemLayout.NOTE)
|
|
||||||
self.setTreeItemValues(tHandle)
|
|
||||||
self._alertTreeChange(tHandle, flush=False)
|
|
||||||
return
|
|
||||||
|
|
||||||
def _covertFolderToFile(self, tHandle: str, itemLayout: nwItemLayout) -> None:
|
|
||||||
"""Convert a folder to a note or document."""
|
|
||||||
tItem = SHARED.project.tree[tHandle]
|
|
||||||
if tItem is not None and tItem.isFolderType():
|
|
||||||
msgYes = SHARED.question(self.tr(
|
|
||||||
"Do you want to convert the folder to a {0}? "
|
|
||||||
"This action cannot be reversed."
|
|
||||||
).format(trConst(nwLabels.LAYOUT_NAME[itemLayout])))
|
|
||||||
if msgYes and itemLayout == nwItemLayout.DOCUMENT and tItem.documentAllowed():
|
|
||||||
tItem.setType(nwItemType.FILE)
|
|
||||||
tItem.setLayout(nwItemLayout.DOCUMENT)
|
|
||||||
self.setTreeItemValues(tHandle)
|
|
||||||
self._alertTreeChange(tHandle, flush=False)
|
|
||||||
elif msgYes and itemLayout == nwItemLayout.NOTE:
|
|
||||||
tItem.setType(nwItemType.FILE)
|
|
||||||
tItem.setLayout(nwItemLayout.NOTE)
|
|
||||||
self.setTreeItemValues(tHandle)
|
|
||||||
self._alertTreeChange(tHandle, flush=False)
|
|
||||||
else:
|
|
||||||
logger.info("Folder conversion cancelled")
|
|
||||||
return
|
|
||||||
|
|
||||||
def _mergeDocuments(self, tHandle: str, newFile: bool) -> bool:
|
def _mergeDocuments(self, tHandle: str, newFile: bool) -> bool:
|
||||||
"""Merge an item's child documents into a single document."""
|
"""Merge an item's child documents into a single document."""
|
||||||
logger.info("Request to merge items under handle '%s'", tHandle)
|
logger.info("Request to merge items under handle '%s'", tHandle)
|
||||||
@@ -1822,87 +1626,322 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# END Class GuiProjectTree
|
||||||
|
|
||||||
|
|
||||||
|
class _TreeContextMenu(QMenu):
|
||||||
|
|
||||||
|
def __init__(self, projTree: GuiProjectTree, nwItem: NWItem) -> None:
|
||||||
|
super().__init__(parent=projTree)
|
||||||
|
|
||||||
|
self.projTree = projTree
|
||||||
|
self.projView = projTree.projView
|
||||||
|
|
||||||
|
self._item = nwItem
|
||||||
|
self._handle = nwItem.itemHandle
|
||||||
|
self._items: list[str] = []
|
||||||
|
|
||||||
|
logger.debug("Ready: _TreeContextMenu")
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
def __del__(self): # pragma: no cover
|
||||||
|
logger.debug("Delete: _TreeContextMenu")
|
||||||
|
return
|
||||||
|
|
||||||
##
|
##
|
||||||
# Multi-Select Context Menu Functions
|
# Methods
|
||||||
##
|
##
|
||||||
|
|
||||||
def _openMultiSelectContextMenu(self, clickPos: QPoint, tItem: NWItem) -> bool:
|
def buildTrashMenu(self) -> None:
|
||||||
"""Alternative menu for multiple selected items."""
|
"""Build the special menu for the Trash folder."""
|
||||||
ctxMenu = QMenu(self)
|
action = self.addAction(self.tr("Empty Trash"))
|
||||||
|
action.triggered.connect(self.projTree.emptyTrash)
|
||||||
|
return
|
||||||
|
|
||||||
mActive = ctxMenu.addMenu(self.tr("Set Active to ..."))
|
def buildSingleSelectMenu(self, hasChild: bool) -> None:
|
||||||
aActive = mActive.addAction(SHARED.theme.getIcon("checked"), self.tr("Active"))
|
"""Build the single-select menu."""
|
||||||
aActive.triggered.connect(lambda: self._iterItemActive(True))
|
isFile = self._item.isFileType()
|
||||||
aInactive = mActive.addAction(SHARED.theme.getIcon("unchecked"), self.tr("Inactive"))
|
isFolder = self._item.isFolderType()
|
||||||
aInactive.triggered.connect(lambda: self._iterItemActive(False))
|
isRoot = self._item.isRootType()
|
||||||
|
|
||||||
if tItem.isNovelLike():
|
# Document Actions
|
||||||
mStatus = ctxMenu.addMenu(self.tr("Set Status to ..."))
|
if isFile:
|
||||||
|
self._docActions()
|
||||||
|
self.addSeparator()
|
||||||
|
|
||||||
|
# Edit Item Settings
|
||||||
|
aLabel = self.addAction(self.tr("Rename"))
|
||||||
|
aLabel.triggered.connect(lambda: self.projTree.renameTreeItem(self._handle))
|
||||||
|
if isFile:
|
||||||
|
self._itemActive(False)
|
||||||
|
self._itemStatusImport(False)
|
||||||
|
|
||||||
|
# Transform Item
|
||||||
|
if isFile or isFolder:
|
||||||
|
self._itemTransform(isFile, isFolder, hasChild)
|
||||||
|
self.addSeparator()
|
||||||
|
|
||||||
|
# Process Item
|
||||||
|
self._itemProcess(isFile, isFolder, isRoot, hasChild)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
def buildMultiSelectMenu(self, items: list[str]) -> None:
|
||||||
|
"""Build the multi-select menu."""
|
||||||
|
self._items = items
|
||||||
|
self._itemActive(True)
|
||||||
|
self._itemStatusImport(True)
|
||||||
|
self.addSeparator()
|
||||||
|
self._moveToTrash(True)
|
||||||
|
return
|
||||||
|
|
||||||
|
##
|
||||||
|
# Menu Builders
|
||||||
|
##
|
||||||
|
|
||||||
|
def _docActions(self) -> None:
|
||||||
|
"""Add document actions."""
|
||||||
|
action = self.addAction(self.tr("Open Document"))
|
||||||
|
action.triggered.connect(
|
||||||
|
lambda: self.projView.openDocumentRequest.emit(self._handle, nwDocMode.EDIT, "", True)
|
||||||
|
)
|
||||||
|
action = self.addAction(self.tr("View Document"))
|
||||||
|
action.triggered.connect(
|
||||||
|
lambda: self.projView.openDocumentRequest.emit(self._handle, nwDocMode.VIEW, "", False)
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
def _itemActive(self, multi: bool) -> None:
|
||||||
|
"""Add Active/Inactive actions."""
|
||||||
|
if multi:
|
||||||
|
mSub = self.addMenu(self.tr("Set Active to ..."))
|
||||||
|
aOne = mSub.addAction(SHARED.theme.getIcon("checked"), self.tr("Active"))
|
||||||
|
aOne.triggered.connect(lambda: self._iterItemActive(True))
|
||||||
|
aTwo = mSub.addAction(SHARED.theme.getIcon("unchecked"), self.tr("Inactive"))
|
||||||
|
aTwo.triggered.connect(lambda: self._iterItemActive(False))
|
||||||
|
else:
|
||||||
|
action = self.addAction(self.tr("Toggle Active"))
|
||||||
|
action.triggered.connect(self._toggleItemActive)
|
||||||
|
return
|
||||||
|
|
||||||
|
def _itemStatusImport(self, multi: bool) -> None:
|
||||||
|
"""Add actions for changing status or importance."""
|
||||||
|
if self._item.isNovelLike():
|
||||||
|
menu = self.addMenu(self.tr("Set Status to ..."))
|
||||||
|
current = self._item.itemStatus
|
||||||
for n, (key, entry) in enumerate(SHARED.project.data.itemStatus.items()):
|
for n, (key, entry) in enumerate(SHARED.project.data.itemStatus.items()):
|
||||||
aStatus = mStatus.addAction(entry["icon"], entry["name"])
|
name = entry["name"]
|
||||||
aStatus.triggered.connect(lambda n, key=key: self._iterSetItemStatus(key))
|
if not multi and current == key:
|
||||||
mStatus.addSeparator()
|
name += f" ({nwUnicode.U_CHECK})"
|
||||||
aManage1 = mStatus.addAction(self.tr("Manage Labels ..."))
|
action = menu.addAction(entry["icon"], name)
|
||||||
aManage1.triggered.connect(
|
if multi:
|
||||||
|
action.triggered.connect(lambda n, key=key: self._iterSetItemStatus(key))
|
||||||
|
else:
|
||||||
|
action.triggered.connect(lambda n, key=key: self._changeItemStatus(key))
|
||||||
|
menu.addSeparator()
|
||||||
|
action = menu.addAction(self.tr("Manage Labels ..."))
|
||||||
|
action.triggered.connect(
|
||||||
lambda: self.projView.projectSettingsRequest.emit(GuiProjectSettings.TAB_STATUS)
|
lambda: self.projView.projectSettingsRequest.emit(GuiProjectSettings.TAB_STATUS)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
mImport = ctxMenu.addMenu(self.tr("Set Importance to ..."))
|
menu = self.addMenu(self.tr("Set Importance to ..."))
|
||||||
|
current = self._item.itemImport
|
||||||
for n, (key, entry) in enumerate(SHARED.project.data.itemImport.items()):
|
for n, (key, entry) in enumerate(SHARED.project.data.itemImport.items()):
|
||||||
aImport = mImport.addAction(entry["icon"], entry["name"], )
|
name = entry["name"]
|
||||||
aImport.triggered.connect(lambda n, key=key: self._iterSetItemImport(key))
|
if not multi and current == key:
|
||||||
mImport.addSeparator()
|
name += f" ({nwUnicode.U_CHECK})"
|
||||||
aManage2 = mImport.addAction(self.tr("Manage Labels ..."))
|
action = menu.addAction(entry["icon"], name)
|
||||||
aManage2.triggered.connect(
|
if multi:
|
||||||
|
action.triggered.connect(lambda n, key=key: self._iterSetItemImport(key))
|
||||||
|
else:
|
||||||
|
action.triggered.connect(lambda n, key=key: self._changeItemImport(key))
|
||||||
|
menu.addSeparator()
|
||||||
|
action = menu.addAction(self.tr("Manage Labels ..."))
|
||||||
|
action.triggered.connect(
|
||||||
lambda: self.projView.projectSettingsRequest.emit(GuiProjectSettings.TAB_IMPORT)
|
lambda: self.projView.projectSettingsRequest.emit(GuiProjectSettings.TAB_IMPORT)
|
||||||
)
|
)
|
||||||
|
|
||||||
ctxMenu.addSeparator()
|
|
||||||
|
|
||||||
aMoveTrash = ctxMenu.addAction(self.tr("Move to Trash"))
|
|
||||||
aMoveTrash.triggered.connect(self._iterMoveToTrash)
|
|
||||||
|
|
||||||
# Show Context Menu
|
|
||||||
ctxMenu.exec_(self.viewport().mapToGlobal(clickPos))
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def _iterItemActive(self, isActive: bool) -> None:
|
|
||||||
"""Change the active status multiple items."""
|
|
||||||
for item in self.selectedItems():
|
|
||||||
tHandle = str(item.data(self.C_DATA, self.D_HANDLE))
|
|
||||||
tItem = SHARED.project.tree[tHandle]
|
|
||||||
if tItem and tItem.isFileType():
|
|
||||||
tItem.setActive(isActive)
|
|
||||||
self.setTreeItemValues(tItem.itemHandle)
|
|
||||||
self._alertTreeChange(tHandle, flush=False)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def _iterSetItemStatus(self, tStatus: str) -> None:
|
def _itemTransform(self, isFile: bool, isFolder: bool, hasChild: bool) -> None:
|
||||||
"""Change the status value for multiple items."""
|
"""Add actions for the Transform menu."""
|
||||||
for item in self.selectedItems():
|
menu = self.addMenu(self.tr("Transform"))
|
||||||
tHandle = str(item.data(self.C_DATA, self.D_HANDLE))
|
|
||||||
self._changeItemStatus(tHandle, tStatus)
|
tree = self.projTree
|
||||||
|
tHandle = self._handle
|
||||||
|
|
||||||
|
trDoc = trConst(nwLabels.LAYOUT_NAME[nwItemLayout.DOCUMENT])
|
||||||
|
trNote = trConst(nwLabels.LAYOUT_NAME[nwItemLayout.NOTE])
|
||||||
|
loDoc = nwItemLayout.DOCUMENT
|
||||||
|
loNote = nwItemLayout.NOTE
|
||||||
|
isDocFile = isFile and self._item.isDocumentLayout()
|
||||||
|
isNoteFile = isFile and self._item.isNoteLayout()
|
||||||
|
|
||||||
|
if isNoteFile and self._item.documentAllowed():
|
||||||
|
action = menu.addAction(self.tr("Convert to {0}").format(trDoc))
|
||||||
|
action.triggered.connect(lambda: self._changeItemLayout(loDoc))
|
||||||
|
|
||||||
|
if isDocFile:
|
||||||
|
action = menu.addAction(self.tr("Convert to {0}").format(trNote))
|
||||||
|
action.triggered.connect(lambda: self._changeItemLayout(loNote))
|
||||||
|
|
||||||
|
if isFolder and self._item.documentAllowed():
|
||||||
|
action = menu.addAction(self.tr("Convert to {0}").format(trDoc))
|
||||||
|
action.triggered.connect(lambda: self._covertFolderToFile(loDoc))
|
||||||
|
|
||||||
|
if isFolder:
|
||||||
|
action = menu.addAction(self.tr("Convert to {0}").format(trNote))
|
||||||
|
action.triggered.connect(lambda: self._covertFolderToFile(loNote))
|
||||||
|
|
||||||
|
if hasChild and isFile:
|
||||||
|
action = menu.addAction(self.tr("Merge Child Items into Self"))
|
||||||
|
action.triggered.connect(lambda: tree._mergeDocuments(tHandle, False))
|
||||||
|
action = menu.addAction(self.tr("Merge Child Items into New"))
|
||||||
|
action.triggered.connect(lambda: tree._mergeDocuments(tHandle, True))
|
||||||
|
|
||||||
|
if hasChild and isFolder:
|
||||||
|
action = menu.addAction(self.tr("Merge Documents in Folder"))
|
||||||
|
action.triggered.connect(lambda: tree._mergeDocuments(tHandle, True))
|
||||||
|
|
||||||
|
if isFile:
|
||||||
|
action = menu.addAction(self.tr("Split Document by Headers"))
|
||||||
|
action.triggered.connect(lambda: tree._splitDocument(tHandle))
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def _iterSetItemImport(self, tStatus: str) -> None:
|
def _itemProcess(self, isFile: bool, isFolder: bool, isRoot: bool, hasChild: bool) -> None:
|
||||||
"""Change the importance value for multiple items."""
|
"""Add actions for item processing."""
|
||||||
for item in self.selectedItems():
|
tree = self.projTree
|
||||||
tHandle = str(item.data(self.C_DATA, self.D_HANDLE))
|
tHandle = self._handle
|
||||||
self._changeItemImport(tHandle, tStatus)
|
if hasChild:
|
||||||
|
action = self.addAction(self.tr("Expand All"))
|
||||||
|
action.triggered.connect(lambda: tree.setExpandedFromHandle(tHandle, True))
|
||||||
|
action = self.addAction(self.tr("Collapse All"))
|
||||||
|
action.triggered.connect(lambda: tree.setExpandedFromHandle(tHandle, False))
|
||||||
|
action = self.addAction(self.tr("Duplicate from Here"))
|
||||||
|
action.triggered.connect(lambda: tree._duplicateFromHandle(tHandle))
|
||||||
|
elif isFile:
|
||||||
|
action = self.addAction(self.tr("Duplicate Document"))
|
||||||
|
action.triggered.connect(lambda: tree._duplicateFromHandle(tHandle))
|
||||||
|
|
||||||
|
if self._item.itemClass == nwItemClass.TRASH or isRoot or (isFolder and not hasChild):
|
||||||
|
action = self.addAction(self.tr("Delete Permanently"))
|
||||||
|
action.triggered.connect(lambda: tree.permDeleteItem(tHandle))
|
||||||
|
else:
|
||||||
|
action = self.addAction(self.tr("Move to Trash"))
|
||||||
|
action.triggered.connect(lambda: tree.moveItemToTrash(tHandle))
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def _moveToTrash(self, multi: bool) -> None:
|
||||||
|
"""Add move to Trash action."""
|
||||||
|
action = self.addAction(self.tr("Move to Trash"))
|
||||||
|
if multi:
|
||||||
|
action.triggered.connect(self._iterMoveToTrash)
|
||||||
|
return
|
||||||
|
|
||||||
|
##
|
||||||
|
# Private Slots
|
||||||
|
##
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def _iterMoveToTrash(self) -> None:
|
def _iterMoveToTrash(self) -> None:
|
||||||
"""Iterate through files and move them to Trash."""
|
"""Iterate through files and move them to Trash."""
|
||||||
items = self.selectedItems()
|
if SHARED.question(self.tr("Move {0} items to Trash?").format(len(self._items))):
|
||||||
if SHARED.question(self.tr("Move {0} items to Trash?").format(len(items))):
|
for tHandle in self._items:
|
||||||
for item in self.selectedItems():
|
|
||||||
tHandle = str(item.data(self.C_DATA, self.D_HANDLE))
|
|
||||||
tItem = SHARED.project.tree[tHandle]
|
tItem = SHARED.project.tree[tHandle]
|
||||||
if tItem and tItem.isFileType():
|
if tItem and tItem.isFileType():
|
||||||
self.moveItemToTrash(tHandle, askFirst=False, flush=False)
|
self.projTree.moveItemToTrash(tHandle, askFirst=False, flush=False)
|
||||||
self.saveTreeOrder()
|
self.projTree.saveTreeOrder()
|
||||||
return
|
return
|
||||||
|
|
||||||
# END Class GuiProjectTree
|
@pyqtSlot()
|
||||||
|
def _toggleItemActive(self) -> None:
|
||||||
|
"""Toggle the active status of an item."""
|
||||||
|
self._item.setActive(not self._item.isActive)
|
||||||
|
self.projTree.setTreeItemValues(self._handle)
|
||||||
|
self.projTree._alertTreeChange(self._handle, flush=False)
|
||||||
|
return
|
||||||
|
|
||||||
|
##
|
||||||
|
# Internal Functions
|
||||||
|
##
|
||||||
|
|
||||||
|
def _iterItemActive(self, isActive: bool) -> None:
|
||||||
|
"""Set the active status of multiple items."""
|
||||||
|
for tHandle in self._items:
|
||||||
|
tItem = SHARED.project.tree[tHandle]
|
||||||
|
if tItem and tItem.isFileType():
|
||||||
|
tItem.setActive(isActive)
|
||||||
|
self.projTree.setTreeItemValues(tHandle)
|
||||||
|
self.projTree._alertTreeChange(tHandle, flush=False)
|
||||||
|
return
|
||||||
|
|
||||||
|
def _changeItemStatus(self, key: str) -> None:
|
||||||
|
"""Set a new status value of an item."""
|
||||||
|
self._item.setStatus(key)
|
||||||
|
self.projTree.setTreeItemValues(self._handle)
|
||||||
|
self.projTree._alertTreeChange(self._handle, flush=False)
|
||||||
|
return
|
||||||
|
|
||||||
|
def _iterSetItemStatus(self, key: str) -> None:
|
||||||
|
"""Change the status value for multiple items."""
|
||||||
|
for tHandle in self._items:
|
||||||
|
tItem = SHARED.project.tree[tHandle]
|
||||||
|
if tItem and tItem.isNovelLike():
|
||||||
|
tItem.setStatus(key)
|
||||||
|
self.projTree.setTreeItemValues(tHandle)
|
||||||
|
self.projTree._alertTreeChange(tHandle, flush=False)
|
||||||
|
return
|
||||||
|
|
||||||
|
def _changeItemImport(self, key: str) -> None:
|
||||||
|
"""Set a new importance value of an item."""
|
||||||
|
self._item.setImport(key)
|
||||||
|
self.projTree.setTreeItemValues(self._handle)
|
||||||
|
self.projTree._alertTreeChange(self._handle, flush=False)
|
||||||
|
return
|
||||||
|
|
||||||
|
def _iterSetItemImport(self, key: str) -> None:
|
||||||
|
"""Change the status value for multiple items."""
|
||||||
|
for tHandle in self._items:
|
||||||
|
tItem = SHARED.project.tree[tHandle]
|
||||||
|
if tItem and not tItem.isNovelLike():
|
||||||
|
tItem.setImport(key)
|
||||||
|
self.projTree.setTreeItemValues(tHandle)
|
||||||
|
self.projTree._alertTreeChange(tHandle, flush=False)
|
||||||
|
return
|
||||||
|
|
||||||
|
def _changeItemLayout(self, itemLayout: nwItemLayout) -> None:
|
||||||
|
"""Set a new item layout value of an item."""
|
||||||
|
if itemLayout == nwItemLayout.DOCUMENT and self._item.documentAllowed():
|
||||||
|
self._item.setLayout(nwItemLayout.DOCUMENT)
|
||||||
|
self.projTree.setTreeItemValues(self._handle)
|
||||||
|
self.projTree._alertTreeChange(self._handle, flush=False)
|
||||||
|
elif itemLayout == nwItemLayout.NOTE:
|
||||||
|
self._item.setLayout(nwItemLayout.NOTE)
|
||||||
|
self.projTree.setTreeItemValues(self._handle)
|
||||||
|
self.projTree._alertTreeChange(self._handle, flush=False)
|
||||||
|
return
|
||||||
|
|
||||||
|
def _covertFolderToFile(self, itemLayout: nwItemLayout) -> None:
|
||||||
|
"""Convert a folder to a note or document."""
|
||||||
|
if self._item.isFolderType():
|
||||||
|
msgYes = SHARED.question(self.tr(
|
||||||
|
"Do you want to convert the folder to a {0}? "
|
||||||
|
"This action cannot be reversed."
|
||||||
|
).format(trConst(nwLabels.LAYOUT_NAME[itemLayout])))
|
||||||
|
if msgYes and itemLayout == nwItemLayout.DOCUMENT and self._item.documentAllowed():
|
||||||
|
self._item.setType(nwItemType.FILE)
|
||||||
|
self._item.setLayout(nwItemLayout.DOCUMENT)
|
||||||
|
self.projTree.setTreeItemValues(self._handle)
|
||||||
|
self.projTree._alertTreeChange(self._handle, flush=False)
|
||||||
|
elif msgYes and itemLayout == nwItemLayout.NOTE:
|
||||||
|
self._item.setType(nwItemType.FILE)
|
||||||
|
self._item.setLayout(nwItemLayout.NOTE)
|
||||||
|
self.projTree.setTreeItemValues(self._handle)
|
||||||
|
self.projTree._alertTreeChange(self._handle, flush=False)
|
||||||
|
else:
|
||||||
|
logger.info("Folder conversion cancelled")
|
||||||
|
return
|
||||||
|
|
||||||
|
# END Class _TreeContextMenu
|
||||||
|
|||||||
Reference in New Issue
Block a user