diff options
24 files changed, 1149 insertions, 528 deletions
diff --git a/src/quick/CMakeLists.txt b/src/quick/CMakeLists.txt index e14db9ec59..4583a29433 100644 --- a/src/quick/CMakeLists.txt +++ b/src/quick/CMakeLists.txt @@ -169,7 +169,7 @@ qt_internal_add_module(Quick util/qquickanimatorjob.cpp util/qquickanimatorjob_p.h util/qquickapplication.cpp util/qquickapplication_p.h util/qquickbehavior.cpp util/qquickbehavior_p.h - util/qquickdeliveryagent.cpp + util/qquickdeliveryagent.cpp util/qquickdeliveryagent_p.h util/qquickdeliveryagent_p_p.h util/qquickfontloader.cpp util/qquickfontloader_p.h util/qquickfontmetrics.cpp util/qquickfontmetrics_p.h util/qquickforeignutils_p.h diff --git a/src/quick/handlers/qquickdraghandler.cpp b/src/quick/handlers/qquickdraghandler.cpp index 0794b86b11..e5e9b03f32 100644 --- a/src/quick/handlers/qquickdraghandler.cpp +++ b/src/quick/handlers/qquickdraghandler.cpp @@ -247,7 +247,7 @@ void QQuickDragHandler::handlePointerEventImpl(QPointerEvent *event) m_pressedInsideTarget &= target()->contains(localPressPos); m_pressTargetPos = targetCentroidPosition(); } - // QQuickWindowPrivate::deliverToPassiveGrabbers() skips subsequent delivery if the event is filtered. + // QQuickDeliveryAgentPrivate::deliverToPassiveGrabbers() skips subsequent delivery if the event is filtered. // (That affects behavior for mouse but not for touch, because Flickable only handles mouse.) // So we have to compensate by accepting the event here to avoid any parent Flickable from // getting the event via direct delivery and grabbing too soon. diff --git a/src/quick/handlers/qquickpointerhandler.cpp b/src/quick/handlers/qquickpointerhandler.cpp index cae700b1c8..f9ae620c75 100644 --- a/src/quick/handlers/qquickpointerhandler.cpp +++ b/src/quick/handlers/qquickpointerhandler.cpp @@ -40,6 +40,8 @@ #include "qquickpointerhandler_p.h" #include "qquickpointerhandler_p_p.h" #include <QtQuick/private/qquickitem_p.h> +#include <QtQuick/private/qquickhandlerpoint_p.h> +#include <QtQuick/private/qquickdeliveryagent_p_p.h> #include <QtGui/private/qinputdevice_p.h> QT_BEGIN_NAMESPACE @@ -361,8 +363,9 @@ bool QQuickPointerHandler::approveGrabTransition(QPointerEvent *event, const QEv // Flickable's wishes in that case, because then it would never have a chance. if (existingItemGrabber->keepMouseGrab() && !(existingItemGrabber->filtersChildMouseEvents() && existingItemGrabber->isAncestorOf(parentItem()))) { - QQuickWindowPrivate *winPriv = QQuickWindowPrivate::get(parentItem()->window()); - if (winPriv->isDeliveringTouchAsMouse() && point.id() == winPriv->touchMouseId) { + auto da = QQuickItemPrivate::get(parentItem())->deliveryAgentPrivate(); + Q_ASSERT(da); + if (da->isDeliveringTouchAsMouse() && point.id() == da->touchMouseId) { qCDebug(lcPointerHandlerGrab) << this << "wants to grab touchpoint" << point.id() << "but declines to steal grab from touch-mouse grabber with keepMouseGrab=true" << existingItemGrabber; allowed = false; @@ -752,13 +755,7 @@ bool QQuickPointerHandlerPrivate::dragOverThreshold(const QEventPoint &point) co QVector<QObject *> &QQuickPointerHandlerPrivate::deviceDeliveryTargets(const QInputDevice *device) { - QInputDevicePrivate *devPriv = QInputDevicePrivate::get(const_cast<QInputDevice *>(device)); - if (devPriv->qqExtra) - return *static_cast<QVector<QObject *>*>(devPriv->qqExtra); - auto targets = new QVector<QObject *>; - devPriv->qqExtra = targets; - QObject::connect(device, &QObject::destroyed, [targets]() { delete targets; }); - return *targets; + return QQuickDeliveryAgentPrivate::deviceExtra(device)->deliveryTargets; } QT_END_NAMESPACE diff --git a/src/quick/handlers/qquickpointerhandler_p.h b/src/quick/handlers/qquickpointerhandler_p.h index 8d7fdcdcad..f7e7b83164 100644 --- a/src/quick/handlers/qquickpointerhandler_p.h +++ b/src/quick/handlers/qquickpointerhandler_p.h @@ -167,6 +167,7 @@ protected: bool parentContains(const QEventPoint &point) const; bool parentContains(const QPointF &scenePosition) const; + friend class QQuickDeliveryAgentPrivate; friend class QQuickItemPrivate; friend class QQuickWindowPrivate; diff --git a/src/quick/items/qquickdrag.cpp b/src/quick/items/qquickdrag.cpp index a980f83aba..e6ffcee2aa 100644 --- a/src/quick/items/qquickdrag.cpp +++ b/src/quick/items/qquickdrag.cpp @@ -240,7 +240,7 @@ void QQuickDragAttachedPrivate::deliverEvent(QQuickWindow *window, QEvent *event { Q_ASSERT(!inEvent); inEvent = true; - QQuickWindowPrivate::get(window)->deliverDragEvent(&dragGrabber, event); + QQuickWindowPrivate::get(window)->deliveryAgentPrivate()->deliverDragEvent(&dragGrabber, event); inEvent = false; } diff --git a/src/quick/items/qquickflickable.cpp b/src/quick/items/qquickflickable.cpp index 62bc078f4d..b02840208d 100644 --- a/src/quick/items/qquickflickable.cpp +++ b/src/quick/items/qquickflickable.cpp @@ -1341,9 +1341,9 @@ void QQuickFlickablePrivate::handleMoveEvent(QPointerEvent *event) QVector2D velocity = event->point(0).velocity(); if (q->yflick()) - overThreshold |= QQuickWindowPrivate::dragOverThreshold(deltas.y(), Qt::YAxis, firstPoint); + overThreshold |= QQuickDeliveryAgentPrivate::dragOverThreshold(deltas.y(), Qt::YAxis, firstPoint); if (q->xflick()) - overThreshold |= QQuickWindowPrivate::dragOverThreshold(deltas.x(), Qt::XAxis, firstPoint); + overThreshold |= QQuickDeliveryAgentPrivate::dragOverThreshold(deltas.x(), Qt::XAxis, firstPoint); drag(currentTimestamp, event->type(), pos, deltas, overThreshold, false, false, velocity); } @@ -1518,7 +1518,7 @@ void QQuickFlickable::touchEvent(QTouchEvent *event) auto &firstPoint = event->point(0); if (auto grabber = qmlobject_cast<QQuickItem *>(event->exclusiveGrabber(firstPoint))) { const auto localPos = grabber->mapFromScene(firstPoint.scenePosition()); - QScopedPointer<QPointerEvent> localizedEvent(QQuickWindowPrivate::clonePointerEvent(event, localPos)); + QScopedPointer<QPointerEvent> localizedEvent(QQuickDeliveryAgentPrivate::clonePointerEvent(event, localPos)); QCoreApplication::sendEvent(window(), localizedEvent.data()); } @@ -1762,7 +1762,7 @@ void QQuickFlickablePrivate::captureDelayedPress(QQuickItem *item, QPointerEvent if (!isInnermostPressDelay(item)) return; - delayedPressEvent = QQuickWindowPrivate::clonePointerEvent(event); + delayedPressEvent = QQuickDeliveryAgentPrivate::clonePointerEvent(event); delayedPressEvent->setAccepted(false); delayedPressTimer.start(pressDelay, q); qCDebug(lcReplay) << "begin press delay" << pressDelay << "ms with" << delayedPressEvent; @@ -1789,8 +1789,8 @@ void QQuickFlickablePrivate::replayDelayedPress() // If we have the grab, release before delivering the event if (QQuickWindow *window = q->window()) { - QQuickWindowPrivate *wpriv = QQuickWindowPrivate::get(window); - wpriv->allowChildEventFiltering = false; // don't allow re-filtering during replay + auto da = deliveryAgentPrivate(); + da->allowChildEventFiltering = false; // don't allow re-filtering during replay replayingPressEvent = true; auto &firstPoint = event->point(0); // At first glance, it's weird for delayedPressEvent to already have a grabber; @@ -1813,7 +1813,7 @@ void QQuickFlickablePrivate::replayDelayedPress() // We're done with replay, go back to normal delivery behavior replayingPressEvent = false; - wpriv->allowChildEventFiltering = true; + da->allowChildEventFiltering = true; } } } @@ -2577,7 +2577,7 @@ bool QQuickFlickable::filterPointerEvent(QQuickItem *receiver, QPointerEvent *ev bool stealThisEvent = d->stealMouse; bool receiverKeepsGrab = receiver && (receiver->keepMouseGrab() || receiver->keepTouchGrab()); if ((stealThisEvent || contains(localPos)) && (!receiver || !receiverKeepsGrab || receiverDisabled)) { - QScopedPointer<QPointerEvent> localizedEvent(QQuickWindowPrivate::clonePointerEvent(event, localPos)); + QScopedPointer<QPointerEvent> localizedEvent(QQuickDeliveryAgentPrivate::clonePointerEvent(event, localPos)); localizedEvent->setAccepted(false); switch (firstPoint.state()) { case QEventPoint::State::Updated: diff --git a/src/quick/items/qquickitem.cpp b/src/quick/items/qquickitem.cpp index 93512d70f8..f4eea2923a 100644 --- a/src/quick/items/qquickitem.cpp +++ b/src/quick/items/qquickitem.cpp @@ -91,6 +91,7 @@ QT_BEGIN_NAMESPACE Q_DECLARE_LOGGING_CATEGORY(lcMouseTarget) Q_DECLARE_LOGGING_CATEGORY(lcHoverTrace) +Q_DECLARE_LOGGING_CATEGORY(lcPtr) Q_DECLARE_LOGGING_CATEGORY(lcTransient) Q_LOGGING_CATEGORY(lcHandlerParent, "qt.quick.handler.parent") @@ -2684,8 +2685,9 @@ void QQuickItem::setParentItem(QQuickItem *parentItem) while (!scopeItem->isFocusScope() && scopeItem->parentItem()) scopeItem = scopeItem->parentItem(); if (d->window) { - QQuickWindowPrivate::get(d->window)->clearFocusInScope(scopeItem, scopeFocusedItem, Qt::OtherFocusReason, - QQuickWindowPrivate::DontChangeFocusProperty); + d->deliveryAgentPrivate()-> + clearFocusInScope(scopeItem, scopeFocusedItem, Qt::OtherFocusReason, + QQuickDeliveryAgentPrivate::DontChangeFocusProperty); if (scopeFocusedItem != this) QQuickItemPrivate::get(scopeFocusedItem)->updateSubFocusItem(this, true); } else { @@ -2760,8 +2762,9 @@ void QQuickItem::setParentItem(QQuickItem *parentItem) emit scopeFocusedItem->focusChanged(false); } else { if (d->window) { - QQuickWindowPrivate::get(d->window)->setFocusInScope(scopeItem, scopeFocusedItem, Qt::OtherFocusReason, - QQuickWindowPrivate::DontChangeFocusProperty); + d->deliveryAgentPrivate()-> + setFocusInScope(scopeItem, scopeFocusedItem, Qt::OtherFocusReason, + QQuickDeliveryAgentPrivate::DontChangeFocusProperty); } else { QQuickItemPrivate::get(scopeFocusedItem)->updateSubFocusItem(scopeItem, true); } @@ -3037,7 +3040,6 @@ void QQuickItemPrivate::derefWindow() window->unsetCursor(); } #endif - c->hoverItems.removeAll(q); if (itemNodeInstance) c->cleanup(itemNodeInstance); if (!parentItem) @@ -3194,6 +3196,7 @@ QQuickItemPrivate::QQuickItemPrivate() , replayingPressEvent(false) , touchEnabled(false) , hasCursorHandler(false) + , hasSubsceneDeliveryAgent(false) , dirtyAttributes(0) , nextDirtyItem(nullptr) , prevDirtyItem(nullptr) @@ -5195,6 +5198,57 @@ QPointF QQuickItemPrivate::adjustedPosForTransform(const QPointF ¢roidParent return pos; } +/*! \internal + Returns the delivery agent for the narrowest subscene containing this item, + but falls back to QQuickWindowPrivate::deliveryAgent if there are no subscenes. + + \note When a Qt Quick scene is shown in the usual way in its own window, + subscenes are ignored, and QQuickWindowPrivate::deliveryAgent is used. + Subscene delivery agents are used only in QtQuick 3D so far. +*/ +QQuickDeliveryAgent *QQuickItemPrivate::deliveryAgent() +{ + // optimization: don't go up the parent hierarchy if this item is not aware of being in a subscene + if (hasSubsceneDeliveryAgent) { + QQuickItemPrivate *p = this; + do { + if (p->extra.isAllocated()) { + if (auto da = p->extra->subsceneDeliveryAgent) + return da; + p = p->parentItem ? QQuickItemPrivate::get(p->parentItem) : nullptr; + } + } while (p); + } + if (window) + return QQuickWindowPrivate::get(window)->deliveryAgent; + return nullptr; +} + +QQuickDeliveryAgentPrivate *QQuickItemPrivate::deliveryAgentPrivate() +{ + auto da = deliveryAgent(); + return da ? static_cast<QQuickDeliveryAgentPrivate *>(QQuickDeliveryAgentPrivate::get(da)) : nullptr; +} + +/*! \internal + Ensures that this item, presumably the root of a sub-scene (e.g. because it + is mapped onto a 3D object in Qt Quick 3D), has a delivery agent to be used + when delivering events to the subscene: i.e. when the viewport delivers an + event to the subscene, or when the outer delivery agent delivers an update + to an item that grabbed during a previous subscene delivery. Creates a new + agent if it was not already created, and returns a pointer to the instance. +*/ +QQuickDeliveryAgent *QQuickItemPrivate::ensureSubsceneDeliveryAgent() +{ + Q_Q(QQuickItem); + hasSubsceneDeliveryAgent = true; + if (extra.isAllocated() && extra->subsceneDeliveryAgent) + return extra->subsceneDeliveryAgent; + extra.value().subsceneDeliveryAgent = new QQuickDeliveryAgent(q); + qCDebug(lcPtr) << "created new" << extra->subsceneDeliveryAgent; + return extra->subsceneDeliveryAgent; +} + bool QQuickItemPrivate::filterKeyEvent(QKeyEvent *e, bool post) { if (!extra.isAllocated() || !extra->keyHandler) @@ -6089,12 +6143,10 @@ bool QQuickItemPrivate::setEffectiveVisibleRecur(bool newEffectiveVisible) effectiveVisible = newEffectiveVisible; dirty(Visible); - if (parentItem) QQuickItemPrivate::get(parentItem)->dirty(ChildrenStackingChanged); - - if (window) { - QQuickWindowPrivate *windowPriv = QQuickWindowPrivate::get(window); - windowPriv->removeGrabber(q, true, true, true); - } + if (parentItem) + QQuickItemPrivate::get(parentItem)->dirty(ChildrenStackingChanged); + if (window) + deliveryAgentPrivate()->removeGrabber(q, true, true, true); bool childVisibilityChanged = false; for (int ii = 0; ii < childItems.count(); ++ii) @@ -6138,12 +6190,13 @@ void QQuickItemPrivate::setEffectiveEnableRecur(QQuickItem *scope, bool newEffec effectiveEnable = newEffectiveEnable; - if (window) { - QQuickWindowPrivate *windowPriv = QQuickWindowPrivate::get(window); - windowPriv->removeGrabber(q, true, true, true); + QQuickDeliveryAgentPrivate *da = deliveryAgentPrivate(); + if (da) { + da->removeGrabber(q, true, true, true); if (scope && !effectiveEnable && activeFocus) { - windowPriv->clearFocusInScope( - scope, q, Qt::OtherFocusReason, QQuickWindowPrivate::DontChangeFocusProperty | QQuickWindowPrivate::DontChangeSubFocusItem); + da->clearFocusInScope(scope, q, Qt::OtherFocusReason, + QQuickDeliveryAgentPrivate::DontChangeFocusProperty | + QQuickDeliveryAgentPrivate::DontChangeSubFocusItem); } } @@ -6152,9 +6205,10 @@ void QQuickItemPrivate::setEffectiveEnableRecur(QQuickItem *scope, bool newEffec (flags & QQuickItem::ItemIsFocusScope) && scope ? q : scope, newEffectiveEnable); } - if (window && scope && effectiveEnable && focus) { - QQuickWindowPrivate::get(window)->setFocusInScope( - scope, q, Qt::OtherFocusReason, QQuickWindowPrivate::DontChangeFocusProperty | QQuickWindowPrivate::DontChangeSubFocusItem); + if (scope && effectiveEnable && focus && da) { + da->setFocusInScope(scope, q, Qt::OtherFocusReason, + QQuickDeliveryAgentPrivate::DontChangeFocusProperty | + QQuickDeliveryAgentPrivate::DontChangeSubFocusItem); } itemChange(QQuickItem::ItemEnabledHasChanged, effectiveEnable); @@ -7252,10 +7306,12 @@ void QQuickItem::setFocus(bool focus, Qt::FocusReason reason) scope = scope->parentItem(); if (d->window) { if (reason != Qt::PopupFocusReason) { + auto da = d->deliveryAgentPrivate(); + Q_ASSERT(da); if (focus) - QQuickWindowPrivate::get(d->window)->setFocusInScope(scope, this, reason); + da->setFocusInScope(scope, this, reason); else - QQuickWindowPrivate::get(d->window)->clearFocusInScope(scope, this, reason); + da->clearFocusInScope(scope, this, reason); } } else { // do the focus changes from setFocusInScope/clearFocusInScope that are @@ -7276,7 +7332,7 @@ void QQuickItem::setFocus(bool focus, Qt::FocusReason reason) changed << this; emit focusChanged(focus); - QQuickWindowPrivate::notifyFocusChangesRecur(changed.data(), changed.count() - 1); + QQuickDeliveryAgentPrivate::notifyFocusChangesRecur(changed.data(), changed.count() - 1); } } else { QVarLengthArray<QQuickItem *, 20> changed; @@ -7291,7 +7347,7 @@ void QQuickItem::setFocus(bool focus, Qt::FocusReason reason) changed << this; emit focusChanged(focus); - QQuickWindowPrivate::notifyFocusChangesRecur(changed.data(), changed.count() - 1); + QQuickDeliveryAgentPrivate::notifyFocusChangesRecur(changed.data(), changed.count() - 1); } } @@ -7418,7 +7474,7 @@ bool QQuickItem::isUnderMouse() const // QQuickWindow handles QEvent::Leave to reset the lastMousePosition // FIXME: Using QPointF() as the reset value means an item will not be // under the mouse if the mouse is at 0,0 of the window. - if (QQuickWindowPrivate::get(d->window)->lastMousePosition == QPointF()) + if (const_cast<QQuickItemPrivate *>(d)->deliveryAgentPrivate()->lastMousePosition == QPointF()) return false; QPointF cursorPos = QGuiApplicationPrivate::lastCursorPosition; @@ -7703,13 +7759,14 @@ void QQuickItem::grabMouse() Q_D(QQuickItem); if (!d->window) return; - QQuickWindowPrivate *windowPriv = QQuickWindowPrivate::get(d->window); - auto eventInDelivery = windowPriv->eventInDelivery(); + auto da = d->deliveryAgentPrivate(); + Q_ASSERT(da); + auto eventInDelivery = da->eventInDelivery(); if (!eventInDelivery) { qWarning() << "cannot grab mouse: no event is currently being delivered"; return; } - auto epd = windowPriv->mousePointData(); + auto epd = da->mousePointData(); eventInDelivery->setExclusiveGrabber(epd->eventPoint, this); } @@ -7729,14 +7786,15 @@ void QQuickItem::ungrabMouse() Q_D(QQuickItem); if (!d->window) return; - QQuickWindowPrivate *windowPriv = QQuickWindowPrivate::get(d->window); - auto eventInDelivery = windowPriv->eventInDelivery(); + auto da = d->deliveryAgentPrivate(); + Q_ASSERT(da); + auto eventInDelivery = da->eventInDelivery(); if (!eventInDelivery) { // do it the expensive way - windowPriv->removeGrabber(this); + da->removeGrabber(this); return; } - const auto &eventPoint = windowPriv->mousePointData()->eventPoint; + const auto &eventPoint = da->mousePointData()->eventPoint; if (eventInDelivery->exclusiveGrabber(eventPoint) == this) eventInDelivery->setExclusiveGrabber(eventPoint, nullptr); } @@ -7785,8 +7843,8 @@ void QQuickItem::setKeepMouseGrab(bool keep) */ void QQuickItem::grabTouchPoints(const QList<int> &ids) { - QQuickWindowPrivate *windowPriv = QQuickWindowPrivate::get(window()); - auto event = windowPriv->eventInDelivery(); + Q_D(QQuickItem); + auto event = d->deliveryAgentPrivate()->eventInDelivery(); if (Q_UNLIKELY(!event)) { qWarning() << "cannot grab: no event is currently being delivered"; return; @@ -7806,8 +7864,7 @@ void QQuickItem::ungrabTouchPoints() Q_D(QQuickItem); if (!d->window) return; - QQuickWindowPrivate *windowPriv = QQuickWindowPrivate::get(d->window); - windowPriv->removeGrabber(this, false, true); + d->deliveryAgentPrivate()->removeGrabber(this, false, true); } /*! diff --git a/src/quick/items/qquickitem.h b/src/quick/items/qquickitem.h index c7bc2f54c3..887318c23f 100644 --- a/src/quick/items/qquickitem.h +++ b/src/quick/items/qquickitem.h @@ -461,8 +461,8 @@ private: Q_PRIVATE_SLOT(d_func(), void _q_resourceObjectDeleted(QObject *)) Q_PRIVATE_SLOT(d_func(), quint64 _q_createJSWrapper(QV4::ExecutionEngine *)) - friend class QQuickWindow; friend class QQuickWindowPrivate; + friend class QQuickDeliveryAgentPrivate; friend class QSGRenderer; friend class QAccessibleQuickItem; friend class QQuickAccessibleAttached; diff --git a/src/quick/items/qquickitem_p.h b/src/quick/items/qquickitem_p.h index b97b9cefd5..caa6edb15b 100644 --- a/src/quick/items/qquickitem_p.h +++ b/src/quick/items/qquickitem_p.h @@ -388,6 +388,8 @@ public: QSGOpacityNode *opacityNode; QQuickDefaultClipNode *clipNode; QSGRootNode *rootNode; + // subsceneDeliveryAgent is set only if this item is the root of a subscene, not on all items within. + QQuickDeliveryAgent *subsceneDeliveryAgent = nullptr; // Mask contains() method QMetaMethod maskContains; @@ -480,6 +482,8 @@ public: bool replayingPressEvent:1; bool touchEnabled:1; bool hasCursorHandler:1; + // set true when this item expects events via a subscene delivery agent; false otherwise + bool hasSubsceneDeliveryAgent:1; enum DirtyType { TransformOrigin = 0x00000001, @@ -607,6 +611,10 @@ public: qreal startScale, qreal activeScale, qreal startRotation, qreal activeRotation); + QQuickDeliveryAgent *deliveryAgent(); + QQuickDeliveryAgentPrivate *deliveryAgentPrivate(); + QQuickDeliveryAgent *ensureSubsceneDeliveryAgent(); + void deliverKeyEvent(QKeyEvent *); bool filterKeyEvent(QKeyEvent *, bool post); #if QT_CONFIG(im) diff --git a/src/quick/items/qquickmousearea.cpp b/src/quick/items/qquickmousearea.cpp index 037eddd6a8..1d4c707c76 100644 --- a/src/quick/items/qquickmousearea.cpp +++ b/src/quick/items/qquickmousearea.cpp @@ -752,10 +752,10 @@ void QQuickMouseArea::mouseMoveEvent(QMouseEvent *event) d->lastPos = mapFromScene(d->lastScenePos); } - bool dragOverThresholdX = QQuickWindowPrivate::dragOverThreshold(dragPos.x() - startPos.x(), - Qt::XAxis, event, d->drag->threshold()); - bool dragOverThresholdY = QQuickWindowPrivate::dragOverThreshold(dragPos.y() - startPos.y(), - Qt::YAxis, event, d->drag->threshold()); + bool dragOverThresholdX = QQuickDeliveryAgentPrivate::dragOverThreshold(dragPos.x() - startPos.x(), + Qt::XAxis, event, d->drag->threshold()); + bool dragOverThresholdY = QQuickDeliveryAgentPrivate::dragOverThreshold(dragPos.y() - startPos.y(), + Qt::YAxis, event, d->drag->threshold()); if (!d->overThreshold && (((targetPos.x() != boundedDragPos.x()) && dragOverThresholdX) || ((targetPos.y() != boundedDragPos.y()) && dragOverThresholdY))) diff --git a/src/quick/items/qquickmultipointtoucharea.cpp b/src/quick/items/qquickmultipointtoucharea.cpp index ed424a3763..642b5ca047 100644 --- a/src/quick/items/qquickmultipointtoucharea.cpp +++ b/src/quick/items/qquickmultipointtoucharea.cpp @@ -578,7 +578,6 @@ void QQuickMultiPointTouchArea::updateTouchData(QEvent *event) clearTouchLists(); QList<QEventPoint> touchPoints; - QQuickWindowPrivate *windowPriv = QQuickWindowPrivate::get(window()); QPointingDevice *dev = nullptr; switch (event->type()) { @@ -590,10 +589,12 @@ void QQuickMultiPointTouchArea::updateTouchData(QEvent *event) dev = const_cast<QPointingDevice *>(te->pointingDevice()); break; } - case QEvent::MouseButtonPress: - _mouseQpaTouchPoint = QEventPoint(windowPriv->touchMouseId); - _touchMouseDevice = windowPriv->touchMouseDevice; + case QEvent::MouseButtonPress: { + auto da = QQuickItemPrivate::get(this)->deliveryAgentPrivate(); + _mouseQpaTouchPoint = QEventPoint(da->touchMouseId); + _touchMouseDevice = da->touchMouseDevice; Q_FALLTHROUGH(); + } case QEvent::MouseMove: case QEvent::MouseButtonRelease: { QMouseEvent *me = static_cast<QMouseEvent*>(event); @@ -950,11 +951,11 @@ bool QQuickMultiPointTouchArea::childMouseEventFilter(QQuickItem *receiver, QEve return QQuickItem::childMouseEventFilter(receiver, event); switch (event->type()) { case QEvent::MouseButtonPress: { - QQuickWindowPrivate *windowPriv = QQuickWindowPrivate::get(window()); + auto da = QQuickItemPrivate::get(this)->deliveryAgentPrivate(); // If we already got a chance to filter the touchpoint that generated this synth-mouse-press, // and chose not to filter it, ignore it now, too. if (static_cast<QMouseEvent *>(event)->source() == Qt::MouseEventSynthesizedByQt && - _lastFilterableTouchPointIds.contains(windowPriv->touchMouseId)) + _lastFilterableTouchPointIds.contains(da->touchMouseId)) return false; } Q_FALLTHROUGH(); case QEvent::MouseMove: diff --git a/src/quick/items/qquickpathview.cpp b/src/quick/items/qquickpathview.cpp index 3d0b1b97f9..4cde344dce 100644 --- a/src/quick/items/qquickpathview.cpp +++ b/src/quick/items/qquickpathview.cpp @@ -1705,7 +1705,8 @@ void QQuickPathViewPrivate::handleMouseMoveEvent(QMouseEvent *event) QPointF pathPoint = pointNear(event->position(), &newPc); if (!stealMouse) { QPointF posDelta = event->position() - startPos; - if (QQuickWindowPrivate::dragOverThreshold(posDelta.y(), Qt::YAxis, event) || QQuickWindowPrivate::dragOverThreshold(posDelta.x(), Qt::XAxis, event)) { + if (QQuickDeliveryAgentPrivate::dragOverThreshold(posDelta.y(), Qt::YAxis, event) || + QQuickDeliveryAgentPrivate::dragOverThreshold(posDelta.x(), Qt::XAxis, event)) { // The touch has exceeded the threshold. If the movement along the path is close to the drag threshold // then we'll assume that this gesture targets the PathView. This ensures PathView gesture grabbing // is in sync with other items. diff --git a/src/quick/items/qquicktableview.cpp b/src/quick/items/qquicktableview.cpp index 1b71bd5a53..0d66f81b24 100644 --- a/src/quick/items/qquicktableview.cpp +++ b/src/quick/items/qquicktableview.cpp @@ -1295,7 +1295,7 @@ void QQuickTableViewPrivate::releaseItem(FxTableItem *fxTableItem, QQmlTableInst const bool hasFocus = item == focusItem || item->isAncestorOf(focusItem); if (hasFocus) { const auto focusChild = QQuickItemPrivate::get(q)->subFocusItem; - QQuickWindowPrivate::get(window)->clearFocusInScope(q, focusChild, Qt::OtherFocusReason); + deliveryAgentPrivate()->clearFocusInScope(q, focusChild, Qt::OtherFocusReason); } } } diff --git a/src/quick/items/qquickwindow.cpp b/src/quick/items/qquickwindow.cpp index 295ab9dc43..643e47c8a0 100644 --- a/src/quick/items/qquickwindow.cpp +++ b/src/quick/items/qquickwindow.cpp @@ -45,23 +45,16 @@ #include "qquickevents_p_p.h" #include "qquickgraphicsdevice_p.h" -#if QT_CONFIG(quick_draganddrop) -#include <private/qquickdrag_p.h> -#endif -#include <private/qquickhoverhandler_p.h> -#include <private/qquickpointerhandler_p.h> - #include <QtQuick/private/qsgrenderer_p.h> #include <QtQuick/private/qsgplaintexture_p.h> +#include <QtQuick/private/qquickpointerhandler_p.h> #include <private/qsgrenderloop_p.h> #include <private/qsgrhisupport_p.h> #include <private/qquickrendercontrol_p.h> #include <private/qquickanimatorcontroller_p.h> -#include <private/qquickpointerhandler_p_p.h> #include <private/qquickprofiler_p.h> #include <private/qguiapplication_p.h> -#include <QtGui/QInputMethod> #include <private/qabstractanimation_p.h> @@ -70,7 +63,6 @@ #include <QtGui/qmatrix4x4.h> #include <QtGui/private/qevent_p.h> #include <QtGui/private/qpointingdevice_p.h> -#include <QtGui/qpa/qplatformtheme.h> #include <QtCore/qvarlengtharray.h> #include <QtCore/qabstractanimation.h> #include <QtCore/QLibraryInfo> @@ -96,12 +88,11 @@ QT_BEGIN_NAMESPACE -Q_LOGGING_CATEGORY(lcTablet, "qt.quick.tablet") +Q_DECLARE_LOGGING_CATEGORY(lcMouse) +Q_DECLARE_LOGGING_CATEGORY(lcTouch) Q_LOGGING_CATEGORY(lcDirty, "qt.quick.dirty") Q_LOGGING_CATEGORY(lcTransient, "qt.quick.window.transient") -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); - bool QQuickWindowPrivate::defaultAlphaBuffer = false; #if defined(QT_QUICK_DEFAULT_TEXT_RENDER_TYPE) @@ -110,20 +101,6 @@ QQuickWindow::TextRenderType QQuickWindowPrivate::textRenderType = QQuickWindow: QQuickWindow::TextRenderType QQuickWindowPrivate::textRenderType = QQuickWindow::QtTextRendering; #endif -void QQuickWindowPrivate::updateFocusItemTransform() -{ -#if QT_CONFIG(im) - Q_Q(QQuickWindow); - QQuickItem *focus = q->activeFocusItem(); - if (focus && QGuiApplication::focusObject() == focus) { - QQuickItemPrivate *focusPrivate = QQuickItemPrivate::get(focus); - QGuiApplication::inputMethod()->setInputItemTransform(focusPrivate->itemToWindowTransform()); - QGuiApplication::inputMethod()->setInputItemRectangle(QRectF(0, 0, focusPrivate->width, focusPrivate->height)); - focus->updateInputMethod(Qt::ImInputItemClipRectangle); - } -#endif -} - class QQuickWindowIncubationController : public QObject, public QQmlIncubationController { Q_OBJECT @@ -280,9 +257,12 @@ void QQuickWindow::focusOutEvent(QFocusEvent *ev) void QQuickWindow::focusInEvent(QFocusEvent *ev) { Q_D(QQuickWindow); + if (d->inDestructor) + return; if (d->contentItem) d->contentItem->setFocus(true, ev->reason()); - d->updateFocusItemTransform(); + if (auto da = d->deliveryAgentPrivate()) + da->updateFocusItemTransform(); } #if QT_CONFIG(im) @@ -408,7 +388,7 @@ void QQuickWindowPrivate::polishItems() const bool isActiveFocusItem = (focusItem == QGuiApplication::focusObject()); const bool hasImEnabled = focusItem->inputMethodQuery(Qt::ImEnabled).toBool(); if (isActiveFocusItem && hasImEnabled && transformDirtyOnItemOrAncestor(focusItem)) - updateFocusItemTransform(); + deliveryAgentPrivate()->updateFocusItemTransform(); } #endif } @@ -719,47 +699,31 @@ void QQuickWindowPrivate::renderSceneGraph(const QSize &size, const QSize &surfa QQuickWindowPrivate::QQuickWindowPrivate() : contentItem(nullptr) - , activeFocusItem(nullptr) -#if QT_CONFIG(cursor) - , cursorItem(nullptr) - , cursorHandler(nullptr) -#endif -#if QT_CONFIG(quick_draganddrop) - , dragGrabber(nullptr) -#endif - , touchMouseId(-1) - , touchMouseDevice(nullptr) - , touchMousePressTimestamp(0) , dirtyItemList(nullptr) , devicePixelRatio(0) , context(nullptr) , renderer(nullptr) , windowManager(nullptr) , renderControl(nullptr) - , pointerEventRecursionGuard(0) , clearColor(Qt::white) , persistentGraphics(true) , persistentSceneGraph(true) - , lastWheelEventAccepted(false) , componentCompleted(true) - , allowChildEventFiltering(true) - , allowDoubleClick(true) - , lastFocusReason(Qt::OtherFocusReason) + , inDestructor(false) , incubationController(nullptr) , hasActiveSwapchain(false) , hasRenderableSwapchain(false) , swapchainJustBecameRenderable(false) { -#if QT_CONFIG(quick_draganddrop) - dragGrabber = new QQuickDragGrabber; -#endif } QQuickWindowPrivate::~QQuickWindowPrivate() { + inDestructor = true; redirect.rt.reset(rhi, renderer); if (QQmlInspectorService *service = QQmlDebugConnector::service<QQmlInspectorService>()) service->removeWindow(q_func()); + deliveryAgent = nullptr; } void QQuickWindowPrivate::updateChildrenPalettes(const QPalette &parentPalette) @@ -780,6 +744,7 @@ void QQuickWindowPrivate::init(QQuickWindow *c, QQuickRenderControl *control) Q_Q(QQuickWindow); contentItem = new QQuickRootItem; + contentItem->setObjectName(q->objectName()); QQml_setParent_noEvent(contentItem, c); QQmlEngine::setObjectOwnership(contentItem, QQmlEngine::CppOwnership); QQuickItemPrivate *contentItemPrivate = QQuickItemPrivate::get(contentItem); @@ -787,6 +752,7 @@ void QQuickWindowPrivate::init(QQuickWindow *c, QQuickRenderControl *control) contentItemPrivate->windowRefCount = 1; contentItemPrivate->flags |= QQuickItem::ItemIsFocusScope; contentItem->setSize(q->size()); + deliveryAgent = new QQuickDeliveryAgent(contentItem); visualizationMode = qgetenv("QSG_VISUALIZE"); renderControl = control; @@ -839,7 +805,7 @@ void QQuickWindow::handleApplicationStateChanged(Qt::ApplicationState state) { Q_D(QQuickWindow); if (state != Qt::ApplicationActive && d->contentItem) - d->handleWindowDeactivate(); + d->deliveryAgentPrivate()->handleWindowDeactivate(this); } /*! @@ -871,8 +837,8 @@ void QQuickWindowPrivate::dirtyItem(QQuickItem *) QQuickItem *QQuickWindow::mouseGrabberItem() const { Q_D(const QQuickWindow); - auto epd = const_cast<QQuickWindowPrivate *>(d)->mousePointData(); - if (!epd && d->eventsInDelivery.isEmpty()) { + auto epd = const_cast<QQuickWindowPrivate *>(d)->deliveryAgentPrivate()->mousePointData(); + if (!epd && d->deliveryAgentPrivate()->eventsInDelivery.isEmpty()) { qCDebug(lcMouse, "mouse grabber ambiguous: no event is currently being delivered"); return qmlobject_cast<QQuickItem *>(QPointingDevicePrivate::get(QPointingDevice::primaryPointingDevice())-> firstPointExclusiveGrabber()); @@ -1134,7 +1100,7 @@ QQuickWindow::QQuickWindow(QQuickWindowPrivate &dd, QQuickRenderControl *control QQuickWindow::~QQuickWindow() { Q_D(QQuickWindow); - + d->inDestructor = true; if (d->renderControl) { QQuickRenderControlPrivate::get(d->renderControl)->windowDestroyed(); } else if (d->windowManager) { @@ -1143,12 +1109,11 @@ QQuickWindow::~QQuickWindow() } delete d->incubationController; d->incubationController = nullptr; -#if QT_CONFIG(quick_draganddrop) - delete d->dragGrabber; d->dragGrabber = nullptr; -#endif QQuickRootItem *root = d->contentItem; d->contentItem = nullptr; + root->setParent(nullptr); // avoid QChildEvent delivery during deletion delete root; + d->deliveryAgent = nullptr; // avoid forwarding events there during destruction d->renderJobMutex.lock(); qDeleteAll(d->beforeSynchronizingJobs); @@ -1305,9 +1270,6 @@ bool QQuickWindow::isPersistentSceneGraph() const return d->persistentSceneGraph; } - - - /*! \qmlattachedproperty Item Window::contentItem \since 5.4 @@ -1342,7 +1304,7 @@ QQuickItem *QQuickWindow::activeFocusItem() const { Q_D(const QQuickWindow); - return d->activeFocusItem; + return d->deliveryAgentPrivate()->activeFocusItem; } /*! @@ -1353,27 +1315,153 @@ QObject *QQuickWindow::focusObject() const { Q_D(const QQuickWindow); - if (d->activeFocusItem) - return d->activeFocusItem; + if (!d->inDestructor && d->deliveryAgentPrivate()->activeFocusItem) + return d->deliveryAgentPrivate()->activeFocusItem; return const_cast<QQuickWindow*>(this); } /*! \reimp */ +bool QQuickWindow::event(QEvent *e) +{ + Q_D(QQuickWindow); + + // bypass QWindow::event dispatching of input events: deliveryAgent takes care of it + QQuickDeliveryAgent *da = d->deliveryAgent; + if (e->isPointerEvent()) { + /* + When delivering update and release events to existing grabbers, + use the subscene delivery agent, if any. A possible scenario: + 1) Two touchpoints pressed on the main window: QQuickWindowPrivate::deliveryAgent delivers to QQuick3DViewport, + which does picking and finds two subscenes ("root" Items mapped onto two different 3D objects) to deliver it to. + 2) The QTouchEvent is split up so that each subscene sees points relevant to it. + 3) During delivery to either subscene, an item in the subscene grabs. + 4) The user moves finger(s) generating a move event: the correct grabber item needs to get the update + via the same subscene delivery agent from which it got the press, so that the coord transform will be done properly. + 5) Likewise with the touchpoint releases. + With single-point events (mouse, or only one finger) it's simplified: there can only be one subscene of interest; + for (pt : pe->points()) would only iterate once, so we might as well skip that logic. + */ + auto pe = static_cast<QPointerEvent *>(e); + if (pe->pointCount() > 1) { + bool ret = false; + Q_ASSERT(QQuickDeliveryAgentPrivate::isTouchEvent(pe)); + // Split up the multi-point event according to the relevant QQuickDeliveryAgent that should deliver to each existing grabber + // but send ungrabbed points to d->deliveryAgent() + QFlatMap<QQuickDeliveryAgent*, QList<QEventPoint>> deliveryAgentsNeedingPoints; + QEventPoint::States eventStates; + for (const auto &pt : pe->points()) { + eventStates |= pt.state(); + auto *ptda = QQuickDeliveryAgent::grabberAgent(pe, pt); + if (!ptda) + ptda = da; + if (ptda) { + auto danpit = deliveryAgentsNeedingPoints.find(ptda); + if (danpit == deliveryAgentsNeedingPoints.end()) { + deliveryAgentsNeedingPoints.insert(ptda, QList<QEventPoint>() << pt); + } else { + danpit.value().append(pt); + } + } + } + // Make new touch events for each subscene, the same way QQuickItemPrivate::localizedTouchEvent() does it + for (auto daAndPoints : deliveryAgentsNeedingPoints) { + // if all points have the same state, set the event type accordingly + QEvent::Type eventType = pe->type(); + switch (eventStates) { + case QEventPoint::State::Pressed: + eventType = QEvent::TouchBegin; + break; + case QEventPoint::State::Released: + eventType = QEvent::TouchEnd; + break; + default: + eventType = QEvent::TouchUpdate; + break; + } + QMutableTouchEvent te(eventType, pe->pointingDevice(), pe->modifiers(), daAndPoints.second); + te.setTimestamp(pe->timestamp()); + te.accept(); + qCDebug(lcTouch) << "subscene touch:" << daAndPoints.first << "shall now receive" << &te; + ret = daAndPoints.first->event(&te) || ret; + } + if (ret) + return true; + } else if (pe->pointCount()) { + // single-point event + if (auto *ptda = QQuickDeliveryAgent::grabberAgent(pe, pe->points().first())) + da = ptda; + } + // else if it has no points, it's probably a TouchCancel, and DeliveryAgent needs to handle it. + // TODO should we deliver to all DAs at once then, since we don't know which one should get it? + // or fix QTBUG-90851 so that the event always has points? + if (da && da->event(e)) + return true; + } else if (e->isInputEvent()) { + if (da && da->event(e)) + return true; + } + + switch (e->type()) { + // a few more types that are not QInputEvents, but QQuickDeliveryAgent needs to handle them anyway + case QEvent::FocusAboutToChange: + case QEvent::Enter: + case QEvent::Leave: + case QEvent::InputMethod: + case QEvent::InputMethodQuery: +#if QT_CONFIG(quick_draganddrop) + case QEvent::DragEnter: + case QEvent::DragLeave: + case QEvent::DragMove: + case QEvent::Drop: +#endif + if (d->inDestructor) + return false; + if (da && da->event(e)) + return true; + break; + case QEvent::LanguageChange: + if (d->contentItem) + QCoreApplication::sendEvent(d->contentItem, e); + break; + case QEvent::UpdateRequest: + if (d->windowManager) + d->windowManager->handleUpdateRequest(this); + break; + case QEvent::PlatformSurface: + if ((static_cast<QPlatformSurfaceEvent *>(e))->surfaceEventType() == QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed) { + // Ensure that the rendering thread is notified before + // the QPlatformWindow is destroyed. + if (d->windowManager) + d->windowManager->hide(this); + } + break; + case QEvent::WindowDeactivate: + d->deliveryAgentPrivate()->handleWindowDeactivate(this); + break; + default: + break; + } + + if (e->type() == QEvent::Type(QQuickWindowPrivate::FullUpdateRequest)) + update(); + else if (e->type() == QEvent::Type(QQuickWindowPrivate::TriggerContextCreationFailure)) + d->windowManager->handleContextCreationFailure(this); + + return QWindow::event(e); +} + +/*! \reimp */ void QQuickWindow::keyPressEvent(QKeyEvent *e) { Q_D(QQuickWindow); - Q_QUICK_INPUT_PROFILE(QQuickProfiler::Key, QQuickProfiler::InputKeyPress, e->key(), - e->modifiers()); - d->deliverKeyEvent(e); + d->deliveryAgentPrivate()->deliverKeyEvent(e); } /*! \reimp */ void QQuickWindow::keyReleaseEvent(QKeyEvent *e) { Q_D(QQuickWindow); - Q_QUICK_INPUT_PROFILE(QQuickProfiler::Key, QQuickProfiler::InputKeyRelease, e->key(), - e->modifiers()); - d->deliverKeyEvent(e); + d->deliveryAgentPrivate()->deliverKeyEvent(e); } #if QT_CONFIG(wheelevent) @@ -1381,18 +1469,7 @@ void QQuickWindow::keyReleaseEvent(QKeyEvent *e) void QQuickWindow::wheelEvent(QWheelEvent *event) { Q_D(QQuickWindow); - Q_QUICK_INPUT_PROFILE(QQuickProfiler::Mouse, QQuickProfiler::InputMouseWheel, - event->angleDelta().x(), event->angleDelta().y()); - - qCDebug(lcMouse) << event; - - //if the actual wheel event was accepted, accept the compatibility wheel event and return early - if (d->lastWheelEventAccepted && event->angleDelta().isNull() && event->phase() == Qt::ScrollUpdate) - return; - - event->ignore(); - d->deliverSinglePointEventUntilAccepted(event); - d->lastWheelEventAccepted = event->isAccepted(); + d->deliveryAgentPrivate()->deliverSinglePointEventUntilAccepted(event); } #endif // wheelevent @@ -1401,9 +1478,7 @@ void QQuickWindow::wheelEvent(QWheelEvent *event) void QQuickWindow::tabletEvent(QTabletEvent *event) { Q_D(QQuickWindow); - qCDebug(lcTablet) << event; - // TODO Qt 6: make sure TabletEnterProximity and TabletLeaveProximity are delivered here - d->deliverPointerEvent(event); + d->deliveryAgentPrivate()->deliverPointerEvent(event); } #endif // tabletevent @@ -1411,34 +1486,40 @@ void QQuickWindow::tabletEvent(QTabletEvent *event) void QQuickWindow::mousePressEvent(QMouseEvent *event) { Q_D(QQuickWindow); - d->handleMouseEvent(event); + d->deliveryAgentPrivate()->handleMouseEvent(event); } /*! \reimp */ void QQuickWindow::mouseMoveEvent(QMouseEvent *event) { Q_D(QQuickWindow); - d->handleMouseEvent(event); + d->deliveryAgentPrivate()->handleMouseEvent(event); } /*! \reimp */ void QQuickWindow::mouseDoubleClickEvent(QMouseEvent *event) { Q_D(QQuickWindow); - d->handleMouseEvent(event); + d->deliveryAgentPrivate()->handleMouseEvent(event); } /*! \reimp */ void QQuickWindow::mouseReleaseEvent(QMouseEvent *event) { Q_D(QQuickWindow); - d->handleMouseEvent(event); + d->deliveryAgentPrivate()->handleMouseEvent(event); } -#if QT_CONFIG(cursor) -void QQuickWindowPrivate::updateCursor(const QPointF &scenePos) +void QQuickWindowPrivate::flushFrameSynchronousEvents() { Q_Q(QQuickWindow); + deliveryAgentPrivate()->flushFrameSynchronousEvents(q); +} - auto cursorItemAndHandler = findCursorItemAndHandler(contentItem, scenePos); - +#if QT_CONFIG(cursor) +void QQuickWindowPrivate::updateCursor(const QPointF &scenePos, QQuickItem *rootItem) +{ + Q_Q(QQuickWindow); + if (!rootItem) + rootItem = contentItem; + auto cursorItemAndHandler = findCursorItemAndHandler(rootItem, scenePos); if (cursorItem != cursorItemAndHandler.first || cursorHandler != cursorItemAndHandler.second) { QWindow *renderWindow = QQuickRenderControl::renderWindowFor(q); QWindow *window = renderWindow ? renderWindow : q; diff --git a/src/quick/items/qquickwindow.h b/src/quick/items/qquickwindow.h index af22912930..cd39bc13d3 100644 --- a/src/quick/items/qquickwindow.h +++ b/src/quick/items/qquickwindow.h @@ -232,6 +232,9 @@ protected: void focusOutEvent(QFocusEvent *) override; bool event(QEvent *) override; + + // These overrides are no longer normal entry points for + // input events, but kept in case legacy code calls them. void keyPressEvent(QKeyEvent *) override; void keyReleaseEvent(QKeyEvent *) override; void mousePressEvent(QMouseEvent *) override; @@ -267,6 +270,7 @@ private: friend class QQuickRenderControl; friend class QQuickAnimatorController; friend class QQuickWidgetPrivate; + friend class QQuickDeliveryAgentPrivate; Q_DISABLE_COPY(QQuickWindow) }; diff --git a/src/quick/items/qquickwindow_p.h b/src/quick/items/qquickwindow_p.h index 869324c317..30b5a36cb5 100644 --- a/src/quick/items/qquickwindow_p.h +++ b/src/quick/items/qquickwindow_p.h @@ -51,6 +51,7 @@ // We mean it. // +#include <QtQuick/private/qquickdeliveryagent_p_p.h> #include <QtQuick/private/qquickevents_p_p.h> #include <QtQuick/private/qsgcontext_p.h> #include <QtQuick/private/qquickpaletteproviderprivatebase_p.h> @@ -100,6 +101,7 @@ class Q_QUICK_PRIVATE_EXPORT QQuickRootItem : public QQuickItem QML_ADDED_IN_VERSION(2, 0) public: QQuickRootItem(); + public Q_SLOTS: void setWidth(int w) {QQuickItem::setWidth(qreal(w));} void setHeight(int h) {QQuickItem::setHeight(qreal(h));} @@ -142,104 +144,25 @@ public: QSet<QQuickItem *> parentlessItems; QQmlListProperty<QObject> data(); - QQuickItem *activeFocusItem; - - void deliverKeyEvent(QKeyEvent *e); + // primary delivery agent for the whole scene, used by default for events that arrive in this window; + // but any subscene root can have a QQuickItemPrivate::ExtraData::subsceneDeliveryAgent + QQuickDeliveryAgent *deliveryAgent = nullptr; + QQuickDeliveryAgentPrivate *deliveryAgentPrivate() const + { return static_cast<QQuickDeliveryAgentPrivate *>(QQuickDeliveryAgentPrivate::get(deliveryAgent)); } - // Keeps track of the item currently receiving mouse events -#if QT_CONFIG(cursor) - QQuickItem *cursorItem; - QQuickPointerHandler *cursorHandler; -#endif -#if QT_CONFIG(quick_draganddrop) - QQuickDragGrabber *dragGrabber; -#endif - QQuickItem *lastUngrabbed = nullptr; - QStack<QPointerEvent *> eventsInDelivery; - - int touchMouseId; // only for obsolete stuff like QQuickItem::grabMouse() - // TODO get rid of these - const QPointingDevice *touchMouseDevice; - ulong touchMousePressTimestamp; - QPoint touchMousePressPos; // in screen coordiantes - bool isDeliveringTouchAsMouse() const { return touchMouseId != -1 && touchMouseDevice; } - void cancelTouchMouseSynthesis(); - - bool checkIfDoubleTapped(ulong newPressEventTimestamp, QPoint newPressPos); - QPointingDevicePrivate::EventPointData *mousePointData(); - QPointerEvent *eventInDelivery() const; - - // Mouse positions are saved in widget coordinates - QPointF lastMousePosition; - bool deliverTouchAsMouse(QQuickItem *item, QTouchEvent *pointerEvent); - void translateTouchEvent(QTouchEvent *touchEvent); - void removeGrabber(QQuickItem *grabber, bool mouse = true, bool touch = true, bool cancel = false); - void onGrabChanged(QObject *grabber, QPointingDevice::GrabTransition transition, const QPointerEvent *event, const QEventPoint &point); - static QPointerEvent *clonePointerEvent(QPointerEvent *event, std::optional<QPointF> transformedLocalPos = std::nullopt); - void deliverToPassiveGrabbers(const QVector<QPointer<QObject> > &passiveGrabbers, QPointerEvent *pointerEvent); - bool sendFilteredMouseEvent(QEvent *event, QQuickItem *receiver, QQuickItem *filteringParent); - bool sendFilteredPointerEvent(QPointerEvent *event, QQuickItem *receiver, QQuickItem *filteringParent = nullptr); - bool sendFilteredPointerEventImpl(QPointerEvent *event, QQuickItem *receiver, QQuickItem *filteringParent); - bool deliverSinglePointEventUntilAccepted(QPointerEvent *); - - // entry point of events to the window - void handleTouchEvent(QTouchEvent *); - void handleMouseEvent(QMouseEvent *); - bool compressTouchEvent(QTouchEvent *); + // TODO remove these: they were moved to QQuickDeliveryAgentPrivate + static bool isMouseEvent(const QPointerEvent *ev) { return QQuickDeliveryAgentPrivate::isMouseEvent(ev); } + static bool isTouchEvent(const QPointerEvent *ev) { return QQuickDeliveryAgentPrivate::isTouchEvent(ev); } + static bool isTabletEvent(const QPointerEvent *ev) { return QQuickDeliveryAgentPrivate::isTabletEvent(ev); } void flushFrameSynchronousEvents(); - void deliverDelayedTouchEvent(); - void handleWindowDeactivate(); - - // utility functions that used to be in QQuickPointerEvent et al. - bool allUpdatedPointsAccepted(const QPointerEvent *ev); - static void localizePointerEvent(QPointerEvent *ev, const QQuickItem *dest); - QList<QObject *> exclusiveGrabbers(QPointerEvent *ev); - static bool isMouseEvent(const QPointerEvent *ev); - static bool isTouchEvent(const QPointerEvent *ev); - static bool isTabletEvent(const QPointerEvent *ev); - - // delivery of pointer events: - void touchToMouseEvent(QEvent::Type type, const QEventPoint &p, const QTouchEvent *touchEvent, QMutableSinglePointEvent *mouseEvent); - void ensureDeviceConnected(const QPointingDevice *dev); - void deliverPointerEvent(QPointerEvent *); - bool deliverTouchCancelEvent(QTouchEvent *); - bool deliverPressOrReleaseEvent(QPointerEvent *, bool handlersOnly = false); - void deliverUpdatedPoints(QPointerEvent *event); - void deliverMatchingPointsToItem(QQuickItem *item, bool isGrabber, QPointerEvent *pointerEvent, bool handlersOnly = false); - - QVector<QQuickItem *> pointerTargets(QQuickItem *, const QPointerEvent *event, const QEventPoint &point, - bool checkMouseButtons, bool checkAcceptsTouch) const; - QVector<QQuickItem *> mergePointerTargets(const QVector<QQuickItem *> &list1, const QVector<QQuickItem *> &list2) const; - - // hover delivery - bool deliverHoverEvent(QQuickItem *, const QPointF &scenePos, const QPointF &lastScenePos, Qt::KeyboardModifiers modifiers, ulong timestamp, bool &accepted); - bool sendHoverEvent(QEvent::Type, QQuickItem *, const QPointF &scenePos, const QPointF &lastScenePos, - Qt::KeyboardModifiers modifiers, ulong timestamp, bool accepted); - bool clearHover(ulong timestamp = 0); - -#if QT_CONFIG(quick_draganddrop) - void deliverDragEvent(QQuickDragGrabber *, QEvent *); - bool deliverDragEvent(QQuickDragGrabber *, QQuickItem *, QDragMoveEvent *, QVarLengthArray<QQuickItem*, 64> *currentGrabItems = nullptr); -#endif + #if QT_CONFIG(cursor) - void updateCursor(const QPointF &scenePos); + QQuickItem *cursorItem = nullptr; + QQuickPointerHandler *cursorHandler = nullptr; + void updateCursor(const QPointF &scenePos, QQuickItem *rootItem = nullptr); QPair<QQuickItem*, QQuickPointerHandler*> findCursorItemAndHandler(QQuickItem *item, const QPointF &scenePos) const; #endif - QList<QQuickItem*> hoverItems; - enum FocusOption { - DontChangeFocusProperty = 0x01, - DontChangeSubFocusItem = 0x02 - }; - Q_DECLARE_FLAGS(FocusOptions, FocusOption) - - void setFocusInScope(QQuickItem *scope, QQuickItem *item, Qt::FocusReason reason, FocusOptions = { }); - void clearFocusInScope(QQuickItem *scope, QQuickItem *item, Qt::FocusReason reason, FocusOptions = { }); - static void notifyFocusChangesRecur(QQuickItem **item, int remaining); - void clearFocusObject() override; - - void updateFocusItemTransform(); - void dirtyItem(QQuickItem *); void cleanup(QSGNode *); @@ -272,8 +195,6 @@ public: QList<QSGNode *> cleanupNodeList; QVector<QQuickItem *> itemsToPolish; - QVector<QQuickItem *> hasFiltered; // during event delivery to a single receiver, the filtering parents for which childMouseEventFilter was already called - QVector<QQuickItem *> skipDelivery; // during delivery of one event to all receivers, Items to which we know delivery is no longer necessary qreal devicePixelRatio; QMetaObject::Connection physicalDpiChangedConnection; @@ -295,23 +216,13 @@ public: QSGRenderLoop *windowManager; QQuickRenderControl *renderControl; QScopedPointer<QQuickAnimatorController> animationController; - QScopedPointer<QMutableTouchEvent> delayedTouch; - QList<const QPointingDevice *> knownPointingDevices; - - int pointerEventRecursionGuard; QColor clearColor; uint persistentGraphics : 1; uint persistentSceneGraph : 1; - - uint lastWheelEventAccepted : 1; - bool componentCompleted : 1; - - bool allowChildEventFiltering : 1; - bool allowDoubleClick : 1; - - Qt::FocusReason lastFocusReason; + uint componentCompleted : 1; + uint inDestructor : 1; // Storage for setRenderTarget(QQuickRenderTarget). // Gets baked into redirect.renderTarget by ensureCustomRenderTarget() when rendering the next frame. @@ -332,15 +243,18 @@ public: static bool defaultAlphaBuffer; static QQuickWindow::TextRenderType textRenderType; - static bool dragOverThreshold(qreal d, Qt::Axis axis, QMouseEvent *event, int startDragThreshold = -1); - - static bool dragOverThreshold(qreal d, Qt::Axis axis, const QEventPoint &tp, int startDragThreshold = -1); - - // currently in use in Controls 2; TODO remove + // vvv currently in use in Controls 2; TODO remove static bool dragOverThreshold(qreal d, Qt::Axis axis, const QEventPoint *tp, int startDragThreshold = -1) - { return dragOverThreshold(d, axis, *tp, startDragThreshold); } - - static bool dragOverThreshold(QVector2D delta); + { return QQuickDeliveryAgentPrivate::dragOverThreshold(d, axis, *tp, startDragThreshold); } + static bool dragOverThreshold(qreal d, Qt::Axis axis, QMouseEvent *event, int startDragThreshold = -1) + { return QQuickDeliveryAgentPrivate::dragOverThreshold(d, axis, event, startDragThreshold); } + void clearFocusInScope(QQuickItem *scope, QQuickItem *item, Qt::FocusReason reason) + { deliveryAgentPrivate()->clearFocusInScope(scope, item, reason); } + void handleTouchEvent(QTouchEvent *e) + { deliveryAgentPrivate()->handleTouchEvent(e); } + void handleMouseEvent(QMouseEvent *e) + { deliveryAgentPrivate()->handleMouseEvent(e); } + // ^^^ currently in use in Controls 2; TODO remove // data property static void data_append(QQmlListProperty<QObject> *, QObject *); @@ -393,7 +307,6 @@ public: } }; -Q_DECLARE_OPERATORS_FOR_FLAGS(QQuickWindowPrivate::FocusOptions) Q_DECLARE_OPERATORS_FOR_FLAGS(QQuickWindowPrivate::TextureFromNativeTextureFlags) QT_END_NAMESPACE diff --git a/src/quick/util/qquickdeliveryagent.cpp b/src/quick/util/qquickdeliveryagent.cpp index 3dfda6902a..dbe14826db 100644 --- a/src/quick/util/qquickdeliveryagent.cpp +++ b/src/quick/util/qquickdeliveryagent.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2020 The Qt Company Ltd. +** Copyright (C) 2021 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtQuick module of the Qt Toolkit. @@ -37,18 +37,20 @@ ** ****************************************************************************/ +#include <QtCore/qdebug.h> +#include <QtGui/private/qevent_p.h> #include <QtGui/private/qguiapplication_p.h> #include <QtGui/qpa/qplatformtheme.h> #include <QtQml/private/qabstractanimationjob_p.h> -#include <QtQuick/private/qquickdrag_p.h> -#include <QtQuick/private/qquickitem_p.h> +#include <QtQuick/private/qquickdeliveryagent_p_p.h> #include <QtQuick/private/qquickhoverhandler_p.h> -#include <QtQuick/private/qquickpointerhandler_p.h> #include <QtQuick/private/qquickpointerhandler_p_p.h> +#if QT_CONFIG(quick_draganddrop) +#include <QtQuick/private/qquickdrag_p.h> +#endif #include <QtQuick/private/qquickprofiler_p.h> -#include <QtQuick/private/qquickrendercontrol_p.h> +#include <QtQuick/qquickrendercontrol.h> #include <QtQuick/private/qquickwindow_p.h> -#include <QtQuick/private/qsgrenderloop_p.h> QT_BEGIN_NAMESPACE @@ -57,7 +59,9 @@ Q_LOGGING_CATEGORY(lcTouchCmprs, "qt.quick.touch.compression") Q_LOGGING_CATEGORY(lcTouchTarget, "qt.quick.touch.target") Q_LOGGING_CATEGORY(lcMouse, "qt.quick.mouse") Q_LOGGING_CATEGORY(lcMouseTarget, "qt.quick.mouse.target") +Q_LOGGING_CATEGORY(lcTablet, "qt.quick.tablet") Q_LOGGING_CATEGORY(lcPtr, "qt.quick.pointer") +Q_LOGGING_CATEGORY(lcPtrLoc, "qt.quick.pointer.localization") Q_LOGGING_CATEGORY(lcPtrGrab, "qt.quick.pointer.grab") Q_LOGGING_CATEGORY(lcWheelTarget, "qt.quick.wheel.target") Q_LOGGING_CATEGORY(lcGestureTarget, "qt.quick.gesture.target") @@ -66,7 +70,7 @@ Q_LOGGING_CATEGORY(lcFocus, "qt.quick.focus") 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); -void QQuickWindowPrivate::touchToMouseEvent(QEvent::Type type, const QEventPoint &p, const QTouchEvent *touchEvent, QMutableSinglePointEvent *mouseEvent) +void QQuickDeliveryAgentPrivate::touchToMouseEvent(QEvent::Type type, const QEventPoint &p, const QTouchEvent *touchEvent, QMutableSinglePointEvent *mouseEvent) { Q_ASSERT(QCoreApplication::testAttribute(Qt::AA_SynthesizeMouseForUnhandledTouchEvents)); QMutableSinglePointEvent ret(type, touchEvent->pointingDevice(), p, @@ -77,7 +81,7 @@ void QQuickWindowPrivate::touchToMouseEvent(QEvent::Type type, const QEventPoint *mouseEvent = ret; } -bool QQuickWindowPrivate::checkIfDoubleTapped(ulong newPressEventTimestamp, QPoint newPressPos) +bool QQuickDeliveryAgentPrivate::checkIfDoubleTapped(ulong newPressEventTimestamp, QPoint newPressPos) { bool doubleClicked = false; @@ -103,7 +107,7 @@ bool QQuickWindowPrivate::checkIfDoubleTapped(ulong newPressEventTimestamp, QPoi return doubleClicked; } -QPointerEvent *QQuickWindowPrivate::eventInDelivery() const +QPointerEvent *QQuickDeliveryAgentPrivate::eventInDelivery() const { if (eventsInDelivery.isEmpty()) return nullptr; @@ -115,7 +119,7 @@ QPointerEvent *QQuickWindowPrivate::eventInDelivery() const that don't have the currently-being-delivered event in context. Returns the device the currently-being-delivered event comse from. */ -QPointingDevicePrivate::EventPointData *QQuickWindowPrivate::mousePointData() +QPointingDevicePrivate::EventPointData *QQuickDeliveryAgentPrivate::mousePointData() { if (eventsInDelivery.isEmpty()) return nullptr; @@ -123,21 +127,22 @@ QPointingDevicePrivate::EventPointData *QQuickWindowPrivate::mousePointData() return devPriv->pointById(isDeliveringTouchAsMouse() ? touchMouseId : 0); } -void QQuickWindowPrivate::cancelTouchMouseSynthesis() +void QQuickDeliveryAgentPrivate::cancelTouchMouseSynthesis() { qCDebug(lcTouchTarget) << "id" << touchMouseId << "on" << touchMouseDevice; touchMouseId = -1; touchMouseDevice = nullptr; } -bool QQuickWindowPrivate::deliverTouchAsMouse(QQuickItem *item, QTouchEvent *pointerEvent) +bool QQuickDeliveryAgentPrivate::deliverTouchAsMouse(QQuickItem *item, QTouchEvent *pointerEvent) { + Q_Q(QQuickDeliveryAgent); Q_ASSERT(QCoreApplication::testAttribute(Qt::AA_SynthesizeMouseForUnhandledTouchEvents)); auto device = pointerEvent->pointingDevice(); // A touch event from a trackpad is likely to be followed by a mouse or gesture event, so mouse event synth is redundant if (device->type() == QInputDevice::DeviceType::TouchPad && device->capabilities().testFlag(QInputDevice::Capability::MouseEmulation)) { - qCDebug(lcTouchTarget) << "skipping delivery of synth-mouse event from" << device; + qCDebug(lcTouchTarget) << q << "skipping delivery of synth-mouse event from" << device; return false; } @@ -159,7 +164,7 @@ bool QQuickWindowPrivate::deliverTouchAsMouse(QQuickItem *item, QTouchEvent *poi if (!item->contains(pos)) break; - qCDebug(lcTouchTarget) << device << "TP (mouse)" << Qt::hex << p.id() << "->" << item; + qCDebug(lcTouchTarget) << q << device << "TP (mouse)" << Qt::hex << p.id() << "->" << item; QMutableSinglePointEvent mousePress; touchToMouseEvent(QEvent::MouseButtonPress, p, &event, &mousePress); @@ -203,7 +208,7 @@ bool QQuickWindowPrivate::deliverTouchAsMouse(QQuickItem *item, QTouchEvent *poi QCoreApplication::sendEvent(item, &me); event.setAccepted(me.isAccepted()); if (me.isAccepted()) - qCDebug(lcTouchTarget) << device << "TP (mouse)" << Qt::hex << p.id() << "->" << mouseGrabberItem; + qCDebug(lcTouchTarget) << q << device << "TP (mouse)" << Qt::hex << p.id() << "->" << mouseGrabberItem; return event.isAccepted(); } else { // no grabber, check if we care about mouse hover @@ -217,7 +222,7 @@ bool QQuickWindowPrivate::deliverTouchAsMouse(QQuickItem *item, QTouchEvent *poi lastMousePosition = me.scenePosition(); bool accepted = me.isAccepted(); - bool delivered = deliverHoverEvent(contentItem, me.scenePosition(), last, me.modifiers(), me.timestamp(), accepted); + bool delivered = deliverHoverEvent(rootItem, me.scenePosition(), last, me.modifiers(), me.timestamp(), accepted); // take care of any exits if (!delivered) clearHover(me.timestamp()); @@ -260,9 +265,8 @@ bool QQuickWindowPrivate::deliverTouchAsMouse(QQuickItem *item, QTouchEvent *poi but if not all points are released, it cannot be sure whether to call touchUngrabEvent() or not; so we have to do it here. */ -void QQuickWindowPrivate::removeGrabber(QQuickItem *grabber, bool mouse, bool touch, bool cancel) +void QQuickDeliveryAgentPrivate::removeGrabber(QQuickItem *grabber, bool mouse, bool touch, bool cancel) { - Q_Q(QQuickWindow); if (eventsInDelivery.isEmpty()) { // do it the expensive way for (auto dev : knownPointingDevices) { @@ -272,13 +276,13 @@ void QQuickWindowPrivate::removeGrabber(QQuickItem *grabber, bool mouse, bool to return; } auto eventInDelivery = eventsInDelivery.top(); - if (Q_LIKELY(mouse) && q->mouseGrabberItem() == grabber && eventInDelivery) { - const bool fromTouch = isDeliveringTouchAsMouse(); - auto point = eventInDelivery->pointById(fromTouch ? touchMouseId : 0); - Q_ASSERT(point); - QQuickItem *oldGrabber = qobject_cast<QQuickItem *>(eventInDelivery->exclusiveGrabber(*point)); - qCDebug(lcMouseTarget) << "removeGrabber" << oldGrabber << "-> null"; - eventInDelivery->setExclusiveGrabber(*point, nullptr); + if (Q_LIKELY(mouse) && eventInDelivery) { + auto epd = mousePointData(); + if (epd && epd->exclusiveGrabber == grabber) { + QQuickItem *oldGrabber = qobject_cast<QQuickItem *>(epd->exclusiveGrabber); + qCDebug(lcMouseTarget) << "removeGrabber" << oldGrabber << "-> null"; + eventInDelivery->setExclusiveGrabber(epd->eventPoint, nullptr); + } } if (Q_LIKELY(touch)) { bool ungrab = false; @@ -300,7 +304,7 @@ void QQuickWindowPrivate::removeGrabber(QQuickItem *grabber, bool mouse, bool to The item-local QEventPoint::position() is updated later, not here. */ -void QQuickWindowPrivate::translateTouchEvent(QTouchEvent *touchEvent) +void QQuickDeliveryAgentPrivate::translateTouchEvent(QTouchEvent *touchEvent) { for (qsizetype i = 0; i != touchEvent->pointCount(); ++i) { auto &pt = QMutableEventPoint::from(touchEvent->point(i)); @@ -333,18 +337,17 @@ static inline bool singleWindowOnScreen(QQuickWindow *win) #endif /*! -Set the focus inside \a scope to be \a item. -If the scope contains the active focus item, it will be changed to \a item. -Calls notifyFocusChangesRecur for all changed items. + Set the focus inside \a scope to be \a item. + If the scope contains the active focus item, it will be changed to \a item. + Calls notifyFocusChangesRecur for all changed items. */ -void QQuickWindowPrivate::setFocusInScope(QQuickItem *scope, QQuickItem *item, Qt::FocusReason reason, FocusOptions options) +void QQuickDeliveryAgentPrivate::setFocusInScope(QQuickItem *scope, QQuickItem *item, + Qt::FocusReason reason, FocusOptions options) { - Q_Q(QQuickWindow); - Q_ASSERT(item); - Q_ASSERT(scope || item == contentItem); + Q_ASSERT(scope || item == rootItem); - qCDebug(lcFocus) << "QQuickWindowPrivate::setFocusInScope():"; + qCDebug(lcFocus) << "QQuickDeliveryAgentPrivate::setFocusInScope():"; qCDebug(lcFocus) << " scope:" << (QObject *)scope; if (scope) qCDebug(lcFocus) << " scopeSubFocusItem:" << (QObject *)QQuickItemPrivate::get(scope)->subFocusItem; @@ -364,7 +367,7 @@ void QQuickWindowPrivate::setFocusInScope(QQuickItem *scope, QQuickItem *item, Q QVarLengthArray<QQuickItem *, 20> changed; // Does this change the active focus? - if (item == contentItem || scopePrivate->activeFocus) { + if (item == rootItem || scopePrivate->activeFocus) { oldActiveFocusItem = activeFocusItem; if (item->isEnabled()) { newActiveFocusItem = item; @@ -395,7 +398,7 @@ void QQuickWindowPrivate::setFocusInScope(QQuickItem *scope, QQuickItem *item, Q } } - if (item != contentItem && !(options & DontChangeSubFocusItem)) { + if (item != rootItem && !(options & DontChangeSubFocusItem)) { QQuickItem *oldSubFocusItem = scopePrivate->subFocusItem; if (oldSubFocusItem) { QQuickItemPrivate::get(oldSubFocusItem)->focus = false; @@ -406,8 +409,7 @@ void QQuickWindowPrivate::setFocusInScope(QQuickItem *scope, QQuickItem *item, Q } if (!(options & DontChangeFocusProperty)) { - if (item != contentItem - || windowHasFocus(q) + if (item != rootItem || windowHasFocus(rootItem->window()) #ifdef Q_OS_WEBOS // Allow focused if there is only one window in the screen where it belongs. // Temporary fix for webOS until multi-seat is implemented see QTBUG-85272 @@ -419,7 +421,7 @@ void QQuickWindowPrivate::setFocusInScope(QQuickItem *scope, QQuickItem *item, Q } } - if (newActiveFocusItem && contentItem->hasFocus()) { + if (newActiveFocusItem && rootItem->hasFocus()) { activeFocusItem = newActiveFocusItem; QQuickItemPrivate::get(newActiveFocusItem)->activeFocus = true; @@ -451,20 +453,18 @@ void QQuickWindowPrivate::setFocusInScope(QQuickItem *scope, QQuickItem *item, Q } if (activeFocusItem != currentActiveFocusItem) - emit q->focusObjectChanged(activeFocusItem); + emit rootItem->window()->focusObjectChanged(activeFocusItem); if (!changed.isEmpty()) notifyFocusChangesRecur(changed.data(), changed.count() - 1); } -void QQuickWindowPrivate::clearFocusInScope(QQuickItem *scope, QQuickItem *item, Qt::FocusReason reason, FocusOptions options) +void QQuickDeliveryAgentPrivate::clearFocusInScope(QQuickItem *scope, QQuickItem *item, Qt::FocusReason reason, FocusOptions options) { - Q_Q(QQuickWindow); - Q_ASSERT(item); - Q_ASSERT(scope || item == contentItem); + Q_ASSERT(scope || item == rootItem); - qCDebug(lcFocus) << "QQuickWindowPrivate::clearFocusInScope():"; + qCDebug(lcFocus) << "QQuickDeliveryAgentPrivate::clearFocusInScope():"; qCDebug(lcFocus) << " scope:" << (QObject *)scope; qCDebug(lcFocus) << " item:" << (QObject *)item; qCDebug(lcFocus) << " activeFocusItem:" << (QObject *)activeFocusItem; @@ -473,7 +473,7 @@ void QQuickWindowPrivate::clearFocusInScope(QQuickItem *scope, QQuickItem *item, if (scope) { scopePrivate = QQuickItemPrivate::get(scope); if ( !scopePrivate->subFocusItem ) - return;//No focus, nothing to do. + return; // No focus, nothing to do. } QQuickItem *currentActiveFocusItem = activeFocusItem; @@ -484,10 +484,10 @@ void QQuickWindowPrivate::clearFocusInScope(QQuickItem *scope, QQuickItem *item, QVarLengthArray<QQuickItem *, 20> changed; - Q_ASSERT(item == contentItem || item == scopePrivate->subFocusItem); + Q_ASSERT(item == rootItem || item == scopePrivate->subFocusItem); // Does this change the active focus? - if (item == contentItem || scopePrivate->activeFocus) { + if (item == rootItem || scopePrivate->activeFocus) { oldActiveFocusItem = activeFocusItem; newActiveFocusItem = scope; @@ -509,7 +509,7 @@ void QQuickWindowPrivate::clearFocusInScope(QQuickItem *scope, QQuickItem *item, } } - if (item != contentItem && !(options & DontChangeSubFocusItem)) { + if (item != rootItem && !(options & DontChangeSubFocusItem)) { QQuickItem *oldSubFocusItem = scopePrivate->subFocusItem; if (oldSubFocusItem && !(options & DontChangeFocusProperty)) { QQuickItemPrivate::get(oldSubFocusItem)->focus = false; @@ -530,8 +530,7 @@ void QQuickWindowPrivate::clearFocusInScope(QQuickItem *scope, QQuickItem *item, } // Now that all the state is changed, emit signals & events - // We must do this last, as this process may result in further changes to - // focus. + // We must do this last, as this process may result in further changes to focus. if (oldActiveFocusItem) { QFocusEvent event(QEvent::FocusOut, reason); QCoreApplication::sendEvent(oldActiveFocusItem, &event); @@ -544,21 +543,21 @@ void QQuickWindowPrivate::clearFocusInScope(QQuickItem *scope, QQuickItem *item, } if (activeFocusItem != currentActiveFocusItem) - emit q->focusObjectChanged(activeFocusItem); + emit rootItem->window()->focusObjectChanged(activeFocusItem); if (!changed.isEmpty()) notifyFocusChangesRecur(changed.data(), changed.count() - 1); } -void QQuickWindowPrivate::clearFocusObject() +void QQuickDeliveryAgentPrivate::clearFocusObject() { - if (activeFocusItem == contentItem) + if (activeFocusItem == rootItem) return; - clearFocusInScope(contentItem, QQuickItemPrivate::get(contentItem)->subFocusItem, Qt::OtherFocusReason); + clearFocusInScope(rootItem, QQuickItemPrivate::get(rootItem)->subFocusItem, Qt::OtherFocusReason); } -void QQuickWindowPrivate::notifyFocusChangesRecur(QQuickItem **items, int remaining) +void QQuickDeliveryAgentPrivate::notifyFocusChangesRecur(QQuickItem **items, int remaining) { QPointer<QQuickItem> item(*items); @@ -581,61 +580,152 @@ void QQuickWindowPrivate::notifyFocusChangesRecur(QQuickItem **items, int remain } } -bool QQuickWindowPrivate::clearHover(ulong timestamp) +bool QQuickDeliveryAgentPrivate::clearHover(ulong timestamp) { - Q_Q(QQuickWindow); if (hoverItems.isEmpty()) return false; - QPointF pos = q->mapFromGlobal(QGuiApplicationPrivate::lastCursorPosition.toPoint()); + QQuickWindow *window = rootItem->window(); + if (!window) + return false; + + QPointF pos = window->mapFromGlobal(QGuiApplicationPrivate::lastCursorPosition.toPoint()); bool accepted = false; for (QQuickItem* item : qAsConst(hoverItems)) { - accepted = sendHoverEvent(QEvent::HoverLeave, item, pos, pos, QGuiApplication::keyboardModifiers(), timestamp, true) || accepted; + if (item) + accepted = sendHoverEvent(QEvent::HoverLeave, item, pos, pos, QGuiApplication::keyboardModifiers(), timestamp, true) || accepted; } hoverItems.clear(); return accepted; } -/*! \reimp */ -bool QQuickWindow::event(QEvent *e) +void QQuickDeliveryAgentPrivate::updateFocusItemTransform() { - Q_D(QQuickWindow); +#if QT_CONFIG(im) + if (activeFocusItem && QGuiApplication::focusObject() == activeFocusItem) { + QQuickItemPrivate *focusPrivate = QQuickItemPrivate::get(activeFocusItem); + QGuiApplication::inputMethod()->setInputItemTransform(focusPrivate->itemToWindowTransform()); + QGuiApplication::inputMethod()->setInputItemRectangle(QRectF(0, 0, focusPrivate->width, focusPrivate->height)); + activeFocusItem->updateInputMethod(Qt::ImInputItemClipRectangle); + } +#endif +} - switch (e->type()) { +/*! \internal + QQuickDeliveryAgent delivers events to a tree of Qt Quick Items, beginning + with the given root item, which is usually QQuickWindow::rootItem() but + may alternatively be embedded into a Qt Quick 3D scene or something else. +*/ +QQuickDeliveryAgent::QQuickDeliveryAgent(QQuickItem *rootItem) + : QObject(*new QQuickDeliveryAgentPrivate(rootItem), rootItem) +{ +} +QQuickDeliveryAgent::~QQuickDeliveryAgent() +{ +} + +QQuickDeliveryAgent::Transform::~Transform() +{ +} + +/*! \internal + Returns the QQuickDeliveryAgent instance that we remember was delivering the + given \a pt at the time that it was grabbed. Failing that, choose a suitable agent. +*/ +QQuickDeliveryAgent *QQuickDeliveryAgent::grabberAgent(QPointerEvent *pe, const QEventPoint &pt) +{ + auto devExtra = QQuickDeliveryAgentPrivate::deviceExtra(pe->device()); + QQuickDeliveryAgent *ret = devExtra->grabbedEventPointDeliveryAgents.value(pt.id()); + if (ret) { + qCDebug(lcPtr) << pe->type() << "point" << pt.id() << pt.state() + << "@" << pt.scenePosition() << "will be re-delivered via known agent" << ret; + } + return ret; +} + +QQuickItem *QQuickDeliveryAgent::rootItem() const +{ + Q_D(const QQuickDeliveryAgent); + return d->rootItem; +} + +/*! \internal + QQuickDeliveryAgent takes ownership of the given \a transform, which + encapsulates the ability to transform viewport coordinates to rootItem coordinates. +*/ +void QQuickDeliveryAgent::setSceneTransform(QQuickDeliveryAgent::Transform *transform) +{ + Q_D(QQuickDeliveryAgent); + if (d->sceneTransform == transform) + return; + qCDebug(lcPtr) << this << d->sceneTransform << "->" << transform; + if (d->sceneTransform) + delete d->sceneTransform; + d->sceneTransform = transform; +} + +bool QQuickDeliveryAgent::event(QEvent *ev) +{ + Q_D(QQuickDeliveryAgent); + + switch (ev->type()) { + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + case QEvent::MouseButtonDblClick: + case QEvent::MouseMove: { + QMouseEvent *me = static_cast<QMouseEvent*>(ev); + d->handleMouseEvent(me); + break; + } + case QEvent::HoverEnter: + case QEvent::HoverLeave: + case QEvent::HoverMove: { + QHoverEvent *he = static_cast<QHoverEvent*>(ev); + bool accepted = he->isAccepted(); + bool delivered = d->deliverHoverEvent(d->rootItem, he->scenePosition(), + he->points().first().sceneLastPosition(), + he->modifiers(), he->timestamp(), accepted); + d->lastMousePosition = he->scenePosition(); + he->setAccepted(accepted); +#if QT_CONFIG(cursor) + QQuickWindowPrivate::get(d->rootItem->window())->updateCursor(d->sceneTransform ? + d->sceneTransform->map(he->scenePosition()) : he->scenePosition(), d->rootItem); +#endif + return delivered; + } case QEvent::TouchBegin: case QEvent::TouchUpdate: case QEvent::TouchEnd: { - QTouchEvent *touch = static_cast<QTouchEvent*>(e); + QTouchEvent *touch = static_cast<QTouchEvent*>(ev); d->handleTouchEvent(touch); if (Q_LIKELY(QCoreApplication::testAttribute(Qt::AA_SynthesizeMouseForUnhandledTouchEvents))) { // we consume all touch events ourselves to avoid duplicate // mouse delivery by QtGui mouse synthesis - e->accept(); + ev->accept(); } - return true; - } break; + } case QEvent::TouchCancel: // return in order to avoid the QWindow::event below - return d->deliverTouchCancelEvent(static_cast<QTouchEvent*>(e)); + return d->deliverTouchCancelEvent(static_cast<QTouchEvent*>(ev)); break; case QEvent::Enter: { - if (!d->contentItem) + if (!d->rootItem) return false; - QEnterEvent *enter = static_cast<QEnterEvent*>(e); + QEnterEvent *enter = static_cast<QEnterEvent*>(ev); bool accepted = enter->isAccepted(); - bool delivered = d->deliverHoverEvent(d->contentItem, enter->scenePosition(), d->lastMousePosition, - QGuiApplication::keyboardModifiers(), 0L, accepted); + bool delivered = d->deliverHoverEvent(d->rootItem, enter->scenePosition(), + enter->points().first().sceneLastPosition(), + enter->modifiers(), enter->timestamp(), accepted); d->lastMousePosition = enter->scenePosition(); enter->setAccepted(accepted); #if QT_CONFIG(cursor) - d->updateCursor(mapFromGlobal(QCursor::pos())); + QQuickWindowPrivate::get(d->rootItem->window())->updateCursor(enter->scenePosition(), d->rootItem); #endif return delivered; } - break; case QEvent::Leave: d->clearHover(); d->lastMousePosition = QPointF(); @@ -645,43 +735,23 @@ bool QQuickWindow::event(QEvent *e) case QEvent::DragLeave: case QEvent::DragMove: case QEvent::Drop: - d->deliverDragEvent(d->dragGrabber, e); + d->deliverDragEvent(d->dragGrabber, ev); break; #endif - case QEvent::WindowDeactivate: - d->handleWindowDeactivate(); - break; - case QEvent::PlatformSurface: - if ((static_cast<QPlatformSurfaceEvent *>(e))->surfaceEventType() == QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed) { - // Ensure that the rendering thread is notified before - // the QPlatformWindow is destroyed. - if (d->windowManager) - d->windowManager->hide(this); - } - break; case QEvent::FocusAboutToChange: #if QT_CONFIG(im) if (d->activeFocusItem) qGuiApp->inputMethod()->commit(); #endif break; - case QEvent::UpdateRequest: { - if (d->windowManager) - d->windowManager->handleUpdateRequest(this); - break; - } #if QT_CONFIG(gestures) case QEvent::NativeGesture: - d->deliverSinglePointEventUntilAccepted(static_cast<QPointerEvent *>(e)); + d->deliverSinglePointEventUntilAccepted(static_cast<QPointerEvent *>(ev)); break; #endif case QEvent::ShortcutOverride: if (d->activeFocusItem) - QCoreApplication::sendEvent(d->activeFocusItem, e); - return true; - case QEvent::LanguageChange: - if (d->contentItem) - QCoreApplication::sendEvent(d->contentItem, e); + QCoreApplication::sendEvent(d->activeFocusItem, ev); break; case QEvent::InputMethod: case QEvent::InputMethodQuery: @@ -689,38 +759,52 @@ bool QQuickWindow::event(QEvent *e) QQuickItem *target = d->activeFocusItem; // while an input method delivers the event, this window might still be inactive if (!target) { - target = d->contentItem; + target = d->rootItem; if (!target || !target->isEnabled()) break; // see setFocusInScope for a similar loop while (target->isFocusScope() && target->scopedFocusItem() && target->scopedFocusItem()->isEnabled()) target = target->scopedFocusItem(); } - if (target) { - QCoreApplication::sendEvent(target, e); - return true; - } + if (target) + QCoreApplication::sendEvent(target, ev); } break; - default: + case QEvent::Wheel: { + auto event = static_cast<QWheelEvent *>(ev); + qCDebug(lcMouse) << event; + + //if the actual wheel event was accepted, accept the compatibility wheel event and return early + if (d->lastWheelEventAccepted && event->angleDelta().isNull() && event->phase() == Qt::ScrollUpdate) + return true; + + event->ignore(); + Q_QUICK_INPUT_PROFILE(QQuickProfiler::Mouse, QQuickProfiler::InputMouseWheel, + event->angleDelta().x(), event->angleDelta().y()); + d->deliverSinglePointEventUntilAccepted(event); + d->lastWheelEventAccepted = event->isAccepted(); break; } + default: + return false; + } - if (e->type() == QEvent::Type(QQuickWindowPrivate::FullUpdateRequest)) - update(); - else if (e->type() == QEvent::Type(QQuickWindowPrivate::TriggerContextCreationFailure)) - d->windowManager->handleContextCreationFailure(this); - - return QWindow::event(e); + return true; } -void QQuickWindowPrivate::deliverKeyEvent(QKeyEvent *e) +void QQuickDeliveryAgentPrivate::deliverKeyEvent(QKeyEvent *e) { if (activeFocusItem) { + const bool keyPress = (e->type() == QEvent::KeyPress); + if (keyPress) + Q_QUICK_INPUT_PROFILE(QQuickProfiler::Key, QQuickProfiler::InputKeyPress, e->key(), e->modifiers()); + else + Q_QUICK_INPUT_PROFILE(QQuickProfiler::Key, QQuickProfiler::InputKeyRelease, e->key(), e->modifiers()); + QQuickItem *item = activeFocusItem; // In case of generated event, trigger ShortcutOverride event - if (e->type() == QEvent::KeyPress && e->spontaneous() == false) + if (keyPress && e->spontaneous() == false) qt_sendShortcutOverrideEvent(item, e->timestamp(), e->key(), e->modifiers(), e->text(), e->isAutoRepeat(), e->count()); @@ -734,6 +818,39 @@ void QQuickWindowPrivate::deliverKeyEvent(QKeyEvent *e) } } +QQuickDeliveryAgentPrivate::QQuickDeliveryAgentPrivate(QQuickItem *root) : + QObjectPrivate(), + rootItem(root), + // a plain QQuickItem can be a subscene root; a QQuickRootItem always belongs directly to a QQuickWindow + isSubsceneAgent(!qmlobject_cast<QQuickRootItem *>(rootItem)) +{ +#if QT_CONFIG(quick_draganddrop) + dragGrabber = new QQuickDragGrabber; +#endif +} + +QQuickDeliveryAgentPrivate::~QQuickDeliveryAgentPrivate() +{ + Q_Q(QQuickDeliveryAgent); + for (auto dev : knownPointingDevices) { + auto devPriv = QPointingDevicePrivate::get(dev); + if (devPriv->qqExtra) { + auto &flatmap = static_cast<QQuickPointingDeviceExtra *>(devPriv->qqExtra)->grabbedEventPointDeliveryAgents; + for (auto it = flatmap.begin(); it != flatmap.end(); ) { + if (it.value() == q) + it = flatmap.erase(it); + else + ++it; + } + } + } +#if QT_CONFIG(quick_draganddrop) + delete dragGrabber; + dragGrabber = nullptr; +#endif + delete sceneTransform; +} + /*! \internal Make a copy of any type of QPointerEvent, and optionally localize it by setting its first point's local position() if \a transformedLocalPos is given. @@ -741,7 +858,7 @@ void QQuickWindowPrivate::deliverKeyEvent(QKeyEvent *e) \note some subclasses of QSinglePointEvent, such as QWheelEvent, add extra storage. This function doesn't yet support cloning all of those; it can be extended if needed. */ -QPointerEvent *QQuickWindowPrivate::clonePointerEvent(QPointerEvent *event, std::optional<QPointF> transformedLocalPos) +QPointerEvent *QQuickDeliveryAgentPrivate::clonePointerEvent(QPointerEvent *event, std::optional<QPointF> transformedLocalPos) { QPointerEvent *ret = event->clone(); QMutableEventPoint &point = QMutableEventPoint::from(ret->point(0)); @@ -753,8 +870,8 @@ QPointerEvent *QQuickWindowPrivate::clonePointerEvent(QPointerEvent *event, std: return ret; } -void QQuickWindowPrivate::deliverToPassiveGrabbers(const QVector<QPointer <QObject> > &passiveGrabbers, - QPointerEvent *pointerEvent) +void QQuickDeliveryAgentPrivate::deliverToPassiveGrabbers(const QVector<QPointer <QObject> > &passiveGrabbers, + QPointerEvent *pointerEvent) { const QVector<QObject *> &eventDeliveryTargets = QQuickPointerHandlerPrivate::deviceDeliveryTargets(pointerEvent->device()); @@ -786,7 +903,7 @@ void QQuickWindowPrivate::deliverToPassiveGrabbers(const QVector<QPointer <QObje } } -bool QQuickWindowPrivate::sendHoverEvent(QEvent::Type type, QQuickItem *item, +bool QQuickDeliveryAgentPrivate::sendHoverEvent(QEvent::Type type, QQuickItem *item, const QPointF &scenePos, const QPointF &lastScenePos, Qt::KeyboardModifiers modifiers, ulong timestamp, bool accepted) @@ -808,23 +925,21 @@ bool QQuickWindowPrivate::sendHoverEvent(QEvent::Type type, QQuickItem *item, } // TODO later: specify the device in case of multi-mouse scenario, or mouse and tablet both in use -bool QQuickWindowPrivate::deliverHoverEvent(QQuickItem *item, const QPointF &scenePos, const QPointF &lastScenePos, - Qt::KeyboardModifiers modifiers, ulong timestamp, bool &accepted) +bool QQuickDeliveryAgentPrivate::deliverHoverEvent(QQuickItem *item, const QPointF &scenePos, const QPointF &lastScenePos, + Qt::KeyboardModifiers modifiers, ulong timestamp, bool &accepted) { - Q_Q(QQuickWindow); + Q_Q(QQuickDeliveryAgent); QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item); + const QPointF itemPos = item->mapFromScene(sceneTransform ? sceneTransform->map(scenePos) : scenePos); - if (itemPrivate->flags & QQuickItem::ItemClipsChildrenToShape) { - QPointF p = item->mapFromScene(scenePos); - if (!item->contains(p)) - return false; - } + if ((itemPrivate->flags & QQuickItem::ItemClipsChildrenToShape || item == rootItem) && !item->contains(itemPos)) + return false; if (Q_UNLIKELY(lcHoverTrace().isDebugEnabled())) { if (lastScenePos == scenePos) - qCDebug(lcHoverTrace) << scenePos << "(unchanged)" << item << "subtreeHoverEnabled" << itemPrivate->subtreeHoverEnabled << "in window" << windowTitle; + qCDebug(lcHoverTrace) << q << scenePos << "(unchanged)" << "," << itemPos << "in" << item << "subtreeHoverEnabled" << itemPrivate->subtreeHoverEnabled; else - qCDebug(lcHoverTrace) << lastScenePos << "->" << scenePos << item << "subtreeHoverEnabled" << itemPrivate->subtreeHoverEnabled << "in window" << windowTitle; + qCDebug(lcHoverTrace) << q << lastScenePos << "->" << scenePos << "," << itemPos << "in" << item << ", subtreeHoverEnabled" << itemPrivate->subtreeHoverEnabled; } if (itemPrivate->subtreeHoverEnabled) { QList<QQuickItem *> children = itemPrivate->paintOrderChildItems(); @@ -840,8 +955,9 @@ bool QQuickWindowPrivate::deliverHoverEvent(QQuickItem *item, const QPointF &sce } if (itemPrivate->hasPointerHandlers()) { - const QPointF localPos = item->mapFromScene(scenePos); - QMouseEvent hoverEvent(QEvent::MouseMove, localPos, scenePos, q->mapToGlobal(scenePos), Qt::NoButton, Qt::NoButton, modifiers); + QMouseEvent hoverEvent(QEvent::MouseMove, itemPos, scenePos, + itemPrivate->window->mapToGlobal(scenePos), Qt::NoButton, Qt::NoButton, modifiers); + ensureDeviceConnected(hoverEvent.pointingDevice()); hoverEvent.setTimestamp(timestamp); hoverEvent.setAccepted(true); for (QQuickPointerHandler *h : itemPrivate->extra->pointerHandlers) @@ -849,53 +965,50 @@ bool QQuickWindowPrivate::deliverHoverEvent(QQuickItem *item, const QPointF &sce hh->handlePointerEvent(&hoverEvent); } - if (itemPrivate->hoverEnabled) { - QPointF p = item->mapFromScene(scenePos); - if (item->contains(p)) { - if (!hoverItems.isEmpty() && hoverItems.at(0) == item) { - //move + if (itemPrivate->hoverEnabled && item->contains(itemPos)) { + if (!hoverItems.isEmpty() && hoverItems.at(0) == item) { + //move + accepted = sendHoverEvent(QEvent::HoverMove, item, scenePos, lastScenePos, modifiers, timestamp, accepted); + } else { + QList<QQuickItem *> itemsToHover; + QQuickItem* parent = item; + itemsToHover << item; + while ((parent = parent->parentItem())) + itemsToHover << parent; + + // Leaving from previous hovered items until we reach the item or one of its ancestors. + while (!hoverItems.isEmpty() && !itemsToHover.contains(hoverItems.at(0))) { + QQuickItem *hoverLeaveItem = hoverItems.takeFirst(); + sendHoverEvent(QEvent::HoverLeave, hoverLeaveItem, scenePos, lastScenePos, modifiers, timestamp, accepted); + } + + if (!hoverItems.isEmpty() && hoverItems.at(0) == item) {//Not entering a new Item + // ### Shouldn't we send moves for the parent items as well? accepted = sendHoverEvent(QEvent::HoverMove, item, scenePos, lastScenePos, modifiers, timestamp, accepted); } else { - QList<QQuickItem *> itemsToHover; - QQuickItem* parent = item; - itemsToHover << item; - while ((parent = parent->parentItem())) - itemsToHover << parent; - - // Leaving from previous hovered items until we reach the item or one of its ancestors. - while (!hoverItems.isEmpty() && !itemsToHover.contains(hoverItems.at(0))) { - QQuickItem *hoverLeaveItem = hoverItems.takeFirst(); - sendHoverEvent(QEvent::HoverLeave, hoverLeaveItem, scenePos, lastScenePos, modifiers, timestamp, accepted); - } - - if (!hoverItems.isEmpty() && hoverItems.at(0) == item) {//Not entering a new Item - // ### Shouldn't we send moves for the parent items as well? - accepted = sendHoverEvent(QEvent::HoverMove, item, scenePos, lastScenePos, modifiers, timestamp, accepted); - } else { - // Enter items that are not entered yet. - int startIdx = -1; - if (!hoverItems.isEmpty()) - startIdx = itemsToHover.indexOf(hoverItems.at(0)) - 1; - if (startIdx == -1) - startIdx = itemsToHover.count() - 1; - - for (int i = startIdx; i >= 0; i--) { - QQuickItem *itemToHover = itemsToHover.at(i); - QQuickItemPrivate *itemToHoverPrivate = QQuickItemPrivate::get(itemToHover); - // The item may be about to be deleted or reparented to another window - // due to another hover event delivered in this function. If that is the - // case, sending a hover event here will cause a crash or other bad - // behavior when the leave event is generated. Checking - // itemToHoverPrivate->window here prevents that case. - if (itemToHoverPrivate->window == q && itemToHoverPrivate->hoverEnabled) { - hoverItems.prepend(itemToHover); - sendHoverEvent(QEvent::HoverEnter, itemToHover, scenePos, lastScenePos, modifiers, timestamp, accepted); - } + // Enter items that are not entered yet. + int startIdx = -1; + if (!hoverItems.isEmpty()) + startIdx = itemsToHover.indexOf(hoverItems.at(0)) - 1; + if (startIdx == -1) + startIdx = itemsToHover.count() - 1; + + for (int i = startIdx; i >= 0; i--) { + QQuickItem *itemToHover = itemsToHover.at(i); + QQuickItemPrivate *itemToHoverPrivate = QQuickItemPrivate::get(itemToHover); + // The item may be about to be deleted or reparented to another window + // due to another hover event delivered in this function. If that is the + // case, sending a hover event here will cause a crash or other bad + // behavior when the leave event is generated. Checking + // itemToHoverPrivate->window here prevents that case. + if (itemToHoverPrivate->window == itemPrivate->window && itemToHoverPrivate->hoverEnabled) { + hoverItems.prepend(itemToHover); + sendHoverEvent(QEvent::HoverEnter, itemToHover, scenePos, lastScenePos, modifiers, timestamp, accepted); } } } - return true; } + return true; } return false; @@ -903,12 +1016,12 @@ bool QQuickWindowPrivate::deliverHoverEvent(QQuickItem *item, const QPointF &sce // Simple delivery of non-mouse, non-touch Pointer Events: visit the items and handlers // in the usual reverse-paint-order until propagation is stopped -bool QQuickWindowPrivate::deliverSinglePointEventUntilAccepted(QPointerEvent *event) +bool QQuickDeliveryAgentPrivate::deliverSinglePointEventUntilAccepted(QPointerEvent *event) { Q_ASSERT(event->points().count() == 1); QQuickPointerHandlerPrivate::deviceDeliveryTargets(event->pointingDevice()).clear(); QEventPoint &point = event->point(0); - QVector<QQuickItem *> targetItems = pointerTargets(contentItem, event, point, false, false); + QVector<QQuickItem *> targetItems = pointerTargets(rootItem, event, point, false, false); point.setAccepted(false); for (QQuickItem *item : targetItems) { @@ -929,7 +1042,7 @@ bool QQuickWindowPrivate::deliverSinglePointEventUntilAccepted(QPointerEvent *ev return false; // it wasn't handled } -bool QQuickWindowPrivate::deliverTouchCancelEvent(QTouchEvent *event) +bool QQuickDeliveryAgentPrivate::deliverTouchCancelEvent(QTouchEvent *event) { qCDebug(lcTouch) << event; @@ -938,11 +1051,11 @@ bool QQuickWindowPrivate::deliverTouchCancelEvent(QTouchEvent *event) // Deliver it to all items and handlers that have active touches. const_cast<QPointingDevicePrivate *>(QPointingDevicePrivate::get(event->pointingDevice()))-> sendTouchCancelEvent(event); - cancelTouchMouseSynthesis(); + return true; } -void QQuickWindowPrivate::deliverDelayedTouchEvent() +void QQuickDeliveryAgentPrivate::deliverDelayedTouchEvent() { // Deliver and delete delayedTouch. // Set delayedTouch to nullptr before delivery to avoid redelivery in case of @@ -962,10 +1075,9 @@ void QQuickWindowPrivate::deliverDelayedTouchEvent() just a plain QEvent with no extra data, and because the application state change is delivered via a signal rather than an event. */ -void QQuickWindowPrivate::handleWindowDeactivate() +void QQuickDeliveryAgentPrivate::handleWindowDeactivate(QQuickWindow *win) { - Q_Q(QQuickWindow); - qCDebug(lcFocus) << "deactivated" << windowTitle; + qCDebug(lcFocus) << "deactivated" << win->title(); const auto inputDevices = QInputDevice::devices(); for (auto device : inputDevices) { if (auto pointingDevice = qobject_cast<const QPointingDevice *>(device)) { @@ -974,9 +1086,9 @@ void QQuickWindowPrivate::handleWindowDeactivate() if (!epd.exclusiveGrabber.isNull()) { bool relevant = false; if (QQuickItem *item = qmlobject_cast<QQuickItem *>(epd.exclusiveGrabber.data())) - relevant = (item->window() == q); + relevant = (item->window() == win); else if (QQuickPointerHandler *handler = qmlobject_cast<QQuickPointerHandler *>(epd.exclusiveGrabber.data())) - relevant = (handler->parentItem()->window() == q); + relevant = (handler->parentItem()->window() == win); if (relevant) devPriv->setExclusiveGrabber(nullptr, epd.eventPoint, nullptr); } @@ -987,7 +1099,7 @@ void QQuickWindowPrivate::handleWindowDeactivate() } } -bool QQuickWindowPrivate::allUpdatedPointsAccepted(const QPointerEvent *ev) +bool QQuickDeliveryAgentPrivate::allUpdatedPointsAccepted(const QPointerEvent *ev) { for (auto &point : ev->points()) { if (point.state() != QEventPoint::State::Pressed && !point.isAccepted()) @@ -1002,15 +1114,17 @@ bool QQuickWindowPrivate::allUpdatedPointsAccepted(const QPointerEvent *ev) Unlike QMutableTouchEvent::localized(), this modifies the QEventPoint instances in \a ev, which is more efficient than making a copy. */ -void QQuickWindowPrivate::localizePointerEvent(QPointerEvent *ev, const QQuickItem *dest) +void QQuickDeliveryAgentPrivate::localizePointerEvent(QPointerEvent *ev, const QQuickItem *dest) { for (int i = 0; i < ev->pointCount(); ++i) { auto &point = QMutableEventPoint::from(ev->point(i)); QMutableEventPoint::from(point).setPosition(dest->mapFromScene(point.scenePosition())); + qCDebug(lcPtrLoc) << ev->type() << "@" << point.scenePosition() << "to" + << dest << "@" << dest->mapToScene(QPointF()) << "->" << point; } } -QList<QObject *> QQuickWindowPrivate::exclusiveGrabbers(QPointerEvent *ev) +QList<QObject *> QQuickDeliveryAgentPrivate::exclusiveGrabbers(QPointerEvent *ev) { QList<QObject *> result; for (const QEventPoint &point : ev->points()) { @@ -1022,7 +1136,16 @@ QList<QObject *> QQuickWindowPrivate::exclusiveGrabbers(QPointerEvent *ev) return result; } -bool QQuickWindowPrivate::isMouseEvent(const QPointerEvent *ev) +bool QQuickDeliveryAgentPrivate::anyPointGrabbed(const QPointerEvent *ev) +{ + for (const QEventPoint &point : ev->points()) { + if (ev->exclusiveGrabber(point) || !ev->passiveGrabbers(point).isEmpty()) + return true; + } + return false; +} + +bool QQuickDeliveryAgentPrivate::isMouseEvent(const QPointerEvent *ev) { switch (ev->type()) { case QEvent::MouseButtonPress: @@ -1035,7 +1158,7 @@ bool QQuickWindowPrivate::isMouseEvent(const QPointerEvent *ev) } } -bool QQuickWindowPrivate::isTouchEvent(const QPointerEvent *ev) +bool QQuickDeliveryAgentPrivate::isTouchEvent(const QPointerEvent *ev) { switch (ev->type()) { case QEvent::TouchBegin: @@ -1048,7 +1171,7 @@ bool QQuickWindowPrivate::isTouchEvent(const QPointerEvent *ev) } } -bool QQuickWindowPrivate::isTabletEvent(const QPointerEvent *ev) +bool QQuickDeliveryAgentPrivate::isTabletEvent(const QPointerEvent *ev) { switch (ev->type()) { case QEvent::TabletPress: @@ -1062,9 +1185,22 @@ bool QQuickWindowPrivate::isTabletEvent(const QPointerEvent *ev) } } -bool QQuickWindowPrivate::compressTouchEvent(QTouchEvent *event) +QQuickPointingDeviceExtra *QQuickDeliveryAgentPrivate::deviceExtra(const QInputDevice *device) +{ + QInputDevicePrivate *devPriv = QInputDevicePrivate::get(const_cast<QInputDevice *>(device)); + if (devPriv->qqExtra) + return static_cast<QQuickPointingDeviceExtra *>(devPriv->qqExtra); + auto extra = new QQuickPointingDeviceExtra; + devPriv->qqExtra = extra; + QObject::connect(device, &QObject::destroyed, [devPriv]() { + delete static_cast<QQuickPointingDeviceExtra *>(devPriv->qqExtra); + devPriv->qqExtra = nullptr; + }); + return extra; +} + +bool QQuickDeliveryAgentPrivate::compressTouchEvent(QTouchEvent *event) { - Q_Q(QQuickWindow); QEventPoint::States states = event->touchPointStates(); if (states.testFlag(QEventPoint::State::Pressed) || states.testFlag(QEventPoint::State::Released)) { // we can only compress an event that doesn't include any pressed or released points @@ -1075,10 +1211,8 @@ bool QQuickWindowPrivate::compressTouchEvent(QTouchEvent *event) delayedTouch.reset(new QMutableTouchEvent(event->type(), event->pointingDevice(), event->modifiers(), event->points())); delayedTouch->setTimestamp(event->timestamp()); qCDebug(lcTouchCmprs) << "delayed" << delayedTouch.data(); - if (renderControl) - QQuickRenderControlPrivate::get(renderControl)->maybeUpdate(); - else if (windowManager) - windowManager->maybeUpdate(q); + if (QQuickWindow *window = rootItem->window()) + window->maybeUpdate(); return true; } @@ -1127,8 +1261,9 @@ bool QQuickWindowPrivate::compressTouchEvent(QTouchEvent *event) // - translate the event to window coordinates // - compress the event instead of delivering it if applicable // - call deliverTouchPoints to actually dispatch the points -void QQuickWindowPrivate::handleTouchEvent(QTouchEvent *event) +void QQuickDeliveryAgentPrivate::handleTouchEvent(QTouchEvent *event) { + Q_Q(QQuickDeliveryAgent); translateTouchEvent(event); // TODO remove: touch and mouse should be independent until we come to touch->mouse synth if (event->pointCount()) { @@ -1140,7 +1275,7 @@ void QQuickWindowPrivate::handleTouchEvent(QTouchEvent *event) } } - qCDebug(lcTouch) << event; + qCDebug(lcTouch) << q << event; static bool qquickwindow_no_touch_compression = qEnvironmentVariableIsSet("QML_NO_TOUCH_COMPRESSION"); @@ -1158,13 +1293,14 @@ void QQuickWindowPrivate::handleTouchEvent(QTouchEvent *event) } } -void QQuickWindowPrivate::handleMouseEvent(QMouseEvent *event) +void QQuickDeliveryAgentPrivate::handleMouseEvent(QMouseEvent *event) { + Q_Q(QQuickDeliveryAgent); if (event->source() == Qt::MouseEventSynthesizedBySystem) { event->accept(); return; } - qCDebug(lcMouse) << event; + qCDebug(lcMouse) << q << event; switch (event->type()) { case QEvent::MouseButtonPress: @@ -1177,7 +1313,7 @@ void QQuickWindowPrivate::handleMouseEvent(QMouseEvent *event) event->buttons()); deliverPointerEvent(event); #if QT_CONFIG(cursor) - updateCursor(event->scenePosition()); + QQuickWindowPrivate::get(rootItem->window())->updateCursor(event->scenePosition()); #endif break; case QEvent::MouseButtonDblClick: @@ -1190,17 +1326,15 @@ void QQuickWindowPrivate::handleMouseEvent(QMouseEvent *event) Q_QUICK_INPUT_PROFILE(QQuickProfiler::Mouse, QQuickProfiler::InputMouseMove, event->position().x(), event->position().y()); - qCDebug(lcHoverTrace) << this; - - #if QT_CONFIG(cursor) - updateCursor(event->scenePosition()); - #endif +#if QT_CONFIG(cursor) + QQuickWindowPrivate::get(rootItem->window())->updateCursor(event->scenePosition()); +#endif if (!event->points().count() || !event->exclusiveGrabber(event->point(0))) { QPointF last = lastMousePosition.isNull() ? event->scenePosition() : lastMousePosition; lastMousePosition = event->scenePosition(); - + qCDebug(lcHoverTrace) << q << "mouse pos" << last << "->" << lastMousePosition << event; bool accepted = event->isAccepted(); - bool delivered = deliverHoverEvent(contentItem, event->scenePosition(), last, event->modifiers(), event->timestamp(), accepted); + bool delivered = deliverHoverEvent(rootItem, event->scenePosition(), last, event->modifiers(), event->timestamp(), accepted); if (!delivered) { //take care of any exits accepted = clearHover(event->timestamp()); @@ -1216,9 +1350,9 @@ void QQuickWindowPrivate::handleMouseEvent(QMouseEvent *event) } } -void QQuickWindowPrivate::flushFrameSynchronousEvents() +void QQuickDeliveryAgentPrivate::flushFrameSynchronousEvents(QQuickWindow *win) { - Q_Q(QQuickWindow); + Q_Q(QQuickDeliveryAgent); if (delayedTouch) { deliverDelayedTouchEvent(); @@ -1239,23 +1373,39 @@ void QQuickWindowPrivate::flushFrameSynchronousEvents() // whose delegates contain MouseAreas), a MouseArea needs to know // whether it has moved into a position where it is now under the cursor. // TODO do this for each known mouse device or come up with a different strategy - if (!q->mouseGrabberItem() && !lastMousePosition.isNull() && dirtyItemList) { + if (frameSynchronousHoverEnabled && !win->mouseGrabberItem() && + !lastMousePosition.isNull() && QQuickWindowPrivate::get(win)->dirtyItemList) { bool accepted = false; - bool delivered = deliverHoverEvent(contentItem, lastMousePosition, lastMousePosition, QGuiApplication::keyboardModifiers(), 0, accepted); + qCDebug(lcHoverTrace) << q << "delivering frame-sync hover to root"; + bool delivered = deliverHoverEvent(rootItem, lastMousePosition, lastMousePosition, QGuiApplication::keyboardModifiers(), 0, accepted); if (!delivered) clearHover(); // take care of any exits + qCDebug(lcHoverTrace) << q << "frame-sync hover delivery done"; } #endif } -void QQuickWindowPrivate::onGrabChanged(QObject *grabber, QPointingDevice::GrabTransition transition, - const QPointerEvent *event, const QEventPoint &point) +void QQuickDeliveryAgentPrivate::onGrabChanged(QObject *grabber, QPointingDevice::GrabTransition transition, + const QPointerEvent *event, const QEventPoint &point) { - qCDebug(lcPtrGrab) << grabber << transition << event << point; + Q_Q(QQuickDeliveryAgent); + const bool grabGained = (transition == QPointingDevice::GrabTransition::GrabExclusive || + transition == QPointingDevice::GrabTransition::GrabPassive); + + QQuickDeliveryAgent *subsceneAgent = nullptr; + // note: event can be null, if the signal was emitted from QPointingDevicePrivate::removeGrabber(grabber) if (auto *handler = qmlobject_cast<QQuickPointerHandler *>(grabber)) { handler->onGrabChanged(handler, transition, const_cast<QPointerEvent *>(event), const_cast<QEventPoint &>(point)); + if (isSubsceneAgent) { + auto itemPriv = QQuickItemPrivate::get(handler->parentItem()); + // An item that is NOT a subscene root needs to track whether it got a grab via a subscene delivery agent, + // whereas the subscene root item already knows it has its own DA. + if (!itemPriv->extra.isAllocated() || !itemPriv->extra->subsceneDeliveryAgent) + itemPriv->hasSubsceneDeliveryAgent = grabGained; + subsceneAgent = itemPriv->deliveryAgent(); + } } else { switch (transition) { case QPointingDevice::CancelGrabExclusive: @@ -1291,28 +1441,77 @@ void QQuickWindowPrivate::onGrabChanged(QObject *grabber, QPointingDevice::GrabT default: break; } + auto grabberItem = static_cast<QQuickItem *>(grabber); // cannot be a handler: we checked above + if (isSubsceneAgent && grabberItem) { + auto itemPriv = QQuickItemPrivate::get(grabberItem); + // An item that is NOT a subscene root needs to track whether it got a grab via a subscene delivery agent, + // whereas the subscene root item already knows it has its own DA. + if (!itemPriv->extra.isAllocated() || !itemPriv->extra->subsceneDeliveryAgent) + itemPriv->hasSubsceneDeliveryAgent = grabGained; + subsceneAgent = itemPriv->deliveryAgent(); + } + } + + if (subsceneAgent == q && event && event->device()) { + auto devExtra = QQuickDeliveryAgentPrivate::deviceExtra(event->device()); + QFlatMap<int, QQuickDeliveryAgent*> &agentMap = devExtra->grabbedEventPointDeliveryAgents; + // workaround for QFlatMap error: somehow having a local copy of id makes insert() happy (move semantics?) otherwise we get + // no matching function for call to ‘QList<int>::insert(QFlatMap<int, QQuickDeliveryAgent*>::iterator&, std::remove_reference<int&>::type)’ + const int id = point.id(); + if (grabGained) { + // If any grab is gained while a subscene agent is delivering an event, + // the same agent should keep delivering all subsequent events containing that QEventPoint. + qCDebug(lcPtr) << "remembering that" << q << "handles point" << id << "after" << transition; + agentMap.insert(id, q); + } else if (!event->exclusiveGrabber(point) && event->passiveGrabbers(point).isEmpty()) { + // If all grabs are lost, we can forget the fact that a particular agent was handling a particular point. + // If the event point ID appears again in a later event, it will be delivered via the main window's delivery agent by default. + qCDebug(lcPtr) << "dissociating" << q << "from point" << id << "after" << transition; + agentMap.remove(id); + } } } -void QQuickWindowPrivate::ensureDeviceConnected(const QPointingDevice *dev) +void QQuickDeliveryAgentPrivate::ensureDeviceConnected(const QPointingDevice *dev) { if (knownPointingDevices.contains(dev)) return; knownPointingDevices.append(dev); - connect(dev, &QPointingDevice::grabChanged, this, &QQuickWindowPrivate::onGrabChanged); + connect(dev, &QPointingDevice::grabChanged, this, &QQuickDeliveryAgentPrivate::onGrabChanged); } -void QQuickWindowPrivate::deliverPointerEvent(QPointerEvent *event) +void QQuickDeliveryAgentPrivate::deliverPointerEvent(QPointerEvent *event) { + Q_Q(QQuickDeliveryAgent); + if (isTabletEvent(event)) + qCDebug(lcTablet) << q << event; + // If users spin the eventloop as a result of event delivery, we disable // event compression and send events directly. This is because we consider // the usecase a bit evil, but we at least don't want to lose events. ++pointerEventRecursionGuard; eventsInDelivery.push(event); + // So far this is for use in Qt Quick 3D: if a QEventPoint is grabbed, + // updates get delivered here pretty directly, bypassing picking; but we need to + // be able to map the 2D viewport coordinate to a 2D coordinate within + // d->rootItem, a 2D scene that has been arbitrarily mapped onto a 3D object. + if (sceneTransform) { + for (int i = 0; i < event->pointCount(); ++i) { + auto &mut = QMutableEventPoint::from(event->point(i)); + mut.setScenePosition(sceneTransform->map(mut.scenePosition())); + qCDebug(lcPtrLoc) << q << event->type() << mut.id() << "transformed scene pos" << mut.scenePosition(); + } + } else if (isSubsceneAgent) { + qCDebug(lcPtrLoc) << q << event->type() << "no scene transform set"; + } + skipDelivery.clear(); QQuickPointerHandlerPrivate::deviceDeliveryTargets(event->pointingDevice()).clear(); - qCDebug(lcPtr) << "delivering" << event; + if (sceneTransform) + qCDebug(lcPtr) << q << "delivering with" << sceneTransform << event; + else + qCDebug(lcPtr) << q << "delivering" << event; for (int i = 0; i < event->pointCount(); ++i) event->point(i).setAccepted(false); @@ -1326,14 +1525,16 @@ void QQuickWindowPrivate::deliverPointerEvent(QPointerEvent *event) if (event->isEndEvent()) deliverPressOrReleaseEvent(event, true); - // failsafe: never allow any kind of grab to persist after release + // failsafe: never allow any kind of grab or point-agent association to persist after release if (event->isEndEvent()) { + auto &agentMap = QQuickDeliveryAgentPrivate::deviceExtra(event->device())->grabbedEventPointDeliveryAgents; if (isTouchEvent(event)) { for (int i = 0; i < event->pointCount(); ++i) { auto &point = event->point(i); if (point.state() == QEventPoint::State::Released) { event->setExclusiveGrabber(point, nullptr); event->clearPassiveGrabbers(point); + agentMap.remove(point.id()); } } // never allow touch->mouse synthesis to persist either @@ -1342,6 +1543,7 @@ void QQuickWindowPrivate::deliverPointerEvent(QPointerEvent *event) auto &firstPt = event->point(0); event->setExclusiveGrabber(firstPt, nullptr); event->clearPassiveGrabbers(firstPt); + agentMap.remove(firstPt.id()); } } @@ -1355,13 +1557,15 @@ void QQuickWindowPrivate::deliverPointerEvent(QPointerEvent *event) // If checkMouseButtons is true, it means we are finding targets for a mouse event, so no item for which acceptedMouseButtons() is NoButton will be added. // If checkAcceptsTouch is true, it means we are finding targets for a touch event, so either acceptTouchEvents() must return true OR // it must accept a synth. mouse event, thus if acceptTouchEvents() returns false but acceptedMouseButtons() is true, gets added; if not, it doesn't. -QVector<QQuickItem *> QQuickWindowPrivate::pointerTargets(QQuickItem *item, const QPointerEvent *event, const QEventPoint &point, +QVector<QQuickItem *> QQuickDeliveryAgentPrivate::pointerTargets(QQuickItem *item, const QPointerEvent *event, const QEventPoint &point, bool checkMouseButtons, bool checkAcceptsTouch) const { + Q_Q(const QQuickDeliveryAgent); QVector<QQuickItem *> targets; auto itemPrivate = QQuickItemPrivate::get(item); QPointF itemPos = item->mapFromScene(point.scenePosition()); bool relevant = item->contains(itemPos); + qCDebug(lcPtrLoc) << q << "point" << point.id() << point.scenePosition() << "->" << itemPos << ": relevant?" << relevant << "to" << item << point; // if the item clips, we can potentially return early if (itemPrivate->flags & QQuickItem::ItemClipsChildrenToShape) { if (!relevant) @@ -1403,7 +1607,7 @@ QVector<QQuickItem *> QQuickWindowPrivate::pointerTargets(QQuickItem *item, cons // return the joined lists // list1 has priority, common items come last -QVector<QQuickItem *> QQuickWindowPrivate::mergePointerTargets(const QVector<QQuickItem *> &list1, const QVector<QQuickItem *> &list2) const +QVector<QQuickItem *> QQuickDeliveryAgentPrivate::mergePointerTargets(const QVector<QQuickItem *> &list1, const QVector<QQuickItem *> &list2) const { QVector<QQuickItem *> targets = list1; // start at the end of list2 @@ -1426,7 +1630,7 @@ QVector<QQuickItem *> QQuickWindowPrivate::mergePointerTargets(const QVector<QQu /*! \internal Deliver updated points to existing grabbers. */ -void QQuickWindowPrivate::deliverUpdatedPoints(QPointerEvent *event) +void QQuickDeliveryAgentPrivate::deliverUpdatedPoints(QPointerEvent *event) { bool done = false; const auto grabbers = exclusiveGrabbers(event); @@ -1467,7 +1671,7 @@ void QQuickWindowPrivate::deliverUpdatedPoints(QPointerEvent *event) // Don't find handlers for points that are already grabbed by an Item (such as Flickable). if (point.state() == QEventPoint::Pressed || qmlobject_cast<QQuickItem *>(event->exclusiveGrabber(point))) continue; - QVector<QQuickItem *> targetItemsForPoint = pointerTargets(contentItem, event, point, false, false); + QVector<QQuickItem *> targetItemsForPoint = pointerTargets(rootItem, event, point, false, false); if (targetItems.count()) { targetItems = mergePointerTargets(targetItems, targetItemsForPoint); } else { @@ -1487,7 +1691,7 @@ void QQuickWindowPrivate::deliverUpdatedPoints(QPointerEvent *event) } // Deliver an event containing newly pressed or released touch points -bool QQuickWindowPrivate::deliverPressOrReleaseEvent(QPointerEvent *event, bool handlersOnly) +bool QQuickDeliveryAgentPrivate::deliverPressOrReleaseEvent(QPointerEvent *event, bool handlersOnly) { QVector<QQuickItem *> targetItems; const bool isTouch = isTouchEvent(event); @@ -1512,7 +1716,7 @@ bool QQuickWindowPrivate::deliverPressOrReleaseEvent(QPointerEvent *event, bool auto &point = event->point(i); if (point.state() == QEventPoint::Pressed) event->clearPassiveGrabbers(point); - QVector<QQuickItem *> targetItemsForPoint = pointerTargets(contentItem, event, point, !isTouch, isTouch); + QVector<QQuickItem *> targetItemsForPoint = pointerTargets(rootItem, event, point, !isTouch, isTouch); if (targetItems.count()) { targetItems = mergePointerTargets(targetItems, targetItemsForPoint); } else { @@ -1546,7 +1750,7 @@ bool QQuickWindowPrivate::deliverPressOrReleaseEvent(QPointerEvent *event, bool return event->allPointsAccepted(); } -void QQuickWindowPrivate::deliverMatchingPointsToItem(QQuickItem *item, bool isGrabber, QPointerEvent *pointerEvent, bool handlersOnly) +void QQuickDeliveryAgentPrivate::deliverMatchingPointsToItem(QQuickItem *item, bool isGrabber, QPointerEvent *pointerEvent, bool handlersOnly) { QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item); #if defined(Q_OS_ANDROID) && QT_VERSION < QT_VERSION_CHECK(6, 0, 0) @@ -1596,7 +1800,7 @@ void QQuickWindowPrivate::deliverMatchingPointsToItem(QQuickItem *item, bool isG auto &point = pointerEvent->point(0); auto mouseGrabber = pointerEvent->exclusiveGrabber(point); if (mouseGrabber && mouseGrabber != item && mouseGrabber != oldMouseGrabber) { - // Normally we don't need item->mouseUngrabEvent() here, because QQuickWindowPrivate::onGrabChanged does it. + // Normally we don't need item->mouseUngrabEvent() here, because QQuickDeliveryAgentPrivate::onGrabChanged does it. // However, if one item accepted the mouse event, it expects to have the grab and be in "pressed" state, // because accepting implies grabbing. But before it actually gets the grab, another item could steal it. // In that case, onGrabChanged() does NOT notify the item that accepted the event that it's not getting the grab after all. @@ -1619,7 +1823,7 @@ void QQuickWindowPrivate::deliverMatchingPointsToItem(QQuickItem *item, bool isG bool eventAccepted = false; QMutableTouchEvent touchEvent; - QQuickItemPrivate::get(item)->localizedTouchEvent(static_cast<QTouchEvent *>(pointerEvent), false, &touchEvent); + itemPrivate->localizedTouchEvent(static_cast<QTouchEvent *>(pointerEvent), false, &touchEvent); if (touchEvent.type() == QEvent::None) return; // no points inside this item @@ -1638,7 +1842,7 @@ void QQuickWindowPrivate::deliverMatchingPointsToItem(QQuickItem *item, bool isG } else if (Q_LIKELY(QCoreApplication::testAttribute(Qt::AA_SynthesizeMouseForUnhandledTouchEvents))) { // If the touch event wasn't accepted, synthesize a mouse event and see if the item wants it. if (!eventAccepted && (itemPrivate->acceptedMouseButtons() & Qt::LeftButton)) { - // send mouse event + // send mouse event if (deliverTouchAsMouse(item, &touchEvent)) eventAccepted = true; } @@ -1652,7 +1856,8 @@ void QQuickWindowPrivate::deliverMatchingPointsToItem(QQuickItem *item, bool isG auto &point = QMutableEventPoint::from(touchEvent.point(i)); // legacy-style delivery: if the item doesn't reject the event, that means it handled ALL the points point.setAccepted(); - if (isPressOrRelease) + // but don't let the root of a subscene implicitly steal the grab from some other item (such as one of its children) + if (isPressOrRelease && !(itemPrivate->deliveryAgent() && pointerEvent->exclusiveGrabber(point))) pointerEvent->setExclusiveGrabber(point, item); } } else { @@ -1670,7 +1875,7 @@ void QQuickWindowPrivate::deliverMatchingPointsToItem(QQuickItem *item, bool isG } #if QT_CONFIG(quick_draganddrop) -void QQuickWindowPrivate::deliverDragEvent(QQuickDragGrabber *grabber, QEvent *event) +void QQuickDeliveryAgentPrivate::deliverDragEvent(QQuickDragGrabber *grabber, QEvent *event) { grabber->resetTarget(); QQuickDragGrabber::iterator grabItem = grabber->begin(); @@ -1715,7 +1920,7 @@ void QQuickWindowPrivate::deliverDragEvent(QQuickDragGrabber *grabber, QEvent *e moveEvent->buttons(), moveEvent->modifiers()); QQuickDropEventEx::copyActions(&enterEvent, *moveEvent); - event->setAccepted(deliverDragEvent(grabber, contentItem, &enterEvent, ¤tGrabItems)); + event->setAccepted(deliverDragEvent(grabber, rootItem, &enterEvent, ¤tGrabItems)); for (grabItem = grabber->begin(); grabItem != grabber->end(); ++grabItem) { int i = currentGrabItems.indexOf(**grabItem); @@ -1752,11 +1957,11 @@ void QQuickWindowPrivate::deliverDragEvent(QQuickDragGrabber *grabber, QEvent *e e->buttons(), e->modifiers()); QQuickDropEventEx::copyActions(&enterEvent, *e); - event->setAccepted(deliverDragEvent(grabber, contentItem, &enterEvent)); + event->setAccepted(deliverDragEvent(grabber, rootItem, &enterEvent)); } } -bool QQuickWindowPrivate::deliverDragEvent(QQuickDragGrabber *grabber, QQuickItem *item, QDragMoveEvent *event, QVarLengthArray<QQuickItem*, 64> *currentGrabItems) +bool QQuickDeliveryAgentPrivate::deliverDragEvent(QQuickDragGrabber *grabber, QQuickItem *item, QDragMoveEvent *event, QVarLengthArray<QQuickItem*, 64> *currentGrabItems) { QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item); if (!item->isVisible() || !item->isEnabled() || QQuickItemPrivate::get(item)->culled) @@ -1831,12 +2036,12 @@ bool QQuickWindowPrivate::deliverDragEvent(QQuickDragGrabber *grabber, QQuickIte } #endif // quick_draganddrop -bool QQuickWindowPrivate::sendFilteredPointerEvent(QPointerEvent *event, QQuickItem *receiver, QQuickItem *filteringParent) +bool QQuickDeliveryAgentPrivate::sendFilteredPointerEvent(QPointerEvent *event, QQuickItem *receiver, QQuickItem *filteringParent) { return sendFilteredPointerEventImpl(event, receiver, filteringParent ? filteringParent : receiver->parentItem()); } -bool QQuickWindowPrivate::sendFilteredPointerEventImpl(QPointerEvent *event, QQuickItem *receiver, QQuickItem *filteringParent) +bool QQuickDeliveryAgentPrivate::sendFilteredPointerEventImpl(QPointerEvent *event, QQuickItem *receiver, QQuickItem *filteringParent) { if (!allowChildEventFiltering) return false; @@ -1879,7 +2084,7 @@ bool QQuickWindowPrivate::sendFilteredPointerEventImpl(QPointerEvent *event, QQu qCDebug(lcTouchTarget) << "skipping filtering of synth-mouse event from" << device; } else if (acceptsTouchEvents || receiver->acceptedMouseButtons()) { // get a touch event customized for delivery to filteringParent - // TODO should not be necessary? because QQuickWindowPrivate::deliverMatchingPointsToItem() does it + // TODO should not be necessary? because QQuickDeliveryAgentPrivate::deliverMatchingPointsToItem() does it QMutableTouchEvent filteringParentTouchEvent; QQuickItemPrivate::get(receiver)->localizedTouchEvent(static_cast<QTouchEvent *>(event), true, &filteringParentTouchEvent); if (filteringParentTouchEvent.type() != QEvent::None) { @@ -1955,7 +2160,7 @@ bool QQuickWindowPrivate::sendFilteredPointerEventImpl(QPointerEvent *event, QQu return sendFilteredPointerEventImpl(event, receiver, filteringParent->parentItem()) || filtered; } -bool QQuickWindowPrivate::sendFilteredMouseEvent(QEvent *event, QQuickItem *receiver, QQuickItem *filteringParent) +bool QQuickDeliveryAgentPrivate::sendFilteredMouseEvent(QEvent *event, QQuickItem *receiver, QQuickItem *filteringParent) { if (!filteringParent) return false; @@ -1977,7 +2182,7 @@ bool QQuickWindowPrivate::sendFilteredMouseEvent(QEvent *event, QQuickItem *rece return sendFilteredMouseEvent(event, receiver, filteringParent->parentItem()) || filtered; } -bool QQuickWindowPrivate::dragOverThreshold(qreal d, Qt::Axis axis, QMouseEvent *event, int startDragThreshold) +bool QQuickDeliveryAgentPrivate::dragOverThreshold(qreal d, Qt::Axis axis, QMouseEvent *event, int startDragThreshold) { QStyleHints *styleHints = QGuiApplication::styleHints(); bool dragVelocityLimitAvailable = event->device()->capabilities().testFlag(QInputDevice::Capability::Velocity) @@ -1991,7 +2196,7 @@ bool QQuickWindowPrivate::dragOverThreshold(qreal d, Qt::Axis axis, QMouseEvent return overThreshold; } -bool QQuickWindowPrivate::dragOverThreshold(qreal d, Qt::Axis axis, const QEventPoint &tp, int startDragThreshold) +bool QQuickDeliveryAgentPrivate::dragOverThreshold(qreal d, Qt::Axis axis, const QEventPoint &tp, int startDragThreshold) { QStyleHints *styleHints = qApp->styleHints(); bool overThreshold = qAbs(d) > (startDragThreshold >= 0 ? startDragThreshold : styleHints->startDragDistance()); @@ -2003,10 +2208,38 @@ bool QQuickWindowPrivate::dragOverThreshold(qreal d, Qt::Axis axis, const QEvent return overThreshold; } -bool QQuickWindowPrivate::dragOverThreshold(QVector2D delta) +bool QQuickDeliveryAgentPrivate::dragOverThreshold(QVector2D delta) { int threshold = qApp->styleHints()->startDragDistance(); return qAbs(delta.x()) > threshold || qAbs(delta.y()) > threshold; } +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug debug, const QQuickDeliveryAgent *da) +{ + QDebugStateSaver saver(debug); + debug.nospace(); + if (!da) { + debug << "QQuickDeliveryAgent(0)"; + return debug; + } + + debug << "QQuickDeliveryAgent("; + if (!da->objectName().isEmpty()) + debug << da->objectName() << ' '; + auto root = da->rootItem(); + if (Q_LIKELY(root)) { + debug << "root=" << root->metaObject()->className(); + if (!root->objectName().isEmpty()) + debug << ' ' << root->objectName(); + } else { + debug << "root=0"; + } + debug << ')'; + return debug; +} +#endif + QT_END_NAMESPACE + +#include "moc_qquickdeliveryagent_p.cpp" diff --git a/src/quick/util/qquickdeliveryagent_p.h b/src/quick/util/qquickdeliveryagent_p.h new file mode 100644 index 0000000000..41b3a5390b --- /dev/null +++ b/src/quick/util/qquickdeliveryagent_p.h @@ -0,0 +1,101 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQuick module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKDELIVERYAGENT_P_H +#define QQUICKDELIVERYAGENT_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuick/private/qtquickglobal_p.h> + +#include <QtQml/qqml.h> +#include <QtQml/private/qqmlglobal_p.h> + +#include <QtCore/qobject.h> +#include <QtGui/qevent.h> + +QT_BEGIN_NAMESPACE + +class QQuickItem; +class QQuickDeliveryAgentPrivate; + +class Q_QUICK_EXPORT QQuickDeliveryAgent : public QObject +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QQuickDeliveryAgent) + +public: + struct Q_QUICK_EXPORT Transform + { + virtual ~Transform(); + virtual QPointF map(const QPointF &point) = 0; + }; + + explicit QQuickDeliveryAgent(QQuickItem *rootItem); + virtual ~QQuickDeliveryAgent(); + + static QQuickDeliveryAgent *grabberAgent(QPointerEvent *pe, const QEventPoint &pt); + + QQuickItem *rootItem() const; + + void setSceneTransform(Transform *transform); + bool event(QEvent *ev) override; + +Q_SIGNALS: + +private: + Q_DISABLE_COPY(QQuickDeliveryAgent) +}; + +#ifndef QT_NO_DEBUG_STREAM +QDebug Q_QUICK_EXPORT operator<<(QDebug debug, const QQuickDeliveryAgent *da); +#endif + +QT_END_NAMESPACE + +#endif // QQUICKDELIVERYAGENT_P_H diff --git a/src/quick/util/qquickdeliveryagent_p_p.h b/src/quick/util/qquickdeliveryagent_p_p.h new file mode 100644 index 0000000000..123eea1191 --- /dev/null +++ b/src/quick/util/qquickdeliveryagent_p_p.h @@ -0,0 +1,209 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQuick module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKDELIVERYAGENT_P_P_H +#define QQUICKDELIVERYAGENT_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuick/private/qquickdeliveryagent_p.h> +#include <QtGui/qevent.h> +#include <QtCore/qstack.h> + +#include <private/qevent_p.h> +#include <private/qpointingdevice_p.h> +#include <private/qobject_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickDragGrabber; +class QQuickItem; +class QQuickPointerHandler; +class QQuickWindow; + +/*! \internal + Extra device-specific data to be stored in QInputDevicePrivate::qqExtra +*/ +struct QQuickPointingDeviceExtra { + // used in QQuickPointerHandlerPrivate::deviceDeliveryTargets + QVector<QObject *> deliveryTargets; + // memory of which agent was delivering when each QEventPoint was grabbed + // TODO maybe add QEventPointPrivate::qqExtra, or sth in QPointingDevicePrivate::EventPointData + QFlatMap<int, QQuickDeliveryAgent*> grabbedEventPointDeliveryAgents; +}; + +class Q_QUICK_PRIVATE_EXPORT QQuickDeliveryAgentPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QQuickDeliveryAgent) +public: + QQuickDeliveryAgentPrivate(QQuickItem *root); + ~QQuickDeliveryAgentPrivate(); + + QQuickItem *rootItem = nullptr; + + QQuickItem *activeFocusItem = nullptr; + + void deliverKeyEvent(QKeyEvent *e); + + enum FocusOption { + DontChangeFocusProperty = 0x01, + DontChangeSubFocusItem = 0x02 + }; + Q_DECLARE_FLAGS(FocusOptions, FocusOption) + + void setFocusInScope(QQuickItem *scope, QQuickItem *item, Qt::FocusReason reason, FocusOptions = { }); + void clearFocusInScope(QQuickItem *scope, QQuickItem *item, Qt::FocusReason reason, FocusOptions = { }); + static void notifyFocusChangesRecur(QQuickItem **item, int remaining); + void clearFocusObject(); + void updateFocusItemTransform(); + + // Keeps track of the item currently receiving mouse events +#if QT_CONFIG(quick_draganddrop) + QQuickDragGrabber *dragGrabber = nullptr; +#endif + QQuickItem *lastUngrabbed = nullptr; + QStack<QPointerEvent *> eventsInDelivery; + QList<QPointer<QQuickItem>> hoverItems; + QVector<QQuickItem *> hasFiltered; // during event delivery to a single receiver, the filtering parents for which childMouseEventFilter was already called + QVector<QQuickItem *> skipDelivery; // during delivery of one event to all receivers, Items to which we know delivery is no longer necessary + + QScopedPointer<QMutableTouchEvent> delayedTouch; + QList<const QPointingDevice *> knownPointingDevices; + + uint lastWheelEventAccepted = 0; + bool allowChildEventFiltering = true; + bool allowDoubleClick = true; + bool frameSynchronousHoverEnabled = true; + + bool isSubsceneAgent = false; + + Qt::FocusReason lastFocusReason = Qt::OtherFocusReason; + int pointerEventRecursionGuard = 0; + + int touchMouseId = -1; // only for obsolete stuff like QQuickItem::grabMouse() + // TODO get rid of these + const QPointingDevice *touchMouseDevice = nullptr; + ulong touchMousePressTimestamp = 0; + QPoint touchMousePressPos; // in screen coordinates + + QQuickDeliveryAgent::Transform *sceneTransform = nullptr; + + bool isDeliveringTouchAsMouse() const { return touchMouseId != -1 && touchMouseDevice; } + void cancelTouchMouseSynthesis(); + + bool checkIfDoubleTapped(ulong newPressEventTimestamp, QPoint newPressPos); + QPointingDevicePrivate::EventPointData *mousePointData(); + QPointerEvent *eventInDelivery() const; + + // Mouse positions are saved in widget coordinates + QPointF lastMousePosition; + bool deliverTouchAsMouse(QQuickItem *item, QTouchEvent *pointerEvent); + void translateTouchEvent(QTouchEvent *touchEvent); + void removeGrabber(QQuickItem *grabber, bool mouse = true, bool touch = true, bool cancel = false); + void onGrabChanged(QObject *grabber, QPointingDevice::GrabTransition transition, const QPointerEvent *event, const QEventPoint &point); + static QPointerEvent *clonePointerEvent(QPointerEvent *event, std::optional<QPointF> transformedLocalPos = std::nullopt); + void deliverToPassiveGrabbers(const QVector<QPointer<QObject> > &passiveGrabbers, QPointerEvent *pointerEvent); + bool sendFilteredMouseEvent(QEvent *event, QQuickItem *receiver, QQuickItem *filteringParent); + bool sendFilteredPointerEvent(QPointerEvent *event, QQuickItem *receiver, QQuickItem *filteringParent = nullptr); + bool sendFilteredPointerEventImpl(QPointerEvent *event, QQuickItem *receiver, QQuickItem *filteringParent); + bool deliverSinglePointEventUntilAccepted(QPointerEvent *); + + // entry point of events to the window + void handleTouchEvent(QTouchEvent *); + void handleMouseEvent(QMouseEvent *); + bool compressTouchEvent(QTouchEvent *); + void flushFrameSynchronousEvents(QQuickWindow *win); + void deliverDelayedTouchEvent(); + void handleWindowDeactivate(QQuickWindow *win); + + // utility functions that used to be in QQuickPointerEvent et al. + bool allUpdatedPointsAccepted(const QPointerEvent *ev); + static void localizePointerEvent(QPointerEvent *ev, const QQuickItem *dest); + QList<QObject *> exclusiveGrabbers(QPointerEvent *ev); + static bool anyPointGrabbed(const QPointerEvent *ev); + static bool isMouseEvent(const QPointerEvent *ev); + static bool isTouchEvent(const QPointerEvent *ev); + static bool isTabletEvent(const QPointerEvent *ev); + static QQuickPointingDeviceExtra *deviceExtra(const QInputDevice *device); + + // delivery of pointer events: + void touchToMouseEvent(QEvent::Type type, const QEventPoint &p, const QTouchEvent *touchEvent, QMutableSinglePointEvent *mouseEvent); + void ensureDeviceConnected(const QPointingDevice *dev); + void deliverPointerEvent(QPointerEvent *); + bool deliverTouchCancelEvent(QTouchEvent *); + bool deliverPressOrReleaseEvent(QPointerEvent *, bool handlersOnly = false); + void deliverUpdatedPoints(QPointerEvent *event); + void deliverMatchingPointsToItem(QQuickItem *item, bool isGrabber, QPointerEvent *pointerEvent, bool handlersOnly = false); + + QVector<QQuickItem *> pointerTargets(QQuickItem *, const QPointerEvent *event, const QEventPoint &point, + bool checkMouseButtons, bool checkAcceptsTouch) const; + QVector<QQuickItem *> mergePointerTargets(const QVector<QQuickItem *> &list1, const QVector<QQuickItem *> &list2) const; + + // hover delivery + bool deliverHoverEvent(QQuickItem *, const QPointF &scenePos, const QPointF &lastScenePos, Qt::KeyboardModifiers modifiers, ulong timestamp, bool &accepted); + bool sendHoverEvent(QEvent::Type, QQuickItem *, const QPointF &scenePos, const QPointF &lastScenePos, + Qt::KeyboardModifiers modifiers, ulong timestamp, bool accepted); + bool clearHover(ulong timestamp = 0); + +#if QT_CONFIG(quick_draganddrop) + void deliverDragEvent(QQuickDragGrabber *, QEvent *); + bool deliverDragEvent(QQuickDragGrabber *, QQuickItem *, QDragMoveEvent *, QVarLengthArray<QQuickItem*, 64> *currentGrabItems = nullptr); +#endif + + static bool dragOverThreshold(qreal d, Qt::Axis axis, QMouseEvent *event, int startDragThreshold = -1); + + static bool dragOverThreshold(qreal d, Qt::Axis axis, const QEventPoint &tp, int startDragThreshold = -1); + + static bool dragOverThreshold(QVector2D delta); +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QQuickDeliveryAgentPrivate::FocusOptions) + +QT_END_NAMESPACE + +#endif // QQUICKDELIVERYAGENT_P_P_H diff --git a/src/quickwidgets/qquickwidget.cpp b/src/quickwidgets/qquickwidget.cpp index c94c70f579..8ddd2d76c7 100644 --- a/src/quickwidgets/qquickwidget.cpp +++ b/src/quickwidgets/qquickwidget.cpp @@ -850,9 +850,15 @@ void QQuickWidgetPrivate::updateSize() if (resizeMode == QQuickWidget::SizeViewToRootObject) { QSize newSize = QSize(root->width(), root->height()); - if (newSize.isValid() && newSize != q->size()) { - q->resize(newSize); - q->updateGeometry(); + if (newSize.isValid()) { + if (newSize != q->size()) { + q->resize(newSize); + q->updateGeometry(); + } else if (offscreenWindow->size().isEmpty()) { + // QQuickDeliveryAgentPrivate::deliverHoverEvent() ignores events that + // occur outside of QQuickRootItem's geometry, so we need it to match root's size. + offscreenWindow->contentItem()->setSize(newSize); + } } } else if (resizeMode == QQuickWidget::SizeRootObjectToView) { const bool needToUpdateWidth = !qFuzzyCompare(q->width(), root->width()); diff --git a/tests/auto/quick/qquickitem/tst_qquickitem.cpp b/tests/auto/quick/qquickitem/tst_qquickitem.cpp index d38f6b4e54..d7c9c0df2a 100644 --- a/tests/auto/quick/qquickitem/tst_qquickitem.cpp +++ b/tests/auto/quick/qquickitem/tst_qquickitem.cpp @@ -1600,6 +1600,10 @@ void tst_qquickitem::hoverEvent() QQuickWindow *window = new QQuickWindow(); window->resize(200, 200); window->show(); + QTest::qWaitForWindowExposed(window); +#if QT_CONFIG(cursor) // Get the cursor out of the way. + QCursor::setPos(window->geometry().topRight() + QPoint(100, 100)); +#endif HoverItem *item = new HoverItem; item->setSize(QSizeF(100, 100)); @@ -1641,6 +1645,10 @@ void tst_qquickitem::hoverEventInParent() QQuickWindow window; window.resize(200, 200); window.show(); + QTest::qWaitForWindowExposed(&window); +#if QT_CONFIG(cursor) // Get the cursor out of the way. + QCursor::setPos(window.geometry().topRight() + QPoint(100, 100)); +#endif HoverItem *parentItem = new HoverItem(window.contentItem()); parentItem->setSize(QSizeF(200, 200)); diff --git a/tests/auto/quick/qquickwindow/tst_qquickwindow.cpp b/tests/auto/quick/qquickwindow/tst_qquickwindow.cpp index 7fe8d94c74..05fda64654 100644 --- a/tests/auto/quick/qquickwindow/tst_qquickwindow.cpp +++ b/tests/auto/quick/qquickwindow/tst_qquickwindow.cpp @@ -1137,7 +1137,7 @@ void tst_qquickwindow::mergeTouchPointLists() for (const auto &item : list2) b.append(item.get()); - auto targetList = windowPrivate->mergePointerTargets(a, b); + auto targetList = windowPrivate->deliveryAgentPrivate()->mergePointerTargets(a, b); QCOMPARE(targetList, expected); } @@ -1760,13 +1760,13 @@ void tst_qquickwindow::focusReason() secondItem->setParentItem(window->contentItem()); firstItem->forceActiveFocus(Qt::OtherFocusReason); - QCOMPARE(QQuickWindowPrivate::get(window)->lastFocusReason, Qt::OtherFocusReason); + QCOMPARE(QQuickWindowPrivate::get(window)->deliveryAgentPrivate()->lastFocusReason, Qt::OtherFocusReason); secondItem->forceActiveFocus(Qt::TabFocusReason); - QCOMPARE(QQuickWindowPrivate::get(window)->lastFocusReason, Qt::TabFocusReason); + QCOMPARE(QQuickWindowPrivate::get(window)->deliveryAgentPrivate()->lastFocusReason, Qt::TabFocusReason); firstItem->forceActiveFocus(Qt::BacktabFocusReason); - QCOMPARE(QQuickWindowPrivate::get(window)->lastFocusReason, Qt::BacktabFocusReason); + QCOMPARE(QQuickWindowPrivate::get(window)->deliveryAgentPrivate()->lastFocusReason, Qt::BacktabFocusReason); } diff --git a/tests/auto/quick/shared/viewtestutil.cpp b/tests/auto/quick/shared/viewtestutil.cpp index 5f84147792..5d9e6ba8dc 100644 --- a/tests/auto/quick/shared/viewtestutil.cpp +++ b/tests/auto/quick/shared/viewtestutil.cpp @@ -35,8 +35,9 @@ #include <QtTest/QTest> -#include <private/qquickwindow_p.h> -#include <private/qquickitemview_p_p.h> +#include <QtQuick/private/qquickdeliveryagent_p_p.h> +#include <QtQuick/private/qquickitemview_p_p.h> +#include <QtQuick/private/qquickwindow_p.h> QT_BEGIN_NAMESPACE @@ -460,10 +461,10 @@ namespace QQuickTouchUtils { void flush(QQuickWindow *window) { if (!window) return; - QQuickWindowPrivate *wd = QQuickWindowPrivate::get(window); - if (!wd || !wd->delayedTouch) + QQuickDeliveryAgentPrivate *da = QQuickWindowPrivate::get(window)->deliveryAgentPrivate(); + if (!da || !da->delayedTouch) return; - wd->deliverDelayedTouchEvent(); + da->deliverDelayedTouchEvent(); } } diff --git a/tests/auto/quick/touchmouse/tst_touchmouse.cpp b/tests/auto/quick/touchmouse/tst_touchmouse.cpp index 33698b4141..ef0df0cddb 100644 --- a/tests/auto/quick/touchmouse/tst_touchmouse.cpp +++ b/tests/auto/quick/touchmouse/tst_touchmouse.cpp @@ -194,7 +194,7 @@ public: switch (transition) { case QPointingDevice::GrabTransition::GrabExclusive: exclusiveGrabber = grabber; - fromMouseEvent = event && QQuickWindowPrivate::isMouseEvent(event); + fromMouseEvent = event && QQuickDeliveryAgentPrivate::isMouseEvent(event); canceled = false; break; case QPointingDevice::GrabTransition::UngrabExclusive: @@ -641,7 +641,7 @@ void tst_TouchMouse::buttonOnFlickable() QCOMPARE(eventItem1->eventList.at(0).type, QEvent::MouseButtonPress); QQuickWindowPrivate *windowPriv = QQuickWindowPrivate::get(&window); - QVERIFY(windowPriv->touchMouseId != -1); + QVERIFY(windowPriv->deliveryAgentPrivate()->touchMouseId != -1); auto devPriv = QPointingDevicePrivate::get(device); QCOMPARE(devPriv->pointById(0)->exclusiveGrabber, eventItem1); QCOMPARE(grabMonitor.exclusiveGrabber, eventItem1); @@ -662,7 +662,7 @@ void tst_TouchMouse::buttonOnFlickable() QCOMPARE(eventItem1->eventList.at(3).type, QEvent::UngrabMouse); QCOMPARE(grabMonitor.exclusiveGrabber, flickable); - QVERIFY(windowPriv->touchMouseId != -1); + QVERIFY(windowPriv->deliveryAgentPrivate()->touchMouseId != -1); QCOMPARE(devPriv->pointById(0)->exclusiveGrabber, flickable); QTest::touchEvent(&window, device).release(0, p1, &window); @@ -696,7 +696,7 @@ void tst_TouchMouse::touchButtonOnFlickable() QCOMPARE(eventItem2->eventList.at(0).type, QEvent::TouchBegin); QQuickWindowPrivate *windowPriv = QQuickWindowPrivate::get(&window); - QVERIFY(windowPriv->touchMouseId == -1); + QVERIFY(windowPriv->deliveryAgentPrivate()->touchMouseId == -1); auto devPriv = QPointingDevicePrivate::get(device); QCOMPARE(devPriv->pointById(0)->exclusiveGrabber, eventItem2); QCOMPARE(grabMonitor.exclusiveGrabber, eventItem2); @@ -720,7 +720,7 @@ void tst_TouchMouse::touchButtonOnFlickable() QCOMPARE(eventItem2->eventList.at(1).type, QEvent::TouchUpdate); QCOMPARE(grabMonitor.exclusiveGrabber, flickable); // both EventItem and Flickable handled the actual touch, so synth-mouse doesn't happen - QCOMPARE(windowPriv->touchMouseId, -1); + QCOMPARE(windowPriv->deliveryAgentPrivate()->touchMouseId, -1); QCOMPARE(devPriv->pointById(0)->exclusiveGrabber, flickable); QVERIFY(flickable->isMovingVertically()); @@ -783,7 +783,7 @@ void tst_TouchMouse::buttonOnDelayedPressFlickable() // wait to avoid getting a double click event QTest::qWait(qApp->styleHints()->mouseDoubleClickInterval() + 10); QQuickWindowPrivate *windowPriv = QQuickWindowPrivate::get(&window); - QCOMPARE(windowPriv->touchMouseId, -1); // no grabber + QCOMPARE(windowPriv->deliveryAgentPrivate()->touchMouseId, -1); // no grabber // touch press QPoint p1 = QPoint(10, 110); @@ -1535,7 +1535,7 @@ void tst_TouchMouse::implicitUngrab() QVERIFY(!eventItem->eventList.isEmpty()); QCOMPARE(eventItem->eventList.at(0).type, QEvent::UngrabMouse); QTest::touchEvent(&window, device).release(0, p1); // clean up potential state - QCOMPARE(windowPriv->touchMouseId, -1); + QCOMPARE(windowPriv->deliveryAgentPrivate()->touchMouseId, -1); eventItem->setEnabled(true); QTest::touchEvent(&window, device).press(0, p1); |