diff options
author | Shawn Rutledge <shawn.rutledge@qt.io> | 2021-04-19 15:44:45 +0200 |
---|---|---|
committer | Shawn Rutledge <shawn.rutledge@qt.io> | 2021-05-05 08:40:59 +0200 |
commit | 5c08e911375966761ee8e4d7cd425120985876e2 (patch) | |
tree | 912266b9875b7ddcdad3a1ffeb5162db830502ea /src/quick/util/qquickdeliveryagent.cpp | |
parent | ba0e42d645f6882db32fa557d2d8da6a497d502a (diff) |
Store memory of grabbing agents in EventPointData
QQuickDeliveryAgentPrivate::grabberAgent(event, point) was too naive:
it's not only that each point could be delivered by a different agent,
but also each point could have an exclusive grab in the context of one
agent and multiple passive grabs in the contexts of different agents.
E.g. in qtquick3d/examples/quick3d/dynamictexture, many grabs can occur
if you press on a door: the View3D has a TapHandler to pick the door, so
that you can click to open; each Panel has a TapHandler to defocus
whichever TextArea has focus; there is a ListView for dragging through
the panel delegates sideways; if you press on a yellow note, its
DragHandler prepares to start dragging; etc. So at least, a TapHandler
in the subscene and the other one in the View3D need to get passive
grabs, and need to receive the release event in the correct coordinate
system, to detect taps. When we apply the sceneTransform of a
particular agent before re-delivering an update or a release, based on
the grab, it has to be in the correct coordinate system for that grabber.
So on grab, we use EventPointData::exclusiveGrabberContext or
passiveGrabbersContext to store the pointer to the QQDeliveryAgent that
was doing the delivery when the grab occurred. When we deliver the next
event to a grabber, we also look up the correct DA to do the delivery.
This gets done twice: once in QQuickWindow::event() to find the DAs that
need to handle the event, and in QQDeliveryAgentPriv::deliverUpdatedPoints()
to ignore passive grabbers for which a different DA is responsible.
The failsafe "never allow any kind of grab to persist after release" is
moved to QQuickWindow::event because we don't want to do it prematurely
in a subscene agent, and ATM we don't require the main DA to deliver
last: it depends on the order in EventPointData::passiveGrabbersContext.
QQuickPointerHandler::onGrabChanged() should only be called from the
relevant DA, to avoid overreaction: that is, the DA that is delivering
an event at the time the grab occurs. QQDelAgentPrivate::onGrabChanged()
gets called on all DA instances, but only one of them is supposed to
store itself as the assigned DA for handling a particular point ID.
It's not always the same as QQuickItemPrivate::deliveryAgent(): that
goes astray when the same 2D subscene is mapped to the main scene and
also onto multiple QQ3D models. In that case, if the user interacts
directly with the 2D scene, the main DA should be assigned; or if the
user interacts with one of the mapped subscenes, its DA should be assigned.
We have to stop delivering to subscenes when the event is accepted,
at least, because of the usual convention that delivery is done when
the event remains accepted rather than being purposely ignored. So in
the dynamictexture example, if you click on a TextEdit on one of the
doors, it receives the event first (because it's on top) and accepts it;
that stops delivery in QQuickDeliveryAgentPrivate::deliverPressOrReleaseEvent(),
and now also stops QQuickWindow::event() from visiting the next
subscene, or the main scene, so that the TapHandler in the View3D
doesn't get clicked, and the doors don't open.
Task-number: QTBUG-92944
Pick-to: 6.1
Change-Id: I1b4520b665e58874d17a936024cf62e4c7175d8e
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Andy Nichols <andy.nichols@qt.io>
Diffstat (limited to 'src/quick/util/qquickdeliveryagent.cpp')
-rw-r--r-- | src/quick/util/qquickdeliveryagent.cpp | 143 |
1 files changed, 64 insertions, 79 deletions
diff --git a/src/quick/util/qquickdeliveryagent.cpp b/src/quick/util/qquickdeliveryagent.cpp index 6f763342c1..3149482070 100644 --- a/src/quick/util/qquickdeliveryagent.cpp +++ b/src/quick/util/qquickdeliveryagent.cpp @@ -70,6 +70,9 @@ Q_LOGGING_CATEGORY(lcFocus, "qt.quick.focus") extern Q_GUI_EXPORT bool qt_sendShortcutOverrideEvent(QObject *o, ulong timestamp, int k, Qt::KeyboardModifiers mods, const QString &text = QString(), bool autorep = false, ushort count = 1); +bool QQuickDeliveryAgentPrivate::subsceneAgentsExist(false); +QQuickDeliveryAgent *QQuickDeliveryAgentPrivate::currentEventDeliveryAgent(nullptr); + void QQuickDeliveryAgentPrivate::touchToMouseEvent(QEvent::Type type, const QEventPoint &p, const QTouchEvent *touchEvent, QMutableSinglePointEvent *mouseEvent) { Q_ASSERT(QCoreApplication::testAttribute(Qt::AA_SynthesizeMouseForUnhandledTouchEvents)); @@ -267,6 +270,7 @@ bool QQuickDeliveryAgentPrivate::deliverTouchAsMouse(QQuickItem *item, QTouchEve */ void QQuickDeliveryAgentPrivate::removeGrabber(QQuickItem *grabber, bool mouse, bool touch, bool cancel) { + Q_Q(QQuickDeliveryAgent); if (eventsInDelivery.isEmpty()) { // do it the expensive way for (auto dev : knownPointingDevices) { @@ -278,7 +282,7 @@ void QQuickDeliveryAgentPrivate::removeGrabber(QQuickItem *grabber, bool mouse, auto eventInDelivery = eventsInDelivery.top(); if (Q_LIKELY(mouse) && eventInDelivery) { auto epd = mousePointData(); - if (epd && epd->exclusiveGrabber == grabber) { + if (epd && epd->exclusiveGrabber == grabber && epd->exclusiveGrabberContext.data() == q) { QQuickItem *oldGrabber = qobject_cast<QQuickItem *>(epd->exclusiveGrabber); qCDebug(lcMouseTarget) << "removeGrabber" << oldGrabber << "-> null"; eventInDelivery->setExclusiveGrabber(epd->eventPoint, nullptr); @@ -639,21 +643,6 @@ QQuickDeliveryAgent::Transform::~Transform() { } -/*! \internal - Returns the QQuickDeliveryAgent instance that we remember was delivering the - given \a pt at the time that it was grabbed. Failing that, choose a suitable agent. -*/ -QQuickDeliveryAgent *QQuickDeliveryAgent::grabberAgent(QPointerEvent *pe, const QEventPoint &pt) -{ - auto devExtra = QQuickDeliveryAgentPrivate::deviceExtra(pe->device()); - QQuickDeliveryAgent *ret = devExtra->grabbedEventPointDeliveryAgents.value(pt.id()); - if (ret) { - qCDebug(lcPtr) << pe->type() << "point" << pt.id() << pt.state() - << "@" << pt.scenePosition() << "will be re-delivered via known agent" << ret; - } - return ret; -} - QQuickItem *QQuickDeliveryAgent::rootItem() const { Q_D(const QQuickDeliveryAgent); @@ -678,6 +667,8 @@ void QQuickDeliveryAgent::setSceneTransform(QQuickDeliveryAgent::Transform *tran bool QQuickDeliveryAgent::event(QEvent *ev) { Q_D(QQuickDeliveryAgent); + d->currentEventDeliveryAgent = this; + auto cleanup = qScopeGuard([d] { d->currentEventDeliveryAgent = nullptr; }); switch (ev->type()) { case QEvent::MouseButtonPress: @@ -845,23 +836,12 @@ QQuickDeliveryAgentPrivate::QQuickDeliveryAgentPrivate(QQuickItem *root) : #if QT_CONFIG(quick_draganddrop) dragGrabber = new QQuickDragGrabber; #endif + if (isSubsceneAgent) + subsceneAgentsExist = true; } QQuickDeliveryAgentPrivate::~QQuickDeliveryAgentPrivate() { - Q_Q(QQuickDeliveryAgent); - for (auto dev : knownPointingDevices) { - auto devPriv = QPointingDevicePrivate::get(dev); - if (devPriv->qqExtra) { - auto &flatmap = static_cast<QQuickPointingDeviceExtra *>(devPriv->qqExtra)->grabbedEventPointDeliveryAgents; - for (auto it = flatmap.begin(); it != flatmap.end(); ) { - if (it.value() == q) - it = flatmap.erase(it); - else - ++it; - } - } - } #if QT_CONFIG(quick_draganddrop) delete dragGrabber; dragGrabber = nullptr; @@ -1095,6 +1075,7 @@ void QQuickDeliveryAgentPrivate::deliverDelayedTouchEvent() */ void QQuickDeliveryAgentPrivate::handleWindowDeactivate(QQuickWindow *win) { + Q_Q(QQuickDeliveryAgent); qCDebug(lcFocus) << "deactivated" << win->title(); const auto inputDevices = QInputDevice::devices(); for (auto device : inputDevices) { @@ -1106,7 +1087,7 @@ void QQuickDeliveryAgentPrivate::handleWindowDeactivate(QQuickWindow *win) if (QQuickItem *item = qmlobject_cast<QQuickItem *>(epd.exclusiveGrabber.data())) relevant = (item->window() == win); else if (QQuickPointerHandler *handler = qmlobject_cast<QQuickPointerHandler *>(epd.exclusiveGrabber.data())) - relevant = (handler->parentItem()->window() == win); + relevant = (handler->parentItem()->window() == win && epd.exclusiveGrabberContext.data() == q); if (relevant) devPriv->setExclusiveGrabber(nullptr, epd.eventPoint, nullptr); } @@ -1422,19 +1403,21 @@ void QQuickDeliveryAgentPrivate::onGrabChanged(QObject *grabber, QPointingDevice const bool grabGained = (transition == QPointingDevice::GrabTransition::GrabExclusive || transition == QPointingDevice::GrabTransition::GrabPassive); - QQuickDeliveryAgent *subsceneAgent = nullptr; + QQuickDeliveryAgent *deliveryAgent = nullptr; // note: event can be null, if the signal was emitted from QPointingDevicePrivate::removeGrabber(grabber) if (auto *handler = qmlobject_cast<QQuickPointerHandler *>(grabber)) { - handler->onGrabChanged(handler, transition, const_cast<QPointerEvent *>(event), - const_cast<QEventPoint &>(point)); - if (isSubsceneAgent) { - auto itemPriv = QQuickItemPrivate::get(handler->parentItem()); + auto itemPriv = QQuickItemPrivate::get(handler->parentItem()); + deliveryAgent = itemPriv->deliveryAgent(); + if (deliveryAgent == q) { + handler->onGrabChanged(handler, transition, const_cast<QPointerEvent *>(event), + const_cast<QEventPoint &>(point)); + } + if (grabGained) { // An item that is NOT a subscene root needs to track whether it got a grab via a subscene delivery agent, // whereas the subscene root item already knows it has its own DA. - if (grabGained && (!itemPriv->extra.isAllocated() || !itemPriv->extra->subsceneDeliveryAgent)) + if (isSubsceneAgent && (!itemPriv->extra.isAllocated() || !itemPriv->extra->subsceneDeliveryAgent)) itemPriv->maybeHasSubsceneDeliveryAgent = true; - subsceneAgent = itemPriv->deliveryAgent(); } } else { switch (transition) { @@ -1472,32 +1455,39 @@ void QQuickDeliveryAgentPrivate::onGrabChanged(QObject *grabber, QPointingDevice break; } auto grabberItem = static_cast<QQuickItem *>(grabber); // cannot be a handler: we checked above - if (isSubsceneAgent && grabberItem) { + if (grabberItem) { auto itemPriv = QQuickItemPrivate::get(grabberItem); + deliveryAgent = itemPriv->deliveryAgent(); // An item that is NOT a subscene root needs to track whether it got a grab via a subscene delivery agent, // whereas the subscene root item already knows it has its own DA. - if (grabGained && (!itemPriv->extra.isAllocated() || !itemPriv->extra->subsceneDeliveryAgent)) + if (isSubsceneAgent && grabGained && (!itemPriv->extra.isAllocated() || !itemPriv->extra->subsceneDeliveryAgent)) itemPriv->maybeHasSubsceneDeliveryAgent = true; - subsceneAgent = itemPriv->deliveryAgent(); } } - if (subsceneAgent == q && event && event->device()) { - auto devExtra = QQuickDeliveryAgentPrivate::deviceExtra(event->device()); - QFlatMap<int, QQuickDeliveryAgent*> &agentMap = devExtra->grabbedEventPointDeliveryAgents; - // workaround for QFlatMap error: somehow having a local copy of id makes insert() happy (move semantics?) otherwise we get - // no matching function for call to ‘QList<int>::insert(QFlatMap<int, QQuickDeliveryAgent*>::iterator&, std::remove_reference<int&>::type)’ - const int id = point.id(); - if (grabGained) { - // If any grab is gained while a subscene agent is delivering an event, - // the same agent should keep delivering all subsequent events containing that QEventPoint. - qCDebug(lcPtr) << "remembering that" << q << "handles point" << id << "after" << transition; - agentMap.insert(id, q); - } else if (!event->exclusiveGrabber(point) && event->passiveGrabbers(point).isEmpty()) { - // If all grabs are lost, we can forget the fact that a particular agent was handling a particular point. - // If the event point ID appears again in a later event, it will be delivered via the main window's delivery agent by default. - qCDebug(lcPtr) << "dissociating" << q << "from point" << id << "after" << transition; - agentMap.remove(id); + if (currentEventDeliveryAgent == q && event && event->device()) { + auto epd = QPointingDevicePrivate::get(const_cast<QPointingDevice*>(event->pointingDevice()))->queryPointById(point.id()); + Q_ASSERT(epd); + switch (transition) { + case QPointingDevice::GrabPassive: { + QPointingDevicePrivate::setPassiveGrabberContext(epd, grabber, q); + qCDebug(lcPtr) << "remembering that" << q << "handles point" << point.id() << "after" << transition; + } break; + case QPointingDevice::GrabExclusive: + epd->exclusiveGrabberContext = q; + qCDebug(lcPtr) << "remembering that" << q << "handles point" << point.id() << "after" << transition; + break; + case QPointingDevice::CancelGrabExclusive: + case QPointingDevice::UngrabExclusive: + // taken care of in QPointingDevicePrivate::setExclusiveGrabber(,,nullptr), removeExclusiveGrabber() + break; + case QPointingDevice::UngrabPassive: + case QPointingDevice::CancelGrabPassive: + // taken care of in QPointingDevicePrivate::removePassiveGrabber(), clearPassiveGrabbers() + break; + case QPointingDevice::OverrideGrabPassive: + // not in use at this time + break; } } } @@ -1558,27 +1548,9 @@ void QQuickDeliveryAgentPrivate::deliverPointerEvent(QPointerEvent *event) if (event->isEndEvent()) deliverPressOrReleaseEvent(event, true); - // failsafe: never allow any kind of grab or point-agent association to persist after release - if (event->isEndEvent()) { - auto &agentMap = QQuickDeliveryAgentPrivate::deviceExtra(event->device())->grabbedEventPointDeliveryAgents; - if (isTouchEvent(event)) { - for (int i = 0; i < event->pointCount(); ++i) { - auto &point = event->point(i); - if (point.state() == QEventPoint::State::Released) { - event->setExclusiveGrabber(point, nullptr); - event->clearPassiveGrabbers(point); - agentMap.remove(point.id()); - } - } - // never allow touch->mouse synthesis to persist either - cancelTouchMouseSynthesis(); - } else if (static_cast<QSinglePointEvent *>(event)->buttons() == Qt::NoButton) { - auto &firstPt = event->point(0); - event->setExclusiveGrabber(firstPt, nullptr); - event->clearPassiveGrabbers(firstPt); - agentMap.remove(firstPt.id()); - } - } + // failsafe: never allow touch->mouse synthesis to persist after release + if (event->isEndEvent() && isTouchEvent(event)) + cancelTouchMouseSynthesis(); eventsInDelivery.pop(); if (sceneTransform) { @@ -1669,6 +1641,7 @@ QVector<QQuickItem *> QQuickDeliveryAgentPrivate::mergePointerTargets(const QVec */ void QQuickDeliveryAgentPrivate::deliverUpdatedPoints(QPointerEvent *event) { + Q_Q(const QQuickDeliveryAgent); bool done = false; const auto grabbers = exclusiveGrabbers(event); hasFiltered.clear(); @@ -1694,8 +1667,20 @@ void QQuickDeliveryAgentPrivate::deliverUpdatedPoints(QPointerEvent *event) } // Deliver to each eventpoint's passive grabbers (but don't visit any handler more than once) - for (auto &point : event->points()) - deliverToPassiveGrabbers(event->passiveGrabbers(point), event); + for (auto &point : event->points()) { + auto epd = QPointingDevicePrivate::get(event->pointingDevice())->queryPointById(point.id()); + if (Q_UNLIKELY(!epd)) { + qWarning() << "point is not in activePoints" << point; + continue; + } + QList<QPointer<QObject>> relevantPassiveGrabbers; + for (int i = 0; i < epd->passiveGrabbersContext.count(); ++i) { + if (epd->passiveGrabbersContext.at(i).data() == q) + relevantPassiveGrabbers << epd->passiveGrabbers.at(i); + } + if (!relevantPassiveGrabbers.isEmpty()) + deliverToPassiveGrabbers(relevantPassiveGrabbers, event); + } if (done) return; |