Add model support for drag and drop

This commit is contained in:
Veronica Berglyd Olsen
2024-11-20 01:31:56 +01:00
parent 54685c8184
commit 72f1bc2d77
3 changed files with 78 additions and 14 deletions
+3
View File
@@ -53,6 +53,9 @@ class nwConst:
# Requests
USER_AGENT = "Mozilla/5.0 (compatible; novelWriter (Python))"
# Mime Types
MIME_HANDLE = "text/vnd.novelwriter.handle"
# Gui Settings
STATUS_MSG_TIMEOUT = 15000 # milliseconds
MAX_SEARCH_RESULT = 1000
+68 -8
View File
@@ -28,11 +28,12 @@ import logging
from typing import TYPE_CHECKING
from PyQt5.QtCore import QAbstractItemModel, QModelIndex, Qt
from PyQt5.QtCore import QAbstractItemModel, QMimeData, QModelIndex, Qt
from PyQt5.QtGui import QIcon
from novelwriter import SHARED
from novelwriter.common import minmax
from novelwriter.constants import nwConst
from novelwriter.core.item import NWItem
from novelwriter.enum import nwItemClass
from novelwriter.types import QtAlignRight
@@ -54,12 +55,16 @@ C_ACTIVE_TIP = 0x0200 | Qt.ItemDataRole.ToolTipRole
C_STATUS_ICON = 0x0300 | Qt.ItemDataRole.DecorationRole
C_STATUS_TIP = 0x0300 | Qt.ItemDataRole.ToolTipRole
NODE_FLAGS = Qt.ItemFlag.ItemIsEnabled
NODE_FLAGS |= Qt.ItemFlag.ItemIsSelectable
NODE_FLAGS |= Qt.ItemFlag.ItemIsDropEnabled
T_NodeData = str | QIcon | Qt.AlignmentFlag | None
class ProjectNode:
__slots__ = ("_item", "_children", "_parent", "_row", "_cache", "_count")
__slots__ = ("_item", "_children", "_parent", "_row", "_cache", "_flags", "_count")
def __init__(self, item: NWItem) -> None:
self._item = item
@@ -67,6 +72,7 @@ class ProjectNode:
self._parent: ProjectNode | None = None
self._row = 0
self._cache: dict[int, str | QIcon | Qt.AlignmentFlag] = {}
self._flags = NODE_FLAGS
self.refresh()
return
@@ -145,6 +151,10 @@ class ProjectNode:
"""Return cached node data."""
return self._cache.get(COL_MASK*column | role)
def flags(self) -> Qt.ItemFlag:
"""Return cached node flags."""
return self._flags
def parent(self) -> ProjectNode | None:
return self._parent
@@ -213,9 +223,10 @@ class ProjectNode:
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)
child.item.setClassDefaults(self.item.itemClass)
child.item.setParent(self._item.itemHandle)
child.item.setRoot(self._item.itemRoot)
child.item.setClassDefaults(self._item.itemClass)
child._flags |= Qt.ItemFlag.ItemIsDragEnabled
else:
child.item.setParent(None)
child.item.setRoot(child.item.itemHandle)
@@ -230,6 +241,7 @@ class ProjectModel(QAbstractItemModel):
super().__init__()
self._tree = tree
self._root = ProjectNode(NWItem(tree._project, "invisibleRoot"))
self._root.item.setName("Invisible Root")
logger.debug("Ready: ProjectModel")
return
@@ -276,6 +288,55 @@ class ProjectModel(QAbstractItemModel):
return index.internalPointer().data(index.column(), role)
return None
def flags(self, index: QModelIndex) -> Qt.ItemFlag:
"""Return flags for a project node."""
if index.isValid():
return index.internalPointer().flags()
return Qt.ItemFlag.NoItemFlags
##
# Drag and Drop
##
def supportedDropActions(self) -> Qt.DropAction:
"""Return supported drop actions"""
return Qt.DropAction.MoveAction
def mimeTypes(self) -> list[str]:
"""Return the supported mime types of the model."""
return [nwConst.MIME_HANDLE]
def mimeData(self, indices: list[QModelIndex]) -> QMimeData:
"""Encode mime data about a selection."""
handles = [
i.internalPointer().item.itemHandle.encode()
for i in indices if i.isValid() and i.column() == 0
]
mime = QMimeData()
mime.setData(nwConst.MIME_HANDLE, b"|".join(handles))
return mime
def canDropMimeData(
self, data: QMimeData, action: Qt.DropAction,
row: int, column: int, parent: QModelIndex
) -> bool:
"""Check if mime data can be dropped on the current location."""
return data.hasFormat(nwConst.MIME_HANDLE) and action == Qt.DropAction.MoveAction
def dropMimeData(
self, data: QMimeData, action: Qt.DropAction,
row: int, column: int, parent: QModelIndex
) -> bool:
"""Process mime data drop."""
if self.canDropMimeData(data, action, row, column, parent):
items = []
for handle in data.data(nwConst.MIME_HANDLE).data().decode().split("|"):
if (index := self.indexFromHandle(handle)).isValid():
items.append(index)
self.multiMove(items, parent, row)
return True
return False
##
# Data Access
##
@@ -353,12 +414,11 @@ class ProjectModel(QAbstractItemModel):
for index in indices:
if index.isValid():
node: ProjectNode = index.internalPointer()
print(node)
if node.item.itemClass != nwItemClass.TRASH:
return False
return True
def multiMove(self, indices: list[QModelIndex], target: QModelIndex) -> None:
def multiMove(self, indices: list[QModelIndex], target: QModelIndex, pos: int = -1) -> None:
"""Move multiple items to a new location."""
if target.isValid():
# This is a two pass process. First we only select unique
@@ -379,7 +439,7 @@ class ProjectModel(QAbstractItemModel):
if node.item.itemParent not in handles:
index = self.indexFromNode(node)
if child := self.removeChild(index.parent(), index.row()):
self.insertChild(child, target, -1)
self.insertChild(child, target, pos)
return
##
+7 -6
View File
@@ -3,10 +3,11 @@ novelWriter GUI Project Tree
==============================
File History:
Created: 2018-09-29 [0.0.1] GuiProjectTree
Created: 2022-06-06 [2.0rc1] GuiProjectView
Created: 2022-06-06 [2.0rc1] GuiProjectToolBar
Created: 2023-11-22 [2.2rc1] _TreeContextMenu
Created: 2018-09-29 [0.0.1] GuiProjectTree
Created: 2022-06-06 [2.0rc1] GuiProjectView
Created: 2022-06-06 [2.0rc1] GuiProjectToolBar
Created: 2023-11-22 [2.2rc1] _TreeContextMenu
Rewritten: 2024-11-17 [2.7b1] GuiProjectTree
This file is a part of novelWriter
Copyright 20182024, Veronica Berglyd Olsen
@@ -543,8 +544,8 @@ class GuiProjectTree(QTreeView):
self.setIndentation(iPx)
# Allow Move by Drag & Drop
# self.setDragEnabled(True)
# self.setDragDropMode(QAbstractItemView.DragDropMode.InternalMove)
self.setDragEnabled(True)
self.setDragDropMode(QAbstractItemView.DragDropMode.InternalMove)
# Disable built-in auto scroll as it isn't working in some Qt
# releases (see #1561) and instead use our own implementation