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

source code of qtdeclarative/src/quick/util/qquickdeliveryagent.cpp