Mouse event propagation

Have you ever implemented mouse handling in a Qt widget? Either in a custom widget, (that inherits QWidget directly,) or by extending some existing widget, let's say QLabel, with interaction abilities? For the most simple cases, mouse handling is quite intuitive in Qt: just reimplement a mouse handler function (virtual function, inherited from QWidget), and add your code there. Press followed by release: open dialog. Or press, drag: draw line. What happens if you ignore or accept mouse events? Should you always call the base class' implementation of these events? Well sometimes, even Qt does expect some tongue-in-cheek from you as a developer.

This one goes out to those who have ever reimplemented mouse event handlers in Qt.

Mouse events in Qt come in four flavors: Press, Move, Release, and DoubleClick (press-release-doubleclick-release, right?); all delivered to your widget as events, via the four event handlers QWidget::mousePressEvent(), QWidget::mouseMoveEvent(), and so on. At least six other events are related to mouse events, but treated separately: Enter and leave events are delivered to the widget when the mouse cursor enters or leaves the widget, as are hover enter and hover leave events (for widgets the set the WA_Hover attribute). There's the context menu event QContextMenuEvent, which on some platforms is invoked by the right mouse button (press on Unix, release on Windows), and on others it's a separate button on the keyboard. And the wheel event QWheelEvent, which represents scrolling activity, typically by a scroll ball on your keyboard, or the wheel on your mouse. None of the six latter are mouse events in Qt, so I'll stick to the four ones that are. :-)

Mouse press events are delivered by the window system to the widget under the cursor, (remember you can also punch holes in a widget by setting a mask,) at the point when any of the mouse buttons is pressed. From that point on, the window system stores this widget as the mouse grabber, that is, the widget that from this point on, and for as long as any mouse button stays pressed, will continue to receive mouse events. If you then move the mouse cursor, (even outside the widget's area,) that widget will receive mouse move events, mouse release events, and any additional mouse press events. When you release the last mouse button, the mouse grabber widget loses the mouse grab, and no longer receives mouse events. Now, if you want your widget to track mouse movement without clicking, you can enable something called mouse tracking on your widget, in which case Qt will send mouse move events to the widget if there is no current mouse grabber, and the mouse cursor is hovering over the widget. And, of course, you can always grab the mouse explicitly, but that's dangerous. Anyway. Phew. :-)

All these events can be accepted and ignored, like with any event in Qt. And for events that can propagate, mouse events being the typical example, understanding the differences is quite crucial.

Say you implement your own mouse event handling. If you ignore the mouse press event in mousePressEvent() by calling event->ignore(), it will first be mapped to your widget's parent geometry (QWidget::mapToParent()), and then sent to the parent widget. In turn, your parent can also ignore the event, in which case the event will be resent to your parent's parent widget, and so on. This is what we call event propagation; the event is "propagated to parent". By default, the mouse handlers ignore events. So if you don't implement mouse handlers, the events will be ignored. However, if you do implement an event handler, the event is by default accepted! So you don't actually have to call accept() inside any mouse event handler. Howz dat? ;-)

void MyMouseyWidget:mousePressEvent(QMouseEvent *event)
event->accept(); // A no-op, but I will do it anyway!!!

(!) Mouse events propagate to the widget's parent. Pare-ern-nett. Children are, of course, always on top of their parents [sic]. So the event goes to the widget underneith? No!! :-) No no no. For sibling widgets that overlap, you may expect the event to be propagated to the sibling widget underneith, since siblings can be arbitrarily stacked, but now you know better: Qt delivers the event to the parent.

(!!) Despite having ignored the mouse press, your widget is still the mouse grabber. So your widget will receive the mouse move events, even if your parent actually accepted the press. And recall that the default mouse event handlers ignore the events? If you only implement the press and release event handlers, your mouse move events will be ignored. Which means they will propagate to parent. A common cause of crashes in mouse event handling widgets is the arrival of an unexpected event. :-)

So here's the core of confusion. If you accept mouse press events, you probably want to accept move and release and doubleclick events too. All four handlers, or none of them, need to be reimplemented. Common mistake: ignoring the press event, accepting the release event, and assuming you got a perfectly nice click. ;-) If you have done this, then feel very guilty ;-). Why? Because your parent widget will get a single press, followed by a move move, perhaps, and that can be enough to throw any well-written state machine off guard.

And, btw, you don't want to start thinking about event filters until you've grasped all this. ;-)

Ehem. I guess that'll be all for now.

PS: A followup blog will describe how this works in GraphicsView. ;-)

PPS: I guess I forgot to say anything about calling the base implementation. No, ignoring an event does not send the event to the base implementation. It only affects event propagation for events that support it. If you want the base implementation (like for a line edit, the interaction implementation), call it. Otherwise, don't.

PPPS: There's a WA_ for everything. WA_NoMousePropagation turns off mouse event propagation for a widget.

Blog Topics: