PyQt - Drag and Drop into Different QLabel Widgets

I am using Windows, Python 3.6, OpenCV3, and PyQt5. I have a main window that has two QLabel widgets (label1 and label2). I want to drag and drop different video files into both QLabel widgets. My script displays the first frame of each of the two video files.

  • If I drag a file through label1 into label2 and then release the mouse, then the first video frame is displayed in label1 (not what I want).

  • If I drag a file around label1 into label2 and then release the mouse, then the first video frame is displayed in label2 (desired effect).

  • If I drag a file through label2 into label1 and then release the mouse, then the first video frame is displayed in label1 (desired effect).

I want to have the video display in label2 regardless if I drag the file around or through label1. Suggestions?

import sys, cv2
from PyQt5.QtWidgets import QApplication, QLabel, QFrame, QMainWindow
from PyQt5.QtGui import QImage, QPixmap
from PyQt5.QtCore import Qt

class Example(QMainWindow):

    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):

        self.setGeometry(200, 300, 800, 600)
        self.setAcceptDrops(True)
        self.setMouseTracking(True)

        self.label1 = QLabel(self)
        self.label1.move(10, 10)
        self.label1.resize(780, 280)
        self.label1.setFrameShape(QFrame.Box)
        self.label1.setAcceptDrops(True)

        self.label2 = QLabel(self)
        self.label2.move(10, 310)
        self.label2.resize(780, 280)
        self.label2.setFrameShape(QFrame.Box)
        self.label2.setAcceptDrops(True)

        self.label1.setText("Label 1")
        self.label2.setText("Label 2")
        self.show()

    def dragEnterEvent(self, e):
        if e.mimeData().hasUrls:
            e.accept()
        else:
            e.ignore()

    def dropEvent(self, e):
        if e.mimeData().hasUrls:
            e.accept()
            for url in e.mimeData().urls():
                if self.label1.underMouse():
                    fname = str(url.toLocalFile())
                    self.openFile1(fname)
                elif self.label2.underMouse():
                    fname = str(url.toLocalFile())
                    self.openFile2(fname)
        else:
            e.ignore()

    def openFile1(self, filename):
        self.cap1 = cv2.VideoCapture(str(filename))
        self.cap1.set(cv2.CAP_PROP_POS_FRAMES, 0)
        width = self.cap1.get(cv2.CAP_PROP_FRAME_WIDTH)
        height = self.cap1.get(cv2.CAP_PROP_FRAME_HEIGHT)
        ret, frame = self.cap1.read()
        if ret == True:
            frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
            img = QImage(frame, frame.shape[1], frame.shape[0], QImage.Format_RGB888)
            pix = QPixmap.fromImage(img)
            pix = self.scalePix(self.label1, pix, width, height)
            self.label1.setPixmap(pix)

    def openFile2(self, filename):
        self.cap2 = cv2.VideoCapture(str(filename))
        self.cap2.set(cv2.CAP_PROP_POS_FRAMES, 0)
        width = self.cap2.get(cv2.CAP_PROP_FRAME_WIDTH)
        height = self.cap2.get(cv2.CAP_PROP_FRAME_HEIGHT)
        ret, frame = self.cap2.read()
        if ret == True:
            frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
            img = QImage(frame, frame.shape[1], frame.shape[0], QImage.Format_RGB888)
            pix = QPixmap.fromImage(img)
            pix = self.scalePix(self.label1, pix, width, height)
            self.label2.setPixmap(pix)

    def scalePix(self, label, p, width, height):
        window_width = label.width()
        ratio = height / width
        window_height = int(ratio * window_width)
        window_height = label.height() 
        window_width = int(1 / ratio * window_height)
        p = p.scaledToWidth(window_width, Qt.SmoothTransformation)
        p = p.scaledToHeight(window_height, Qt.SmoothTransformation)
        return p

if __name__ == '__main__':    
    app = QApplication(sys.argv)
    ex = Example()
    ex.show()
    sys.exit(app.exec_())

1 answer

  • answered 2018-12-06 06:50 eyllanesc

    Documentation indicates that underMouse() may fail in the drag and drop process:

    bool QWidget::underMouse() const

    Returns true if the widget is under the mouse cursor; otherwise returns false.

    This value is not updated properly during drag and drop operations.

    So it's better not to use, instead we can create a custom QLabel that implements the drag and drop:

    import sys, cv2
    from PyQt5 import QtCore, QtGui, QtWidgets
    
    class OpenCVLabel(QtWidgets.QLabel):
        def __init__(self, *args, **kwargs):
            super(OpenCVLabel, self).__init__(*args, **kwargs)
            self.setFrameShape(QtWidgets.QFrame.Box)
            self.setAcceptDrops(True)
    
        def dragEnterEvent(self, e):
            if e.mimeData().hasUrls():
                e.accept()
            else:
                e.ignore()
    
        def dropEvent(self, e):
            if e.mimeData().hasUrls():
                e.accept()
                for url in e.mimeData().urls():
                    self.openFile(url.toLocalFile())
            else:
                e.ignore()
    
        def openFile(self, filename):
            cap = cv2.VideoCapture(str(filename))
            cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
            width = cap.get(cv2.CAP_PROP_FRAME_WIDTH)
            height = cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
            ret, frame = cap.read()
            if ret:
                frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
                img = QtGui.QImage(frame, frame.shape[1], frame.shape[0], QtGui.QImage.Format_RGB888)
                pix = QtGui.QPixmap.fromImage(img)
                pix = pix.scaled(self.size(), QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)
                self.setPixmap(pix)
    
    class Example(QtWidgets.QMainWindow):
        def __init__(self):
            super().__init__()
            self.initUI()
    
        def initUI(self):
            label1 = OpenCVLabel("label1")
            label2 = OpenCVLabel("label2")
            central_widget = QtWidgets.QWidget()
            self.setCentralWidget(central_widget)
            lay = QtWidgets.QVBoxLayout(central_widget)
            lay.addWidget(label1)
            lay.addWidget(label2)
            self.resize(780, 560)
    
    if __name__ == '__main__':    
        app = QtWidgets.QApplication(sys.argv)
        ex = Example()
        ex.show()
        sys.exit(app.exec_())