/**************************************************************************** ** ** Copyright (C) 2018 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtQml module 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$ ** ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../../../shared/util.h" #include "../../shared/viewtestutil.h" Q_LOGGING_CATEGORY(lcPointerTests, "qt.quick.pointer.tests") class tst_FlickableInterop : public QQmlDataTest { Q_OBJECT public: tst_FlickableInterop() :touchDevice(QTest::createTouchDevice()) {} private slots: void touchTapButton_data(); void touchTapButton(); void touchDragFlickableBehindButton_data(); void touchDragFlickableBehindButton(); void mouseClickButton_data(); void mouseClickButton(); void mouseDragFlickableBehindButton_data(); void mouseDragFlickableBehindButton(); void touchDragSlider(); void touchDragFlickableBehindSlider(); void mouseDragSlider_data(); void mouseDragSlider(); void mouseDragFlickableBehindSlider(); void touchDragFlickableBehindItemWithHandlers_data(); void touchDragFlickableBehindItemWithHandlers(); void mouseDragFlickableBehindItemWithHandlers_data(); void mouseDragFlickableBehindItemWithHandlers(); void touchDragSliderAndFlickable(); void touchAndDragHandlerOnFlickable_data(); void touchAndDragHandlerOnFlickable(); private: void createView(QScopedPointer &window, const char *fileName); QTouchDevice *touchDevice; }; void tst_FlickableInterop::createView(QScopedPointer &window, const char *fileName) { window.reset(new QQuickView); window->setSource(testFileUrl(fileName)); QTRY_COMPARE(window->status(), QQuickView::Ready); QQuickViewTestUtil::centerOnScreen(window.data()); QQuickViewTestUtil::moveMouseAway(window.data()); window->show(); QVERIFY(QTest::qWaitForWindowActive(window.data())); QVERIFY(window->rootObject() != nullptr); } void tst_FlickableInterop::touchTapButton_data() { QTest::addColumn("buttonName"); QTest::newRow("DragThreshold") << QStringLiteral("DragThreshold"); QTest::newRow("WithinBounds") << QStringLiteral("WithinBounds"); QTest::newRow("ReleaseWithinBounds") << QStringLiteral("ReleaseWithinBounds"); } void tst_FlickableInterop::touchTapButton() { const int dragThreshold = QGuiApplication::styleHints()->startDragDistance(); QScopedPointer windowPtr; createView(windowPtr, "flickableWithHandlers.qml"); QQuickView * window = windowPtr.data(); QFETCH(QString, buttonName); QQuickItem *button = window->rootObject()->findChild(buttonName); QVERIFY(button); QSignalSpy tappedSpy(button, SIGNAL(tapped())); // Button changes pressed state and emits tapped on release QPoint p1 = button->mapToScene(QPointF(20, 20)).toPoint(); QTest::touchEvent(window, touchDevice).press(1, p1, window); QQuickTouchUtils::flush(window); QTRY_VERIFY(button->property("pressed").toBool()); QTest::touchEvent(window, touchDevice).release(1, p1, window); QQuickTouchUtils::flush(window); QTRY_VERIFY(!button->property("pressed").toBool()); QCOMPARE(tappedSpy.count(), 1); // We can drag <= dragThreshold and the button still acts normal, Flickable doesn't grab p1 = button->mapToScene(QPointF(20, 20)).toPoint(); QTest::touchEvent(window, touchDevice).press(1, p1, window); QQuickTouchUtils::flush(window); QTRY_VERIFY(button->property("pressed").toBool()); p1 += QPoint(dragThreshold, 0); QTest::touchEvent(window, touchDevice).move(1, p1, window); QQuickTouchUtils::flush(window); QVERIFY(button->property("pressed").toBool()); QTest::touchEvent(window, touchDevice).release(1, p1, window); QQuickTouchUtils::flush(window); QTRY_VERIFY(!button->property("pressed").toBool()); QCOMPARE(tappedSpy.count(), 2); } void tst_FlickableInterop::touchDragFlickableBehindButton_data() { QTest::addColumn("buttonName"); QTest::newRow("DragThreshold") << QStringLiteral("DragThreshold"); QTest::newRow("WithinBounds") << QStringLiteral("WithinBounds"); QTest::newRow("ReleaseWithinBounds") << QStringLiteral("ReleaseWithinBounds"); } void tst_FlickableInterop::touchDragFlickableBehindButton() { const int dragThreshold = QGuiApplication::styleHints()->startDragDistance(); QScopedPointer windowPtr; createView(windowPtr, "flickableWithHandlers.qml"); QQuickView * window = windowPtr.data(); QFETCH(QString, buttonName); QQuickItem *button = window->rootObject()->findChild(buttonName); QVERIFY(button); QQuickFlickable *flickable = window->rootObject()->findChild(); QVERIFY(flickable); QSignalSpy tappedSpy(button, SIGNAL(tapped())); tappedSpy.clear(); QPoint p1 = button->mapToScene(QPointF(20, 20)).toPoint(); QTest::touchEvent(window, touchDevice).press(1, p1, window); QQuickTouchUtils::flush(window); QTRY_VERIFY(button->property("pressed").toBool()); p1 += QPoint(dragThreshold, 0); QTest::touchEvent(window, touchDevice).move(1, p1, window); QQuickTouchUtils::flush(window); QVERIFY(button->property("pressed").toBool()); int i = 0; // Start dragging; eventually when the touchpoint goes beyond dragThreshold, // Button is no longer pressed because Flickable steals the grab for (; i < 100 && !flickable->isMoving(); ++i) { p1 += QPoint(1, 0); QTest::touchEvent(window, touchDevice).move(1, p1, window); QQuickTouchUtils::flush(window); } qCDebug(lcPointerTests) << "flickable started moving after" << i << "moves, when we got to" << p1; QVERIFY(flickable->isMoving()); QCOMPARE(i, 2); QVERIFY(!button->property("pressed").toBool()); QTest::touchEvent(window, touchDevice).release(1, p1, window); QQuickTouchUtils::flush(window); QVERIFY(!button->property("pressed").toBool()); QCOMPARE(tappedSpy.count(), 0); } void tst_FlickableInterop::mouseClickButton_data() { QTest::addColumn("buttonName"); QTest::newRow("DragThreshold") << QStringLiteral("DragThreshold"); QTest::newRow("WithinBounds") << QStringLiteral("WithinBounds"); QTest::newRow("ReleaseWithinBounds") << QStringLiteral("ReleaseWithinBounds"); } void tst_FlickableInterop::mouseClickButton() { const int dragThreshold = QGuiApplication::styleHints()->startDragDistance(); QScopedPointer windowPtr; createView(windowPtr, "flickableWithHandlers.qml"); QQuickView * window = windowPtr.data(); QFETCH(QString, buttonName); QQuickItem *button = window->rootObject()->findChild(buttonName); QVERIFY(button); QSignalSpy tappedSpy(button, SIGNAL(tapped())); // Button changes pressed state and emits tapped on release QPoint p1 = button->mapToScene(QPointF(20, 20)).toPoint(); QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1); QTRY_VERIFY(button->property("pressed").toBool()); QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1); QTRY_VERIFY(!button->property("pressed").toBool()); QCOMPARE(tappedSpy.count(), 1); // We can drag <= dragThreshold and the button still acts normal, Flickable doesn't grab p1 = button->mapToScene(QPointF(20, 20)).toPoint(); QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1, qApp->styleHints()->mouseDoubleClickInterval() + 10); QTRY_VERIFY(button->property("pressed").toBool()); p1 += QPoint(dragThreshold, 0); QTest::mouseMove(window, p1); QVERIFY(button->property("pressed").toBool()); QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1); QTRY_VERIFY(!button->property("pressed").toBool()); QCOMPARE(tappedSpy.count(), 2); } void tst_FlickableInterop::mouseDragFlickableBehindButton_data() { QTest::addColumn("buttonName"); QTest::newRow("DragThreshold") << QStringLiteral("DragThreshold"); QTest::newRow("WithinBounds") << QStringLiteral("WithinBounds"); QTest::newRow("ReleaseWithinBounds") << QStringLiteral("ReleaseWithinBounds"); } void tst_FlickableInterop::mouseDragFlickableBehindButton() { const int dragThreshold = QGuiApplication::styleHints()->startDragDistance(); QScopedPointer windowPtr; createView(windowPtr, "flickableWithHandlers.qml"); QQuickView * window = windowPtr.data(); QFETCH(QString, buttonName); QQuickItem *button = window->rootObject()->findChild(buttonName); QVERIFY(button); QQuickFlickable *flickable = window->rootObject()->findChild(); QVERIFY(flickable); QSignalSpy tappedSpy(button, SIGNAL(tapped())); // Button is no longer pressed if touchpoint goes beyond dragThreshold, // because Flickable steals the grab tappedSpy.clear(); QPoint p1 = button->mapToScene(QPointF(20, 20)).toPoint(); QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1); QTRY_VERIFY(button->property("pressed").toBool()); p1 += QPoint(dragThreshold, 0); QTest::touchEvent(window, touchDevice).move(1, p1, window); QVERIFY(button->property("pressed").toBool()); int i = 0; for (; i < 100 && !flickable->isMoving(); ++i) { p1 += QPoint(1, 0); QTest::mouseMove(window, p1); } qCDebug(lcPointerTests) << "flickable started moving after" << i << "moves, when we got to" << p1; QVERIFY(flickable->isMoving()); QCOMPARE(i, 2); QVERIFY(!button->property("pressed").toBool()); QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1); QVERIFY(!button->property("pressed").toBool()); QCOMPARE(tappedSpy.count(), 0); } void tst_FlickableInterop::touchDragSlider() { const int dragThreshold = QGuiApplication::styleHints()->startDragDistance(); QScopedPointer windowPtr; createView(windowPtr, "flickableWithHandlers.qml"); QQuickView * window = windowPtr.data(); QQuickItem *slider = window->rootObject()->findChild("knobSlider"); QVERIFY(slider); QQuickDragHandler *drag = slider->findChild(); QVERIFY(drag); QQuickItem *knob = slider->findChild("Slider Knob"); QVERIFY(knob); QQuickFlickable *flickable = window->rootObject()->findChild(); QVERIFY(flickable); QSignalSpy tappedSpy(knob->parent(), SIGNAL(tapped())); QSignalSpy translationChangedSpy(drag, SIGNAL(translationChanged())); // Drag the slider in the allowed (vertical) direction tappedSpy.clear(); QPoint p1 = knob->mapToScene(knob->clipRect().center()).toPoint() - QPoint(0, 8); QTest::touchEvent(window, touchDevice).press(1, p1, window); QQuickTouchUtils::flush(window); QTRY_VERIFY(slider->property("pressed").toBool()); p1 += QPoint(0, dragThreshold); QTest::touchEvent(window, touchDevice).move(1, p1, window); QQuickTouchUtils::flush(window); QVERIFY(slider->property("pressed").toBool()); QCOMPARE(slider->property("value").toInt(), 49); p1 += QPoint(0, 1); QTest::touchEvent(window, touchDevice).move(1, p1, window); QQuickTouchUtils::flush(window); p1 += QPoint(0, 10); QTest::touchEvent(window, touchDevice).move(1, p1, window); QQuickTouchUtils::flush(window); QVERIFY(slider->property("value").toInt() < 49); QVERIFY(!flickable->isMoving()); QVERIFY(!slider->property("pressed").toBool()); // Now that the DragHandler is active, the Flickable will not steal the grab // even if we move a large distance horizontally p1 += QPoint(dragThreshold * 2, 0); QTest::touchEvent(window, touchDevice).move(1, p1, window); QQuickTouchUtils::flush(window); QVERIFY(!flickable->isMoving()); // Release, and do not expect the tapped signal QTest::touchEvent(window, touchDevice).release(1, p1, window); QQuickTouchUtils::flush(window); QCOMPARE(tappedSpy.count(), 0); QCOMPARE(translationChangedSpy.count(), 1); } void tst_FlickableInterop::mouseDragSlider_data() { QTest::addColumn("nameOfSliderToDrag"); QTest::addColumn("pressPositionRelativeToKnob"); QTest::addColumn("dragDirection"); // a unit vector QTest::addColumn("expectedTapHandlerPressed"); QTest::addColumn("expectedDragHandlerActive"); QTest::addColumn("expectedFlickableMoving"); QTest::newRow("drag down on knob of knobSlider") << "knobSlider" << QPoint(0, -8) << QPoint(0, 1) << true << true << false; QTest::newRow("drag sideways on knob of knobSlider") << "knobSlider" << QPoint(0, 0) << QPoint(1, 0) << true << false << true; QTest::newRow("drag down on groove of knobSlider") << "knobSlider" << QPoint(0, 20) << QPoint(0, 1) << false << false << true; QTest::newRow("drag sideways on groove of knobSlider") << "knobSlider" << QPoint(0, 20) << QPoint(1, 0) << false << false << true; QTest::newRow("drag down on knob of grooveSlider") << "grooveSlider" << QPoint(0, -8) << QPoint(0, 1) << true << true << false; QTest::newRow("drag sideways on knob of grooveSlider") << "grooveSlider" << QPoint(0, 0) << QPoint(1, 0) << true << false << true; QTest::newRow("drag down on groove of grooveSlider") << "grooveSlider" << QPoint(0, 20) << QPoint(0, 1) << false << true << false; QTest::newRow("drag sideways on groove of grooveSlider") << "grooveSlider" << QPoint(0, 20) << QPoint(1, 0) << false << false << true; } void tst_FlickableInterop::mouseDragSlider() { const int dragThreshold = QGuiApplication::styleHints()->startDragDistance(); QScopedPointer windowPtr; createView(windowPtr, "flickableWithHandlers.qml"); QQuickView * window = windowPtr.data(); QFETCH(QString, nameOfSliderToDrag); QFETCH(QPoint, pressPositionRelativeToKnob); QFETCH(QPoint, dragDirection); // a unit vector QFETCH(bool, expectedTapHandlerPressed); QFETCH(bool, expectedDragHandlerActive); QFETCH(bool, expectedFlickableMoving); QQuickItem *slider = window->rootObject()->findChild(nameOfSliderToDrag); QVERIFY(slider); QQuickDragHandler *drag = slider->findChild(); QVERIFY(drag); QQuickItem *knob = slider->findChild("Slider Knob"); QVERIFY(knob); QQuickFlickable *flickable = window->rootObject()->findChild(); QVERIFY(flickable); QSignalSpy tappedSpy(knob->parent(), SIGNAL(tapped())); QSignalSpy translationChangedSpy(drag, SIGNAL(translationChanged())); // Drag the slider tappedSpy.clear(); QPoint p1 = knob->mapToScene(knob->clipRect().center()).toPoint() + pressPositionRelativeToKnob; QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1); QTRY_COMPARE(slider->property("pressed").toBool(), expectedTapHandlerPressed); p1 += QPoint(dragThreshold * dragDirection.x(), dragThreshold * dragDirection.y()); QTest::mouseMove(window, p1); QCOMPARE(drag->active(), false); QCOMPARE(slider->property("pressed").toBool(), expectedTapHandlerPressed); QCOMPARE(slider->property("value").toInt(), 49); p1 += dragDirection; // one more pixel QTest::mouseMove(window, p1); // After moving by the drag threshold, the point should still be inside the knob. // However, QQuickTapHandler::wantsEventPoint() returns false because the drag threshold is exceeded. // Therefore QQuickTapHandler::setPressed(false, true, point) is called: the active state is canceled. QCOMPARE(slider->property("pressed").toBool(), false); QCOMPARE(drag->active(), expectedDragHandlerActive); // drag farther, to make sure the knob gets adjusted significantly p1 += QPoint(10 * dragDirection.x(), 10 * dragDirection.y()); QTest::mouseMove(window, p1); if (expectedDragHandlerActive && dragDirection.y() > 0) QVERIFY(slider->property("value").toInt() < 49); // by now, Flickable will have stolen the grab, if it decided that it wanted to during filtering of the last event QCOMPARE(flickable->isMoving(), expectedFlickableMoving); QCOMPARE(slider->property("pressed").toBool(), false); // If the DragHandler is active, the Flickable will not steal the grab // even if we move a large distance horizontally if (expectedDragHandlerActive) { p1 += QPoint(dragThreshold * 2, 0); QTest::mouseMove(window, p1); QCOMPARE(flickable->isMoving(), false); } // Release, and do not expect the tapped signal QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1); QCOMPARE(tappedSpy.count(), 0); QCOMPARE(translationChangedSpy.count(), expectedDragHandlerActive ? 1 : 0); } void tst_FlickableInterop::touchDragFlickableBehindSlider() { const int dragThreshold = QGuiApplication::styleHints()->startDragDistance(); QScopedPointer windowPtr; createView(windowPtr, "flickableWithHandlers.qml"); QQuickView * window = windowPtr.data(); QQuickItem *slider = window->rootObject()->findChild("knobSlider"); QVERIFY(slider); QQuickDragHandler *drag = slider->findChild(); QVERIFY(drag); QQuickItem *knob = slider->findChild("Slider Knob"); QVERIFY(knob); QQuickFlickable *flickable = window->rootObject()->findChild(); QVERIFY(flickable); QSignalSpy tappedSpy(knob->parent(), SIGNAL(tapped())); QSignalSpy translationChangedSpy(drag, SIGNAL(translationChanged())); // Button is no longer pressed if touchpoint goes beyond dragThreshold, // because Flickable steals the grab tappedSpy.clear(); QPoint p1 = knob->mapToScene(knob->clipRect().center()).toPoint(); QTest::touchEvent(window, touchDevice).press(1, p1, window); QQuickTouchUtils::flush(window); QTRY_VERIFY(slider->property("pressed").toBool()); p1 += QPoint(dragThreshold, 0); QTest::touchEvent(window, touchDevice).move(1, p1, window); QQuickTouchUtils::flush(window); QVERIFY(slider->property("pressed").toBool()); int i = 0; for (; i < 100 && !flickable->isMoving(); ++i) { p1 += QPoint(1, 0); QTest::touchEvent(window, touchDevice).move(1, p1, window); QQuickTouchUtils::flush(window); } qCDebug(lcPointerTests) << "flickable started moving after" << i << "moves, when we got to" << p1; QVERIFY(flickable->isMoving()); QCOMPARE(i, 2); QVERIFY(!slider->property("pressed").toBool()); QTest::touchEvent(window, touchDevice).release(1, p1, window); QQuickTouchUtils::flush(window); QVERIFY(!slider->property("pressed").toBool()); QCOMPARE(tappedSpy.count(), 0); QCOMPARE(translationChangedSpy.count(), 0); } void tst_FlickableInterop::mouseDragFlickableBehindSlider() { const int dragThreshold = QGuiApplication::styleHints()->startDragDistance(); QScopedPointer windowPtr; createView(windowPtr, "flickableWithHandlers.qml"); QQuickView * window = windowPtr.data(); QQuickItem *slider = window->rootObject()->findChild("knobSlider"); QVERIFY(slider); QQuickDragHandler *drag = slider->findChild(); QVERIFY(drag); QQuickItem *knob = slider->findChild("Slider Knob"); QVERIFY(knob); QQuickFlickable *flickable = window->rootObject()->findChild(); QVERIFY(flickable); QSignalSpy tappedSpy(knob->parent(), SIGNAL(tapped())); QSignalSpy translationChangedSpy(drag, SIGNAL(translationChanged())); // Button is no longer pressed if touchpoint goes beyond dragThreshold, // because Flickable steals the grab tappedSpy.clear(); QPoint p1 = knob->mapToScene(knob->clipRect().center()).toPoint(); QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1); QTRY_VERIFY(slider->property("pressed").toBool()); p1 += QPoint(dragThreshold, 0); QTest::mouseMove(window, p1); QQuickTouchUtils::flush(window); QVERIFY(slider->property("pressed").toBool()); int i = 0; for (; i < 100 && !flickable->isMoving(); ++i) { p1 += QPoint(1, 0); QTest::mouseMove(window, p1); } qCDebug(lcPointerTests) << "flickable started moving after" << i << "moves, when we got to" << p1; QVERIFY(flickable->isMoving()); QCOMPARE(i, 2); QVERIFY(!slider->property("pressed").toBool()); QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1); QCOMPARE(tappedSpy.count(), 0); QCOMPARE(translationChangedSpy.count(), 0); } void tst_FlickableInterop::touchDragFlickableBehindItemWithHandlers_data() { QTest::addColumn("nameOfRectangleToDrag"); QTest::addColumn("expectedFlickableMoving"); QTest::newRow("drag") << QByteArray("drag") << false; QTest::newRow("tap") << QByteArray("tap") << true; QTest::newRow("dragAndTap") << QByteArray("dragAndTap") << false; QTest::newRow("tapAndDrag") << QByteArray("tapAndDrag") << false; } void tst_FlickableInterop::touchDragFlickableBehindItemWithHandlers() { QFETCH(bool, expectedFlickableMoving); QFETCH(QByteArray, nameOfRectangleToDrag); const int dragThreshold = QGuiApplication::styleHints()->startDragDistance(); QScopedPointer windowPtr; createView(windowPtr, "flickableWithHandlers.qml"); QQuickView * window = windowPtr.data(); QQuickItem *rect = window->rootObject()->findChild(nameOfRectangleToDrag); QVERIFY(rect); QQuickFlickable *flickable = window->rootObject()->findChild(); QVERIFY(flickable); QPoint p1 = rect->mapToScene(rect->clipRect().center()).toPoint(); QPoint originP1 = p1; QTest::touchEvent(window, touchDevice).press(1, p1, window); QQuickTouchUtils::flush(window); for (int i = 0; i < dragThreshold * 3; ++i) { p1 = originP1; p1.rx() += i; QTest::touchEvent(window, touchDevice).move(1, p1, window); QQuickTouchUtils::flush(window); } QCOMPARE(flickable->isMoving(), expectedFlickableMoving); if (!expectedFlickableMoving) { QVERIFY(rect->mapToScene(rect->clipRect().center()).toPoint().x() > originP1.x()); } QTest::touchEvent(window, touchDevice).release(1, p1, window); QQuickTouchUtils::flush(window); } void tst_FlickableInterop::mouseDragFlickableBehindItemWithHandlers_data() { touchDragFlickableBehindItemWithHandlers_data(); } void tst_FlickableInterop::mouseDragFlickableBehindItemWithHandlers() { QFETCH(bool, expectedFlickableMoving); QFETCH(QByteArray, nameOfRectangleToDrag); const int dragThreshold = QGuiApplication::styleHints()->startDragDistance(); QScopedPointer windowPtr; createView(windowPtr, "flickableWithHandlers.qml"); QQuickView * window = windowPtr.data(); QQuickItem *rect = window->rootObject()->findChild(nameOfRectangleToDrag); QVERIFY(rect); QQuickFlickable *flickable = window->rootObject()->findChild(); QVERIFY(flickable); QPoint p1 = rect->mapToScene(rect->clipRect().center()).toPoint(); QPoint originP1 = p1; QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1); for (int i = 0; i < 3; ++i) { p1 += QPoint(dragThreshold, 0); QTest::mouseMove(window, p1); QQuickTouchUtils::flush(window); } QCOMPARE(flickable->isMoving(), expectedFlickableMoving); if (!expectedFlickableMoving) { QCOMPARE(originP1 + QPoint(3*dragThreshold, 0), p1); } QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1); // wait until flickable stops QTRY_COMPARE(flickable->isMoving(), false); // After the mouse button has been released, move the mouse and ensure that nothing is moving // because of that (this tests if all grabs are released when the mouse button is released). p1 = rect->mapToScene(rect->clipRect().center()).toPoint(); originP1 = p1; for (int i = 0; i < 3; ++i) { p1 += QPoint(dragThreshold, 0); QTest::mouseMove(window, p1); QQuickTouchUtils::flush(window); } QCOMPARE(flickable->isMoving(), false); QCOMPARE(originP1, rect->mapToScene(rect->clipRect().center()).toPoint()); } void tst_FlickableInterop::touchDragSliderAndFlickable() { const int dragThreshold = QGuiApplication::styleHints()->startDragDistance(); QScopedPointer windowPtr; createView(windowPtr, "flickableWithHandlers.qml"); QQuickView * window = windowPtr.data(); QQuickItem *slider = window->rootObject()->findChild("knobSlider"); QVERIFY(slider); QQuickDragHandler *drag = slider->findChild(); QVERIFY(drag); QQuickItem *knob = slider->findChild("Slider Knob"); QVERIFY(knob); QQuickFlickable *flickable = window->rootObject()->findChild(); QVERIFY(flickable); QTest::QTouchEventSequence touchSeq = QTest::touchEvent(window, touchDevice, false); // The knob is initially centered over the slider's "groove" qreal initialXOffset = qAbs(knob->mapToScene(knob->clipRect().center()).x() - slider->mapToScene (slider->clipRect().center()).x()); QVERIFY(initialXOffset <= 1); // Drag the slider in the allowed (vertical) direction with one finger QPoint p1 = knob->mapToScene(knob->clipRect().center()).toPoint(); touchSeq.press(1, p1, window).commit(); QQuickTouchUtils::flush(window); p1 += QPoint(0, dragThreshold); touchSeq.move(1, p1, window).commit(); QQuickTouchUtils::flush(window); p1 += QPoint(0, dragThreshold); touchSeq.move(1, p1, window).commit(); QQuickTouchUtils::flush(window); p1 += QPoint(0, dragThreshold); touchSeq.move(1, p1, window).commit(); QQuickTouchUtils::flush(window); QTRY_VERIFY(slider->property("value").toInt() < 49); QVERIFY(!flickable->isMoving()); // Drag the Flickable with a second finger QPoint p2(300,300); touchSeq.stationary(1).press(2, p2, window).commit(); QQuickTouchUtils::flush(window); for (int i = 0; i < 4; ++i) { p1 += QPoint(-10, -10); p2 += QPoint(dragThreshold, 0); touchSeq.move(1, p1, window).stationary(2).commit(); QQuickTouchUtils::flush(window); p1 += QPoint(-10, -10); p2 += QPoint(dragThreshold, 0); touchSeq.stationary(1).move(2, p2, window).commit(); QQuickTouchUtils::flush(window); qCDebug(lcPointerTests) << "step" << i << ": fingers @" << p1 << p2 << "is Flickable moving yet?" << flickable->isMoving(); } QVERIFY(flickable->isMoving()); qreal knobSliderXOffset = qAbs(knob->mapToScene(knob->clipRect().center()).toPoint().x() - slider->mapToScene(slider->clipRect().center()).toPoint().x()) - initialXOffset; if (knobSliderXOffset > 1) qCDebug(lcPointerTests) << "knob has slipped out of groove by" << knobSliderXOffset << "pixels"; // See if the knob is still centered over the slider's "groove" QVERIFY(qAbs(knobSliderXOffset) <= 1); // Release touchSeq.release(1, p1, window).release(2, p2, window).commit(); } void tst_FlickableInterop::touchAndDragHandlerOnFlickable_data() { QTest::addColumn("qmlFile"); QTest::addColumn("pressDelay"); QTest::addColumn("targetNull"); QTest::newRow("tapOnFlickable") << QByteArray("tapOnFlickable.qml") << false << false; QTest::newRow("tapOnList") << QByteArray("tapOnList.qml") << false << false; QTest::newRow("tapOnTable") << QByteArray("tapOnTable.qml") << false << false; QTest::newRow("dragOnFlickable") << QByteArray("dragOnFlickable.qml") << false << false; QTest::newRow("dragOnList") << QByteArray("dragOnList.qml") << false << false; QTest::newRow("dragOnTable") << QByteArray("dragOnTable.qml") << false << false; QTest::newRow("tapDelayOnFlickable") << QByteArray("tapOnFlickable.qml") << true << false; QTest::newRow("tapDelayOnList") << QByteArray("tapOnList.qml") << true << false; QTest::newRow("tapDelayOnTable") << QByteArray("tapOnTable.qml") << true << false; QTest::newRow("dragDelayOnFlickable") << QByteArray("dragOnFlickable.qml") << true << false; QTest::newRow("dragDelayOnList") << QByteArray("dragOnList.qml") << true << false; QTest::newRow("dragDelayOnTable") << QByteArray("dragOnTable.qml") << true << false; QTest::newRow("tapOnFlickableWithNullTargets") << QByteArray("tapOnFlickable.qml") << false << true; QTest::newRow("tapOnListWithNullTargets") << QByteArray("tapOnList.qml") << false << true; QTest::newRow("tapOnTableWithNullTargets") << QByteArray("tapOnTable.qml") << false << true; QTest::newRow("dragOnFlickableWithNullTargets") << QByteArray("dragOnFlickable.qml") << false << true; QTest::newRow("dragOnListWithNullTargets") << QByteArray("dragOnList.qml") << false << true; QTest::newRow("dragOnTableWithNullTargets") << QByteArray("dragOnTable.qml") << false << true; QTest::newRow("tapDelayOnFlickableWithNullTargets") << QByteArray("tapOnFlickable.qml") << true << true; QTest::newRow("tapDelayOnListWithNullTargets") << QByteArray("tapOnList.qml") << true << true; QTest::newRow("tapDelayOnTableWithNullTargets") << QByteArray("tapOnTable.qml") << true << true; QTest::newRow("dragDelayOnFlickableWithNullTargets") << QByteArray("dragOnFlickable.qml") << true << true; QTest::newRow("dragDelayOnListWithNullTargets") << QByteArray("dragOnList.qml") << true << true; QTest::newRow("dragDelayOnTableWithNullTargets") << QByteArray("dragOnTable.qml") << true << true; } void tst_FlickableInterop::touchAndDragHandlerOnFlickable() { QFETCH(QByteArray, qmlFile); QFETCH(bool, pressDelay); QFETCH(bool, targetNull); const int dragThreshold = QGuiApplication::styleHints()->startDragDistance(); QScopedPointer windowPtr; createView(windowPtr, qmlFile.constData()); QQuickView * window = windowPtr.data(); QQuickFlickable *flickable = qmlobject_cast(window->rootObject()); QVERIFY(flickable); flickable->setPressDelay(pressDelay ? 5000 : 0); QQuickItem *delegate = nullptr; if (QQuickItemView *itemView = qmlobject_cast(flickable)) delegate = itemView->currentItem(); if (!delegate) delegate = flickable->property("delegateUnderTest").value(); QQuickItem *button = delegate ? delegate->findChild("button") : flickable->findChild("button"); if (!button) button = flickable->property("buttonUnderTest").value(); QVERIFY(button); QQuickPointerHandler *buttonHandler = button->findChild(); QVERIFY(buttonHandler); QQuickTapHandler *buttonTapHandler = qmlobject_cast(buttonHandler); QQuickDragHandler *buttonDragHandler = qmlobject_cast(buttonHandler); QQuickPointerHandler *delegateHandler = delegate ? delegate->findChild() : nullptr; QQuickPointerHandler *contentItemHandler = flickable->findChild(); QVERIFY(contentItemHandler); // a handler declared directly in a Flickable (or item view) must actually be a child of the contentItem, // just as Items declared inside are (QTBUG-71918 and QTBUG-73035) QCOMPARE(contentItemHandler->parentItem(), flickable->contentItem()); if (targetNull) { buttonHandler->setTarget(nullptr); if (delegateHandler) delegateHandler->setTarget(nullptr); contentItemHandler->setTarget(nullptr); } // Drag one finger on the Flickable and make sure it flicks QTest::QTouchEventSequence touchSeq = QTest::touchEvent(window, touchDevice, false); QPoint p1(780, 460); touchSeq.press(1, p1, window).commit(); QQuickTouchUtils::flush(window); for (int i = 0; i < 4; ++i) { p1 -= QPoint(dragThreshold, dragThreshold); touchSeq.move(1, p1, window).commit(); QQuickTouchUtils::flush(window); } if (!(buttonDragHandler && !pressDelay)) QTRY_VERIFY(flickable->contentY() >= dragThreshold); if (buttonTapHandler) QCOMPARE(buttonTapHandler->isPressed(), false); touchSeq.release(1, p1, window).commit(); QQuickTouchUtils::flush(window); // Drag one finger on the delegate and make sure Flickable flicks if (delegate) { flickable->setContentY(0); QTRY_COMPARE(flickable->isMoving(), false); QVERIFY(delegateHandler); QQuickTapHandler *delegateTapHandler = qmlobject_cast(delegateHandler); p1 = button->mapToScene(button->clipRect().bottomRight()).toPoint() + QPoint(10, 0); touchSeq.press(1, p1, window).commit(); QQuickTouchUtils::flush(window); if (delegateTapHandler && !pressDelay) QCOMPARE(delegateTapHandler->isPressed(), true); for (int i = 0; i < 4; ++i) { p1 -= QPoint(dragThreshold, dragThreshold); touchSeq.move(1, p1, window).commit(); QQuickTouchUtils::flush(window); if (i > 1) QTRY_VERIFY(delegateHandler->active() || flickable->isMoving()); } if (!(buttonDragHandler && !pressDelay)) QVERIFY(flickable->contentY() > 0); if (delegateTapHandler) QCOMPARE(delegateTapHandler->isPressed(), false); touchSeq.release(1, p1, window).commit(); QQuickTouchUtils::flush(window); } // Drag one finger on the button and make sure Flickable flicks flickable->setContentY(0); QTRY_COMPARE(flickable->isMoving(), false); p1 = button->mapToScene(button->clipRect().center()).toPoint(); touchSeq.press(1, p1, window).commit(); QQuickTouchUtils::flush(window); if (buttonTapHandler && !pressDelay) QTRY_COMPARE(buttonTapHandler->isPressed(), true); for (int i = 0; i < 4; ++i) { p1 -= QPoint(dragThreshold, dragThreshold); touchSeq.move(1, p1, window).commit(); QQuickTouchUtils::flush(window); } if (!(buttonDragHandler && !pressDelay)) QVERIFY(flickable->contentY() > 0); if (buttonTapHandler) QCOMPARE(buttonTapHandler->isPressed(), false); touchSeq.release(1, p1, window).commit(); } QTEST_MAIN(tst_FlickableInterop) #include "tst_flickableinterop.moc"