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