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