diff options
Diffstat (limited to 'tests/auto/quick/pointerhandlers/qquickhoverhandler/tst_qquickhoverhandler.cpp')
-rw-r--r-- | tests/auto/quick/pointerhandlers/qquickhoverhandler/tst_qquickhoverhandler.cpp | 523 |
1 files changed, 457 insertions, 66 deletions
diff --git a/tests/auto/quick/pointerhandlers/qquickhoverhandler/tst_qquickhoverhandler.cpp b/tests/auto/quick/pointerhandlers/qquickhoverhandler/tst_qquickhoverhandler.cpp index 11a5393390..0569fed472 100644 --- a/tests/auto/quick/pointerhandlers/qquickhoverhandler/tst_qquickhoverhandler.cpp +++ b/tests/auto/quick/pointerhandlers/qquickhoverhandler/tst_qquickhoverhandler.cpp @@ -1,36 +1,12 @@ -/**************************************************************************** -** -** Copyright (C) 2018 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** 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 General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** 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-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QtTest/QtTest> #include <QtQuick/qquickview.h> #include <QtQuick/qquickitem.h> #include <QtQuick/private/qquickhoverhandler_p.h> +#include <QtQuick/private/qquickpointerhandler_p_p.h> #include <QtQuick/private/qquickmousearea_p.h> #include <qpa/qwindowsysteminterface.h> @@ -38,9 +14,10 @@ #include <QtQml/qqmlengine.h> #include <QtQml/qqmlproperty.h> +#include <QQmlComponent> -#include "../../../shared/util.h" -#include "../../shared/viewtestutil.h" +#include <QtQuickTestUtils/private/qmlutils_p.h> +#include <QtQuickTestUtils/private/viewtestutils_p.h> Q_LOGGING_CATEGORY(lcPointerTests, "qt.quick.pointer.tests") @@ -54,17 +31,30 @@ class tst_HoverHandler : public QQmlDataTest Q_OBJECT public: tst_HoverHandler() + : QQmlDataTest(QT_QMLTEST_DATADIR) {} private slots: + void hoverHandlerAndUnderlyingHoverHandler_data(); void hoverHandlerAndUnderlyingHoverHandler(); void mouseAreaAndUnderlyingHoverHandler(); void hoverHandlerAndUnderlyingMouseArea(); + void disabledHoverHandlerAndUnderlyingMouseArea(); + void hoverHandlerOnDisabledItem(); void movingItemWithHoverHandler(); void margin(); + void window(); + void deviceCursor_data(); + void deviceCursor(); + void addHandlerFromCpp(); + void ensureHoverHandlerWorksWhenItemHasHoverDisabled(); + void changeCursor(); + void touchDrag(); private: void createView(QScopedPointer<QQuickView> &window, const char *fileName); + + QScopedPointer<QPointingDevice> touchscreen = QScopedPointer<QPointingDevice>(QTest::createTouchDevice()); }; void tst_HoverHandler::createView(QScopedPointer<QQuickView> &window, const char *fileName) @@ -72,16 +62,26 @@ void tst_HoverHandler::createView(QScopedPointer<QQuickView> &window, const char window.reset(new QQuickView); window->setSource(testFileUrl(fileName)); QTRY_COMPARE(window->status(), QQuickView::Ready); - QQuickViewTestUtil::centerOnScreen(window.data()); - QQuickViewTestUtil::moveMouseAway(window.data()); + QQuickViewTestUtils::centerOnScreen(window.data()); + QQuickViewTestUtils::moveMouseAway(window.data()); window->show(); QVERIFY(QTest::qWaitForWindowActive(window.data())); QVERIFY(window->rootObject() != nullptr); } +void tst_HoverHandler::hoverHandlerAndUnderlyingHoverHandler_data() +{ + QTest::addColumn<bool>("blocking"); + + QTest::newRow("default: nonblocking") << false; + QTest::newRow("blocking") << true; +} + void tst_HoverHandler::hoverHandlerAndUnderlyingHoverHandler() { + QFETCH(bool, blocking); + QScopedPointer<QQuickView> windowPtr; createView(windowPtr, "lesHoverables.qml"); QQuickView * window = windowPtr.data(); @@ -94,6 +94,9 @@ void tst_HoverHandler::hoverHandlerAndUnderlyingHoverHandler() QQuickHoverHandler *buttonHH = button->findChild<QQuickHoverHandler *>("buttonHH"); QVERIFY(buttonHH); + QCOMPARE(buttonHH->isBlocking(), false); // default property value + buttonHH->setBlocking(blocking); + QPoint buttonCenter(button->mapToScene(QPointF(button->width() / 2, button->height() / 2)).toPoint()); QPoint rightOfButton(button->mapToScene(QPointF(button->width() + 2, button->height() / 2)).toPoint()); QPoint outOfSidebar(topSidebar->mapToScene(QPointF(topSidebar->width() + 2, topSidebar->height() / 2)).toPoint()); @@ -102,45 +105,45 @@ void tst_HoverHandler::hoverHandlerAndUnderlyingHoverHandler() QTest::mouseMove(window, outOfSidebar); QCOMPARE(topSidebarHH->isHovered(), false); - QCOMPARE(sidebarHoveredSpy.count(), 0); + QCOMPARE(sidebarHoveredSpy.size(), 0); QCOMPARE(buttonHH->isHovered(), false); - QCOMPARE(buttonHoveredSpy.count(), 0); + QCOMPARE(buttonHoveredSpy.size(), 0); #if QT_CONFIG(cursor) QCOMPARE(window->cursor().shape(), Qt::ArrowCursor); #endif QTest::mouseMove(window, rightOfButton); QCOMPARE(topSidebarHH->isHovered(), true); - QCOMPARE(sidebarHoveredSpy.count(), 1); + QCOMPARE(sidebarHoveredSpy.size(), 1); QCOMPARE(buttonHH->isHovered(), false); - QCOMPARE(buttonHoveredSpy.count(), 0); + QCOMPARE(buttonHoveredSpy.size(), 0); #if QT_CONFIG(cursor) QCOMPARE(window->cursor().shape(), Qt::OpenHandCursor); #endif QTest::mouseMove(window, buttonCenter); - QCOMPARE(topSidebarHH->isHovered(), true); - QCOMPARE(sidebarHoveredSpy.count(), 1); + QCOMPARE(topSidebarHH->isHovered(), !blocking); + QCOMPARE(sidebarHoveredSpy.size(), blocking ? 2 : 1); QCOMPARE(buttonHH->isHovered(), true); - QCOMPARE(buttonHoveredSpy.count(), 1); + QCOMPARE(buttonHoveredSpy.size(), 1); #if QT_CONFIG(cursor) QCOMPARE(window->cursor().shape(), Qt::PointingHandCursor); #endif QTest::mouseMove(window, rightOfButton); QCOMPARE(topSidebarHH->isHovered(), true); - QCOMPARE(sidebarHoveredSpy.count(), 1); + QCOMPARE(sidebarHoveredSpy.size(), blocking ? 3 : 1); QCOMPARE(buttonHH->isHovered(), false); - QCOMPARE(buttonHoveredSpy.count(), 2); + QCOMPARE(buttonHoveredSpy.size(), 2); #if QT_CONFIG(cursor) QCOMPARE(window->cursor().shape(), Qt::OpenHandCursor); #endif QTest::mouseMove(window, outOfSidebar); QCOMPARE(topSidebarHH->isHovered(), false); - QCOMPARE(sidebarHoveredSpy.count(), 2); + QCOMPARE(sidebarHoveredSpy.size(), blocking ? 4 : 2); QCOMPARE(buttonHH->isHovered(), false); - QCOMPARE(buttonHoveredSpy.count(), 2); + QCOMPARE(buttonHoveredSpy.size(), 2); #if QT_CONFIG(cursor) QCOMPARE(window->cursor().shape(), Qt::ArrowCursor); #endif @@ -158,6 +161,13 @@ void tst_HoverHandler::mouseAreaAndUnderlyingHoverHandler() QQuickHoverHandler *topSidebarHH = topSidebar->findChild<QQuickHoverHandler *>("topSidebarHH"); QVERIFY(topSidebarHH); + // Ensure that we don't get extra hover events delivered on the + // side, since it can affect the number of hover move events we receive below. + QQuickWindowPrivate::get(window)->deliveryAgentPrivate()->frameSynchronousHoverEnabled = false; + // And flush out any mouse events that might be queued up + // in QPA, since QTest::mouseMove() calls processEvents. + qGuiApp->processEvents(); + QPoint buttonCenter(buttonMA->mapToScene(QPointF(buttonMA->width() / 2, buttonMA->height() / 2)).toPoint()); QPoint rightOfButton(buttonMA->mapToScene(QPointF(buttonMA->width() + 2, buttonMA->height() / 2)).toPoint()); QPoint outOfSidebar(topSidebar->mapToScene(QPointF(topSidebar->width() + 2, topSidebar->height() / 2)).toPoint()); @@ -166,45 +176,45 @@ void tst_HoverHandler::mouseAreaAndUnderlyingHoverHandler() QTest::mouseMove(window, outOfSidebar); QCOMPARE(topSidebarHH->isHovered(), false); - QCOMPARE(sidebarHoveredSpy.count(), 0); + QCOMPARE(sidebarHoveredSpy.size(), 0); QCOMPARE(buttonMA->hovered(), false); - QCOMPARE(buttonHoveredSpy.count(), 0); + QCOMPARE(buttonHoveredSpy.size(), 0); #if QT_CONFIG(cursor) QCOMPARE(window->cursor().shape(), Qt::ArrowCursor); #endif QTest::mouseMove(window, rightOfButton); QCOMPARE(topSidebarHH->isHovered(), true); - QCOMPARE(sidebarHoveredSpy.count(), 1); + QCOMPARE(sidebarHoveredSpy.size(), 1); QCOMPARE(buttonMA->hovered(), false); - QCOMPARE(buttonHoveredSpy.count(), 0); + QCOMPARE(buttonHoveredSpy.size(), 0); #if QT_CONFIG(cursor) QCOMPARE(window->cursor().shape(), Qt::OpenHandCursor); #endif QTest::mouseMove(window, buttonCenter); QCOMPARE(topSidebarHH->isHovered(), true); - QCOMPARE(sidebarHoveredSpy.count(), 1); + QCOMPARE(sidebarHoveredSpy.size(), 1); QCOMPARE(buttonMA->hovered(), true); - QCOMPARE(buttonHoveredSpy.count(), 1); + QCOMPARE(buttonHoveredSpy.size(), 1); #if QT_CONFIG(cursor) QCOMPARE(window->cursor().shape(), Qt::UpArrowCursor); #endif QTest::mouseMove(window, rightOfButton); QCOMPARE(topSidebarHH->isHovered(), true); - QCOMPARE(sidebarHoveredSpy.count(), 1); + QCOMPARE(sidebarHoveredSpy.size(), 1); QCOMPARE(buttonMA->hovered(), false); - QCOMPARE(buttonHoveredSpy.count(), 2); + QCOMPARE(buttonHoveredSpy.size(), 2); #if QT_CONFIG(cursor) QCOMPARE(window->cursor().shape(), Qt::OpenHandCursor); #endif QTest::mouseMove(window, outOfSidebar); QCOMPARE(topSidebarHH->isHovered(), false); - QCOMPARE(sidebarHoveredSpy.count(), 2); + QCOMPARE(sidebarHoveredSpy.size(), 2); QCOMPARE(buttonMA->hovered(), false); - QCOMPARE(buttonHoveredSpy.count(), 2); + QCOMPARE(buttonHoveredSpy.size(), 2); #if QT_CONFIG(cursor) QCOMPARE(window->cursor().shape(), Qt::ArrowCursor); #endif @@ -232,50 +242,131 @@ void tst_HoverHandler::hoverHandlerAndUnderlyingMouseArea() QTest::mouseMove(window, outOfSidebar); QCOMPARE(bottomSidebarMA->hovered(), false); - QCOMPARE(sidebarHoveredSpy.count(), 0); + QCOMPARE(sidebarHoveredSpy.size(), 0); QCOMPARE(buttonHH->isHovered(), false); - QCOMPARE(buttonHoveredSpy.count(), 0); + QCOMPARE(buttonHoveredSpy.size(), 0); #if QT_CONFIG(cursor) QCOMPARE(window->cursor().shape(), Qt::ArrowCursor); #endif QTest::mouseMove(window, rightOfButton); QCOMPARE(bottomSidebarMA->hovered(), true); - QCOMPARE(sidebarHoveredSpy.count(), 1); + QCOMPARE(sidebarHoveredSpy.size(), 1); QCOMPARE(buttonHH->isHovered(), false); - QCOMPARE(buttonHoveredSpy.count(), 0); + QCOMPARE(buttonHoveredSpy.size(), 0); #if QT_CONFIG(cursor) QCOMPARE(window->cursor().shape(), Qt::ClosedHandCursor); #endif QTest::mouseMove(window, buttonCenter); QCOMPARE(bottomSidebarMA->hovered(), false); - QCOMPARE(sidebarHoveredSpy.count(), 2); + QCOMPARE(sidebarHoveredSpy.size(), 2); QCOMPARE(buttonHH->isHovered(), true); - QCOMPARE(buttonHoveredSpy.count(), 1); + QCOMPARE(buttonHoveredSpy.size(), 1); #if QT_CONFIG(cursor) QCOMPARE(window->cursor().shape(), Qt::PointingHandCursor); #endif QTest::mouseMove(window, rightOfButton); QCOMPARE(bottomSidebarMA->hovered(), true); - QCOMPARE(sidebarHoveredSpy.count(), 3); + QCOMPARE(sidebarHoveredSpy.size(), 3); QCOMPARE(buttonHH->isHovered(), false); - QCOMPARE(buttonHoveredSpy.count(), 2); + QCOMPARE(buttonHoveredSpy.size(), 2); #if QT_CONFIG(cursor) QCOMPARE(window->cursor().shape(), Qt::ClosedHandCursor); #endif QTest::mouseMove(window, outOfSidebar); QCOMPARE(bottomSidebarMA->hovered(), false); - QCOMPARE(sidebarHoveredSpy.count(), 4); + QCOMPARE(sidebarHoveredSpy.size(), 4); QCOMPARE(buttonHH->isHovered(), false); - QCOMPARE(buttonHoveredSpy.count(), 2); + QCOMPARE(buttonHoveredSpy.size(), 2); #if QT_CONFIG(cursor) QCOMPARE(window->cursor().shape(), Qt::ArrowCursor); #endif } +void tst_HoverHandler::disabledHoverHandlerAndUnderlyingMouseArea() +{ + // Check that if a disabled HoverHandler is installed on an item, it + // will not participate in hover event delivery, and as such, also + // not block propagation to siblings. + QScopedPointer<QQuickView> windowPtr; + createView(windowPtr, "lesHoverables.qml"); + QQuickView * window = windowPtr.data(); + QQuickItem * bottomSidebar = window->rootObject()->findChild<QQuickItem *>("bottomSidebar"); + QVERIFY(bottomSidebar); + QQuickMouseArea *bottomSidebarMA = bottomSidebar->findChild<QQuickMouseArea *>("bottomSidebarMA"); + QVERIFY(bottomSidebarMA); + QQuickItem * button = bottomSidebar->findChild<QQuickItem *>("buttonWithHH"); + QVERIFY(button); + QQuickHoverHandler *buttonHH = button->findChild<QQuickHoverHandler *>("buttonHH"); + QVERIFY(buttonHH); + + // By disabling the HoverHandler, it should no longer + // block the sibling MouseArea underneath from receiving hover events. + buttonHH->setEnabled(false); + + QPoint buttonCenter(button->mapToScene(QPointF(button->width() / 2, button->height() / 2)).toPoint()); + QPoint rightOfButton(button->mapToScene(QPointF(button->width() + 2, button->height() / 2)).toPoint()); + QPoint outOfSidebar(bottomSidebar->mapToScene(QPointF(bottomSidebar->width() + 2, bottomSidebar->height() / 2)).toPoint()); + QSignalSpy sidebarHoveredSpy(bottomSidebarMA, SIGNAL(hoveredChanged())); + QSignalSpy buttonHoveredSpy(buttonHH, SIGNAL(hoveredChanged())); + + QTest::mouseMove(window, outOfSidebar); + QCOMPARE(bottomSidebarMA->hovered(), false); + QCOMPARE(sidebarHoveredSpy.size(), 0); + QCOMPARE(buttonHH->isHovered(), false); + QCOMPARE(buttonHoveredSpy.size(), 0); + + QTest::mouseMove(window, buttonCenter); + QCOMPARE(bottomSidebarMA->hovered(), true); + QCOMPARE(sidebarHoveredSpy.size(), 1); + QCOMPARE(buttonHH->isHovered(), false); + QCOMPARE(buttonHoveredSpy.size(), 0); + + QTest::mouseMove(window, rightOfButton); + QCOMPARE(bottomSidebarMA->hovered(), true); + QCOMPARE(sidebarHoveredSpy.size(), 1); + QCOMPARE(buttonHH->isHovered(), false); + QCOMPARE(buttonHoveredSpy.size(), 0); +} + +void tst_HoverHandler::hoverHandlerOnDisabledItem() +{ + // Check that if HoverHandler on a disabled item will + // continue to receive hover events (QTBUG-30801) + QScopedPointer<QQuickView> windowPtr; + createView(windowPtr, "lesHoverables.qml"); + QQuickView * window = windowPtr.data(); + QQuickItem * bottomSidebar = window->rootObject()->findChild<QQuickItem *>("bottomSidebar"); + QVERIFY(bottomSidebar); + QQuickItem * button = bottomSidebar->findChild<QQuickItem *>("buttonWithHH"); + QVERIFY(button); + QQuickHoverHandler *buttonHH = button->findChild<QQuickHoverHandler *>("buttonHH"); + QVERIFY(buttonHH); + + // Disable the button/rectangle item. This should not + // block its HoverHandler from being hovered + button->setEnabled(false); + + QPoint buttonCenter(button->mapToScene(QPointF(button->width() / 2, button->height() / 2)).toPoint()); + QPoint rightOfButton(button->mapToScene(QPointF(button->width() + 2, button->height() / 2)).toPoint()); + QSignalSpy buttonHoveredSpy(buttonHH, SIGNAL(hoveredChanged())); + + QTest::mouseMove(window, rightOfButton); + QCOMPARE(buttonHH->isHovered(), false); + QCOMPARE(buttonHoveredSpy.size(), 0); + + QTest::mouseMove(window, buttonCenter); + QCOMPARE(buttonHH->isHovered(), true); + QCOMPARE(buttonHoveredSpy.size(), 1); + + QTest::mouseMove(window, rightOfButton); + QCOMPARE(buttonHH->isHovered(), false); + QCOMPARE(buttonHoveredSpy.size(), 2); +} + void tst_HoverHandler::movingItemWithHoverHandler() { if (isPlatformWayland()) @@ -306,7 +397,7 @@ void tst_HoverHandler::movingItemWithHoverHandler() paddle->setX(100); QTRY_COMPARE(paddleHH->isHovered(), false); - paddle->setX(p.x()); + paddle->setX(p.x() - paddle->width() / 2); QTRY_COMPARE(paddleHH->isHovered(), true); paddle->setX(540); @@ -329,21 +420,21 @@ void tst_HoverHandler::margin() // QTBUG-85303 QTest::mouseMove(window, {10, 10}); QCOMPARE(hh->isHovered(), false); - QCOMPARE(hoveredSpy.count(), 0); + QCOMPARE(hoveredSpy.size(), 0); #if QT_CONFIG(cursor) QCOMPARE(window->cursor().shape(), Qt::ArrowCursor); #endif QTest::mouseMove(window, leftMargin); QCOMPARE(hh->isHovered(), true); - QCOMPARE(hoveredSpy.count(), 1); + QCOMPARE(hoveredSpy.size(), 1); #if QT_CONFIG(cursor) QCOMPARE(window->cursor().shape(), Qt::OpenHandCursor); #endif QTest::mouseMove(window, itemCenter); QCOMPARE(hh->isHovered(), true); - QCOMPARE(hoveredSpy.count(), 1); + QCOMPARE(hoveredSpy.size(), 1); #if QT_CONFIG(cursor) QCOMPARE(window->cursor().shape(), Qt::OpenHandCursor); #endif @@ -363,6 +454,306 @@ void tst_HoverHandler::margin() // QTBUG-85303 #endif } +void tst_HoverHandler::window() // QTBUG-98717 +{ + QQmlEngine engine; + QQmlComponent component(&engine); + component.loadUrl(testFileUrl("windowCursorShape.qml")); + QScopedPointer<QQuickWindow> window(qobject_cast<QQuickWindow *>(component.create())); + QVERIFY(!window.isNull()); + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window.data())); +#if QT_CONFIG(cursor) + if (isPlatformWayland()) + QSKIP("Wayland: QCursor::setPos() doesn't work."); + auto cursorPos = window->mapToGlobal(QPoint(100, 100)); + qCDebug(lcPointerTests) << "in window @" << window->position() << "setting cursor pos" << cursorPos; + QCursor::setPos(cursorPos); + if (!QTest::qWaitFor([cursorPos]{ return QCursor::pos() == cursorPos; })) + QSKIP("QCursor::setPos() doesn't work (QTBUG-76312)."); + QTRY_COMPARE(window->cursor().shape(), Qt::OpenHandCursor); +#endif +} + +void tst_HoverHandler::deviceCursor_data() +{ + QTest::addColumn<bool>("synthMouseForTabletEvents"); + QTest::addColumn<bool>("earlierTabletBeforeMouse"); + + QTest::newRow("nosynth, tablet wins") << false << false; + QTest::newRow("synth, tablet wins") << true << false; + QTest::newRow("synth, mouse wins") << true << true; +} + +void tst_HoverHandler::deviceCursor() +{ +#if !QT_CONFIG(tabletevent) + QSKIP("This test depends on QTabletEvent delivery."); +#endif + QFETCH(bool, synthMouseForTabletEvents); + QFETCH(bool, earlierTabletBeforeMouse); + qApp->setAttribute(Qt::AA_SynthesizeMouseForUnhandledTabletEvents, synthMouseForTabletEvents); + QQuickView window; + QVERIFY(QQuickTest::showView(window, testFileUrl("hoverDeviceCursors.qml"))); + // Ensure that we don't get extra hover events delivered on the side + QQuickWindowPrivate::get(&window)->deliveryAgentPrivate()->frameSynchronousHoverEnabled = false; + // And flush out any mouse events that might be queued up in QPA, since QTest::mouseMove() calls processEvents. + qGuiApp->processEvents(); + const QQuickItem *root = window.rootObject(); + QQuickHoverHandler *stylusHandler = root->findChild<QQuickHoverHandler *>("stylus"); + QVERIFY(stylusHandler); + QQuickHoverHandler *eraserHandler = root->findChild<QQuickHoverHandler *>("stylus eraser"); + QVERIFY(eraserHandler); + QQuickHoverHandler *aibrushHandler = root->findChild<QQuickHoverHandler *>("airbrush"); + QVERIFY(aibrushHandler); + QQuickHoverHandler *airbrushEraserHandler = root->findChild<QQuickHoverHandler *>("airbrush eraser"); + QVERIFY(airbrushEraserHandler); + QQuickHoverHandler *mouseHandler = root->findChild<QQuickHoverHandler *>("mouse"); + QVERIFY(mouseHandler); + + QPoint point(100, 100); + + const qint64 stylusId = 1234567890; + QElapsedTimer timer; + timer.start(); + auto testStylusDevice = [&](QInputDevice::DeviceType dt, QPointingDevice::PointerType pt, + Qt::CursorShape expectedCursor, QQuickHoverHandler* expectedActiveHandler) { + // We will follow up with a mouse event afterwards, and we want to simulate that the tablet events occur + // either slightly before (earlierTabletBeforeMouse == true) or some time before. + // It turns out that the first mouse move happens at timestamp 501 (simulated). + const ulong timestamp = (earlierTabletBeforeMouse ? 0 : 400) + timer.elapsed(); + qCDebug(lcPointerTests) << "@" << timestamp << "sending" << dt << pt << "expecting" << expectedCursor << expectedActiveHandler->objectName(); + QWindowSystemInterface::handleTabletEvent(&window, timestamp, point, window.mapToGlobal(point), + int(dt), int(pt), Qt::NoButton, 0, 0, 0, 0, 0, 0, stylusId, Qt::NoModifier); + point += QPoint(1, 0); +#if QT_CONFIG(cursor) + // QQuickItem::setCursor() doesn't get called: we only have HoverHandlers in this test + QCOMPARE(root->cursor().shape(), Qt::ArrowCursor); + QTRY_COMPARE(window.cursor().shape(), expectedCursor); +#endif + QCOMPARE(stylusHandler->isHovered(), stylusHandler == expectedActiveHandler); + QCOMPARE(eraserHandler->isHovered(), eraserHandler == expectedActiveHandler); + QCOMPARE(aibrushHandler->isHovered(), aibrushHandler == expectedActiveHandler); + QCOMPARE(airbrushEraserHandler->isHovered(), airbrushEraserHandler == expectedActiveHandler); + }; + + // simulate move events from various tablet stylus types + testStylusDevice(QInputDevice::DeviceType::Stylus, QPointingDevice::PointerType::Pen, + Qt::CrossCursor, stylusHandler); + testStylusDevice(QInputDevice::DeviceType::Stylus, QPointingDevice::PointerType::Eraser, + Qt::PointingHandCursor, eraserHandler); + testStylusDevice(QInputDevice::DeviceType::Airbrush, QPointingDevice::PointerType::Pen, + Qt::BusyCursor, aibrushHandler); + testStylusDevice(QInputDevice::DeviceType::Airbrush, QPointingDevice::PointerType::Eraser, + Qt::OpenHandCursor, airbrushEraserHandler); + + qCDebug(lcPointerTests) << "---- no more tablet events, now we send a mouse move"; + + // move the mouse: the mouse-specific HoverHandler gets to set the cursor only if + // more than kCursorOverrideTimeout ms have elapsed (100ms) + QTest::mouseMove(&window, point, 100); + QTRY_IMPL(mouseHandler->isHovered() == true, 500); + const bool afterTimeout = + QQuickPointerHandlerPrivate::get(airbrushEraserHandler)->lastEventTime + 100 < + QQuickPointerHandlerPrivate::get(mouseHandler)->lastEventTime; + qCDebug(lcPointerTests) << "airbrush handler reacted last time:" << QQuickPointerHandlerPrivate::get(airbrushEraserHandler)->lastEventTime + << "and the mouse handler reacted at time:" << QQuickPointerHandlerPrivate::get(mouseHandler)->lastEventTime + << "so > 100 ms have elapsed?" << afterTimeout; + if (afterTimeout) + QCOMPARE(mouseHandler->isHovered(), true); + else + QSKIP("Failed to delay mouse move 100ms after the previous tablet event"); + +#if QT_CONFIG(cursor) + QCOMPARE(window.cursor().shape(), afterTimeout ? Qt::IBeamCursor : Qt::OpenHandCursor); +#endif + QCOMPARE(stylusHandler->isHovered(), false); + QCOMPARE(eraserHandler->isHovered(), false); + QCOMPARE(aibrushHandler->isHovered(), false); + QCOMPARE(airbrushEraserHandler->isHovered(), true); // there was no fresh QTabletEvent to tell it not to be hovered + + // hover with the stylus again, then move the mouse outside the handlers' parent item + testStylusDevice(QInputDevice::DeviceType::Stylus, QPointingDevice::PointerType::Pen, + Qt::CrossCursor, stylusHandler); + QTest::mouseMove(&window, QPoint(180, 180)); + // the mouse has left the item: all its HoverHandlers should be unhovered (QTBUG-116505) + QCOMPARE(stylusHandler->isHovered(), false); + QCOMPARE(eraserHandler->isHovered(), false); + QCOMPARE(aibrushHandler->isHovered(), false); + QCOMPARE(airbrushEraserHandler->isHovered(), false); + QCOMPARE(mouseHandler->isHovered(), false); +} + +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.size(), 1); + + // Move mouse outside child + QTest::mouseMove(window.data(), outside); + QVERIFY(!handler->isHovered()); + QCOMPARE(spy.size(), 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.size(), 0); + + // Move mouse outside child + QTest::mouseMove(window.data(), outside); + QVERIFY(!handler->isHovered()); + QCOMPARE(spy.size(), 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.size(), 1); + + // Move mouse outside child + QTest::mouseMove(window.data(), outside); + QVERIFY(!handler->isHovered()); + QCOMPARE(spy.size(), 2); +} + +void tst_HoverHandler::ensureHoverHandlerWorksWhenItemHasHoverDisabled() +{ + // Check that a hover handler with a leaf item as parent, continues to + // receive hover, even if the item itself stops listening for hover. + 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); + + // Toggle hover on the item. This should not clear subtreeHoverEnabled + // on the item as a whole, since it still has a hover handler. + childItem->setAcceptHoverEvents(true); + childItem->setAcceptHoverEvents(false); + QSignalSpy spy(handler, &QQuickHoverHandler::hoveredChanged); + + // Move mouse inside child + QTest::mouseMove(window.data(), inside); + QVERIFY(handler->isHovered()); + QCOMPARE(spy.size(), 1); + + // Move mouse outside child + QTest::mouseMove(window.data(), outside); + QVERIFY(!handler->isHovered()); + QCOMPARE(spy.size(), 2); +} + +void tst_HoverHandler::changeCursor() +{ + QScopedPointer<QQuickView> windowPtr; + createView(windowPtr, "changingCursor.qml"); + QQuickView * window = windowPtr.data(); + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window)); + + QQuickItem *item = window->findChild<QQuickItem *>("brownRect"); + QVERIFY(item); + QQuickHoverHandler *hh = item->findChild<QQuickHoverHandler *>(); + QVERIFY(hh); + + QPoint itemCenter(item->mapToScene(QPointF(item->width() / 2, item->height() / 2)).toPoint()); + QSignalSpy hoveredSpy(hh, SIGNAL(hoveredChanged())); + + QTest::mouseMove(window, itemCenter); + + QTRY_COMPARE(hoveredSpy.size(), 1); + +#if QT_CONFIG(cursor) + QTRY_COMPARE(window->cursor().shape(), Qt::CrossCursor); + QTRY_COMPARE(window->cursor().shape(), Qt::OpenHandCursor); + QTRY_COMPARE(window->cursor().shape(), Qt::CrossCursor); + QTRY_COMPARE(window->cursor().shape(), Qt::OpenHandCursor); +#endif +} + +void tst_HoverHandler::touchDrag() +{ + QQuickView window; + QVERIFY(QQuickTest::showView(window, testFileUrl("hoverHandler.qml"))); + const QQuickItem *root = window.rootObject(); + QQuickHoverHandler *handler = root->findChild<QQuickHoverHandler *>(); + QVERIFY(handler); + + // polishAndSync() calls flushFrameSynchronousEvents() before emitting afterAnimating() + QSignalSpy frameSyncSpy(&window, &QQuickWindow::afterAnimating); + + const QPoint out(root->width() - 1, root->height() / 2); + QPoint in(root->width() / 2, root->height() / 2); + + QTest::touchEvent(&window, touchscreen.get()).press(0, out, &window); + QQuickTouchUtils::flush(&window); + QCOMPARE(handler->isHovered(), false); + + frameSyncSpy.clear(); + QTest::touchEvent(&window, touchscreen.get()).move(0, in, &window); + QQuickTouchUtils::flush(&window); + QTRY_COMPARE(handler->isHovered(), true); + QCOMPARE(handler->point().scenePosition(), in); + + in += {10, 10}; + QTest::touchEvent(&window, touchscreen.get()).move(0, in, &window); + QQuickTouchUtils::flush(&window); + // ensure that the color change is visible + QTRY_COMPARE_GE(frameSyncSpy.size(), 1); + QCOMPARE(handler->isHovered(), true); + QCOMPARE(handler->point().scenePosition(), in); + + QTest::touchEvent(&window, touchscreen.get()).move(0, out, &window); + QQuickTouchUtils::flush(&window); + QTRY_COMPARE_GE(frameSyncSpy.size(), 2); + QCOMPARE(handler->isHovered(), false); + + QTest::touchEvent(&window, touchscreen.get()).release(0, out, &window); +} + QTEST_MAIN(tst_HoverHandler) #include "tst_qquickhoverhandler.moc" |