Mouse tracking on a widget

I am creating a generic testing widget that I may inherit from later on to track the mouse in a specific widget. The advantage of this is that the mouse position is relative to the widget (useful for painting in the GraphicsView for example) and that I am certain on which widget the mouse is when the events are triggered.

Setting up a mouse widget is fairly simple, we inherit from QWidget and override any mouse related events. What I wish to know about the mouse is where it is now, where it was last pressed and if it is still pressed.

class MouseWidget(QtGui.QWidget):
    def __init__(self, parent=None):
        QtGui.QWidget.__init__(self, parent)
        self.position = Vec(0,0)
        self.leftState = ButtonState.up
        self.dragStart = Vec(0,0)

Also we need to add signals so that other code can attach events to the mouse callbacks – even an inherited class may choose to, instead of overriding the mousePressed again, connect to it’s own signals for cleaner code.

These signals need to be class variables, because pyqtSignals are always unbound, which can’t be connected or emitted to, this is because of the difference in python and C++ implementations of singals. The problem gets resolved when the class is created (not instanced), so classes can store bound signals, instances can’t, so creating signals in a function (whether it’s __init__ or another) is useless.

    onMousePressed = QtCore.pyqtSignal()
    onMouseReleased = QtCore.pyqtSignal()
    onMouseMoved = QtCore.pyqtSignal()
    onMouseLeave = QtCore.pyqtSignal()
    onMouseEnter = QtCore.pyqtSignal()

The last thing to do in the init funtoin is to enable mouse tracking, this will make sure the mouseMove event is triggered also when no mouse button is pressed (which normally isn’t the case).

        self.setMouseTracking(True)

– On press I update the last clicked position and say left button is pressed,

    def mousePressEvent(self,e):
        self.leftState = ButtonState.press
        v = self.mouseEventPosition(e)
        self.dragStart = v
        self.position = v
        self.onMousePressed.emit()

On release I say left button is up

    def mouseMoveEvent(self, e):
        if self.leftState%2:
            self.leftState -= 1 
        self.position = self.mouseEventPosition(e)
        self.onMouseMoved.emit()

On move I update the position and update the state from release to up and from press to down.

    def mouseReleaseEvent(self,e):
        self.leftState = ButtonState.release
        self.position = self.mouseEventPosition(e)
        self.onMouseReleased.emit()

But now there’s the special cases of leave and enter, when I am dragging an object around and leave the widget, I can release the mouse there and won’t get notified about it, so instead I treat leaving as undoing, I snap the mouse position to the last clicked position.

    def leaveEvent(self,e):
        if self.leftState > 1:
            #clear dragging
            self.mouse.position = self.mouse.dragStart
        self.onMouseLeave.emit()

Also on the enterEvent the mouse is reset to be up because we have no measurement of what happened while the mouse was off the widget.

    def enterEvent(self,e):
        if self.leftState > 1:
            #assume left mouse button as long released
            self.mouse.leftState = ButtonState.up
        self.onMouseEnter.emit()

Then this is my ButtonState, which is basically a python enum. Please do read the link in the comments for more information:

'''
Enum pattern from
http://stackoverflow.com/a/1695250/1971060
'''
def enum(*sequential, **named):
    enums = dict(zip(sequential, range(len(sequential))), **named)
    reverse = dict((value, key) for key, value in enums.iteritems())
    enums['index'] = reverse
    return type('Enum', (), enums)

ButtonState = enum('UP','RELEASE','DOWN','PRESS')

Leave a Reply

Your email address will not be published. Required fields are marked *