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