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