0

I have a QTreeView with implemented dropEvent()-handler. If the item to be dropped is already existing under the target-root, how could I avoid the dropping at all? E.g. drag "user_b" from the "Master Data" Node on the node "Security Model"/"client_c"/"stakeholder_d" which already exists.

Full working code example:

#!/usr/bin/env python3
# coding = utf-8
from PyQt5 import QtWidgets, QtCore, QtGui

TXT_CLIENT = "Clients"
TXT_STAKEHLD = "Stakeholders"
TXT_USER = "Users"

TXT_SYSTEM = "Master Data"
TXT_SECURITY = "Security Model"

CLS_LVL_ROOT = 0
CLS_LVL_CLIENT = 1
CLS_LVL_STAKEHLD = 2
CLS_LVL_USER = 3

ICON_LVL_CLIENT = "img/icons8-bank-16.png"
ICON_LVL_STAKEHLD = "img/icons8-initiate-money-transfer-24.png"
ICON_LVL_USER = "img/icons8-checked-user-male-32.png"

DATA = [
    (TXT_SYSTEM, [
    (TXT_USER, [
        ("user_a", []),
        ("user_b", [])
        ]),
    (TXT_CLIENT, [
        ("client_a", []),    
        ("client_b", []),    
        ("client_c", []),
        ("client_d", [])
        ]),
    (TXT_STAKEHLD, [
        ("stakeholder_a", []),    
        ("stakeholder_b", []),    
        ("stakeholder_c", []),            
        ("stakeholder_d", [])        
        ])
    ]),
    (TXT_SECURITY, [
        ("client_a", [
            ("stakeholder_b",[
                ("user_a",[])
            ])
        ]),
        ("client_c", [
            ("stakeholder_d",[
                ("user_b",[])
            ])
        ])
    ])
    ]

def create_tree_data(tree):
    model = QtGui.QStandardItemModel()
    addItems(tree, model, DATA)
    tree.setModel(model)

def addItems(tree, parent, elements, level=0, root=0):
    level += 1

    for text, children in elements:                    
        if text == TXT_SYSTEM:
            root = 1

        elif text == TXT_SECURITY:
            root = 2

        item = QtGui.QStandardItem(text)            
        icon = QtGui.QIcon(TXT_USER)
        item.setIcon(icon)

        parent.appendRow(item)

        if root==1:
            if children:
                if text == TXT_CLIENT:                    
                    icon = QtGui.QIcon(ICON_LVL_CLIENT)
                    item.setIcon(icon)
                elif text == TXT_STAKEHLD:                                        
                    icon = QtGui.QIcon(ICON_LVL_STAKEHLD)
                    item.setIcon(icon)
                elif text == TXT_USER:                                                            
                    icon = QtGui.QIcon(ICON_LVL_USER)
                    item.setIcon(icon)
        elif root == 2:
            if level == 2:
                icon = QtGui.QIcon(ICON_LVL_CLIENT)
                item.setIcon(icon)
            if level == 3:
                icon = QtGui.QIcon(ICON_LVL_STAKEHLD)
                item.setIcon(icon)
            elif level == 4:
                icon = QtGui.QIcon(ICON_LVL_USER)
                item.setIcon(icon)

        addItems(tree, item, children, level, root)

def get_tree_selection_level(index):
    level = 0
    while index.parent().isValid():
        index = index.parent()
        level += 1

    return level


class TreeView(QtWidgets.QTreeView):
    def __init__(self, parent=None):
        super().__init__(parent)

        self.initUI()

    def initUI(self):
        self.setHeaderHidden(True)
        self.setColumnHidden(1, True)
        self.setSelectionMode(self.SingleSelection)
        self.setDragDropMode(QtWidgets.QAbstractItemView.DragDrop)  # InternalMove)        

    def dropEvent(self, event):
        tree = event.source()        

        if self.viewport().rect().contains(event.pos()):
            fake_model = QtGui.QStandardItemModel()
            fake_model.dropMimeData(
                event.mimeData(), event.dropAction(), 0, 0, QtCore.QModelIndex()
            )            
            for r in range(fake_model.rowCount()):
                for c in range(fake_model.columnCount()):
                    ix = fake_model.index(r, c)
                    print("item: ", ix.data())

                    item = QtGui.QStandardItem(ix.data())
                    icon = QtGui.QIcon(TXT_USER)
                    item.setIcon(icon)

            sParent: str = ""
            par_ix = tree.selectedIndexes()[0].parent()
            if par_ix.isValid():
                sParent = par_ix.data()
                print("par. item: ", sParent)

            to_index = self.indexAt(event.pos())
            if to_index.isValid():
                print("to:", to_index.data())

            if (sParent == TXT_CLIENT and get_tree_selection_level(to_index) == CLS_LVL_ROOT) or (sParent == TXT_STAKEHLD and get_tree_selection_level(to_index) == CLS_LVL_CLIENT) or (sParent == TXT_USER and get_tree_selection_level(to_index) == CLS_LVL_STAKEHLD):
                # to-do:
                # 1 - check if the item is already there; if yes: omit
                pass

                # 2 - set the proper icon
                pass

                super().dropEvent(event)
                self.setExpanded(to_index, True)

class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()

        self.initUI()        

    def initUI(self):
        centralwidget = QtWidgets.QWidget()
        self.setCentralWidget(centralwidget)

        hBox = QtWidgets.QHBoxLayout(centralwidget)        

        self.treeView = TreeView(centralwidget)       

        hBox.addWidget(self.treeView)


if __name__ == "__main__":
    app = QtWidgets.QApplication([])
    window = MainWindow()
    create_tree_data(window.treeView)
    window.treeView.expand(window.treeView.model().index(0, 0))  # expand the System-Branch
    window.setGeometry(400, 400, 500, 400)
    window.show()

    app.exec_()
ProfP30
  • 358
  • 3
  • 18
  • From what you point out I can deduce that you should only be able to drag items that have no children, for example the "Master Data" or "Users" node should not be able to drag but if "user_a", am I correct? – eyllanesc Dec 30 '19 at 13:27
  • On the other hand, can you create a new level? That is, for example, drag an item as a child of "user_b" – eyllanesc Dec 30 '19 at 13:32
  • In the 1st root, "Master Data" there is no hierarchy. Just flat three parents and their children. This structure is "fixed" and should not be modified by the user. The manual building of a hierarchy is done in the second node "Security Model". Here the user will build up x users under y stakeholders under z clients. – ProfP30 Dec 30 '19 at 13:45
  • The "Master Data" can be thought of like a pool of objects. In the "Security Model" the end user can build a flexible structure of putting this nodes into a relationship but the parent stucture is client -> stakeholder -> user So on the deepest level I want to drag "users" from the "Master Data" pool under the desired stakeholder in "Security Model". The containers in "Security Model" will be created with a right-click context-menu later on (will have to implement this on my own later). – ProfP30 Dec 30 '19 at 13:45
  • Okay, from what I understand the sources of the drag can only be the final nodes of the "Master Data" subnode, and the "Security Model" node will have the hierarchy "client_x" -> "stakeholder_y" -> "user_z" so a "user_a" which is the final node of "Master Data" can only be dragged as a child of an existing "stakeholder_b", another example is that a stakeholder_p can only be dragged to be a child of client_q (x, y, z, etc. are generic) Am I correct? – eyllanesc Dec 30 '19 at 13:54
  • Correct! But a "user_a" can exist multiple times in the total on the "Security Model" tree, but only once as per stakeholder! E.g. "stakeholder_a" contains "user_a". but "stakeholder_b" also contains "user_a". etc. Also a stakeholder can exist multiple times in the the Security Model Tree, but only once *per client*. – ProfP30 Dec 30 '19 at 14:03
  • ok i got it.... – eyllanesc Dec 30 '19 at 14:03
  • for screenshot and more example please see also https://stackoverflow.com/questions/58919468/drag-drop-operation-in-qtreewidget-not-copying-the-dropped-item/58919684 – ProfP30 Dec 30 '19 at 14:20

1 Answers1

0

You can use dragMoveEvent to perform the filters:

  • There can be nodes with the same text as the same father.
  • The SM node can only have clients as children.
  • Client nodes can only have stakeholders as children.
  • The stakeholders nodes can only have users as children.

Considering the above, the solution is:

from enum import Enum
import os
import sys

from PyQt5 import QtCore, QtGui, QtWidgets

TXT_SYSTEM = "Master Data"
TXT_SECURITY = "Security Model"

TXT_CLIENT = "Clients"
TXT_STAKEHLD = "Stakeholders"
TXT_USER = "Users"

ICON_LVL_CLIENT = "img/icons8-bank-16.png"
ICON_LVL_STAKEHLD = "img/icons8-initiate-money-transfer-24.png"
ICON_LVL_USER = "img/icons8-checked-user-male-32.png"

TYPE_ROLE = QtCore.Qt.UserRole + 1000


class NodeRoles(Enum):
    ROOT_ROLE = 0
    CLIENT_ROLE = 1
    STAKEHOLD_ROLE = 2
    USER_ROLE = 3


CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))

SYSTEM_DATA = {
    TXT_USER: ["user_a", "user_b"],
    TXT_CLIENT: ["client_a", "client_b", "client_c", "client_d"],
    TXT_STAKEHLD: ["stakeholder_a", "stakeholder_b", "stakeholder_c", "stakeholder_d"],
}


SECURITY_DATA = {
    "client_a": {"stakeholder_b": ["user_a"]},
    "client_c": {"stakeholder_d": ["user_b"]},
}


class CustomModel(QtGui.QStandardItemModel):
    def __init__(self, system_data, security_data, parent=None):
        super().__init__(parent)
        self.create_system_node(system_data)
        self.create_security_node(security_data)

    def create_system_node(self, system_data):
        root = QtGui.QStandardItem(TXT_SYSTEM)
        self.appendRow(root)

        for it in (root, self.invisibleRootItem()):
            it.setFlags(
                it.flags() & ~QtCore.Qt.ItemIsDragEnabled & ~QtCore.Qt.ItemIsDropEnabled
            )
            it.setData(NodeRoles.ROOT_ROLE, TYPE_ROLE)

        for key, path_icon, role in zip(
            (TXT_USER, TXT_CLIENT, TXT_STAKEHLD),
            (ICON_LVL_USER, ICON_LVL_CLIENT, ICON_LVL_STAKEHLD),
            (NodeRoles.USER_ROLE, NodeRoles.CLIENT_ROLE, NodeRoles.STAKEHOLD_ROLE),
        ):
            it = QtGui.QStandardItem(key)
            it.setFlags(
                it.flags() & ~QtCore.Qt.ItemIsDragEnabled & ~QtCore.Qt.ItemIsDropEnabled
            )
            icon = QtGui.QIcon(os.path.join(CURRENT_DIR, path_icon))
            it.setIcon(icon)
            it.setData(NodeRoles.ROOT_ROLE, TYPE_ROLE)
            root.appendRow(it)

            for value in system_data[key]:
                child = QtGui.QStandardItem(value)
                child.setFlags(child.flags() & ~QtCore.Qt.ItemIsDropEnabled)
                child.setData(role, TYPE_ROLE)
                it.appendRow(child)

    def create_security_node(self, security_data):
        root = QtGui.QStandardItem(TXT_SECURITY)
        root.setData(NodeRoles.ROOT_ROLE, TYPE_ROLE)
        self.appendRow(root)
        root.setFlags(root.flags() & ~QtCore.Qt.ItemIsDragEnabled)

        self._fill_node(security_data, root, 0)

    def _fill_node(self, data, root, level):
        role = (NodeRoles.CLIENT_ROLE, NodeRoles.STAKEHOLD_ROLE, NodeRoles.USER_ROLE)[
            level
        ]
        icon_path = (ICON_LVL_CLIENT, ICON_LVL_STAKEHLD, ICON_LVL_USER)[level]
        icon = QtGui.QIcon(os.path.join(CURRENT_DIR, icon_path))
        if isinstance(data, dict):
            for key, value in data.items():
                it = QtGui.QStandardItem(key)
                it.setFlags(it.flags() & ~QtCore.Qt.ItemIsDropEnabled)
                it.setIcon(icon)
                it.setData(role, TYPE_ROLE)
                root.appendRow(it)
                self._fill_node(value, it, level + 1)
            return
        else:
            for d in data:
                it = QtGui.QStandardItem(d)
                it.setIcon(icon)
                it.setData(role, TYPE_ROLE)
                root.appendRow(it)


class TreeView(QtWidgets.QTreeView):
    def __init__(self, parent=None):
        super().__init__(parent)

        self.initUI()

    def initUI(self):
        self.setHeaderHidden(True)
        self.setColumnHidden(1, True)
        self.setSelectionMode(self.SingleSelection)
        self.setDragDropMode(QtWidgets.QAbstractItemView.DragDrop)
        self.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)

        self.resize(640, 480)

    def dragMoveEvent(self, event):
        if event.source() is not self:
            event.ignore()
            return
        fake_model = QtGui.QStandardItemModel()
        fake_model.dropMimeData(
            event.mimeData(), event.dropAction(), 0, 0, QtCore.QModelIndex()
        )
        it = fake_model.item(0)
        role = it.data(TYPE_ROLE)

        to_index = self.indexAt(event.pos())
        root = to_index
        while root.parent().isValid():
            root = root.parent()
        if root == self.model().item(0).index():
            event.ignore()
        else:
            to_role = to_index.data(TYPE_ROLE)
            if (
                (to_role == NodeRoles.ROOT_ROLE and role == NodeRoles.CLIENT_ROLE)
                or (
                    to_role == NodeRoles.CLIENT_ROLE
                    and role == NodeRoles.STAKEHOLD_ROLE
                )
                or (to_role == NodeRoles.STAKEHOLD_ROLE and role == NodeRoles.USER_ROLE)
            ):
                to_item = self.model().itemFromIndex(to_index)
                for i in range(to_item.rowCount()):
                    child_it = to_item.child(i)
                    if child_it.text() == it.text():
                        event.ignore()
                        return
                self.setExpanded(to_index, True)
                super().dragMoveEvent(event)
            else:
                event.ignore()


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    w = TreeView()
    model = CustomModel(system_data=SYSTEM_DATA, security_data=SECURITY_DATA)
    w.setModel(model)
    w.show()
    sys.exit(app.exec_())
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
  • With the given code I am not able to drag any user from the Master Data tree anymore. See screenshot here: https://imgur.com/a/xCNZ8Ez – ProfP30 Jan 01 '20 at 17:39