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

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