aboutsummaryrefslogtreecommitdiffstats
path: root/src/quick/util/qquickdeliveryagent.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/quick/util/qquickdeliveryagent.cpp')
-rw-r--r--src/quick/util/qquickdeliveryagent.cpp412
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();