Ahmed4end
Ahmed4end

Reputation: 324

Minimal example of dragging a QLabel image to desktop

I'm trying to make a Qlabel that is draggable outside of the main widget but all my attempts fail to drag it to the file explorer path and save it as image. Is there any third party documentation that allow that functionality?

My last attempt is as follows:

class DraggableLabel(QtWidgets.QLabel):
    def __init__(self,image):
        QtWidgets.QLabel.__init__(self)
        self.setPixmap(image)    
        self.setAcceptDrops(True)
        self.show()

    def mousePressEvent(self, event):
        if event.button() == QtGui.Qt.LeftButton:
            self.drag_start_position = event.pos()

    def mouseMoveEvent(self, event):
        if not (event.buttons() & QtGui.Qt.LeftButton):
            return
        if (event.pos() - self.drag_start_position).manhattanLength() < QtWidgets.QApplication.startDragDistance():
            return
        drag = QtGui.QDrag(self)
        mimedata = QtCore.QMimeData()
        mimedata.setImageData(self.pixmap().toImage())

        drag.setMimeData(mimedata)
        drag.setHotSpot(event.pos())
        drag.exec_(QtGui.Qt.CopyAction | QtGui.Qt.MoveAction)
        
        source = drag.source()
        print("source = %s" % source)
        target = drag.target()   # how to return this target as file explorer path ?
        print("target = %s" % target)

Upvotes: 1

Views: 192

Answers (1)

musicamante
musicamante

Reputation: 48384

The target() of QDrag only works for objects that are part of the application (in fact, it returns a QObject type, meaning that only internal Qt types can be retrieved). This means that you cannot know anything about drops on external targets.

The only Qt (and cross-platform compliant) possible solution is to create a temporary file on which the image is saved, and use setUrls() with a single-item list that contains the temporary file path.

The only (and unavoidable) caveat of this approach is that you cannot set the target file name: the file name is internally decided by Qt, and the only possible customization is based on a template file name that will result in some "random" letters and numbers used instead of the XXXXXX placeholder.

class DraggableLabel(QtWidgets.QLabel):
    def __init__(self, image):
        super().__init__(pixmap=image)
        self.tempFiles = []
        self.setAcceptDrops(True)
        QtWidgets.QApplication.instance().aboutToQuit.connect(
            self.deleteTemporaryFiles)

    def mousePressEvent(self, event):
        if event.button() == QtCore.Qt.LeftButton:
            self.drag_start_position = event.pos()

    def mouseMoveEvent(self, event):
        if not event.buttons() & QtCore.Qt.LeftButton:
            return
        if (event.pos() - self.drag_start_position).manhattanLength() < QtWidgets.QApplication.startDragDistance():
            return

        drag = QtGui.QDrag(self)
        mimedata = QtCore.QMimeData()
        mimedata.setImageData(self.pixmap().toImage())

        tempFile = QtCore.QTemporaryFile(
            QtCore.QDir.tempPath()
            + QtCore.QDir.separator()
            + 'exportXXXXXX.png')
        # temporary files are removed upon their destruction, since the copy
        # or move operations can take an undefined amount of time, we need to
        # keep a reference and eventually destroy them after a reasonable
        # amount of time
        self.tempFiles.append(tempFile)

        self.pixmap().save(tempFile)
        mimedata.setUrls([QtCore.QUrl.fromLocalFile(tempFile.fileName())])

        drag.setPixmap(self.pixmap().scaled(64, 64, 
            QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation))
        drag.setMimeData(mimedata)
        drag.exec_(QtCore.Qt.CopyAction | QtCore.Qt.MoveAction)

        # an arbitrary amount of time for the removal of the temporary file
        QtCore.QTimer.singleShot(30000, self.deleteTemporaryFiles)

    def deleteTemporaryFiles(self):
        while self.tempFiles:
            self.tempFiles.pop().deleteLater()

Finally, an extremely important note: considering that the drag operation is completely based on temporary files, this can represent a serious issue whenever the file is dropped on a program that supports direct file dragging from the file manager and doesn't properly manage the access to that file: since the code above deletes the temporary file(s) for performance/safety/security reasons, that source file can become invalid. If the target program accepts a drop from your application, it is possible that the program won't be able to access it whenever the deleteTemporaryFiles gets called and the result is completely unexpected. That program could crash, your program could crash, or you might even get a total system crash.
While the provided code does work, you must be aware of this aspect, and eventually warn the user about its usage.

Upvotes: 2

Related Questions