diff options
author | Richard Moe Gustavsen <richard.gustavsen@qt.io> | 2022-10-26 14:03:30 +0200 |
---|---|---|
committer | Qt Cherry-pick Bot <cherrypick_bot@qt-project.org> | 2022-11-01 09:18:09 +0000 |
commit | eca92bea571cf691423b627d4d6270f7c2807bd9 (patch) | |
tree | 70541098ba712942836f07d21386fc166e344bda | |
parent | e27b91e4cc277698c62191ce3c349afc988a2a2b (diff) |
QQuickHoverHandler: listen for parent changes, and update hasHoverInChild
A QQuickHoverHandler is normally created by the QML engine, but
can sometimes also be created directly from C++. And for
the latter case, QQuickHoverHandler::componentComplete() will
not be called. This causes a problem, since it takes care of
subscribing for hover events on the parent item.
To support creating hover handlers from c++, we therefore need
to also subscribe to hover events from the constructor.
Moreover, since the parentItem can change at runtime, we also
need a virtual function that informs it when the parent item
changes, so that we can remove hover subscription from the old
parent, and subscribe for it on the new parent.
Change-Id: I52f3cd16d6bbfbbe2e4c3c019efdc7f06c5f2c31
Reviewed-by: Shawn Rutledge <shawn.rutledge@qt.io>
(cherry picked from commit 7db67e59d45b47e45a2723808f23e8cdadf5065c)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
5 files changed, 123 insertions, 13 deletions
diff --git a/src/quick/handlers/qquickhoverhandler.cpp b/src/quick/handlers/qquickhoverhandler.cpp index 2a2208c567..072ca626a6 100644 --- a/src/quick/handlers/qquickhoverhandler.cpp +++ b/src/quick/handlers/qquickhoverhandler.cpp @@ -40,35 +40,58 @@ class QQuickHoverHandlerPrivate : public QQuickSinglePointHandlerPrivate public: void onEnabledChanged() override; + void onParentChanged(QQuickItem *oldParent, QQuickItem *newParent) override; + + void updateHasHoverInChild(QQuickItem *item, bool hasHover); }; void QQuickHoverHandlerPrivate::onEnabledChanged() { Q_Q(QQuickHoverHandler); - if (auto parent = q->parentItem()) { - QQuickItemPrivate *itemPriv = QQuickItemPrivate::get(parent); - itemPriv->setHasHoverInChild(enabled); - // The DA needs to resolve which items and handlers should now be hovered or unhovered. - // Marking the parent item dirty ensures that flushFrameSynchronousEvents() will be called from the render loop, - // even if this change is not in response to a mouse event and no item has already marked itself dirty. - itemPriv->dirty(QQuickItemPrivate::Content); - } + if (auto parent = q->parentItem()) + updateHasHoverInChild(parent, enabled); if (!enabled) q->setHovered(false); + + QQuickSinglePointHandlerPrivate::onEnabledChanged(); +} + +void QQuickHoverHandlerPrivate::onParentChanged(QQuickItem *oldParent, QQuickItem *newParent) +{ + if (oldParent) + updateHasHoverInChild(oldParent, false); + if (newParent) + updateHasHoverInChild(newParent, true); + + QQuickSinglePointHandlerPrivate::onParentChanged(oldParent, newParent); +} + +void QQuickHoverHandlerPrivate::updateHasHoverInChild(QQuickItem *item, bool hasHover) +{ + QQuickItemPrivate *itemPriv = QQuickItemPrivate::get(item); + itemPriv->setHasHoverInChild(hasHover); + // The DA needs to resolve which items and handlers should now be hovered or unhovered. + // Marking the parent item dirty ensures that flushFrameSynchronousEvents() will be called from the render loop, + // even if this change is not in response to a mouse event and no item has already marked itself dirty. + itemPriv->dirty(QQuickItemPrivate::Content); } QQuickHoverHandler::QQuickHoverHandler(QQuickItem *parent) : QQuickSinglePointHandler(*(new QQuickHoverHandlerPrivate), parent) { + Q_D(QQuickHoverHandler); // Tell QQuickPointerDeviceHandler::wantsPointerEvent() to ignore button state - d_func()->acceptedButtons = Qt::NoButton; + d->acceptedButtons = Qt::NoButton; + if (parent) + d->updateHasHoverInChild(parent, true); } QQuickHoverHandler::~QQuickHoverHandler() { + Q_D(QQuickHoverHandler); if (auto parent = parentItem()) - QQuickItemPrivate::get(parent)->setHasHoverInChild(false); + d->updateHasHoverInChild(parent, false); } /*! @@ -105,9 +128,13 @@ bool QQuickHoverHandler::event(QEvent *event) void QQuickHoverHandler::componentComplete() { + Q_D(QQuickHoverHandler); QQuickSinglePointHandler::componentComplete(); - if (auto par = parentItem()) - QQuickItemPrivate::get(par)->setHasHoverInChild(true); + + if (d->enabled) { + if (auto parent = parentItem()) + d->updateHasHoverInChild(parent, true); + } } bool QQuickHoverHandler::wantsPointerEvent(QPointerEvent *event) diff --git a/src/quick/handlers/qquickpointerhandler.cpp b/src/quick/handlers/qquickpointerhandler.cpp index 96f87d6943..a851334b0e 100644 --- a/src/quick/handlers/qquickpointerhandler.cpp +++ b/src/quick/handlers/qquickpointerhandler.cpp @@ -597,15 +597,18 @@ QQuickItem *QQuickPointerHandler::parentItem() const void QQuickPointerHandler::setParentItem(QQuickItem *p) { + Q_D(QQuickPointerHandler); if (QObject::parent() == p) return; qCDebug(lcHandlerParent) << "reparenting handler" << this << ":" << parent() << "->" << p; - if (auto *oldParent = static_cast<QQuickItem *>(QObject::parent())) + auto *oldParent = static_cast<QQuickItem *>(QObject::parent()); + if (oldParent) QQuickItemPrivate::get(oldParent)->removePointerHandler(this); setParent(p); if (p) QQuickItemPrivate::get(p)->addPointerHandler(this); + d->onParentChanged(oldParent, p); emit parentChanged(); } diff --git a/src/quick/handlers/qquickpointerhandler_p_p.h b/src/quick/handlers/qquickpointerhandler_p_p.h index fa4ab737db..aa09f4edb7 100644 --- a/src/quick/handlers/qquickpointerhandler_p_p.h +++ b/src/quick/handlers/qquickpointerhandler_p_p.h @@ -38,6 +38,7 @@ public: bool dragOverThreshold(QVector2D delta) const; bool dragOverThreshold(const QEventPoint &point) const; + virtual void onParentChanged(QQuickItem * /*oldParent*/, QQuickItem * /*newParent*/) {} virtual void onEnabledChanged() {} static QVector<QObject *> &deviceDeliveryTargets(const QInputDevice *device); diff --git a/tests/auto/quick/pointerhandlers/qquickhoverhandler/data/nohandler.qml b/tests/auto/quick/pointerhandlers/qquickhoverhandler/data/nohandler.qml new file mode 100644 index 0000000000..ed3728e278 --- /dev/null +++ b/tests/auto/quick/pointerhandlers/qquickhoverhandler/data/nohandler.qml @@ -0,0 +1,15 @@ +import QtQuick 2.15 +import QtQuick.Window 2.15 + +Window { + width: 320 + height: 240 + visible: true + + Rectangle { + objectName: "childItem" + width: 100 + height: 100 + color: "lightblue" + } +} diff --git a/tests/auto/quick/pointerhandlers/qquickhoverhandler/tst_qquickhoverhandler.cpp b/tests/auto/quick/pointerhandlers/qquickhoverhandler/tst_qquickhoverhandler.cpp index b17395a99d..be308cfa64 100644 --- a/tests/auto/quick/pointerhandlers/qquickhoverhandler/tst_qquickhoverhandler.cpp +++ b/tests/auto/quick/pointerhandlers/qquickhoverhandler/tst_qquickhoverhandler.cpp @@ -46,6 +46,7 @@ private slots: void window(); void deviceCursor_data(); void deviceCursor(); + void addHandlerFromCpp(); private: void createView(QScopedPointer<QQuickView> &window, const char *fileName); @@ -567,6 +568,69 @@ void tst_HoverHandler::deviceCursor() QCOMPARE(airbrushEraserHandler->isHovered(), true); // there was no fresh QTabletEvent to tell it not to be hovered } +void tst_HoverHandler::addHandlerFromCpp() +{ + // Check that you can create a hover handler from c++, and add it + // as a child of an existing item. Continue to check that you can + // also change the parent item at runtime. + QQmlEngine engine; + QQmlComponent component(&engine); + component.loadUrl(testFileUrl("nohandler.qml")); + QScopedPointer<QQuickWindow> window(qobject_cast<QQuickWindow *>(component.create())); + QVERIFY(!window.isNull()); + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window.data())); + + QQuickItem *childItem = window->findChild<QQuickItem *>("childItem"); + QVERIFY(childItem); + + // Move mouse outside child + const QPoint outside(200, 200); + const QPoint inside(50, 50); + QTest::mouseMove(window.data(), outside); + + QQuickHoverHandler *handler = new QQuickHoverHandler(childItem); + QSignalSpy spy(handler, &QQuickHoverHandler::hoveredChanged); + + // Move mouse inside child + QTest::mouseMove(window.data(), inside); + QVERIFY(handler->isHovered()); + QCOMPARE(spy.count(), 1); + + // Move mouse outside child + QTest::mouseMove(window.data(), outside); + QVERIFY(!handler->isHovered()); + QCOMPARE(spy.count(), 2); + + // Remove the parent item from the handler + spy.clear(); + handler->setParentItem(nullptr); + + // Move mouse inside child + QTest::mouseMove(window.data(), inside); + QVERIFY(!handler->isHovered()); + QCOMPARE(spy.count(), 0); + + // Move mouse outside child + QTest::mouseMove(window.data(), outside); + QVERIFY(!handler->isHovered()); + QCOMPARE(spy.count(), 0); + + // Reparent back the item to the handler + spy.clear(); + handler->setParentItem(childItem); + + // Move mouse inside child + QTest::mouseMove(window.data(), inside); + QVERIFY(handler->isHovered()); + QCOMPARE(spy.count(), 1); + + // Move mouse outside child + QTest::mouseMove(window.data(), outside); + QVERIFY(!handler->isHovered()); + QCOMPARE(spy.count(), 2); +} + QTEST_MAIN(tst_HoverHandler) #include "tst_qquickhoverhandler.moc" |