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.cpp2833
1 files changed, 2833 insertions, 0 deletions
diff --git a/src/quick/util/qquickdeliveryagent.cpp b/src/quick/util/qquickdeliveryagent.cpp
new file mode 100644
index 0000000000..acb082af82
--- /dev/null
+++ b/src/quick/util/qquickdeliveryagent.cpp
@@ -0,0 +1,2833 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include <QtCore/qdebug.h>
+#include <QtGui/private/qevent_p.h>
+#include <QtGui/private/qeventpoint_p.h>
+#include <QtGui/private/qguiapplication_p.h>
+#include <QtGui/qpa/qplatformtheme.h>
+#include <QtQml/private/qabstractanimationjob_p.h>
+#include <QtQuick/private/qquickdeliveryagent_p_p.h>
+#include <QtQuick/private/qquickhoverhandler_p.h>
+#include <QtQuick/private/qquickpointerhandler_p_p.h>
+#if QT_CONFIG(quick_draganddrop)
+#include <QtQuick/private/qquickdrag_p.h>
+#endif
+#include <QtQuick/private/qquickitem_p.h>
+#include <QtQuick/private/qquickprofiler_p.h>
+#include <QtQuick/private/qquickrendercontrol_p.h>
+#include <QtQuick/private/qquickwindow_p.h>
+
+#include <QtCore/qpointer.h>
+
+#include <memory>
+
+QT_BEGIN_NAMESPACE
+
+Q_LOGGING_CATEGORY(lcTouch, "qt.quick.touch")
+Q_STATIC_LOGGING_CATEGORY(lcTouchCmprs, "qt.quick.touch.compression")
+Q_LOGGING_CATEGORY(lcTouchTarget, "qt.quick.touch.target")
+Q_LOGGING_CATEGORY(lcMouse, "qt.quick.mouse")
+Q_STATIC_LOGGING_CATEGORY(lcMouseTarget, "qt.quick.mouse.target")
+Q_STATIC_LOGGING_CATEGORY(lcTablet, "qt.quick.tablet")
+Q_LOGGING_CATEGORY(lcPtr, "qt.quick.pointer")
+Q_STATIC_LOGGING_CATEGORY(lcPtrLoc, "qt.quick.pointer.localization")
+Q_STATIC_LOGGING_CATEGORY(lcWheelTarget, "qt.quick.wheel.target")
+Q_LOGGING_CATEGORY(lcHoverTrace, "qt.quick.hover.trace")
+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);
+
+static bool allowSyntheticRightClick()
+{
+ static int allowRightClick = -1;
+ if (allowRightClick < 0) {
+ bool ok = false;
+ allowRightClick = qEnvironmentVariableIntValue("QT_QUICK_ALLOW_SYNTHETIC_RIGHT_CLICK", &ok);
+ if (!ok)
+ allowRightClick = 1; // user didn't opt out
+ }
+ return allowRightClick != 0;
+}
+
+void QQuickDeliveryAgentPrivate::touchToMouseEvent(QEvent::Type type, const QEventPoint &p, const QTouchEvent *touchEvent, QMutableSinglePointEvent *mouseEvent)
+{
+ Q_ASSERT(QCoreApplication::testAttribute(Qt::AA_SynthesizeMouseForUnhandledTouchEvents));
+ QMutableSinglePointEvent ret(type, touchEvent->pointingDevice(), p,
+ (type == QEvent::MouseMove ? Qt::NoButton : Qt::LeftButton),
+ (type == QEvent::MouseButtonRelease ? Qt::NoButton : Qt::LeftButton),
+ touchEvent->modifiers(), Qt::MouseEventSynthesizedByQt);
+ ret.setAccepted(true); // this now causes the persistent touchpoint to be accepted too
+ ret.setTimestamp(touchEvent->timestamp());
+ *mouseEvent = ret;
+ // It's very important that the recipient of the event shall be able to see that
+ // this "mouse" event actually comes from a touch device.
+ Q_ASSERT(mouseEvent->device() == touchEvent->device());
+ if (Q_UNLIKELY(mouseEvent->device()->type() == QInputDevice::DeviceType::Mouse))
+ qWarning() << "Unexpected: synthesized an indistinguishable mouse event" << mouseEvent;
+}
+
+bool QQuickDeliveryAgentPrivate::checkIfDoubleTapped(ulong newPressEventTimestamp, QPoint newPressPos)
+{
+ bool doubleClicked = false;
+
+ if (touchMousePressTimestamp > 0) {
+ QPoint distanceBetweenPresses = newPressPos - touchMousePressPos;
+ const int doubleTapDistance = QGuiApplication::styleHints()->touchDoubleTapDistance();
+ doubleClicked = (qAbs(distanceBetweenPresses.x()) <= doubleTapDistance) && (qAbs(distanceBetweenPresses.y()) <= doubleTapDistance);
+
+ if (doubleClicked) {
+ ulong timeBetweenPresses = newPressEventTimestamp - touchMousePressTimestamp;
+ ulong doubleClickInterval = static_cast<ulong>(QGuiApplication::styleHints()->
+ mouseDoubleClickInterval());
+ doubleClicked = timeBetweenPresses < doubleClickInterval;
+ }
+ }
+ if (doubleClicked) {
+ touchMousePressTimestamp = 0;
+ } else {
+ touchMousePressTimestamp = newPressEventTimestamp;
+ touchMousePressPos = newPressPos;
+ }
+
+ 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())
+ return nullptr;
+ return eventsInDelivery.top();
+}
+
+/*! \internal
+ A helper function for the benefit of obsolete APIs like QQuickItem::grabMouse()
+ that don't have the currently-being-delivered event in context.
+ Returns the device the currently-being-delivered event comse from.
+*/
+QPointingDevicePrivate::EventPointData *QQuickDeliveryAgentPrivate::mousePointData()
+{
+ if (eventsInDelivery.isEmpty())
+ return nullptr;
+ auto devPriv = QPointingDevicePrivate::get(const_cast<QPointingDevice*>(eventsInDelivery.top()->pointingDevice()));
+ return devPriv->pointById(isDeliveringTouchAsMouse() ? touchMouseId : 0);
+}
+
+void QQuickDeliveryAgentPrivate::cancelTouchMouseSynthesis()
+{
+ qCDebug(lcTouchTarget) << "id" << touchMouseId << "on" << touchMouseDevice;
+ touchMouseId = -1;
+ touchMouseDevice = nullptr;
+}
+
+bool QQuickDeliveryAgentPrivate::deliverTouchAsMouse(QQuickItem *item, QTouchEvent *pointerEvent)
+{
+ Q_Q(QQuickDeliveryAgent);
+ Q_ASSERT(QCoreApplication::testAttribute(Qt::AA_SynthesizeMouseForUnhandledTouchEvents));
+ auto device = pointerEvent->pointingDevice();
+
+ // A touch event from a trackpad is likely to be followed by a mouse or gesture event, so mouse event synth is redundant
+ if (device->type() == QInputDevice::DeviceType::TouchPad && device->capabilities().testFlag(QInputDevice::Capability::MouseEmulation)) {
+ qCDebug(lcTouchTarget) << q << "skipping delivery of synth-mouse event from" << device;
+ return false;
+ }
+
+ // FIXME: make this work for mouse events too and get rid of the asTouchEvent in here.
+ QMutableTouchEvent event;
+ QQuickItemPrivate::get(item)->localizedTouchEvent(pointerEvent, false, &event);
+ if (!event.points().size())
+ return false;
+
+ // For each point, check if it is accepted, if not, try the next point.
+ // Any of the fingers can become the mouse one.
+ // This can happen because a mouse area might not accept an event at some point but another.
+ for (auto &p : event.points()) {
+ // A new touch point
+ if (touchMouseId == -1 && p.state() & QEventPoint::State::Pressed) {
+ QPointF pos = item->mapFromScene(p.scenePosition());
+
+ // probably redundant, we check bounds in the calling function (matchingNewPoints)
+ if (!item->contains(pos))
+ break;
+
+ qCDebug(lcTouchTarget) << q << device << "TP (mouse)" << Qt::hex << p.id() << "->" << item;
+ QMutableSinglePointEvent mousePress;
+ touchToMouseEvent(QEvent::MouseButtonPress, p, &event, &mousePress);
+
+ // Send a single press and see if that's accepted
+ QCoreApplication::sendEvent(item, &mousePress);
+ event.setAccepted(mousePress.isAccepted());
+ if (mousePress.isAccepted()) {
+ touchMouseDevice = device;
+ touchMouseId = p.id();
+ const auto &pt = mousePress.point(0);
+ if (!mousePress.exclusiveGrabber(pt))
+ mousePress.setExclusiveGrabber(pt, item);
+
+ if (checkIfDoubleTapped(event.timestamp(), p.globalPosition().toPoint())) {
+ // since we synth the mouse event from from touch, we respect the
+ // QPlatformTheme::TouchDoubleTapDistance instead of QPlatformTheme::MouseDoubleClickDistance
+ QMutableSinglePointEvent mouseDoubleClick;
+ touchToMouseEvent(QEvent::MouseButtonDblClick, p, &event, &mouseDoubleClick);
+ QCoreApplication::sendEvent(item, &mouseDoubleClick);
+ event.setAccepted(mouseDoubleClick.isAccepted());
+ if (!mouseDoubleClick.isAccepted())
+ cancelTouchMouseSynthesis();
+ }
+
+ return true;
+ }
+ // try the next point
+
+ // Touch point was there before and moved
+ } else if (touchMouseDevice == device && p.id() == touchMouseId) {
+ if (p.state() & QEventPoint::State::Updated) {
+ if (touchMousePressTimestamp != 0) {
+ const int doubleTapDistance = QGuiApplicationPrivate::platformTheme()->themeHint(QPlatformTheme::TouchDoubleTapDistance).toInt();
+ const QPoint moveDelta = p.globalPosition().toPoint() - touchMousePressPos;
+ if (moveDelta.x() >= doubleTapDistance || moveDelta.y() >= doubleTapDistance)
+ touchMousePressTimestamp = 0; // Got dragged too far, dismiss the double tap
+ }
+ if (QQuickItem *mouseGrabberItem = qmlobject_cast<QQuickItem *>(pointerEvent->exclusiveGrabber(p))) {
+ QMutableSinglePointEvent me;
+ touchToMouseEvent(QEvent::MouseMove, p, &event, &me);
+ QCoreApplication::sendEvent(item, &me);
+ event.setAccepted(me.isAccepted());
+ if (me.isAccepted())
+ qCDebug(lcTouchTarget) << q << device << "TP (mouse)" << Qt::hex << p.id() << "->" << mouseGrabberItem;
+ return event.isAccepted();
+ } else {
+ // no grabber, check if we care about mouse hover
+ // FIXME: this should only happen once, not recursively... I'll ignore it just ignore hover now.
+ // hover for touch???
+ QMutableSinglePointEvent me;
+ touchToMouseEvent(QEvent::MouseMove, p, &event, &me);
+ if (lastMousePosition.isNull())
+ lastMousePosition = me.scenePosition();
+ QPointF last = lastMousePosition;
+ lastMousePosition = me.scenePosition();
+
+ deliverHoverEvent(me.scenePosition(), last, me.modifiers(), me.timestamp());
+ break;
+ }
+ } else if (p.state() & QEventPoint::State::Released) {
+ // currently handled point was released
+ if (QQuickItem *mouseGrabberItem = qmlobject_cast<QQuickItem *>(pointerEvent->exclusiveGrabber(p))) {
+ QMutableSinglePointEvent me;
+ touchToMouseEvent(QEvent::MouseButtonRelease, p, &event, &me);
+ QCoreApplication::sendEvent(item, &me);
+
+ if (item->acceptHoverEvents() && p.globalPosition() != QGuiApplicationPrivate::lastCursorPosition) {
+ QPointF localMousePos(qInf(), qInf());
+ if (QWindow *w = item->window())
+ localMousePos = item->mapFromScene(w->mapFromGlobal(QGuiApplicationPrivate::lastCursorPosition));
+ QMouseEvent mm(QEvent::MouseMove, localMousePos, QGuiApplicationPrivate::lastCursorPosition,
+ Qt::NoButton, Qt::NoButton, event.modifiers());
+ QCoreApplication::sendEvent(item, &mm);
+ }
+ if (pointerEvent->exclusiveGrabber(p) == mouseGrabberItem) // might have ungrabbed due to event
+ pointerEvent->setExclusiveGrabber(p, nullptr);
+
+ cancelTouchMouseSynthesis();
+ return me.isAccepted();
+ }
+ }
+ break;
+ }
+ }
+ return false;
+}
+
+/*!
+ Ungrabs all touchpoint grabs and/or the mouse grab from the given item \a grabber.
+ This should not be called when processing a release event - that's redundant.
+ It is called in other cases, when the points may not be released, but the item
+ nevertheless must lose its grab due to becoming disabled, invisible, etc.
+ QPointerEvent::setExclusiveGrabber() calls touchUngrabEvent() when all points are released,
+ but if not all points are released, it cannot be sure whether to call touchUngrabEvent()
+ or not; so we have to do it here.
+*/
+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) {
+ auto devPriv = QPointingDevicePrivate::get(const_cast<QPointingDevice *>(dev));
+ devPriv->removeGrabber(grabber, cancel);
+ }
+ return;
+ }
+ auto eventInDelivery = eventsInDelivery.top();
+ if (Q_LIKELY(mouse) && eventInDelivery) {
+ auto epd = mousePointData();
+ 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);
+ }
+ }
+ if (Q_LIKELY(touch)) {
+ bool ungrab = false;
+ const auto touchDevices = QPointingDevice::devices();
+ for (auto device : touchDevices) {
+ if (device->type() != QInputDevice::DeviceType::TouchScreen)
+ continue;
+ if (QPointingDevicePrivate::get(const_cast<QPointingDevice *>(static_cast<const QPointingDevice *>(device)))->
+ removeExclusiveGrabber(eventInDelivery, grabber))
+ ungrab = true;
+ }
+ if (ungrab)
+ grabber->touchUngrabEvent();
+ }
+}
+
+/*! \internal
+ Translates QEventPoint::scenePosition() in \a touchEvent to this window.
+
+ The item-local QEventPoint::position() is updated later, not here.
+*/
+void QQuickDeliveryAgentPrivate::translateTouchEvent(QTouchEvent *touchEvent)
+{
+ for (qsizetype i = 0; i != touchEvent->pointCount(); ++i) {
+ auto &pt = touchEvent->point(i);
+ QMutableEventPoint::setScenePosition(pt, pt.position());
+ }
+}
+
+
+static inline bool windowHasFocus(QQuickWindow *win)
+{
+ const QWindow *focusWindow = QGuiApplication::focusWindow();
+ 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)
+{
+ const QWindowList windowList = QGuiApplication::allWindows();
+ for (int i = 0; i < windowList.count(); i++) {
+ QWindow *ii = windowList.at(i);
+ if (ii == win)
+ continue;
+ if (ii->screen() == win->screen())
+ return false;
+ }
+
+ return true;
+}
+#endif
+
+/*!
+ Set the focus inside \a scope to be \a item.
+ If the scope contains the active focus item, it will be changed to \a item.
+ Calls notifyFocusChangesRecur for all changed items.
+*/
+void QQuickDeliveryAgentPrivate::setFocusInScope(QQuickItem *scope, QQuickItem *item,
+ Qt::FocusReason reason, FocusOptions options)
+{
+ Q_Q(QQuickDeliveryAgent);
+ Q_ASSERT(item);
+ Q_ASSERT(scope || item == rootItem);
+
+ qCDebug(lcFocus) << q << "focus" << item << "in scope" << scope;
+ if (scope)
+ qCDebug(lcFocus) << " scopeSubFocusItem:" << QQuickItemPrivate::get(scope)->subFocusItem;
+
+ QQuickItemPrivate *scopePrivate = scope ? QQuickItemPrivate::get(scope) : nullptr;
+ QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item);
+
+ QQuickItem *oldActiveFocusItem = nullptr;
+ QQuickItem *currentActiveFocusItem = activeFocusItem;
+ QQuickItem *newActiveFocusItem = nullptr;
+ bool sendFocusIn = false;
+
+ lastFocusReason = reason;
+
+ QVarLengthArray<QQuickItem *, 20> changed;
+
+ // Does this change the active focus?
+ if (item == rootItem || scopePrivate->activeFocus) {
+ oldActiveFocusItem = activeFocusItem;
+ if (item->isEnabled()) {
+ newActiveFocusItem = item;
+ while (newActiveFocusItem->isFocusScope()
+ && newActiveFocusItem->scopedFocusItem()
+ && newActiveFocusItem->scopedFocusItem()->isEnabled()) {
+ newActiveFocusItem = newActiveFocusItem->scopedFocusItem();
+ }
+ } else {
+ newActiveFocusItem = scope;
+ }
+
+ if (oldActiveFocusItem) {
+#if QT_CONFIG(im)
+ QGuiApplication::inputMethod()->commit();
+#endif
+
+ activeFocusItem = nullptr;
+
+ QQuickItem *afi = oldActiveFocusItem;
+ while (afi && afi != scope) {
+ if (QQuickItemPrivate::get(afi)->activeFocus) {
+ QQuickItemPrivate::get(afi)->activeFocus = false;
+ changed << afi;
+ }
+ afi = afi->parentItem();
+ }
+ }
+ }
+
+ if (item != rootItem && !(options & DontChangeSubFocusItem)) {
+ QQuickItem *oldSubFocusItem = scopePrivate->subFocusItem;
+ if (oldSubFocusItem) {
+ QQuickItemPrivate *priv = QQuickItemPrivate::get(oldSubFocusItem);
+ priv->focus = false;
+ priv->notifyChangeListeners(QQuickItemPrivate::Focus, &QQuickItemChangeListener::itemFocusChanged, oldSubFocusItem, reason);
+ changed << oldSubFocusItem;
+ }
+
+ QQuickItemPrivate::get(item)->updateSubFocusItem(scope, true);
+ }
+
+ if (!(options & DontChangeFocusProperty)) {
+ if (item != rootItem || windowHasFocus(rootItem->window())
+#ifdef Q_OS_WEBOS
+ // Allow focused if there is only one window in the screen where it belongs.
+ // Temporary fix for webOS until multi-seat is implemented see QTBUG-85272
+ || singleWindowOnScreen(rootItem->window())
+#endif
+ ) {
+ itemPrivate->focus = true;
+ itemPrivate->notifyChangeListeners(QQuickItemPrivate::Focus, &QQuickItemChangeListener::itemFocusChanged, item, reason);
+ changed << item;
+ }
+ }
+
+ if (newActiveFocusItem && (rootItem->hasFocus() || (rootItem->window()->type() == Qt::Popup))) {
+ activeFocusItem = newActiveFocusItem;
+
+ QQuickItemPrivate::get(newActiveFocusItem)->activeFocus = true;
+ changed << newActiveFocusItem;
+
+ QQuickItem *afi = newActiveFocusItem->parentItem();
+ while (afi && afi != scope) {
+ if (afi->isFocusScope()) {
+ QQuickItemPrivate::get(afi)->activeFocus = true;
+ changed << afi;
+ }
+ afi = afi->parentItem();
+ }
+ updateFocusItemTransform();
+ sendFocusIn = true;
+ }
+
+ // Now that all the state is changed, emit signals & events
+ // We must do this last, as this process may result in further changes to focus.
+ if (oldActiveFocusItem) {
+ QFocusEvent event(QEvent::FocusOut, reason);
+ QCoreApplication::sendEvent(oldActiveFocusItem, &event);
+ }
+
+ // Make sure that the FocusOut didn't result in another focus change.
+ if (sendFocusIn && activeFocusItem == newActiveFocusItem) {
+ QFocusEvent event(QEvent::FocusIn, reason);
+ QCoreApplication::sendEvent(newActiveFocusItem, &event);
+ }
+
+ if (activeFocusItem != currentActiveFocusItem)
+ emit rootItem->window()->focusObjectChanged(activeFocusItem);
+
+ if (!changed.isEmpty())
+ notifyFocusChangesRecur(changed.data(), changed.size() - 1, reason);
+ 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)
+ qCDebug(lcFocus) << " activeFocusItem remains" << activeFocusItem << "in" << q;
+ else
+ qCDebug(lcFocus) << " activeFocusItem" << oldActiveFocusItem << "->" << activeFocusItem << "in" << q;
+}
+
+void QQuickDeliveryAgentPrivate::clearFocusInScope(QQuickItem *scope, QQuickItem *item, Qt::FocusReason reason, FocusOptions options)
+{
+ Q_ASSERT(item);
+ Q_ASSERT(scope || item == rootItem);
+ Q_Q(QQuickDeliveryAgent);
+ qCDebug(lcFocus) << q << "clear focus" << item << "in scope" << scope;
+
+ QQuickItemPrivate *scopePrivate = nullptr;
+ if (scope) {
+ scopePrivate = QQuickItemPrivate::get(scope);
+ if ( !scopePrivate->subFocusItem )
+ return; // No focus, nothing to do.
+ }
+
+ QQuickItem *currentActiveFocusItem = activeFocusItem;
+ QQuickItem *oldActiveFocusItem = nullptr;
+ QQuickItem *newActiveFocusItem = nullptr;
+
+ lastFocusReason = reason;
+
+ QVarLengthArray<QQuickItem *, 20> changed;
+
+ Q_ASSERT(item == rootItem || item == scopePrivate->subFocusItem);
+
+ // Does this change the active focus?
+ if (item == rootItem || scopePrivate->activeFocus) {
+ oldActiveFocusItem = activeFocusItem;
+ newActiveFocusItem = scope;
+
+#if QT_CONFIG(im)
+ QGuiApplication::inputMethod()->commit();
+#endif
+
+ activeFocusItem = nullptr;
+
+ if (oldActiveFocusItem) {
+ QQuickItem *afi = oldActiveFocusItem;
+ while (afi && afi != scope) {
+ if (QQuickItemPrivate::get(afi)->activeFocus) {
+ QQuickItemPrivate::get(afi)->activeFocus = false;
+ changed << afi;
+ }
+ afi = afi->parentItem();
+ }
+ }
+ }
+
+ if (item != rootItem && !(options & DontChangeSubFocusItem)) {
+ QQuickItem *oldSubFocusItem = scopePrivate->subFocusItem;
+ if (oldSubFocusItem && !(options & DontChangeFocusProperty)) {
+ QQuickItemPrivate *priv = QQuickItemPrivate::get(oldSubFocusItem);
+ priv->focus = false;
+ priv->notifyChangeListeners(QQuickItemPrivate::Focus, &QQuickItemChangeListener::itemFocusChanged, oldSubFocusItem, reason);
+ changed << oldSubFocusItem;
+ }
+
+ QQuickItemPrivate::get(item)->updateSubFocusItem(scope, false);
+
+ } else if (!(options & DontChangeFocusProperty)) {
+ QQuickItemPrivate *priv = QQuickItemPrivate::get(item);
+ priv->focus = false;
+ priv->notifyChangeListeners(QQuickItemPrivate::Focus, &QQuickItemChangeListener::itemFocusChanged, item, reason);
+ changed << item;
+ }
+
+ if (newActiveFocusItem) {
+ Q_ASSERT(newActiveFocusItem == scope);
+ activeFocusItem = scope;
+ updateFocusItemTransform();
+ }
+
+ // Now that all the state is changed, emit signals & events
+ // We must do this last, as this process may result in further changes to focus.
+ if (oldActiveFocusItem) {
+ QFocusEvent event(QEvent::FocusOut, reason);
+ QCoreApplication::sendEvent(oldActiveFocusItem, &event);
+ }
+
+ // Make sure that the FocusOut didn't result in another focus change.
+ if (newActiveFocusItem && activeFocusItem == newActiveFocusItem) {
+ QFocusEvent event(QEvent::FocusIn, reason);
+ QCoreApplication::sendEvent(newActiveFocusItem, &event);
+ }
+
+ if (activeFocusItem != currentActiveFocusItem)
+ emit rootItem->window()->focusObjectChanged(activeFocusItem);
+
+ if (!changed.isEmpty())
+ notifyFocusChangesRecur(changed.data(), changed.size() - 1, reason);
+ if (isSubsceneAgent) {
+ auto da = QQuickWindowPrivate::get(rootItem->window())->deliveryAgent;
+ qCDebug(lcFocus) << " delegating clearFocusInScope to" << da;
+ QQuickWindowPrivate::get(rootItem->window())->deliveryAgentPrivate()->clearFocusInScope(da->rootItem(), item, reason, options);
+ }
+ if (oldActiveFocusItem == activeFocusItem)
+ qCDebug(lcFocus) << "activeFocusItem remains" << activeFocusItem << "in" << q;
+ else
+ qCDebug(lcFocus) << " activeFocusItem" << oldActiveFocusItem << "->" << activeFocusItem << "in" << q;
+}
+
+void QQuickDeliveryAgentPrivate::clearFocusObject()
+{
+ if (activeFocusItem == rootItem)
+ return;
+
+ clearFocusInScope(rootItem, QQuickItemPrivate::get(rootItem)->subFocusItem, Qt::OtherFocusReason);
+}
+
+void QQuickDeliveryAgentPrivate::notifyFocusChangesRecur(QQuickItem **items, int remaining, Qt::FocusReason reason)
+{
+ QPointer<QQuickItem> item(*items);
+
+ if (item) {
+ QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item);
+
+ if (itemPrivate->notifiedFocus != itemPrivate->focus) {
+ itemPrivate->notifiedFocus = itemPrivate->focus;
+ itemPrivate->notifyChangeListeners(QQuickItemPrivate::Focus, &QQuickItemChangeListener::itemFocusChanged, item, reason);
+ emit item->focusChanged(itemPrivate->focus);
+ }
+
+ if (item && itemPrivate->notifiedActiveFocus != itemPrivate->activeFocus) {
+ itemPrivate->notifiedActiveFocus = itemPrivate->activeFocus;
+ itemPrivate->itemChange(QQuickItem::ItemActiveFocusHasChanged, bool(itemPrivate->activeFocus));
+ itemPrivate->notifyChangeListeners(QQuickItemPrivate::Focus, &QQuickItemChangeListener::itemFocusChanged, item, reason);
+ emit item->activeFocusChanged(itemPrivate->activeFocus);
+ }
+ }
+
+ if (remaining)
+ notifyFocusChangesRecur(items + 1, remaining - 1, reason);
+}
+
+bool QQuickDeliveryAgentPrivate::clearHover(ulong timestamp)
+{
+ if (hoverItems.isEmpty())
+ return false;
+
+ QQuickWindow *window = rootItem->window();
+ if (!window)
+ return false;
+
+ const QPointF lastPos = window->mapFromGlobal(QGuiApplicationPrivate::lastCursorPosition);
+ const auto modifiers = QGuiApplication::keyboardModifiers();
+
+ for (const auto &[item, id] : hoverItems) {
+ if (item) {
+ deliverHoverEventToItem(item, lastPos, lastPos, modifiers, timestamp, HoverChange::Clear);
+ Q_ASSERT(id == 0);
+ }
+ }
+
+ return true;
+}
+
+void QQuickDeliveryAgentPrivate::updateFocusItemTransform()
+{
+#if QT_CONFIG(im)
+ if (activeFocusItem && QGuiApplication::focusObject() == activeFocusItem) {
+ QQuickItemPrivate *focusPrivate = QQuickItemPrivate::get(activeFocusItem);
+ QGuiApplication::inputMethod()->setInputItemTransform(focusPrivate->itemToWindowTransform());
+ QGuiApplication::inputMethod()->setInputItemRectangle(QRectF(0, 0, focusPrivate->width, focusPrivate->height));
+ activeFocusItem->updateInputMethod(Qt::ImInputItemClipRectangle);
+ }
+#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.
+ Otherwise returns QQuickItemPrivate::deliveryAgent() (the delivery agent for
+ the narrowest subscene containing \a item), or \c null if \a item is \c null.
+*/
+QQuickDeliveryAgent *QQuickDeliveryAgentPrivate::currentOrItemDeliveryAgent(const QQuickItem *item)
+{
+ if (currentEventDeliveryAgent)
+ return currentEventDeliveryAgent;
+ if (item)
+ return QQuickItemPrivate::get(const_cast<QQuickItem *>(item))->deliveryAgent();
+ return nullptr;
+}
+
+/*! \internal
+ QQuickDeliveryAgent delivers events to a tree of Qt Quick Items, beginning
+ with the given root item, which is usually QQuickWindow::rootItem() but
+ may alternatively be embedded into a Qt Quick 3D scene or something else.
+*/
+QQuickDeliveryAgent::QQuickDeliveryAgent(QQuickItem *rootItem)
+ : QObject(*new QQuickDeliveryAgentPrivate(rootItem), rootItem)
+{
+}
+
+QQuickDeliveryAgent::~QQuickDeliveryAgent()
+{
+}
+
+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);
+ return d->rootItem;
+}
+
+/*! \internal
+ Returns the object that was set in setSceneTransform(): a functor that
+ transforms from scene coordinates in the parent scene to scene coordinates
+ within this DA's subscene, or \c null if none was set.
+*/
+QQuickDeliveryAgent::Transform *QQuickDeliveryAgent::sceneTransform() const
+{
+ Q_D(const QQuickDeliveryAgent);
+ return d->sceneTransform;
+}
+
+/*! \internal
+ QQuickDeliveryAgent takes ownership of the given \a transform, which
+ encapsulates the ability to transform parent scene coordinates to rootItem
+ (subscene) coordinates.
+*/
+void QQuickDeliveryAgent::setSceneTransform(QQuickDeliveryAgent::Transform *transform)
+{
+ Q_D(QQuickDeliveryAgent);
+ if (d->sceneTransform == transform)
+ return;
+ qCDebug(lcPtr) << this << d->sceneTransform << "->" << transform;
+ if (d->sceneTransform)
+ delete d->sceneTransform;
+ 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);
+ d->currentEventDeliveryAgent = this;
+ auto cleanup = qScopeGuard([d] { d->currentEventDeliveryAgent = nullptr; });
+
+ switch (ev->type()) {
+ case QEvent::MouseButtonPress:
+ case QEvent::MouseButtonRelease:
+ case QEvent::MouseButtonDblClick:
+ case QEvent::MouseMove: {
+ QMouseEvent *me = static_cast<QMouseEvent*>(ev);
+ d->handleMouseEvent(me);
+ break;
+ }
+ case QEvent::HoverEnter:
+ case QEvent::HoverLeave:
+ case QEvent::HoverMove: {
+ QHoverEvent *he = static_cast<QHoverEvent*>(ev);
+ bool accepted = d->deliverHoverEvent(he->scenePosition(),
+ he->points().first().sceneLastPosition(),
+ he->modifiers(), he->timestamp());
+ d->lastMousePosition = he->scenePosition();
+ he->setAccepted(accepted);
+#if QT_CONFIG(cursor)
+ QQuickWindowPrivate::get(d->rootItem->window())->updateCursor(d->sceneTransform ?
+ d->sceneTransform->map(he->scenePosition()) : he->scenePosition(), d->rootItem);
+#endif
+ return accepted;
+ }
+ case QEvent::TouchBegin:
+ case QEvent::TouchUpdate:
+ case QEvent::TouchEnd: {
+ QTouchEvent *touch = static_cast<QTouchEvent*>(ev);
+ d->handleTouchEvent(touch);
+ if (Q_LIKELY(QCoreApplication::testAttribute(Qt::AA_SynthesizeMouseForUnhandledTouchEvents))) {
+ // we consume all touch events ourselves to avoid duplicate
+ // mouse delivery by QtGui mouse synthesis
+ ev->accept();
+ }
+ break;
+ }
+ case QEvent::TouchCancel:
+ // return in order to avoid the QWindow::event below
+ return d->deliverTouchCancelEvent(static_cast<QTouchEvent*>(ev));
+ break;
+ case QEvent::Enter: {
+ if (!d->rootItem)
+ return false;
+ QEnterEvent *enter = static_cast<QEnterEvent*>(ev);
+ const auto scenePos = enter->scenePosition();
+ bool accepted = d->deliverHoverEvent(scenePos,
+ enter->points().first().sceneLastPosition(),
+ enter->modifiers(), enter->timestamp());
+ d->lastMousePosition = scenePos;
+ // deliverHoverEvent() constructs QHoverEvents: check that EPD didn't end up with corrupted scenePos
+ Q_ASSERT(enter->scenePosition() == scenePos);
+ enter->setAccepted(accepted);
+#if QT_CONFIG(cursor)
+ QQuickWindowPrivate::get(d->rootItem->window())->updateCursor(enter->scenePosition(), d->rootItem);
+#endif
+ return accepted;
+ }
+ case QEvent::Leave:
+ d->clearHover();
+ d->lastMousePosition = QPointF();
+ break;
+#if QT_CONFIG(quick_draganddrop)
+ case QEvent::DragEnter:
+ case QEvent::DragLeave:
+ case QEvent::DragMove:
+ case QEvent::Drop:
+ d->deliverDragEvent(d->dragGrabber, ev);
+ break;
+#endif
+ case QEvent::FocusAboutToChange:
+#if QT_CONFIG(im)
+ if (d->activeFocusItem)
+ qGuiApp->inputMethod()->commit();
+#endif
+ break;
+#if QT_CONFIG(gestures)
+ case QEvent::NativeGesture:
+ d->deliverSinglePointEventUntilAccepted(static_cast<QPointerEvent *>(ev));
+ break;
+#endif
+ case QEvent::ShortcutOverride:
+ d->deliverKeyEvent(static_cast<QKeyEvent *>(ev));
+ break;
+ case QEvent::InputMethod:
+ case QEvent::InputMethodQuery:
+ {
+ QQuickItem *target = d->focusTargetItem();
+ if (target)
+ QCoreApplication::sendEvent(target, ev);
+ }
+ break;
+#if QT_CONFIG(wheelevent)
+ case QEvent::Wheel: {
+ auto event = static_cast<QWheelEvent *>(ev);
+ qCDebug(lcMouse) << event;
+
+ //if the actual wheel event was accepted, accept the compatibility wheel event and return early
+ if (d->lastWheelEventAccepted && event->angleDelta().isNull() && event->phase() == Qt::ScrollUpdate)
+ return true;
+
+ event->ignore();
+ Q_QUICK_INPUT_PROFILE(QQuickProfiler::Mouse, QQuickProfiler::InputMouseWheel,
+ event->angleDelta().x(), event->angleDelta().y());
+ d->deliverSinglePointEventUntilAccepted(event);
+ d->lastWheelEventAccepted = event->isAccepted();
+ break;
+ }
+#endif
+#if QT_CONFIG(tabletevent)
+ case QEvent::TabletPress:
+ case QEvent::TabletMove:
+ case QEvent::TabletRelease:
+ {
+ auto *tabletEvent = static_cast<QTabletEvent *>(ev);
+ d->deliverPointerEvent(tabletEvent); // visits HoverHandlers too (unlike the mouse event case)
+#if QT_CONFIG(cursor)
+ QQuickWindowPrivate::get(d->rootItem->window())->updateCursor(tabletEvent->scenePosition(), d->rootItem);
+#endif
+ }
+ break;
+#endif
+ default:
+ return false;
+ }
+
+ return true;
+}
+
+void QQuickDeliveryAgentPrivate::deliverKeyEvent(QKeyEvent *e)
+{
+ if (activeFocusItem) {
+ const bool keyPress = (e->type() == QEvent::KeyPress);
+ switch (e->type()) {
+ case QEvent::KeyPress:
+ Q_QUICK_INPUT_PROFILE(QQuickProfiler::Key, QQuickProfiler::InputKeyPress, e->key(), e->modifiers());
+ break;
+ case QEvent::KeyRelease:
+ Q_QUICK_INPUT_PROFILE(QQuickProfiler::Key, QQuickProfiler::InputKeyRelease, e->key(), e->modifiers());
+ break;
+ default:
+ break;
+ }
+
+ QQuickItem *item = activeFocusItem;
+
+ // In case of generated event, trigger ShortcutOverride event
+ if (keyPress && e->spontaneous() == false)
+ qt_sendShortcutOverrideEvent(item, e->timestamp(),
+ e->key(), e->modifiers(), e->text(),
+ e->isAutoRepeat(), e->count());
+
+ do {
+ e->accept();
+ QCoreApplication::sendEvent(item, e);
+ } while (!e->isAccepted() && (item = item->parentItem()));
+ }
+}
+
+QQuickDeliveryAgentPrivate::QQuickDeliveryAgentPrivate(QQuickItem *root) :
+ QObjectPrivate(),
+ rootItem(root),
+ // a plain QQuickItem can be a subscene root; a QQuickRootItem always belongs directly to a QQuickWindow
+ isSubsceneAgent(!qmlobject_cast<QQuickRootItem *>(rootItem))
+{
+#if QT_CONFIG(quick_draganddrop)
+ dragGrabber = new QQuickDragGrabber;
+#endif
+ if (isSubsceneAgent)
+ subsceneAgentsExist = true;
+}
+
+QQuickDeliveryAgentPrivate::~QQuickDeliveryAgentPrivate()
+{
+#if QT_CONFIG(quick_draganddrop)
+ delete dragGrabber;
+ dragGrabber = nullptr;
+#endif
+ delete sceneTransform;
+}
+
+/*! \internal
+ Make a copy of any type of QPointerEvent, and optionally localize it
+ by setting its first point's local position() if \a transformedLocalPos is given.
+
+ \note some subclasses of QSinglePointEvent, such as QWheelEvent, add extra storage.
+ This function doesn't yet support cloning all of those; it can be extended if needed.
+*/
+QPointerEvent *QQuickDeliveryAgentPrivate::clonePointerEvent(QPointerEvent *event, std::optional<QPointF> transformedLocalPos)
+{
+ QPointerEvent *ret = event->clone();
+ QEventPoint &point = ret->point(0);
+ QMutableEventPoint::detach(point);
+ QMutableEventPoint::setTimestamp(point, event->timestamp());
+ if (transformedLocalPos)
+ QMutableEventPoint::setPosition(point, *transformedLocalPos);
+
+ return ret;
+}
+
+void QQuickDeliveryAgentPrivate::deliverToPassiveGrabbers(const QVector<QPointer <QObject> > &passiveGrabbers,
+ QPointerEvent *pointerEvent)
+{
+ const QVector<QObject *> &eventDeliveryTargets =
+ QQuickPointerHandlerPrivate::deviceDeliveryTargets(pointerEvent->device());
+ QVarLengthArray<QPair<QQuickItem *, bool>, 4> sendFilteredPointerEventResult;
+ hasFiltered.clear();
+ for (QObject *grabberObject : passiveGrabbers) {
+ // a null pointer in passiveGrabbers is unlikely, unless the grabbing handler was deleted dynamically
+ if (Q_UNLIKELY(!grabberObject))
+ continue;
+ // a passiveGrabber might be an item or a handler
+ if (QQuickPointerHandler *handler = qobject_cast<QQuickPointerHandler *>(grabberObject)) {
+ if (handler && !eventDeliveryTargets.contains(handler)) {
+ bool alreadyFiltered = false;
+ QQuickItem *par = handler->parentItem();
+
+ // see if we already have sent a filter event to the parent
+ auto it = std::find_if(sendFilteredPointerEventResult.begin(), sendFilteredPointerEventResult.end(),
+ [par](const QPair<QQuickItem *, bool> &pair) { return pair.first == par; });
+ if (it != sendFilteredPointerEventResult.end()) {
+ // Yes, the event was sent to that parent for filtering: do not call it again, but use
+ // the result of the previous call to determine whether we should call the handler.
+ alreadyFiltered = it->second;
+ } else if (par) {
+ alreadyFiltered = sendFilteredPointerEvent(pointerEvent, par);
+ sendFilteredPointerEventResult << qMakePair(par, alreadyFiltered);
+ }
+ if (!alreadyFiltered) {
+ if (par)
+ localizePointerEvent(pointerEvent, par);
+ handler->handlePointerEvent(pointerEvent);
+ }
+ }
+ } else if (QQuickItem *grabberItem = static_cast<QQuickItem *>(grabberObject)) {
+ // don't steal the grab if input should remain with the exclusive grabber only
+ if (QQuickItem *excGrabber = static_cast<QQuickItem *>(pointerEvent->exclusiveGrabber(pointerEvent->point(0)))) {
+ if ((isMouseEvent(pointerEvent) && excGrabber->keepMouseGrab())
+ || (isTouchEvent(pointerEvent) && excGrabber->keepTouchGrab())) {
+ return;
+ }
+ }
+ localizePointerEvent(pointerEvent, grabberItem);
+ QCoreApplication::sendEvent(grabberItem, pointerEvent);
+ pointerEvent->accept();
+ }
+ }
+}
+
+bool QQuickDeliveryAgentPrivate::sendHoverEvent(QEvent::Type type, QQuickItem *item,
+ const QPointF &scenePos, const QPointF &lastScenePos,
+ Qt::KeyboardModifiers modifiers, ulong timestamp)
+{
+ auto itemPrivate = QQuickItemPrivate::get(item);
+ const auto transform = itemPrivate->windowToItemTransform();
+ const auto transformToGlobal = itemPrivate->windowToGlobalTransform();
+ auto globalPos = transformToGlobal.map(scenePos);
+ QHoverEvent hoverEvent(type, scenePos, globalPos, transform.map(lastScenePos), modifiers);
+ hoverEvent.setTimestamp(timestamp);
+ hoverEvent.setAccepted(true);
+ QEventPoint &point = hoverEvent.point(0);
+ QMutableEventPoint::setPosition(point, transform.map(scenePos));
+ QMutableEventPoint::setGlobalLastPosition(point, transformToGlobal.map(lastScenePos));
+
+ hasFiltered.clear();
+ if (sendFilteredMouseEvent(&hoverEvent, item, item->parentItem()))
+ return true;
+
+ QCoreApplication::sendEvent(item, &hoverEvent);
+
+ return hoverEvent.isAccepted();
+}
+
+/*! \internal
+ Delivers a hover event at \a scenePos to the whole scene or subscene
+ that this DeliveryAgent is responsible for. Returns \c true if
+ delivery is "done".
+*/
+// TODO later: specify the device in case of multi-mouse scenario, or mouse and tablet both in use
+bool QQuickDeliveryAgentPrivate::deliverHoverEvent(
+ const QPointF &scenePos, const QPointF &lastScenePos,
+ Qt::KeyboardModifiers modifiers, ulong timestamp)
+{
+ // The first time this function is called, hoverItems is empty.
+ // We then call deliverHoverEventRecursive from the rootItem, and
+ // populate the list with all the children and grandchildren that
+ // we find that should receive hover events (in addition to sending
+ // hover events to them and their HoverHandlers). We also set the
+ // hoverId for each item to the currentHoverId.
+ // The next time this function is called, we bump currentHoverId,
+ // and call deliverHoverEventRecursive once more.
+ // When that call returns, the list will contain the items that
+ // were hovered the first time, as well as the items that were hovered
+ // this time. But only the items that were hovered this time
+ // will have their hoverId equal to currentHoverId; the ones we didn't
+ // visit will still have an old hoverId. We can therefore go through the
+ // list at the end of this function and look for items with an old hoverId,
+ // remove them from the list, and update their state accordingly.
+
+ const bool subtreeHoverEnabled = QQuickItemPrivate::get(rootItem)->subtreeHoverEnabled;
+ const bool itemsWasHovered = !hoverItems.isEmpty();
+
+ if (!subtreeHoverEnabled && !itemsWasHovered)
+ return false;
+
+ currentHoverId++;
+
+ if (subtreeHoverEnabled) {
+ hoveredLeafItemFound = false;
+ deliverHoverEventRecursive(rootItem, scenePos, lastScenePos, modifiers, timestamp);
+ }
+
+ // Prune the list for items that are no longer hovered
+ for (auto it = hoverItems.begin(); it != hoverItems.end();) {
+ const auto &[item, hoverId] = *it;
+ if (hoverId == currentHoverId) {
+ // Still being hovered
+ it++;
+ } else {
+ // 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)
+ deliverHoverEventToItem(item, scenePos, lastScenePos, modifiers, timestamp, HoverChange::Clear);
+ it = hoverItems.erase(it);
+ }
+ }
+
+ const bool itemsAreHovered = !hoverItems.isEmpty();
+ return itemsWasHovered || itemsAreHovered;
+}
+
+/*! \internal
+ Delivers a hover event at \a scenePos to \a item and all its children.
+ The children get it first. As soon as any item allows the event to remain
+ accepted, recursion stops. Returns \c true in that case, or \c false if the
+ event is rejected.
+
+ Each item that has hover enabled (from setAcceptHoverEvents()) has the
+ QQuickItemPrivate::hoverEnabled flag set. This only controls whether we
+ should send hover events to the item itself. (HoverHandlers no longer set
+ this flag.) When an item has hoverEnabled set, all its ancestors have the
+ QQuickItemPrivate::subtreeHoverEnabled set. This function will
+ follow the subtrees that have subtreeHoverEnabled by recursing into each
+ child with that flag set. And for each child (in addition to the item
+ itself) that also has hoverEnabled set, we call deliverHoverEventToItem()
+ to actually deliver the event to it. The item can then choose to accept or
+ reject the event. This is only for control over whether we stop propagation
+ or not: an item can reject the event, but at the same time be hovered (and
+ therefore in hoverItems). By accepting the event, the item will effectivly
+ end up as the only one hovered. Any other HoverHandler that may be a child
+ of an item that is stacked underneath, will not. Note that since siblings
+ can overlap, there can be more than one leaf item under the mouse.
+
+ Note that HoverHandler doesn't set the hoverEnabled flag on the parent item.
+ But still, adding a HoverHandler to an item will set its subtreeHoverEnabled flag.
+ So all the propagation logic described above will otherwise be the same.
+ But the hoverEnabled flag can be used to resolve if subtreeHoverEnabled is on
+ because the application explicitly requested it (setAcceptHoverEvents()), or
+ indirectly, because the item has HoverHandlers.
+
+ For legacy reasons (Qt 6.1), as soon as we find a leaf item that has hover
+ enabled, and therefore receives the event, we stop recursing into the remaining
+ siblings (even if the event was ignored). This means that we only allow hover
+ events to propagate up the direct parent-child hierarchy, and not to siblings.
+ However, if the first candidate HoverHandler is disabled, delivery continues
+ to the next one, which may be a sibling (QTBUG-106548).
+*/
+bool QQuickDeliveryAgentPrivate::deliverHoverEventRecursive(
+ QQuickItem *item, const QPointF &scenePos, const QPointF &lastScenePos,
+ Qt::KeyboardModifiers modifiers, ulong timestamp)
+{
+
+ const QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item);
+ const QList<QQuickItem *> children = itemPrivate->paintOrderChildItems();
+
+ for (int ii = children.size() - 1; ii >= 0; --ii) {
+ QQuickItem *child = children.at(ii);
+ const QQuickItemPrivate *childPrivate = QQuickItemPrivate::get(child);
+
+ if (!child->isVisible() || childPrivate->culled)
+ continue;
+ if (!childPrivate->subtreeHoverEnabled)
+ continue;
+ if (childPrivate->flags & QQuickItem::ItemClipsChildrenToShape) {
+ const QPointF localPos = child->mapFromScene(scenePos);
+ if (!child->contains(localPos))
+ continue;
+ }
+
+ // Recurse into the child
+ const bool accepted = deliverHoverEventRecursive(child, scenePos, lastScenePos, modifiers, timestamp);
+ if (accepted) {
+ // Stop propagation / recursion
+ return true;
+ }
+ if (hoveredLeafItemFound) {
+ // Don't propagate to siblings, only to ancestors
+ break;
+ }
+ }
+
+ // All decendants have been visited.
+ // Now deliver the event to the item
+ return deliverHoverEventToItem(item, scenePos, lastScenePos, modifiers, timestamp, HoverChange::Set);
+}
+
+/*! \internal
+ Delivers a hover event at \a scenePos to \a item and its HoverHandlers if any.
+ Returns \c true if the event remains accepted, \c false if rejected.
+
+ If \a clearHover is \c true, it will be sent as a QEvent::HoverLeave event,
+ and the item and its handlers are expected to transition into their non-hovered
+ states even if the position still indicates that the mouse is inside.
+*/
+bool QQuickDeliveryAgentPrivate::deliverHoverEventToItem(
+ QQuickItem *item, const QPointF &scenePos, const QPointF &lastScenePos,
+ 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 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;
+
+ bool accepted = false;
+
+ // 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 && 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;
+ 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
+ hoverItemIterator.value() = 0;
+ sendHoverEvent(QEvent::HoverLeave, item, scenePos, lastScenePos, modifiers, timestamp);
+ }
+
+ if (!itemPrivate->hasPointerHandlers())
+ return accepted;
+
+ // Next, send out hover events to the hover handlers.
+ // If the item didn't accept the hover event, 'accepted' is now false.
+ // Otherwise it's true, and then it should stay the way regardless of
+ // whether or not the hoverhandlers themselves are hovered.
+ // Note that since a HoverHandler can have a margin, a HoverHandler
+ // can be hovered even if the item itself is not.
+
+ if (hoverChange == HoverChange::Clear) {
+ // Note: a leave should never stop propagation
+ QHoverEvent hoverEvent(QEvent::HoverLeave, scenePos, globalPos, lastScenePos, modifiers);
+ hoverEvent.setTimestamp(timestamp);
+
+ for (QQuickPointerHandler *h : itemPrivate->extra->pointerHandlers) {
+ if (QQuickHoverHandler *hh = qmlobject_cast<QQuickHoverHandler *>(h)) {
+ if (!hh->isHovered())
+ continue;
+ hoverEvent.setAccepted(true);
+ QCoreApplication::sendEvent(hh, &hoverEvent);
+ }
+ }
+ } else {
+ QMouseEvent hoverEvent(QEvent::MouseMove, localPos, scenePos, globalPos, Qt::NoButton, Qt::NoButton, modifiers);
+ hoverEvent.setTimestamp(timestamp);
+
+ for (QQuickPointerHandler *h : itemPrivate->extra->pointerHandlers) {
+ if (QQuickHoverHandler *hh = qmlobject_cast<QQuickHoverHandler *>(h)) {
+ if (!hh->enabled())
+ continue;
+ hoverEvent.setAccepted(true);
+ hh->handlePointerEvent(&hoverEvent);
+ if (hh->isHovered()) {
+ // Mark the whole item as updated, even if only the handler is
+ // actually in a hovered state (because of HoverHandler.margins)
+ hoveredLeafItemFound = true;
+ 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;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ return accepted;
+}
+
+// Simple delivery of non-mouse, non-touch Pointer Events: visit the items and handlers
+// in the usual reverse-paint-order until propagation is stopped
+bool QQuickDeliveryAgentPrivate::deliverSinglePointEventUntilAccepted(QPointerEvent *event)
+{
+ Q_ASSERT(event->points().size() == 1);
+ QQuickPointerHandlerPrivate::deviceDeliveryTargets(event->pointingDevice()).clear();
+ QEventPoint &point = event->point(0);
+ QVector<QQuickItem *> targetItems = pointerTargets(rootItem, event, point, false, false);
+ point.setAccepted(false);
+
+ // Let passive grabbers see the event. This must be done before we deliver the
+ // event to the target and to handlers that might stop event propagation.
+ // Passive grabbers cannot stop event delivery.
+ for (const auto &passiveGrabber : event->passiveGrabbers(point)) {
+ if (auto *grabberItem = qobject_cast<QQuickItem *>(passiveGrabber)) {
+ if (targetItems.contains(grabberItem))
+ continue;
+ localizePointerEvent(event, grabberItem);
+ QCoreApplication::sendEvent(grabberItem, event);
+ }
+ }
+ // Maintain the invariant that items receive input events in accepted state.
+ // A passive grabber might have explicitly ignored the event.
+ event->accept();
+
+ for (QQuickItem *item : targetItems) {
+ QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item);
+ localizePointerEvent(event, item);
+ // Let Pointer Handlers have the first shot
+ itemPrivate->handlePointerEvent(event);
+ if (point.isAccepted())
+ return true;
+ event->accept();
+ QCoreApplication::sendEvent(item, event);
+ if (event->isAccepted()) {
+ qCDebug(lcWheelTarget) << event << "->" << item;
+ return true;
+ }
+ }
+
+ return false; // it wasn't handled
+}
+
+bool QQuickDeliveryAgentPrivate::deliverTouchCancelEvent(QTouchEvent *event)
+{
+ qCDebug(lcTouch) << event;
+
+ // An incoming TouchCancel event will typically not contain any points,
+ // but sendTouchCancelEvent() adds the points that have grabbers to the event.
+ // Deliver it to all items and handlers that have active touches.
+ const_cast<QPointingDevicePrivate *>(QPointingDevicePrivate::get(event->pointingDevice()))->
+ sendTouchCancelEvent(event);
+
+ cancelTouchMouseSynthesis();
+
+ return true;
+}
+
+void QQuickDeliveryAgentPrivate::deliverDelayedTouchEvent()
+{
+ // Deliver and delete delayedTouch.
+ // Set delayedTouch to nullptr before delivery to avoid redelivery in case of
+ // event loop recursions (e.g if it the touch starts a dnd session).
+ std::unique_ptr<QTouchEvent> e(std::move(delayedTouch));
+ qCDebug(lcTouchCmprs) << "delivering" << e.get();
+ compressedTouchCount = 0;
+ deliverPointerEvent(e.get());
+}
+
+/*! \internal
+ The handler for the QEvent::WindowDeactivate event, and also when
+ Qt::ApplicationState tells us the application is no longer active.
+ It clears all exclusive grabs of items and handlers whose window is this one,
+ for all known pointing devices.
+
+ The QEvent is not passed into this function because in the first case it's
+ just a plain QEvent with no extra data, and because the application state
+ change is delivered via a signal rather than an event.
+*/
+void QQuickDeliveryAgentPrivate::handleWindowDeactivate(QQuickWindow *win)
+{
+ Q_Q(QQuickDeliveryAgent);
+ qCDebug(lcFocus) << "deactivated" << win->title();
+ const auto inputDevices = QInputDevice::devices();
+ for (auto device : inputDevices) {
+ if (auto pointingDevice = qobject_cast<const QPointingDevice *>(device)) {
+ auto devPriv = QPointingDevicePrivate::get(const_cast<QPointingDevice *>(pointingDevice));
+ for (auto epd : devPriv->activePoints.values()) {
+ if (!epd.exclusiveGrabber.isNull()) {
+ bool relevant = false;
+ if (QQuickItem *item = qmlobject_cast<QQuickItem *>(epd.exclusiveGrabber.data()))
+ relevant = (item->window() == win);
+ else if (QQuickPointerHandler *handler = qmlobject_cast<QQuickPointerHandler *>(epd.exclusiveGrabber.data())) {
+ if (handler->parentItem())
+ relevant = (handler->parentItem()->window() == win && epd.exclusiveGrabberContext.data() == q);
+ else
+ // a handler with no Item parent probably has a 3D Model parent.
+ // TODO actually check the window somehow
+ relevant = true;
+ }
+ if (relevant)
+ devPriv->setExclusiveGrabber(nullptr, epd.eventPoint, nullptr);
+ }
+ // For now, we don't clearPassiveGrabbers(), just in case passive grabs
+ // can be useful to keep monitoring the mouse even after window deactivation.
+ }
+ }
+ }
+}
+
+void QQuickDeliveryAgentPrivate::handleWindowHidden(QQuickWindow *win)
+{
+ qCDebug(lcFocus) << "hidden" << win->title();
+ clearHover();
+ lastMousePosition = QPointF();
+}
+
+bool QQuickDeliveryAgentPrivate::allUpdatedPointsAccepted(const QPointerEvent *ev)
+{
+ for (auto &point : ev->points()) {
+ if (point.state() != QEventPoint::State::Pressed && !point.isAccepted())
+ return false;
+ }
+ return true;
+}
+
+/*! \internal
+ Localize \a ev for delivery to \a dest.
+
+ Unlike QMutableTouchEvent::localized(), this modifies the QEventPoint
+ instances in \a ev, which is more efficient than making a copy.
+*/
+void QQuickDeliveryAgentPrivate::localizePointerEvent(QPointerEvent *ev, const QQuickItem *dest)
+{
+ for (int i = 0; i < ev->pointCount(); ++i) {
+ auto &point = ev->point(i);
+ QMutableEventPoint::setPosition(point, dest->mapFromScene(point.scenePosition()));
+ qCDebug(lcPtrLoc) << ev->type() << "@" << point.scenePosition() << "to"
+ << dest << "@" << dest->mapToScene(QPointF()) << "->" << point;
+ }
+}
+
+QList<QObject *> QQuickDeliveryAgentPrivate::exclusiveGrabbers(QPointerEvent *ev)
+{
+ QList<QObject *> result;
+ for (const QEventPoint &point : ev->points()) {
+ if (QObject *grabber = ev->exclusiveGrabber(point)) {
+ if (!result.contains(grabber))
+ result << grabber;
+ }
+ }
+ return result;
+}
+
+bool QQuickDeliveryAgentPrivate::anyPointGrabbed(const QPointerEvent *ev)
+{
+ for (const QEventPoint &point : ev->points()) {
+ if (ev->exclusiveGrabber(point) || !ev->passiveGrabbers(point).isEmpty())
+ return true;
+ }
+ return false;
+}
+
+bool QQuickDeliveryAgentPrivate::allPointsGrabbed(const QPointerEvent *ev)
+{
+ for (const auto &point : ev->points()) {
+ if (!ev->exclusiveGrabber(point) && ev->passiveGrabbers(point).isEmpty())
+ return false;
+ }
+ return true;
+}
+
+bool QQuickDeliveryAgentPrivate::isMouseEvent(const QPointerEvent *ev)
+{
+ switch (ev->type()) {
+ case QEvent::MouseButtonPress:
+ case QEvent::MouseButtonRelease:
+ case QEvent::MouseButtonDblClick:
+ case QEvent::MouseMove:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool QQuickDeliveryAgentPrivate::isMouseOrWheelEvent(const QPointerEvent *ev)
+{
+ return isMouseEvent(ev) || ev->type() == QEvent::Wheel;
+}
+
+bool QQuickDeliveryAgentPrivate::isHoverEvent(const QPointerEvent *ev)
+{
+ switch (ev->type()) {
+ case QEvent::HoverEnter:
+ case QEvent::HoverMove:
+ case QEvent::HoverLeave:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool QQuickDeliveryAgentPrivate::isTouchEvent(const QPointerEvent *ev)
+{
+ switch (ev->type()) {
+ case QEvent::TouchBegin:
+ case QEvent::TouchUpdate:
+ case QEvent::TouchEnd:
+ case QEvent::TouchCancel:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool QQuickDeliveryAgentPrivate::isTabletEvent(const QPointerEvent *ev)
+{
+ switch (ev->type()) {
+ case QEvent::TabletPress:
+ case QEvent::TabletMove:
+ case QEvent::TabletRelease:
+ case QEvent::TabletEnterProximity:
+ case QEvent::TabletLeaveProximity:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool QQuickDeliveryAgentPrivate::isEventFromMouseOrTouchpad(const QPointerEvent *ev)
+{
+ const auto devType = ev->device()->type();
+ return devType == QInputDevice::DeviceType::Mouse ||
+ devType == QInputDevice::DeviceType::TouchPad;
+}
+
+bool QQuickDeliveryAgentPrivate::isSynthMouse(const QPointerEvent *ev)
+{
+ return (!isEventFromMouseOrTouchpad(ev) && isMouseEvent(ev));
+}
+
+QQuickPointingDeviceExtra *QQuickDeliveryAgentPrivate::deviceExtra(const QInputDevice *device)
+{
+ QInputDevicePrivate *devPriv = QInputDevicePrivate::get(const_cast<QInputDevice *>(device));
+ if (devPriv->qqExtra)
+ return static_cast<QQuickPointingDeviceExtra *>(devPriv->qqExtra);
+ auto extra = new QQuickPointingDeviceExtra;
+ devPriv->qqExtra = extra;
+ QObject::connect(device, &QObject::destroyed, [devPriv]() {
+ delete static_cast<QQuickPointingDeviceExtra *>(devPriv->qqExtra);
+ devPriv->qqExtra = nullptr;
+ });
+ return extra;
+}
+
+/*!
+ \internal
+ This function is called from handleTouchEvent() in case a series of touch
+ events containing only \c Updated and \c Stationary points arrives within a
+ short period of time. (Some touchscreens are more "jittery" than others.)
+
+ It would be a waste of CPU time to deliver events and have items in the
+ scene getting modified more often than once per frame; so here we try to
+ coalesce the series of updates into a single event containing all updates
+ that occur within one frame period, and deliverDelayedTouchEvent() is
+ called from flushFrameSynchronousEvents() to send that single event. This
+ is the reason why touch compression lives here so far, instead of in a
+ lower layer: the render loop updates the scene in sync with the screen's
+ vsync, and flushFrameSynchronousEvents() is called from there (for example
+ from QSGThreadedRenderLoop::polishAndSync(), and equivalent places in other
+ render loops). It would be preferable to move this code down to a lower
+ level eventually, though, because it's not fundamentally a Qt Quick concern.
+
+ This optimization can be turned off by setting the environment variable
+ \c QML_NO_TOUCH_COMPRESSION.
+
+ Returns \c true if "done", \c false if the caller needs to finish the
+ \a event delivery.
+*/
+bool QQuickDeliveryAgentPrivate::compressTouchEvent(QTouchEvent *event)
+{
+ // If this is a subscene agent, don't store any events, because
+ // flushFrameSynchronousEvents() is only called on the window's DA.
+ if (isSubsceneAgent)
+ return false;
+
+ QEventPoint::States states = event->touchPointStates();
+ if (states.testFlag(QEventPoint::State::Pressed) || states.testFlag(QEventPoint::State::Released)) {
+ qCDebug(lcTouchCmprs) << "no compression" << event;
+ // we can only compress an event that doesn't include any pressed or released points
+ return false;
+ }
+
+ if (!delayedTouch) {
+ delayedTouch.reset(new QMutableTouchEvent(event->type(), event->pointingDevice(), event->modifiers(), event->points()));
+ delayedTouch->setTimestamp(event->timestamp());
+ for (qsizetype i = 0; i < delayedTouch->pointCount(); ++i) {
+ auto &tp = delayedTouch->point(i);
+ QMutableEventPoint::detach(tp);
+ }
+ ++compressedTouchCount;
+ qCDebug(lcTouchCmprs) << "delayed" << compressedTouchCount << delayedTouch.get();
+ if (QQuickWindow *window = rootItem->window())
+ window->maybeUpdate();
+ return true;
+ }
+
+ // check if this looks like the last touch event
+ if (delayedTouch->type() == event->type() &&
+ delayedTouch->device() == event->device() &&
+ delayedTouch->modifiers() == event->modifiers() &&
+ delayedTouch->pointCount() == event->pointCount())
+ {
+ // possible match.. is it really the same?
+ bool mismatch = false;
+
+ auto tpts = event->points();
+ for (qsizetype i = 0; i < event->pointCount(); ++i) {
+ const auto &tp = tpts.at(i);
+ const auto &tpDelayed = delayedTouch->point(i);
+ if (tp.id() != tpDelayed.id()) {
+ mismatch = true;
+ break;
+ }
+
+ if (tpDelayed.state() == QEventPoint::State::Updated && tp.state() == QEventPoint::State::Stationary)
+ QMutableEventPoint::setState(tpts[i], QEventPoint::State::Updated);
+ }
+
+ // matching touch event? then give delayedTouch a merged set of touchpoints
+ if (!mismatch) {
+ // have to create a new event because QMutableTouchEvent::setTouchPoints() is missing
+ // TODO optimize, or move event compression elsewhere
+ delayedTouch.reset(new QMutableTouchEvent(event->type(), event->pointingDevice(), event->modifiers(), tpts));
+ delayedTouch->setTimestamp(event->timestamp());
+ for (qsizetype i = 0; i < delayedTouch->pointCount(); ++i) {
+ auto &tp = delayedTouch->point(i);
+ QMutableEventPoint::detach(tp);
+ }
+ ++compressedTouchCount;
+ qCDebug(lcTouchCmprs) << "coalesced" << compressedTouchCount << delayedTouch.get();
+ if (QQuickWindow *window = rootItem->window())
+ window->maybeUpdate();
+ return true;
+ }
+ }
+
+ // merging wasn't possible, so deliver the delayed event first, and then delay this one
+ deliverDelayedTouchEvent();
+ delayedTouch.reset(new QMutableTouchEvent(event->type(), event->pointingDevice(),
+ event->modifiers(), event->points()));
+ delayedTouch->setTimestamp(event->timestamp());
+ return true;
+}
+
+// entry point for touch event delivery:
+// - translate the event to window coordinates
+// - compress the event instead of delivering it if applicable
+// - call deliverTouchPoints to actually dispatch the points
+void QQuickDeliveryAgentPrivate::handleTouchEvent(QTouchEvent *event)
+{
+ Q_Q(QQuickDeliveryAgent);
+ translateTouchEvent(event);
+ // TODO remove: touch and mouse should be independent until we come to touch->mouse synth
+ if (event->pointCount()) {
+ auto &point = event->point(0);
+ if (point.state() == QEventPoint::State::Released) {
+ lastMousePosition = QPointF();
+ } else {
+ lastMousePosition = point.position();
+ }
+ }
+
+ qCDebug(lcTouch) << q << event;
+
+ static bool qquickwindow_no_touch_compression = qEnvironmentVariableIsSet("QML_NO_TOUCH_COMPRESSION");
+
+ if (qquickwindow_no_touch_compression || pointerEventRecursionGuard) {
+ deliverPointerEvent(event);
+ return;
+ }
+
+ if (!compressTouchEvent(event)) {
+ if (delayedTouch) {
+ deliverDelayedTouchEvent();
+ qCDebug(lcTouchCmprs) << "resuming delivery" << event;
+ }
+ deliverPointerEvent(event);
+ }
+}
+
+/*!
+ Handle \a event on behalf of this delivery agent's window or subscene.
+*/
+void QQuickDeliveryAgentPrivate::handleMouseEvent(QMouseEvent *event)
+{
+ Q_Q(QQuickDeliveryAgent);
+ // We generally don't want OS-synthesized mouse events, because Qt Quick does its own touch->mouse synthesis.
+ // But if the platform converts long-press to right-click, it's ok to react to that,
+ // unless the user has opted out by setting QT_QUICK_ALLOW_SYNTHETIC_RIGHT_CLICK=0.
+ if (event->source() == Qt::MouseEventSynthesizedBySystem &&
+ !(event->button() == Qt::RightButton && allowSyntheticRightClick())) {
+ event->accept();
+ return;
+ }
+ qCDebug(lcMouse) << q << event;
+
+ switch (event->type()) {
+ case QEvent::MouseButtonPress:
+ Q_QUICK_INPUT_PROFILE(QQuickProfiler::Mouse, QQuickProfiler::InputMousePress, event->button(),
+ event->buttons());
+ deliverPointerEvent(event);
+ break;
+ case QEvent::MouseButtonRelease:
+ Q_QUICK_INPUT_PROFILE(QQuickProfiler::Mouse, QQuickProfiler::InputMouseRelease, event->button(),
+ event->buttons());
+ deliverPointerEvent(event);
+#if QT_CONFIG(cursor)
+ QQuickWindowPrivate::get(rootItem->window())->updateCursor(event->scenePosition());
+#endif
+ break;
+ case QEvent::MouseButtonDblClick:
+ Q_QUICK_INPUT_PROFILE(QQuickProfiler::Mouse, QQuickProfiler::InputMouseDoubleClick,
+ event->button(), event->buttons());
+ deliverPointerEvent(event);
+ break;
+ case QEvent::MouseMove: {
+ Q_QUICK_INPUT_PROFILE(QQuickProfiler::Mouse, QQuickProfiler::InputMouseMove,
+ event->position().x(), event->position().y());
+
+ const QPointF last = lastMousePosition.isNull() ? event->scenePosition() : lastMousePosition;
+ lastMousePosition = event->scenePosition();
+ qCDebug(lcHoverTrace) << q << "mouse pos" << last << "->" << lastMousePosition;
+ if (!event->points().size() || !event->exclusiveGrabber(event->point(0))) {
+ bool accepted = deliverHoverEvent(event->scenePosition(), last, event->modifiers(), event->timestamp());
+ event->setAccepted(accepted);
+ }
+ deliverPointerEvent(event);
+#if QT_CONFIG(cursor)
+ // The pointer event could result in a cursor change (reaction), so update it afterwards.
+ QQuickWindowPrivate::get(rootItem->window())->updateCursor(event->scenePosition());
+#endif
+ break;
+ }
+ default:
+ Q_ASSERT(false);
+ break;
+ }
+}
+
+/*! \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);
+ QQuickDeliveryAgent *deliveringAgent = QQuickDeliveryAgentPrivate::currentEventDeliveryAgent;
+ QQuickDeliveryAgentPrivate::currentEventDeliveryAgent = q;
+
+ if (delayedTouch) {
+ deliverDelayedTouchEvent();
+
+ // Touch events which constantly start animations (such as a behavior tracking
+ // the mouse point) need animations to start.
+ QQmlAnimationTimer *ut = QQmlAnimationTimer::instance();
+ if (ut && ut->hasStartAnimationPending())
+ ut->startAnimations();
+ }
+
+ // In webOS we already have the alternative to the issue that this
+ // wanted to address and thus skipping this part won't break anything.
+#if !defined(Q_OS_WEBOS)
+ // Once per frame, if any items are dirty, send a synthetic hover,
+ // in case items have changed position, visibility, etc.
+ // For instance, during animation (including the case of a ListView
+ // whose delegates contain MouseAreas), a MouseArea needs to know
+ // whether it has moved into a position where it is now under the cursor.
+ // TODO do this for each known mouse device or come up with a different strategy
+ if (frameSynchronousHoverEnabled && !win->mouseGrabberItem() &&
+ !lastMousePosition.isNull() && QQuickWindowPrivate::get(win)->dirtyItemList) {
+ qCDebug(lcHoverTrace) << q << "delivering frame-sync hover to root @" << lastMousePosition;
+ 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
+ Q_UNUSED(win);
+#endif
+ if (Q_UNLIKELY(QQuickDeliveryAgentPrivate::currentEventDeliveryAgent &&
+ QQuickDeliveryAgentPrivate::currentEventDeliveryAgent != q))
+ qCWarning(lcPtr, "detected interleaved frame-sync and actual events");
+ 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)
+{
+ Q_Q(QQuickDeliveryAgent);
+ const bool grabGained = (transition == QPointingDevice::GrabTransition::GrabExclusive ||
+ transition == QPointingDevice::GrabTransition::GrabPassive);
+
+ // note: event can be null, if the signal was emitted from QPointingDevicePrivate::removeGrabber(grabber)
+ if (auto *handler = qmlobject_cast<QQuickPointerHandler *>(grabber)) {
+ if (handler->parentItem()) {
+ auto itemPriv = QQuickItemPrivate::get(handler->parentItem());
+ if (itemPriv->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 (isSubsceneAgent && (!itemPriv->extra.isAllocated() || !itemPriv->extra->subsceneDeliveryAgent))
+ itemPriv->maybeHasSubsceneDeliveryAgent = true;
+ }
+ } else if (!isSubsceneAgent) {
+ handler->onGrabChanged(handler, transition, const_cast<QPointerEvent *>(event),
+ const_cast<QEventPoint &>(point));
+ }
+ } else if (auto *grabberItem = qmlobject_cast<QQuickItem *>(grabber)) {
+ switch (transition) {
+ case QPointingDevice::CancelGrabExclusive:
+ case QPointingDevice::UngrabExclusive:
+ 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 (allReleasedOrCancelled)
+ grabberItem->touchUngrabEvent();
+ }
+ break;
+ default:
+ break;
+ }
+ auto *itemPriv = QQuickItemPrivate::get(grabberItem);
+ // 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()) {
+ switch (transition) {
+ case QPointingDevice::GrabPassive: {
+ auto epd = QPointingDevicePrivate::get(const_cast<QPointingDevice*>(event->pointingDevice()))->queryPointById(point.id());
+ Q_ASSERT(epd);
+ QPointingDevicePrivate::setPassiveGrabberContext(epd, grabber, q);
+ qCDebug(lcPtr) << "remembering that" << q << "handles point" << point.id() << "after" << transition;
+ } break;
+ case QPointingDevice::GrabExclusive: {
+ auto epd = QPointingDevicePrivate::get(const_cast<QPointingDevice*>(event->pointingDevice()))->queryPointById(point.id());
+ Q_ASSERT(epd);
+ 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;
+ }
+ }
+}
+
+/*! \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);
+ if (knownPointingDevices.contains(dev))
+ return;
+ knownPointingDevices.append(dev);
+ connect(dev, &QPointingDevice::grabChanged, this, &QQuickDeliveryAgentPrivate::onGrabChanged);
+ 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);
+ if (isTabletEvent(event))
+ qCDebug(lcTablet) << q << event;
+
+ // If users spin the eventloop as a result of event delivery, we disable
+ // event compression and send events directly. This is because we consider
+ // the usecase a bit evil, but we at least don't want to lose events.
+ ++pointerEventRecursionGuard;
+ eventsInDelivery.push(event);
+
+ // So far this is for use in Qt Quick 3D: if a QEventPoint is grabbed,
+ // updates get delivered here pretty directly, bypassing picking; but we need to
+ // be able to map the 2D viewport coordinate to a 2D coordinate within
+ // d->rootItem, a 2D scene that has been arbitrarily mapped onto a 3D object.
+ QVarLengthArray<QPointF, 16> originalScenePositions;
+ if (sceneTransform) {
+ originalScenePositions.resize(event->pointCount());
+ for (int i = 0; i < event->pointCount(); ++i) {
+ auto &pt = event->point(i);
+ originalScenePositions[i] = pt.scenePosition();
+ QMutableEventPoint::setScenePosition(pt, sceneTransform->map(pt.scenePosition()));
+ qCDebug(lcPtrLoc) << q << event->type() << pt.id() << "transformed scene pos" << pt.scenePosition();
+ }
+ } else if (isSubsceneAgent) {
+ qCDebug(lcPtrLoc) << q << event->type() << "no scene transform set";
+ }
+
+ skipDelivery.clear();
+ QQuickPointerHandlerPrivate::deviceDeliveryTargets(event->pointingDevice()).clear();
+ if (sceneTransform)
+ qCDebug(lcPtr) << q << "delivering with" << sceneTransform << event;
+ else
+ qCDebug(lcPtr) << q << "delivering" << event;
+ for (int i = 0; i < event->pointCount(); ++i)
+ event->point(i).setAccepted(false);
+
+ if (event->isBeginEvent()) {
+ ensureDeviceConnected(event->pointingDevice());
+ if (!deliverPressOrReleaseEvent(event))
+ event->setAccepted(false);
+ }
+ if (!allUpdatedPointsAccepted(event))
+ deliverUpdatedPoints(event);
+ if (event->isEndEvent())
+ deliverPressOrReleaseEvent(event, true);
+
+ // failsafe: never allow touch->mouse synthesis to persist after all touchpoints are released,
+ // or after the touchmouse is released
+ if (isTouchEvent(event) && touchMouseId >= 0) {
+ if (static_cast<QTouchEvent *>(event)->touchPointStates() == QEventPoint::State::Released) {
+ cancelTouchMouseSynthesis();
+ } else {
+ auto touchMousePoint = event->pointById(touchMouseId);
+ if (touchMousePoint && touchMousePoint->state() == QEventPoint::State::Released)
+ cancelTouchMouseSynthesis();
+ }
+ }
+
+ eventsInDelivery.pop();
+ if (sceneTransform) {
+ for (int i = 0; i < event->pointCount(); ++i)
+ QMutableEventPoint::setScenePosition(event->point(i), originalScenePositions.at(i));
+ }
+ --pointerEventRecursionGuard;
+ lastUngrabbed = nullptr;
+}
+
+/*! \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?
+QVector<QQuickItem *> QQuickDeliveryAgentPrivate::pointerTargets(QQuickItem *item, const QPointerEvent *event, const QEventPoint &point,
+ bool checkMouseButtons, bool checkAcceptsTouch) const
+{
+ Q_Q(const QQuickDeliveryAgent);
+ QVector<QQuickItem *> targets;
+ auto itemPrivate = QQuickItemPrivate::get(item);
+ QPointF itemPos = item->mapFromScene(point.scenePosition());
+ bool relevant = item->contains(itemPos);
+ 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 (!item->clipRect().contains(itemPos))
+ return targets;
+ }
+
+ if (itemPrivate->hasPointerHandlers()) {
+ if (!relevant)
+ if (itemPrivate->anyPointerHandlerWants(event, point))
+ relevant = true;
+ } else {
+ if (relevant && checkMouseButtons && item->acceptedMouseButtons() == Qt::NoButton)
+ relevant = false;
+ if (relevant && checkAcceptsTouch && !(item->acceptTouchEvents() || item->acceptedMouseButtons()))
+ relevant = false;
+ }
+
+ QList<QQuickItem *> children = itemPrivate->paintOrderChildItems();
+ if (relevant) {
+ auto it = std::lower_bound(children.begin(), children.end(), 0,
+ [](auto lhs, auto rhs) -> bool { return lhs->z() < rhs; });
+ children.insert(it, item);
+ }
+
+ for (int ii = children.size() - 1; ii >= 0; --ii) {
+ QQuickItem *child = children.at(ii);
+ auto childPrivate = QQuickItemPrivate::get(child);
+ if (!child->isVisible() || !child->isEnabled() || childPrivate->culled ||
+ (child != item && childPrivate->extra.isAllocated() && childPrivate->extra->subsceneDeliveryAgent))
+ continue;
+
+ if (child != item)
+ targets << pointerTargets(child, event, point, checkMouseButtons, checkAcceptsTouch);
+ else
+ targets << child;
+ }
+
+ return targets;
+}
+
+/*! \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;
+ // start at the end of list2
+ // if item not in list, append it
+ // if item found, move to next one, inserting before the last found one
+ int insertPosition = targets.size();
+ for (int i = list2.size() - 1; i >= 0; --i) {
+ int newInsertPosition = targets.lastIndexOf(list2.at(i), insertPosition);
+ if (newInsertPosition >= 0) {
+ Q_ASSERT(newInsertPosition <= insertPosition);
+ insertPosition = newInsertPosition;
+ }
+ // check for duplicates, only insert if the item isn't there already
+ if (insertPosition == targets.size() || list2.at(i) != targets.at(insertPosition))
+ targets.insert(insertPosition, list2.at(i));
+ }
+ return targets;
+}
+
+/*! \internal
+ Deliver updated points to existing grabbers.
+*/
+void QQuickDeliveryAgentPrivate::deliverUpdatedPoints(QPointerEvent *event)
+{
+ Q_Q(const QQuickDeliveryAgent);
+ bool done = false;
+ const auto grabbers = exclusiveGrabbers(event);
+ hasFiltered.clear();
+ for (auto grabber : grabbers) {
+ // The grabber is guaranteed to be either an item or a handler.
+ QQuickItem *receiver = qmlobject_cast<QQuickItem *>(grabber);
+ if (!receiver) {
+ // The grabber is not an item? It's a handler then. Let it have the event first.
+ QQuickPointerHandler *handler = static_cast<QQuickPointerHandler *>(grabber);
+ receiver = static_cast<QQuickPointerHandler *>(grabber)->parentItem();
+ // Filtering via QQuickItem::childMouseEventFilter() is only possible
+ // if the handler's parent is an Item. It could be a QQ3D object.
+ if (receiver) {
+ hasFiltered.clear();
+ if (sendFilteredPointerEvent(event, receiver))
+ done = true;
+ localizePointerEvent(event, receiver);
+ }
+ handler->handlePointerEvent(event);
+ }
+ if (done)
+ break;
+ // If the grabber is an item or the grabbing handler didn't handle it,
+ // then deliver the event to the item (which may have multiple handlers).
+ hasFiltered.clear();
+ if (receiver)
+ deliverMatchingPointsToItem(receiver, true, event);
+ }
+
+ // Deliver to each eventpoint's passive grabbers (but don't visit any handler more than once)
+ 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.size(); ++i) {
+ if (epd->passiveGrabbersContext.at(i).data() == q)
+ relevantPassiveGrabbers << epd->passiveGrabbers.at(i);
+ }
+ 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)
+ return;
+
+ // If some points weren't grabbed, deliver only to non-grabber PointerHandlers in reverse paint order
+ if (!allPointsGrabbed(event)) {
+ QVector<QQuickItem *> targetItems;
+ for (auto &point : event->points()) {
+ // Presses were delivered earlier; not the responsibility of deliverUpdatedTouchPoints.
+ // Don't find handlers for points that are already grabbed by an Item (such as Flickable).
+ if (point.state() == QEventPoint::Pressed || qmlobject_cast<QQuickItem *>(event->exclusiveGrabber(point)))
+ continue;
+ QVector<QQuickItem *> targetItemsForPoint = pointerTargets(rootItem, event, point, false, false);
+ if (targetItems.size()) {
+ targetItems = mergePointerTargets(targetItems, targetItemsForPoint);
+ } else {
+ targetItems = targetItemsForPoint;
+ }
+ }
+ for (QQuickItem *item : targetItems) {
+ if (grabbers.contains(item))
+ continue;
+ QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item);
+ localizePointerEvent(event, item);
+ itemPrivate->handlePointerEvent(event, true); // avoid re-delivering to grabbers
+ if (allPointsGrabbed(event))
+ break;
+ }
+ }
+}
+
+/*! \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;
+ const bool isTouch = isTouchEvent(event);
+ if (isTouch && event->isBeginEvent() && isDeliveringTouchAsMouse()) {
+ if (auto point = const_cast<QPointingDevicePrivate *>(QPointingDevicePrivate::get(touchMouseDevice))->queryPointById(touchMouseId)) {
+ // When a second point is pressed, if the first point's existing
+ // grabber was a pointer handler while a filtering parent is filtering
+ // the same first point _as mouse_: we're starting over with delivery,
+ // so we need to allow the second point to now be sent as a synth-mouse
+ // instead of the first one, so that filtering parents (maybe even the
+ // same one) can get a chance to see the second touchpoint as a
+ // synth-mouse and perhaps grab it. Ideally we would always do this
+ // when a new touchpoint is pressed, but this compromise fixes
+ // QTBUG-70998 and avoids breaking tst_FlickableInterop::touchDragSliderAndFlickable
+ if (qobject_cast<QQuickPointerHandler *>(event->exclusiveGrabber(point->eventPoint)))
+ cancelTouchMouseSynthesis();
+ } else {
+ qCWarning(lcTouchTarget) << "during delivery of touch press, synth-mouse ID" << Qt::hex << touchMouseId << "is missing from" << event;
+ }
+ }
+ for (int i = 0; i < event->pointCount(); ++i) {
+ auto &point = event->point(i);
+ QVector<QQuickItem *> targetItemsForPoint = pointerTargets(rootItem, event, point, !isTouch, isTouch);
+ if (targetItems.size()) {
+ targetItems = mergePointerTargets(targetItems, targetItemsForPoint);
+ } else {
+ targetItems = targetItemsForPoint;
+ }
+ }
+
+ 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;
+
+ hasFiltered.clear();
+ if (!handlersOnly && sendFilteredPointerEvent(event, item)) {
+ if (event->isAccepted())
+ return true;
+ skipDelivery.append(item);
+ }
+
+ // Do not deliverMatchingPointsTo any item for which the filtering parent already intercepted the event,
+ // nor to any item which already had a chance to filter.
+ if (skipDelivery.contains(item))
+ continue;
+
+ // sendFilteredPointerEvent() changed the QEventPoint::accepted() state,
+ // but per-point acceptance is opt-in during normal delivery to items.
+ for (int i = 0; i < event->pointCount(); ++i)
+ event->point(i).setAccepted(false);
+
+ deliverMatchingPointsToItem(item, false, event, handlersOnly);
+ if (event->allPointsAccepted())
+ handlersOnly = true;
+ }
+
+ 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);
+#if defined(Q_OS_ANDROID) && QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+ // QTBUG-85379
+ // In QT_VERSION below 6.0.0 touchEnabled for QtQuickItems is set by default to true
+ // It causes delivering touch events to Items which are not interested
+ // In some cases (like using Material Style in Android) it may cause a crash
+ if (itemPrivate->wasDeleted)
+ return;
+#endif
+ localizePointerEvent(pointerEvent, item);
+ bool isMouse = isMouseEvent(pointerEvent);
+
+ // Let the Item's handlers (if any) have the event first.
+ // However, double click should never be delivered to handlers.
+ if (pointerEvent->type() != QEvent::MouseButtonDblClick)
+ itemPrivate->handlePointerEvent(pointerEvent);
+
+ if (handlersOnly)
+ return;
+
+ // If all points are released and the item is not the grabber, it doesn't get the event.
+ // But if at least one point is still pressed, we might be in a potential gesture-takeover scenario.
+ if (pointerEvent->isEndEvent() && !pointerEvent->isUpdateEvent()
+ && !exclusiveGrabbers(pointerEvent).contains(item))
+ return;
+
+ // If any parent filters the event, we're done.
+ if (sendFilteredPointerEvent(pointerEvent, item))
+ 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)) {
+ // The only reason to already have a mouse grabber here is
+ // synthetic events - flickable sends one when setPressDelay is used.
+ auto oldMouseGrabber = pointerEvent->exclusiveGrabber(pointerEvent->point(0));
+ pointerEvent->accept();
+ if (isGrabber && sendFilteredPointerEvent(pointerEvent, item))
+ return;
+ localizePointerEvent(pointerEvent, item);
+ QCoreApplication::sendEvent(item, pointerEvent);
+ if (pointerEvent->isAccepted()) {
+ auto &point = pointerEvent->point(0);
+ auto mouseGrabber = pointerEvent->exclusiveGrabber(point);
+ if (mouseGrabber && mouseGrabber != item && mouseGrabber != oldMouseGrabber) {
+ // Normally we don't need item->mouseUngrabEvent() here, because QQuickDeliveryAgentPrivate::onGrabChanged does it.
+ // However, if one item accepted the mouse event, it expects to have the grab and be in "pressed" state,
+ // because accepting implies grabbing. But before it actually gets the grab, another item could steal it.
+ // In that case, onGrabChanged() does NOT notify the item that accepted the event that it's not getting the grab after all.
+ // So after ensuring that it's not redundant, we send a notification here, for that case (QTBUG-55325).
+ if (item != lastUngrabbed) {
+ item->mouseUngrabEvent();
+ lastUngrabbed = item;
+ }
+ } else if (item->isEnabled() && item->isVisible() && point.state() == QEventPoint::State::Pressed) {
+ pointerEvent->setExclusiveGrabber(point, item);
+ }
+ point.setAccepted(true);
+ }
+ return;
+ }
+ }
+
+ if (!isTouchEvent(pointerEvent))
+ return;
+
+ bool eventAccepted = false;
+ QMutableTouchEvent touchEvent;
+ itemPrivate->localizedTouchEvent(static_cast<QTouchEvent *>(pointerEvent), false, &touchEvent);
+ if (touchEvent.type() == QEvent::None)
+ return; // no points inside this item
+
+ if (item->acceptTouchEvents()) {
+ qCDebug(lcTouch) << "considering delivering" << &touchEvent << " to " << item;
+
+ // Deliver the touch event to the given item
+ qCDebug(lcTouch) << "actually delivering" << &touchEvent << " to " << item;
+ QCoreApplication::sendEvent(item, &touchEvent);
+ eventAccepted = touchEvent.isAccepted();
+ } else {
+ // If the touch event wasn't accepted, synthesize a mouse event and see if the item wants it.
+ if (Q_LIKELY(QCoreApplication::testAttribute(Qt::AA_SynthesizeMouseForUnhandledTouchEvents)) &&
+ !eventAccepted && (itemPrivate->acceptedMouseButtons() & Qt::LeftButton))
+ deliverTouchAsMouse(item, &touchEvent);
+ return;
+ }
+
+ Q_ASSERT(item->acceptTouchEvents()); // else we would've returned early above
+ if (eventAccepted) {
+ bool isPressOrRelease = pointerEvent->isBeginEvent() || pointerEvent->isEndEvent();
+ for (int i = 0; i < touchEvent.pointCount(); ++i) {
+ auto &point = touchEvent.point(i);
+ // legacy-style delivery: if the item doesn't reject the event, that means it handled ALL the points
+ point.setAccepted();
+ // but don't let the root of a subscene implicitly steal the grab from some other item (such as one of its children)
+ if (isPressOrRelease && !(itemPrivate->deliveryAgent() && pointerEvent->exclusiveGrabber(point)))
+ pointerEvent->setExclusiveGrabber(point, item);
+ }
+ } else {
+ // But if the event was not accepted then we know this item
+ // will not be interested in further updates for those touchpoint IDs either.
+ for (const auto &point: touchEvent.points()) {
+ if (point.state() == QEventPoint::State::Pressed) {
+ if (pointerEvent->exclusiveGrabber(point) == item) {
+ qCDebug(lcTouchTarget) << "TP" << Qt::hex << point.id() << "disassociated";
+ pointerEvent->setExclusiveGrabber(point, nullptr);
+ }
+ }
+ }
+ }
+}
+
+#if QT_CONFIG(quick_draganddrop)
+void QQuickDeliveryAgentPrivate::deliverDragEvent(QQuickDragGrabber *grabber, QEvent *event)
+{
+ QObject *formerTarget = grabber->target();
+ grabber->resetTarget();
+ QQuickDragGrabber::iterator grabItem = grabber->begin();
+ if (grabItem != grabber->end()) {
+ Q_ASSERT(event->type() != QEvent::DragEnter);
+ if (event->type() == QEvent::Drop) {
+ QDropEvent *e = static_cast<QDropEvent *>(event);
+ for (e->setAccepted(false); !e->isAccepted() && grabItem != grabber->end(); grabItem = grabber->release(grabItem)) {
+ QPointF p = (**grabItem)->mapFromScene(e->position().toPoint());
+ QDropEvent translatedEvent(
+ p.toPoint(),
+ e->possibleActions(),
+ e->mimeData(),
+ e->buttons(),
+ e->modifiers());
+ QQuickDropEventEx::copyActions(&translatedEvent, *e);
+ QCoreApplication::sendEvent(**grabItem, &translatedEvent);
+ e->setAccepted(translatedEvent.isAccepted());
+ e->setDropAction(translatedEvent.dropAction());
+ grabber->setTarget(**grabItem);
+ }
+ }
+ if (event->type() != QEvent::DragMove) { // Either an accepted drop or a leave.
+ QDragLeaveEvent leaveEvent;
+ for (; grabItem != grabber->end(); grabItem = grabber->release(grabItem))
+ QCoreApplication::sendEvent(**grabItem, &leaveEvent);
+ grabber->ignoreList().clear();
+ return;
+ } else {
+ QDragMoveEvent *moveEvent = static_cast<QDragMoveEvent *>(event);
+
+ // Used to ensure we don't send DragEnterEvents to current drop targets,
+ // and to detect which current drop targets we have left
+ QVarLengthArray<QQuickItem*, 64> currentGrabItems;
+ for (; grabItem != grabber->end(); grabItem = grabber->release(grabItem))
+ currentGrabItems.append(**grabItem);
+
+ // Look for any other potential drop targets that are higher than the current ones
+ QDragEnterEvent enterEvent(
+ moveEvent->position().toPoint(),
+ moveEvent->possibleActions(),
+ moveEvent->mimeData(),
+ moveEvent->buttons(),
+ moveEvent->modifiers());
+ QQuickDropEventEx::copyActions(&enterEvent, *moveEvent);
+ event->setAccepted(deliverDragEvent(grabber, rootItem, &enterEvent, &currentGrabItems,
+ formerTarget));
+
+ for (grabItem = grabber->begin(); grabItem != grabber->end(); ++grabItem) {
+ int i = currentGrabItems.indexOf(**grabItem);
+ if (i >= 0) {
+ currentGrabItems.remove(i);
+ // Still grabbed: send move event
+ QDragMoveEvent translatedEvent(
+ (**grabItem)->mapFromScene(moveEvent->position().toPoint()).toPoint(),
+ moveEvent->possibleActions(),
+ moveEvent->mimeData(),
+ moveEvent->buttons(),
+ moveEvent->modifiers());
+ QQuickDropEventEx::copyActions(&translatedEvent, *moveEvent);
+ QCoreApplication::sendEvent(**grabItem, &translatedEvent);
+ event->setAccepted(translatedEvent.isAccepted());
+ QQuickDropEventEx::copyActions(moveEvent, translatedEvent);
+ }
+ }
+
+ // Anything left in currentGrabItems is no longer a drop target and should be sent a DragLeaveEvent
+ QDragLeaveEvent leaveEvent;
+ for (QQuickItem *i : currentGrabItems)
+ QCoreApplication::sendEvent(i, &leaveEvent);
+
+ return;
+ }
+ }
+ if (event->type() == QEvent::DragEnter || event->type() == QEvent::DragMove) {
+ QDragMoveEvent *e = static_cast<QDragMoveEvent *>(event);
+ QDragEnterEvent enterEvent(
+ e->position().toPoint(),
+ e->possibleActions(),
+ e->mimeData(),
+ e->buttons(),
+ e->modifiers());
+ QQuickDropEventEx::copyActions(&enterEvent, *e);
+ event->setAccepted(deliverDragEvent(grabber, rootItem, &enterEvent));
+ } else {
+ grabber->ignoreList().clear();
+ }
+}
+
+bool QQuickDeliveryAgentPrivate::deliverDragEvent(
+ QQuickDragGrabber *grabber, QQuickItem *item, QDragMoveEvent *event,
+ QVarLengthArray<QQuickItem *, 64> *currentGrabItems, QObject *formerTarget)
+{
+ QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item);
+ if (!item->isVisible() || !item->isEnabled() || QQuickItemPrivate::get(item)->culled)
+ return false;
+ QPointF p = item->mapFromScene(event->position().toPoint());
+ bool itemContained = item->contains(p);
+
+ const int itemIndex = grabber->ignoreList().indexOf(item);
+ if (!itemContained) {
+ if (itemIndex >= 0)
+ grabber->ignoreList().remove(itemIndex);
+
+ if (itemPrivate->flags & QQuickItem::ItemClipsChildrenToShape)
+ return false;
+ }
+
+ QDragEnterEvent enterEvent(
+ event->position().toPoint(),
+ event->possibleActions(),
+ event->mimeData(),
+ event->buttons(),
+ event->modifiers());
+ QQuickDropEventEx::copyActions(&enterEvent, *event);
+ QList<QQuickItem *> children = itemPrivate->paintOrderChildItems();
+
+ // Check children in front of this item first
+ for (int ii = children.size() - 1; ii >= 0; --ii) {
+ if (children.at(ii)->z() < 0)
+ continue;
+ if (deliverDragEvent(grabber, children.at(ii), &enterEvent, currentGrabItems, formerTarget))
+ return true;
+ }
+
+ if (itemContained) {
+ // If this item is currently grabbed, don't send it another DragEnter,
+ // just grab it again if it's still contained.
+ if (currentGrabItems && currentGrabItems->contains(item)) {
+ grabber->grab(item);
+ grabber->setTarget(item);
+ return true;
+ }
+
+ if (event->type() == QEvent::DragMove || itemPrivate->flags & QQuickItem::ItemAcceptsDrops) {
+ if (event->type() == QEvent::DragEnter) {
+ if (formerTarget) {
+ QQuickItem *formerTargetItem = qobject_cast<QQuickItem *>(formerTarget);
+ if (formerTargetItem && currentGrabItems) {
+ QDragLeaveEvent leaveEvent;
+ QCoreApplication::sendEvent(formerTarget, &leaveEvent);
+
+ // Remove the item from the currentGrabItems so a leave event won't be generated
+ // later on
+ currentGrabItems->removeAll(formerTarget);
+ }
+ } else if (itemIndex >= 0) {
+ return false;
+ }
+ }
+
+ QDragMoveEvent translatedEvent(p.toPoint(), event->possibleActions(), event->mimeData(),
+ event->buttons(), event->modifiers(), event->type());
+ QQuickDropEventEx::copyActions(&translatedEvent, *event);
+ translatedEvent.setAccepted(event->isAccepted());
+ QCoreApplication::sendEvent(item, &translatedEvent);
+ event->setAccepted(translatedEvent.isAccepted());
+ event->setDropAction(translatedEvent.dropAction());
+ if (event->type() == QEvent::DragEnter) {
+ if (translatedEvent.isAccepted()) {
+ grabber->grab(item);
+ grabber->setTarget(item);
+ return true;
+ } else if (itemIndex < 0) {
+ grabber->ignoreList().append(item);
+ }
+ } else {
+ return true;
+ }
+ }
+ }
+
+ // Check children behind this item if this item or any higher children have not accepted
+ for (int ii = children.size() - 1; ii >= 0; --ii) {
+ if (children.at(ii)->z() >= 0)
+ continue;
+ if (deliverDragEvent(grabber, children.at(ii), &enterEvent, currentGrabItems, formerTarget))
+ return true;
+ }
+
+ return false;
+}
+#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)
+ return false;
+ if (!filteringParent)
+ return false;
+ bool filtered = false;
+ const bool hasHandlers = QQuickItemPrivate::get(receiver)->hasPointerHandlers();
+ if (filteringParent->filtersChildMouseEvents() && !hasFiltered.contains(filteringParent)) {
+ hasFiltered.append(filteringParent);
+ if (isMouseEvent(event)) {
+ if (receiver->acceptedMouseButtons()) {
+ const bool wasAccepted = event->allPointsAccepted();
+ Q_ASSERT(event->pointCount());
+ localizePointerEvent(event, receiver);
+ event->setAccepted(true);
+ auto oldMouseGrabber = event->exclusiveGrabber(event->point(0));
+ if (filteringParent->childMouseEventFilter(receiver, event)) {
+ qCDebug(lcMouse) << "mouse event intercepted by childMouseEventFilter of " << filteringParent;
+ skipDelivery.append(filteringParent);
+ filtered = true;
+ if (event->isAccepted() && event->isBeginEvent()) {
+ auto &point = event->point(0);
+ auto mouseGrabber = event->exclusiveGrabber(point);
+ if (mouseGrabber && mouseGrabber != receiver && mouseGrabber != oldMouseGrabber) {
+ receiver->mouseUngrabEvent();
+ } else {
+ event->setExclusiveGrabber(point, receiver);
+ }
+ }
+ } else {
+ // Restore accepted state if the event was not filtered.
+ event->setAccepted(wasAccepted);
+ }
+ }
+ } else if (isTouchEvent(event)) {
+ const bool acceptsTouchEvents = receiver->acceptTouchEvents() || hasHandlers;
+ auto device = event->device();
+ if (device->type() == QInputDevice::DeviceType::TouchPad &&
+ device->capabilities().testFlag(QInputDevice::Capability::MouseEmulation)) {
+ qCDebug(lcTouchTarget) << "skipping filtering of synth-mouse event from" << device;
+ } else if (acceptsTouchEvents || receiver->acceptedMouseButtons()) {
+ // get a touch event customized for delivery to filteringParent
+ // TODO should not be necessary? because QQuickDeliveryAgentPrivate::deliverMatchingPointsToItem() does it
+ QMutableTouchEvent filteringParentTouchEvent;
+ QQuickItemPrivate::get(receiver)->localizedTouchEvent(static_cast<QTouchEvent *>(event), true, &filteringParentTouchEvent);
+ if (filteringParentTouchEvent.type() != QEvent::None) {
+ qCDebug(lcTouch) << "letting parent" << filteringParent << "filter for" << 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);
+ 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);
+ }
+ }
+ } else if (Q_LIKELY(QCoreApplication::testAttribute(Qt::AA_SynthesizeMouseForUnhandledTouchEvents)) &&
+ !filteringParent->acceptTouchEvents()) {
+ qCDebug(lcTouch) << "touch event NOT intercepted by childMouseEventFilter of " << filteringParent
+ << "; accepts touch?" << filteringParent->acceptTouchEvents()
+ << "receiver accepts touch?" << acceptsTouchEvents
+ << "so, letting parent filter a synth-mouse event";
+ // filteringParent didn't filter the touch event. Give it a chance to filter a synthetic mouse event.
+ for (auto &tp : filteringParentTouchEvent.points()) {
+ QEvent::Type t;
+ switch (tp.state()) {
+ case QEventPoint::State::Pressed:
+ t = QEvent::MouseButtonPress;
+ break;
+ case QEventPoint::State::Released:
+ t = QEvent::MouseButtonRelease;
+ break;
+ case QEventPoint::State::Stationary:
+ continue;
+ default:
+ t = QEvent::MouseMove;
+ break;
+ }
+
+ bool touchMouseUnset = (touchMouseId == -1);
+ // Only deliver mouse event if it is the touchMouseId or it could become the touchMouseId
+ if (touchMouseUnset || touchMouseId == tp.id()) {
+ // convert filteringParentTouchEvent (which is already transformed wrt local position, velocity, etc.)
+ // into a synthetic mouse event, and let childMouseEventFilter() have another chance with that
+ QMutableSinglePointEvent mouseEvent;
+ touchToMouseEvent(t, tp, &filteringParentTouchEvent, &mouseEvent);
+ // If a filtering item calls QQuickWindow::mouseGrabberItem(), it should
+ // report the touchpoint's grabber. Whenever we send a synthetic mouse event,
+ // touchMouseId and touchMouseDevice must be set, even if it's only temporarily and isn't grabbed.
+ touchMouseId = tp.id();
+ touchMouseDevice = event->pointingDevice();
+ 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 (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
+ filteringParent->grabMouse();
+ }
+ }
+ if (touchMouseUnset)
+ // Now that we're done sending a synth mouse event, and it wasn't grabbed,
+ // the touchpoint is no longer acting as a synthetic mouse. Restore previous state.
+ cancelTouchMouseSynthesis();
+ mouseEvent.point(0).setAccepted(false); // because touchToMouseEvent() set it true
+ // Only one touchpoint can be treated as a synthetic mouse, so after childMouseEventFilter
+ // has been called once, we're done with this loop over the touchpoints.
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ 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)
+ return false;
+
+ QQuickItemPrivate *filteringParentPrivate = QQuickItemPrivate::get(filteringParent);
+ if (filteringParentPrivate->replayingPressEvent)
+ return false;
+
+ bool filtered = false;
+ if (filteringParentPrivate->filtersChildMouseEvents && !hasFiltered.contains(filteringParent)) {
+ hasFiltered.append(filteringParent);
+ if (filteringParent->childMouseEventFilter(receiver, event)) {
+ filtered = true;
+ skipDelivery.append(filteringParent);
+ }
+ qCDebug(lcMouseTarget) << "for" << receiver << filteringParent << "childMouseEventFilter ->" << filtered;
+ }
+
+ 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();
+ bool dragVelocityLimitAvailable = event->device()->capabilities().testFlag(QInputDevice::Capability::Velocity)
+ && styleHints->startDragVelocity();
+ bool overThreshold = qAbs(d) > (startDragThreshold >= 0 ? startDragThreshold : styleHints->startDragDistance());
+ if (dragVelocityLimitAvailable) {
+ QVector2D velocityVec = event->point(0).velocity();
+ qreal velocity = axis == Qt::XAxis ? velocityVec.x() : velocityVec.y();
+ overThreshold |= qAbs(velocity) > styleHints->startDragVelocity();
+ }
+ 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();
+ bool overThreshold = qAbs(d) > (startDragThreshold >= 0 ? startDragThreshold : styleHints->startDragDistance());
+ const bool dragVelocityLimitAvailable = (styleHints->startDragVelocity() > 0);
+ if (!overThreshold && dragVelocityLimitAvailable) {
+ qreal velocity = axis == Qt::XAxis ? tp.velocity().x() : tp.velocity().y();
+ overThreshold |= qAbs(velocity) > styleHints->startDragVelocity();
+ }
+ 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();
+ return qAbs(delta.x()) > threshold || qAbs(delta.y()) > threshold;
+}
+
+#ifndef QT_NO_DEBUG_STREAM
+QDebug operator<<(QDebug debug, const QQuickDeliveryAgent *da)
+{
+ QDebugStateSaver saver(debug);
+ debug.nospace();
+ if (!da) {
+ debug << "QQuickDeliveryAgent(0)";
+ return debug;
+ }
+
+ debug << "QQuickDeliveryAgent(";
+ if (!da->objectName().isEmpty())
+ debug << da->objectName() << ' ';
+ auto root = da->rootItem();
+ if (Q_LIKELY(root)) {
+ debug << "root=" << root->metaObject()->className();
+ if (!root->objectName().isEmpty())
+ debug << ' ' << root->objectName();
+ } else {
+ debug << "root=0";
+ }
+ debug << ')';
+ return debug;
+}
+#endif
+
+QT_END_NAMESPACE
+
+#include "moc_qquickdeliveryagent_p.cpp"