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