blob: 66314f88a26f7c61031fbb16d8b1f7ed9897376c [file] [log] [blame]
/****************************************************************************
**
** 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 <QtTest/QtTest>
#include <QtQml/qqmlengine.h>
#include <QtQml/qqmlproperty.h>
#include <QtQuick/private/qquickdraghandler_p.h>
#include <QtQuick/private/qquickrepeater_p.h>
#include <QtQuick/private/qquicktaphandler_p.h>
#include <QtQuick/qquickitem.h>
#include <QtQuick/qquickview.h>
#include "../../../shared/util.h"
#include "../../shared/viewtestutil.h"
Q_LOGGING_CATEGORY(lcPointerTests, "qt.quick.pointer.tests")
class tst_DragHandler : public QQmlDataTest
{
Q_OBJECT
public:
tst_DragHandler()
:touchDevice(QTest::createTouchDevice())
{}
private slots:
void initTestCase();
void defaultPropertyValues();
void touchDrag();
void mouseDrag_data();
void mouseDrag();
void dragFromMargin();
void snapMode_data();
void snapMode();
void touchDragMulti();
void touchDragMultiSliders_data();
void touchDragMultiSliders();
void touchPassiveGrabbers_data();
void touchPassiveGrabbers();
void touchPinchAndMouseMove();
private:
void createView(QScopedPointer<QQuickView> &window, const char *fileName);
QSet<QQuickPointerHandler *> passiveGrabbers(QQuickWindow *window, int pointId = 0);
QTouchDevice *touchDevice;
};
void tst_DragHandler::createView(QScopedPointer<QQuickView> &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);
}
QSet<QQuickPointerHandler*> tst_DragHandler::passiveGrabbers(QQuickWindow *window, int pointId /*= 0*/)
{
QSet<QQuickPointerHandler*> result;
QQuickWindowPrivate *winp = QQuickWindowPrivate::get(window);
if (QQuickPointerDevice* device = QQuickPointerDevice::touchDevice(touchDevice)) {
QQuickPointerEvent *pointerEvent = winp->pointerEventInstance(device);
for (int i = 0; i < pointerEvent->pointCount(); ++i) {
QQuickEventPoint *eventPoint = pointerEvent->point(i);
QVector<QPointer <QQuickPointerHandler> > passives = eventPoint->passiveGrabbers();
if (!pointId || eventPoint->pointId() == pointId) {
for (auto it = passives.constBegin(); it != passives.constEnd(); ++it)
result << it->data();
}
}
}
return result;
}
void tst_DragHandler::initTestCase()
{
// This test assumes that we don't get synthesized mouse events from QGuiApplication
qApp->setAttribute(Qt::AA_SynthesizeMouseForUnhandledTouchEvents, false);
QQmlDataTest::initTestCase();
}
void tst_DragHandler::defaultPropertyValues()
{
QScopedPointer<QQuickView> windowPtr;
createView(windowPtr, "draggables.qml");
QQuickView * window = windowPtr.data();
QQuickItem *ball = window->rootObject()->childItems().first();
QVERIFY(ball);
QQuickDragHandler *dragHandler = ball->findChild<QQuickDragHandler*>();
QVERIFY(dragHandler);
QCOMPARE(dragHandler->acceptedButtons(), Qt::LeftButton);
QCOMPARE(dragHandler->translation(), QVector2D());
QCOMPARE(dragHandler->centroid().position(), QPointF());
QCOMPARE(dragHandler->centroid().scenePosition(), QPointF());
QCOMPARE(dragHandler->centroid().pressPosition(), QPointF());
QCOMPARE(dragHandler->centroid().scenePressPosition(), QPointF());
QCOMPARE(dragHandler->centroid().sceneGrabPosition(), QPointF());
}
void tst_DragHandler::touchDrag()
{
const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();
QScopedPointer<QQuickView> windowPtr;
createView(windowPtr, "draggables.qml");
QQuickView * window = windowPtr.data();
QQuickItem *ball = window->rootObject()->childItems().first();
QVERIFY(ball);
QQuickDragHandler *dragHandler = ball->findChild<QQuickDragHandler*>();
QVERIFY(dragHandler);
QSignalSpy translationChangedSpy(dragHandler, SIGNAL(translationChanged()));
QSignalSpy centroidChangedSpy(dragHandler, SIGNAL(centroidChanged()));
QPointF ballCenter = ball->clipRect().center();
QPointF scenePressPos = ball->mapToScene(ballCenter);
QPoint p1 = scenePressPos.toPoint();
QTest::touchEvent(window, touchDevice).press(1, p1, window);
QQuickTouchUtils::flush(window);
QVERIFY(!dragHandler->active());
QCOMPARE(dragHandler->centroid().position(), ballCenter);
QCOMPARE(dragHandler->centroid().pressPosition(), ballCenter);
QCOMPARE(dragHandler->centroid().scenePosition(), scenePressPos);
QCOMPARE(dragHandler->centroid().scenePressPosition(), scenePressPos);
QCOMPARE(dragHandler->centroid().velocity(), QVector2D());
QCOMPARE(centroidChangedSpy.count(), 1);
p1 += QPoint(dragThreshold, 0);
QTest::touchEvent(window, touchDevice).move(1, p1, window);
QQuickTouchUtils::flush(window);
QTRY_VERIFY(dragHandler->centroid().velocity().x() > 0);
QCOMPARE(centroidChangedSpy.count(), 2);
QVERIFY(!dragHandler->active());
p1 += QPoint(1, 0);
QTest::touchEvent(window, touchDevice).move(1, p1, window);
QQuickTouchUtils::flush(window);
QTRY_VERIFY(dragHandler->active());
QCOMPARE(translationChangedSpy.count(), 0);
QCOMPARE(centroidChangedSpy.count(), 3);
QCOMPARE(dragHandler->translation().x(), 0.0);
QPointF sceneGrabPos = p1;
QCOMPARE(dragHandler->centroid().sceneGrabPosition(), sceneGrabPos);
p1 += QPoint(19, 0);
QTest::touchEvent(window, touchDevice).move(1, p1, window);
QQuickTouchUtils::flush(window);
QTRY_VERIFY(dragHandler->active());
QCOMPARE(dragHandler->centroid().position(), ballCenter);
QCOMPARE(dragHandler->centroid().pressPosition(), ballCenter);
QCOMPARE(dragHandler->centroid().scenePosition(), ball->mapToScene(ballCenter));
QCOMPARE(dragHandler->centroid().scenePressPosition(), scenePressPos);
QCOMPARE(dragHandler->centroid().sceneGrabPosition(), sceneGrabPos);
QCOMPARE(dragHandler->translation().x(), dragThreshold + 20.0);
QCOMPARE(dragHandler->translation().y(), 0.0);
QVERIFY(dragHandler->centroid().velocity().x() > 0);
QCOMPARE(centroidChangedSpy.count(), 4);
QTest::touchEvent(window, touchDevice).release(1, p1, window);
QQuickTouchUtils::flush(window);
QTRY_VERIFY(!dragHandler->active());
QCOMPARE(dragHandler->centroid().pressedButtons(), Qt::NoButton);
QCOMPARE(dragHandler->centroid().velocity(), QVector2D());
QCOMPARE(ball->mapToScene(ballCenter).toPoint(), p1);
QCOMPARE(translationChangedSpy.count(), 1);
QCOMPARE(centroidChangedSpy.count(), 5);
}
void tst_DragHandler::mouseDrag_data()
{
QTest::addColumn<Qt::MouseButtons>("acceptedButtons");
QTest::addColumn<Qt::MouseButtons>("dragButton");
QTest::newRow("left: drag") << Qt::MouseButtons(Qt::LeftButton) << Qt::MouseButtons(Qt::LeftButton);
QTest::newRow("right: don't drag") << Qt::MouseButtons(Qt::LeftButton) << Qt::MouseButtons(Qt::RightButton);
QTest::newRow("left: don't drag") << Qt::MouseButtons(Qt::RightButton | Qt::MiddleButton) << Qt::MouseButtons(Qt::LeftButton);
QTest::newRow("right or middle: drag") << Qt::MouseButtons(Qt::RightButton | Qt::MiddleButton) << Qt::MouseButtons(Qt::MiddleButton);
}
void tst_DragHandler::mouseDrag()
{
QFETCH(Qt::MouseButtons, acceptedButtons);
QFETCH(Qt::MouseButtons, dragButton);
bool shouldDrag = bool(acceptedButtons & dragButton);
const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();
QScopedPointer<QQuickView> windowPtr;
createView(windowPtr, "draggables.qml");
QQuickView * window = windowPtr.data();
QQuickItem *ball = window->rootObject()->childItems().first();
QVERIFY(ball);
QQuickDragHandler *dragHandler = ball->findChild<QQuickDragHandler*>();
QVERIFY(dragHandler);
dragHandler->setAcceptedButtons(acceptedButtons); // QTBUG-76875
QSignalSpy translationChangedSpy(dragHandler, SIGNAL(translationChanged()));
QSignalSpy centroidChangedSpy(dragHandler, SIGNAL(centroidChanged()));
QPointF ballCenter = ball->clipRect().center();
QPointF scenePressPos = ball->mapToScene(ballCenter);
QPoint p1 = scenePressPos.toPoint();
QTest::mousePress(window, static_cast<Qt::MouseButton>(int(dragButton)), Qt::NoModifier, p1);
QVERIFY(!dragHandler->active());
if (shouldDrag) {
QCOMPARE(dragHandler->centroid().position(), ballCenter);
QCOMPARE(dragHandler->centroid().pressPosition(), ballCenter);
QCOMPARE(dragHandler->centroid().scenePosition(), scenePressPos);
QCOMPARE(dragHandler->centroid().scenePressPosition(), scenePressPos);
QCOMPARE(dragHandler->centroid().velocity(), QVector2D());
QCOMPARE(centroidChangedSpy.count(), 1);
}
p1 += QPoint(dragThreshold, 0);
QTest::mouseMove(window, p1);
if (shouldDrag) {
QTRY_VERIFY(dragHandler->centroid().velocity().x() > 0);
QCOMPARE(centroidChangedSpy.count(), 2);
QVERIFY(!dragHandler->active());
}
p1 += QPoint(1, 0);
QTest::mouseMove(window, p1);
if (shouldDrag)
QTRY_VERIFY(dragHandler->active());
else
QVERIFY(!dragHandler->active());
QCOMPARE(translationChangedSpy.count(), 0);
if (shouldDrag)
QCOMPARE(centroidChangedSpy.count(), 3);
QCOMPARE(dragHandler->translation().x(), 0.0);
QPointF sceneGrabPos = p1;
if (shouldDrag)
QCOMPARE(dragHandler->centroid().sceneGrabPosition(), sceneGrabPos);
p1 += QPoint(19, 0);
QTest::mouseMove(window, p1);
QVERIFY(shouldDrag ? dragHandler->active() : !dragHandler->active());
if (shouldDrag) {
QCOMPARE(dragHandler->centroid().position(), ballCenter);
QCOMPARE(dragHandler->centroid().pressPosition(), ballCenter);
QCOMPARE(dragHandler->centroid().scenePosition(), ball->mapToScene(ballCenter));
QCOMPARE(dragHandler->centroid().scenePressPosition(), scenePressPos);
QCOMPARE(dragHandler->centroid().sceneGrabPosition(), sceneGrabPos);
QCOMPARE(dragHandler->translation().x(), dragThreshold + 20.0);
QCOMPARE(dragHandler->translation().y(), 0.0);
QVERIFY(dragHandler->centroid().velocity().x() > 0);
QCOMPARE(centroidChangedSpy.count(), 4);
}
QTest::mouseRelease(window, static_cast<Qt::MouseButton>(int(dragButton)), Qt::NoModifier, p1);
QTRY_VERIFY(!dragHandler->active());
QCOMPARE(dragHandler->centroid().pressedButtons(), Qt::NoButton);
if (shouldDrag)
QCOMPARE(ball->mapToScene(ballCenter).toPoint(), p1);
QCOMPARE(translationChangedSpy.count(), shouldDrag ? 1 : 0);
QCOMPARE(centroidChangedSpy.count(), shouldDrag ? 5 : 0);
}
void tst_DragHandler::dragFromMargin() // QTBUG-74966
{
const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();
QScopedPointer<QQuickView> windowPtr;
createView(windowPtr, "dragMargin.qml");
QQuickView * window = windowPtr.data();
QQuickItem *draggableItem = window->rootObject()->childItems().first();
QVERIFY(draggableItem);
QQuickDragHandler *dragHandler = draggableItem->findChild<QQuickDragHandler*>();
QVERIFY(dragHandler);
QPointF originalPos = draggableItem->position();
QPointF scenePressPos = originalPos - QPointF(10, 0);
QPoint p1 = scenePressPos.toPoint();
QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1);
QVERIFY(!dragHandler->active());
QCOMPARE(dragHandler->centroid().scenePosition(), scenePressPos);
QCOMPARE(dragHandler->centroid().scenePressPosition(), scenePressPos);
p1 += QPoint(dragThreshold * 2, 0);
QTest::mouseMove(window, p1);
QTRY_VERIFY(dragHandler->active());
QCOMPARE(dragHandler->centroid().scenePressPosition(), scenePressPos);
QCOMPARE(dragHandler->centroid().sceneGrabPosition(), p1);
QCOMPARE(dragHandler->translation().x(), 0.0); // hmm that's odd
QCOMPARE(dragHandler->translation().y(), 0.0);
QCOMPARE(draggableItem->position(), originalPos + QPointF(dragThreshold * 2, 0));
QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1);
QTRY_VERIFY(!dragHandler->active());
QCOMPARE(dragHandler->centroid().pressedButtons(), Qt::NoButton);
}
void tst_DragHandler::snapMode_data()
{
const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();
QTest::addColumn<QString>("subTree");
QTest::addColumn<int>("snapMode");
QTest::addColumn<QPoint>("startDragPos");
QTest::addColumn<QPoint>("dragMovement");
QTest::addColumn<QPoint>("expectedMovement");
struct TestEntry {
const char *desc;
const char *subTree;
QQuickDragHandler::SnapMode mode;
QPoint startDragPos;
QPoint dragMovement;
QPoint expectedMovement;
};
TestEntry testdata[] = {
{"outside the target", "rect1", QQuickDragHandler::SnapAuto, QPoint(45, -10), QPoint(dragThreshold*2, 0), QPoint(dragThreshold*2, 0)},
{"inside the target", "rect1", QQuickDragHandler::SnapAuto, QPoint(45, 10), QPoint(dragThreshold*2, 0), QPoint(dragThreshold*2, 0)},
{"outside the target", "rect1", QQuickDragHandler::SnapAlways, QPoint(45, -10), QPoint(dragThreshold*2, 0), QPoint(dragThreshold*2, -50-10)},
{"outside the target", "rect1", QQuickDragHandler::NoSnap, QPoint(45, -10), QPoint(dragThreshold*2, 0), QPoint(dragThreshold*2, 0)},
{"outside the target", "rect1", QQuickDragHandler::SnapIfPressedOutsideTarget, QPoint(45, -10), QPoint(dragThreshold*2, 0), QPoint(dragThreshold*2, -50-10)},
{"inside the target", "rect1", QQuickDragHandler::SnapIfPressedOutsideTarget, QPoint(45, 10), QPoint(dragThreshold*2, 0), QPoint(dragThreshold*2, 0)},
//targets y pos moves from -25 to (25 + dragThreshold*2) because of snapping to center:
{"outside target, should snap", "rect2", QQuickDragHandler::SnapAuto, QPoint(45, 50), QPoint(0, dragThreshold*2), QPoint(0, 25 + 25 + dragThreshold*2)},
{"inside target, shouldn't snap", "rect2", QQuickDragHandler::SnapAuto, QPoint(45, 10), QPoint(0, dragThreshold*2), QPoint(0, dragThreshold*2)}
};
for (const TestEntry& e : testdata) {
const QMetaEnum menum = QMetaEnum::fromType<QQuickDragHandler::SnapMode>();
const QString dataTag = QString::fromLatin1("%1, %2, %3").arg(e.subTree).arg(menum.valueToKey(e.mode)).arg(e.desc);
QTest::newRow(dataTag.toUtf8().constData()) << e.subTree << (int)e.mode
<< e.startDragPos << e.dragMovement << e.expectedMovement;
}
}
void tst_DragHandler::snapMode()
{
QFETCH(QString, subTree);
QFETCH(QPoint, startDragPos);
QFETCH(QPoint, dragMovement);
QFETCH(int, snapMode);
QFETCH(QPoint, expectedMovement);
QScopedPointer<QQuickView> windowPtr;
createView(windowPtr, "snapMode.qml");
QQuickView * window = windowPtr.data();
QQuickItem *rect1 = window->rootObject()->findChild<QQuickItem*>(subTree);
QVERIFY(rect1);
QQuickItem *rect1b = rect1->childItems().first();
QVERIFY(rect1b);
QQuickDragHandler *dragHandler1 = rect1->findChild<QQuickDragHandler*>();
QVERIFY(dragHandler1);
dragHandler1->setSnapMode((QQuickDragHandler::SnapMode)snapMode);
QQuickItem *dragTarget = dragHandler1->target();
QPointF oldTargetPos = dragTarget->position();
QPoint p1 = rect1->mapToScene(QPointF(startDragPos)).toPoint();
QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1);
QVERIFY(!dragHandler1->active());
p1 += dragMovement;
QTest::mouseMove(window, p1);
QTRY_VERIFY(dragHandler1->active());
QCOMPARE(dragTarget->position(), oldTargetPos + expectedMovement);
QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1);
QTRY_VERIFY(!dragHandler1->active());
QCOMPARE(dragHandler1->centroid().pressedButtons(), Qt::NoButton);
}
void tst_DragHandler::touchDragMulti()
{
const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();
QScopedPointer<QQuickView> windowPtr;
createView(windowPtr, "draggables.qml");
QQuickView * window = windowPtr.data();
QQuickItem *ball1 = window->rootObject()->childItems().first();
QVERIFY(ball1);
QQuickDragHandler *dragHandler1 = ball1->findChild<QQuickDragHandler*>();
QVERIFY(dragHandler1);
QSignalSpy translationChangedSpy1(dragHandler1, SIGNAL(translationChanged()));
QSignalSpy centroidChangedSpy1(dragHandler1, SIGNAL(centroidChanged()));
QQuickItem *ball2 = window->rootObject()->childItems().at(1);
QVERIFY(ball2);
QQuickDragHandler *dragHandler2 = ball2->findChild<QQuickDragHandler*>();
QVERIFY(dragHandler2);
QSignalSpy translationChangedSpy2(dragHandler2, SIGNAL(translationChanged()));
QSignalSpy centroidChangedSpy2(dragHandler1, SIGNAL(centroidChanged()));
QPointF ball1Center = ball1->clipRect().center();
QPointF scenePressPos1 = ball1->mapToScene(ball1Center);
QPoint p1 = scenePressPos1.toPoint();
QPointF ball2Center = ball2->clipRect().center();
QPointF scenePressPos2 = ball2->mapToScene(ball2Center);
QPoint p2 = scenePressPos2.toPoint();
QTest::QTouchEventSequence touchSeq = QTest::touchEvent(window, touchDevice, false);
touchSeq.press(1, p1, window).press(2, p2, window).commit();
QQuickTouchUtils::flush(window);
QVERIFY(!dragHandler1->active());
QCOMPARE(centroidChangedSpy1.count(), 1);
QCOMPARE(dragHandler1->centroid().position(), ball1Center);
QCOMPARE(dragHandler1->centroid().pressPosition(), ball1Center);
QCOMPARE(dragHandler1->centroid().scenePosition(), scenePressPos1);
QCOMPARE(dragHandler1->centroid().scenePressPosition(), scenePressPos1);
QVERIFY(!dragHandler2->active());
QCOMPARE(centroidChangedSpy2.count(), 1);
QCOMPARE(dragHandler2->centroid().position(), ball2Center);
QCOMPARE(dragHandler2->centroid().pressPosition(), ball2Center);
QCOMPARE(dragHandler2->centroid().scenePosition(), scenePressPos2);
QCOMPARE(dragHandler2->centroid().scenePressPosition(), scenePressPos2);
p1 += QPoint(dragThreshold, 0);
p2 += QPoint(0, dragThreshold);
touchSeq.move(1, p1, window).move(2, p2, window).commit();
QQuickTouchUtils::flush(window);
QVERIFY(!dragHandler1->active());
QCOMPARE(centroidChangedSpy1.count(), 2);
QCOMPARE(dragHandler1->centroid().position(), ball1Center + QPointF(dragThreshold, 0));
QCOMPARE(dragHandler1->centroid().pressPosition(), ball1Center);
QCOMPARE(dragHandler1->centroid().scenePosition().toPoint(), p1);
QCOMPARE(dragHandler1->centroid().scenePressPosition(), scenePressPos1);
QVERIFY(!dragHandler2->active());
QCOMPARE(centroidChangedSpy2.count(), 2);
QCOMPARE(dragHandler2->centroid().position(), ball2Center + QPointF(0, dragThreshold));
QCOMPARE(dragHandler2->centroid().pressPosition(), ball2Center);
QCOMPARE(dragHandler2->centroid().scenePosition().toPoint(), p2);
QCOMPARE(dragHandler2->centroid().scenePressPosition(), scenePressPos2);
p1 += QPoint(1, 0);
p2 += QPoint(0, 1);
touchSeq.move(1, p1, window).move(2, p2, window).commit();
QQuickTouchUtils::flush(window);
QTRY_VERIFY(dragHandler1->active());
QVERIFY(dragHandler2->active());
QCOMPARE(translationChangedSpy1.count(), 0);
QCOMPARE(dragHandler1->translation().x(), 0.0);
QPointF sceneGrabPos1 = p1;
QPointF sceneGrabPos2 = p2;
QCOMPARE(dragHandler1->centroid().sceneGrabPosition(), sceneGrabPos1);
QCOMPARE(dragHandler2->centroid().sceneGrabPosition(), sceneGrabPos2);
p1 += QPoint(19, 0);
p2 += QPoint(0, 19);
QVERIFY(dragHandler2->active());
QCOMPARE(translationChangedSpy2.count(), 0);
QCOMPARE(dragHandler2->translation().x(), 0.0);
QCOMPARE(dragHandler2->centroid().sceneGrabPosition(), sceneGrabPos2);
touchSeq.move(1, p1, window).move(2, p2, window).commit();
QQuickTouchUtils::flush(window);
QVERIFY(dragHandler1->active());
QVERIFY(dragHandler2->active());
QCOMPARE(dragHandler1->centroid().position(), ball1Center);
QCOMPARE(dragHandler1->centroid().pressPosition(), ball1Center);
QCOMPARE(dragHandler1->centroid().scenePosition(), ball1->mapToScene(ball1Center));
QCOMPARE(dragHandler1->centroid().scenePressPosition(), scenePressPos1);
QCOMPARE(dragHandler1->centroid().sceneGrabPosition(), sceneGrabPos1);
QCOMPARE(dragHandler1->translation().x(), dragThreshold + 20.0);
QCOMPARE(dragHandler1->translation().y(), 0.0);
QCOMPARE(dragHandler2->centroid().position(), ball2Center);
QCOMPARE(dragHandler2->centroid().pressPosition(), ball2Center);
QCOMPARE(dragHandler2->centroid().scenePosition(), ball2->mapToScene(ball2Center));
QCOMPARE(dragHandler2->centroid().scenePressPosition(), scenePressPos2);
QCOMPARE(dragHandler2->centroid().sceneGrabPosition(), sceneGrabPos2);
QCOMPARE(dragHandler2->translation().x(), 0.0);
QCOMPARE(dragHandler2->translation().y(), dragThreshold + 20.0);
touchSeq.release(1, p1, window).stationary(2).commit();
QQuickTouchUtils::flush(window);
QTRY_VERIFY(!dragHandler1->active());
QVERIFY(dragHandler2->active());
QCOMPARE(dragHandler1->centroid().pressedButtons(), Qt::NoButton);
QCOMPARE(ball1->mapToScene(ball1Center).toPoint(), p1);
QCOMPARE(translationChangedSpy1.count(), 1);
touchSeq.release(2, p2, window).commit();
QQuickTouchUtils::flush(window);
QTRY_VERIFY(!dragHandler2->active());
QCOMPARE(ball2->mapToScene(ball2Center).toPoint(), p2);
QCOMPARE(translationChangedSpy2.count(), 1);
}
void tst_DragHandler::touchDragMultiSliders_data()
{
QTest::addColumn<int>("sliderRow");
QTest::addColumn<QVector<int> >("whichSliders");
QTest::addColumn<QVector<int> >("startingCenterOffsets");
QTest::addColumn<QVector<QVector2D> >("movements");
QTest::newRow("Drag Knob: start on the knobs, drag down") <<
0 << QVector<int> { 0, 1, 2 } << QVector<int> { 0, 0, 0 } << QVector<QVector2D> { {0, 60}, {0, 60}, {0, 60} };
QTest::newRow("Drag Knob: start on the knobs, drag diagonally downward") <<
0 << QVector<int> { 0, 1, 2 } << QVector<int> { 0, 0, 0 } << QVector<QVector2D> { {20, 40}, {20, 60}, {20, 80} };
QTest::newRow("Drag Anywhere: start on the knobs, drag down") <<
1 << QVector<int> { 0, 1, 2 } << QVector<int> { 0, 0, 0 } << QVector<QVector2D> { {0, 60}, {0, 60}, {0, 60} };
QTest::newRow("Drag Anywhere: start on the knobs, drag diagonally downward") <<
1 << QVector<int> { 0, 1, 2 } << QVector<int> { 0, 0, 0 } << QVector<QVector2D> { {20, 40}, {20, 60}, {20, 80} };
// TODO these next two fail because the DragHandler grabs when a finger
// drags across it from outside, but should rather start only if it is pressed inside
// QTest::newRow("Drag Knob: start above the knobs, drag down") <<
// 0 << QVector<int> { 0, 1, 2 } << QVector<int> { -30, -30, -30 } << QVector<QVector2D> { {0, 40}, {0, 60}, {0, 80} };
// QTest::newRow("Drag Knob: start above the knobs, drag diagonally downward") <<
// 0 << QVector<int> { 0, 1, 2 } << QVector<int> { -30, -30, -30 } << QVector<QVector2D> { {20, 40}, {20, 60}, {20, 80} };
QTest::newRow("Drag Anywhere: start above the knobs, drag down") <<
1 << QVector<int> { 0, 1, 2 } << QVector<int> { -20, -30, -40 } << QVector<QVector2D> { {0, 60}, {0, 60}, {0, 60} };
QTest::newRow("Drag Anywhere: start above the knobs, drag diagonally downward") <<
1 << QVector<int> { 0, 1, 2 } << QVector<int> { -20, -30, -40 } << QVector<QVector2D> { {20, 40}, {20, 60}, {20, 80} };
}
void tst_DragHandler::touchDragMultiSliders()
{
QFETCH(int, sliderRow);
QFETCH(QVector<int>, whichSliders);
QFETCH(QVector<int>, startingCenterOffsets);
QFETCH(QVector<QVector2D>, movements);
const int moveCount = 8;
QScopedPointer<QQuickView> windowPtr;
createView(windowPtr, "multipleSliders.qml");
QQuickView * window = windowPtr.data();
QTest::QTouchEventSequence touch = QTest::touchEvent(window, touchDevice);
QQuickRepeater *rowRepeater = window->rootObject()->findChildren<QQuickRepeater *>()[sliderRow];
QVector<QQuickItem *> knobs;
QVector<QQuickDragHandler *> dragHandlers;
QVector<QQuickTapHandler *> tapHandlers;
QVector<QPointF> startPoints;
for (int sli : whichSliders) {
QQuickItem *slider = rowRepeater->itemAt(sli);
QVERIFY(slider);
dragHandlers << slider->findChild<QQuickDragHandler*>();
QVERIFY(dragHandlers[sli]);
tapHandlers << slider->findChild<QQuickTapHandler*>();
QVERIFY(tapHandlers[sli]);
knobs << tapHandlers[sli]->parentItem();
QPointF startPoint = knobs[sli]->mapToScene(knobs[sli]->clipRect().center());
startPoint.setY(startPoint.y() + startingCenterOffsets[sli]);
startPoints << startPoint;
qCDebug(lcPointerTests) << "row" << sliderRow << "slider" << sli << slider->objectName() <<
"start" << startingCenterOffsets[sli] << startPoints[sli];
}
QVector<QPointF> touchPoints = startPoints;
// Press
for (int sli : whichSliders)
touch.press(sli, touchPoints[sli].toPoint());
touch.commit();
// Moves
for (int m = 0; m < moveCount; ++m) {
for (int sli : whichSliders) {
QVector2D incr = movements[sli] / moveCount;
touchPoints[sli] += incr.toPointF();
touch.move(sli, touchPoints[sli].toPoint());
}
touch.commit();
QQuickTouchUtils::flush(window);
}
// Check that they moved to where they should: since the slider is constrained,
// only the y component should have an effect; knobs should not come out of their "grooves"
for (int sli : whichSliders) {
QPoint endPosition = knobs[sli]->mapToScene(knobs[sli]->clipRect().center()).toPoint();
QPoint expectedEndPosition(startPoints[sli].x(), startPoints[sli].y() + movements[sli].y());
if (sliderRow == 0 && qAbs(startingCenterOffsets[sli]) > knobs[sli]->height() / 2)
expectedEndPosition = startPoints[sli].toPoint();
qCDebug(lcPointerTests) << "slider " << knobs[sli]->objectName() << "started @" << startPoints[sli]
<< "tried to move by" << movements[sli] << "ended up @" << endPosition << "expected" << expectedEndPosition;
QTRY_COMPARE(endPosition, expectedEndPosition);
}
// Release
for (int sli : whichSliders)
touch.release(sli, touchPoints[sli].toPoint());
touch.commit();
}
void tst_DragHandler::touchPassiveGrabbers_data()
{
QTest::addColumn<QString>("itemName");
QTest::addColumn<QStringList>("expectedPassiveGrabberNames");
QTest::newRow("Drag And Tap") << "dragAndTap" << QStringList({"drag", "tap"});
QTest::newRow("Tap And Drag") << "tapAndDrag" << QStringList({"tap", "drag"});
QTest::newRow("Drag And Tap (not siblings)") << "dragAndTapNotSiblings" << QStringList({"drag", "tap"});
QTest::newRow("Tap And Drag (not siblings)") << "tapAndDragNotSiblings" << QStringList({"tap", "drag"});
}
void tst_DragHandler::touchPassiveGrabbers()
{
QFETCH(QString, itemName);
QFETCH(QStringList, expectedPassiveGrabberNames);
QScopedPointer<QQuickView> windowPtr;
createView(windowPtr, "simpleTapAndDragHandlers.qml");
QQuickView * window = windowPtr.data();
QQuickItem *row2 = window->rootObject()->findChild<QQuickItem*>(itemName);
QSet<QQuickPointerHandler *> expectedPassiveGrabbers;
for (QString objectName : expectedPassiveGrabberNames)
expectedPassiveGrabbers << row2->findChild<QQuickPointerHandler*>(objectName);
QPointF p1 = row2->mapToScene(row2->clipRect().center());
QTest::QTouchEventSequence touch = QTest::touchEvent(window, touchDevice);
touch.press(1, p1.toPoint()).commit();
QQuickTouchUtils::flush(window);
QCOMPARE(passiveGrabbers(window), expectedPassiveGrabbers);
QQuickDragHandler *dragHandler = nullptr;
for (QQuickPointerHandler *handler: expectedPassiveGrabbers) {
QPointF scenePressPos;
if (QQuickMultiPointHandler *mph = qmlobject_cast<QQuickMultiPointHandler *>(handler))
scenePressPos = mph->centroid().scenePressPosition();
else
scenePressPos = static_cast<QQuickSinglePointHandler *>(handler)->point().scenePressPosition();
QCOMPARE(scenePressPos, p1);
QQuickDragHandler *dh = qmlobject_cast<QQuickDragHandler *>(handler);
if (dh)
dragHandler = dh;
}
QVERIFY(dragHandler);
QPointF initialPos = dragHandler->target()->position();
p1 += QPointF(50, 50);
touch.move(1, p1.toPoint()).commit();
QQuickTouchUtils::flush(window);
QTRY_VERIFY(dragHandler->active());
p1 += QPointF(50, 50);
touch.move(1, p1.toPoint()).commit();
QQuickTouchUtils::flush(window);
QPointF movementDelta = dragHandler->target()->position() - initialPos;
qCDebug(lcPointerTests) << "DragHandler moved the target by" << movementDelta;
QVERIFY(movementDelta.x() >= 100);
QVERIFY(movementDelta.y() >= 100);
QTest::qWait(500);
touch.release(1, p1.toPoint());
touch.commit();
QQuickTouchUtils::flush(window);
}
void tst_DragHandler::touchPinchAndMouseMove()
{
QScopedPointer<QQuickView> windowPtr;
createView(windowPtr, "draghandler_and_pinchhandler.qml");
QQuickView *window = windowPtr.data();
QQuickItem *rect = window->rootObject()->findChild<QQuickItem*>(QLatin1String("Rect"));
QQuickPointerHandler *pinchHandler = window->rootObject()->findChild<QQuickPointerHandler*>(QLatin1String("PinchHandler"));
QPoint p1(150,200);
QPoint p2(250,200);
// Trigger a scale pinch, PinchHandler should activate
QTest::QTouchEventSequence touch = QTest::touchEvent(window, touchDevice);
touch.press(1, p1).press(2, p2).commit();
QQuickTouchUtils::flush(window);
QPoint delta(10,0);
for (int i = 0; i < 10 && !pinchHandler->active(); ++i) {
p1-=delta;
p2+=delta;
touch.move(1, p1).move(2, p2).commit();
QQuickTouchUtils::flush(window);
}
QCOMPARE(pinchHandler->active(), true);
// While having the touch points pressed, send wrong mouse event as MS Windows did:
// * A MoveMove with LeftButton down
// (in order to synthesize that, qtestMouseButtons needs to be modified)
// (This will make the DragHandler do a passive grab)
QTestPrivate::qtestMouseButtons = Qt::LeftButton;
QTest::mouseMove(window, p1 + delta);
touch.release(1, p1).release(2, p2).commit();
QQuickTouchUtils::flush(window);
// Now move the mouse with no buttons down and check if the rect did not move
// At this point, no touch points are pressed and no mouse buttons are pressed.
QTestPrivate::qtestMouseButtons = Qt::NoButton;
QSignalSpy rectMovedSpy(rect, SIGNAL(xChanged()));
for (int i = 0; i < 10; ++i) {
p1 += delta;
QTest::mouseMove(window, p1);
QCOMPARE(rectMovedSpy.count(), 0);
}
}
QTEST_MAIN(tst_DragHandler)
#include "tst_qquickdraghandler.moc"