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