Issue
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)
Solution
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.
Answered By - musicamante
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.