Add shift + arrow navigation feature to project tree (#1348)

This commit is contained in:
Veronica Berglyd Olsen
2023-07-29 22:25:58 +02:00
parent 1dadd1ddb7
commit c52a37c276
+60 -17
View File
@@ -41,7 +41,7 @@ from PyQt5.QtWidgets import (
) )
from novelwriter import CONFIG from novelwriter import CONFIG
from novelwriter.enum import nwDocMode, nwItemType, nwItemClass, nwItemLayout, nwAlert, nwWidget from novelwriter.common import minmax
from novelwriter.constants import nwHeaders, nwUnicode, trConst, nwLabels from novelwriter.constants import nwHeaders, nwUnicode, trConst, nwLabels
from novelwriter.core.item import NWItem from novelwriter.core.item import NWItem
from novelwriter.core.coretools import DocDuplicator, DocMerger, DocSplitter from novelwriter.core.coretools import DocDuplicator, DocMerger, DocSplitter
@@ -49,6 +49,9 @@ from novelwriter.dialogs.docmerge import GuiDocMerge
from novelwriter.dialogs.docsplit import GuiDocSplit from novelwriter.dialogs.docsplit import GuiDocSplit
from novelwriter.dialogs.editlabel import GuiEditLabel from novelwriter.dialogs.editlabel import GuiEditLabel
from novelwriter.dialogs.projsettings import GuiProjectSettings from novelwriter.dialogs.projsettings import GuiProjectSettings
from novelwriter.enum import (
nwDocMode, nwItemType, nwItemClass, nwItemLayout, nwAlert, nwWidget
)
if TYPE_CHECKING: # pragma: no cover if TYPE_CHECKING: # pragma: no cover
from novelwriter.guimain import GuiMain from novelwriter.guimain import GuiMain
@@ -104,6 +107,26 @@ class GuiProjectView(QWidget):
self.keyMoveDn.setContext(Qt.WidgetShortcut) self.keyMoveDn.setContext(Qt.WidgetShortcut)
self.keyMoveDn.activated.connect(lambda: self.projTree.moveTreeItem(1)) self.keyMoveDn.activated.connect(lambda: self.projTree.moveTreeItem(1))
self.keyGoPrev = QShortcut(self.projTree)
self.keyGoPrev.setKey("Shift+Up")
self.keyGoPrev.setContext(Qt.WidgetShortcut)
self.keyGoPrev.activated.connect(lambda: self.projTree.moveToNextItem(-1))
self.keyGoNext = QShortcut(self.projTree)
self.keyGoNext.setKey("Shift+Down")
self.keyGoNext.setContext(Qt.WidgetShortcut)
self.keyGoNext.activated.connect(lambda: self.projTree.moveToNextItem(1))
self.keyGoUp = QShortcut(self.projTree)
self.keyGoUp.setKey("Shift+Left")
self.keyGoUp.setContext(Qt.WidgetShortcut)
self.keyGoUp.activated.connect(lambda: self.projTree.moveToLevel(-1))
self.keyGoDown = QShortcut(self.projTree)
self.keyGoDown.setKey("Shift+Right")
self.keyGoDown.setContext(Qt.WidgetShortcut)
self.keyGoDown.activated.connect(lambda: self.projTree.moveToLevel(1))
self.keyUndoMv = QShortcut(self.projTree) self.keyUndoMv = QShortcut(self.projTree)
self.keyUndoMv.setKey("Ctrl+Shift+Z") self.keyUndoMv.setKey("Ctrl+Shift+Z")
self.keyUndoMv.setContext(Qt.WidgetShortcut) self.keyUndoMv.setContext(Qt.WidgetShortcut)
@@ -665,21 +688,21 @@ class GuiProjectTree(QTreeWidget):
return True return True
def moveTreeItem(self, nStep: int) -> bool: def moveTreeItem(self, step: int) -> bool:
"""Move an item up or down in the tree.""" """Move an item up or down in the tree."""
tHandle = self.getSelectedHandle() tHandle = self.getSelectedHandle()
trItem = self._getTreeItem(tHandle) tItem = self._getTreeItem(tHandle)
if trItem is None: if tItem is None:
logger.debug("No item selected") logger.debug("No item selected")
return False return False
pItem = trItem.parent() pItem = tItem.parent()
isExp = trItem.isExpanded() isExp = tItem.isExpanded()
if pItem is None: if pItem is None:
tIndex = self.indexOfTopLevelItem(trItem) tIndex = self.indexOfTopLevelItem(tItem)
nChild = self.topLevelItemCount() nChild = self.topLevelItemCount()
nIndex = tIndex + nStep nIndex = tIndex + step
if nIndex < 0 or nIndex >= nChild: if nIndex < 0 or nIndex >= nChild:
return False return False
@@ -687,10 +710,10 @@ class GuiProjectTree(QTreeWidget):
self.insertTopLevelItem(nIndex, cItem) self.insertTopLevelItem(nIndex, cItem)
else: else:
tIndex = pItem.indexOfChild(trItem) tIndex = pItem.indexOfChild(tItem)
nChild = pItem.childCount() nChild = pItem.childCount()
nIndex = tIndex + nStep nIndex = tIndex + step
if nIndex < 0 or nIndex >= nChild: if nIndex < 0 or nIndex >= nChild:
return False return False
@@ -699,11 +722,32 @@ class GuiProjectTree(QTreeWidget):
self._recordLastMove(cItem, pItem, tIndex) self._recordLastMove(cItem, pItem, tIndex)
self._alertTreeChange(tHandle, flush=True) self._alertTreeChange(tHandle, flush=True)
self.setCurrentItem(trItem) self.setCurrentItem(tItem)
trItem.setExpanded(isExp) tItem.setExpanded(isExp)
return True return True
def moveToNextItem(self, step: int) -> None:
"""Move to the next item of the same tree level."""
tHandle = self.getSelectedHandle()
tItem = self._getTreeItem(tHandle) if tHandle else None
if tItem:
pItem = tItem.parent() or self.invisibleRootItem()
next = minmax(pItem.indexOfChild(tItem) + step, 0, pItem.childCount() - 1)
self.setCurrentItem(pItem.child(next))
return
def moveToLevel(self, step: int) -> None:
"""Move to the next item in the parent/child chain."""
tHandle = self.getSelectedHandle()
tItem = self._getTreeItem(tHandle) if tHandle else None
if tItem:
if step < 0 and tItem.parent():
self.setCurrentItem(tItem.parent())
elif step > 0 and tItem.childCount() > 0:
self.setCurrentItem(tItem.child(0))
return
def renameTreeItem(self, tHandle: str) -> bool: def renameTreeItem(self, tHandle: str) -> bool:
"""Open a dialog to edit the label of an item.""" """Open a dialog to edit the label of an item."""
tItem = self.theProject.tree[tHandle] tItem = self.theProject.tree[tHandle]
@@ -773,7 +817,7 @@ class GuiProjectTree(QTreeWidget):
return False return False
if self.theProject.tree.isTrash(tHandle) or nwItem.isRootType(): if self.theProject.tree.isTrash(tHandle) or nwItem.isRootType():
status = self.permanentlyDeleteItem(tHandle) status = self.permDeleteItem(tHandle)
else: else:
status = self.moveItemToTrash(tHandle) status = self.moveItemToTrash(tHandle)
@@ -820,7 +864,7 @@ class GuiProjectTree(QTreeWidget):
for tHandle in reversed(self.getTreeFromHandle(trashHandle)): for tHandle in reversed(self.getTreeFromHandle(trashHandle)):
if tHandle == trashHandle: if tHandle == trashHandle:
continue continue
self.permanentlyDeleteItem(tHandle, askFirst=False, flush=False) self.permDeleteItem(tHandle, askFirst=False, flush=False)
if nTrash > 0: if nTrash > 0:
self._alertTreeChange(trashHandle, flush=True) self._alertTreeChange(trashHandle, flush=True)
@@ -878,8 +922,7 @@ class GuiProjectTree(QTreeWidget):
return True return True
def permanentlyDeleteItem(self, tHandle: str, def permDeleteItem(self, tHandle: str, askFirst: bool = True, flush: bool = True) -> bool:
askFirst: bool = True, flush: bool = True) -> bool:
"""Permanently delete a tree item from the project and the map. """Permanently delete a tree item from the project and the map.
Root items are handled a little different than other items. Root items are handled a little different than other items.
""" """
@@ -1307,7 +1350,7 @@ class GuiProjectTree(QTreeWidget):
if tItem.itemClass == nwItemClass.TRASH or isRoot or (isFolder and not hasChild): if tItem.itemClass == nwItemClass.TRASH or isRoot or (isFolder and not hasChild):
aDelete = ctxMenu.addAction(self.tr("Delete Permanently")) aDelete = ctxMenu.addAction(self.tr("Delete Permanently"))
aDelete.triggered.connect(lambda: self.permanentlyDeleteItem(tHandle)) aDelete.triggered.connect(lambda: self.permDeleteItem(tHandle))
else: else:
aMoveTrash = ctxMenu.addAction(self.tr("Move to Trash")) aMoveTrash = ctxMenu.addAction(self.tr("Move to Trash"))
aMoveTrash.triggered.connect(lambda: self.moveItemToTrash(tHandle)) aMoveTrash.triggered.connect(lambda: self.moveItemToTrash(tHandle))