Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qquickdeliveryagent.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5#include <QtCore/qdebug.h>
6#include <QtGui/private/qevent_p.h>
7#include <QtGui/private/qeventpoint_p.h>
8#include <QtGui/private/qguiapplication_p.h>
9#include <QtGui/qpa/qplatformtheme.h>
10#include <QtQml/private/qabstractanimationjob_p.h>
11#include <QtQuick/private/qquickdeliveryagent_p_p.h>
12#include <QtQuick/private/qquickhoverhandler_p.h>
13#include <QtQuick/private/qquickpointerhandler_p_p.h>
14#if QT_CONFIG(quick_draganddrop)
15#include <QtQuick/private/qquickdrag_p.h>
16#endif
17#include <QtQuick/private/qquickitem_p.h>
18#include <QtQuick/private/qquickprofiler_p.h>
19#include <QtQuick/private/qquickrendercontrol_p.h>
20#include <QtQuick/private/qquickwindow_p.h>
21
22#include <QtCore/qpointer.h>
23#include <QtCore/qvarlengtharray.h>
24
25#include <algorithm>
26#include <memory>
27
29
30Q_LOGGING_CATEGORY(lcTouch, "qt.quick.touch")
31Q_STATIC_LOGGING_CATEGORY(lcTouchCmprs, "qt.quick.touch.compression")
32Q_LOGGING_CATEGORY(lcTouchTarget, "qt.quick.touch.target")
33Q_LOGGING_CATEGORY(lcMouse, "qt.quick.mouse")
34Q_STATIC_LOGGING_CATEGORY(lcMouseTarget, "qt.quick.mouse.target")
35Q_STATIC_LOGGING_CATEGORY(lcTablet, "qt.quick.tablet")
36Q_LOGGING_CATEGORY(lcPtr, "qt.quick.pointer")
37Q_STATIC_LOGGING_CATEGORY(lcPtrLoc, "qt.quick.pointer.localization")
38Q_STATIC_LOGGING_CATEGORY(lcWheelTarget, "qt.quick.wheel.target")
39Q_LOGGING_CATEGORY(lcHoverTrace, "qt.quick.hover.trace")
40Q_LOGGING_CATEGORY(lcHoverCursor, "qt.quick.hover.cursor")
41Q_LOGGING_CATEGORY(lcFocus, "qt.quick.focus")
42Q_STATIC_LOGGING_CATEGORY(lcContextMenu, "qt.quick.contextmenu")
43
44extern 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);
45
46bool QQuickDeliveryAgentPrivate::subsceneAgentsExist(false);
47QQuickDeliveryAgent *QQuickDeliveryAgentPrivate::currentEventDeliveryAgent(nullptr);
48
50{
51 static int allowRightClick = -1;
52 if (allowRightClick < 0) {
53 bool ok = false;
54 allowRightClick = qEnvironmentVariableIntValue("QT_QUICK_ALLOW_SYNTHETIC_RIGHT_CLICK", &ok);
55 if (!ok)
56 allowRightClick = 1; // user didn't opt out
57 }
58 return allowRightClick != 0;
59}
60
61static QQuickDeliveryAgentPrivate::HoverItems::iterator findHoverStateByItem(QQuickDeliveryAgentPrivate::HoverItems &hoverItems, QQuickItem *item)
62{
63 return std::find_if(hoverItems.begin(), hoverItems.end(),
64 [item](const QQuickDeliveryAgentPrivate::HoverItemState &hoverState) {
65 return hoverState.item == item;
66 });
67}
68
69static QQuickDeliveryAgentPrivate::HoverItems::const_iterator findHoverStateByItem(const QQuickDeliveryAgentPrivate::HoverItems &hoverItems, const QQuickItem *item)
70{
71 return std::find_if(hoverItems.cbegin(), hoverItems.cend(),
72 [item](const QQuickDeliveryAgentPrivate::HoverItemState &hoverState) {
73 return hoverState.item == item;
74 });
75}
76
77/*! \internal
78 Marks \a state as hovered, by a device that is a fingertip if \a fromTouch is \c true.
79
80 fromTouch is refreshed only if the entry is not hovered right now: an entry whose
81 hoverId is 0 is a leftover from a hover that has already ended (it stays in
82 hoverItems until deliverHoverEvent() prunes it), so hovering it again belongs to
83 whichever device is establishing hover now. As long as the entry is hovered it keeps
84 the flag it got when hover began, so that a finger tapping an item that a cursor
85 already hovers cannot turn the cursor's hover into touch hover - which
86 clearTouchHover() would then take away when the contact ends.
87*/
88static void markHovered(QQuickDeliveryAgentPrivate::HoverItemState &state, uint hoverId, bool fromTouch)
89{
90 if (state.hoverId == 0)
91 state.fromTouch = fromTouch;
92 state.hoverId = hoverId;
93}
94
95void QQuickDeliveryAgentPrivate::touchToMouseEvent(QEvent::Type type, const QEventPoint &p, const QTouchEvent *touchEvent, QMutableSinglePointEvent *mouseEvent)
96{
97 Q_ASSERT(QCoreApplication::testAttribute(Qt::AA_SynthesizeMouseForUnhandledTouchEvents));
98 QMutableSinglePointEvent ret(type, touchEvent->pointingDevice(), p,
99 (type == QEvent::MouseMove ? Qt::NoButton : Qt::LeftButton),
100 (type == QEvent::MouseButtonRelease ? Qt::NoButton : Qt::LeftButton),
101 touchEvent->modifiers(), Qt::MouseEventSynthesizedByQt);
102 ret.setAccepted(true); // this now causes the persistent touchpoint to be accepted too
103 ret.setTimestamp(touchEvent->timestamp());
104 *mouseEvent = ret;
105 // It's very important that the recipient of the event shall be able to see that
106 // this "mouse" event actually comes from a touch device.
107 Q_ASSERT(mouseEvent->device() == touchEvent->device());
108 if (Q_UNLIKELY(mouseEvent->device()->type() == QInputDevice::DeviceType::Mouse))
109 qWarning() << "Unexpected: synthesized an indistinguishable mouse event" << mouseEvent;
110}
111
112/*!
113 Returns \c false if the time constraint for detecting a double-click is violated.
114*/
115bool QQuickDeliveryAgentPrivate::isWithinDoubleClickInterval(ulong timeInterval)
116{
117 return timeInterval < static_cast<ulong>(QGuiApplication::styleHints()->mouseDoubleClickInterval());
118}
119
120/*!
121 Returns \c false if the spatial constraint for detecting a touchscreen double-tap is violated.
122*/
123bool QQuickDeliveryAgentPrivate::isWithinDoubleTapDistance(const QPoint &distanceBetweenPresses)
124{
125 auto square = [](qint64 v) { return v * v; };
126 return square(distanceBetweenPresses.x()) + square(distanceBetweenPresses.y()) <
127 square(QGuiApplication::styleHints()->touchDoubleTapDistance());
128}
129
130bool QQuickDeliveryAgentPrivate::checkIfDoubleTapped(ulong newPressEventTimestamp, const QPoint &newPressPos)
131{
132 const bool doubleClicked = isDeliveringTouchAsMouse() &&
133 isWithinDoubleTapDistance(newPressPos - touchMousePressPos) &&
134 isWithinDoubleClickInterval(newPressEventTimestamp - touchMousePressTimestamp);
135 if (doubleClicked) {
136 touchMousePressTimestamp = 0;
137 } else {
138 touchMousePressTimestamp = newPressEventTimestamp;
139 touchMousePressPos = newPressPos;
140 }
141 return doubleClicked;
142}
143
144void QQuickDeliveryAgentPrivate::resetIfDoubleTapPrevented(const QEventPoint &pressedPoint)
145{
146 if (touchMousePressTimestamp > 0 &&
147 (!isWithinDoubleTapDistance(pressedPoint.globalPosition().toPoint() - touchMousePressPos) ||
148 !isWithinDoubleClickInterval(pressedPoint.timestamp() - touchMousePressTimestamp))) {
149 touchMousePressTimestamp = 0;
150 touchMousePressPos = QPoint();
151 }
152}
153
154/*! \internal
155 \deprecated events are handled by methods in which the event is an argument.
156
157 Accessor for use by legacy methods such as QQuickItem::grabMouse(),
158 QQuickItem::ungrabMouse(), and QQuickItem::grabTouchPoints() which
159 are not given sufficient context to do the grabbing.
160 We should remove eventsInDelivery in Qt 7.
161*/
162QPointerEvent *QQuickDeliveryAgentPrivate::eventInDelivery() const
163{
164 if (eventsInDelivery.isEmpty())
165 return nullptr;
166 return eventsInDelivery.top();
167}
168
169/*! \internal
170 A helper function for the benefit of obsolete APIs like QQuickItem::grabMouse()
171 that don't have the currently-being-delivered event in context.
172 Returns the point that acts as the mouse on the device of the currently-being-delivered
173 event, or \nullptr if there is no such event or the device has no such point.
174*/
175QPointingDevicePrivate::EventPointData *QQuickDeliveryAgentPrivate::mousePointData()
176{
177 if (eventsInDelivery.isEmpty())
178 return nullptr;
179 auto devPriv = QPointingDevicePrivate::get(const_cast<QPointingDevice*>(eventsInDelivery.top()->pointingDevice()));
180 return devPriv->queryPointById(isDeliveringTouchAsMouse() ? touchMouseId : 0);
181}
182
183void QQuickDeliveryAgentPrivate::cancelTouchMouseSynthesis()
184{
185 qCDebug(lcTouchTarget) << "id" << touchMouseId << "on" << touchMouseDevice;
186 touchMouseId = -1;
187 touchMouseDevice = nullptr;
188}
189
190bool QQuickDeliveryAgentPrivate::deliverTouchAsMouse(QQuickItem *item, QTouchEvent *pointerEvent)
191{
192 Q_Q(QQuickDeliveryAgent);
193 Q_ASSERT(QCoreApplication::testAttribute(Qt::AA_SynthesizeMouseForUnhandledTouchEvents));
194 auto device = pointerEvent->pointingDevice();
195
196 // A touch event from a trackpad is likely to be followed by a mouse or gesture event, so mouse event synth is redundant
197 if (device->type() == QInputDevice::DeviceType::TouchPad && device->capabilities().testFlag(QInputDevice::Capability::MouseEmulation)) {
198 qCDebug(lcTouchTarget) << q << "skipping delivery of synth-mouse event from" << device;
199 return false;
200 }
201
202 // FIXME: make this work for mouse events too and get rid of the asTouchEvent in here.
203 QMutableTouchEvent event;
204 QQuickItemPrivate::get(item)->localizedTouchEvent(pointerEvent, false, &event);
205 if (!event.points().size())
206 return false;
207
208 // For each point, check if it is accepted, if not, try the next point.
209 // Any of the fingers can become the mouse one.
210 // This can happen because a mouse area might not accept an event at some point but another.
211 for (auto &p : event.points()) {
212 // A new touch point
213 if (touchMouseId == -1 && p.state() & QEventPoint::State::Pressed) {
214 QPointF pos = item->mapFromScene(p.scenePosition());
215
216 // probably redundant, we check bounds in the calling function (matchingNewPoints)
217 if (!item->contains(pos))
218 break;
219
220 qCDebug(lcTouchTarget) << q << device << "TP (mouse)" << Qt::hex << p.id() << "->" << item;
221 QMutableSinglePointEvent mousePress;
222 touchToMouseEvent(QEvent::MouseButtonPress, p, &event, &mousePress);
223
224 // Send a single press and see if that's accepted
225 QCoreApplication::sendEvent(item, &mousePress);
226 event.setAccepted(mousePress.isAccepted());
227 if (mousePress.isAccepted()) {
228 touchMouseDevice = device;
229 touchMouseId = p.id();
230 const auto &pt = mousePress.point(0);
231 if (!mousePress.exclusiveGrabber(pt))
232 mousePress.setExclusiveGrabber(pt, item);
233
234 if (checkIfDoubleTapped(event.timestamp(), p.globalPosition().toPoint())) {
235 // since we synth the mouse event from from touch, we respect the
236 // QPlatformTheme::TouchDoubleTapDistance instead of QPlatformTheme::MouseDoubleClickDistance
237 QMutableSinglePointEvent mouseDoubleClick;
238 touchToMouseEvent(QEvent::MouseButtonDblClick, p, &event, &mouseDoubleClick);
239 QCoreApplication::sendEvent(item, &mouseDoubleClick);
240 event.setAccepted(mouseDoubleClick.isAccepted());
241 if (!mouseDoubleClick.isAccepted())
242 cancelTouchMouseSynthesis();
243 }
244
245 return true;
246 }
247 // try the next point
248
249 // Touch point was there before and moved
250 } else if (touchMouseDevice == device && p.id() == touchMouseId) {
251 if (p.state() & QEventPoint::State::Updated) {
252 if (touchMousePressTimestamp != 0) {
253 if (!isWithinDoubleTapDistance(p.globalPosition().toPoint() - touchMousePressPos))
254 touchMousePressTimestamp = 0; // Got dragged too far, dismiss the double tap
255 }
256 if (QQuickItem *mouseGrabberItem = qmlobject_cast<QQuickItem *>(pointerEvent->exclusiveGrabber(p))) {
257 QMutableSinglePointEvent me;
258 touchToMouseEvent(QEvent::MouseMove, p, &event, &me);
259 QCoreApplication::sendEvent(item, &me);
260 event.setAccepted(me.isAccepted());
261 if (me.isAccepted())
262 qCDebug(lcTouchTarget) << q << device << "TP (mouse)" << Qt::hex << p.id() << "->" << mouseGrabberItem;
263 return event.isAccepted();
264 } else {
265 // no grabber, check if we care about mouse hover
266 // FIXME: this should only happen once, not recursively... I'll ignore it just ignore hover now.
267 // hover for touch???
268 QMutableSinglePointEvent me;
269 touchToMouseEvent(QEvent::MouseMove, p, &event, &me);
270 if (lastMousePosition.isNull())
271 lastMousePosition = me.scenePosition();
272 QPointF last = lastMousePosition;
273 lastMousePosition = me.scenePosition();
274
275 deliverHoverEvent(me.scenePosition(), last, me.modifiers(), me.timestamp());
276 break;
277 }
278 } else if (p.state() & QEventPoint::State::Released) {
279 // currently handled point was released
280 if (QQuickItem *mouseGrabberItem = qmlobject_cast<QQuickItem *>(pointerEvent->exclusiveGrabber(p))) {
281 QMutableSinglePointEvent me;
282 touchToMouseEvent(QEvent::MouseButtonRelease, p, &event, &me);
283 QCoreApplication::sendEvent(item, &me);
284
285 if (pointerEvent->exclusiveGrabber(p) == mouseGrabberItem) // might have ungrabbed due to event
286 pointerEvent->setExclusiveGrabber(p, nullptr);
287
288 cancelTouchMouseSynthesis();
289 return me.isAccepted();
290 }
291 }
292 break;
293 }
294 }
295 return false;
296}
297
298/*!
299 Ungrabs all touchpoint grabs and/or the mouse grab from the given item \a grabber.
300 This should not be called when processing a release event - that's redundant.
301 It is called in other cases, when the points may not be released, but the item
302 nevertheless must lose its grab due to becoming disabled, invisible, etc.
303 QPointerEvent::setExclusiveGrabber() calls touchUngrabEvent() when all points are released,
304 but if not all points are released, it cannot be sure whether to call touchUngrabEvent()
305 or not; so we have to do it here.
306*/
307void QQuickDeliveryAgentPrivate::removeGrabber(QQuickItem *grabber, bool mouse, bool touch, bool cancel)
308{
309 Q_Q(QQuickDeliveryAgent);
310 if (eventsInDelivery.isEmpty()) {
311 // do it the expensive way
312 for (auto dev : knownPointingDevices) {
313 auto devPriv = QPointingDevicePrivate::get(const_cast<QPointingDevice *>(dev));
314 devPriv->removeGrabber(grabber, cancel);
315 }
316 return;
317 }
318 auto eventInDelivery = eventsInDelivery.top();
319 if (Q_LIKELY(mouse) && eventInDelivery) {
320 auto epd = mousePointData();
321 if (epd && epd->exclusiveGrabber == grabber && epd->exclusiveGrabberContext.data() == q) {
322 QQuickItem *oldGrabber = qobject_cast<QQuickItem *>(epd->exclusiveGrabber);
323 qCDebug(lcMouseTarget) << "removeGrabber" << oldGrabber << "-> null";
324 eventInDelivery->setExclusiveGrabber(epd->eventPoint, nullptr);
325 }
326 }
327 if (Q_LIKELY(touch)) {
328 bool ungrab = false;
329 const auto touchDevices = QPointingDevice::devices();
330 for (auto device : touchDevices) {
331 if (device->type() != QInputDevice::DeviceType::TouchScreen)
332 continue;
333 if (QPointingDevicePrivate::get(const_cast<QPointingDevice *>(static_cast<const QPointingDevice *>(device)))->
334 removeExclusiveGrabber(eventInDelivery, grabber))
335 ungrab = true;
336 }
337 if (ungrab)
338 grabber->touchUngrabEvent();
339 }
340}
341
342/*!
343 \internal
344
345 Clears all exclusive and passive grabs for the points in \a pointerEvent.
346
347 We never allow any kind of grab to persist after release, unless we're waiting
348 for a synth event from QtGui (as with most tablet events), so for points that
349 are fully released, the grab is cleared.
350
351 Called when QQuickWindow::event dispatches events, or when the QQuickOverlay
352 has filtered an event so that it bypasses normal delivery.
353*/
354void QQuickDeliveryAgentPrivate::clearGrabbers(QPointerEvent *pointerEvent)
355{
356 if (pointerEvent->isEndEvent()
357 && !(isTabletEvent(pointerEvent)
358 && (qApp->testAttribute(Qt::AA_SynthesizeMouseForUnhandledTabletEvents)
359 || QWindowSystemInterfacePrivate::TabletEvent::platformSynthesizesMouse))) {
360 if (pointerEvent->isSinglePointEvent()) {
361 if (static_cast<QSinglePointEvent *>(pointerEvent)->buttons() == Qt::NoButton) {
362 auto &firstPt = pointerEvent->point(0);
363 pointerEvent->setExclusiveGrabber(firstPt, nullptr);
364 pointerEvent->clearPassiveGrabbers(firstPt);
365 }
366 } else {
367 for (auto &point : pointerEvent->points()) {
368 if (point.state() == QEventPoint::State::Released) {
369 pointerEvent->setExclusiveGrabber(point, nullptr);
370 pointerEvent->clearPassiveGrabbers(point);
371 }
372 }
373 }
374 }
375}
376
377/*! \internal
378 Translates QEventPoint::scenePosition() in \a touchEvent to this window.
379
380 The item-local QEventPoint::position() is updated later, not here.
381*/
382void QQuickDeliveryAgentPrivate::translateTouchEvent(QTouchEvent *touchEvent)
383{
384 for (qsizetype i = 0; i != touchEvent->pointCount(); ++i) {
385 auto &pt = touchEvent->point(i);
386 QMutableEventPoint::setScenePosition(pt, pt.position());
387 }
388}
389
390
391static inline bool windowHasFocus(QQuickWindow *win)
392{
393 const QWindow *focusWindow = QGuiApplication::focusWindow();
394 if (!focusWindow || win == focusWindow)
395 return true;
396 return QQuickRenderControlPrivate::isFocusWindowFor(win, focusWindow);
397}
398
400{
401 QQuickItem *parentItem = item->parentItem();
402
403 if (parentItem && parentItem->flags() & QQuickItem::ItemIsFocusScope)
404 return findFurthestFocusScopeAncestor(parentItem);
405
406 return item;
407}
408
409#ifdef Q_OS_WEBOS
410// Temporary fix for webOS until multi-seat is implemented see QTBUG-85272
411static inline bool singleWindowOnScreen(QQuickWindow *win)
412{
413 const QWindowList windowList = QGuiApplication::allWindows();
414 for (int i = 0; i < windowList.count(); i++) {
415 QWindow *ii = windowList.at(i);
416 if (ii == win)
417 continue;
418 if (ii->screen() == win->screen())
419 return false;
420 }
421
422 return true;
423}
424#endif
425
426/*!
427 Set the focus inside \a scope to be \a item.
428 If the scope contains the active focus item, it will be changed to \a item.
429 Calls notifyFocusChangesRecur for all changed items.
430*/
431void QQuickDeliveryAgentPrivate::setFocusInScope(QQuickItem *scope, QQuickItem *item,
432 Qt::FocusReason reason, FocusOptions options)
433{
434 Q_Q(QQuickDeliveryAgent);
435 Q_ASSERT(item);
436 Q_ASSERT(scope || item == rootItem);
437
438 qCDebug(lcFocus) << q << "focus" << item << "in scope" << scope;
439 if (scope)
440 qCDebug(lcFocus) << " scopeSubFocusItem:" << QQuickItemPrivate::get(scope)->subFocusItem;
441
442 QQuickItemPrivate *scopePrivate = scope ? QQuickItemPrivate::get(scope) : nullptr;
443 QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item);
444
445 QQuickItem *oldActiveFocusItem = nullptr;
446 QQuickItem *currentActiveFocusItem = activeFocusItem;
447 QQuickItem *newActiveFocusItem = nullptr;
448 bool sendFocusIn = false;
449
450 lastFocusReason = reason;
451
452 QVarLengthArray<QQuickItem *, 20> changed;
453
454 // Does this change the active focus?
455 if (item == rootItem || scopePrivate->activeFocus) {
456 oldActiveFocusItem = activeFocusItem;
457 if (item->isEnabled()) {
458 newActiveFocusItem = item;
459 while (newActiveFocusItem->isFocusScope()
460 && newActiveFocusItem->scopedFocusItem()
461 && newActiveFocusItem->scopedFocusItem()->isEnabled()) {
462 newActiveFocusItem = newActiveFocusItem->scopedFocusItem();
463 }
464 } else {
465 newActiveFocusItem = scope;
466 }
467
468 if (oldActiveFocusItem) {
469#if QT_CONFIG(im)
470 QGuiApplication::inputMethod()->commit();
471#endif
472
473 activeFocusItem = nullptr;
474
475 QQuickItem *afi = oldActiveFocusItem;
476 while (afi && afi != scope) {
477 if (QQuickItemPrivate::get(afi)->activeFocus) {
478 QQuickItemPrivate::get(afi)->activeFocus = false;
479 changed << afi;
480 }
481 afi = afi->parentItem();
482 }
483 }
484 }
485
486 if (item != rootItem && !(options & DontChangeSubFocusItem)) {
487 QQuickItem *oldSubFocusItem = scopePrivate->subFocusItem;
488 if (oldSubFocusItem) {
489 QQuickItemPrivate *priv = QQuickItemPrivate::get(oldSubFocusItem);
490 priv->focus = false;
491 priv->notifyChangeListeners(QQuickItemPrivate::Focus, &QQuickItemChangeListener::itemFocusChanged, oldSubFocusItem, reason);
492 changed << oldSubFocusItem;
493 }
494
495 QQuickItemPrivate::get(item)->updateSubFocusItem(scope, true);
496 }
497
498 if (!(options & DontChangeFocusProperty)) {
499 if (item != rootItem || windowHasFocus(rootItem->window())
500#ifdef Q_OS_WEBOS
501 // Allow focused if there is only one window in the screen where it belongs.
502 // Temporary fix for webOS until multi-seat is implemented see QTBUG-85272
503 || singleWindowOnScreen(rootItem->window())
504#endif
505 ) {
506 itemPrivate->focus = true;
507 itemPrivate->notifyChangeListeners(QQuickItemPrivate::Focus, &QQuickItemChangeListener::itemFocusChanged, item, reason);
508 changed << item;
509 }
510 }
511
512 if (newActiveFocusItem && (rootItem->hasFocus() || (rootItem->window()->type() == Qt::Popup))) {
513 activeFocusItem = newActiveFocusItem;
514
515 QQuickItemPrivate::get(newActiveFocusItem)->activeFocus = true;
516 changed << newActiveFocusItem;
517
518 QQuickItem *afi = newActiveFocusItem->parentItem();
519 while (afi && afi != scope) {
520 if (afi->isFocusScope()) {
521 QQuickItemPrivate::get(afi)->activeFocus = true;
522 changed << afi;
523 }
524 afi = afi->parentItem();
525 }
526 updateFocusItemTransform();
527 sendFocusIn = true;
528 }
529
530 // Now that all the state is changed, emit signals & events
531 // We must do this last, as this process may result in further changes to focus.
532 if (oldActiveFocusItem) {
533 QFocusEvent event(QEvent::FocusOut, reason);
534 QCoreApplication::sendEvent(oldActiveFocusItem, &event);
535 }
536
537 // Make sure that the FocusOut didn't result in another focus change.
538 if (sendFocusIn && activeFocusItem == newActiveFocusItem) {
539 QFocusEvent event(QEvent::FocusIn, reason);
540 QCoreApplication::sendEvent(newActiveFocusItem, &event);
541 }
542
543 if (activeFocusItem != currentActiveFocusItem)
544 emit rootItem->window()->focusObjectChanged(activeFocusItem);
545
546 if (!changed.isEmpty())
547 notifyFocusChangesRecur(changed.data(), changed.size() - 1, reason);
548 if (isSubsceneAgent) {
549 auto da = QQuickWindowPrivate::get(rootItem->window())->deliveryAgent;
550 qCDebug(lcFocus) << " delegating setFocusInScope to" << da;
551
552 // When setting subFocusItem, hierarchy is important. Each focus ancestor's
553 // subFocusItem must be its nearest descendant with focus. Changing the rootItem's
554 // subFocusItem to 'item' here would make 'item' the subFocusItem of all ancestor
555 // focus scopes up until root item.
556 // That is why we should avoid altering subFocusItem until having traversed
557 // all the focus hierarchy.
558 QQuickItem *ancestorFS = findFurthestFocusScopeAncestor(item);
559 if (ancestorFS != item)
560 options |= QQuickDeliveryAgentPrivate::DontChangeSubFocusItem;
561 QQuickWindowPrivate::get(rootItem->window())->deliveryAgentPrivate()->setFocusInScope(da->rootItem(), item, reason, options);
562 }
563 if (oldActiveFocusItem == activeFocusItem)
564 qCDebug(lcFocus) << " activeFocusItem remains" << activeFocusItem << "in" << q;
565 else
566 qCDebug(lcFocus) << " activeFocusItem" << oldActiveFocusItem << "->" << activeFocusItem << "in" << q;
567}
568
569void QQuickDeliveryAgentPrivate::clearFocusInScope(QQuickItem *scope, QQuickItem *item, Qt::FocusReason reason, FocusOptions options)
570{
571 Q_ASSERT(item);
572 Q_ASSERT(scope || item == rootItem);
573 Q_Q(QQuickDeliveryAgent);
574 qCDebug(lcFocus) << q << "clear focus" << item << "in scope" << scope;
575
576 QQuickItemPrivate *scopePrivate = nullptr;
577 if (scope) {
578 scopePrivate = QQuickItemPrivate::get(scope);
579 if ( !scopePrivate->subFocusItem )
580 return; // No focus, nothing to do.
581 }
582
583 QQuickItem *currentActiveFocusItem = activeFocusItem;
584 QQuickItem *oldActiveFocusItem = nullptr;
585 QQuickItem *newActiveFocusItem = nullptr;
586
587 lastFocusReason = reason;
588
589 QVarLengthArray<QQuickItem *, 20> changed;
590
591 Q_ASSERT(item == rootItem || item == scopePrivate->subFocusItem);
592
593 // Does this change the active focus?
594 if (item == rootItem || scopePrivate->activeFocus) {
595 oldActiveFocusItem = activeFocusItem;
596 newActiveFocusItem = scope;
597
598#if QT_CONFIG(im)
599 QGuiApplication::inputMethod()->commit();
600#endif
601
602 activeFocusItem = nullptr;
603
604 if (oldActiveFocusItem) {
605 QQuickItem *afi = oldActiveFocusItem;
606 while (afi && afi != scope) {
607 if (QQuickItemPrivate::get(afi)->activeFocus) {
608 QQuickItemPrivate::get(afi)->activeFocus = false;
609 changed << afi;
610 }
611 afi = afi->parentItem();
612 }
613 }
614 }
615
616 if (item != rootItem && !(options & DontChangeSubFocusItem)) {
617 QQuickItem *oldSubFocusItem = scopePrivate->subFocusItem;
618 if (oldSubFocusItem && !(options & DontChangeFocusProperty)) {
619 QQuickItemPrivate *priv = QQuickItemPrivate::get(oldSubFocusItem);
620 priv->focus = false;
621 priv->notifyChangeListeners(QQuickItemPrivate::Focus, &QQuickItemChangeListener::itemFocusChanged, oldSubFocusItem, reason);
622 changed << oldSubFocusItem;
623 }
624
625 QQuickItemPrivate::get(item)->updateSubFocusItem(scope, false);
626
627 } else if (!(options & DontChangeFocusProperty)) {
628 QQuickItemPrivate *priv = QQuickItemPrivate::get(item);
629 priv->focus = false;
630 priv->notifyChangeListeners(QQuickItemPrivate::Focus, &QQuickItemChangeListener::itemFocusChanged, item, reason);
631 changed << item;
632 }
633
634 if (newActiveFocusItem) {
635 Q_ASSERT(newActiveFocusItem == scope);
636 activeFocusItem = scope;
637 updateFocusItemTransform();
638 }
639
640 // Now that all the state is changed, emit signals & events
641 // We must do this last, as this process may result in further changes to focus.
642 if (oldActiveFocusItem) {
643 QFocusEvent event(QEvent::FocusOut, reason);
644 QCoreApplication::sendEvent(oldActiveFocusItem, &event);
645 }
646
647 // Make sure that the FocusOut didn't result in another focus change.
648 if (newActiveFocusItem && activeFocusItem == newActiveFocusItem) {
649 QFocusEvent event(QEvent::FocusIn, reason);
650 QCoreApplication::sendEvent(newActiveFocusItem, &event);
651 }
652
653 QQuickWindow *rootItemWindow = rootItem->window();
654 if (activeFocusItem != currentActiveFocusItem && rootItemWindow)
655 emit rootItemWindow->focusObjectChanged(activeFocusItem);
656
657 if (!changed.isEmpty())
658 notifyFocusChangesRecur(changed.data(), changed.size() - 1, reason);
659 if (isSubsceneAgent && rootItemWindow) {
660 auto da = QQuickWindowPrivate::get(rootItemWindow)->deliveryAgent;
661 qCDebug(lcFocus) << " delegating clearFocusInScope to" << da;
662 QQuickWindowPrivate::get(rootItemWindow)->deliveryAgentPrivate()->clearFocusInScope(da->rootItem(), item, reason, options);
663 }
664 if (oldActiveFocusItem == activeFocusItem)
665 qCDebug(lcFocus) << "activeFocusItem remains" << activeFocusItem << "in" << q;
666 else
667 qCDebug(lcFocus) << " activeFocusItem" << oldActiveFocusItem << "->" << activeFocusItem << "in" << q;
668}
669
670void QQuickDeliveryAgentPrivate::clearFocusObject()
671{
672 if (activeFocusItem == rootItem)
673 return;
674
675 clearFocusInScope(rootItem, QQuickItemPrivate::get(rootItem)->subFocusItem, Qt::OtherFocusReason);
676}
677
678void QQuickDeliveryAgentPrivate::notifyFocusChangesRecur(QQuickItem **items, int remaining, Qt::FocusReason reason)
679{
680 QPointer<QQuickItem> item(*items);
681
682 if (item) {
683 QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item);
684
685 if (itemPrivate->notifiedFocus != itemPrivate->focus) {
686 itemPrivate->notifiedFocus = itemPrivate->focus;
687 itemPrivate->notifyChangeListeners(QQuickItemPrivate::Focus, &QQuickItemChangeListener::itemFocusChanged, item, reason);
688 emit item->focusChanged(itemPrivate->focus);
689 }
690
691 if (item && itemPrivate->notifiedActiveFocus != itemPrivate->activeFocus) {
692 itemPrivate->notifiedActiveFocus = itemPrivate->activeFocus;
693 itemPrivate->itemChange(QQuickItem::ItemActiveFocusHasChanged, bool(itemPrivate->activeFocus));
694 itemPrivate->notifyChangeListeners(QQuickItemPrivate::Focus, &QQuickItemChangeListener::itemFocusChanged, item, reason);
695 emit item->activeFocusChanged(itemPrivate->activeFocus);
696 }
697 }
698
699 if (remaining)
700 notifyFocusChangesRecur(items + 1, remaining - 1, reason);
701}
702
703bool QQuickDeliveryAgentPrivate::clearHover(ulong timestamp)
704{
705 if (hoverItems.isEmpty())
706 return false;
707
708 QQuickWindow *window = rootItem->window();
709 if (!window)
710 return false;
711
712 const auto globalPos = QGuiApplicationPrivate::lastCursorPosition;
713 const QPointF lastPos = window->mapFromGlobal(globalPos);
714 const auto modifiers = QGuiApplication::keyboardModifiers();
715
716 // while we don't modify hoveritems directly in the loop, the delivery of the event
717 // is expected to reset the stored ID for each cleared item, and items might also
718 // be removed from the list in response to event delivery.
719 // So we don't want to iterate over a const version of hoverItems here (it would be
720 // misleading), but still use const_iterators to avoid premature detach and constant
721 // ref-count-checks.
722 for (auto it = hoverItems.cbegin(); it != hoverItems.cend(); ++it) {
723 if (QQuickItem *item = it->item) {
724 deliverHoverEventToItem(item, item->mapFromScene(lastPos), lastPos, lastPos,
725 globalPos, modifiers, timestamp, HoverChange::Clear);
726 Q_ASSERT(([this, item]{
727 const auto it2 = findHoverStateByItem(std::as_const(hoverItems), item);
728 return it2 == hoverItems.cend() || it2->hoverId == 0;
729 }()));
730 }
731 }
732
733 return true;
734}
735
736/*! \internal
737 Returns \c true if \a item is currently hovered by a device that hovers (mouse,
738 or stylus in proximity) rather than only because a fingertip is touching it.
739
740 An item that sets its own hover state outside deliverHoverEvent()'s bookkeeping
741 (see QQuickMouseArea::mousePressEvent()) needs this to tell whether lifting a
742 finger should end its hover, or whether a cursor is still resting on it.
743*/
744bool QQuickDeliveryAgentPrivate::isHoveredByHoveringDevice(const QQuickItem *item) const
745{
746 const auto it = findHoverStateByItem(hoverItems, item);
747 if (it != hoverItems.cend() && it->hoverId != 0 && !it->fromTouch)
748 return true;
749
750 // deliverHoverEvent() erases an item's entry as soon as the finger moves off,
751 // so a cursor-established entry can be replaced by a touch-established hover
752 // while the cursor has not moved at all. Fall back to where the cursor is.
753 // On a platform with no cursor, lastCursorPosition is never written; this returns false.
754 if (!item->window())
755 return false;
756 const QPointF cursorScenePos =
757 item->window()->mapFromGlobal(QPointF(QGuiApplicationPrivate::lastCursorPosition));
758 return item->contains(item->mapFromScene(cursorScenePos));
759}
760
761/*! \internal
762 Clears hover on those items whose hover was established by a fingertip touching
763 them, and leaves alone any item that a hovering device - a mouse cursor, or a
764 stylus in proximity - is hovering.
765
766 Called when the last finger is lifted: a fingertip has no hover state that
767 outlives contact, so its hover has to end with the contact (QTBUG-62912). But a
768 cursor resting on an item goes on hovering it regardless of what the fingers do,
769 which is why this is not simply clearHover().
770*/
771void QQuickDeliveryAgentPrivate::clearTouchHover(ulong timestamp)
772{
773 if (hoverItems.isEmpty())
774 return;
775
776 QQuickWindow *window = rootItem->window();
777 if (!window)
778 return;
779
780 const auto globalPos = QGuiApplicationPrivate::lastCursorPosition;
781 const QPointF lastPos = window->mapFromGlobal(globalPos);
782 const auto modifiers = QGuiApplication::keyboardModifiers();
783
784 // Take a copy of the items to clear before delivering anything: delivery can both
785 // reset the stored ids and remove entries from hoverItems (see clearHover()).
786 QVarLengthArray<QPointer<QQuickItem>, 4> toClear;
787 for (auto it = hoverItems.cbegin(); it != hoverItems.cend(); ++it) {
788 if (it->item && it->fromTouch && !isHoveredByHoveringDevice(it->item))
789 toClear.append(it->item);
790 }
791
792 for (const auto &item : toClear) {
793 if (!item)
794 continue;
795 // The position is only carried in the QHoverEvent for the benefit of anything
796 // that reads it in hoverLeaveEvent(); HoverChange::Clear un-hovers regardless of
797 // whether the position is inside the item, which matters because on a
798 // touch-only platform lastCursorPosition was never set by any real cursor.
799 deliverHoverEventToItem(item, item->mapFromScene(lastPos), lastPos, lastPos,
800 globalPos, modifiers, timestamp, HoverChange::Clear);
801 }
802}
803
804void QQuickDeliveryAgentPrivate::updateFocusItemTransform()
805{
806#if QT_CONFIG(im)
807 if (activeFocusItem && QGuiApplication::focusObject() == activeFocusItem) {
808 QQuickItemPrivate *focusPrivate = QQuickItemPrivate::get(activeFocusItem);
809 QGuiApplication::inputMethod()->setInputItemTransform(focusPrivate->itemToWindowTransform());
810 QGuiApplication::inputMethod()->setInputItemRectangle(QRectF(0, 0, focusPrivate->width, focusPrivate->height));
811 activeFocusItem->updateInputMethod(Qt::ImInputItemClipRectangle);
812 }
813#endif
814}
815
816/*!
817 Returns the item that should get active focus when the
818 root focus scope gets active focus.
819*/
820QQuickItem *QQuickDeliveryAgentPrivate::focusTargetItem() const
821{
822 if (activeFocusItem)
823 return activeFocusItem;
824
825 Q_ASSERT(rootItem);
826 QQuickItem *targetItem = rootItem;
827
828 while (targetItem->isFocusScope()
829 && targetItem->scopedFocusItem()
830 && targetItem->scopedFocusItem()->isEnabled()) {
831 targetItem = targetItem->scopedFocusItem();
832 }
833
834 return targetItem;
835}
836
837/*! \internal
838 If called during event delivery, returns the agent that is delivering the
839 event, without checking whether \a item is reachable from there.
840 Otherwise returns QQuickItemPrivate::deliveryAgent() (the delivery agent for
841 the narrowest subscene containing \a item), or \c null if \a item is \c null.
842*/
843QQuickDeliveryAgent *QQuickDeliveryAgentPrivate::currentOrItemDeliveryAgent(const QQuickItem *item)
844{
845 if (currentEventDeliveryAgent)
846 return currentEventDeliveryAgent;
847 if (item)
848 return QQuickItemPrivate::get(const_cast<QQuickItem *>(item))->deliveryAgent();
849 return nullptr;
850}
851
852/*! \internal
853 QQuickDeliveryAgent delivers events to a tree of Qt Quick Items, beginning
854 with the given root item, which is usually QQuickWindow::rootItem() but
855 may alternatively be embedded into a Qt Quick 3D scene or something else.
856*/
857QQuickDeliveryAgent::QQuickDeliveryAgent(QQuickItem *rootItem)
858 : QObject(*new QQuickDeliveryAgentPrivate(rootItem), rootItem)
859{
860}
861
862QQuickDeliveryAgent::~QQuickDeliveryAgent()
863{
864}
865
866QQuickDeliveryAgent::Transform::~Transform()
867{
868}
869
870/*! \internal
871 Get the QQuickRootItem or subscene root item on behalf of which
872 this delivery agent was constructed to handle events.
873*/
874QQuickItem *QQuickDeliveryAgent::rootItem() const
875{
876 Q_D(const QQuickDeliveryAgent);
877 return d->rootItem;
878}
879
880/*! \internal
881 Returns the object that was set in setSceneTransform(): a functor that
882 transforms from scene coordinates in the parent scene to scene coordinates
883 within this DA's subscene, or \c null if none was set.
884*/
885QQuickDeliveryAgent::Transform *QQuickDeliveryAgent::sceneTransform() const
886{
887 Q_D(const QQuickDeliveryAgent);
888 return d->sceneTransform;
889}
890
891/*! \internal
892 QQuickDeliveryAgent takes ownership of the given \a transform, which
893 encapsulates the ability to transform parent scene coordinates to rootItem
894 (subscene) coordinates.
895*/
896void QQuickDeliveryAgent::setSceneTransform(QQuickDeliveryAgent::Transform *transform)
897{
898 Q_D(QQuickDeliveryAgent);
899 if (d->sceneTransform == transform)
900 return;
901 qCDebug(lcPtr) << this << d->sceneTransform << "->" << transform;
902 if (d->sceneTransform)
903 delete d->sceneTransform;
904 d->sceneTransform = transform;
905}
906
907/*!
908 Handle \a ev on behalf of this delivery agent's window or subscene.
909
910 This is the usual main entry point for every incoming event:
911 QQuickWindow::event() and QQuick3DViewport::forwardEventToSubscenes()
912 both call this function.
913*/
914bool QQuickDeliveryAgent::event(QEvent *ev)
915{
916 Q_D(QQuickDeliveryAgent);
917 d->currentEventDeliveryAgent = this;
918 auto cleanup = qScopeGuard([d] { d->currentEventDeliveryAgent = nullptr; });
919
920 switch (ev->type()) {
921 case QEvent::MouseButtonPress:
922 case QEvent::MouseButtonRelease:
923 case QEvent::MouseButtonDblClick:
924 case QEvent::MouseMove: {
925 QMouseEvent *me = static_cast<QMouseEvent*>(ev);
926 d->handleMouseEvent(me);
927 break;
928 }
929 case QEvent::HoverEnter:
930 case QEvent::HoverLeave:
931 case QEvent::HoverMove: {
932 QHoverEvent *he = static_cast<QHoverEvent*>(ev);
933 // A QHoverEvent comes from a device that hovers, so any hover established below
934 // belongs to it and not to a fingertip.
935 d->lastMousePositionFromTouch = false;
936 bool accepted = d->deliverHoverEvent(he->scenePosition(),
937 he->points().first().sceneLastPosition(),
938 he->modifiers(), he->timestamp());
939 d->lastMousePosition = he->scenePosition();
940 he->setAccepted(accepted);
941#if QT_CONFIG(cursor)
942 QQuickWindowPrivate::get(d->rootItem->window())->updateCursor(d->sceneTransform ?
943 d->sceneTransform->map(he->scenePosition()) : he->scenePosition(), d->rootItem);
944#endif
945 return accepted;
946 }
947 case QEvent::TouchBegin:
948 case QEvent::TouchUpdate:
949 case QEvent::TouchEnd: {
950 QTouchEvent *touch = static_cast<QTouchEvent*>(ev);
951 d->handleTouchEvent(touch);
952 if (Q_LIKELY(QCoreApplication::testAttribute(Qt::AA_SynthesizeMouseForUnhandledTouchEvents))) {
953 // we consume all touch events ourselves to avoid duplicate
954 // mouse delivery by QtGui mouse synthesis
955 ev->accept();
956 }
957 break;
958 }
959 case QEvent::TouchCancel:
960 // return in order to avoid the QWindow::event below
961 return d->deliverTouchCancelEvent(static_cast<QTouchEvent*>(ev));
962 break;
963 case QEvent::Enter: {
964 if (!d->rootItem)
965 return false;
966 QEnterEvent *enter = static_cast<QEnterEvent*>(ev);
967 const auto scenePos = enter->scenePosition();
968 d->lastMousePositionFromTouch = false;
969 bool accepted = d->deliverHoverEvent(scenePos,
970 enter->points().first().sceneLastPosition(),
971 enter->modifiers(), enter->timestamp());
972 d->lastMousePosition = scenePos;
973 // deliverHoverEvent() constructs QHoverEvents: check that EPD didn't end up with corrupted scenePos
974 Q_ASSERT(enter->scenePosition() == scenePos);
975 enter->setAccepted(accepted);
976#if QT_CONFIG(cursor)
977 QQuickWindowPrivate::get(d->rootItem->window())->updateCursor(enter->scenePosition(), d->rootItem);
978#endif
979 return accepted;
980 }
981 case QEvent::Leave:
982 d->clearHover();
983 d->lastMousePosition = QPointF();
984 d->lastMousePositionFromTouch = false;
985 break;
986#if QT_CONFIG(quick_draganddrop)
987 case QEvent::DragEnter:
988 case QEvent::DragLeave:
989 case QEvent::DragMove:
990 case QEvent::Drop:
991 d->deliverDragEvent(d->dragGrabber, ev);
992 break;
993#endif
994 case QEvent::FocusAboutToChange:
995#if QT_CONFIG(im)
996 if (d->activeFocusItem)
997 qGuiApp->inputMethod()->commit();
998#endif
999 break;
1000#if QT_CONFIG(gestures)
1001 case QEvent::NativeGesture:
1002 d->deliverSinglePointEventUntilAccepted(static_cast<QPointerEvent *>(ev));
1003 break;
1004#endif
1005 case QEvent::ShortcutOverride:
1006 d->deliverKeyEvent(static_cast<QKeyEvent *>(ev));
1007 break;
1008 case QEvent::InputMethod:
1009 case QEvent::InputMethodQuery:
1010 {
1011 QQuickItem *target = d->focusTargetItem();
1012 if (target)
1013 QCoreApplication::sendEvent(target, ev);
1014 }
1015 break;
1016#if QT_CONFIG(wheelevent)
1017 case QEvent::Wheel: {
1018 auto event = static_cast<QWheelEvent *>(ev);
1019 qCDebug(lcMouse) << event;
1020
1021 //if the actual wheel event was accepted, accept the compatibility wheel event and return early
1022 if (d->lastWheelEventAccepted && event->angleDelta().isNull() && event->phase() == Qt::ScrollUpdate)
1023 return true;
1024
1025 event->ignore();
1026 Q_QUICK_INPUT_PROFILE(QQuickProfiler::Mouse, QQuickProfiler::InputMouseWheel,
1027 event->angleDelta().x(), event->angleDelta().y());
1028 d->deliverSinglePointEventUntilAccepted(event);
1029 d->lastWheelEventAccepted = event->isAccepted();
1030 break;
1031 }
1032#endif
1033#if QT_CONFIG(tabletevent)
1034 case QEvent::TabletPress:
1035 case QEvent::TabletMove:
1036 case QEvent::TabletRelease:
1037 {
1038 auto *tabletEvent = static_cast<QTabletEvent *>(ev);
1039 d->deliverPointerEvent(tabletEvent); // visits HoverHandlers too (unlike the mouse event case)
1040#if QT_CONFIG(cursor)
1041 QQuickWindowPrivate::get(d->rootItem->window())->updateCursor(tabletEvent->scenePosition(), d->rootItem);
1042#endif
1043 }
1044 break;
1045#endif
1046#ifndef QT_NO_CONTEXTMENU
1047 case QEvent::ContextMenu:
1048 d->deliverContextMenuEvent(static_cast<QContextMenuEvent *>(ev));
1049 break;
1050#endif
1051 case QEvent::Timer:
1052 Q_ASSERT(static_cast<QTimerEvent *>(ev)->timerId() == d->frameSynchronousDelayTimer.timerId());
1053 d->frameSynchronousDelayTimer.stop();
1054 d->flushFrameSynchronousEvents(d->rootItem->window());
1055 break;
1056 default:
1057 return false;
1058 }
1059
1060 return true;
1061}
1062
1063void QQuickDeliveryAgentPrivate::deliverKeyEvent(QKeyEvent *e)
1064{
1065 if (activeFocusItem) {
1066 const bool keyPress = (e->type() == QEvent::KeyPress);
1067 switch (e->type()) {
1068 case QEvent::KeyPress:
1069 Q_QUICK_INPUT_PROFILE(QQuickProfiler::Key, QQuickProfiler::InputKeyPress, e->key(), e->modifiers());
1070 break;
1071 case QEvent::KeyRelease:
1072 Q_QUICK_INPUT_PROFILE(QQuickProfiler::Key, QQuickProfiler::InputKeyRelease, e->key(), e->modifiers());
1073 break;
1074 default:
1075 break;
1076 }
1077
1078 QQuickItem *item = activeFocusItem;
1079
1080 // In case of generated event, trigger ShortcutOverride event
1081 if (keyPress && e->spontaneous() == false)
1082 qt_sendShortcutOverrideEvent(item, e->timestamp(),
1083 e->key(), e->modifiers(), e->text(),
1084 e->isAutoRepeat(), e->count());
1085
1086 do {
1087 Q_ASSERT(e->type() != QEvent::ShortcutOverride || !e->isAccepted());
1088 if (e->type() != QEvent::ShortcutOverride)
1089 e->accept();
1090 QCoreApplication::sendEvent(item, e);
1091 } while (!e->isAccepted() && (item = item->parentItem()));
1092 }
1093}
1094
1095QQuickDeliveryAgentPrivate::QQuickDeliveryAgentPrivate(QQuickItem *root) :
1096 QObjectPrivate(),
1097 rootItem(root),
1098 // a plain QQuickItem can be a subscene root; a QQuickRootItem always belongs directly to a QQuickWindow
1099 isSubsceneAgent(!qmlobject_cast<QQuickRootItem *>(rootItem))
1100{
1101#if QT_CONFIG(quick_draganddrop)
1102 dragGrabber = new QQuickDragGrabber;
1103#endif
1104 if (isSubsceneAgent)
1105 subsceneAgentsExist = true;
1106 const auto interval = qEnvironmentVariableIntegerValue("QT_QUICK_FRAME_SYNCHRONOUS_HOVER_INTERVAL");
1107 if (interval.has_value()) {
1108 qCDebug(lcHoverTrace) << "frame-synchronous hover interval" << interval;
1109 frameSynchronousHoverInterval = int(interval.value());
1110 }
1111 if (frameSynchronousHoverInterval > 0)
1112 frameSynchronousHoverTimer.start();
1113}
1114
1115QQuickDeliveryAgentPrivate::~QQuickDeliveryAgentPrivate()
1116{
1117#if QT_CONFIG(quick_draganddrop)
1118 delete dragGrabber;
1119 dragGrabber = nullptr;
1120#endif
1121 delete sceneTransform;
1122}
1123
1124/*! \internal
1125 Make a copy of any type of QPointerEvent, and optionally localize it
1126 by setting its first point's local position() if \a transformedLocalPos is given.
1127
1128 \note some subclasses of QSinglePointEvent, such as QWheelEvent, add extra storage.
1129 This function doesn't yet support cloning all of those; it can be extended if needed.
1130*/
1131QPointerEvent *QQuickDeliveryAgentPrivate::clonePointerEvent(QPointerEvent *event, std::optional<QPointF> transformedLocalPos)
1132{
1133 QPointerEvent *ret = event->clone();
1134 QEventPoint &point = ret->point(0);
1135 QMutableEventPoint::detach(point);
1136 QMutableEventPoint::setTimestamp(point, event->timestamp());
1137 if (transformedLocalPos)
1138 QMutableEventPoint::setPosition(point, *transformedLocalPos);
1139
1140 return ret;
1141}
1142
1143void QQuickDeliveryAgentPrivate::deliverToPassiveGrabbers(const QList<QPointer <QObject> > &passiveGrabbers,
1144 QPointerEvent *pointerEvent)
1145{
1146 const QList<QObject *> &eventDeliveryTargets =
1147 QQuickPointerHandlerPrivate::deviceDeliveryTargets(pointerEvent->device());
1148 QVarLengthArray<std::pair<QQuickItem *, bool>, 4> sendFilteredPointerEventResult;
1149 hasFiltered.clear();
1150 for (QObject *grabberObject : passiveGrabbers) {
1151 // a null pointer in passiveGrabbers is unlikely, unless the grabbing handler was deleted dynamically
1152 if (Q_UNLIKELY(!grabberObject))
1153 continue;
1154 // a passiveGrabber might be an item or a handler
1155 if (QQuickPointerHandler *handler = qobject_cast<QQuickPointerHandler *>(grabberObject)) {
1156 if (handler && !eventDeliveryTargets.contains(handler)) {
1157 bool alreadyFiltered = false;
1158 QQuickItem *par = handler->parentItem();
1159
1160 // see if we already have sent a filter event to the parent
1161 auto it = std::find_if(sendFilteredPointerEventResult.begin(), sendFilteredPointerEventResult.end(),
1162 [par](const std::pair<QQuickItem *, bool> &pair) { return pair.first == par; });
1163 if (it != sendFilteredPointerEventResult.end()) {
1164 // Yes, the event was sent to that parent for filtering: do not call it again, but use
1165 // the result of the previous call to determine whether we should call the handler.
1166 alreadyFiltered = it->second;
1167 } else if (par) {
1168 alreadyFiltered = sendFilteredPointerEvent(pointerEvent, par);
1169 sendFilteredPointerEventResult << std::make_pair(par, alreadyFiltered);
1170 }
1171 if (!alreadyFiltered) {
1172 if (par)
1173 localizePointerEvent(pointerEvent, par);
1174 handler->handlePointerEvent(pointerEvent);
1175 }
1176 }
1177 } else if (QQuickItem *grabberItem = static_cast<QQuickItem *>(grabberObject)) {
1178 // don't steal the grab if input should remain with the exclusive grabber only
1179 if (QQuickItem *excGrabber = static_cast<QQuickItem *>(pointerEvent->exclusiveGrabber(pointerEvent->point(0)))) {
1180 if ((isMouseEvent(pointerEvent) && excGrabber->keepMouseGrab())
1181 || (isTouchEvent(pointerEvent) && excGrabber->keepTouchGrab())) {
1182 return;
1183 }
1184 }
1185 localizePointerEvent(pointerEvent, grabberItem);
1186 QCoreApplication::sendEvent(grabberItem, pointerEvent);
1187 pointerEvent->accept();
1188 }
1189 }
1190}
1191
1192bool QQuickDeliveryAgentPrivate::sendHoverEvent(QEvent::Type type, QQuickItem *item,
1193 const QPointF &localPos, const QPointF &scenePos, const QPointF &lastScenePos,
1194 const QPointF &globalPos, Qt::KeyboardModifiers modifiers, ulong timestamp)
1195{
1196 QHoverEvent hoverEvent(type, scenePos, globalPos, lastScenePos, modifiers);
1197 hoverEvent.setTimestamp(timestamp);
1198 hoverEvent.setAccepted(true);
1199 QEventPoint &point = hoverEvent.point(0);
1200 QMutableEventPoint::setPosition(point, localPos);
1201 if (Q_LIKELY(item->window()))
1202 QMutableEventPoint::setGlobalLastPosition(point, item->window()->mapToGlobal(lastScenePos));
1203
1204 hasFiltered.clear();
1205 if (sendFilteredMouseEvent(&hoverEvent, item, item->parentItem()))
1206 return true;
1207
1208 QCoreApplication::sendEvent(item, &hoverEvent);
1209
1210 return hoverEvent.isAccepted();
1211}
1212
1213/*! \internal
1214 Delivers a hover event at \a scenePos to the whole scene or subscene
1215 that this DeliveryAgent is responsible for. Returns \c true if
1216 delivery is "done".
1217*/
1218// TODO later: specify the device in case of multi-mouse scenario, or mouse and tablet both in use
1219bool QQuickDeliveryAgentPrivate::deliverHoverEvent(
1220 const QPointF &scenePos, const QPointF &lastScenePos,
1221 Qt::KeyboardModifiers modifiers, ulong timestamp)
1222{
1223 // The first time this function is called, hoverItems is empty.
1224 // We then call deliverHoverEventRecursive from the rootItem, and
1225 // populate the list with all the children and grandchildren that
1226 // we find that should receive hover events (in addition to sending
1227 // hover events to them and their HoverHandlers). We also set the
1228 // hoverId for each item to the currentHoverId.
1229 // The next time this function is called, we bump currentHoverId,
1230 // and call deliverHoverEventRecursive once more.
1231 // When that call returns, the list will contain the items that
1232 // were hovered the first time, as well as the items that were hovered
1233 // this time. But only the items that were hovered this time
1234 // will have their hoverId equal to currentHoverId; the ones we didn't
1235 // visit will still have an old hoverId. We can therefore go through the
1236 // list at the end of this function and look for items with an old hoverId,
1237 // remove them from the list, and update their state accordingly.
1238
1239 const bool subtreeHoverEnabled = QQuickItemPrivate::get(rootItem)->subtreeHoverEnabled;
1240 const bool itemsWasHovered = !hoverItems.isEmpty();
1241
1242 if (!subtreeHoverEnabled && !itemsWasHovered)
1243 return false;
1244
1245 currentHoverId++;
1246
1247 if (subtreeHoverEnabled) {
1248 hoveredLeafItemFound = false;
1249 QQuickPointerHandlerPrivate::deviceDeliveryTargets(QPointingDevice::primaryPointingDevice()).clear();
1250 deliverHoverEventRecursive(rootItem, scenePos, scenePos, lastScenePos,
1251 rootItem->mapToGlobal(scenePos), modifiers, timestamp);
1252 }
1253
1254 // Prune the list for items that are no longer hovered
1255 for (auto it = hoverItems.begin(); it != hoverItems.end();) {
1256 const auto &[item, hoverId, fromFinger] = *it;
1257 if (hoverId == currentHoverId) {
1258 // Still being hovered
1259 it++;
1260 } else {
1261 // No longer hovered. If hoverId is 0, it means that we have sent a HoverLeave
1262 // event to the item already, and it can just be removed from the list. Note that
1263 // the item can have been deleted as well.
1264 if (item && hoverId != 0)
1265 deliverHoverEventToItem(item, item->mapFromScene(scenePos), scenePos, lastScenePos,
1266 QGuiApplicationPrivate::lastCursorPosition, modifiers, timestamp, HoverChange::Clear);
1267 it = hoverItems.erase(it);
1268 }
1269 }
1270
1271 const bool itemsAreHovered = !hoverItems.isEmpty();
1272 return itemsWasHovered || itemsAreHovered;
1273}
1274
1275/*! \internal
1276 Delivers a hover event at \a scenePos to \a item and all its children.
1277 The children get it first. As soon as any item allows the event to remain
1278 accepted, recursion stops. Returns \c true in that case, or \c false if the
1279 event is rejected.
1280
1281 Each item that has hover enabled (from setAcceptHoverEvents()) has the
1282 QQuickItemPrivate::hoverEnabled flag set. This only controls whether we
1283 should send hover events to the item itself. (HoverHandlers no longer set
1284 this flag.) When an item has hoverEnabled set, all its ancestors have the
1285 QQuickItemPrivate::subtreeHoverEnabled set. This function will
1286 follow the subtrees that have subtreeHoverEnabled by recursing into each
1287 child with that flag set. And for each child (in addition to the item
1288 itself) that also has hoverEnabled set, we call deliverHoverEventToItem()
1289 to actually deliver the event to it. The item can then choose to accept or
1290 reject the event. This is only for control over whether we stop propagation
1291 or not: an item can reject the event, but at the same time be hovered (and
1292 therefore in hoverItems). By accepting the event, the item will effectivly
1293 end up as the only one hovered. Any other HoverHandler that may be a child
1294 of an item that is stacked underneath, will not. Note that since siblings
1295 can overlap, there can be more than one leaf item under the mouse.
1296
1297 Note that HoverHandler doesn't set the hoverEnabled flag on the parent item.
1298 But still, adding a HoverHandler to an item will set its subtreeHoverEnabled flag.
1299 So all the propagation logic described above will otherwise be the same.
1300 But the hoverEnabled flag can be used to resolve if subtreeHoverEnabled is on
1301 because the application explicitly requested it (setAcceptHoverEvents()), or
1302 indirectly, because the item has HoverHandlers.
1303
1304 For legacy reasons (Qt 6.1), as soon as we find a leaf item that has hover
1305 enabled, and therefore receives the event, we stop recursing into the remaining
1306 siblings (even if the event was ignored). This means that we only allow hover
1307 events to propagate up the direct parent-child hierarchy, and not to siblings.
1308 However, if the first candidate HoverHandler is disabled, delivery continues
1309 to the next one, which may be a sibling (QTBUG-106548).
1310*/
1311bool QQuickDeliveryAgentPrivate::deliverHoverEventRecursive(QQuickItem *item,
1312 const QPointF &localPos, const QPointF &scenePos, const QPointF &lastScenePos, const QPointF &globalPos,
1313 Qt::KeyboardModifiers modifiers, ulong timestamp)
1314{
1315 const QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item);
1316 const QList<QQuickItem *> children = itemPrivate->paintOrderChildItems();
1317 const bool hadChildrenChanged = itemPrivate->dirtyAttributes & QQuickItemPrivate::ChildrenChanged;
1318
1319 for (int ii = children.size() - 1; ii >= 0; --ii) {
1320 // If the children had not changed before we started the loop, but now they have changed,
1321 // stop looping to avoid potentially dereferencing a dangling pointer.
1322 // This is unusual, and hover delivery occurs frequently anyway, so just wait until next time.
1323 if (!hadChildrenChanged && Q_UNLIKELY(itemPrivate->dirtyAttributes & QQuickItemPrivate::ChildrenChanged))
1324 break;
1325 QQuickItem *child = children.at(ii);
1326 const QQuickItemPrivate *childPrivate = QQuickItemPrivate::get(child);
1327
1328 if (!child->isVisible() || childPrivate->culled)
1329 continue;
1330 if (!childPrivate->subtreeHoverEnabled)
1331 continue;
1332
1333 QTransform childToParent;
1334 childPrivate->itemToParentTransform(&childToParent);
1335 const QPointF childLocalPos = childToParent.inverted().map(localPos);
1336
1337 // If the child clips, or all children are inside, and scenePos is
1338 // outside its rectangular bounds, we can skip this item and all its
1339 // children, to save time.
1340 if (childPrivate->effectivelyClipsEventHandlingChildren() &&
1341 !childPrivate->eventHandlingBounds().contains(childLocalPos)) {
1342#ifdef QT_BUILD_INTERNAL
1343 ++QQuickItemPrivate::effectiveClippingSkips_counter;
1344#endif
1345 continue;
1346 }
1347
1348 // Recurse into the child
1349 const bool accepted = deliverHoverEventRecursive(child, childLocalPos, scenePos, lastScenePos, globalPos, modifiers, timestamp);
1350 if (accepted) {
1351 // Stop propagation / recursion
1352 return true;
1353 }
1354 if (hoveredLeafItemFound) {
1355 // Don't propagate to siblings, only to ancestors
1356 break;
1357 }
1358 }
1359
1360 // All decendants have been visited.
1361 // Now deliver the event to the item
1362 return deliverHoverEventToItem(item, localPos, scenePos, lastScenePos, globalPos, modifiers, timestamp, HoverChange::Set);
1363}
1364
1365/*! \internal
1366 Delivers a hover event at \a scenePos to \a item and its HoverHandlers if any.
1367 Returns \c true if the event remains accepted, \c false if rejected.
1368
1369 If \a clearHover is \c true, it will be sent as a QEvent::HoverLeave event,
1370 and the item and its handlers are expected to transition into their non-hovered
1371 states even if the position still indicates that the mouse is inside.
1372*/
1373bool QQuickDeliveryAgentPrivate::deliverHoverEventToItem(
1374 QQuickItem *item, const QPointF &localPos, const QPointF &scenePos, const QPointF &lastScenePos,
1375 const QPointF &globalPos, Qt::KeyboardModifiers modifiers, ulong timestamp, HoverChange hoverChange)
1376{
1377 QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item);
1378 const bool isHovering = item->contains(localPos);
1379 auto hoverItemIterator = findHoverStateByItem(hoverItems, item);
1380 const bool wasHovering = hoverItemIterator != hoverItems.end() && hoverItemIterator->hoverId != 0;
1381
1382 qCDebug(lcHoverTrace) << "item:" << item << "scene pos:" << scenePos << "localPos:" << localPos
1383 << "wasHovering:" << wasHovering << "isHovering:" << isHovering;
1384
1385 bool accepted = false;
1386
1387 // Start by sending out enter/move/leave events to the item.
1388 // Note that hoverEnabled only controls if we should send out hover events to the
1389 // item itself. HoverHandlers are not included, and are dealt with separately below.
1390 if (itemPrivate->hoverEnabled && isHovering && hoverChange == HoverChange::Set) {
1391 // Add the item to the list of hovered items (if it doesn't exist there
1392 // from before), and update hoverId to mark that it's (still) hovered.
1393 // Also set hoveredLeafItemFound, so that only propagate in a straight
1394 // line towards the root from now on.
1395 hoveredLeafItemFound = true;
1396 if (hoverItemIterator != hoverItems.end())
1397 markHovered(*hoverItemIterator, currentHoverId, lastMousePositionFromTouch);
1398 else
1399 hoverItems.append({item, currentHoverId, lastMousePositionFromTouch});
1400
1401 if (wasHovering)
1402 accepted = sendHoverEvent(QEvent::HoverMove, item, localPos, scenePos, lastScenePos, globalPos, modifiers, timestamp);
1403 else
1404 accepted = sendHoverEvent(QEvent::HoverEnter, item, localPos, scenePos, lastScenePos, globalPos, modifiers, timestamp);
1405 } else if (wasHovering) {
1406 // A leave should never stop propagation
1407 hoverItemIterator->hoverId = 0;
1408 sendHoverEvent(QEvent::HoverLeave, item, localPos, scenePos, lastScenePos, globalPos, modifiers, timestamp);
1409 }
1410
1411 if (!itemPrivate->hasPointerHandlers())
1412 return accepted;
1413
1414 // Next, send out hover events to the hover handlers.
1415 // If the item didn't accept the hover event, 'accepted' is now false.
1416 // Otherwise it's true, and then it should stay the way regardless of
1417 // whether or not the hoverhandlers themselves are hovered.
1418 // Note that since a HoverHandler can have a margin, a HoverHandler
1419 // can be hovered even if the item itself is not.
1420
1421 if (hoverChange == HoverChange::Clear) {
1422 // Note: a leave should never stop propagation
1423 QHoverEvent hoverEvent(QEvent::HoverLeave, scenePos, globalPos, lastScenePos, modifiers);
1424 hoverEvent.setTimestamp(timestamp);
1425
1426 for (QQuickPointerHandler *h : itemPrivate->extra->pointerHandlers) {
1427 if (QQuickHoverHandler *hh = qmlobject_cast<QQuickHoverHandler *>(h)) {
1428 if (!hh->isHovered())
1429 continue;
1430 hoverEvent.setAccepted(true);
1431 QCoreApplication::sendEvent(hh, &hoverEvent);
1432 }
1433 }
1434 } else {
1435 QMouseEvent hoverEvent(QEvent::MouseMove, localPos, scenePos, globalPos, Qt::NoButton, Qt::NoButton, modifiers);
1436 hoverEvent.setTimestamp(timestamp);
1437
1438 for (QQuickPointerHandler *h : itemPrivate->extra->pointerHandlers) {
1439 if (QQuickHoverHandler *hh = qmlobject_cast<QQuickHoverHandler *>(h)) {
1440 if (!hh->enabled())
1441 continue;
1442 hoverEvent.setAccepted(true);
1443 hh->handlePointerEvent(&hoverEvent);
1444 if (hh->isHovered()) {
1445 // Mark the whole item as updated, even if only the handler is
1446 // actually in a hovered state (because of HoverHandler.margins)
1447 hoveredLeafItemFound = true;
1448 hoverItemIterator = findHoverStateByItem(hoverItems, item);
1449 if (hoverItemIterator != hoverItems.end())
1450 markHovered(*hoverItemIterator, currentHoverId, lastMousePositionFromTouch);
1451 else
1452 hoverItems.append({item, currentHoverId, lastMousePositionFromTouch});
1453 if (hh->isBlocking()) {
1454 qCDebug(lcHoverTrace) << "skipping rest of hover delivery due to blocking" << hh;
1455 accepted = true;
1456 break;
1457 }
1458 }
1459 }
1460 }
1461 }
1462
1463 return accepted;
1464}
1465
1466// Simple delivery of non-mouse, non-touch Pointer Events: visit the items and handlers
1467// in the usual reverse-paint-order until propagation is stopped
1468bool QQuickDeliveryAgentPrivate::deliverSinglePointEventUntilAccepted(QPointerEvent *event)
1469{
1470 Q_ASSERT(event->points().size() == 1);
1471 QQuickPointerHandlerPrivate::deviceDeliveryTargets(event->pointingDevice()).clear();
1472 QEventPoint &point = event->point(0);
1473 QList<QQuickItem *> targetItems = pointerTargets(rootItem, event, point, false, false);
1474 point.setAccepted(false);
1475
1476 // Let passive grabbers see the event. This must be done before we deliver the
1477 // event to the target and to handlers that might stop event propagation.
1478 // Passive grabbers cannot stop event delivery.
1479 for (const auto &passiveGrabber : event->passiveGrabbers(point)) {
1480 if (auto *grabberItem = qobject_cast<QQuickItem *>(passiveGrabber)) {
1481 if (targetItems.contains(grabberItem))
1482 continue;
1483 localizePointerEvent(event, grabberItem);
1484 QCoreApplication::sendEvent(grabberItem, event);
1485 }
1486 }
1487 // Maintain the invariant that items receive input events in accepted state.
1488 // A passive grabber might have explicitly ignored the event.
1489 event->accept();
1490
1491 for (QQuickItem *item : targetItems) {
1492 QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item);
1493 localizePointerEvent(event, item);
1494 // Let Pointer Handlers have the first shot
1495 itemPrivate->handlePointerEvent(event);
1496 if (point.isAccepted())
1497 return true;
1498 event->accept();
1499 QCoreApplication::sendEvent(item, event);
1500 if (event->isAccepted()) {
1501 qCDebug(lcWheelTarget) << event << "->" << item;
1502 return true;
1503 }
1504 }
1505
1506 return false; // it wasn't handled
1507}
1508
1509bool QQuickDeliveryAgentPrivate::deliverTouchCancelEvent(QTouchEvent *event)
1510{
1511 qCDebug(lcTouch) << event;
1512
1513 // An incoming TouchCancel event will typically not contain any points,
1514 // but sendTouchCancelEvent() adds the points that have grabbers to the event.
1515 // Deliver it to all items and handlers that have active touches.
1516 const_cast<QPointingDevicePrivate *>(QPointingDevicePrivate::get(event->pointingDevice()))->
1517 sendTouchCancelEvent(event);
1518
1519 cancelTouchMouseSynthesis();
1520
1521 // A cancelled sequence ends just as definitely as a released one,
1522 // so hover that the fingers established has to go too.
1523 if (event->pointerType() == QPointingDevice::PointerType::Finger)
1524 clearTouchHover(event->timestamp());
1525 // Whatever the pointer type, a cancelled sequence must not leave frame-synchronous
1526 // hover delivery running at the position where contact was lost.
1527 lastMousePosition = {};
1528 lastMousePositionFromTouch = false;
1529
1530 return true;
1531}
1532
1533void QQuickDeliveryAgentPrivate::deliverDelayedTouchEvent()
1534{
1535 // Deliver and delete delayedTouch.
1536 // Set delayedTouch to nullptr before delivery to avoid redelivery in case of
1537 // event loop recursions (e.g if it the touch starts a dnd session).
1538 std::unique_ptr<QTouchEvent> e(std::move(delayedTouch));
1539 qCDebug(lcTouchCmprs) << "delivering" << e.get();
1540 compressedTouchCount = 0;
1541 deliverPointerEvent(e.get());
1542}
1543
1544/*! \internal
1545 The handler for the QEvent::WindowDeactivate event, and also when
1546 Qt::ApplicationState tells us the application is no longer active.
1547 It clears all exclusive grabs of items and handlers whose window is this one,
1548 for all known pointing devices.
1549
1550 The QEvent is not passed into this function because in the first case it's
1551 just a plain QEvent with no extra data, and because the application state
1552 change is delivered via a signal rather than an event.
1553*/
1554void QQuickDeliveryAgentPrivate::handleWindowDeactivate(QQuickWindow *win)
1555{
1556 Q_Q(QQuickDeliveryAgent);
1557 qCDebug(lcFocus) << "deactivated" << win->title();
1558 const auto inputDevices = QInputDevice::devices();
1559 for (auto device : inputDevices) {
1560 if (auto pointingDevice = qobject_cast<const QPointingDevice *>(device)) {
1561 auto devPriv = QPointingDevicePrivate::get(const_cast<QPointingDevice *>(pointingDevice));
1562 for (auto epd : devPriv->activePoints.values()) {
1563 if (!epd.exclusiveGrabber.isNull()) {
1564 bool relevant = false;
1565 if (QQuickItem *item = qmlobject_cast<QQuickItem *>(epd.exclusiveGrabber.data()))
1566 relevant = (item->window() == win);
1567 else if (QQuickPointerHandler *handler = qmlobject_cast<QQuickPointerHandler *>(epd.exclusiveGrabber.data())) {
1568 if (handler->parentItem())
1569 relevant = (handler->parentItem()->window() == win && epd.exclusiveGrabberContext.data() == q);
1570 else
1571 // a handler with no Item parent probably has a 3D Model parent.
1572 // TODO actually check the window somehow
1573 relevant = true;
1574 }
1575 if (relevant)
1576 devPriv->setExclusiveGrabber(nullptr, epd.eventPoint, nullptr);
1577 }
1578 // For now, we don't clearPassiveGrabbers(), just in case passive grabs
1579 // can be useful to keep monitoring the mouse even after window deactivation.
1580 }
1581 }
1582 }
1583}
1584
1585void QQuickDeliveryAgentPrivate::handleWindowHidden(QQuickWindow *win)
1586{
1587 qCDebug(lcFocus) << "hidden" << win->title();
1588 clearHover();
1589 lastMousePosition = QPointF();
1590 lastMousePositionFromTouch = false;
1591}
1592
1593bool QQuickDeliveryAgentPrivate::allUpdatedPointsAccepted(const QPointerEvent *ev)
1594{
1595 for (auto &point : ev->points()) {
1596 if (point.state() != QEventPoint::State::Pressed && !point.isAccepted())
1597 return false;
1598 }
1599 return true;
1600}
1601
1602/*! \internal
1603 Localize \a ev for delivery to \a dest.
1604
1605 Unlike QMutableTouchEvent::localized(), this modifies the QEventPoint
1606 instances in \a ev, which is more efficient than making a copy.
1607*/
1608void QQuickDeliveryAgentPrivate::localizePointerEvent(QPointerEvent *ev, const QQuickItem *dest)
1609{
1610 for (int i = 0; i < ev->pointCount(); ++i) {
1611 auto &point = ev->point(i);
1612 QMutableEventPoint::setPosition(point, dest->mapFromScene(point.scenePosition()));
1613 qCDebug(lcPtrLoc) << ev->type() << "@" << point.scenePosition() << "to"
1614 << dest << "@" << dest->mapToScene(QPointF()) << "->" << point;
1615 }
1616}
1617
1618QList<QObject *> QQuickDeliveryAgentPrivate::exclusiveGrabbers(QPointerEvent *ev)
1619{
1620 QList<QObject *> result;
1621 for (const QEventPoint &point : ev->points()) {
1622 if (QObject *grabber = ev->exclusiveGrabber(point)) {
1623 if (!result.contains(grabber))
1624 result << grabber;
1625 }
1626 }
1627 return result;
1628}
1629
1630bool QQuickDeliveryAgentPrivate::anyPointGrabbed(const QPointerEvent *ev)
1631{
1632 for (const QEventPoint &point : ev->points()) {
1633 if (ev->exclusiveGrabber(point) || !ev->passiveGrabbers(point).isEmpty())
1634 return true;
1635 }
1636 return false;
1637}
1638
1639bool QQuickDeliveryAgentPrivate::allPointsGrabbed(const QPointerEvent *ev)
1640{
1641 for (const auto &point : ev->points()) {
1642 if (!ev->exclusiveGrabber(point) && ev->passiveGrabbers(point).isEmpty())
1643 return false;
1644 }
1645 return true;
1646}
1647
1648bool QQuickDeliveryAgentPrivate::isMouseEvent(const QPointerEvent *ev)
1649{
1650 switch (ev->type()) {
1651 case QEvent::MouseButtonPress:
1652 case QEvent::MouseButtonRelease:
1653 case QEvent::MouseButtonDblClick:
1654 case QEvent::MouseMove:
1655 return true;
1656 default:
1657 return false;
1658 }
1659}
1660
1661bool QQuickDeliveryAgentPrivate::isMouseOrWheelEvent(const QPointerEvent *ev)
1662{
1663 return isMouseEvent(ev) || ev->type() == QEvent::Wheel;
1664}
1665
1666bool QQuickDeliveryAgentPrivate::isHoverEvent(const QPointerEvent *ev)
1667{
1668 switch (ev->type()) {
1669 case QEvent::HoverEnter:
1670 case QEvent::HoverMove:
1671 case QEvent::HoverLeave:
1672 return true;
1673 default:
1674 return false;
1675 }
1676}
1677
1678bool QQuickDeliveryAgentPrivate::isTouchEvent(const QPointerEvent *ev)
1679{
1680 switch (ev->type()) {
1681 case QEvent::TouchBegin:
1682 case QEvent::TouchUpdate:
1683 case QEvent::TouchEnd:
1684 case QEvent::TouchCancel:
1685 return true;
1686 default:
1687 return false;
1688 }
1689}
1690
1691bool QQuickDeliveryAgentPrivate::isTabletEvent(const QPointerEvent *ev)
1692{
1693#if QT_CONFIG(tabletevent)
1694 switch (ev->type()) {
1695 case QEvent::TabletPress:
1696 case QEvent::TabletMove:
1697 case QEvent::TabletRelease:
1698 case QEvent::TabletEnterProximity:
1699 case QEvent::TabletLeaveProximity:
1700 return true;
1701 default:
1702 break;
1703 }
1704#else
1705 Q_UNUSED(ev);
1706#endif // tabletevent
1707 return false;
1708}
1709
1710bool QQuickDeliveryAgentPrivate::isEventFromMouseOrTouchpad(const QPointerEvent *ev)
1711{
1712 const auto devType = ev->device()->type();
1713 return devType == QInputDevice::DeviceType::Mouse ||
1714 devType == QInputDevice::DeviceType::TouchPad;
1715}
1716
1717bool QQuickDeliveryAgentPrivate::isSynthMouse(const QPointerEvent *ev)
1718{
1719 return (!isEventFromMouseOrTouchpad(ev) && isMouseEvent(ev));
1720}
1721
1722/*!
1723 Returns \c true if \a dev is a type of device that only sends
1724 QSinglePointEvents.
1725*/
1726bool QQuickDeliveryAgentPrivate::isSinglePointDevice(const QInputDevice *dev)
1727{
1728 switch (dev->type()) {
1729 case QInputDevice::DeviceType::Mouse:
1730 case QInputDevice::DeviceType::TouchPad:
1731 case QInputDevice::DeviceType::Puck:
1732 case QInputDevice::DeviceType::Stylus:
1733 case QInputDevice::DeviceType::Airbrush:
1734 return true;
1735 case QInputDevice::DeviceType::TouchScreen:
1736 case QInputDevice::DeviceType::Keyboard:
1737 case QInputDevice::DeviceType::Unknown:
1738 case QInputDevice::DeviceType::AllDevices:
1739 return false;
1740 }
1741 return false;
1742}
1743
1744QQuickPointingDeviceExtra *QQuickDeliveryAgentPrivate::deviceExtra(const QInputDevice *device)
1745{
1746 QInputDevicePrivate *devPriv = QInputDevicePrivate::get(const_cast<QInputDevice *>(device));
1747 if (devPriv->qqExtra)
1748 return static_cast<QQuickPointingDeviceExtra *>(devPriv->qqExtra);
1749 auto extra = new QQuickPointingDeviceExtra;
1750 devPriv->qqExtra = extra;
1751 QObject::connect(device, &QObject::destroyed, device, [devPriv]() {
1752 delete static_cast<QQuickPointingDeviceExtra *>(devPriv->qqExtra);
1753 devPriv->qqExtra = nullptr;
1754 });
1755 return extra;
1756}
1757
1758/*!
1759 \internal
1760 This function is called from handleTouchEvent() in case a series of touch
1761 events containing only \c Updated and \c Stationary points arrives within a
1762 short period of time. (Some touchscreens are more "jittery" than others.)
1763
1764 It would be a waste of CPU time to deliver events and have items in the
1765 scene getting modified more often than once per frame; so here we try to
1766 coalesce the series of updates into a single event containing all updates
1767 that occur within one frame period, and deliverDelayedTouchEvent() is
1768 called from flushFrameSynchronousEvents() to send that single event. This
1769 is the reason why touch compression lives here so far, instead of in a
1770 lower layer: the render loop updates the scene in sync with the screen's
1771 vsync, and flushFrameSynchronousEvents() is called from there (for example
1772 from QSGThreadedRenderLoop::polishAndSync(), and equivalent places in other
1773 render loops). It would be preferable to move this code down to a lower
1774 level eventually, though, because it's not fundamentally a Qt Quick concern.
1775
1776 This optimization can be turned off by setting the environment variable
1777 \c QML_NO_TOUCH_COMPRESSION.
1778
1779 Returns \c true if "done", \c false if the caller needs to finish the
1780 \a event delivery.
1781*/
1782bool QQuickDeliveryAgentPrivate::compressTouchEvent(QTouchEvent *event)
1783{
1784 // If this is a subscene agent, don't store any events, because
1785 // flushFrameSynchronousEvents() is only called on the window's DA.
1786 if (isSubsceneAgent)
1787 return false;
1788
1789 QEventPoint::States states = event->touchPointStates();
1790 if (states.testFlag(QEventPoint::State::Pressed) || states.testFlag(QEventPoint::State::Released)) {
1791 qCDebug(lcTouchCmprs) << "no compression" << event;
1792 // we can only compress an event that doesn't include any pressed or released points
1793 return false;
1794 }
1795
1796 if (!delayedTouch) {
1797 delayedTouch.reset(new QMutableTouchEvent(event->type(), event->pointingDevice(), event->modifiers(), event->points()));
1798 delayedTouch->setTimestamp(event->timestamp());
1799 for (qsizetype i = 0; i < delayedTouch->pointCount(); ++i) {
1800 auto &tp = delayedTouch->point(i);
1801 QMutableEventPoint::detach(tp);
1802 }
1803 ++compressedTouchCount;
1804 qCDebug(lcTouchCmprs) << "delayed" << compressedTouchCount << delayedTouch.get();
1805 if (QQuickWindow *window = rootItem->window())
1806 window->maybeUpdate();
1807 return true;
1808 }
1809
1810 // check if this looks like the last touch event
1811 if (delayedTouch->type() == event->type() &&
1812 delayedTouch->device() == event->device() &&
1813 delayedTouch->modifiers() == event->modifiers() &&
1814 delayedTouch->pointCount() == event->pointCount())
1815 {
1816 // possible match.. is it really the same?
1817 bool mismatch = false;
1818
1819 auto tpts = event->points();
1820 for (qsizetype i = 0; i < event->pointCount(); ++i) {
1821 const auto &tp = tpts.at(i);
1822 const auto &tpDelayed = delayedTouch->point(i);
1823 if (tp.id() != tpDelayed.id()) {
1824 mismatch = true;
1825 break;
1826 }
1827
1828 if (tpDelayed.state() == QEventPoint::State::Updated && tp.state() == QEventPoint::State::Stationary)
1829 QMutableEventPoint::setState(tpts[i], QEventPoint::State::Updated);
1830 }
1831
1832 // matching touch event? then give delayedTouch a merged set of touchpoints
1833 if (!mismatch) {
1834 // have to create a new event because QMutableTouchEvent::setTouchPoints() is missing
1835 // TODO optimize, or move event compression elsewhere
1836 delayedTouch.reset(new QMutableTouchEvent(event->type(), event->pointingDevice(), event->modifiers(), tpts));
1837 delayedTouch->setTimestamp(event->timestamp());
1838 for (qsizetype i = 0; i < delayedTouch->pointCount(); ++i) {
1839 auto &tp = delayedTouch->point(i);
1840 QMutableEventPoint::detach(tp);
1841 }
1842 ++compressedTouchCount;
1843 qCDebug(lcTouchCmprs) << "coalesced" << compressedTouchCount << delayedTouch.get();
1844 if (QQuickWindow *window = rootItem->window())
1845 window->maybeUpdate();
1846 return true;
1847 }
1848 }
1849
1850 // merging wasn't possible, so deliver the delayed event first, and then delay this one
1851 deliverDelayedTouchEvent();
1852 delayedTouch.reset(new QMutableTouchEvent(event->type(), event->pointingDevice(),
1853 event->modifiers(), event->points()));
1854 delayedTouch->setTimestamp(event->timestamp());
1855 return true;
1856}
1857
1858/*!
1859 \internal
1860 Returns \c true if \a event ends a finger touch sequence: that is,
1861 of one or more touchpoints, every point has been released,
1862 and we are sure that they came from fingers, not synthesized.
1863
1864 Finger touch has no hover state of its own (until we support hover-detecting touchscreens).
1865 Stylus-proximity hover must not be cleared here.
1866
1867 This mirrors the rule in QQuickHoverHandler::handleEventPoint().
1868*/
1869static bool isEndOfFingerTouchSequence(const QTouchEvent *event)
1870{
1871 if (!event->isEndEvent())
1872 return false;
1873 if (!event->pointCount())
1874 return false;
1875 // in case of AA_SynthesizeTouchForUnhandledMouseEvents, for example (unusual)
1876 if (Q_UNLIKELY(event->pointerType() != QPointingDevice::PointerType::Finger))
1877 return false;
1878 // Ask the device to be sure, since some don't report every still-down contact in every event:
1879 // e.g. the Stationary points that QWindowsPointerHandler::translateTouchEvent()
1880 // synthesizes for digitizers that send one message per finger.
1881 const auto *devPriv = QPointingDevicePrivate::get(event->pointingDevice());
1882 for (const auto &activePoint : devPriv->activePoints) {
1883 if (activePoint.second.eventPoint.state() != QEventPoint::State::Released)
1884 return false;
1885 }
1886 return true;
1887}
1888
1889// entry point for touch event delivery:
1890// - translate the event to window coordinates
1891// - compress the event instead of delivering it if applicable
1892// - call deliverTouchPoints to actually dispatch the points
1893void QQuickDeliveryAgentPrivate::handleTouchEvent(QTouchEvent *event)
1894{
1895 Q_Q(QQuickDeliveryAgent);
1896 translateTouchEvent(event);
1897 // TODO remove: touch and mouse should be independent until we come to touch->mouse synth
1898 if (event->pointCount()) {
1899 auto &point = event->point(0);
1900 if (point.state() == QEventPoint::State::Released) {
1901 // Clearing lastMousePosition here switches off the frame-synchronous hover delivery
1902 // in flushFrameSynchronousEvents(), which would otherwise notice that
1903 // hoverItems holds entries with stale hoverIds. Hence clearTouchHover() below.
1904 lastMousePosition = QPointF();
1905 lastMousePositionFromTouch = false;
1906 } else {
1907 lastMousePosition = point.position();
1908 lastMousePositionFromTouch =
1909 event->pointerType() == QPointingDevice::PointerType::Finger;
1910 }
1911 }
1912
1913 // After delivery of the last touchpoint release, hover that the fingertips established
1914 // has to go with it. It's not the platform plugin's job to synthesize a LeaveEvent
1915 // on touch release. And not every device has a mouse to change hover state, either.
1916 const bool fingerHoverEnded = isEndOfFingerTouchSequence(event);
1917
1918 qCDebug(lcTouch) << q << event;
1919
1920 static bool qquickwindow_no_touch_compression = qEnvironmentVariableIsSet("QML_NO_TOUCH_COMPRESSION");
1921
1922 if (qquickwindow_no_touch_compression || pointerEventRecursionGuard) {
1923 deliverPointerEvent(event);
1924 } else if (!compressTouchEvent(event)) {
1925 if (delayedTouch) {
1926 deliverDelayedTouchEvent();
1927 qCDebug(lcTouchCmprs) << "resuming delivery" << event;
1928 }
1929 deliverPointerEvent(event);
1930 }
1931
1932 // compressTouchEvent() never compresses an event that carries a released point,
1933 // so an ended sequence is always delivered by one of the branches above first.
1934 if (fingerHoverEnded)
1935 clearTouchHover(event->timestamp());
1936}
1937
1938/*!
1939 Handle \a event on behalf of this delivery agent's window or subscene.
1940*/
1941void QQuickDeliveryAgentPrivate::handleMouseEvent(QMouseEvent *event)
1942{
1943 Q_Q(QQuickDeliveryAgent);
1944 // We generally don't want OS-synthesized mouse events, because Qt Quick does its own touch->mouse synthesis.
1945 // But if the platform converts long-press to right-click, it's ok to react to that,
1946 // unless the user has opted out by setting QT_QUICK_ALLOW_SYNTHETIC_RIGHT_CLICK=0.
1947 if (event->source() == Qt::MouseEventSynthesizedBySystem &&
1948 !(event->button() == Qt::RightButton && allowSyntheticRightClick())) {
1949 event->accept();
1950 return;
1951 }
1952 qCDebug(lcMouse) << q << event;
1953
1954 switch (event->type()) {
1955 case QEvent::MouseButtonPress:
1956 Q_QUICK_INPUT_PROFILE(QQuickProfiler::Mouse, QQuickProfiler::InputMousePress, event->button(),
1957 event->buttons());
1958 deliverPointerEvent(event);
1959 break;
1960 case QEvent::MouseButtonRelease:
1961 Q_QUICK_INPUT_PROFILE(QQuickProfiler::Mouse, QQuickProfiler::InputMouseRelease, event->button(),
1962 event->buttons());
1963 deliverPointerEvent(event);
1964#if QT_CONFIG(cursor)
1965 QQuickWindowPrivate::get(rootItem->window())->updateCursor(event->scenePosition());
1966#endif
1967 break;
1968 case QEvent::MouseButtonDblClick:
1969 Q_QUICK_INPUT_PROFILE(QQuickProfiler::Mouse, QQuickProfiler::InputMouseDoubleClick,
1970 event->button(), event->buttons());
1971 deliverPointerEvent(event);
1972 break;
1973 case QEvent::MouseMove: {
1974 Q_QUICK_INPUT_PROFILE(QQuickProfiler::Mouse, QQuickProfiler::InputMouseMove,
1975 event->position().x(), event->position().y());
1976
1977 const QPointF last = lastMousePosition.isNull() ? event->scenePosition() : lastMousePosition;
1978 lastMousePosition = event->scenePosition();
1979 // A real mouse move takes the position back from any fingertip that had claimed
1980 // it, so hover established from here on belongs to the cursor.
1981 lastMousePositionFromTouch = false;
1982 qCDebug(lcHoverTrace) << q << "mouse pos" << last << "->" << lastMousePosition;
1983 if (!event->points().size() || !event->exclusiveGrabber(event->point(0))) {
1984 bool accepted = deliverHoverEvent(event->scenePosition(), last, event->modifiers(), event->timestamp());
1985 event->setAccepted(accepted);
1986 }
1987 deliverPointerEvent(event);
1988#if QT_CONFIG(cursor)
1989 // The pointer event could result in a cursor change (reaction), so update it afterwards.
1990 QQuickWindowPrivate::get(rootItem->window())->updateCursor(event->scenePosition());
1991#endif
1992 break;
1993 }
1994 default:
1995 Q_ASSERT(false);
1996 break;
1997 }
1998}
1999
2000/*! \internal
2001 Flush events before a frame is rendered in \a win.
2002
2003 This is here because of compressTouchEvent(): we need to ensure that
2004 coalesced touch events are actually delivered in time to cause the desired
2005 reactions of items and their handlers. And then since it was introduced
2006 because of that, we started using this function for once-per-frame hover
2007 events too, to take care of changing hover state when an item animates
2008 under the mouse cursor at a time that the mouse cursor is not moving.
2009
2010 This is done before QQuickItem::updatePolish() is called on all the items
2011 that requested polishing.
2012
2013 \sa qq-hover-event-delivery
2014*/
2015void QQuickDeliveryAgentPrivate::flushFrameSynchronousEvents(QQuickWindow *win)
2016{
2017 Q_Q(QQuickDeliveryAgent);
2018 QQuickDeliveryAgent *deliveringAgent = QQuickDeliveryAgentPrivate::currentEventDeliveryAgent;
2019 QQuickDeliveryAgentPrivate::currentEventDeliveryAgent = q;
2020
2021 if (delayedTouch) {
2022 deliverDelayedTouchEvent();
2023
2024 // Touch events which constantly start animations (such as a behavior tracking
2025 // the mouse point) need animations to start.
2026 QQmlAnimationTimer *ut = QQmlAnimationTimer::instance();
2027 if (ut && ut->hasStartAnimationPending())
2028 ut->startAnimations();
2029 }
2030
2031 // In webOS we already have the alternative to the issue that this
2032 // wanted to address and thus skipping this part won't break anything.
2033#if !defined(Q_OS_WEBOS)
2034 // Periodically, if any items are dirty, send a synthetic hover,
2035 // in case items have changed position, visibility, etc.
2036 // For instance, during animation (including the case of a ListView
2037 // whose delegates contain MouseAreas), a MouseArea needs to know
2038 // whether it has moved into a position where it is now under the cursor.
2039 // We do this once per frame if frameSynchronousHoverInterval == 0, or
2040 // skip some frames until elapsed time > frameSynchronousHoverInterval,
2041 // or skip it altogether if frameSynchronousHoverInterval < 0.
2042 // TODO do this for each known mouse device or come up with a different strategy
2043 if (frameSynchronousHoverInterval >= 0) {
2044 const bool timerActive = frameSynchronousHoverInterval > 0;
2045 const bool timerMature = frameSynchronousHoverTimer.elapsed() >= frameSynchronousHoverInterval;
2046 if (timerActive && !timerMature) {
2047 qCDebug(lcHoverTrace) << q << "frame-sync hover delivery delayed: elapsed"
2048 << frameSynchronousHoverTimer.elapsed() << "<" << frameSynchronousHoverInterval;
2049 if (!frameSynchronousDelayTimer.isActive())
2050 frameSynchronousDelayTimer.start(frameSynchronousHoverInterval - frameSynchronousHoverTimer.elapsed(), q);
2051 } else if (!win->mouseGrabberItem() && !lastMousePosition.isNull() &&
2052 (timerMature || QQuickWindowPrivate::get(win)->dirtyItemList)) {
2053 frameSynchronousDelayTimer.stop();
2054 qCDebug(lcHoverTrace) << q << "delivering frame-sync hover to root @" << lastMousePosition
2055 << "after elapsed time" << frameSynchronousHoverTimer.elapsed();
2056 if (deliverHoverEvent(lastMousePosition, lastMousePosition, QGuiApplication::keyboardModifiers(), 0)) {
2057#if QT_CONFIG(cursor)
2058 QQuickWindowPrivate::get(rootItem->window())->updateCursor(
2059 sceneTransform ? sceneTransform->map(lastMousePosition) : lastMousePosition, rootItem);
2060#endif
2061 }
2062
2063 if (timerActive)
2064 frameSynchronousHoverTimer.restart();
2065 ++frameSynchronousHover_counter;
2066 qCDebug(lcHoverTrace) << q << "frame-sync hover delivery done: round" << frameSynchronousHover_counter;
2067 }
2068 }
2069#else
2070 Q_UNUSED(win);
2071#endif
2072 if (Q_UNLIKELY(QQuickDeliveryAgentPrivate::currentEventDeliveryAgent &&
2073 QQuickDeliveryAgentPrivate::currentEventDeliveryAgent != q))
2074 qCWarning(lcPtr, "detected interleaved frame-sync and actual events");
2075 QQuickDeliveryAgentPrivate::currentEventDeliveryAgent = deliveringAgent;
2076}
2077
2078/*! \internal
2079 React to the fact that \a grabber underwent a grab \a transition
2080 while an item or handler was handling \a point from \a event.
2081 I.e. handle the QPointingDevice::grabChanged() signal.
2082
2083 This notifies the relevant items and/or pointer handlers, and
2084 does cleanup when grabs are lost or relinquished.
2085*/
2086void QQuickDeliveryAgentPrivate::onGrabChanged(QObject *grabber, QPointingDevice::GrabTransition transition,
2087 const QPointerEvent *event, const QEventPoint &point)
2088{
2089 Q_Q(QQuickDeliveryAgent);
2090 const bool grabGained = (transition == QPointingDevice::GrabTransition::GrabExclusive ||
2091 transition == QPointingDevice::GrabTransition::GrabPassive);
2092
2093 // note: event can be null, if the signal was emitted from QPointingDevicePrivate::removeGrabber(grabber)
2094 if (auto *handler = qmlobject_cast<QQuickPointerHandler *>(grabber)) {
2095 if (handler->parentItem()) {
2096 auto itemPriv = QQuickItemPrivate::get(handler->parentItem());
2097 if (itemPriv->deliveryAgent() == q) {
2098 if (grabGained) {
2099 handler->onGrabChanged(handler, transition, const_cast<QPointerEvent *>(event),
2100 const_cast<QEventPoint &>(point));
2101 } else {
2102 // When a grab is relinquished/lost, the event point is localized to the new grabber
2103 // or not localized at all (QTBUG-146781, QTBUG-147003). Thus, we must re-localize it
2104 // for the old grabber's onGrabChanged() handler.
2105 QEventPoint lostPoint(point);
2106 QMutableEventPoint::setPosition(lostPoint, handler->parentItem()->mapFromScene(point.scenePosition()));
2107 handler->onGrabChanged(handler, transition, const_cast<QPointerEvent *>(event),
2108 lostPoint);
2109 qCDebug(lcPtrLoc) << event->type() << "@" << point.scenePosition()
2110 << "to old handler" << grabber << "->" << lostPoint;
2111 }
2112 }
2113 if (grabGained) {
2114 // An item that is NOT a subscene root needs to track whether it got a grab via a subscene delivery agent,
2115 // whereas the subscene root item already knows it has its own DA.
2116 if (isSubsceneAgent && (!itemPriv->extra.isAllocated() || !itemPriv->extra->subsceneDeliveryAgent))
2117 itemPriv->maybeHasSubsceneDeliveryAgent = true;
2118 }
2119 } else if (!isSubsceneAgent) {
2120 handler->onGrabChanged(handler, transition, const_cast<QPointerEvent *>(event),
2121 const_cast<QEventPoint &>(point));
2122 }
2123 } else if (auto *grabberItem = qmlobject_cast<QQuickItem *>(grabber)) {
2124 switch (transition) {
2125 case QPointingDevice::CancelGrabExclusive:
2126 case QPointingDevice::UngrabExclusive:
2127 if (isDeliveringTouchAsMouse() || isSinglePointDevice(point.device())) {
2128 // If an EventPoint from the mouse or the synth-mouse or from any
2129 // mouse-like device is ungrabbed, call QQuickItem::mouseUngrabEvent().
2130 QMutableSinglePointEvent e(QEvent::UngrabMouse, point.device(), point);
2131 hasFiltered.clear();
2132 if (!sendFilteredMouseEvent(&e, grabberItem, grabberItem->parentItem())) {
2133 lastUngrabbed = grabberItem;
2134 grabberItem->mouseUngrabEvent();
2135 }
2136 } else {
2137 // Multi-point event: call QQuickItem::touchUngrabEvent() only if
2138 // all eventpoints are released or cancelled.
2139 bool allReleasedOrCancelled = true;
2140 if (transition == QPointingDevice::UngrabExclusive && event) {
2141 for (const auto &pt : event->points()) {
2142 if (pt.state() != QEventPoint::State::Released) {
2143 allReleasedOrCancelled = false;
2144 break;
2145 }
2146 }
2147 }
2148 if (allReleasedOrCancelled)
2149 grabberItem->touchUngrabEvent();
2150 }
2151 break;
2152 default:
2153 break;
2154 }
2155 auto *itemPriv = QQuickItemPrivate::get(grabberItem);
2156 // An item that is NOT a subscene root needs to track whether it got a grab via a subscene delivery agent,
2157 // whereas the subscene root item already knows it has its own DA.
2158 if (isSubsceneAgent && grabGained && (!itemPriv->extra.isAllocated() || !itemPriv->extra->subsceneDeliveryAgent))
2159 itemPriv->maybeHasSubsceneDeliveryAgent = true;
2160 }
2161
2162 if (currentEventDeliveryAgent == q && event && event->device()) {
2163 switch (transition) {
2164 case QPointingDevice::GrabPassive: {
2165 auto epd = QPointingDevicePrivate::get(const_cast<QPointingDevice*>(event->pointingDevice()))->queryPointById(point.id());
2166 Q_ASSERT(epd);
2167 QPointingDevicePrivate::setPassiveGrabberContext(epd, grabber, q);
2168 qCDebug(lcPtr) << "remembering that" << q << "handles point" << point.id() << "after" << transition;
2169 } break;
2170 case QPointingDevice::GrabExclusive: {
2171 auto epd = QPointingDevicePrivate::get(const_cast<QPointingDevice*>(event->pointingDevice()))->queryPointById(point.id());
2172 Q_ASSERT(epd);
2173 epd->exclusiveGrabberContext = q;
2174 qCDebug(lcPtr) << "remembering that" << q << "handles point" << point.id() << "after" << transition;
2175 } break;
2176 case QPointingDevice::CancelGrabExclusive:
2177 case QPointingDevice::UngrabExclusive:
2178 // taken care of in QPointingDevicePrivate::setExclusiveGrabber(,,nullptr), removeExclusiveGrabber()
2179 break;
2180 case QPointingDevice::UngrabPassive:
2181 case QPointingDevice::CancelGrabPassive:
2182 // taken care of in QPointingDevicePrivate::removePassiveGrabber(), clearPassiveGrabbers()
2183 break;
2184 case QPointingDevice::OverrideGrabPassive:
2185 // not in use at this time
2186 break;
2187 }
2188 }
2189}
2190
2191/*! \internal
2192 Called when a QPointingDevice is detected, to ensure that the
2193 QPointingDevice::grabChanged() signal is connected to
2194 QQuickDeliveryAgentPrivate::onGrabChanged().
2195
2196 \c knownPointingDevices is maintained only to track signal connections, and
2197 should not be used for other purposes. The usual place to get a list of all
2198 devices is QInputDevice::devices().
2199*/
2200void QQuickDeliveryAgentPrivate::ensureDeviceConnected(const QPointingDevice *dev)
2201{
2202 Q_Q(QQuickDeliveryAgent);
2203 if (knownPointingDevices.contains(dev))
2204 return;
2205 knownPointingDevices.append(dev);
2206 connect(dev, &QPointingDevice::grabChanged, this, &QQuickDeliveryAgentPrivate::onGrabChanged);
2207 QObject::connect(dev, &QObject::destroyed, q, [this, dev] {this->knownPointingDevices.removeAll(dev);});
2208}
2209
2210/*! \internal
2211 The entry point for delivery of \a event after determining that it \e is a
2212 pointer event, and either does not need to be coalesced in
2213 compressTouchEvent(), or already has been.
2214
2215 When it returns, event delivery is done.
2216*/
2217void QQuickDeliveryAgentPrivate::deliverPointerEvent(QPointerEvent *event)
2218{
2219 Q_Q(QQuickDeliveryAgent);
2220 if (isTabletEvent(event))
2221 qCDebug(lcTablet) << q << event;
2222
2223 // If users spin the eventloop as a result of event delivery, we disable
2224 // event compression and send events directly. This is because we consider
2225 // the usecase a bit evil, but we at least don't want to lose events.
2226 ++pointerEventRecursionGuard;
2227 eventsInDelivery.push(event);
2228
2229 // So far this is for use in Qt Quick 3D: if a QEventPoint is grabbed,
2230 // updates get delivered here pretty directly, bypassing picking; but we need to
2231 // be able to map the 2D viewport coordinate to a 2D coordinate within
2232 // d->rootItem, a 2D scene that has been arbitrarily mapped onto a 3D object.
2233 QVarLengthArray<QPointF, 16> originalScenePositions;
2234 if (sceneTransform) {
2235 originalScenePositions.resize(event->pointCount());
2236 for (int i = 0; i < event->pointCount(); ++i) {
2237 auto &pt = event->point(i);
2238 originalScenePositions[i] = pt.scenePosition();
2239 QMutableEventPoint::setScenePosition(pt, sceneTransform->map(pt.scenePosition()));
2240 qCDebug(lcPtrLoc) << q << event->type() << pt.id() << "transformed scene pos" << pt.scenePosition();
2241 }
2242 } else if (isSubsceneAgent) {
2243 qCDebug(lcPtrLoc) << q << event->type() << "no scene transform set";
2244 }
2245
2246 skipDelivery.clear();
2247 QQuickPointerHandlerPrivate::deviceDeliveryTargets(event->pointingDevice()).clear();
2248 if (sceneTransform)
2249 qCDebug(lcPtr) << q << "delivering with" << sceneTransform << event;
2250 else
2251 qCDebug(lcPtr) << q << "delivering" << event;
2252 for (int i = 0; i < event->pointCount(); ++i)
2253 event->point(i).setAccepted(false);
2254
2255 if (event->isBeginEvent()) {
2256 ensureDeviceConnected(event->pointingDevice());
2257 if (event->type() == QEvent::MouseButtonPress && rootItem->window()
2258 && static_cast<QSinglePointEvent *>(event)->button() == Qt::RightButton) {
2259 QQuickWindowPrivate::get(rootItem->window())->rmbContextMenuEventEnabled = true;
2260 }
2261 if (!deliverPressOrReleaseEvent(event))
2262 event->setAccepted(false);
2263 }
2264
2265 auto isHoveringMoveEvent = [](QPointerEvent *event) -> bool {
2266 if (event->type() == QEvent::MouseMove) {
2267 const auto *spe = static_cast<const QSinglePointEvent *>(event);
2268 if (spe->button() == Qt::NoButton && spe->buttons() == Qt::NoButton)
2269 return true;
2270 }
2271 return false;
2272 };
2273
2274 /*
2275 If some QEventPoints were not yet handled, deliver to existing grabbers,
2276 and then non-grabbing pointer handlers.
2277 But don't deliver stray mouse moves in which no buttons are pressed:
2278 stray mouse moves risk deactivating handlers that don't expect them;
2279 for mouse hover tracking, we rather use deliverHoverEvent().
2280 But do deliver TabletMove events, in case there is a HoverHandler that
2281 changes its cursorShape depending on stylus type.
2282 */
2283 if (!allUpdatedPointsAccepted(event) && !isHoveringMoveEvent(event))
2284 deliverUpdatedPoints(event);
2285 if (event->isEndEvent())
2286 deliverPressOrReleaseEvent(event, true);
2287
2288 // failsafe: never allow touch->mouse synthesis to persist after all touchpoints are released,
2289 // or after the touchmouse is released
2290 if (isTouchEvent(event) && touchMouseId >= 0) {
2291 if (static_cast<QTouchEvent *>(event)->touchPointStates() == QEventPoint::State::Released) {
2292 cancelTouchMouseSynthesis();
2293 } else {
2294 auto touchMousePoint = event->pointById(touchMouseId);
2295 if (touchMousePoint && touchMousePoint->state() == QEventPoint::State::Released)
2296 cancelTouchMouseSynthesis();
2297 }
2298 }
2299
2300 eventsInDelivery.pop();
2301 if (sceneTransform) {
2302 for (int i = 0; i < event->pointCount(); ++i)
2303 QMutableEventPoint::setScenePosition(event->point(i), originalScenePositions.at(i));
2304 }
2305 --pointerEventRecursionGuard;
2306 lastUngrabbed = nullptr;
2307}
2308
2309/*! \internal
2310 Returns a list of all items that are spatially relevant to receive \a event
2311 occurring at \a scenePos, starting with \a item and recursively
2312 checking all the children.
2313
2314 \a localPos is the same as \a scenePos mapped to \a item (given as an
2315 optimization, to avoid mapping it again). If \a pointId is given (if
2316 pointId >= 0), the event is a QPointerEvent: so the expectation is that
2317 this function must map the position to each child, during recursion.
2318 The reason we need to do it is that \a predicate may expect the QEventPoint
2319 to be localized already. eventTargets() is able to do the mapping using
2320 only QQuickItemPrivate::itemToParentTransform(), which is cheaper than
2321 calling windowToItemTransform() at each step.
2322
2323 \a event could alternatively be a QContextMenuEvent: then there is no
2324 QEventPoint available, so pointId is given as -1 to indicate that
2325 this function does \e not have responsibility to remap it to each child.
2326
2327 \list
2328 \li If QQuickItemPrivate::effectivelyClipsEventHandlingChildren() is
2329 \c true \e and \a scenePos is outside of QQuickItem::clipRect(), and
2330 \a item is \e not the root item, its children are also omitted.
2331 (We stop the recursion, because any clipped-off portions of children
2332 under \a scenePos are invisible; or, because we know that all children
2333 are fully inside the parent.)
2334 \li Ignore any item in a subscene that "belongs to" a different
2335 DeliveryAgent. (In current practice, this only happens in 2D scenes in
2336 Qt Quick 3D.)
2337 \li Ignore any item for which the given \a predicate returns \c false;
2338 include any item for which the predicate returns \c true.
2339 \endlist
2340
2341 \note If \c {QQuickView::resizeMode() == SizeViewToRootObject} (the default),
2342 the root item might not fill the window: so we don't check
2343 effectivelyClipsEventHandlingChildren() on it. It could even be 0 x 0 if
2344 width and height aren't declared.)
2345*/
2346QList<QQuickItem *> QQuickDeliveryAgentPrivate::eventTargets(QQuickItem *item, const QEvent *event, int pointId,
2347 QPointF localPos, QPointF scenePos, qxp::function_ref<std::optional<bool> (QQuickItem *, const QEvent *)> predicate) const
2348{
2349 QList<QQuickItem *> targets;
2350 eventTargetsAppend(item, event, pointId, localPos, scenePos, predicate, targets);
2351 return targets;
2352}
2353
2354/*!
2355 \internal
2356 Recursively collects event-target items into \a targets in reverse
2357 paint order (highest stacking order first), interleaving \a item
2358 among its children according to z-order.
2359*/
2360// FIXME: should this be iterative instead of recursive?
2361void QQuickDeliveryAgentPrivate::eventTargetsAppend(QQuickItem *item, const QEvent *event, int pointId,
2362 QPointF localPos, QPointF scenePos, qxp::function_ref<std::optional<bool> (QQuickItem *, const QEvent *)> predicate,
2363 QList<QQuickItem *> &targets) const
2364{
2365 auto itemPrivate = QQuickItemPrivate::get(item);
2366
2367 // If the item clips, or all children are inside, and it's not the root item,
2368 // and scenePos is outside its rectangular bounds, we can skip this item
2369 // and all its children, to save time. (This check is performance-sensitive!)
2370 if (item != rootItem && !itemPrivate->eventHandlingBounds().contains(localPos) &&
2371 itemPrivate->effectivelyClipsEventHandlingChildren()) {
2372 qCDebug(lcPtrLoc) << "skipping because" << localPos << "is outside rectangular bounds of" << item;
2373 return;
2374 }
2375
2376 // If we didn't return early: check containment more thoroughly, then build
2377 // a list of children in paint order, modified to respect z property adjustments.
2378 const QList<QQuickItem *> children = itemPrivate->paintOrderChildItems();
2379 if (pointId >= 0) {
2380 // If pointId is set, it's meant to indicate that this is a QPointerEvent, not e.g. a QContextMenuEvent.
2381 // Localize the relevant QEventPoint before calling the predicate: if it calls anyPointerHandlerWants(),
2382 // position() must be correct.
2383 Q_ASSERT(event->isPointerEvent());
2384 QPointerEvent *pev = const_cast<QPointerEvent *>(static_cast<const QPointerEvent *>(event));
2385 QEventPoint *point = pev->pointById(pointId);
2386 Q_ASSERT(point);
2387 QMutableEventPoint::setPosition(*point, localPos);
2388 }
2389 const std::optional<bool> override = predicate(item, event);
2390 const bool relevant = override.has_value() ? override.value()
2391 : item == rootItem || item->contains(localPos);
2392
2393 // Iterate in reverse paint order (highest stacking order first).
2394 // Emit self before the first child with z < 0 (i.e. between children
2395 // painted above and below self).
2396 bool selfEmitted = !relevant;
2397 for (int ii = children.size() - 1; ii >= 0; --ii) {
2398 QQuickItem *child = children.at(ii);
2399 if (!selfEmitted && child->z() < 0) {
2400 targets.append(item);
2401 selfEmitted = true;
2402 }
2403
2404 auto childPrivate = QQuickItemPrivate::get(child);
2405 if (!child->isVisible() || !child->isEnabled() || childPrivate->culled ||
2406 (childPrivate->extra.isAllocated() && childPrivate->extra->subsceneDeliveryAgent))
2407 continue;
2408
2409 QTransform childToParent;
2410 childPrivate->itemToParentTransform(&childToParent);
2411 const QPointF childLocalPos = childToParent.inverted().map(localPos);
2412 eventTargetsAppend(child, event, pointId, childLocalPos, scenePos, predicate, targets);
2413 }
2414
2415 // If all children have z >= 0, self goes last in reverse order
2416 if (!selfEmitted)
2417 targets.append(item);
2418}
2419
2420/*! \internal
2421 Returns a list of all items that are spatially relevant to receive \a event
2422 occurring at \a point, starting with \a item and recursively checking all
2423 the children.
2424 \list
2425 \li If an item has pointer handlers, call
2426 QQuickPointerHandler::wantsEventPoint()
2427 on every handler to decide whether the item is eligible.
2428 \li Otherwise, if \a checkMouseButtons is \c true, it means we are
2429 finding targets for a mouse event, so no item for which
2430 acceptedMouseButtons() is NoButton will be added.
2431 \li Otherwise, if \a checkAcceptsTouch is \c true, it means we are
2432 finding targets for a touch event, so either acceptTouchEvents() must
2433 return true \e or it must accept a synthesized mouse event. I.e. if
2434 acceptTouchEvents() returns false, it gets added only if
2435 acceptedMouseButtons() is true.
2436 \li If QQuickItem::clip() is \c true \e and the \a point is outside of
2437 QQuickItem::clipRect(), its children are also omitted. (We stop the
2438 recursion, because any clipped-off portions of children under \a point
2439 are invisible.)
2440 \li Ignore any item in a subscene that "belongs to" a different
2441 DeliveryAgent. (In current practice, this only happens in 2D scenes in
2442 Qt Quick 3D.)
2443 \endlist
2444
2445 The list returned from this function is the list of items that will be
2446 "visited" when delivering any event for which QPointerEvent::isBeginEvent()
2447 is \c true.
2448*/
2449QList<QQuickItem *> QQuickDeliveryAgentPrivate::pointerTargets(QQuickItem *item, const QPointerEvent *event, const QEventPoint &point,
2450 bool checkMouseButtons, bool checkAcceptsTouch) const
2451{
2452 auto predicate = [point, checkMouseButtons, checkAcceptsTouch](QQuickItem *item, const QEvent *ev) -> std::optional<bool> {
2453 const QPointerEvent *event = static_cast<const QPointerEvent *>(ev);
2454 auto itemPrivate = QQuickItemPrivate::get(item);
2455 if (itemPrivate->hasPointerHandlers()) {
2456 if (itemPrivate->anyPointerHandlerWants(event, point))
2457 return true;
2458 } else {
2459 if (checkMouseButtons && item->acceptedMouseButtons() == Qt::NoButton)
2460 return false;
2461 if (checkAcceptsTouch && !(item->acceptTouchEvents() || item->acceptedMouseButtons()))
2462 return false;
2463 }
2464
2465 return std::nullopt;
2466 };
2467
2468 return eventTargets(item, event, point.id(), item->mapFromScene(point.scenePosition()), point.scenePosition(), predicate);
2469}
2470
2471/*! \internal
2472 Returns a joined list consisting of the items in \a list1 and \a list2.
2473 \a list1 has priority; common items come last.
2474*/
2475QList<QQuickItem *> QQuickDeliveryAgentPrivate::mergePointerTargets(const QList<QQuickItem *> &list1, const QList<QQuickItem *> &list2) const
2476{
2477 QList<QQuickItem *> targets = list1;
2478 // start at the end of list2
2479 // if item not in list, append it
2480 // if item found, move to next one, inserting before the last found one
2481 int insertPosition = targets.size();
2482 for (int i = list2.size() - 1; i >= 0; --i) {
2483 int newInsertPosition = targets.lastIndexOf(list2.at(i), insertPosition);
2484 if (newInsertPosition >= 0) {
2485 Q_ASSERT(newInsertPosition <= insertPosition);
2486 insertPosition = newInsertPosition;
2487 }
2488 // check for duplicates, only insert if the item isn't there already
2489 if (insertPosition == targets.size() || list2.at(i) != targets.at(insertPosition))
2490 targets.insert(insertPosition, list2.at(i));
2491 }
2492 return targets;
2493}
2494
2495/*! \internal
2496 Deliver updated points to existing grabbers.
2497*/
2498void QQuickDeliveryAgentPrivate::deliverUpdatedPoints(QPointerEvent *event)
2499{
2500 Q_Q(const QQuickDeliveryAgent);
2501 bool done = false;
2502 const auto grabbers = exclusiveGrabbers(event);
2503 hasFiltered.clear();
2504 for (auto grabber : grabbers) {
2505 // The grabber is guaranteed to be either an item or a handler.
2506 QQuickItem *receiver = qmlobject_cast<QQuickItem *>(grabber);
2507 if (!receiver) {
2508 // The grabber is not an item? It's a handler then. Let it have the event first.
2509 QQuickPointerHandler *handler = static_cast<QQuickPointerHandler *>(grabber);
2510 receiver = static_cast<QQuickPointerHandler *>(grabber)->parentItem();
2511 // Filtering via QQuickItem::childMouseEventFilter() is only possible
2512 // if the handler's parent is an Item. It could be a QQ3D object.
2513 if (receiver) {
2514 hasFiltered.clear();
2515 if (sendFilteredPointerEvent(event, receiver))
2516 done = true;
2517 localizePointerEvent(event, receiver);
2518 }
2519 handler->handlePointerEvent(event);
2520 }
2521 if (done)
2522 break;
2523 // If the grabber is an item or the grabbing handler didn't handle it,
2524 // then deliver the event to the item (which may have multiple handlers).
2525 hasFiltered.clear();
2526 if (receiver)
2527 deliverMatchingPointsToItem(receiver, true, event);
2528 }
2529
2530 // Deliver to each eventpoint's passive grabbers (but don't visit any handler more than once)
2531 for (auto &point : event->points()) {
2532 auto epd = QPointingDevicePrivate::get(event->pointingDevice())->queryPointById(point.id());
2533 if (Q_UNLIKELY(!epd)) {
2534 qWarning() << "point is not in activePoints" << point;
2535 continue;
2536 }
2537 QList<QPointer<QObject>> relevantPassiveGrabbers;
2538 for (int i = 0; i < epd->passiveGrabbersContext.size(); ++i) {
2539 if (epd->passiveGrabbersContext.at(i).data() == q)
2540 relevantPassiveGrabbers << epd->passiveGrabbers.at(i);
2541 }
2542 if (!relevantPassiveGrabbers.isEmpty())
2543 deliverToPassiveGrabbers(relevantPassiveGrabbers, event);
2544
2545 // Ensure that HoverHandlers are updated, in case no items got dirty so far and there's no update request
2546 if (event->type() == QEvent::TouchUpdate) {
2547 for (const auto &[item, id, fromFinger] : hoverItems) {
2548 if (item) {
2549 bool res = deliverHoverEventToItem(item, item->mapFromScene(point.scenePosition()), point.scenePosition(), point.sceneLastPosition(),
2550 point.globalPosition(), event->modifiers(), event->timestamp(), HoverChange::Set);
2551 // if the event was accepted, then the item's ID must be valid
2552 Q_ASSERT(([this, item = item.get(), res]{
2553 const auto it2 = findHoverStateByItem(std::as_const(hoverItems), item);
2554 return !res || it2->hoverId != 0;
2555 }()));
2556 }
2557 }
2558 }
2559 }
2560
2561 if (done)
2562 return;
2563
2564 // If some points weren't grabbed, deliver only to non-grabber PointerHandlers in reverse paint order
2565 if (!allPointsGrabbed(event)) {
2566 QList<QQuickItem *> targetItems;
2567 for (auto &point : event->points()) {
2568 // Presses were delivered earlier; not the responsibility of deliverUpdatedTouchPoints.
2569 // Don't find handlers for points that are already grabbed by an Item (such as Flickable).
2570 if (point.state() == QEventPoint::Pressed || qmlobject_cast<QQuickItem *>(event->exclusiveGrabber(point)))
2571 continue;
2572 QList<QQuickItem *> targetItemsForPoint = pointerTargets(rootItem, event, point, false, false);
2573 if (targetItems.size()) {
2574 targetItems = mergePointerTargets(targetItems, targetItemsForPoint);
2575 } else {
2576 targetItems = targetItemsForPoint;
2577 }
2578 }
2579 for (QQuickItem *item : targetItems) {
2580 if (grabbers.contains(item))
2581 continue;
2582 QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item);
2583 localizePointerEvent(event, item);
2584 itemPrivate->handlePointerEvent(event, true); // avoid re-delivering to grabbers
2585 if (allPointsGrabbed(event))
2586 break;
2587 }
2588 }
2589}
2590
2591/*! \internal
2592 Deliver a pointer \a event containing newly pressed or released QEventPoints.
2593 If \a handlersOnly is \c true, skip the items and just deliver to Pointer Handlers
2594 (via QQuickItemPrivate::handlePointerEvent()).
2595
2596 For the sake of determinism, this function first builds the list
2597 \c targetItems by calling pointerTargets() on the root item. That is, the
2598 list of items to "visit" is determined at the beginning, and will not be
2599 affected if items reparent, hide, or otherwise try to make themselves
2600 eligible or ineligible during delivery. (Avoid bugs due to ugly
2601 just-in-time tricks in JS event handlers, filters etc.)
2602
2603 Whenever a touch gesture is in progress, and another touchpoint is pressed,
2604 or an existing touchpoint is released, we "start over" with delivery:
2605 that's why this function is called whenever the event \e contains newly
2606 pressed or released points. It's not necessary for a handler or an item to
2607 greedily grab all touchpoints just in case a valid gesture might start.
2608 QQuickMultiPointHandler::wantsPointerEvent() can calmly return \c false if
2609 the number of points is less than QQuickMultiPointHandler::minimumPointCount(),
2610 because it knows it will be asked again if the number of points increases.
2611
2612 When \a handlersOnly is \c false, \a event visits the items in \c targetItems
2613 via QQuickItem::event(). We have to call sendFilteredPointerEvent()
2614 before visiting each item, just in case a Flickable (or some other
2615 parent-filter) will decide to intercept the event. But we also have to be
2616 very careful never to let the same Flickable filter the same event twice,
2617 because when Flickable decides to intercept, it lets the child item have
2618 that event, and then grabs the next event. That allows you to drag a
2619 Slider, DragHandler or whatever inside a ListView delegate: if you're
2620 dragging in the correct direction for the draggable child, it can use
2621 QQuickItem::setKeepMouseGrab(), QQuickItem::setKeepTouchGrab() or
2622 QQuickPointerHandler::grabPermissions() to prevent Flickable from
2623 intercepting during filtering, only if it actually \e has the exclusive
2624 grab already when Flickable attempts to take it. Typically, both the
2625 Flickable and the child are checking the same drag threshold, so the
2626 child must have a chance to grab and \e keep the grab before Flickable
2627 gets a chance to steal it, even though Flickable actually sees the
2628 event first during filtering.
2629*/
2630bool QQuickDeliveryAgentPrivate::deliverPressOrReleaseEvent(QPointerEvent *event, bool handlersOnly)
2631{
2632 QList<QQuickItem *> targetItems;
2633 const bool isTouch = isTouchEvent(event);
2634 if (isTouch && event->isBeginEvent() && isDeliveringTouchAsMouse()) {
2635 if (auto point = const_cast<QPointingDevicePrivate *>(QPointingDevicePrivate::get(touchMouseDevice))->queryPointById(touchMouseId)) {
2636 // When a second point is pressed, if the first point's existing
2637 // grabber was a pointer handler while a filtering parent is filtering
2638 // the same first point _as mouse_: we're starting over with delivery,
2639 // so we need to allow the second point to now be sent as a synth-mouse
2640 // instead of the first one, so that filtering parents (maybe even the
2641 // same one) can get a chance to see the second touchpoint as a
2642 // synth-mouse and perhaps grab it. Ideally we would always do this
2643 // when a new touchpoint is pressed, but this compromise fixes
2644 // QTBUG-70998 and avoids breaking tst_FlickableInterop::touchDragSliderAndFlickable
2645 if (qobject_cast<QQuickPointerHandler *>(event->exclusiveGrabber(point->eventPoint)))
2646 cancelTouchMouseSynthesis();
2647 } else {
2648 qCWarning(lcTouchTarget) << "during delivery of touch press, synth-mouse ID" << Qt::hex << touchMouseId << "is missing from" << event;
2649 }
2650 }
2651 for (int i = 0; i < event->pointCount(); ++i) {
2652 auto &point = event->point(i);
2653 // Regardless whether a touchpoint could later result in a synth-mouse event:
2654 // if the double-tap time or space constraint has been violated,
2655 // reset state to prevent a double-click event.
2656 if (isTouch && point.state() == QEventPoint::Pressed)
2657 resetIfDoubleTapPrevented(point);
2658 QList<QQuickItem *> targetItemsForPoint = pointerTargets(rootItem, event, point, !isTouch, isTouch);
2659 if (targetItems.size()) {
2660 targetItems = mergePointerTargets(targetItems, targetItemsForPoint);
2661 } else {
2662 targetItems = targetItemsForPoint;
2663 }
2664 }
2665
2666 QList<QPointer<QQuickItem>> safeTargetItems(targetItems.begin(), targetItems.end());
2667
2668 for (auto &item : safeTargetItems) {
2669 if (item.isNull())
2670 continue;
2671 // failsafe: when items get into a subscene somehow, ensure that QQuickItemPrivate::deliveryAgent() can find it
2672 if (isSubsceneAgent)
2673 QQuickItemPrivate::get(item)->maybeHasSubsceneDeliveryAgent = true;
2674
2675 hasFiltered.clear();
2676 if (!handlersOnly && sendFilteredPointerEvent(event, item)) {
2677 if (event->isAccepted())
2678 return true;
2679 skipDelivery.append(item);
2680 }
2681
2682 // Do not deliverMatchingPointsTo any item for which the filtering parent already intercepted the event,
2683 // nor to any item which already had a chance to filter.
2684 if (skipDelivery.contains(item))
2685 continue;
2686
2687 // sendFilteredPointerEvent() changed the QEventPoint::accepted() state,
2688 // but per-point acceptance is opt-in during normal delivery to items.
2689 for (int i = 0; i < event->pointCount(); ++i)
2690 event->point(i).setAccepted(false);
2691
2692 deliverMatchingPointsToItem(item, false, event, handlersOnly);
2693 if (event->allPointsAccepted())
2694 handlersOnly = true;
2695 }
2696
2697 // Return this because it's true if all events were accepted, rather than
2698 // event->allPointsAccepted(), which can be false even if the event was accepted, because the
2699 // event points' accepted states are set to false before delivery.
2700 return handlersOnly;
2701}
2702
2703/*! \internal
2704 Deliver \a pointerEvent to \a item and its handlers, if any.
2705 If \a handlersOnly is \c true, skip QQuickItem::event() and just visit its
2706 handlers via QQuickItemPrivate::handlePointerEvent().
2707
2708 This function exists just to de-duplicate the common code between
2709 deliverPressOrReleaseEvent() and deliverUpdatedPoints().
2710*/
2711void QQuickDeliveryAgentPrivate::deliverMatchingPointsToItem(QQuickItem *item, bool isGrabber, QPointerEvent *pointerEvent, bool handlersOnly)
2712{
2713 QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item);
2714#if defined(Q_OS_ANDROID) && QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
2715 // QTBUG-85379
2716 // In QT_VERSION below 6.0.0 touchEnabled for QtQuickItems is set by default to true
2717 // It causes delivering touch events to Items which are not interested
2718 // In some cases (like using Material Style in Android) it may cause a crash
2719 if (itemPrivate->wasDeleted)
2720 return;
2721#endif
2722 localizePointerEvent(pointerEvent, item);
2723 bool isMouse = isMouseEvent(pointerEvent);
2724
2725 // Let the Item's handlers (if any) have the event first.
2726 // However, double click should never be delivered to handlers.
2727 if (pointerEvent->type() != QEvent::MouseButtonDblClick)
2728 itemPrivate->handlePointerEvent(pointerEvent);
2729
2730 if (handlersOnly)
2731 return;
2732
2733 // If all points are released and the item is not the grabber, it doesn't get the event.
2734 // But if at least one point is still pressed, we might be in a potential gesture-takeover scenario.
2735 if (pointerEvent->isEndEvent() && !pointerEvent->isUpdateEvent()
2736 && !exclusiveGrabbers(pointerEvent).contains(item))
2737 return;
2738
2739 // If any parent filters the event, we're done.
2740 if (sendFilteredPointerEvent(pointerEvent, item))
2741 return;
2742
2743 // TODO: unite this mouse point delivery with the synthetic mouse event below
2744 // TODO: remove isGrabber then?
2745 if (isMouse) {
2746 auto button = static_cast<QSinglePointEvent *>(pointerEvent)->button();
2747 if ((isGrabber && button == Qt::NoButton) || item->acceptedMouseButtons().testFlag(button)) {
2748 // The only reason to already have a mouse grabber here is
2749 // synthetic events - flickable sends one when setPressDelay is used.
2750 auto oldMouseGrabber = pointerEvent->exclusiveGrabber(pointerEvent->point(0));
2751 pointerEvent->accept();
2752 if (isGrabber && sendFilteredPointerEvent(pointerEvent, item))
2753 return;
2754 localizePointerEvent(pointerEvent, item);
2755 QCoreApplication::sendEvent(item, pointerEvent);
2756 if (pointerEvent->isAccepted()) {
2757 auto &point = pointerEvent->point(0);
2758 auto mouseGrabber = pointerEvent->exclusiveGrabber(point);
2759 if (mouseGrabber && mouseGrabber != item && mouseGrabber != oldMouseGrabber) {
2760 // Normally we don't need item->mouseUngrabEvent() here, because QQuickDeliveryAgentPrivate::onGrabChanged does it.
2761 // However, if one item accepted the mouse event, it expects to have the grab and be in "pressed" state,
2762 // because accepting implies grabbing. But before it actually gets the grab, another item could steal it.
2763 // In that case, onGrabChanged() does NOT notify the item that accepted the event that it's not getting the grab after all.
2764 // So after ensuring that it's not redundant, we send a notification here, for that case (QTBUG-55325).
2765 if (item != lastUngrabbed) {
2766 item->mouseUngrabEvent();
2767 lastUngrabbed = item;
2768 }
2769 } else if (item->isEnabled() && item->isVisible() && point.state() == QEventPoint::State::Pressed) {
2770 pointerEvent->setExclusiveGrabber(point, item);
2771 }
2772 point.setAccepted(true);
2773 }
2774 return;
2775 }
2776 }
2777
2778 if (!isTouchEvent(pointerEvent))
2779 return;
2780
2781 bool eventAccepted = false;
2782 QMutableTouchEvent touchEvent;
2783 itemPrivate->localizedTouchEvent(static_cast<QTouchEvent *>(pointerEvent), false, &touchEvent);
2784 if (touchEvent.type() == QEvent::None)
2785 return; // no points inside this item
2786
2787 if (item->acceptTouchEvents()) {
2788 qCDebug(lcTouch) << "considering delivering" << &touchEvent << " to " << item;
2789
2790 // Deliver the touch event to the given item
2791 qCDebug(lcTouch) << "actually delivering" << &touchEvent << " to " << item;
2792 QCoreApplication::sendEvent(item, &touchEvent);
2793 eventAccepted = touchEvent.isAccepted();
2794 } else {
2795 // If the touch event wasn't accepted, synthesize a mouse event and see if the item wants it.
2796 if (Q_LIKELY(QCoreApplication::testAttribute(Qt::AA_SynthesizeMouseForUnhandledTouchEvents)) &&
2797 !eventAccepted && (itemPrivate->acceptedMouseButtons() & Qt::LeftButton))
2798 deliverTouchAsMouse(item, &touchEvent);
2799 return;
2800 }
2801
2802 Q_ASSERT(item->acceptTouchEvents()); // else we would've returned early above
2803 if (eventAccepted) {
2804 bool isPressOrRelease = pointerEvent->isBeginEvent() || pointerEvent->isEndEvent();
2805 for (int i = 0; i < touchEvent.pointCount(); ++i) {
2806 auto &point = touchEvent.point(i);
2807 // legacy-style delivery: if the item doesn't reject the event, that means it handled ALL the points
2808 point.setAccepted();
2809 // but don't let the root of a subscene implicitly steal the grab from some other item (such as one of its children)
2810 if (isPressOrRelease && !(itemPrivate->deliveryAgent() && pointerEvent->exclusiveGrabber(point)))
2811 pointerEvent->setExclusiveGrabber(point, item);
2812 }
2813 } else {
2814 // But if the event was not accepted then we know this item
2815 // will not be interested in further updates for those touchpoint IDs either.
2816 for (const auto &point: touchEvent.points()) {
2817 if (point.state() == QEventPoint::State::Pressed) {
2818 if (pointerEvent->exclusiveGrabber(point) == item) {
2819 qCDebug(lcTouchTarget) << "TP" << Qt::hex << point.id() << "disassociated";
2820 pointerEvent->setExclusiveGrabber(point, nullptr);
2821 }
2822 }
2823 }
2824 }
2825}
2826
2827#if QT_CONFIG(quick_draganddrop)
2828void QQuickDeliveryAgentPrivate::deliverDragEvent(QQuickDragGrabber *grabber, QEvent *event)
2829{
2830 QObject *formerTarget = grabber->target();
2831 grabber->resetTarget();
2832 QQuickDragGrabber::iterator grabItem = grabber->begin();
2833 if (grabItem != grabber->end()) {
2834 Q_ASSERT(event->type() != QEvent::DragEnter);
2835 if (event->type() == QEvent::Drop) {
2836 QDropEvent *e = static_cast<QDropEvent *>(event);
2837 for (e->setAccepted(false); !e->isAccepted() && grabItem != grabber->end(); grabItem = grabber->release(grabItem)) {
2838 QPointF p = (**grabItem)->mapFromScene(e->position().toPoint());
2839 QDropEvent translatedEvent(
2840 p.toPoint(),
2841 e->possibleActions(),
2842 e->mimeData(),
2843 e->buttons(),
2844 e->modifiers());
2845 QQuickDropEventEx::copyActions(&translatedEvent, *e);
2846 QCoreApplication::sendEvent(**grabItem, &translatedEvent);
2847 e->setAccepted(translatedEvent.isAccepted());
2848 e->setDropAction(translatedEvent.dropAction());
2849 grabber->setTarget(**grabItem);
2850 }
2851 }
2852 if (event->type() != QEvent::DragMove) { // Either an accepted drop or a leave.
2853 QDragLeaveEvent leaveEvent;
2854 for (; grabItem != grabber->end(); grabItem = grabber->release(grabItem))
2855 QCoreApplication::sendEvent(**grabItem, &leaveEvent);
2856 grabber->ignoreList().clear();
2857 return;
2858 } else {
2859 QDragMoveEvent *moveEvent = static_cast<QDragMoveEvent *>(event);
2860
2861 // Used to ensure we don't send DragEnterEvents to current drop targets,
2862 // and to detect which current drop targets we have left
2863 QVarLengthArray<QQuickItem*, 64> currentGrabItems;
2864 for (; grabItem != grabber->end(); grabItem = grabber->release(grabItem))
2865 currentGrabItems.append(**grabItem);
2866
2867 // Look for any other potential drop targets that are higher than the current ones
2868 QDragEnterEvent enterEvent(
2869 moveEvent->position(),
2870 moveEvent->possibleActions(),
2871 moveEvent->mimeData(),
2872 moveEvent->buttons(),
2873 moveEvent->modifiers());
2874 QQuickDropEventEx::copyActions(&enterEvent, *moveEvent);
2875 event->setAccepted(deliverDragEvent(grabber, rootItem, &enterEvent, &currentGrabItems,
2876 formerTarget));
2877
2878 for (grabItem = grabber->begin(); grabItem != grabber->end(); ++grabItem) {
2879 int i = currentGrabItems.indexOf(**grabItem);
2880 if (i >= 0) {
2881 currentGrabItems.remove(i);
2882 // Still grabbed: send move event
2883 QDragMoveEvent translatedEvent(
2884 (**grabItem)->mapFromScene(moveEvent->position()),
2885 moveEvent->possibleActions(),
2886 moveEvent->mimeData(),
2887 moveEvent->buttons(),
2888 moveEvent->modifiers());
2889 QQuickDropEventEx::copyActions(&translatedEvent, *moveEvent);
2890 QCoreApplication::sendEvent(**grabItem, &translatedEvent);
2891 event->setAccepted(translatedEvent.isAccepted());
2892 QQuickDropEventEx::copyActions(moveEvent, translatedEvent);
2893 }
2894 }
2895
2896 // Anything left in currentGrabItems is no longer a drop target and should be sent a DragLeaveEvent
2897 QDragLeaveEvent leaveEvent;
2898 for (QQuickItem *i : currentGrabItems)
2899 QCoreApplication::sendEvent(i, &leaveEvent);
2900
2901 return;
2902 }
2903 }
2904 if (event->type() == QEvent::DragEnter || event->type() == QEvent::DragMove) {
2905 QDragMoveEvent *e = static_cast<QDragMoveEvent *>(event);
2906 QDragEnterEvent enterEvent(
2907 e->position(),
2908 e->possibleActions(),
2909 e->mimeData(),
2910 e->buttons(),
2911 e->modifiers());
2912 QQuickDropEventEx::copyActions(&enterEvent, *e);
2913 event->setAccepted(deliverDragEvent(grabber, rootItem, &enterEvent));
2914 } else {
2915 grabber->ignoreList().clear();
2916 }
2917}
2918
2919bool QQuickDeliveryAgentPrivate::deliverDragEvent(
2920 QQuickDragGrabber *grabber, QQuickItem *item, QDragMoveEvent *event,
2921 QVarLengthArray<QQuickItem *, 64> *currentGrabItems, QObject *formerTarget)
2922{
2923 QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item);
2924 if (!item->isVisible() || !item->isEnabled() || QQuickItemPrivate::get(item)->culled)
2925 return false;
2926 QPointF p = item->mapFromScene(event->position().toPoint());
2927 bool itemContained = item->contains(p);
2928
2929 const int itemIndex = grabber->ignoreList().indexOf(item);
2930 if (!itemContained) {
2931 if (itemIndex >= 0)
2932 grabber->ignoreList().remove(itemIndex);
2933
2934 if (itemPrivate->flags & QQuickItem::ItemClipsChildrenToShape)
2935 return false;
2936 }
2937
2938 QDragEnterEvent enterEvent(
2939 event->position(),
2940 event->possibleActions(),
2941 event->mimeData(),
2942 event->buttons(),
2943 event->modifiers());
2944 QQuickDropEventEx::copyActions(&enterEvent, *event);
2945 QList<QQuickItem *> children = itemPrivate->paintOrderChildItems();
2946
2947 // Check children in front of this item first
2948 for (int ii = children.size() - 1; ii >= 0; --ii) {
2949 if (children.at(ii)->z() < 0)
2950 continue;
2951 if (deliverDragEvent(grabber, children.at(ii), &enterEvent, currentGrabItems, formerTarget))
2952 return true;
2953 }
2954
2955 if (itemContained) {
2956 // If this item is currently grabbed, don't send it another DragEnter,
2957 // just grab it again if it's still contained.
2958 if (currentGrabItems && currentGrabItems->contains(item)) {
2959 grabber->grab(item);
2960 grabber->setTarget(item);
2961 return true;
2962 }
2963
2964 if (event->type() == QEvent::DragMove || itemPrivate->flags & QQuickItem::ItemAcceptsDrops) {
2965 if (event->type() == QEvent::DragEnter) {
2966 if (formerTarget) {
2967 QQuickItem *formerTargetItem = qobject_cast<QQuickItem *>(formerTarget);
2968 if (formerTargetItem && currentGrabItems) {
2969 QDragLeaveEvent leaveEvent;
2970 QCoreApplication::sendEvent(formerTarget, &leaveEvent);
2971
2972 // Remove the item from the currentGrabItems so a leave event won't be generated
2973 // later on
2974 currentGrabItems->removeAll(formerTarget);
2975 }
2976 } else if (itemIndex >= 0) {
2977 return false;
2978 }
2979 }
2980
2981 QDragMoveEvent translatedEvent(p, event->possibleActions(), event->mimeData(),
2982 event->buttons(), event->modifiers(), event->type());
2983 QQuickDropEventEx::copyActions(&translatedEvent, *event);
2984 translatedEvent.setAccepted(event->isAccepted());
2985 QCoreApplication::sendEvent(item, &translatedEvent);
2986 event->setAccepted(translatedEvent.isAccepted());
2987 event->setDropAction(translatedEvent.dropAction());
2988 if (event->type() == QEvent::DragEnter) {
2989 if (translatedEvent.isAccepted()) {
2990 grabber->grab(item);
2991 grabber->setTarget(item);
2992 return true;
2993 } else if (itemIndex < 0) {
2994 grabber->ignoreList().append(item);
2995 }
2996 } else {
2997 return true;
2998 }
2999 }
3000 }
3001
3002 // Check children behind this item if this item or any higher children have not accepted
3003 for (int ii = children.size() - 1; ii >= 0; --ii) {
3004 if (children.at(ii)->z() >= 0)
3005 continue;
3006 if (deliverDragEvent(grabber, children.at(ii), &enterEvent, currentGrabItems, formerTarget))
3007 return true;
3008 }
3009
3010 return false;
3011}
3012#endif // quick_draganddrop
3013
3014/*! \internal
3015 Allow \a filteringParent to filter \a event on behalf of \a receiver, via
3016 QQuickItem::childMouseEventFilter(). This happens right \e before we would
3017 send \a event to \a receiver.
3018
3019 Returns \c true only if \a event has been intercepted (by \a filteringParent
3020 or some other filtering ancestor) and should \e not be sent to \a receiver.
3021*/
3022bool QQuickDeliveryAgentPrivate::sendFilteredPointerEvent(QPointerEvent *event, QQuickItem *receiver, QQuickItem *filteringParent)
3023{
3024 return sendFilteredPointerEventImpl(event, receiver, filteringParent ? filteringParent : receiver->parentItem());
3025}
3026
3027/*! \internal
3028 The recursive implementation of sendFilteredPointerEvent().
3029*/
3030bool QQuickDeliveryAgentPrivate::sendFilteredPointerEventImpl(QPointerEvent *event, QQuickItem *receiver, QQuickItem *filteringParent)
3031{
3032 if (!allowChildEventFiltering)
3033 return false;
3034 if (!filteringParent)
3035 return false;
3036 bool filtered = false;
3037 const bool hasHandlers = QQuickItemPrivate::get(receiver)->hasPointerHandlers();
3038 if (filteringParent->filtersChildMouseEvents() && !hasFiltered.contains(filteringParent)) {
3039 hasFiltered.append(filteringParent);
3040 if (isMouseEvent(event)) {
3041 if (receiver->acceptedMouseButtons()) {
3042 const bool wasAccepted = event->allPointsAccepted();
3043 Q_ASSERT(event->pointCount());
3044 localizePointerEvent(event, receiver);
3045 event->setAccepted(true);
3046 auto oldMouseGrabber = event->exclusiveGrabber(event->point(0));
3047 if (filteringParent->childMouseEventFilter(receiver, event)) {
3048 qCDebug(lcMouse) << "mouse event intercepted by childMouseEventFilter of " << filteringParent;
3049 skipDelivery.append(filteringParent);
3050 filtered = true;
3051 if (event->isAccepted() && event->isBeginEvent()) {
3052 auto &point = event->point(0);
3053 auto mouseGrabber = event->exclusiveGrabber(point);
3054 if (mouseGrabber && mouseGrabber != receiver && mouseGrabber != oldMouseGrabber) {
3055 receiver->mouseUngrabEvent();
3056 } else {
3057 event->setExclusiveGrabber(point, receiver);
3058 }
3059 }
3060 } else {
3061 // Restore accepted state if the event was not filtered.
3062 event->setAccepted(wasAccepted);
3063 }
3064 }
3065 } else if (isTouchEvent(event)) {
3066 const bool acceptsTouchEvents = receiver->acceptTouchEvents() || hasHandlers;
3067 auto device = event->device();
3068 if (device->type() == QInputDevice::DeviceType::TouchPad &&
3069 device->capabilities().testFlag(QInputDevice::Capability::MouseEmulation)) {
3070 qCDebug(lcTouchTarget) << "skipping filtering of synth-mouse event from" << device;
3071 } else if (acceptsTouchEvents || receiver->acceptedMouseButtons()) {
3072 // get a touch event customized for delivery to filteringParent
3073 // TODO should not be necessary? because QQuickDeliveryAgentPrivate::deliverMatchingPointsToItem() does it
3074 QMutableTouchEvent filteringParentTouchEvent;
3075 QQuickItemPrivate::get(receiver)->localizedTouchEvent(static_cast<QTouchEvent *>(event), true, &filteringParentTouchEvent);
3076 if (filteringParentTouchEvent.type() != QEvent::None) {
3077 qCDebug(lcTouch) << "letting parent" << filteringParent << "filter for" << receiver << &filteringParentTouchEvent;
3078 filtered = filteringParent->childMouseEventFilter(receiver, &filteringParentTouchEvent);
3079 if (filtered) {
3080 qCDebug(lcTouch) << "touch event intercepted by childMouseEventFilter of " << filteringParent;
3081 event->setAccepted(filteringParentTouchEvent.isAccepted());
3082 skipDelivery.append(filteringParent);
3083 if (event->isAccepted()) {
3084 for (auto point : filteringParentTouchEvent.points()) {
3085 const QQuickItem *exclusiveGrabber = qobject_cast<const QQuickItem *>(event->exclusiveGrabber(point));
3086 // Transfer the grab to the filtering parent unless the current exclusive grabber has
3087 // keepTouchGrab set AND the filtering parent is not an ancestor of that grabber.
3088 // If filteringParent is an ancestor of exclusiveGrabber (e.g. PinchArea wrapping a Flickable),
3089 // allow the transfer: presumably the user intended the parent to intercept multi-touch gestures.
3090 // But only if the grabber itself accepts touch events — meaning it set keepTouchGrab
3091 // on its own behalf (like Flickable). If the grabber doesn't accept touch events
3092 // (like a passive QQuickText), keepTouchGrab was set by the filtering parent's
3093 // childMouseEventFilter to maintain the filter-based event routing pattern
3094 // (e.g. SplitView sets a handle child as grabber to keep receiving filter calls).
3095 const bool grabberInsideFilteringParent = exclusiveGrabber &&
3096 exclusiveGrabber->acceptTouchEvents() &&
3097 filteringParent->isAncestorOf(const_cast<QQuickItem *>(exclusiveGrabber));
3098 if (!exclusiveGrabber || !exclusiveGrabber->keepTouchGrab() || grabberInsideFilteringParent)
3099 event->setExclusiveGrabber(point, filteringParent);
3100 }
3101 // QPointerEvent::setAccepted(true) marks all individual QEventPoints as accepted,
3102 // which causes localizedTouchEvent() to skip them (line 9577: if (p.isAccepted()) continue).
3103 // This would prevent any ancestor filtering parent (e.g. PinchArea wrapping this Flickable)
3104 // from seeing the points in the recursive sendFilteredPointerEventImpl() call below.
3105 // Reset per-point accepted state so ancestor filters can still see all relevant points.
3106 // The overall event->isAccepted() remains true to stop non-filter item delivery.
3107 for (int i = 0; i < event->pointCount(); ++i)
3108 event->point(i).setAccepted(false);
3109 }
3110 } else if (Q_LIKELY(QCoreApplication::testAttribute(Qt::AA_SynthesizeMouseForUnhandledTouchEvents)) &&
3111 !filteringParent->acceptTouchEvents()) {
3112 qCDebug(lcTouch) << "touch event NOT intercepted by childMouseEventFilter of " << filteringParent
3113 << "; accepts touch?" << filteringParent->acceptTouchEvents()
3114 << "receiver accepts touch?" << acceptsTouchEvents
3115 << "so, letting parent filter a synth-mouse event";
3116 // filteringParent didn't filter the touch event. Give it a chance to filter a synthetic mouse event.
3117 for (auto &tp : filteringParentTouchEvent.points()) {
3118 QEvent::Type t;
3119 switch (tp.state()) {
3120 case QEventPoint::State::Pressed:
3121 t = QEvent::MouseButtonPress;
3122 break;
3123 case QEventPoint::State::Released:
3124 t = QEvent::MouseButtonRelease;
3125 break;
3126 case QEventPoint::State::Stationary:
3127 continue;
3128 default:
3129 t = QEvent::MouseMove;
3130 break;
3131 }
3132
3133 bool touchMouseUnset = (touchMouseId == -1);
3134 // Only deliver mouse event if it is the touchMouseId or it could become the touchMouseId
3135 if (touchMouseUnset || touchMouseId == tp.id()) {
3136 // convert filteringParentTouchEvent (which is already transformed wrt local position, velocity, etc.)
3137 // into a synthetic mouse event, and let childMouseEventFilter() have another chance with that
3138 QMutableSinglePointEvent mouseEvent;
3139 touchToMouseEvent(t, tp, &filteringParentTouchEvent, &mouseEvent);
3140 // If a filtering item calls QQuickWindow::mouseGrabberItem(), it should
3141 // report the touchpoint's grabber. Whenever we send a synthetic mouse event,
3142 // touchMouseId and touchMouseDevice must be set, even if it's only temporarily and isn't grabbed.
3143 touchMouseId = tp.id();
3144 touchMouseDevice = event->pointingDevice();
3145 filtered = filteringParent->childMouseEventFilter(receiver, &mouseEvent);
3146 if (filtered) {
3147 qCDebug(lcTouch) << "touch event intercepted as synth mouse event by childMouseEventFilter of " << filteringParent;
3148 event->setAccepted(mouseEvent.isAccepted());
3149 skipDelivery.append(filteringParent);
3150 if (event->isAccepted() && event->isBeginEvent()) {
3151 qCDebug(lcTouchTarget) << "TP (mouse)" << Qt::hex << tp.id() << "->" << filteringParent;
3152 filteringParentTouchEvent.setExclusiveGrabber(tp, filteringParent);
3153 touchMouseUnset = false; // We want to leave touchMouseId and touchMouseDevice set
3154 filteringParent->grabMouse();
3155 }
3156 }
3157 if (touchMouseUnset)
3158 // Now that we're done sending a synth mouse event, and it wasn't grabbed,
3159 // the touchpoint is no longer acting as a synthetic mouse. Restore previous state.
3160 cancelTouchMouseSynthesis();
3161 mouseEvent.point(0).setAccepted(false); // because touchToMouseEvent() set it true
3162 // Only one touchpoint can be treated as a synthetic mouse, so after childMouseEventFilter
3163 // has been called once, we're done with this loop over the touchpoints.
3164 break;
3165 }
3166 }
3167 }
3168 }
3169 }
3170 }
3171 }
3172 return sendFilteredPointerEventImpl(event, receiver, filteringParent->parentItem()) || filtered;
3173}
3174
3175/*! \internal
3176 Allow \a filteringParent to filter \a event on behalf of \a receiver, via
3177 QQuickItem::childMouseEventFilter(). This happens right \e before we would
3178 send \a event to \a receiver.
3179
3180 Returns \c true only if \a event has been intercepted (by \a filteringParent
3181 or some other filtering ancestor) and should \e not be sent to \a receiver.
3182
3183 Unlike sendFilteredPointerEvent(), this version does not synthesize a
3184 mouse event from touch (presumably it's already an actual mouse event).
3185*/
3186bool QQuickDeliveryAgentPrivate::sendFilteredMouseEvent(QEvent *event, QQuickItem *receiver, QQuickItem *filteringParent)
3187{
3188 if (!filteringParent)
3189 return false;
3190
3191 QQuickItemPrivate *filteringParentPrivate = QQuickItemPrivate::get(filteringParent);
3192 if (filteringParentPrivate->replayingPressEvent)
3193 return false;
3194
3195 bool filtered = false;
3196 if (filteringParentPrivate->filtersChildMouseEvents && !hasFiltered.contains(filteringParent)) {
3197 hasFiltered.append(filteringParent);
3198 if (filteringParent->childMouseEventFilter(receiver, event)) {
3199 filtered = true;
3200 skipDelivery.append(filteringParent);
3201 }
3202 qCDebug(lcMouseTarget) << "for" << receiver << filteringParent << "childMouseEventFilter ->" << filtered;
3203 }
3204
3205 return sendFilteredMouseEvent(event, receiver, filteringParent->parentItem()) || filtered;
3206}
3207
3208/*! \internal
3209 Returns \c true if the movement delta \a d in pixels along the \a axis
3210 exceeds \a startDragThreshold if it is set, or QStyleHints::startDragDistance();
3211 \e or, if QEventPoint::velocity() of \a event exceeds QStyleHints::startDragVelocity().
3212
3213 \sa QQuickPointerHandlerPrivate::dragOverThreshold()
3214*/
3215bool QQuickDeliveryAgentPrivate::dragOverThreshold(qreal d, Qt::Axis axis, QMouseEvent *event, int startDragThreshold)
3216{
3217 QStyleHints *styleHints = QGuiApplication::styleHints();
3218 bool dragVelocityLimitAvailable = event->device()->capabilities().testFlag(QInputDevice::Capability::Velocity)
3219 && styleHints->startDragVelocity();
3220 bool overThreshold = qAbs(d) > (startDragThreshold >= 0 ? startDragThreshold : styleHints->startDragDistance());
3221 if (dragVelocityLimitAvailable) {
3222 QVector2D velocityVec = event->point(0).velocity();
3223 qreal velocity = axis == Qt::XAxis ? velocityVec.x() : velocityVec.y();
3224 overThreshold |= qAbs(velocity) > styleHints->startDragVelocity();
3225 }
3226 return overThreshold;
3227}
3228
3229/*! \internal
3230 Returns \c true if the movement delta \a d in pixels along the \a axis
3231 exceeds \a startDragThreshold if it is set, or QStyleHints::startDragDistance();
3232 \e or, if QEventPoint::velocity() of \a tp exceeds QStyleHints::startDragVelocity().
3233
3234 \sa QQuickPointerHandlerPrivate::dragOverThreshold()
3235*/
3236bool QQuickDeliveryAgentPrivate::dragOverThreshold(qreal d, Qt::Axis axis, const QEventPoint &tp, int startDragThreshold)
3237{
3238 QStyleHints *styleHints = qApp->styleHints();
3239 bool overThreshold = qAbs(d) > (startDragThreshold >= 0 ? startDragThreshold : styleHints->startDragDistance());
3240 const bool dragVelocityLimitAvailable = (styleHints->startDragVelocity() > 0);
3241 if (!overThreshold && dragVelocityLimitAvailable) {
3242 qreal velocity = axis == Qt::XAxis ? tp.velocity().x() : tp.velocity().y();
3243 overThreshold |= qAbs(velocity) > styleHints->startDragVelocity();
3244 }
3245 return overThreshold;
3246}
3247
3248/*! \internal
3249 Returns \c true if the movement \a delta in pixels exceeds QStyleHints::startDragDistance().
3250
3251 \sa QQuickDeliveryAgentPrivate::dragOverThreshold()
3252*/
3253bool QQuickDeliveryAgentPrivate::dragOverThreshold(QVector2D delta)
3254{
3255 int threshold = qApp->styleHints()->startDragDistance();
3256 return qAbs(delta.x()) > threshold || qAbs(delta.y()) > threshold;
3257}
3258
3259/*!
3260 \internal
3261 Returns all items that could potentially want \a event.
3262
3263 (Similar to \l pointerTargets(), necessary because QContextMenuEvent is not
3264 a QPointerEvent.)
3265*/
3266QList<QQuickItem *> QQuickDeliveryAgentPrivate::contextMenuTargets(QQuickItem *item, const QContextMenuEvent *event) const
3267{
3268 auto predicate = [](QQuickItem *, const QEvent *) -> std::optional<bool> {
3269 return std::nullopt;
3270 };
3271
3272 const auto pos = event->pos().isNull() ? activeFocusItem->mapToScene({}).toPoint() : event->pos();
3273 if (event->pos().isNull())
3274 qCDebug(lcContextMenu) << "for QContextMenuEvent, active focus item is" << activeFocusItem << "@" << pos;
3275 return eventTargets(item, event, -1, pos, pos, predicate);
3276}
3277
3278/*!
3279 \internal
3280
3281 Based on \l deliverPointerEvent().
3282*/
3283void QQuickDeliveryAgentPrivate::deliverContextMenuEvent(QContextMenuEvent *event)
3284{
3285 skipDelivery.clear();
3286 QList<QQuickItem *> targetItems = contextMenuTargets(rootItem, event);
3287 qCDebug(lcContextMenu) << "delivering context menu event" << event << "to" << targetItems.size() << "target item(s)";
3288 QList<QPointer<QQuickItem>> safeTargetItems(targetItems.begin(), targetItems.end());
3289 for (auto &item : safeTargetItems) {
3290 qCDebug(lcContextMenu) << "- attempting to deliver to" << item;
3291 if (item.isNull())
3292 continue;
3293 // failsafe: when items get into a subscene somehow, ensure that QQuickItemPrivate::deliveryAgent() can find it
3294 if (isSubsceneAgent)
3295 QQuickItemPrivate::get(item)->maybeHasSubsceneDeliveryAgent = true;
3296
3297 QCoreApplication::sendEvent(item, event);
3298 if (event->isAccepted())
3299 return;
3300 }
3301}
3302
3303#ifndef QT_NO_DEBUG_STREAM
3304QDebug operator<<(QDebug debug, const QQuickDeliveryAgent *da)
3305{
3306 QDebugStateSaver saver(debug);
3307 debug.nospace();
3308 if (!da) {
3309 debug << "QQuickDeliveryAgent(0)";
3310 return debug;
3311 }
3312
3313 debug << "QQuickDeliveryAgent(";
3314 if (!da->objectName().isEmpty())
3315 debug << da->objectName() << ' ';
3316 auto root = da->rootItem();
3317 if (Q_LIKELY(root)) {
3318 debug << "root=" << root->metaObject()->className();
3319 if (!root->objectName().isEmpty())
3320 debug << ' ' << root->objectName();
3321 } else {
3322 debug << "root=0";
3323 }
3324 debug << ')';
3325 return debug;
3326}
3327#endif
3328
3329QT_END_NAMESPACE
3330
3331#include "moc_qquickdeliveryagent_p.cpp"
QDebug operator<<(QDebug dbg, const NSObject *nsObject)
Definition qcore_mac.mm:209
Q_LOGGING_CATEGORY(lcEventDispatcher, "qt.eventdispatcher")
static bool isEndOfFingerTouchSequence(const QTouchEvent *event)
static void markHovered(QQuickDeliveryAgentPrivate::HoverItemState &state, uint hoverId, bool fromTouch)
static QQuickDeliveryAgentPrivate::HoverItems::iterator findHoverStateByItem(QQuickDeliveryAgentPrivate::HoverItems &hoverItems, QQuickItem *item)
static QQuickDeliveryAgentPrivate::HoverItems::const_iterator findHoverStateByItem(const QQuickDeliveryAgentPrivate::HoverItems &hoverItems, const QQuickItem *item)
static bool allowSyntheticRightClick()
static bool windowHasFocus(QQuickWindow *win)
static QQuickItem * findFurthestFocusScopeAncestor(QQuickItem *item)
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)