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 | |
23 | QT_BEGIN_NAMESPACE |
24 | |
25 | Q_LOGGING_CATEGORY(lcTouch, "qt.quick.touch" ) |
26 | Q_LOGGING_CATEGORY(lcTouchCmprs, "qt.quick.touch.compression" ) |
27 | Q_LOGGING_CATEGORY(lcTouchTarget, "qt.quick.touch.target" ) |
28 | Q_LOGGING_CATEGORY(lcMouse, "qt.quick.mouse" ) |
29 | Q_LOGGING_CATEGORY(lcMouseTarget, "qt.quick.mouse.target" ) |
30 | Q_LOGGING_CATEGORY(lcTablet, "qt.quick.tablet" ) |
31 | Q_LOGGING_CATEGORY(lcPtr, "qt.quick.pointer" ) |
32 | Q_LOGGING_CATEGORY(lcPtrLoc, "qt.quick.pointer.localization" ) |
33 | Q_LOGGING_CATEGORY(lcPtrGrab, "qt.quick.pointer.grab" ) |
34 | Q_LOGGING_CATEGORY(lcWheelTarget, "qt.quick.wheel.target" ) |
35 | Q_LOGGING_CATEGORY(lcGestureTarget, "qt.quick.gesture.target" ) |
36 | Q_LOGGING_CATEGORY(lcHoverTrace, "qt.quick.hover.trace" ) |
37 | Q_LOGGING_CATEGORY(lcFocus, "qt.quick.focus" ) |
38 | |
39 | extern 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 | |
41 | bool QQuickDeliveryAgentPrivate::subsceneAgentsExist(false); |
42 | QQuickDeliveryAgent *QQuickDeliveryAgentPrivate::currentEventDeliveryAgent(nullptr); |
43 | |
44 | static 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 | |
56 | void 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 | |
73 | bool 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 | |
99 | QPointerEvent *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 | */ |
111 | QPointingDevicePrivate::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 | |
119 | void QQuickDeliveryAgentPrivate::cancelTouchMouseSynthesis() |
120 | { |
121 | qCDebug(lcTouchTarget) << "id" << touchMouseId << "on" << touchMouseDevice; |
122 | touchMouseId = -1; |
123 | touchMouseDevice = nullptr; |
124 | } |
125 | |
126 | bool 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 | */ |
253 | void 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 | */ |
293 | void 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 | |
302 | static 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 |
310 | static 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 | */ |
330 | void 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 | |
458 | void 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 | |
558 | void 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 | |
566 | void 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 | |
591 | bool 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 | |
615 | void 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 | */ |
633 | QQuickDeliveryAgent *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 | */ |
647 | QQuickDeliveryAgent::QQuickDeliveryAgent(QQuickItem *rootItem) |
648 | : QObject(*new QQuickDeliveryAgentPrivate(rootItem), rootItem) |
649 | { |
650 | } |
651 | |
652 | QQuickDeliveryAgent::~QQuickDeliveryAgent() |
653 | { |
654 | } |
655 | |
656 | QQuickDeliveryAgent::Transform::~Transform() |
657 | { |
658 | } |
659 | |
660 | QQuickItem *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 | */ |
671 | QQuickDeliveryAgent::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 | */ |
682 | void 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 | |
693 | bool 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 | |
836 | void 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 | |
866 | QQuickDeliveryAgentPrivate::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 | |
879 | QQuickDeliveryAgentPrivate::~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 | */ |
895 | QPointerEvent *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 | |
907 | void 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 | |
956 | bool 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 |
986 | bool 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 | */ |
1078 | bool 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 | */ |
1128 | bool 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 |
1216 | bool 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 | |
1257 | bool 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 | |
1272 | void 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 | */ |
1293 | void 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 | |
1324 | void QQuickDeliveryAgentPrivate::handleWindowHidden(QQuickWindow *win) |
1325 | { |
1326 | qCDebug(lcFocus) << "hidden" << win->title(); |
1327 | clearHover(); |
1328 | lastMousePosition = QPointF(); |
1329 | } |
1330 | |
1331 | bool 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 | */ |
1346 | void 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 | |
1356 | QList<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 | |
1368 | bool 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 | |
1377 | bool 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 | |
1386 | bool 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 | |
1399 | bool QQuickDeliveryAgentPrivate::isMouseOrWheelEvent(const QPointerEvent *ev) |
1400 | { |
1401 | return isMouseEvent(ev) || ev->type() == QEvent::Wheel; |
1402 | } |
1403 | |
1404 | bool 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 | |
1416 | bool 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 | |
1429 | bool 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 | |
1443 | bool 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 | |
1450 | bool QQuickDeliveryAgentPrivate::isSynthMouse(const QPointerEvent *ev) |
1451 | { |
1452 | return (!isEventFromMouseOrTouchpad(ev) && isMouseEvent(ev)); |
1453 | } |
1454 | |
1455 | QQuickPointingDeviceExtra *QQuickDeliveryAgentPrivate::(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 = 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 | */ |
1493 | bool 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 |
1573 | void 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 | |
1605 | void 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 | |
1661 | void 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 | |
1701 | void 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 | |
1797 | void 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 | |
1807 | void 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? |
1906 | QVector<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 |
1957 | QVector<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 | */ |
1980 | void 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 |
2059 | bool 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 | |
2120 | void 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) |
2236 | void 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: ¤tGrabItems, |
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 | |
2327 | bool 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 | |
2422 | bool QQuickDeliveryAgentPrivate::sendFilteredPointerEvent(QPointerEvent *event, QQuickItem *receiver, QQuickItem *filteringParent) |
2423 | { |
2424 | return sendFilteredPointerEventImpl(event, receiver, filteringParent: filteringParent ? filteringParent : receiver->parentItem()); |
2425 | } |
2426 | |
2427 | bool 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 | |
2549 | bool 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 | |
2571 | bool 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 | |
2585 | bool 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 | |
2597 | bool 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 |
2604 | QDebug 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 | |
2629 | QT_END_NAMESPACE |
2630 | |
2631 | #include "moc_qquickdeliveryagent_p.cpp" |
2632 | |