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