qtdeclarative/tests/auto/quick/pointerhandlers/qquickhoverhandler/tst_qquickhoverhandler.cpp

749 lines
29 KiB
C++

// Copyright (C) 2018 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#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>
#include <private/qquickwindow_p.h>
#include <QtQml/qqmlengine.h>
#include <QtQml/qqmlproperty.h>
#include <QQmlComponent>
#include <QtQuickTestUtils/private/qmlutils_p.h>
#include <QtQuickTestUtils/private/viewtestutils_p.h>
Q_LOGGING_CATEGORY(lcPointerTests, "qt.quick.pointer.tests")
static bool isPlatformWayland()
{
return !QGuiApplication::platformName().compare(QLatin1String("wayland"), Qt::CaseInsensitive);
}
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)
{
window.reset(new QQuickView);
window->setSource(testFileUrl(fileName));
QTRY_COMPARE(window->status(), QQuickView::Ready);
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();
QQuickItem * topSidebar = window->rootObject()->findChild<QQuickItem *>("topSidebar");
QVERIFY(topSidebar);
QQuickItem * button = topSidebar->findChild<QQuickItem *>("buttonWithHH");
QVERIFY(button);
QQuickHoverHandler *topSidebarHH = topSidebar->findChild<QQuickHoverHandler *>("topSidebarHH");
QVERIFY(topSidebarHH);
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());
QSignalSpy sidebarHoveredSpy(topSidebarHH, SIGNAL(hoveredChanged()));
QSignalSpy buttonHoveredSpy(buttonHH, SIGNAL(hoveredChanged()));
QTest::mouseMove(window, outOfSidebar);
QCOMPARE(topSidebarHH->isHovered(), false);
QCOMPARE(sidebarHoveredSpy.size(), 0);
QCOMPARE(buttonHH->isHovered(), false);
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.size(), 1);
QCOMPARE(buttonHH->isHovered(), false);
QCOMPARE(buttonHoveredSpy.size(), 0);
#if QT_CONFIG(cursor)
QCOMPARE(window->cursor().shape(), Qt::OpenHandCursor);
#endif
QTest::mouseMove(window, buttonCenter);
QCOMPARE(topSidebarHH->isHovered(), !blocking);
QCOMPARE(sidebarHoveredSpy.size(), blocking ? 2 : 1);
QCOMPARE(buttonHH->isHovered(), true);
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.size(), blocking ? 3 : 1);
QCOMPARE(buttonHH->isHovered(), false);
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.size(), blocking ? 4 : 2);
QCOMPARE(buttonHH->isHovered(), false);
QCOMPARE(buttonHoveredSpy.size(), 2);
#if QT_CONFIG(cursor)
QCOMPARE(window->cursor().shape(), Qt::ArrowCursor);
#endif
}
void tst_HoverHandler::mouseAreaAndUnderlyingHoverHandler()
{
QScopedPointer<QQuickView> windowPtr;
createView(windowPtr, "lesHoverables.qml");
QQuickView * window = windowPtr.data();
QQuickItem * topSidebar = window->rootObject()->findChild<QQuickItem *>("topSidebar");
QVERIFY(topSidebar);
QQuickMouseArea * buttonMA = topSidebar->findChild<QQuickMouseArea *>("buttonMA");
QVERIFY(buttonMA);
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());
QSignalSpy sidebarHoveredSpy(topSidebarHH, SIGNAL(hoveredChanged()));
QSignalSpy buttonHoveredSpy(buttonMA, SIGNAL(hoveredChanged()));
QTest::mouseMove(window, outOfSidebar);
QCOMPARE(topSidebarHH->isHovered(), false);
QCOMPARE(sidebarHoveredSpy.size(), 0);
QCOMPARE(buttonMA->hovered(), false);
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.size(), 1);
QCOMPARE(buttonMA->hovered(), false);
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.size(), 1);
QCOMPARE(buttonMA->hovered(), true);
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.size(), 1);
QCOMPARE(buttonMA->hovered(), false);
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.size(), 2);
QCOMPARE(buttonMA->hovered(), false);
QCOMPARE(buttonHoveredSpy.size(), 2);
#if QT_CONFIG(cursor)
QCOMPARE(window->cursor().shape(), Qt::ArrowCursor);
#endif
}
void tst_HoverHandler::hoverHandlerAndUnderlyingMouseArea()
{
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);
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);
#if QT_CONFIG(cursor)
QCOMPARE(window->cursor().shape(), Qt::ArrowCursor);
#endif
QTest::mouseMove(window, rightOfButton);
QCOMPARE(bottomSidebarMA->hovered(), true);
QCOMPARE(sidebarHoveredSpy.size(), 1);
QCOMPARE(buttonHH->isHovered(), false);
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.size(), 2);
QCOMPARE(buttonHH->isHovered(), true);
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.size(), 3);
QCOMPARE(buttonHH->isHovered(), false);
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.size(), 4);
QCOMPARE(buttonHH->isHovered(), false);
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())
QSKIP("Wayland: QCursor::setPos() doesn't work.");
QScopedPointer<QQuickView> windowPtr;
createView(windowPtr, "lesHoverables.qml");
QQuickView * window = windowPtr.data();
QQuickItem * paddle = window->rootObject()->findChild<QQuickItem *>("paddle");
QVERIFY(paddle);
QQuickHoverHandler *paddleHH = paddle->findChild<QQuickHoverHandler *>("paddleHH");
QVERIFY(paddleHH);
// Find the global coordinate of the paddle
const QPoint p(paddle->mapToScene(paddle->clipRect().center()).toPoint());
const QPoint paddlePos = window->mapToGlobal(p);
// Now hide the window, put the cursor where the paddle was and show it again
window->hide();
QTRY_COMPARE(window->isVisible(), false);
QCursor::setPos(paddlePos);
window->show();
QVERIFY(QTest::qWaitForWindowExposed(window));
QTRY_COMPARE(paddleHH->isHovered(), true);
// TODO check the cursor shape after fixing QTBUG-53987
paddle->setX(100);
QTRY_COMPARE(paddleHH->isHovered(), false);
paddle->setX(p.x() - paddle->width() / 2);
QTRY_COMPARE(paddleHH->isHovered(), true);
paddle->setX(540);
QTRY_COMPARE(paddleHH->isHovered(), false);
}
void tst_HoverHandler::margin() // QTBUG-85303
{
QScopedPointer<QQuickView> windowPtr;
createView(windowPtr, "hoverMargin.qml");
QQuickView * window = windowPtr.data();
QQuickItem * item = window->rootObject()->findChild<QQuickItem *>();
QVERIFY(item);
QQuickHoverHandler *hh = item->findChild<QQuickHoverHandler *>();
QVERIFY(hh);
QPoint itemCenter(item->mapToScene(QPointF(item->width() / 2, item->height() / 2)).toPoint());
QPoint leftMargin = itemCenter - QPoint(35, 35);
QSignalSpy hoveredSpy(hh, SIGNAL(hoveredChanged()));
QTest::mouseMove(window, {10, 10});
QCOMPARE(hh->isHovered(), false);
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.size(), 1);
#if QT_CONFIG(cursor)
QCOMPARE(window->cursor().shape(), Qt::OpenHandCursor);
#endif
QTest::mouseMove(window, itemCenter);
QCOMPARE(hh->isHovered(), true);
QCOMPARE(hoveredSpy.size(), 1);
#if QT_CONFIG(cursor)
QCOMPARE(window->cursor().shape(), Qt::OpenHandCursor);
#endif
QTest::mouseMove(window, leftMargin);
QCOMPARE(hh->isHovered(), true);
// QCOMPARE(hoveredSpy.count(), 1);
#if QT_CONFIG(cursor)
QCOMPARE(window->cursor().shape(), Qt::OpenHandCursor);
#endif
QTest::mouseMove(window, {10, 10});
QCOMPARE(hh->isHovered(), false);
// QCOMPARE(hoveredSpy.count(), 2);
#if QT_CONFIG(cursor)
QCOMPARE(window->cursor().shape(), Qt::ArrowCursor);
#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
}
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"