Search code examples
pythonpyqt4

Read or pass data outside class of mouse event


I have a widget that uses rubberband to select region on an image, it can be editable on any aspect. I need to add functions that once the rubberband is placed it can be resizable or editable again so my idea is to save the region points(x,y,w,h) after the mouse release event.

But the problem now is how can I pass the data read in mouse release event outside my rubberband class.

Here is my code of drag and edit of rubberband.

class rubberBandWidget(QtGui.QWidget):
    def __init__(self, parent=None):
        QtGui.QWidget.__init__(self, parent)
        self.rubberBand = QRubberBand(QRubberBand.Rectangle, self)
        self.tweaking = False
        self.tweakingpart = ""

    def mousePressEvent(self, event):
        pt = self.mapFromGlobal(event.globalPos())
        rg = self.rubberBand.geometry()

        if rg.isValid():
            tl, tr, bl, br = rg.topLeft(), rg.topRight(), rg.bottomLeft(), rg.bottomRight()
            off, offx, offy = QPoint(3, 3), QPoint(4, -3), QPoint(-3, 4)

            if QRect(tl - off, tl + off).contains(pt):
                self.tweakingpart = "topLeft";
                self.setCursor(Qt.SizeFDiagCursor)
            elif QRect(tr - off, tr + off).contains(pt):
                self.tweakingpart = "topRight";
                self.setCursor(Qt.SizeBDiagCursor)
            elif QRect(bl - off, bl + off).contains(pt):
                self.tweakingpart = "bottomLeft";
                self.setCursor(Qt.SizeBDiagCursor)
            elif QRect(br - off, br + off).contains(pt):
                self.tweakingpart = "bottomRight";
                self.setCursor(Qt.SizeFDiagCursor)
            elif QRect(tl + offx, tr - offx).contains(pt):
                self.tweakingpart = "top";
                self.setCursor(Qt.SizeVerCursor)
            elif QRect(bl + offx, br - offx).contains(pt):
                self.tweakingpart = "bottom"
                self.setCursor(Qt.SizeVerCursor)
            elif QRect(tl + offy, bl - offy).contains(pt):
                self.tweakingpart = "left";
                self.setCursor(Qt.SizeHorCursor)
            elif QRect(tr + offy, br - offy).contains(pt):
                self.tweakingpart = "right";
                self.setCursor(Qt.SizeHorCursor)

            if self.tweakingpart != "":
                self.tweaking = True
                return

        self.origin = pt
        self.rubberBand.setGeometry(QRect(self.origin, QtCore.QSize()))
        self.rubberBand.show()

    def mouseMoveEvent(self, event):
        pt = self.mapFromGlobal(event.globalPos())
        if self.tweaking:
            rg = self.rubberBand.geometry()
            if self.tweakingpart == "topLeft":
                rg.setTopLeft(pt)
            elif self.tweakingpart == "topRight":
                rg.setTopRight(pt)
            elif self.tweakingpart == "bottomLeft":
                rg.setBottomLeft(pt)
            elif self.tweakingpart == "bottomRight":
                rg.setBottomRight(pt)
            elif self.tweakingpart == "top":
                rg.setTop(pt.y())
            elif self.tweakingpart == "bottom":
                rg.setBottom(pt.y())
            elif self.tweakingpart == "left":
                rg.setLeft(pt.x())
            elif self.tweakingpart == "right":
                rg.setRight(pt.x())
            self.rubberBand.setGeometry(rg)
        else:
            self.rubberBand.setGeometry(QRect(self.origin, pt).normalized())

And here is my code on release event and the data of (x, y, w, h) that need to be pass or read outside the class.

def mouseReleaseEvent(self, event):
    self.tweaking = False
    self.tweakingpart = ""
    self.unsetCursor()

    if self.rubberBand.width() != 0 and self.rubberBand.height() != 0:
        print(self.rubberBand.x(), self.rubberBand.y(), self.rubberBand.width(), self.rubberBand.height())

I need the data every mouse release and save it. So once the user need to resize and edit again, my idea is to set the geometry of rubberband and re run the class so it can be editable again.


Solution

  • If you want to expose the data outside the class you should use signals as I show below:

    import sys
    
    from PyQt4 import QtCore, QtGui
    
    class rubberBandWidget(QtGui.QWidget):
        rectChanged = QtCore.pyqtSignal(QtCore.QRect) # create signal
        ...
        def mouseReleaseEvent(self, event):
            self.tweaking = False
            self.tweakingpart = ""
            self.unsetCursor()
    
            if not self.rubberBand.geometry().isNull():
                self.rectChanged.emit(self.rubberBand.geometry()) # emit signal
    
    
    if __name__ == '__main__':
        app = QtGui.QApplication(sys.argv)
        w = rubberBandWidget()
        def on_rectChanged(rect):
            # receiver
            print(rect.x(), rect.y(), rect.width(), rect.height())
        w.rectChanged.connect(on_rectChanged) # connect signal
        w.show()
        sys.exit(app.exec_())
    

    On the other hand I see that your code is very coupled because if you want to use the same functionality in another widget you will have to copy all the code and that is not desirable, so I take the time to create a custom QRubberband that has that functionality, In the next part I show an example.

    import sys
    
    from PyQt4 import QtCore, QtGui
    
    
    class RubberBand(QtGui.QRubberBand):
        rectChanged = QtCore.pyqtSignal(QtCore.QRect)
    
        TopLeft, TopRight, BottomLeft, BottomRight, Top, Bottom, Left, Right, NonePos = range(9)
    
        def __init__(self, parent=None):
            super(RubberBand, self).__init__(QtGui.QRubberBand.Rectangle, parent)
            self._widget = None
            self.setWidget(parent)
            self.tweakingpart = RubberBand.NonePos
    
            self.cursors = [QtCore.Qt.SizeFDiagCursor,
                            QtCore.Qt.SizeBDiagCursor,
                            QtCore.Qt.SizeBDiagCursor,
                            QtCore.Qt.SizeFDiagCursor,
                            QtCore.Qt.SizeVerCursor,
                            QtCore.Qt.SizeVerCursor,
                            QtCore.Qt.SizeHorCursor,
                            QtCore.Qt.SizeHorCursor]
    
        def setWidget(self, widget):
            if widget is None:
                return
            if self._widget is not None:
                self._widget.removeEventFilter(self)
            self._widget = widget
            self._widget.installEventFilter(self)
            self.setParent(widget)
    
        def eventFilter(self, obj, event):
            if self._widget is obj:
                if event.type() == QtCore.QEvent.MouseButtonPress:
                    self.handleMousePressEvent(event.pos())
                    return True
                elif event.type() == QtCore.QEvent.MouseMove:
                    self.handleMouseMoveEvent(event.pos())
                    return True
                elif event.type() == QtCore.QEvent.MouseButtonRelease:
                    self.handleMouseReleaseEvent(event.pos())
                    return True
            return super(RubberBand, self).eventFilter(obj, event)
    
        def handleMousePressEvent(self, pt):
            rg = self.geometry()
            if not rg.isValid():
                return
            off, offx, offy = QtCore.QPoint(3, 3), QtCore.QPoint(4, -3), QtCore.QPoint(-3, 4)
            rect = QtCore.QRect(-off, off)
            tl, tr, bl, br = rg.topLeft(), rg.topRight(), rg.bottomLeft(), rg.bottomRight()
            for i, coord in enumerate([tl, tr, bl, br]):
                rect.moveCenter(coord)
                if rect.contains(pt):
                    self.tweakingpart = i
            if QtCore.QRect(tl + offx, tr - offx).contains(pt):
                self.tweakingpart = RubberBand.Top
            elif QtCore.QRect(bl + offx, br - offx).contains(pt):
                self.tweakingpart = RubberBand.Bottom
            elif QtCore.QRect(tl + offy, bl - offy).contains(pt):
                self.tweakingpart = RubberBand.Left
            elif QtCore.QRect(tr + offy, br - offy).contains(pt):
                    self.tweakingpart = RubberBand.Right
    
            if 0 <= self.tweakingpart < RubberBand.NonePos:
                self._widget.setCursor(self.cursors[self.tweakingpart])
                return
            self.setGeometry(QtCore.QRect(pt, QtCore.QSize()))
            self.show()
    
        def handleMouseMoveEvent(self, pt):
            rg = self.geometry()
            if 0 <= self.tweakingpart < RubberBand.NonePos:
                if self.tweakingpart == RubberBand.TopLeft:
                    rg.setTopLeft(pt)
                elif self.tweakingpart == RubberBand.TopRight:
                    rg.setTopRight(pt)
                elif self.tweakingpart == RubberBand.BottomLeft:
                    rg.setBottomLeft(pt)
                elif self.tweakingpart == RubberBand.BottomRight:
                    rg.setBottomRight(pt)
                elif self.tweakingpart == RubberBand.Top:
                    rg.setTop(pt.y())
                elif self.tweakingpart == RubberBand.Bottom:
                    rg.setBottom(pt.y())
                elif self.tweakingpart == RubberBand.Left:
                    rg.setLeft(pt.x())
                elif self.tweakingpart == RubberBand.Right:
                    rg.setRight(pt.x())
            else:
                rg = QtCore.QRect(rg.topLeft(), pt).normalized()
            self.setGeometry(rg)
    
        def handleMouseReleaseEvent(self, pt):
            self.tweakingpart = RubberBand.NonePos
            self._widget.unsetCursor()
            if not self.geometry().isNull():
                self.rectChanged.emit(self.geometry())
    
    
    class TestWidget(QtGui.QWidget):
        pass
    
    
    if __name__ == '__main__':
        app = QtGui.QApplication(sys.argv)
        w = TestWidget()
        rubberBand = RubberBand(w)
        def on_rectChanged(rect):
            print(rect.x(), rect.y(), rect.width(), rect.height())
        rubberBand.rectChanged.connect(on_rectChanged)
        w.show()
        sys.exit(app.exec_())