If you subclass QQuickItem and start handling events, it becomes clear that 
QEvent::accept() has always meant two things in legacy Qt Quick: 1) stop 
propagation and 2) grab the mouse.  (Does this idea bother you yet?)

The item is basically saying “the buck stops here”: it’s so very sure that no 
other item in the scene could possibly care about that event.  Because it has 
total scene awareness.  (Does that sound arrogant?)

If you place anything that reacts to press (like a Controls Button, or a QML 
component that acts like one) into a Flickable, it will be “on top” of the z 
stack during event delivery, so it gets the press event first.  It also wants 
to see the release (that’s how a click is detected).  In classic Qt Quick, it 
must grab the mouse, otherwise it gets no more events after the press.  And the 
original way was to accept the event.  But then after you press, you still want 
to be able to drag, so as to start flicking (which implies the button should no 
longer be pressed).  But the Flickable won’t see the event because it’s 
underneath this top item that is stopping propagation.  So this item, that is 
acting like it’s so sure that “the buck stops here”, has to be preempted by the 
flickable.  But how?  Well, that’s why we have  virtual bool  
QQuickItem::childMouseEventFilter(QQuickItem *item, QEvent *event) as public 
API in QQuickItem… so that any item can filter all the mouse events that its 
children receive.  (Isn’t the signature beautiful? It says it filters mouse 
events but takes a QEvent* as an argument.  Maybe you can guess why.)  But 
wait, how can it get a grab or take over a grab if the only normal way to do 
that is to accept the event?  Well we also have void QQuickItem::grabMouse().  
(Because there will only ever be one mouse, no matter how many mice you plug 
in, and no matter how many fingers you touch the screen with.  It doesn’t 
matter what’s going on at this moment - just grab the mouse.  Right now.  How 
hard can it be?)

Now how will we handle multi-touch?  What if one or more fingers is pressing 
inside the boundary of one Item, but other fingers are elsewhere?  If you have 
to be able to accept or reject the entire event, that would amount to a little 
Button saying “the buck stops here” for all fingers at the same time.  That 
won’t do!  So of course another ugly hack was introduced a long time ago: 
during delivery to that button, it should only see the fingers that are inside 
of its bounds, so that when it accepts that event, it’s clear which fingers are 
being grabbed.  That way it can see the releases of those touchpoints.  So in 
Qt 5’s delivery of touch events, for each item that we visit, we heap-allocate 
a brand-new QTouchEvent, customized just for that one item: it contains only 
the points that are inside, and each point is localized so that its pos() will 
give the position inside the item that is receiving it.  Let it see the event, 
check afterwards whether it got accepted or not, and then destroy it.

Then cases arise where even child-event-filtering is not enough - you need 
surveillance of all events going to all of those greedy, grabby items in the 
scene, just to make sure you can take over control of any event at any time.  
Well, that’s what QObject event filtering is for.

All the above ought to be obsolete BTW.  (How could it not be?)

Before we could introduce Pointer Handlers, we had to substantially 
re-architect event delivery, while also keeping all the old hacks working 
(because Controls depends on them, and so does just about every QQuickItem 
subclass that handles mouse or touch events).  (So that led to a lot of 
regressions around 5.7 - 5.10 or so.  We fixed most of what we could. I don’t 
see how it was possible to move forward without making big changes like that, 
though.)

Qt Quick is basically trying to detect gestures in a distributed fashion: if 
you press and release a touchpoint on a button, that’s a click gesture.  If you 
press, drag and release, it’s a drag gesture, but it’s up to an item or handler 
to detect that’s what’s going on.  Items and handlers need to have enough 
autonomy to decide for themselves what the user is trying to do, in that 
geometric neighborhood where they live, while other users or other fingers 
might be doing something else in different neighborhoods at the same time.  
That idea is very much in conflict with the idea that stopping propagation of 
events by accepting them could ever be OK.

I think that when a mouse button or a touchpoint is pressed, we need to let 
that event propagate all the way down, until every item and handler that could 
possibly be interested has had a chance to see it.  In Qt 5.10 or so, we 
already added the passive grab concept: a pointer handler can subscribe for 
updates without having to say “the buck stops here”.  But in order to have a 
chance to do that, it needs to see the press event in the first place.  Any 
item that is on top by z-order can deny it the opportunity though, by stopping 
propagation.  So eventually it seems like we need to end up with an agreement 
that individual items don’t stop propagation anymore: they need to be humble 
and cooperative, not arrogant and unilateral.  That opens up the opportunity 
for some handlers to “do their thing” non-exclusively: e.g. PointHandler can 
act upon the movements of an individual touchpoint without ever taking sole 
responsibility.  So it can be used to show feedback as an independent aspect of 
the UI, regardless what else is going on at the same time; and it does it 
without the older event filtering mechanisms.

But the exclusive grab still means what grabMouse() always did: the item or 
handler is pretty sure that it has responsibility for the sequence of 
QEventPoint movements.  It has recognized a gesture and is acting upon it.  In 
pointer handlers, usually active() becomes true and the handler takes the 
exclusive grab of the point(s) within its bounds at the same time.  But it does 
not preempt passive grabs of other handlers: they still get to watch the 
movements too.  Conflict can arise when one handler (probably one that had a 
passive grab until now) decides to steal the exclusive grab from something 
else, and then we have a whole negotiation mechanism in place to deal with 
that: both handlers have to agree that it’s OK.

But how can all that keep working when we still have so many old-fashioned item 
subclasses in use?  Well it’s tricky… we keep fixing those cases one at a time. 
 So far there always seems to be a way.  The sooner we can deprecate some of 
the older techniques, the better, IMO.  But one of the nagging questions is 
still what should QEvent::accept() really mean?  Is it OK to have it mean 
something different in Qt Quick now?  We are not allowed to change what it 
means in widgets.  I wish we had time to refactor event delivery to be 
consistent everywhere, but probably we will never find time for that.

For the use case of in-scene popups (like the dialogs and popups in Controls 
2), I still think QQuickItem needs a modal flag.  I’ve had a patch sitting 
around for years to add that, and I don’t think it would be too hard to get it 
into 6.0.  I think in the future this should be the main way to stop event 
propagation: an item that is modal will not let input events go through to any 
other items behind it in Z order.  This way it becomes declarative (set the 
flag) rather than imperative (accept each event as it comes).  It would be used 
sparingly, probably on the background item of the dialog or popup itself, or 
maybe on the translucent item that is usually shown behind it.  Clicking 
outside to close the popup has to be handled somehow; I think I had something 
elegant in mind a couple of years ago.

A prerequisite for cooperative event handling seems to be that we need to 
rewrite QQuickFlickable some day.  I have had the FakeFlickable.qml manual test 
in place for a long time now, to give a glimpse of what that could look like.  
Flickable is the main user of child-event-filtering, but FakeFlickable gets by 
without it.  Ideally I would have found time to do this rewrite now, for Qt 
6.0.  But here we are… management is insisting that we have to follow the usual 
release schedule, so there’s no time left for much beyond the QInputEvent 
refactoring that I’ve been doing, which has taken most of my year so far, aside 
from some distractions here and there.  It’s very frustrating that it basically 
means rewrite event delivery in Qt Quick yet again, but again keep all the ugly 
old hacks working (thus we don’t get to re-simplify it yet: it’s still more 
complex than it was in early 5.x versions.)  Fixing broken hacks that the tests 
reveal takes a lot of time, and again I’m not getting much help.

As an intermediate step, maybe QQuickFlickable could at least start handling 
touch events directly so that it doesn’t have to rely on touch->mouse synthesis 
anymore.  We’ll see, but time is running out.  But as an intermediate step 
before that, it could at least learn to replay touch events when you’ve set a 
pressDelay and you’re using a touchscreen and the item inside knows how to 
handle a touch event.  There are a lot of bugs we can fix that way.  But 
captureDelayedPress() is called while filtering: if you are pressing a classic 
grab-happy item inside, it’s the only way Flickable gets a chance to see the 
press event.  So again we come to this inelegance that somebody named the 
virtual function QQuickItem::childMouseEventFilter() and then decided later on 
that oh duh actually it has to filter touch events too.  And in Qt 5 mouse and 
touch events were not closely related enough; but I don’t know why it takes 
QEvent, not at least QInputEvent.  In Qt 6 it could be renamed to 
childPointerEventFilter() and take a QPointerEvent, but it’s a virtual public 
function in QQuickItem, which means the world is full of subclasses that 
override it by now.  I don’t want to go through the whole 
deprecate-and-duplicate song and dance, because I think the whole filtering 
concept ought to be obsolete eventually.  Duplicating it would mean having to 
call both the old and new functions during delivery and letting either one of 
them do the same things, which would add overhead and complication for now.  
But getting rid of it depends on everyone eventually agreeing that it’s OK to 
redesign QQuickFlickable like I want to do: its children must never stop event 
propagation, to ensure that the flickable itself gets a chance to see the 
press, so it can take a passive grab.  And that is risky because of another 
atrocious design decision that we are stuck with: all item views inherit 
QQuickFlickable in C++.  It’s not a component, it’s the base class that does 
everything possible.  I can try to refactor it to internally construct pointer 
handler instances instead of overriding virtual functions, but with all those 
inheritance use cases…

BTW: If you ever use Flickable on a touchscreen, you can try FakeFlickable in 
your own application, for the simple cases.  It can be used as a component in 
its current form.  It’s also not that hard to re-create a one-off if you just 
need part of the functionality: if you want to use the mouse wheel to move a 
bigger item back and forth inside of a smaller item that defines a viewport for 
example, you can use WheelHandler and BoundaryRule together.  Add a DragHandler 
to make it possible with touch too.

Can we ever change accept() to mean something else?  It has so much history.  
Should we just document that it should not normally be used with pointer events 
in Qt Quick, because stopping propagation is an extreme thing to do?  Should we 
change behavior so that it doesn’t cause a grab, and therefore you are forced 
to think about that as a separate thing?  Many items need to grab; most don’t 
need to stop propagation.

If you want an exclusive grab, you can get it explicitly now by calling 
QPointerEvent::setExclusiveGrabber(const QEventPoint &point, QObject 
*exclusiveGrabber); note that means you should do it while handling an event, 
not in any other context.  Yes this works the same for mouse and touch events.  
Likewise even items can be passive grabbers now, in theory… I just haven’t 
really used it.  Maybe flickable could do that, if it doesn’t end up working to 
refactor it into multiple internal components.

What would the code look like for a mouse event?

void MyItem::mousePressEvent(QMouseEvent *event)
{
        if (whatever)
                event->setExclusiveGrabber(event->points().first(), this);
}

Yeah, a bit clumsy-looking, but this is perhaps what you should do, instead of 
accepting the event, to only grab but still let the event keep going to other 
items underneath, including parents.  I wanted to add the setExclusiveGrabber() 
function to QEventPoint, but people didn’t like it during code review, so 
that’s why it’s that way.  Anyhow, you have to get the QEventPoint out of the 
event in order to set its grabber.  I suppose we could add a simpler 
setExclusiveGrabber() to QSinglePointEvent though (QMouseEvent inherits that).

To really open up the possibilities for items, I wonder if we should add a few 
more virtual functions to QQuickItem now.  A QQuickPointerHandler subclass can 
see all the touchpoints, not just the ones that are inside its parent item’s 
bounds.  The event gets there via QQuickItemPrivate::handlePointerEvent(), 
which is virtual, so you can also subclass QQuickItemPrivate if you want to 
make an item that handles arbitrary pointer events as-they-come, rather than 
those jit-constructed touch events that only have the points that are inside.  
I want to make QQuickPointerHandler public when we are really sure the API is 
OK (soon enough in the 6.x series?) but maybe it would be useful for items too. 
 And now we have the last chance to add new virtual functions to public 
classes, for a while.  Another thing is to handle QTabletEvents: you can 
subclass QQuickItem, override event() and see all the events, but maybe we 
should add a new tabletEvent() virtual function.  I have been on the fence 
about that, because I want to ship a new handler that I already wrote, which 
should work for basic tablet-drawing applications.  (Too bad the only way to 
draw strokes so far is with Canvas or with Shapes.  We need to add something 
else for inking eventually.)  And maybe if the only convenient way is to use 
that handler, it would be a carrot for users to finally try pointer handlers, 
if they haven’t yet.  But people with long experience usually start by thinking 
they have to subclass QQuickItem, so it’s a surprise if it’s not easy to handle 
tablet events that way… now that we are finally delivering them in Qt Quick.

Another API wart is those QQuickItem::mouseUngrabEvent() and touchUngrabEvent() 
virtual functions.  They look like event handlers but they don’t take events as 
arguments.  Why is that?  Well, there is no touch ungrab event type at all, and 
QEvent::UngrabMouse is rarely used: it’s basically just a way of informing a 
Flickable that has taken a grab during filtering that it has now lost its grab. 
 (And yeah, again there’s that assumption that the only pointing device is one 
mouse.  So mouseUngrabEvent() has to be called in mouse-from-touch scenarios 
too, which is ugly.)  Maybe also for the case when a window loses a 
window-system mouse grab, but who does that?  it never seems to come up in Qt 
Quick.  If there were a QEvent::TouchUngrab, it would have to specify which 
QEventPoints are losing the grab.  But what I use now is a signal, 
QPointingDevice::grabChanged(QObject *grabber, GrabTransition transition, const 
QPointerEvent *event, const QEventPoint &point).  QQuickWindow has to connect 
to that signal for each device that sends events, which is more than one 
connection, but it also works both ways: to inform the window which item has 
gained the grab of either kind (exclusive or passive), and also which item or 
handler has lost it.  QQuickWindow then dispatches to the items and handlers to 
inform them via mouseUngrabEvent(), touchUngrabEvent() and 
QQuickPointerHandler::onGrabChanged().  Is an event so much better than a 
signal, that we ought to take the trouble to switch over to doing it that way?  
It would take time, so it would have to be worthwhile.  But otherwise we could 
easily add a similar onGrabChanged() virtual function to QQuickItem.  It really 
needs to know more than just that it lost some mouse or touch grab: those 
existing virtuals don’t even identify the device.

The onSignal() pattern for virtual functions is something I like a bit, because 
it’s similar to how you write a signal handler in QML.  But it’s an uncommon 
pattern so far in C++, for some reason.  Is the existing pattern better, or 
should we say it’s OK to write C++ virtual signal handlers that way too?

_______________________________________________
Development mailing list
Development@qt-project.org
https://lists.qt-project.org/listinfo/development

Reply via email to