diff options
Diffstat (limited to 'src/quick/util/qquickdeliveryagent.cpp')
-rw-r--r-- | src/quick/util/qquickdeliveryagent.cpp | 412 |
1 files changed, 320 insertions, 92 deletions
diff --git a/src/quick/util/qquickdeliveryagent.cpp b/src/quick/util/qquickdeliveryagent.cpp index 794ac70a25..34032d4801 100644 --- a/src/quick/util/qquickdeliveryagent.cpp +++ b/src/quick/util/qquickdeliveryagent.cpp @@ -18,6 +18,8 @@ #include <QtQuick/private/qquickrendercontrol_p.h> #include <QtQuick/private/qquickwindow_p.h> +#include <QtCore/qpointer.h> + #include <memory> QT_BEGIN_NAMESPACE @@ -96,6 +98,14 @@ bool QQuickDeliveryAgentPrivate::checkIfDoubleTapped(ulong newPressEventTimestam return doubleClicked; } +/*! \internal + \deprecated events are handled by methods in which the event is an argument + + Accessor for use by legacy methods such as QQuickItem::grabMouse(), + QQuickItem::ungrabMouse(), and QQuickItem::grabTouchPoints() which + are not given sufficient context to do the grabbing. + We should remove eventsInDelivery in Qt 7. +*/ QPointerEvent *QQuickDeliveryAgentPrivate::eventInDelivery() const { if (eventsInDelivery.isEmpty()) @@ -305,6 +315,16 @@ static inline bool windowHasFocus(QQuickWindow *win) return win == focusWindow || QQuickRenderControlPrivate::isRenderWindowFor(win, focusWindow) || !focusWindow; } +static QQuickItem *findFurthestFocusScopeAncestor(QQuickItem *item) +{ + QQuickItem *parentItem = item->parentItem(); + + if (parentItem && parentItem->flags() & QQuickItem::ItemIsFocusScope) + return findFurthestFocusScopeAncestor(parentItem); + + return item; +} + #ifdef Q_OS_WEBOS // Temporary fix for webOS until multi-seat is implemented see QTBUG-85272 static inline bool singleWindowOnScreen(QQuickWindow *win) @@ -447,6 +467,16 @@ void QQuickDeliveryAgentPrivate::setFocusInScope(QQuickItem *scope, QQuickItem * if (isSubsceneAgent) { auto da = QQuickWindowPrivate::get(rootItem->window())->deliveryAgent; qCDebug(lcFocus) << " delegating setFocusInScope to" << da; + + // When setting subFocusItem, hierarchy is important. Each focus ancestor's + // subFocusItem must be its nearest descendant with focus. Changing the rootItem's + // subFocusItem to 'item' here would make 'item' the subFocusItem of all ancestor + // focus scopes up until root item. + // That is why we should avoid altering subFocusItem until having traversed + // all the focus hierarchy. + QQuickItem *ancestorFS = findFurthestFocusScopeAncestor(item); + if (ancestorFS != item) + options |= QQuickDeliveryAgentPrivate::DontChangeSubFocusItem; QQuickWindowPrivate::get(rootItem->window())->deliveryAgentPrivate()->setFocusInScope(da->rootItem(), item, reason, options); } if (oldActiveFocusItem == activeFocusItem) @@ -599,16 +629,14 @@ bool QQuickDeliveryAgentPrivate::clearHover(ulong timestamp) const QPointF lastPos = window->mapFromGlobal(QGuiApplicationPrivate::lastCursorPosition); const auto modifiers = QGuiApplication::keyboardModifiers(); - const bool clearHover = true; - for (auto hoverItem : hoverItems) { - auto item = hoverItem.first; - if (item) - deliverHoverEventToItem(item, lastPos, lastPos, modifiers, timestamp, clearHover); + for (const auto &[item, id] : hoverItems) { + if (item) { + deliverHoverEventToItem(item, lastPos, lastPos, modifiers, timestamp, HoverChange::Clear); + Q_ASSERT(id == 0); + } } - hoverItems.clear(); - return true; } @@ -624,6 +652,27 @@ void QQuickDeliveryAgentPrivate::updateFocusItemTransform() #endif } +/*! + Returns the item that should get active focus when the + root focus scope gets active focus. +*/ +QQuickItem *QQuickDeliveryAgentPrivate::focusTargetItem() const +{ + if (activeFocusItem) + return activeFocusItem; + + Q_ASSERT(rootItem); + QQuickItem *targetItem = rootItem; + + while (targetItem->isFocusScope() + && targetItem->scopedFocusItem() + && targetItem->scopedFocusItem()->isEnabled()) { + targetItem = targetItem->scopedFocusItem(); + } + + return targetItem; +} + /*! \internal If called during event delivery, returns the agent that is delivering the event, without checking whether \a item is reachable from there. @@ -657,6 +706,10 @@ QQuickDeliveryAgent::Transform::~Transform() { } +/*! \internal + Get the QQuickRootItem or subscene root item on behalf of which + this delivery agent was constructed to handle events. +*/ QQuickItem *QQuickDeliveryAgent::rootItem() const { Q_D(const QQuickDeliveryAgent); @@ -690,6 +743,13 @@ void QQuickDeliveryAgent::setSceneTransform(QQuickDeliveryAgent::Transform *tran d->sceneTransform = transform; } +/*! + Handle \a ev on behalf of this delivery agent's window or subscene. + + This is the usual main entry point for every incoming event: + QQuickWindow::event() and QQuick3DViewport::forwardEventToSubscenes() + both call this function. +*/ bool QQuickDeliveryAgent::event(QEvent *ev) { Q_D(QQuickDeliveryAgent); @@ -782,16 +842,7 @@ bool QQuickDeliveryAgent::event(QEvent *ev) case QEvent::InputMethod: case QEvent::InputMethodQuery: { - QQuickItem *target = d->activeFocusItem; - // while an input method delivers the event, this window might still be inactive - if (!target) { - target = d->rootItem; - if (!target || !target->isEnabled()) - break; - // see setFocusInScope for a similar loop - while (target->isFocusScope() && target->scopedFocusItem() && target->scopedFocusItem()->isEnabled()) - target = target->scopedFocusItem(); - } + QQuickItem *target = d->focusTargetItem(); if (target) QCoreApplication::sendEvent(target, ev); } @@ -1018,8 +1069,7 @@ bool QQuickDeliveryAgentPrivate::deliverHoverEvent( // Prune the list for items that are no longer hovered for (auto it = hoverItems.begin(); it != hoverItems.end();) { - auto item = (*it).first.data(); - auto hoverId = (*it).second; + const auto &[item, hoverId] = *it; if (hoverId == currentHoverId) { // Still being hovered it++; @@ -1027,10 +1077,8 @@ bool QQuickDeliveryAgentPrivate::deliverHoverEvent( // No longer hovered. If hoverId is 0, it means that we have sent a HoverLeave // event to the item already, and it can just be removed from the list. Note that // the item can have been deleted as well. - if (item && hoverId != 0) { - const bool clearHover = true; - deliverHoverEventToItem(item, scenePos, lastScenePos, modifiers, timestamp, clearHover); - } + if (item && hoverId != 0) + deliverHoverEventToItem(item, scenePos, lastScenePos, modifiers, timestamp, HoverChange::Clear); it = hoverItems.erase(it); } } @@ -1111,10 +1159,7 @@ bool QQuickDeliveryAgentPrivate::deliverHoverEventRecursive( // All decendants have been visited. // Now deliver the event to the item - return deliverHoverEventToItem(item, scenePos, lastScenePos, modifiers, timestamp, false); - - // Continue propagation / recursion - return false; + return deliverHoverEventToItem(item, scenePos, lastScenePos, modifiers, timestamp, HoverChange::Set); } /*! \internal @@ -1127,13 +1172,14 @@ bool QQuickDeliveryAgentPrivate::deliverHoverEventRecursive( */ bool QQuickDeliveryAgentPrivate::deliverHoverEventToItem( QQuickItem *item, const QPointF &scenePos, const QPointF &lastScenePos, - Qt::KeyboardModifiers modifiers, ulong timestamp, bool clearHover) + Qt::KeyboardModifiers modifiers, ulong timestamp, HoverChange hoverChange) { QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item); const QPointF localPos = item->mapFromScene(scenePos); const QPointF globalPos = item->mapToGlobal(localPos); const bool isHovering = item->contains(localPos); - const bool wasHovering = hoverItems.contains(item); + const auto hoverItemIterator = hoverItems.find(item); + const bool wasHovering = hoverItemIterator != hoverItems.end() && hoverItemIterator.value() != 0; qCDebug(lcHoverTrace) << "item:" << item << "scene pos:" << scenePos << "localPos:" << localPos << "wasHovering:" << wasHovering << "isHovering:" << isHovering; @@ -1143,20 +1189,24 @@ bool QQuickDeliveryAgentPrivate::deliverHoverEventToItem( // Start by sending out enter/move/leave events to the item. // Note that hoverEnabled only controls if we should send out hover events to the // item itself. HoverHandlers are not included, and are dealt with separately below. - if (itemPrivate->hoverEnabled && isHovering && !clearHover) { + if (itemPrivate->hoverEnabled && isHovering && hoverChange == HoverChange::Set) { // Add the item to the list of hovered items (if it doesn't exist there // from before), and update hoverId to mark that it's (still) hovered. // Also set hoveredLeafItemFound, so that only propagate in a straight // line towards the root from now on. hoveredLeafItemFound = true; - hoverItems[item] = currentHoverId; + if (hoverItemIterator != hoverItems.end()) + hoverItemIterator.value() = currentHoverId; + else + hoverItems[item] = currentHoverId; + if (wasHovering) accepted = sendHoverEvent(QEvent::HoverMove, item, scenePos, lastScenePos, modifiers, timestamp); else accepted = sendHoverEvent(QEvent::HoverEnter, item, scenePos, lastScenePos, modifiers, timestamp); } else if (wasHovering) { // A leave should never stop propagation - hoverItems[item] = 0; + hoverItemIterator.value() = 0; sendHoverEvent(QEvent::HoverLeave, item, scenePos, lastScenePos, modifiers, timestamp); } @@ -1170,7 +1220,7 @@ bool QQuickDeliveryAgentPrivate::deliverHoverEventToItem( // Note that since a HoverHandler can have a margin, a HoverHandler // can be hovered even if the item itself is not. - if (clearHover) { + if (hoverChange == HoverChange::Clear) { // Note: a leave should never stop propagation QHoverEvent hoverEvent(QEvent::HoverLeave, scenePos, globalPos, lastScenePos, modifiers); hoverEvent.setTimestamp(timestamp); @@ -1197,7 +1247,10 @@ bool QQuickDeliveryAgentPrivate::deliverHoverEventToItem( // Mark the whole item as updated, even if only the handler is // actually in a hovered state (because of HoverHandler.margins) hoveredLeafItemFound = true; - hoverItems[item] = currentHoverId; + if (hoverItemIterator != hoverItems.end()) + hoverItemIterator.value() = currentHoverId; + else + hoverItems[item] = currentHoverId; if (hh->isBlocking()) { qCDebug(lcHoverTrace) << "skipping rest of hover delivery due to blocking" << hh; accepted = true; @@ -1321,6 +1374,13 @@ void QQuickDeliveryAgentPrivate::handleWindowDeactivate(QQuickWindow *win) } } +void QQuickDeliveryAgentPrivate::handleWindowHidden(QQuickWindow *win) +{ + qCDebug(lcFocus) << "hidden" << win->title(); + clearHover(); + lastMousePosition = QPointF(); +} + bool QQuickDeliveryAgentPrivate::allUpdatedPointsAccepted(const QPointerEvent *ev) { for (auto &point : ev->points()) { @@ -1595,6 +1655,9 @@ void QQuickDeliveryAgentPrivate::handleTouchEvent(QTouchEvent *event) } } +/*! + Handle \a event on behalf of this delivery agent's window or subscene. +*/ void QQuickDeliveryAgentPrivate::handleMouseEvent(QMouseEvent *event) { Q_Q(QQuickDeliveryAgent); @@ -1651,6 +1714,19 @@ void QQuickDeliveryAgentPrivate::handleMouseEvent(QMouseEvent *event) } } +/*! \internal + Flush events before a frame is rendered in \a win. + + This is here because of compressTouchEvent(): we need to ensure that + coalesced touch events are actually delivered in time to cause the desired + reactions of items and their handlers. And then since it was introduced + because of that, we started using this function for once-per-frame hover + events too, to take care of changing hover state when an item animates + under the mouse cursor at a time that the mouse cursor is not moving. + + This is done before QQuickItem::updatePolish() is called on all the items + that requested polishing. +*/ void QQuickDeliveryAgentPrivate::flushFrameSynchronousEvents(QQuickWindow *win) { Q_Q(QQuickDeliveryAgent); @@ -1679,7 +1755,13 @@ void QQuickDeliveryAgentPrivate::flushFrameSynchronousEvents(QQuickWindow *win) if (frameSynchronousHoverEnabled && !win->mouseGrabberItem() && !lastMousePosition.isNull() && QQuickWindowPrivate::get(win)->dirtyItemList) { qCDebug(lcHoverTrace) << q << "delivering frame-sync hover to root @" << lastMousePosition; - deliverHoverEvent(lastMousePosition, lastMousePosition, QGuiApplication::keyboardModifiers(), 0); + if (deliverHoverEvent(lastMousePosition, lastMousePosition, QGuiApplication::keyboardModifiers(), 0)) { +#if QT_CONFIG(cursor) + QQuickWindowPrivate::get(rootItem->window())->updateCursor( + sceneTransform ? sceneTransform->map(lastMousePosition) : lastMousePosition, rootItem); +#endif + } + qCDebug(lcHoverTrace) << q << "frame-sync hover delivery done"; } #else @@ -1691,6 +1773,14 @@ void QQuickDeliveryAgentPrivate::flushFrameSynchronousEvents(QQuickWindow *win) QQuickDeliveryAgentPrivate::currentEventDeliveryAgent = deliveringAgent; } +/*! \internal + React to the fact that \a grabber underwent a grab \a transition + while an item or handler was handling \a point from \a event. + I.e. handle the QPointingDevice::grabChanged() signal. + + This notifies the relevant items and/or pointer handlers, and + does cleanup when grabs are lost or relinquished. +*/ void QQuickDeliveryAgentPrivate::onGrabChanged(QObject *grabber, QPointingDevice::GrabTransition transition, const QPointerEvent *event, const QEventPoint &point) { @@ -1719,50 +1809,43 @@ void QQuickDeliveryAgentPrivate::onGrabChanged(QObject *grabber, QPointingDevice handler->onGrabChanged(handler, transition, const_cast<QPointerEvent *>(event), const_cast<QEventPoint &>(point)); } - } else { + } else if (auto *grabberItem = qmlobject_cast<QQuickItem *>(grabber)) { switch (transition) { case QPointingDevice::CancelGrabExclusive: case QPointingDevice::UngrabExclusive: - if (auto *item = qmlobject_cast<QQuickItem *>(grabber)) { - bool filtered = false; - if (isDeliveringTouchAsMouse() || - point.device()->type() == QInputDevice::DeviceType::Mouse || - point.device()->type() == QInputDevice::DeviceType::TouchPad) { - QMutableSinglePointEvent e(QEvent::UngrabMouse, point.device(), point); - hasFiltered.clear(); - filtered = sendFilteredMouseEvent(&e, item, item->parentItem()); - if (!filtered) { - lastUngrabbed = item; - item->mouseUngrabEvent(); - } + if (isDeliveringTouchAsMouse() + || point.device()->type() == QInputDevice::DeviceType::Mouse + || point.device()->type() == QInputDevice::DeviceType::TouchPad) { + QMutableSinglePointEvent e(QEvent::UngrabMouse, point.device(), point); + hasFiltered.clear(); + if (!sendFilteredMouseEvent(&e, grabberItem, grabberItem->parentItem())) { + lastUngrabbed = grabberItem; + grabberItem->mouseUngrabEvent(); } - if (point.device()->type() == QInputDevice::DeviceType::TouchScreen) { - bool allReleasedOrCancelled = true; - if (transition == QPointingDevice::UngrabExclusive && event) { - for (const auto &pt : event->points()) { - if (pt.state() != QEventPoint::State::Released) { - allReleasedOrCancelled = false; - break; - } + } + if (point.device()->type() == QInputDevice::DeviceType::TouchScreen) { + bool allReleasedOrCancelled = true; + if (transition == QPointingDevice::UngrabExclusive && event) { + for (const auto &pt : event->points()) { + if (pt.state() != QEventPoint::State::Released) { + allReleasedOrCancelled = false; + break; } } - if (allReleasedOrCancelled) - item->touchUngrabEvent(); } + if (allReleasedOrCancelled) + grabberItem->touchUngrabEvent(); } break; default: break; } - auto grabberItem = static_cast<QQuickItem *>(grabber); // cannot be a handler: we checked above - 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 (isSubsceneAgent && grabGained && (!itemPriv->extra.isAllocated() || !itemPriv->extra->subsceneDeliveryAgent)) - itemPriv->maybeHasSubsceneDeliveryAgent = true; - } + 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 (isSubsceneAgent && grabGained && (!itemPriv->extra.isAllocated() || !itemPriv->extra->subsceneDeliveryAgent)) + itemPriv->maybeHasSubsceneDeliveryAgent = true; } if (currentEventDeliveryAgent == q && event && event->device()) { @@ -1794,6 +1877,15 @@ void QQuickDeliveryAgentPrivate::onGrabChanged(QObject *grabber, QPointingDevice } } +/*! \internal + Called when a QPointingDevice is detected, to ensure that the + QPointingDevice::grabChanged() signal is connected to + QQuickDeliveryAgentPrivate::onGrabChanged(). + + \c knownPointingDevices is maintained only to track signal connections, and + should not be used for other purposes. The usual place to get a list of all + devices is QInputDevice::devices(). +*/ void QQuickDeliveryAgentPrivate::ensureDeviceConnected(const QPointingDevice *dev) { Q_Q(QQuickDeliveryAgent); @@ -1804,6 +1896,13 @@ void QQuickDeliveryAgentPrivate::ensureDeviceConnected(const QPointingDevice *de QObject::connect(dev, &QObject::destroyed, q, [this, dev] {this->knownPointingDevices.removeAll(dev);}); } +/*! \internal + The entry point for delivery of \a event after determining that it \e is a + pointer event, and either does not need to be coalesced in + compressTouchEvent(), or already has been. + + When it returns, event delivery is done. +*/ void QQuickDeliveryAgentPrivate::deliverPointerEvent(QPointerEvent *event) { Q_Q(QQuickDeliveryAgent); @@ -1873,11 +1972,36 @@ void QQuickDeliveryAgentPrivate::deliverPointerEvent(QPointerEvent *event) lastUngrabbed = nullptr; } -// check if item or any of its child items contain the point, or if any pointer handler "wants" the point +/*! \internal + Returns a list of all items that are spatially relevant to receive \a event + occurring at \a point, starting with \a item and recursively checking all + the children. + \list + \li If an item has pointer handlers, call + QQuickPointerHandler::wantsEventPoint() + on every handler to decide whether the item is eligible. + \li Otherwise, if \a checkMouseButtons is \c true, it means we are + finding targets for a mouse event, so no item for which + acceptedMouseButtons() is NoButton will be added. + \li Otherwise, if \a checkAcceptsTouch is \c true, it means we are + finding targets for a touch event, so either acceptTouchEvents() must + return true \e or it must accept a synthesized mouse event. I.e. if + acceptTouchEvents() returns false, it gets added only if + acceptedMouseButtons() is true. + \li If QQuickItem::clip() is \c true \e and the \a point is outside of + QQuickItem::clipRect(), its children are also omitted. (We stop the + recursion, because any clipped-off portions of children under \a point + are invisible.) + \li Ignore any item in a subscene that "belongs to" a different + DeliveryAgent. (In current practice, this only happens in 2D scenes in + Qt Quick 3D.) + \endlist + + The list returned from this function is the list of items that will be + "visited" when delivering any event for which QPointerEvent::isBeginEvent() + is \c true. +*/ // FIXME: should this be iterative instead of recursive? -// If checkMouseButtons is true, it means we are finding targets for a mouse event, so no item for which acceptedMouseButtons() is NoButton will be added. -// If checkAcceptsTouch is true, it means we are finding targets for a touch event, so either acceptTouchEvents() must return true OR -// it must accept a synth. mouse event, thus if acceptTouchEvents() returns false but acceptedMouseButtons() is true, gets added; if not, it doesn't. QVector<QQuickItem *> QQuickDeliveryAgentPrivate::pointerTargets(QQuickItem *item, const QPointerEvent *event, const QEventPoint &point, bool checkMouseButtons, bool checkAcceptsTouch) const { @@ -1889,7 +2013,7 @@ QVector<QQuickItem *> QQuickDeliveryAgentPrivate::pointerTargets(QQuickItem *ite qCDebug(lcPtrLoc) << q << "point" << point.id() << point.scenePosition() << "->" << itemPos << ": relevant?" << relevant << "to" << item << point; // if the item clips, we can potentially return early if (itemPrivate->flags & QQuickItem::ItemClipsChildrenToShape) { - if (!relevant) + if (!item->clipRect().contains(itemPos)) return targets; } @@ -1927,8 +2051,10 @@ QVector<QQuickItem *> QQuickDeliveryAgentPrivate::pointerTargets(QQuickItem *ite return targets; } -// return the joined lists -// list1 has priority, common items come last +/*! \internal + Returns a joined list consisting of the items in \a list1 and \a list2. + \a list1 has priority; common items come last. +*/ QVector<QQuickItem *> QQuickDeliveryAgentPrivate::mergePointerTargets(const QVector<QQuickItem *> &list1, const QVector<QQuickItem *> &list2) const { QVector<QQuickItem *> targets = list1; @@ -1998,6 +2124,18 @@ void QQuickDeliveryAgentPrivate::deliverUpdatedPoints(QPointerEvent *event) } if (!relevantPassiveGrabbers.isEmpty()) deliverToPassiveGrabbers(relevantPassiveGrabbers, event); + + // Ensure that HoverHandlers are updated, in case no items got dirty so far and there's no update request + if (event->type() == QEvent::TouchUpdate) { + for (const auto &[item, id] : hoverItems) { + if (item) { + bool res = deliverHoverEventToItem(item, point.scenePosition(), point.sceneLastPosition(), + event->modifiers(), event->timestamp(), HoverChange::Set); + // if the event was accepted, then the item's ID must be valid + Q_ASSERT(!res || hoverItems.value(item)); + } + } + } } if (done) @@ -2030,7 +2168,45 @@ void QQuickDeliveryAgentPrivate::deliverUpdatedPoints(QPointerEvent *event) } } -// Deliver an event containing newly pressed or released touch points +/*! \internal + Deliver a pointer \a event containing newly pressed or released QEventPoints. + If \a handlersOnly is \c true, skip the items and just deliver to Pointer Handlers + (via QQuickItemPrivate::handlePointerEvent()). + + For the sake of determinism, this function first builds the list + \c targetItems by calling pointerTargets() on the root item. That is, the + list of items to "visit" is determined at the beginning, and will not be + affected if items reparent, hide, or otherwise try to make themselves + eligible or ineligible during delivery. (Avoid bugs due to ugly + just-in-time tricks in JS event handlers, filters etc.) + + Whenever a touch gesture is in progress, and another touchpoint is pressed, + or an existing touchpoint is released, we "start over" with delivery: + that's why this function is called whenever the event \e contains newly + pressed or released points. It's not necessary for a handler or an item to + greedily grab all touchpoints just in case a valid gesture might start. + QQuickMultiPointHandler::wantsPointerEvent() can calmly return \c false if + the number of points is less than QQuickMultiPointHandler::minimumPointCount(), + because it knows it will be asked again if the number of points increases. + + When \a handlersOnly is \c false, \a event visits the items in \c targetItems + via QQuickItem::event(). We have to call sendFilteredPointerEvent() + before visiting each item, just in case a Flickable (or some other + parent-filter) will decide to intercept the event. But we also have to be + very careful never to let the same Flickable filter the same event twice, + because when Flickable decides to intercept, it lets the child item have + that event, and then grabs the next event. That allows you to drag a + Slider, DragHandler or whatever inside a ListView delegate: if you're + dragging in the correct direction for the draggable child, it can use + QQuickItem::setKeepMouseGrab(), QQuickItem::setKeepTouchGrab() or + QQuickPointerHandler::grabPermissions() to prevent Flickable from + intercepting during filtering, only if it actually \e has the exclusive + grab already when Flickable attempts to take it. Typically, both the + Flickable and the child are checking the same drag threshold, so the + child must have a chance to grab and \e keep the grab before Flickable + gets a chance to steal it, even though Flickable actually sees the + event first during filtering. +*/ bool QQuickDeliveryAgentPrivate::deliverPressOrReleaseEvent(QPointerEvent *event, bool handlersOnly) { QVector<QQuickItem *> targetItems; @@ -2062,7 +2238,11 @@ bool QQuickDeliveryAgentPrivate::deliverPressOrReleaseEvent(QPointerEvent *event } } - for (QQuickItem *item : targetItems) { + QVector<QPointer<QQuickItem>> safeTargetItems(targetItems.begin(), targetItems.end()); + + for (auto &item : safeTargetItems) { + if (item.isNull()) + continue; // failsafe: when items get into a subscene somehow, ensure that QQuickItemPrivate::deliveryAgent() can find it if (isSubsceneAgent) QQuickItemPrivate::get(item)->maybeHasSubsceneDeliveryAgent = true; @@ -2092,6 +2272,14 @@ bool QQuickDeliveryAgentPrivate::deliverPressOrReleaseEvent(QPointerEvent *event return event->allPointsAccepted(); } +/*! \internal + Deliver \a pointerEvent to \a item and its handlers, if any. + If \a handlersOnly is \c true, skip QQuickItem::event() and just visit its + handlers via QQuickItemPrivate::handlePointerEvent(). + + This function exists just to de-duplicate the common code between + deliverPressOrReleaseEvent() and deliverUpdatedPoints(). +*/ void QQuickDeliveryAgentPrivate::deliverMatchingPointsToItem(QQuickItem *item, bool isGrabber, QPointerEvent *pointerEvent, bool handlersOnly) { QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item); @@ -2125,6 +2313,7 @@ void QQuickDeliveryAgentPrivate::deliverMatchingPointsToItem(QQuickItem *item, b return; // TODO: unite this mouse point delivery with the synthetic mouse event below + // TODO: remove isGrabber then? if (isMouse) { auto button = static_cast<QSinglePointEvent *>(pointerEvent)->button(); if ((isGrabber && button == Qt::NoButton) || item->acceptedMouseButtons().testFlag(button)) { @@ -2170,11 +2359,6 @@ void QQuickDeliveryAgentPrivate::deliverMatchingPointsToItem(QQuickItem *item, b if (item->acceptTouchEvents()) { qCDebug(lcTouch) << "considering delivering" << &touchEvent << " to " << item; - // If any parent filters the event, we're done. - hasFiltered.clear(); - if (sendFilteredPointerEvent(&touchEvent, item)) - return; - // Deliver the touch event to the given item qCDebug(lcTouch) << "actually delivering" << &touchEvent << " to " << item; QCoreApplication::sendEvent(item, &touchEvent); @@ -2399,11 +2583,22 @@ bool QQuickDeliveryAgentPrivate::deliverDragEvent( } #endif // quick_draganddrop +/*! \internal + Allow \a filteringParent to filter \a event on behalf of \a receiver, via + QQuickItem::childMouseEventFilter(). This happens right \e before we would + send \a event to \a receiver. + + Returns \c true only if \a event has been intercepted (by \a filteringParent + or some other filtering ancestor) and should \e not be sent to \a receiver. +*/ bool QQuickDeliveryAgentPrivate::sendFilteredPointerEvent(QPointerEvent *event, QQuickItem *receiver, QQuickItem *filteringParent) { return sendFilteredPointerEventImpl(event, receiver, filteringParent ? filteringParent : receiver->parentItem()); } +/*! \internal + The recursive implementation of sendFilteredPointerEvent(). +*/ bool QQuickDeliveryAgentPrivate::sendFilteredPointerEventImpl(QPointerEvent *event, QQuickItem *receiver, QQuickItem *filteringParent) { if (!allowChildEventFiltering) @@ -2452,15 +2647,18 @@ bool QQuickDeliveryAgentPrivate::sendFilteredPointerEventImpl(QPointerEvent *eve QQuickItemPrivate::get(receiver)->localizedTouchEvent(static_cast<QTouchEvent *>(event), true, &filteringParentTouchEvent); if (filteringParentTouchEvent.type() != QEvent::None) { qCDebug(lcTouch) << "letting parent" << filteringParent << "filter for" << receiver << &filteringParentTouchEvent; - if (filteringParent->childMouseEventFilter(receiver, &filteringParentTouchEvent)) { + filtered = filteringParent->childMouseEventFilter(receiver, &filteringParentTouchEvent); + if (filtered) { qCDebug(lcTouch) << "touch event intercepted by childMouseEventFilter of " << filteringParent; + event->setAccepted(filteringParentTouchEvent.isAccepted()); skipDelivery.append(filteringParent); - for (auto point : filteringParentTouchEvent.points()) { - const QQuickItem *exclusiveGrabber = qobject_cast<const QQuickItem *>(event->exclusiveGrabber(point)); - if (!exclusiveGrabber || !exclusiveGrabber->keepTouchGrab()) - event->setExclusiveGrabber(point, filteringParent); + if (event->isAccepted()) { + for (auto point : filteringParentTouchEvent.points()) { + const QQuickItem *exclusiveGrabber = qobject_cast<const QQuickItem *>(event->exclusiveGrabber(point)); + if (!exclusiveGrabber || !exclusiveGrabber->keepTouchGrab()) + event->setExclusiveGrabber(point, filteringParent); + } } - return true; } else if (Q_LIKELY(QCoreApplication::testAttribute(Qt::AA_SynthesizeMouseForUnhandledTouchEvents)) && !filteringParent->acceptTouchEvents()) { qCDebug(lcTouch) << "touch event NOT intercepted by childMouseEventFilter of " << filteringParent @@ -2496,17 +2694,17 @@ bool QQuickDeliveryAgentPrivate::sendFilteredPointerEventImpl(QPointerEvent *eve // touchMouseId and touchMouseDevice must be set, even if it's only temporarily and isn't grabbed. touchMouseId = tp.id(); touchMouseDevice = event->pointingDevice(); - if (filteringParent->childMouseEventFilter(receiver, &mouseEvent)) { + filtered = filteringParent->childMouseEventFilter(receiver, &mouseEvent); + if (filtered) { qCDebug(lcTouch) << "touch event intercepted as synth mouse event by childMouseEventFilter of " << filteringParent; + event->setAccepted(mouseEvent.isAccepted()); skipDelivery.append(filteringParent); - if (t != QEvent::MouseButtonRelease) { + if (event->isAccepted() && event->isBeginEvent()) { qCDebug(lcTouchTarget) << "TP (mouse)" << Qt::hex << tp.id() << "->" << filteringParent; filteringParentTouchEvent.setExclusiveGrabber(tp, filteringParent); touchMouseUnset = false; // We want to leave touchMouseId and touchMouseDevice set - if (mouseEvent.isAccepted()) - filteringParent->grabMouse(); + filteringParent->grabMouse(); } - filtered = true; } if (touchMouseUnset) // Now that we're done sending a synth mouse event, and it wasn't grabbed, @@ -2526,6 +2724,17 @@ bool QQuickDeliveryAgentPrivate::sendFilteredPointerEventImpl(QPointerEvent *eve return sendFilteredPointerEventImpl(event, receiver, filteringParent->parentItem()) || filtered; } +/*! \internal + Allow \a filteringParent to filter \a event on behalf of \a receiver, via + QQuickItem::childMouseEventFilter(). This happens right \e before we would + send \a event to \a receiver. + + Returns \c true only if \a event has been intercepted (by \a filteringParent + or some other filtering ancestor) and should \e not be sent to \a receiver. + + Unlike sendFilteredPointerEvent(), this version does not synthesize a + mouse event from touch (presumably it's already an actual mouse event). +*/ bool QQuickDeliveryAgentPrivate::sendFilteredMouseEvent(QEvent *event, QQuickItem *receiver, QQuickItem *filteringParent) { if (!filteringParent) @@ -2548,6 +2757,13 @@ bool QQuickDeliveryAgentPrivate::sendFilteredMouseEvent(QEvent *event, QQuickIte return sendFilteredMouseEvent(event, receiver, filteringParent->parentItem()) || filtered; } +/*! \internal + Returns \c true if the movement delta \a d in pixels along the \a axis + exceeds \a startDragThreshold if it is set, or QStyleHints::startDragDistance(); + \e or, if QEventPoint::velocity() of \a event exceeds QStyleHints::startDragVelocity(). + + \sa QQuickPointerHandlerPrivate::dragOverThreshold() +*/ bool QQuickDeliveryAgentPrivate::dragOverThreshold(qreal d, Qt::Axis axis, QMouseEvent *event, int startDragThreshold) { QStyleHints *styleHints = QGuiApplication::styleHints(); @@ -2562,6 +2778,13 @@ bool QQuickDeliveryAgentPrivate::dragOverThreshold(qreal d, Qt::Axis axis, QMous return overThreshold; } +/*! \internal + Returns \c true if the movement delta \a d in pixels along the \a axis + exceeds \a startDragThreshold if it is set, or QStyleHints::startDragDistance(); + \e or, if QEventPoint::velocity() of \a tp exceeds QStyleHints::startDragVelocity(). + + \sa QQuickPointerHandlerPrivate::dragOverThreshold() +*/ bool QQuickDeliveryAgentPrivate::dragOverThreshold(qreal d, Qt::Axis axis, const QEventPoint &tp, int startDragThreshold) { QStyleHints *styleHints = qApp->styleHints(); @@ -2574,6 +2797,11 @@ bool QQuickDeliveryAgentPrivate::dragOverThreshold(qreal d, Qt::Axis axis, const return overThreshold; } +/*! \internal + Returns \c true if the movement \a delta in pixels exceeds QStyleHints::startDragDistance(). + + \sa QQuickDeliveryAgentPrivate::dragOverThreshold() +*/ bool QQuickDeliveryAgentPrivate::dragOverThreshold(QVector2D delta) { int threshold = qApp->styleHints()->startDragDistance(); |