1
votes

I've created a custom QListWidget with custom list widgets (just QPushButton widgets, for this example) added to it's QListWidgetItems, all setup so that I can drag and drop from a QPushButton, to add another button to the QListWidget. In the first list widget, you'll notice that you get a dotted line inbetween two list widget items, indicating that you can drag and insert a widget between two existing ones. However, with my custom list, I've lost that behavior. I can still drop them between existing widgets, but there's no visual indicator to the user as before, of a horizontal bar between the two widgets where it will be inserted. Does anyone know how I would do that? You can see in the image below, the line in the first list that is the default indicator for inserting an item between two. The red arrow points to that kind of custom insertion indicator that I'd like to create.

insertIndicator

And secondly, when I drag and drop from my custom QPushButton, it is highlighted black, but never returns to its normal grey color again, as shown in the image above. How would I get this button back to its default state?

from inspect import isclass
from shiboken2 import wrapInstance
from PySide2 import QtCore, QtGui, QtWidgets
from maya import OpenMayaUI as omui


def show_ui():
    main_win_obj = omui.MQtUtil.mainWindow()
    main_win = wrapInstance(long(main_win_obj), QtWidgets.QWidget)
    win = Test(parent=main_win)
    win.show()


class DragButton(QtWidgets.QPushButton):

    def __init__(self, parent=None, text=''):
        super(DragButton, self).__init__()
        self.setText(text)

    def mouseMoveEvent(self, event):

        if event.buttons() != QtCore.Qt.LeftButton:
            super(DragButton, self).mouseMoveEvent(event)
            return

        btn_img = self.grab()
        painter = QtGui.QPainter(btn_img)
        painter.setCompositionMode(
            painter.CompositionMode_DestinationIn
        )
        painter.fillRect(btn_img.rect(), QtGui.QColor(0, 0, 0, 127))
        painter.end()

        data = QtCore.QMimeData()
        data.setText('ReorderListAdd()')

        drag = QtGui.QDrag(self)
        drag.setMimeData(data)
        drag.setPixmap(btn_img)
        drag.setHotSpot(event.pos())
        drag.exec_(QtCore.Qt.CopyAction)

        super(DragButton, self).mouseMoveEvent(event)

    def mouseReleaseEvent(self, event):

        data = QtCore.QMimeData()
        data.setText('my text')

        drag = QtGui.QDrag(self)
        drag.setMimeData(data)
        drag.exec_()

        super(DragButton, self).mouseReleaseEvent(event)


class ReorderList(QtWidgets.QListWidget):

    def __init__(self, parent=None):
        super(ReorderList, self).__init__(parent)

        self.setDragDropMode(
            QtWidgets.QAbstractItemView.InternalMove
        )
        self.setAcceptDrops(True)
        self.setAlternatingRowColors(True)
        self.setSelectionMode(
            QtWidgets.QAbstractItemView.SingleSelection
        )
        self.setDragDropMode(
            QtWidgets.QAbstractItemView.InternalMove
        )

    def add_btn(self, text='', index=-1):

        widget = QtWidgets.QWidget()
        if not text:
            text = 'item {0}'.format(self.count() + 1)
        btn = QtWidgets.QPushButton(text)

        layout = QtWidgets.QVBoxLayout(widget)
        layout.setContentsMargins(2, 2, 2, 2)
        layout.setSpacing(2)
        layout.addWidget(btn)

        item = QtWidgets.QListWidgetItem()
        item.setSizeHint(widget.sizeHint())

        if index < 0:
            self.addItem(item)
        else:
            self.insertItem(index, item)
        self.setItemWidget(item, widget)

    def dropEvent(self, event):
        drop_index = self.indexAt(event.pos()).row()
        self.add_btn(index=drop_index)
        super(ReorderList, self).dropEvent(event)

    def dragEnterEvent(self, event):
        event.accept()

    def dragMoveEvent(self, event):
        event.accept()

    def onClick(self):
        print self.sender().text()


class Test(QtWidgets.QMainWindow):

    def __init__(self, *args, **kwargs):
        super(Test, self).__init__(*args, **kwargs)

        self.list1 = QtWidgets.QListWidget()
        self.list1.setDragDropMode(
            QtWidgets.QAbstractItemView.DragDrop
        )
        self.list1.setSelectionMode(
            QtWidgets.QAbstractItemView.SingleSelection
        )
        self.list1.setDragDropMode(
            QtWidgets.QAbstractItemView.InternalMove
        )
        self.list1.setMaximumHeight(60)
        self.list2 = ReorderList()
        self.list2.setMaximumHeight(120)
        for name in ('item 1', 'item 2', 'item 3'):
            item = QtWidgets.QListWidgetItem(name)
            self.list1.addItem(item)
            self.list2.add_btn(text=name)
        self.btn = DragButton(text='Add list button')
        self.btn.clicked.connect(self.list2.add_btn)

        self.widget = QtWidgets.QWidget()
        self.setCentralWidget(self.widget)
        self.layout = QtWidgets.QVBoxLayout(self.widget)
        for item in (self.list1, self.list2, self.btn):
            self.layout.addWidget(item)


if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    win = Test()
    win.show()
    sys.exit(app.exec_())

I'd like to be able to have a horizontal bar when inserting a widget in the QListWidget, and have the buttons that have been drug return to their default color.

Here are some similar links I could find that helped get me to this point:

2
In SO we require that it be minimum and complete at the same time. Your code is minimal but not complete, complete implies that it is not necessary to add anything else to execute it but your code is far from it. The idea of not adding is that maybe in that part that is missing the error and so we do not see it we can not tell you where the problem was, please take the time to read that it is a minimal reproducible example and provide the requested code. ā€“ eyllanesc
Great, I will do the same. With your modifications, your question is very clear :-) ā€“ eyllanesc

2 Answers

1
votes

You have to do the painting since the drag-and-drop is customized, for that you must detect the rectangle of the item using QModelIndex in the method dragMoveEvent. Then make the painting but because of the size of the widget, it will not be seen so I look at it, I created a delegate that modifies the geometry of the editor.

class Delegate(QtWidgets.QStyledItemDelegate):
    def updateEditorGeometry(self, editor, option, index):
        super(Delegate, self).updateEditorGeometry(editor, option, index)
        geo = editor.geometry().adjusted(0, 4, 0, 0)
        editor.setGeometry(geo)

class ReorderList(QtWidgets.QListWidget):
    def __init__(self, parent=None):
        super(ReorderList, self).__init__(parent)
        delegate = Delegate(self)
        self.setItemDelegate(delegate)
        self.dropIndicatorRect = QtCore.QRect()
        self.setDragDropMode(
            QtWidgets.QAbstractItemView.InternalMove
        )
        self.setAcceptDrops(True)
        self.setAlternatingRowColors(True)
        self.setSelectionMode(
            QtWidgets.QAbstractItemView.SingleSelection
        )
        self.setDragDropMode(
            QtWidgets.QAbstractItemView.InternalMove
        )

    def add_btn(self, text='', index=-1):
        widget = QtWidgets.QWidget()
        if not text:
            text = 'item {0}'.format(self.count() + 1)
        btn = QtWidgets.QPushButton(text)

        item = QtWidgets.QListWidgetItem()
        item.setSizeHint(btn.sizeHint())

        if index < 0:
            self.addItem(item)
        else:
            self.insertItem(index, item)
        self.setItemWidget(item, btn)

    def dropEvent(self, event):
        drop_index = self.indexAt(event.pos()).row()
        self.add_btn(index=drop_index)
        self.dropIndicatorRect = QtCore.QRect()
        self.viewport().update()
        super(ReorderList, self).dropEvent(event)

    def dragEnterEvent(self, event):
        event.accept()

    def dragLeaveEvent(self, event):
        self.dropIndicatorRect = QtCore.QRect()
        self.viewport().update()
        super(ReorderList, self).dragLeaveEvent(event)

    def dragMoveEvent(self, event):
        event.accept()

    def onClick(self):
        print(self.sender().text())

    def dragMoveEvent(self, event):
        index = self.indexAt(event.pos())
        if index.isValid():
            rect = self.visualRect(index)
            if self.dropIndicatorPosition() == QtWidgets.QAbstractItemView.OnItem:
                self.dropIndicatorRect = rect
            else:
                self.dropIndicatorRect = QtCore.QRect()
        else:
            self.dropIndicatorRect = QtCore.QRect()
        self.viewport().update()
        super(ReorderList, self).dragMoveEvent(event)

    def paintEvent(self, event):
        super(ReorderList, self).paintEvent(event)
        if not self.dropIndicatorRect.isNull() and self.showDropIndicator():
            painter = QtGui.QPainter(self.viewport())
            p = QtGui.QPen(painter.pen())
            p.setWidthF(1.5)
            painter.setPen(p)
            r = self.dropIndicatorRect
            painter.drawLine(r.topLeft(), r.topRight())
0
votes

Iā€™m not quite sure that I understood you correctly, but try as an example below:

import sys
from PyQt5 import QtCore, QtGui, QtWidgets

class DragButton(QtWidgets.QPushButton):
    def __init__(self, *args, **kwargs):
        super(DragButton, self).__init__(*args, **kwargs)

    def mouseMoveEvent(self, event):
        if event.buttons() != QtCore.Qt.LeftButton:
            super(DragButton, self).mouseMoveEvent(event)
            return

#        btn_img = QtGui.QPixmap.grabWidget(self)                   # ---
        btn_img = self.grab()                                       # +++

        painter = QtGui.QPainter(btn_img)
        painter.setCompositionMode(
            painter.CompositionMode_DestinationIn
        )
        painter.fillRect(btn_img.rect(), QtGui.QColor(0, 0, 0, 127))
        painter.end()
        data = QtCore.QMimeData()
        data.setText('ReorderListAdd()')
        drag = QtGui.QDrag(self)
        drag.setMimeData(data)
        drag.setPixmap(btn_img)
        drag.setHotSpot(event.pos())
        drag.exec_(QtCore.Qt.CopyAction)
        super(DragButton, self).mouseMoveEvent(event)

    def mouseReleaseEvent(self, event):
        data = QtCore.QMimeData()
        data.setText('my text')
        drag = QtGui.QDrag(self)
        drag.setMimeData(data)
        drag.exec_()
        super(DragButton, self).mouseReleaseEvent(event)


class ReorderList(QtWidgets.QListWidget):

    def __init__(self, parent=None):
        super(ReorderList, self).__init__(parent)

        self.setDragDropMode(
            QtWidgets.QAbstractItemView.InternalMove
        )
        self.setAcceptDrops(True)
        self.setAlternatingRowColors(True)
        self.setSelectionMode(
            QtWidgets.QAbstractItemView.ExtendedSelection
        )

    def add_widget(self, widget=None, index=-1):
        item = QtWidgets.QListWidgetItem()
        item.setSizeHint(widget.sizeHint())
        if index < 0:
            self.addItem(item)
        else:
            self.insertItem(index, item)
        self.setItemWidget(item, widget)

    def add_btn(self, txt='', index=-1):
        if not txt or not isinstance(txt, basestring):
            btn_name = 'Button Item {:02d}'.format(self.count() + 1)
        else:
            btn_name = txt

#        widget = ReorderListAdd(btn_name)                            # ---
        widget = QtWidgets.QPushButton(btn_name)                      # +++
        widget.setIcon(QtGui.QIcon("Ok.png"))                         # +++
        widget.clicked.connect(self.onClick)                          # +++

        if index < 0:
            self.add_widget(widget)
        else:
            self.add_widget(widget, index)

    def dropEvent(self, event):
        drop_index = self.indexAt(event.pos()).row()
        if event.mimeData().hasText():
            if event.mimeData().text() == 'ReorderListAdd()':
                self.add_btn(index=drop_index)
        super(ReorderList, self).dropEvent(event)

    def dragEnterEvent(self, event):
        event.accept()

    def dragMoveEvent(self, event):
        event.accept()

# +++      
    def onClick(self):                                                 # +++
        print(self.sender().text())


class MainView(QtWidgets.QWidget):               
    def __init__(self):
        super().__init__()

        self.listWidget = ReorderList()
        self.listWidget.addItems(["Item 1 1 1 ", "Item 22 22", "Item 3 33 333", ])

        self.button = DragButton()
        self.button.setText("Button")
        self.button.setIcon(QtGui.QIcon("Ok.png"))

        layout = QtWidgets.QVBoxLayout(self)
        layout.addWidget(self.listWidget)
        layout.addWidget(self.button)


if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    w = MainView()
    w.show()
    sys.exit(app.exec_())          

enter image description here