Search code examples
pythonqtbuttondrag-and-droppyqt4

PyQt4 - Drag and Drop


Hey I had been going through this tutorial for understanding drag and drop methods in PyQt4. However I am not able to understand the following points . It would be nice if somepne could make it clearer to me.

 def mouseMoveEvent(self, e): //class Button


    mimeData = QtCore.QMimeData()

    drag = QtGui.QDrag(self)
    drag.setMimeData(mimeData)
    drag.setHotSpot(e.pos() - self.rect().topLeft())

    dropAction = drag.start(QtCore.Qt.MoveAction)

def dropEvent(self, e): //class Example

    position = e.pos()
    self.button.move(position)

    e.setDropAction(QtCore.Qt.MoveAction)
    e.accept()

Why is there are a seperate self.button.move() and e.setDropAction() Doesnt self.button.move() actually move the button itself? And could someone explain what drag.setHotSpot and drag.start() do? Thanks.


Solution

  • That tutorial is seriously outdated. QDrag.start is obsolete since Qt 4.3. QDrag.exec_ should be used instead.

    As you can see from the docs for exec, it has a return value. setDropAction in dropEvent determines this value. It doesn't perform the move. That's why you need a self.button.move() to do the actual moving. So, what's the point of a setDropAction? You might need to know what kind of drag operation you did. Imagine you're implementing drag-drop between two list widgets. If you did a move operation, that means you need to remove the item from the source widget and create one in the target. If it was a copy operation, you can leave the original and just create a copy in the target.

    setHotSpot/hotSpot is related to the setPixmap of a QDrag. You can display a QPixmap as you drag the item. hotSpot determines the positioning of the pixmap. The pixmap will be positioned such that the cursor will be at hotSpot relative to the top-left corner of the pixmap. So, in the case of that tutorial, it is rather pointless since there is no pixmap to be shown.

    Here is a bit modified and updated version of that tutorial. Hopefully, I've included enough comments. You can move with Right-Click or copy with Shift + Right-Click:

    #!/usr/bin/python
    # -*- coding: utf-8 -*-
    
    import sys
    from PyQt4 import QtGui, QtCore
    
    
    class Button(QtGui.QPushButton):
        def mouseMoveEvent(self, e):
            if e.buttons() != QtCore.Qt.RightButton:
                return
    
            # write the relative cursor position to mime data
            mimeData = QtCore.QMimeData()
            # simple string with 'x,y'
            mimeData.setText('%d,%d' % (e.x(), e.y()))
    
            # let's make it fancy. we'll show a "ghost" of the button as we drag
            # grab the button to a pixmap
            pixmap = QtGui.QPixmap.grabWidget(self)
    
            # below makes the pixmap half transparent
            painter = QtGui.QPainter(pixmap)
            painter.setCompositionMode(painter.CompositionMode_DestinationIn)
            painter.fillRect(pixmap.rect(), QtGui.QColor(0, 0, 0, 127))
            painter.end()
    
            # make a QDrag
            drag = QtGui.QDrag(self)
            # put our MimeData
            drag.setMimeData(mimeData)
            # set its Pixmap
            drag.setPixmap(pixmap)
            # shift the Pixmap so that it coincides with the cursor position
            drag.setHotSpot(e.pos())
    
            # start the drag operation
            # exec_ will return the accepted action from dropEvent
            if drag.exec_(QtCore.Qt.CopyAction | QtCore.Qt.MoveAction) == QtCore.Qt.MoveAction:
                print 'moved'
            else:
                print 'copied'
    
    
        def mousePressEvent(self, e):
            QtGui.QPushButton.mousePressEvent(self, e)
            if e.button() == QtCore.Qt.LeftButton:
                print 'press'
    
    
    
    class Example(QtGui.QWidget):
        def __init__(self):
            super(Example, self).__init__()
            self.initUI()
    
    
        def initUI(self):
            self.setAcceptDrops(True)
    
            button = Button('Button', self)
            button.move(100, 65)
    
            self.buttons = [button]
    
            self.setWindowTitle('Copy or Move')
            self.setGeometry(300, 300, 280, 150)
    
    
        def dragEnterEvent(self, e):
            e.accept()
    
    
        def dropEvent(self, e):
            # get the relative position from the mime data
            mime = e.mimeData().text()
            x, y = map(int, mime.split(','))
    
            if e.keyboardModifiers() & QtCore.Qt.ShiftModifier:
                # copy
                # so create a new button
                button = Button('Button', self)
                # move it to the position adjusted with the cursor position at drag
                button.move(e.pos()-QtCore.QPoint(x, y))
                # show it
                button.show()
                # store it
                self.buttons.append(button)
                # set the drop action as Copy
                e.setDropAction(QtCore.Qt.CopyAction)
            else:
                # move
                # so move the dragged button (i.e. event.source())
                e.source().move(e.pos()-QtCore.QPoint(x, y))
                # set the drop action as Move
                e.setDropAction(QtCore.Qt.MoveAction)
            # tell the QDrag we accepted it
            e.accept()
    
    
    
    if __name__ == '__main__':
        app = QtGui.QApplication(sys.argv)
        ex = Example()
        ex.show()
        app.exec_()