Add shift + arrow navigation feature to project tree (#1348)
This commit is contained in:
+60
-17
@@ -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))
|
||||||
|
|||||||
Reference in New Issue
Block a user