Revision control
Copy as Markdown
// Copyright (C) 2019 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include <QtTest/QTest>
#include <QtTest/QSignalSpy>
#include <QtGui/qstylehints.h>
#include <QtQml/qqmlengine.h>
#include <QtQml/qqmlproperty.h>
#include <QtQuick/private/qquickdraghandler_p.h>
#include <QtQuick/private/qquickhoverhandler_p.h>
#include <QtQuick/private/qquickmousearea_p.h>
#include <QtQuick/qquickitem.h>
#include <QtQuick/qquickview.h>
#include <QtGui/private/qpointingdevice_p.h>
#include <QtQuickTestUtils/private/qmlutils_p.h>
#include <QtQuickTestUtils/private/viewtestutils_p.h>
#include <QtCore/qpointer.h>
Q_LOGGING_CATEGORY(lcPointerTests, "qt.quick.pointer.tests")
class tst_MouseAreaInterop : public QQmlDataTest
{
Q_OBJECT
public:
tst_MouseAreaInterop()
: QQmlDataTest(QT_QMLTEST_DATADIR)
{}
private slots:
void dragHandlerInSiblingStealingGrabFromMouseAreaViaMouse();
void dragHandlerInSiblingStealingGrabFromMouseAreaViaTouch_data();
void dragHandlerInSiblingStealingGrabFromMouseAreaViaTouch();
void hoverHandlerDoesntHoverOnPress();
void doubleClickInMouseAreaWithDragHandlerInGrandparent();
private:
void createView(QScopedPointer<QQuickView> &window, const char *fileName);
std::unique_ptr<QPointingDevice> touchscreen{QTest::createTouchDevice()};
};
void tst_MouseAreaInterop::createView(QScopedPointer<QQuickView> &window, const char *fileName)
{
window.reset(new QQuickView);
window->setSource(testFileUrl(fileName));
QTRY_COMPARE(window->status(), QQuickView::Ready);
window.data()->setFlag(Qt::FramelessWindowHint);
QQuickViewTestUtils::centerOnScreen(window.data());
QQuickViewTestUtils::moveMouseAway(window.data());
window->show();
QVERIFY(QTest::qWaitForWindowActive(window.data()));
QVERIFY(window->rootObject() != nullptr);
}
void tst_MouseAreaInterop::dragHandlerInSiblingStealingGrabFromMouseAreaViaMouse()
{
const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();
QScopedPointer<QQuickView> windowPtr;
createView(windowPtr, "dragTakeOverFromSibling.qml");
QQuickView * window = windowPtr.data();
const QPoint pos = QGuiApplication::primaryScreen()->availableGeometry().topLeft();
window->setFramePosition(pos);
QTRY_COMPARE(window->framePosition(), pos);
QPointer<QQuickPointerHandler> handler = window->rootObject()->findChild<QQuickPointerHandler*>();
QVERIFY(handler);
QQuickMouseArea *ma = window->rootObject()->findChild<QQuickMouseArea*>();
QVERIFY(ma);
QPoint p1(150, 150);
QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1);
QCOMPARE(window->mouseGrabberItem(), ma);
QCOMPARE(ma->isPressed(), true);
// Start dragging
// DragHandler keeps monitoring, due to its passive grab,
// and eventually steals the exclusive grab from MA
int dragStoleGrab = 0;
auto devPriv = QPointingDevicePrivate::get(QPointingDevice::primaryPointingDevice());
for (int i = 0; i < 4; ++i) {
p1 += QPoint(dragThreshold / 2, 0);
QTest::mouseMove(window, p1);
if (!dragStoleGrab && devPriv->pointById(0)->exclusiveGrabber == handler)
dragStoleGrab = i;
}
if (dragStoleGrab)
qCDebug(lcPointerTests, "DragHandler stole the grab after %d events", dragStoleGrab);
QVERIFY(dragStoleGrab > 1);
QCOMPARE(handler->active(), true);
QCOMPARE(ma->isPressed(), false);
QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1);
QCOMPARE(handler->active(), false);
}
void tst_MouseAreaInterop::dragHandlerInSiblingStealingGrabFromMouseAreaViaTouch_data()
{
QTest::addColumn<bool>("preventStealing");
QTest::newRow("allow stealing") << false;
QTest::newRow("prevent stealing") << true;
}
void tst_MouseAreaInterop::dragHandlerInSiblingStealingGrabFromMouseAreaViaTouch() // QTBUG-77624 and QTBUG-79163
{
QFETCH(bool, preventStealing);
const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();
QScopedPointer<QQuickView> windowPtr;
createView(windowPtr, "dragTakeOverFromSibling.qml");
QQuickView * window = windowPtr.data();
auto devPriv = QPointingDevicePrivate::get(touchscreen.get());
const QPoint pos = QGuiApplication::primaryScreen()->availableGeometry().topLeft();
window->setFramePosition(pos);
QTRY_COMPARE(window->framePosition(), pos);
QPointer<QQuickPointerHandler> handler = window->rootObject()->findChild<QQuickPointerHandler*>();
QVERIFY(handler);
QQuickMouseArea *ma = window->rootObject()->findChild<QQuickMouseArea*>();
QVERIFY(ma);
ma->setPreventStealing(preventStealing);
QPoint p1(150, 150);
QTest::QTouchEventSequence touch = QTest::touchEvent(window, touchscreen.get());
touch.press(1, p1).commit();
QQuickTouchUtils::flush(window);
QTRY_VERIFY(!devPriv->activePoints.isEmpty());
qCDebug(lcPointerTests) << "active point after press:" << devPriv->activePoints.values().first().eventPoint;
auto epd = devPriv->queryPointById(1);
QVERIFY(epd);
QVERIFY(epd->passiveGrabbers.contains(handler.data()));
QCOMPARE(epd->exclusiveGrabber, ma);
QCOMPARE(ma->isPressed(), true);
// Start dragging
// DragHandler keeps monitoring, due to its passive grab,
// and eventually steals the exclusive grab from MA if MA allows it
int dragStoleGrab = 0;
for (int i = 0; i < 4; ++i) {
p1 += QPoint(dragThreshold / 2, 0);
touch.move(1, p1).commit();
QQuickTouchUtils::flush(window);
if (!dragStoleGrab && epd->exclusiveGrabber == handler)
dragStoleGrab = i;
}
if (dragStoleGrab)
qCDebug(lcPointerTests, "DragHandler stole the grab after %d events", dragStoleGrab);
if (preventStealing) {
QCOMPARE(dragStoleGrab, 0);
QCOMPARE(handler->active(), false);
QCOMPARE(ma->isPressed(), true);
} else {
QVERIFY(dragStoleGrab > 1);
QCOMPARE(handler->active(), true);
QCOMPARE(ma->isPressed(), false);
}
touch.release(1, p1).commit();
QQuickTouchUtils::flush(window);
QCOMPARE(handler->active(), false);
}
void tst_MouseAreaInterop::hoverHandlerDoesntHoverOnPress() // QTBUG-72843
{
QQuickView window;
QVERIFY(QQuickTest::showView(window, testFileUrl("hoverHandlerInGrandparentOfHoverableItem.qml")));
QPointer<QQuickHoverHandler> handler = window.rootObject()->findChild<QQuickHoverHandler*>();
QVERIFY(handler);
QQuickMouseArea *ma = window.rootObject()->findChild<QQuickMouseArea*>();
QVERIFY(ma);
QPoint p = ma->mapToScene(ma->boundingRect().center()).toPoint();
// move the mouse below the "button" but within HoverHandler's region of interest
QTest::mouseMove(&window, p + QPoint(0, 50));
QTRY_COMPARE(handler->isHovered(), true);
// move the mouse into the "button"
QTest::mouseMove(&window, p);
// both the hoverhandler and the mouse area should now be hovered!
QTRY_COMPARE(handler->isHovered(), true);
QCOMPARE(ma->hovered(), true);
QSignalSpy hoveredChangedSpy(handler, SIGNAL(hoveredChanged()));
QTest::mousePress(&window, Qt::LeftButton, Qt::NoModifier, p);
QTRY_COMPARE(ma->isPressed(), true);
QCOMPARE(handler->isHovered(), true);
QCOMPARE(hoveredChangedSpy.size(), 0);
QTest::mouseRelease(&window, Qt::LeftButton, Qt::NoModifier, p);
QTRY_COMPARE(ma->isPressed(), false);
QCOMPARE(handler->isHovered(), true);
QCOMPARE(hoveredChangedSpy.size(), 0);
}
void tst_MouseAreaInterop::doubleClickInMouseAreaWithDragHandlerInGrandparent()
{
QQuickView window;
QVERIFY(QQuickTest::showView(window, testFileUrl("dragHandlerInMouseAreaGrandparent.qml")));
QQuickDragHandler *handler = window.rootObject()->findChild<QQuickDragHandler*>();
QVERIFY(handler);
QSignalSpy dragActiveSpy(handler, &QQuickDragHandler::activeChanged);
QQuickMouseArea *ma = window.rootObject()->findChild<QQuickMouseArea*>();
QVERIFY(ma);
QSignalSpy dClickSpy(ma, &QQuickMouseArea::doubleClicked);
QPoint p = ma->mapToScene(ma->boundingRect().center()).toPoint();
QTest::mouseDClick(&window, Qt::LeftButton, Qt::NoModifier, p);
QCOMPARE(dClickSpy.size(), 1);
QCOMPARE(dragActiveSpy.size(), 0);
}
QTEST_MAIN(tst_MouseAreaInterop)
#include "tst_mousearea_interop.moc"