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