From c52a37c276338865310208a5ccd73d5c7d016d62 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 29 Jul 2023 22:25:58 +0200 Subject: [PATCH] Add shift + arrow navigation feature to project tree (#1348) --- novelwriter/gui/projtree.py | 77 +++++++++++++++++++++++++++++-------- 1 file changed, 60 insertions(+), 17 deletions(-) diff --git a/novelwriter/gui/projtree.py b/novelwriter/gui/projtree.py index c7ac3de6..3af6aa9c 100644 --- a/novelwriter/gui/projtree.py +++ b/novelwriter/gui/projtree.py @@ -41,7 +41,7 @@ from PyQt5.QtWidgets import ( ) 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.core.item import NWItem 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.editlabel import GuiEditLabel from novelwriter.dialogs.projsettings import GuiProjectSettings +from novelwriter.enum import ( + nwDocMode, nwItemType, nwItemClass, nwItemLayout, nwAlert, nwWidget +) if TYPE_CHECKING: # pragma: no cover from novelwriter.guimain import GuiMain @@ -104,6 +107,26 @@ class GuiProjectView(QWidget): self.keyMoveDn.setContext(Qt.WidgetShortcut) 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.setKey("Ctrl+Shift+Z") self.keyUndoMv.setContext(Qt.WidgetShortcut) @@ -665,21 +688,21 @@ class GuiProjectTree(QTreeWidget): return True - def moveTreeItem(self, nStep: int) -> bool: + def moveTreeItem(self, step: int) -> bool: """Move an item up or down in the tree.""" tHandle = self.getSelectedHandle() - trItem = self._getTreeItem(tHandle) - if trItem is None: + tItem = self._getTreeItem(tHandle) + if tItem is None: logger.debug("No item selected") return False - pItem = trItem.parent() - isExp = trItem.isExpanded() + pItem = tItem.parent() + isExp = tItem.isExpanded() if pItem is None: - tIndex = self.indexOfTopLevelItem(trItem) + tIndex = self.indexOfTopLevelItem(tItem) nChild = self.topLevelItemCount() - nIndex = tIndex + nStep + nIndex = tIndex + step if nIndex < 0 or nIndex >= nChild: return False @@ -687,10 +710,10 @@ class GuiProjectTree(QTreeWidget): self.insertTopLevelItem(nIndex, cItem) else: - tIndex = pItem.indexOfChild(trItem) + tIndex = pItem.indexOfChild(tItem) nChild = pItem.childCount() - nIndex = tIndex + nStep + nIndex = tIndex + step if nIndex < 0 or nIndex >= nChild: return False @@ -699,11 +722,32 @@ class GuiProjectTree(QTreeWidget): self._recordLastMove(cItem, pItem, tIndex) self._alertTreeChange(tHandle, flush=True) - self.setCurrentItem(trItem) - trItem.setExpanded(isExp) + self.setCurrentItem(tItem) + tItem.setExpanded(isExp) 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: """Open a dialog to edit the label of an item.""" tItem = self.theProject.tree[tHandle] @@ -773,7 +817,7 @@ class GuiProjectTree(QTreeWidget): return False if self.theProject.tree.isTrash(tHandle) or nwItem.isRootType(): - status = self.permanentlyDeleteItem(tHandle) + status = self.permDeleteItem(tHandle) else: status = self.moveItemToTrash(tHandle) @@ -820,7 +864,7 @@ class GuiProjectTree(QTreeWidget): for tHandle in reversed(self.getTreeFromHandle(trashHandle)): if tHandle == trashHandle: continue - self.permanentlyDeleteItem(tHandle, askFirst=False, flush=False) + self.permDeleteItem(tHandle, askFirst=False, flush=False) if nTrash > 0: self._alertTreeChange(trashHandle, flush=True) @@ -878,8 +922,7 @@ class GuiProjectTree(QTreeWidget): return True - def permanentlyDeleteItem(self, tHandle: str, - askFirst: bool = True, flush: bool = True) -> bool: + def permDeleteItem(self, tHandle: str, askFirst: bool = True, flush: bool = True) -> bool: """Permanently delete a tree item from the project and the map. 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): aDelete = ctxMenu.addAction(self.tr("Delete Permanently")) - aDelete.triggered.connect(lambda: self.permanentlyDeleteItem(tHandle)) + aDelete.triggered.connect(lambda: self.permDeleteItem(tHandle)) else: aMoveTrash = ctxMenu.addAction(self.tr("Move to Trash")) aMoveTrash.triggered.connect(lambda: self.moveItemToTrash(tHandle))