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