Handle item consistency in the nodes tree
This commit is contained in:
@@ -45,7 +45,6 @@ COL_MASK = 0x0100
|
||||
|
||||
C_LABEL_TEXT = 0x0000 | Qt.ItemDataRole.DisplayRole
|
||||
C_LABEL_ICON = 0x0000 | Qt.ItemDataRole.DecorationRole
|
||||
C_LABEL_TIP = 0x0000 | Qt.ItemDataRole.ToolTipRole
|
||||
C_COUNT_TEXT = 0x0100 | Qt.ItemDataRole.DisplayRole
|
||||
C_COUNT_ICON = 0x0100 | Qt.ItemDataRole.DecorationRole
|
||||
C_COUNT_ALIGN = 0x0100 | Qt.ItemDataRole.TextAlignmentRole
|
||||
@@ -95,34 +94,31 @@ class ProjectNode:
|
||||
##
|
||||
|
||||
def refresh(self) -> None:
|
||||
cache: dict[int, str | QIcon | Qt.AlignmentFlag] = {}
|
||||
|
||||
"""Refresh data values."""
|
||||
# Label
|
||||
cache[C_LABEL_ICON] = SHARED.theme.getItemIcon(
|
||||
self._cache[C_LABEL_ICON] = SHARED.theme.getItemIcon(
|
||||
self._item.itemType, self._item.itemClass,
|
||||
self._item.itemLayout, self._item.mainHeading
|
||||
)
|
||||
cache[C_LABEL_TEXT] = self._item.itemName
|
||||
cache[C_LABEL_TIP] = self._item.itemName
|
||||
self._cache[C_LABEL_TEXT] = self._item.itemName
|
||||
|
||||
# Count
|
||||
cache[C_COUNT_ALIGN] = QtAlignRight
|
||||
self._cache[C_COUNT_ALIGN] = QtAlignRight
|
||||
|
||||
# Active
|
||||
if self._item.isFileType():
|
||||
if self._item.isActive:
|
||||
cache[C_ACTIVE_ICON] = SHARED.theme.getIcon("checked")
|
||||
self._cache[C_ACTIVE_ICON] = SHARED.theme.getIcon("checked")
|
||||
else:
|
||||
cache[C_ACTIVE_ICON] = SHARED.theme.getIcon("unchecked")
|
||||
self._cache[C_ACTIVE_ICON] = SHARED.theme.getIcon("unchecked")
|
||||
else:
|
||||
cache[C_ACTIVE_ICON] = SHARED.theme.getIcon("noncheckable")
|
||||
self._cache[C_ACTIVE_ICON] = SHARED.theme.getIcon("noncheckable")
|
||||
|
||||
# Status
|
||||
sText, sIcon = self._item.getImportStatus()
|
||||
cache[C_STATUS_ICON] = sIcon
|
||||
cache[C_STATUS_TIP] = sText
|
||||
self._cache[C_STATUS_ICON] = sIcon
|
||||
self._cache[C_STATUS_TIP] = sText
|
||||
|
||||
self._cache = cache
|
||||
self.updateCount()
|
||||
|
||||
return
|
||||
@@ -145,7 +141,7 @@ class ProjectNode:
|
||||
return len(self._children)
|
||||
|
||||
def data(self, column: int, role: Qt.ItemDataRole) -> T_NodeData:
|
||||
""""""
|
||||
"""Return cached node data."""
|
||||
return self._cache.get(COL_MASK*column | role)
|
||||
|
||||
def parent(self) -> ProjectNode | None:
|
||||
@@ -157,6 +153,7 @@ class ProjectNode:
|
||||
return None
|
||||
|
||||
def allChildren(self) -> list[ProjectNode]:
|
||||
"""Return a recursive list of all children."""
|
||||
nodes: list[ProjectNode] = []
|
||||
self._recursiveAppendChildren(nodes)
|
||||
return nodes
|
||||
@@ -168,15 +165,24 @@ class ProjectNode:
|
||||
def addChild(self, child: ProjectNode, pos: int = -1) -> None:
|
||||
"""Add a child item to this item."""
|
||||
child._parent = self
|
||||
self._updateRelationships(child)
|
||||
if 0 <= pos < len(self._children):
|
||||
self._children.insert(pos, child)
|
||||
self._refreshChildrenPos()
|
||||
else:
|
||||
child._row = len(self._children)
|
||||
self._children.append(child)
|
||||
self.refresh()
|
||||
self.updateCount()
|
||||
return
|
||||
|
||||
def takeChild(self, pos: int) -> ProjectNode | None:
|
||||
"""Remove a child item and return it."""
|
||||
if 0 <= pos < len(self._children):
|
||||
node = self._children.pop()
|
||||
self._refreshChildrenPos()
|
||||
return node
|
||||
return None
|
||||
|
||||
def moveChild(self, source: int, target: int) -> None:
|
||||
"""Move a child internally."""
|
||||
count = len(self._children)
|
||||
@@ -203,6 +209,16 @@ class ProjectNode:
|
||||
child._row = n
|
||||
return
|
||||
|
||||
def _updateRelationships(self, child: ProjectNode) -> None:
|
||||
"""Update a child item's relationships."""
|
||||
if self._parent:
|
||||
child.item.setParent(self.item.itemHandle)
|
||||
child.item.setRoot(self.item.itemRoot)
|
||||
else:
|
||||
child.item.setParent(None)
|
||||
child.item.setRoot(child.item.itemHandle)
|
||||
return
|
||||
|
||||
|
||||
class ProjectModel(QAbstractItemModel):
|
||||
|
||||
|
||||
@@ -87,10 +87,10 @@ class NWTree:
|
||||
# Properties
|
||||
##
|
||||
|
||||
# @property
|
||||
# def trashRoot(self) -> str | None:
|
||||
# """Return the handle of the trash folder, or None."""
|
||||
# return self._trash
|
||||
@property
|
||||
def trash(self) -> ProjectNode | None:
|
||||
"""Return trash node, if it exists."""
|
||||
return self._getTrashNode()
|
||||
|
||||
@property
|
||||
def model(self) -> ProjectModel:
|
||||
@@ -158,7 +158,6 @@ class NWTree:
|
||||
nwItem.setType(itemType)
|
||||
nwItem.setClass(itemClass)
|
||||
if self.add(nwItem, pos):
|
||||
self.updateItemData(tHandle)
|
||||
self._project.setProjectChanged(True)
|
||||
return tHandle
|
||||
return None
|
||||
@@ -238,23 +237,15 @@ class NWTree:
|
||||
mark recovered files.
|
||||
"""
|
||||
storage = self._project.storage
|
||||
files = set(storage.scanContent())
|
||||
for tHandle in self._nodes:
|
||||
if self.updateItemData(tHandle):
|
||||
logger.debug("Checking item '%s' ... OK", tHandle)
|
||||
files.discard(tHandle) # Remove it from the record
|
||||
else:
|
||||
logger.error("Checking item '%s' ... ERROR", tHandle)
|
||||
self.__delitem__(tHandle) # The file will be re-added as orphaned
|
||||
|
||||
orphans = len(files)
|
||||
remains = set(storage.scanContent()).difference(set(self._nodes.keys()))
|
||||
orphans = len(remains)
|
||||
if orphans == 0:
|
||||
logger.info("Checked project files: OK")
|
||||
return 0, 0
|
||||
|
||||
logger.warning("Found %d file(s) not tracked in project", orphans)
|
||||
recovered = 0
|
||||
for cHandle in files:
|
||||
for cHandle in remains:
|
||||
aDoc = storage.getDocument(cHandle)
|
||||
aDoc.readDocument(isOrphan=True)
|
||||
oName, oParent, oClass, oLayout = aDoc.getMeta()
|
||||
@@ -282,7 +273,6 @@ class NWTree:
|
||||
newItem.setClass(oClass)
|
||||
newItem.setLayout(oLayout)
|
||||
if self.add(newItem):
|
||||
self.updateItemData(cHandle)
|
||||
recovered += 1
|
||||
|
||||
return orphans, recovered
|
||||
@@ -346,25 +336,6 @@ class NWTree:
|
||||
# Tree Item Methods
|
||||
##
|
||||
|
||||
def updateItemData(self, tHandle: str) -> bool:
|
||||
"""Update the root item handle of a given item. Returns True if
|
||||
a root was found and data updated, otherwise False.
|
||||
"""
|
||||
if tItem := self._items.get(tHandle):
|
||||
iItem = tItem
|
||||
for _ in range(MAX_DEPTH):
|
||||
if iItem.itemParent is None:
|
||||
tItem.setRoot(iItem.itemHandle)
|
||||
tItem.setClassDefaults(iItem.itemClass)
|
||||
return True
|
||||
else:
|
||||
iItem = self.__getitem__(iItem.itemParent)
|
||||
if iItem is None:
|
||||
return False
|
||||
else:
|
||||
raise RecursionError("Critical internal error")
|
||||
return False
|
||||
|
||||
def checkType(self, tHandle: str, itemType: nwItemType) -> bool:
|
||||
"""Check if item exists and is of the specified item type."""
|
||||
tItem = self.__getitem__(tHandle)
|
||||
@@ -515,8 +486,6 @@ class NWTree:
|
||||
elif pHandle in items:
|
||||
remains[handle] = item
|
||||
logger.warning("Item '%s' found before its parent", handle)
|
||||
else:
|
||||
logger.error("Item '%s' has no parent in current tree", handle)
|
||||
elif item.isRootType():
|
||||
node = ProjectNode(item)
|
||||
self._model.root.addChild(node)
|
||||
|
||||
+314
-361
@@ -39,12 +39,11 @@ from PyQt5.QtWidgets import (
|
||||
|
||||
from novelwriter import CONFIG, SHARED
|
||||
from novelwriter.common import qtLambda
|
||||
from novelwriter.constants import nwLabels, nwStyles, nwUnicode, trConst
|
||||
from novelwriter.constants import nwLabels, nwStyles, trConst
|
||||
from novelwriter.core.item import NWItem
|
||||
from novelwriter.core.itemmodel import ProjectModel, ProjectNode
|
||||
from novelwriter.dialogs.editlabel import GuiEditLabel
|
||||
from novelwriter.dialogs.projectsettings import GuiProjectSettings
|
||||
from novelwriter.enum import nwDocMode, nwItemClass, nwItemLayout, nwItemType
|
||||
from novelwriter.enum import nwDocMode, nwItemClass, nwItemType
|
||||
from novelwriter.extensions.modified import NIconToolButton
|
||||
from novelwriter.gui.theme import STYLES_MIN_TOOLBUTTON
|
||||
from novelwriter.types import (
|
||||
@@ -526,7 +525,6 @@ class GuiProjectTree(QTreeView):
|
||||
|
||||
# Tree Settings
|
||||
iPx = SHARED.theme.baseIconHeight
|
||||
# cMg = CONFIG.pxInt(6)
|
||||
|
||||
self.setIconSize(SHARED.theme.baseIconSize)
|
||||
self.setFrameStyle(QFrame.Shape.NoFrame)
|
||||
@@ -536,18 +534,6 @@ class GuiProjectTree(QTreeView):
|
||||
self.setAutoExpandDelay(1000)
|
||||
self.setHeaderHidden(True)
|
||||
self.setIndentation(iPx)
|
||||
# self.setColumnCount(4)
|
||||
|
||||
# Lock the column sizes
|
||||
# treeHeader = self.header()
|
||||
# treeHeader.setStretchLastSection(False)
|
||||
# treeHeader.setMinimumSectionSize(iPx + cMg)
|
||||
# treeHeader.setSectionResizeMode(self.C_NAME, QHeaderView.ResizeMode.Stretch)
|
||||
# treeHeader.setSectionResizeMode(self.C_COUNT, QHeaderView.ResizeMode.ResizeToContents)
|
||||
# treeHeader.setSectionResizeMode(self.C_ACTIVE, QHeaderView.ResizeMode.Fixed)
|
||||
# treeHeader.setSectionResizeMode(self.C_STATUS, QHeaderView.ResizeMode.Fixed)
|
||||
# treeHeader.resizeSection(self.C_ACTIVE, iPx + cMg)
|
||||
# treeHeader.resizeSection(self.C_STATUS, iPx + cMg)
|
||||
|
||||
# Allow Move by Drag & Drop
|
||||
# self.setDragEnabled(True)
|
||||
@@ -732,13 +718,15 @@ class GuiProjectTree(QTreeView):
|
||||
if dlgOk:
|
||||
# Add the file or folder
|
||||
if itemType == nwItemType.FILE:
|
||||
tHandle = SHARED.project.newFile(newLabel, sHandle, pos)
|
||||
if tHandle and copyDoc:
|
||||
SHARED.project.copyFileContent(tHandle, copyDoc)
|
||||
elif tHandle and hLevel > 0:
|
||||
SHARED.project.writeNewFile(tHandle, hLevel, not isNote)
|
||||
if tHandle := SHARED.project.newFile(newLabel, sHandle, pos):
|
||||
if copyDoc:
|
||||
SHARED.project.copyFileContent(tHandle, copyDoc)
|
||||
elif hLevel > 0:
|
||||
SHARED.project.writeNewFile(tHandle, hLevel, not isNote)
|
||||
SHARED.project.index.reIndexHandle(tHandle)
|
||||
SHARED.project.tree.refreshItems([tHandle])
|
||||
else:
|
||||
tHandle = SHARED.project.newFolder(newLabel, sHandle, pos)
|
||||
SHARED.project.newFolder(newLabel, sHandle, pos)
|
||||
|
||||
return
|
||||
|
||||
@@ -876,41 +864,6 @@ class GuiProjectTree(QTreeView):
|
||||
# self.revealNewTreeItem(tHandle, wordCount=True)
|
||||
return
|
||||
|
||||
def revealNewTreeItem(self, tHandle: str | None, nHandle: str | None = None,
|
||||
wordCount: bool = False) -> bool:
|
||||
"""Reveal a newly added project item in the project tree."""
|
||||
# nwItem = SHARED.project.tree[tHandle] if tHandle else None
|
||||
# if tHandle is None or nwItem is None:
|
||||
# return False
|
||||
|
||||
# trItem = self._addTreeItem(nwItem, nHandle)
|
||||
# if trItem is None:
|
||||
# return False
|
||||
|
||||
# if nwItem.isFileType() and wordCount:
|
||||
# wC = SHARED.project.index.getCounts(tHandle)[1]
|
||||
# self.propagateCount(tHandle, wC)
|
||||
# self.projView.wordCountsChanged.emit()
|
||||
|
||||
# pHandle = nwItem.itemParent
|
||||
# if pHandle is not None and pHandle in self._treeMap:
|
||||
# self._treeMap[pHandle].setExpanded(True)
|
||||
|
||||
# self._alertTreeChange(tHandle, flush=True)
|
||||
# self.setCurrentItem(trItem)
|
||||
|
||||
return True
|
||||
|
||||
def getTreeFromHandle(self, tHandle: str) -> list[str]:
|
||||
"""Recursively return all the child items starting from a given
|
||||
item handle.
|
||||
"""
|
||||
result = []
|
||||
# tIten = self._getTreeItem(tHandle)
|
||||
# if tIten is not None:
|
||||
# result = self._scanChildren(result, tIten, 0)
|
||||
return result
|
||||
|
||||
def requestDeleteItem(self, tHandle: str | None = None) -> bool:
|
||||
"""Request an item deleted from the project tree. This function
|
||||
can be called on any item, and will check whether to attempt a
|
||||
@@ -1768,358 +1721,358 @@ class _UpdatableMenu(QMenu):
|
||||
return
|
||||
|
||||
|
||||
class _TreeContextMenu(QMenu):
|
||||
# class _TreeContextMenu(QMenu):
|
||||
|
||||
def __init__(self, projTree: GuiProjectTree, nwItem: NWItem) -> None:
|
||||
super().__init__(parent=projTree)
|
||||
# def __init__(self, projTree: GuiProjectTree, nwItem: NWItem) -> None:
|
||||
# super().__init__(parent=projTree)
|
||||
|
||||
self.projTree = projTree
|
||||
self.projView = projTree.projView
|
||||
# self.projTree = projTree
|
||||
# self.projView = projTree.projView
|
||||
|
||||
self._item = nwItem
|
||||
self._handle = nwItem.itemHandle
|
||||
self._items: list[NWItem] = []
|
||||
# self._item = nwItem
|
||||
# self._handle = nwItem.itemHandle
|
||||
# self._items: list[NWItem] = []
|
||||
|
||||
logger.debug("Ready: _TreeContextMenu")
|
||||
# logger.debug("Ready: _TreeContextMenu")
|
||||
|
||||
return
|
||||
# return
|
||||
|
||||
def __del__(self) -> None: # pragma: no cover
|
||||
logger.debug("Delete: _TreeContextMenu")
|
||||
return
|
||||
# def __del__(self) -> None: # pragma: no cover
|
||||
# logger.debug("Delete: _TreeContextMenu")
|
||||
# return
|
||||
|
||||
##
|
||||
# Methods
|
||||
##
|
||||
# ##
|
||||
# # Methods
|
||||
# ##
|
||||
|
||||
def buildTrashMenu(self) -> None:
|
||||
"""Build the special menu for the Trash folder."""
|
||||
action = self.addAction(self.tr("Empty Trash"))
|
||||
action.triggered.connect(self.projTree.emptyTrash)
|
||||
return
|
||||
# def buildTrashMenu(self) -> None:
|
||||
# """Build the special menu for the Trash folder."""
|
||||
# action = self.addAction(self.tr("Empty Trash"))
|
||||
# action.triggered.connect(self.projTree.emptyTrash)
|
||||
# return
|
||||
|
||||
def buildSingleSelectMenu(self, hasChild: bool) -> None:
|
||||
"""Build the single-select menu."""
|
||||
isFile = self._item.isFileType()
|
||||
isFolder = self._item.isFolderType()
|
||||
isRoot = self._item.isRootType()
|
||||
# def buildSingleSelectMenu(self, hasChild: bool) -> None:
|
||||
# """Build the single-select menu."""
|
||||
# isFile = self._item.isFileType()
|
||||
# isFolder = self._item.isFolderType()
|
||||
# isRoot = self._item.isRootType()
|
||||
|
||||
# Document Actions
|
||||
if isFile:
|
||||
self._docActions()
|
||||
self.addSeparator()
|
||||
# # Document Actions
|
||||
# if isFile:
|
||||
# self._docActions()
|
||||
# self.addSeparator()
|
||||
|
||||
# Create New Items
|
||||
self._itemCreation()
|
||||
self.addSeparator()
|
||||
# # Create New Items
|
||||
# self._itemCreation()
|
||||
# self.addSeparator()
|
||||
|
||||
# Edit Item Settings
|
||||
action = self.addAction(self.tr("Rename"))
|
||||
action.triggered.connect(qtLambda(self.projView.renameTreeItem, self._handle))
|
||||
if isFile:
|
||||
self._itemHeader()
|
||||
self._itemActive(False)
|
||||
self._itemStatusImport(False)
|
||||
# # Edit Item Settings
|
||||
# action = self.addAction(self.tr("Rename"))
|
||||
# action.triggered.connect(qtLambda(self.projView.renameTreeItem, self._handle))
|
||||
# if isFile:
|
||||
# self._itemHeader()
|
||||
# self._itemActive(False)
|
||||
# self._itemStatusImport(False)
|
||||
|
||||
# Transform Item
|
||||
if isFile or isFolder:
|
||||
self._itemTransform(isFile, isFolder, hasChild)
|
||||
self.addSeparator()
|
||||
# # Transform Item
|
||||
# if isFile or isFolder:
|
||||
# self._itemTransform(isFile, isFolder, hasChild)
|
||||
# self.addSeparator()
|
||||
|
||||
# Process Item
|
||||
self._itemProcess(isFile, isFolder, isRoot, hasChild)
|
||||
# # Process Item
|
||||
# self._itemProcess(isFile, isFolder, isRoot, hasChild)
|
||||
|
||||
return
|
||||
# return
|
||||
|
||||
def buildMultiSelectMenu(self, handles: list[str]) -> None:
|
||||
"""Build the multi-select menu."""
|
||||
self._items = []
|
||||
for tHandle in handles:
|
||||
if (tItem := SHARED.project.tree[tHandle]):
|
||||
self._items.append(tItem)
|
||||
# def buildMultiSelectMenu(self, handles: list[str]) -> None:
|
||||
# """Build the multi-select menu."""
|
||||
# self._items = []
|
||||
# for tHandle in handles:
|
||||
# if (tItem := SHARED.project.tree[tHandle]):
|
||||
# self._items.append(tItem)
|
||||
|
||||
self._itemActive(True)
|
||||
self._itemStatusImport(True)
|
||||
self.addSeparator()
|
||||
self._multiMoveToTrash()
|
||||
return
|
||||
# self._itemActive(True)
|
||||
# self._itemStatusImport(True)
|
||||
# self.addSeparator()
|
||||
# self._multiMoveToTrash()
|
||||
# return
|
||||
|
||||
##
|
||||
# Menu Builders
|
||||
##
|
||||
# ##
|
||||
# # Menu Builders
|
||||
# ##
|
||||
|
||||
def _docActions(self) -> None:
|
||||
"""Add document actions."""
|
||||
action = self.addAction(self.tr("Open Document"))
|
||||
action.triggered.connect(qtLambda(
|
||||
self.projView.openDocumentRequest.emit,
|
||||
self._handle, nwDocMode.EDIT, "", True
|
||||
))
|
||||
action = self.addAction(self.tr("View Document"))
|
||||
action.triggered.connect(qtLambda(
|
||||
self.projView.openDocumentRequest.emit,
|
||||
self._handle, nwDocMode.VIEW, "", False
|
||||
))
|
||||
return
|
||||
# def _docActions(self) -> None:
|
||||
# """Add document actions."""
|
||||
# action = self.addAction(self.tr("Open Document"))
|
||||
# action.triggered.connect(qtLambda(
|
||||
# self.projView.openDocumentRequest.emit,
|
||||
# self._handle, nwDocMode.EDIT, "", True
|
||||
# ))
|
||||
# action = self.addAction(self.tr("View Document"))
|
||||
# action.triggered.connect(qtLambda(
|
||||
# self.projView.openDocumentRequest.emit,
|
||||
# self._handle, nwDocMode.VIEW, "", False
|
||||
# ))
|
||||
# return
|
||||
|
||||
def _itemCreation(self) -> None:
|
||||
"""Add create item actions."""
|
||||
menu = self.addMenu(self.tr("Create New ..."))
|
||||
menu.addAction(self.projView.projBar.aAddEmpty)
|
||||
menu.addAction(self.projView.projBar.aAddChap)
|
||||
menu.addAction(self.projView.projBar.aAddScene)
|
||||
menu.addAction(self.projView.projBar.aAddNote)
|
||||
menu.addAction(self.projView.projBar.aAddFolder)
|
||||
return
|
||||
# def _itemCreation(self) -> None:
|
||||
# """Add create item actions."""
|
||||
# menu = self.addMenu(self.tr("Create New ..."))
|
||||
# menu.addAction(self.projView.projBar.aAddEmpty)
|
||||
# menu.addAction(self.projView.projBar.aAddChap)
|
||||
# menu.addAction(self.projView.projBar.aAddScene)
|
||||
# menu.addAction(self.projView.projBar.aAddNote)
|
||||
# menu.addAction(self.projView.projBar.aAddFolder)
|
||||
# return
|
||||
|
||||
def _itemHeader(self) -> None:
|
||||
"""Check if there is a header that can be used for rename."""
|
||||
SHARED.saveEditor()
|
||||
if hItem := SHARED.project.index.getItemHeading(self._handle, "T0001"):
|
||||
action = self.addAction(self.tr("Rename to Heading"))
|
||||
action.triggered.connect(
|
||||
qtLambda(self.projView.renameTreeItem, self._handle, hItem.title)
|
||||
)
|
||||
return
|
||||
# def _itemHeader(self) -> None:
|
||||
# """Check if there is a header that can be used for rename."""
|
||||
# SHARED.saveEditor()
|
||||
# if hItem := SHARED.project.index.getItemHeading(self._handle, "T0001"):
|
||||
# action = self.addAction(self.tr("Rename to Heading"))
|
||||
# action.triggered.connect(
|
||||
# qtLambda(self.projView.renameTreeItem, self._handle, hItem.title)
|
||||
# )
|
||||
# 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.projTree.trActive)
|
||||
aOne.triggered.connect(qtLambda(self._iterItemActive, True))
|
||||
aTwo = mSub.addAction(SHARED.theme.getIcon("unchecked"), self.projTree.trInactive)
|
||||
aTwo.triggered.connect(qtLambda(self._iterItemActive, False))
|
||||
else:
|
||||
action = self.addAction(self.tr("Toggle Active"))
|
||||
action.triggered.connect(self._toggleItemActive)
|
||||
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.projTree.trActive)
|
||||
# aOne.triggered.connect(qtLambda(self._iterItemActive, True))
|
||||
# aTwo = mSub.addAction(SHARED.theme.getIcon("unchecked"), self.projTree.trInactive)
|
||||
# aTwo.triggered.connect(qtLambda(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 key, entry in SHARED.project.data.itemStatus.iterItems():
|
||||
name = entry.name
|
||||
if not multi and current == key:
|
||||
name += f" ({nwUnicode.U_CHECK})"
|
||||
action = menu.addAction(entry.icon, name)
|
||||
if multi:
|
||||
action.triggered.connect(qtLambda(self._iterSetItemStatus, key))
|
||||
else:
|
||||
action.triggered.connect(qtLambda(self._changeItemStatus, key))
|
||||
menu.addSeparator()
|
||||
action = menu.addAction(self.tr("Manage Labels ..."))
|
||||
action.triggered.connect(qtLambda(
|
||||
self.projView.projectSettingsRequest.emit,
|
||||
GuiProjectSettings.PAGE_STATUS
|
||||
))
|
||||
else:
|
||||
menu = self.addMenu(self.tr("Set Importance to ..."))
|
||||
current = self._item.itemImport
|
||||
for key, entry in SHARED.project.data.itemImport.iterItems():
|
||||
name = entry.name
|
||||
if not multi and current == key:
|
||||
name += f" ({nwUnicode.U_CHECK})"
|
||||
action = menu.addAction(entry.icon, name)
|
||||
if multi:
|
||||
action.triggered.connect(qtLambda(self._iterSetItemImport, key))
|
||||
else:
|
||||
action.triggered.connect(qtLambda(self._changeItemImport, key))
|
||||
menu.addSeparator()
|
||||
action = menu.addAction(self.tr("Manage Labels ..."))
|
||||
action.triggered.connect(qtLambda(
|
||||
self.projView.projectSettingsRequest.emit,
|
||||
GuiProjectSettings.PAGE_IMPORT
|
||||
))
|
||||
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 key, entry in SHARED.project.data.itemStatus.iterItems():
|
||||
# name = entry.name
|
||||
# if not multi and current == key:
|
||||
# name += f" ({nwUnicode.U_CHECK})"
|
||||
# action = menu.addAction(entry.icon, name)
|
||||
# if multi:
|
||||
# action.triggered.connect(qtLambda(self._iterSetItemStatus, key))
|
||||
# else:
|
||||
# action.triggered.connect(qtLambda(self._changeItemStatus, key))
|
||||
# menu.addSeparator()
|
||||
# action = menu.addAction(self.tr("Manage Labels ..."))
|
||||
# action.triggered.connect(qtLambda(
|
||||
# self.projView.projectSettingsRequest.emit,
|
||||
# GuiProjectSettings.PAGE_STATUS
|
||||
# ))
|
||||
# else:
|
||||
# menu = self.addMenu(self.tr("Set Importance to ..."))
|
||||
# current = self._item.itemImport
|
||||
# for key, entry in SHARED.project.data.itemImport.iterItems():
|
||||
# name = entry.name
|
||||
# if not multi and current == key:
|
||||
# name += f" ({nwUnicode.U_CHECK})"
|
||||
# action = menu.addAction(entry.icon, name)
|
||||
# if multi:
|
||||
# action.triggered.connect(qtLambda(self._iterSetItemImport, key))
|
||||
# else:
|
||||
# action.triggered.connect(qtLambda(self._changeItemImport, key))
|
||||
# menu.addSeparator()
|
||||
# action = menu.addAction(self.tr("Manage Labels ..."))
|
||||
# action.triggered.connect(qtLambda(
|
||||
# self.projView.projectSettingsRequest.emit,
|
||||
# GuiProjectSettings.PAGE_IMPORT
|
||||
# ))
|
||||
# return
|
||||
|
||||
def _itemTransform(self, isFile: bool, isFolder: bool, hasChild: bool) -> None:
|
||||
"""Add actions for the Transform menu."""
|
||||
menu = self.addMenu(self.tr("Transform ..."))
|
||||
# def _itemTransform(self, isFile: bool, isFolder: bool, hasChild: bool) -> None:
|
||||
# """Add actions for the Transform menu."""
|
||||
# menu = self.addMenu(self.tr("Transform ..."))
|
||||
|
||||
tree = self.projTree
|
||||
tHandle = self._handle
|
||||
# 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()
|
||||
# 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(qtLambda(self._changeItemLayout, loDoc))
|
||||
# if isNoteFile and self._item.documentAllowed():
|
||||
# action = menu.addAction(self.tr("Convert to {0}").format(trDoc))
|
||||
# action.triggered.connect(qtLambda(self._changeItemLayout, loDoc))
|
||||
|
||||
if isDocFile:
|
||||
action = menu.addAction(self.tr("Convert to {0}").format(trNote))
|
||||
action.triggered.connect(qtLambda(self._changeItemLayout, loNote))
|
||||
# if isDocFile:
|
||||
# action = menu.addAction(self.tr("Convert to {0}").format(trNote))
|
||||
# action.triggered.connect(qtLambda(self._changeItemLayout, loNote))
|
||||
|
||||
if isFolder and self._item.documentAllowed():
|
||||
action = menu.addAction(self.tr("Convert to {0}").format(trDoc))
|
||||
action.triggered.connect(qtLambda(self._covertFolderToFile, loDoc))
|
||||
# if isFolder and self._item.documentAllowed():
|
||||
# action = menu.addAction(self.tr("Convert to {0}").format(trDoc))
|
||||
# action.triggered.connect(qtLambda(self._covertFolderToFile, loDoc))
|
||||
|
||||
if isFolder:
|
||||
action = menu.addAction(self.tr("Convert to {0}").format(trNote))
|
||||
action.triggered.connect(qtLambda(self._covertFolderToFile, loNote))
|
||||
# if isFolder:
|
||||
# action = menu.addAction(self.tr("Convert to {0}").format(trNote))
|
||||
# action.triggered.connect(qtLambda(self._covertFolderToFile, loNote))
|
||||
|
||||
if hasChild and isFile:
|
||||
action = menu.addAction(self.tr("Merge Child Items into Self"))
|
||||
action.triggered.connect(qtLambda(tree._mergeDocuments, tHandle, False))
|
||||
action = menu.addAction(self.tr("Merge Child Items into New"))
|
||||
action.triggered.connect(qtLambda(tree._mergeDocuments, tHandle, True))
|
||||
# if hasChild and isFile:
|
||||
# action = menu.addAction(self.tr("Merge Child Items into Self"))
|
||||
# action.triggered.connect(qtLambda(tree._mergeDocuments, tHandle, False))
|
||||
# action = menu.addAction(self.tr("Merge Child Items into New"))
|
||||
# action.triggered.connect(qtLambda(tree._mergeDocuments, tHandle, True))
|
||||
|
||||
if hasChild and isFolder:
|
||||
action = menu.addAction(self.tr("Merge Documents in Folder"))
|
||||
action.triggered.connect(qtLambda(tree._mergeDocuments, tHandle, True))
|
||||
# if hasChild and isFolder:
|
||||
# action = menu.addAction(self.tr("Merge Documents in Folder"))
|
||||
# action.triggered.connect(qtLambda(tree._mergeDocuments, tHandle, True))
|
||||
|
||||
if isFile:
|
||||
action = menu.addAction(self.tr("Split Document by Headings"))
|
||||
action.triggered.connect(qtLambda(tree._splitDocument, tHandle))
|
||||
# if isFile:
|
||||
# action = menu.addAction(self.tr("Split Document by Headings"))
|
||||
# action.triggered.connect(qtLambda(tree._splitDocument, tHandle))
|
||||
|
||||
return
|
||||
# return
|
||||
|
||||
def _itemProcess(self, isFile: bool, isFolder: bool, isRoot: bool, hasChild: bool) -> None:
|
||||
"""Add actions for item processing."""
|
||||
tree = self.projTree
|
||||
tHandle = self._handle
|
||||
if hasChild:
|
||||
action = self.addAction(self.tr("Expand All"))
|
||||
action.triggered.connect(qtLambda(tree.setExpandedFromHandle, tHandle, True))
|
||||
action = self.addAction(self.tr("Collapse All"))
|
||||
action.triggered.connect(qtLambda(tree.setExpandedFromHandle, tHandle, False))
|
||||
# def _itemProcess(self, isFile: bool, isFolder: bool, isRoot: bool, hasChild: bool) -> None:
|
||||
# """Add actions for item processing."""
|
||||
# tree = self.projTree
|
||||
# tHandle = self._handle
|
||||
# if hasChild:
|
||||
# action = self.addAction(self.tr("Expand All"))
|
||||
# action.triggered.connect(qtLambda(tree.setExpandedFromHandle, tHandle, True))
|
||||
# action = self.addAction(self.tr("Collapse All"))
|
||||
# action.triggered.connect(qtLambda(tree.setExpandedFromHandle, tHandle, False))
|
||||
|
||||
action = self.addAction(self.tr("Duplicate"))
|
||||
action.triggered.connect(qtLambda(tree._duplicateFromHandle, tHandle))
|
||||
# action = self.addAction(self.tr("Duplicate"))
|
||||
# action.triggered.connect(qtLambda(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(qtLambda(tree.permDeleteItem, tHandle))
|
||||
else:
|
||||
action = self.addAction(self.tr("Move to Trash"))
|
||||
action.triggered.connect(qtLambda(tree.moveItemToTrash, tHandle))
|
||||
# if self._item.itemClass == nwItemClass.TRASH or isRoot or (isFolder and not hasChild):
|
||||
# action = self.addAction(self.tr("Delete Permanently"))
|
||||
# action.triggered.connect(qtLambda(tree.permDeleteItem, tHandle))
|
||||
# else:
|
||||
# action = self.addAction(self.tr("Move to Trash"))
|
||||
# action.triggered.connect(qtLambda(tree.moveItemToTrash, tHandle))
|
||||
|
||||
return
|
||||
# return
|
||||
|
||||
def _multiMoveToTrash(self) -> None:
|
||||
"""Add move to Trash action."""
|
||||
areTrash = [i.itemClass == nwItemClass.TRASH for i in self._items]
|
||||
if all(areTrash):
|
||||
action = self.addAction(self.tr("Delete Permanently"))
|
||||
action.triggered.connect(self._iterPermDelete)
|
||||
elif not any(areTrash):
|
||||
action = self.addAction(self.tr("Move to Trash"))
|
||||
action.triggered.connect(self._iterMoveToTrash)
|
||||
return
|
||||
# def _multiMoveToTrash(self) -> None:
|
||||
# """Add move to Trash action."""
|
||||
# areTrash = [i.itemClass == nwItemClass.TRASH for i in self._items]
|
||||
# if all(areTrash):
|
||||
# action = self.addAction(self.tr("Delete Permanently"))
|
||||
# action.triggered.connect(self._iterPermDelete)
|
||||
# elif not any(areTrash):
|
||||
# action = self.addAction(self.tr("Move to Trash"))
|
||||
# action.triggered.connect(self._iterMoveToTrash)
|
||||
# return
|
||||
|
||||
##
|
||||
# Private Slots
|
||||
##
|
||||
# ##
|
||||
# # Private Slots
|
||||
# ##
|
||||
|
||||
@pyqtSlot()
|
||||
def _iterMoveToTrash(self) -> None:
|
||||
"""Iterate through files and move them to Trash."""
|
||||
if SHARED.question(self.tr("Move {0} items to Trash?").format(len(self._items))):
|
||||
for tItem in self._items:
|
||||
if tItem.isFileType() and tItem.itemClass != nwItemClass.TRASH:
|
||||
self.projTree.moveItemToTrash(tItem.itemHandle, askFirst=False, flush=False)
|
||||
# self.projTree.saveTreeOrder()
|
||||
return
|
||||
# @pyqtSlot()
|
||||
# def _iterMoveToTrash(self) -> None:
|
||||
# """Iterate through files and move them to Trash."""
|
||||
# if SHARED.question(self.tr("Move {0} items to Trash?").format(len(self._items))):
|
||||
# for tItem in self._items:
|
||||
# if tItem.isFileType() and tItem.itemClass != nwItemClass.TRASH:
|
||||
# self.projTree.moveItemToTrash(tItem.itemHandle, askFirst=False, flush=False)
|
||||
# # self.projTree.saveTreeOrder()
|
||||
# return
|
||||
|
||||
@pyqtSlot()
|
||||
def _iterPermDelete(self) -> None:
|
||||
"""Iterate through files and delete them."""
|
||||
if SHARED.question(self.projTree.trPermDelete.format(len(self._items))):
|
||||
for tItem in self._items:
|
||||
if tItem.isFileType() and tItem.itemClass == nwItemClass.TRASH:
|
||||
self.projTree.permDeleteItem(tItem.itemHandle, askFirst=False, flush=False)
|
||||
# self.projTree.saveTreeOrder()
|
||||
return
|
||||
# @pyqtSlot()
|
||||
# def _iterPermDelete(self) -> None:
|
||||
# """Iterate through files and delete them."""
|
||||
# if SHARED.question(self.projTree.trPermDelete.format(len(self._items))):
|
||||
# for tItem in self._items:
|
||||
# if tItem.isFileType() and tItem.itemClass == nwItemClass.TRASH:
|
||||
# self.projTree.permDeleteItem(tItem.itemHandle, askFirst=False, flush=False)
|
||||
# # self.projTree.saveTreeOrder()
|
||||
# return
|
||||
|
||||
@pyqtSlot()
|
||||
def _toggleItemActive(self) -> None:
|
||||
"""Toggle the active status of an item."""
|
||||
self._item.setActive(not self._item.isActive)
|
||||
self.projTree.setTreeItemValues(self._item)
|
||||
self.projTree._alertTreeChange(self._handle, flush=False)
|
||||
return
|
||||
# @pyqtSlot()
|
||||
# def _toggleItemActive(self) -> None:
|
||||
# """Toggle the active status of an item."""
|
||||
# self._item.setActive(not self._item.isActive)
|
||||
# self.projTree.setTreeItemValues(self._item)
|
||||
# self.projTree._alertTreeChange(self._handle, flush=False)
|
||||
# return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
##
|
||||
# ##
|
||||
# # Internal Functions
|
||||
# ##
|
||||
|
||||
def _iterItemActive(self, isActive: bool) -> None:
|
||||
"""Set the active status of multiple items."""
|
||||
for tItem in self._items:
|
||||
if tItem and tItem.isFileType():
|
||||
tItem.setActive(isActive)
|
||||
self.projTree.setTreeItemValues(tItem)
|
||||
self.projTree._alertTreeChange(tItem.itemHandle, flush=False)
|
||||
return
|
||||
# def _iterItemActive(self, isActive: bool) -> None:
|
||||
# """Set the active status of multiple items."""
|
||||
# for tItem in self._items:
|
||||
# if tItem and tItem.isFileType():
|
||||
# tItem.setActive(isActive)
|
||||
# self.projTree.setTreeItemValues(tItem)
|
||||
# self.projTree._alertTreeChange(tItem.itemHandle, 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._item)
|
||||
self.projTree._alertTreeChange(self._handle, 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._item)
|
||||
# self.projTree._alertTreeChange(self._handle, flush=False)
|
||||
# return
|
||||
|
||||
def _iterSetItemStatus(self, key: str) -> None:
|
||||
"""Change the status value for multiple items."""
|
||||
for tItem in self._items:
|
||||
if tItem and tItem.isNovelLike():
|
||||
tItem.setStatus(key)
|
||||
self.projTree.setTreeItemValues(tItem)
|
||||
self.projTree._alertTreeChange(tItem.itemHandle, flush=False)
|
||||
return
|
||||
# def _iterSetItemStatus(self, key: str) -> None:
|
||||
# """Change the status value for multiple items."""
|
||||
# for tItem in self._items:
|
||||
# if tItem and tItem.isNovelLike():
|
||||
# tItem.setStatus(key)
|
||||
# self.projTree.setTreeItemValues(tItem)
|
||||
# self.projTree._alertTreeChange(tItem.itemHandle, 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._item)
|
||||
self.projTree._alertTreeChange(self._handle, 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._item)
|
||||
# self.projTree._alertTreeChange(self._handle, flush=False)
|
||||
# return
|
||||
|
||||
def _iterSetItemImport(self, key: str) -> None:
|
||||
"""Change the status value for multiple items."""
|
||||
for tItem in self._items:
|
||||
if tItem and not tItem.isNovelLike():
|
||||
tItem.setImport(key)
|
||||
self.projTree.setTreeItemValues(tItem)
|
||||
self.projTree._alertTreeChange(tItem.itemHandle, flush=False)
|
||||
return
|
||||
# def _iterSetItemImport(self, key: str) -> None:
|
||||
# """Change the status value for multiple items."""
|
||||
# for tItem in self._items:
|
||||
# if tItem and not tItem.isNovelLike():
|
||||
# tItem.setImport(key)
|
||||
# self.projTree.setTreeItemValues(tItem)
|
||||
# self.projTree._alertTreeChange(tItem.itemHandle, 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._item)
|
||||
self.projTree._alertTreeChange(self._handle, flush=False)
|
||||
elif itemLayout == nwItemLayout.NOTE:
|
||||
self._item.setLayout(nwItemLayout.NOTE)
|
||||
self.projTree.setTreeItemValues(self._item)
|
||||
self.projTree._alertTreeChange(self._handle, 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._item)
|
||||
# self.projTree._alertTreeChange(self._handle, flush=False)
|
||||
# elif itemLayout == nwItemLayout.NOTE:
|
||||
# self._item.setLayout(nwItemLayout.NOTE)
|
||||
# self.projTree.setTreeItemValues(self._item)
|
||||
# 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._item)
|
||||
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._item)
|
||||
self.projTree._alertTreeChange(self._handle, flush=False)
|
||||
else:
|
||||
logger.info("Folder conversion cancelled")
|
||||
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._item)
|
||||
# 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._item)
|
||||
# self.projTree._alertTreeChange(self._handle, flush=False)
|
||||
# else:
|
||||
# logger.info("Folder conversion cancelled")
|
||||
# return
|
||||
|
||||
Reference in New Issue
Block a user