1

In my QtreeViewI use a 'QStandardItemModel` for displaying several items with individual properties. I want to avoid that the item will not be mixed. e.g. Bananas should be moveable to vegetables (same child level) but not below Asia (higher level), moving to Asia - Fruits is ok (same child level)

Sample

I've worked with .itemChanged but it appears to late. I need a signal before it will be dropped and the item where it will be dropped. I tried eventFilterand get

event.type() == QtCore.QEvent.DragMove: 

but how do I get the index of the item where the item will be dropped to decide it its in the same child level?

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
Papageno
  • 305
  • 3
  • 15

2 Answers2

1

To solve this problem I have created a custom mimetype that sends the information of the index and the level of depth that it has, and it will only move those indexes that have the same level as the children of the destination.

class TreeView(QTreeView):
    customMimeType = "application/x-customqstandarditemmodeldatalist"

    def __init__(self, *args, **kwargs):
        QTreeView.__init__(self, *args, **kwargs)
        self.setSelectionMode(QAbstractItemView.SingleSelection)
        self.setDragEnabled(True)
        self.viewport().setAcceptDrops(True)
        self.setDropIndicatorShown(True)
        self.setDragDropMode(QTreeView.InternalMove)

    def itemsToPixmap(self, indexes):
        rect = self.viewport().visibleRegion().boundingRect()
        pixmap = QPixmap(rect.size())
        pixmap.fill(Qt.transparent)
        painter = QPainter(pixmap)
        for index in indexes:
            painter.drawPixmap(self.visualRect(index), self.viewport().grab(self.visualRect(index)))
        return pixmap

    def mimeTypes(self):
        mimetypes = QTreeView.mimeTypes(self)
        mimetypes.append(TreeView.customMimeType)
        return mimetypes

    def startDrag(self, supportedActions):
        drag = QDrag(self)
        mimedata = self.model().mimeData(self.selectedIndexes())

        encoded = QByteArray()
        stream = QDataStream(encoded, QIODevice.WriteOnly)
        self.encodeData(self.selectedIndexes(), stream)
        mimedata.setData(TreeView.customMimeType, encoded)

        drag.setMimeData(mimedata)
        px = self.itemsToPixmap(self.selectedIndexes())
        drag.setPixmap(px)
        drag.setHotSpot(self.viewport().mapFromGlobal(QCursor.pos()) - QPoint(self.horizontalOffset(),
                                                                              self.verticalOffset()))
        drag.exec_(supportedActions)

    def encodeData(self, items, stream):
        stream.writeInt32(len(items))
        for item in items:
            p = item
            rows = []
            while p.isValid():
                rows.append(p.row())
                p = p.parent()
            stream.writeInt32(len(rows))
            for row in reversed(rows):
                stream.writeInt32(row)

    def dropEvent(self, event):
        if event.source() == self:
            if event.mimeData().hasFormat(TreeView.customMimeType):
                encoded = event.mimeData().data(TreeView.customMimeType)
                items = self.decodeData(encoded, event.source())
                ix = self.indexAt(event.pos())
                current = self.model().itemFromIndex(ix)
                p = current
                level = 1
                while p:
                    p = p.parent()
                    level += 1
                for item, ilevel in items:
                    if level == ilevel:
                        item.parent().takeRow(item.row())
                        current.appendRow(item)
                self.clearSelection()
                event.acceptProposedAction()
        else:
            event.ignore()

    def decodeData(self, encoded, tree):
        items = []
        rows = []
        stream = QDataStream(encoded, QIODevice.ReadOnly)
        while not stream.atEnd():
            nItems = stream.readInt32()
            for i in range(nItems):
                path = stream.readInt32()
                row = []
                for j in range(path):
                    row.append(stream.readInt32())
                rows.append(row)

        for row in rows:
            it = self.model().item(row[0])
            for r in row[1:]:
                it = it.child(r)
            items.append((it, len(row)))
        return items

A complete example can be found in the following link

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
  • I've got problems with subclassing, because I use `loadUIC`, solved by copying the settings (height, width). But the big thing is that I didn't found a solution for the fact that its not longer possible to sort the entries. I've tried `current.insertRow(current.row(),item)`, but it inserts it as 1st child. `level` and ìlevel`are different, so there must be something else... – Papageno Nov 30 '17 at 07:49
  • If you create a QTreeView via Qt Designer then just promote it to use my class. – eyllanesc Nov 30 '17 at 08:25
  • I assigned it by `self.structureTreeModel = TreeModel()`, but it appears as extra widget in my window. – Papageno Nov 30 '17 at 11:34
  • You have to promote your QTreeWidget, check the following link: https://stackoverflow.com/questions/19622014/how-do-i-use-promote-to-in-qt-designer-in-pyqt4 – eyllanesc Nov 30 '17 at 11:58
  • I did check, the last link was broken. So I played around and finaly I got no error message. But the TreeView is either invisible, left upper corner or inside the QtreeView widget. I did what mentioned in the link. – Papageno Nov 30 '17 at 17:05
0

The following changes allow to move an item inside a row of childs, but not outside. Still working for changing the cursor if target is not the same level.

def dropEvent(self, event):
    if event.source() == self:
        if event.mimeData().hasFormat(TreeView.customMimeType):
            encoded = event.mimeData().data(TreeView.customMimeType)
            items = self.decodeData(encoded, event.source())
            ix = self.indexAt(event.pos())
            current = self.model().itemFromIndex(ix)
            p = current
            level = 0
            while p:
                p = p.parent()
                level += 1
            for item, ilevel in items:
                if level == ilevel:
                    item.parent().takeRow(item.row())
                    current.parent().insertRow(current.row(),item)
            self.clearSelection()
            event.acceptProposedAction()
    else:
        event.ignore()
Papageno
  • 305
  • 3
  • 15