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