blob: 5b7108d96b2a083b99f367b513cb2a44df140ea3 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <QtTest/QtTest>
#include <QtTest/QSignalSpy>
#include <QtGui/QStyleHints>
#include <qpa/qwindowsysteminterface.h>
#include <private/qquickpincharea_p.h>
#include <QtQuick/private/qquickrectangle_p.h>
#include <QtQuick/qquickview.h>
#include <QtQml/qqmlcontext.h>
#include "../../shared/util.h"
#include "../shared/viewtestutil.h"
class tst_QQuickPinchArea: public QQmlDataTest
{
Q_OBJECT
public:
tst_QQuickPinchArea() { }
private slots:
void initTestCase();
void cleanupTestCase();
void pinchProperties();
void scale();
void pan();
void retouch();
void cancel();
void transformedPinchArea_data();
void transformedPinchArea();
private:
QQuickView *createView();
QTouchDevice *device = nullptr;
};
void tst_QQuickPinchArea::initTestCase()
{
QQmlDataTest::initTestCase();
if (!device) {
device = new QTouchDevice;
device->setType(QTouchDevice::TouchScreen);
QWindowSystemInterface::registerTouchDevice(device);
}
}
void tst_QQuickPinchArea::cleanupTestCase()
{
}
void tst_QQuickPinchArea::pinchProperties()
{
QScopedPointer<QQuickView> window(createView());
window->setSource(testFileUrl("pinchproperties.qml"));
window->show();
QVERIFY(window->rootObject() != nullptr);
QQuickPinchArea *pinchArea = window->rootObject()->findChild<QQuickPinchArea*>("pincharea");
QQuickPinch *pinch = pinchArea->pinch();
QVERIFY(pinchArea != nullptr);
QVERIFY(pinch != nullptr);
// target
QQuickItem *blackRect = window->rootObject()->findChild<QQuickItem*>("blackrect");
QVERIFY(blackRect != nullptr);
QCOMPARE(blackRect, pinch->target());
QQuickItem *rootItem = qobject_cast<QQuickItem*>(window->rootObject());
QVERIFY(rootItem != nullptr);
QSignalSpy targetSpy(pinch, SIGNAL(targetChanged()));
pinch->setTarget(rootItem);
QCOMPARE(targetSpy.count(),1);
pinch->setTarget(rootItem);
QCOMPARE(targetSpy.count(),1);
// axis
QCOMPARE(pinch->axis(), QQuickPinch::XAndYAxis);
QSignalSpy axisSpy(pinch, SIGNAL(dragAxisChanged()));
pinch->setAxis(QQuickPinch::XAxis);
QCOMPARE(pinch->axis(), QQuickPinch::XAxis);
QCOMPARE(axisSpy.count(),1);
pinch->setAxis(QQuickPinch::XAxis);
QCOMPARE(axisSpy.count(),1);
// minimum and maximum drag properties
QSignalSpy xminSpy(pinch, SIGNAL(minimumXChanged()));
QSignalSpy xmaxSpy(pinch, SIGNAL(maximumXChanged()));
QSignalSpy yminSpy(pinch, SIGNAL(minimumYChanged()));
QSignalSpy ymaxSpy(pinch, SIGNAL(maximumYChanged()));
QCOMPARE(pinch->xmin(), 0.0);
QCOMPARE(pinch->xmax(), rootItem->width()-blackRect->width());
QCOMPARE(pinch->ymin(), 0.0);
QCOMPARE(pinch->ymax(), rootItem->height()-blackRect->height());
pinch->setXmin(10);
pinch->setXmax(10);
pinch->setYmin(10);
pinch->setYmax(10);
QCOMPARE(pinch->xmin(), 10.0);
QCOMPARE(pinch->xmax(), 10.0);
QCOMPARE(pinch->ymin(), 10.0);
QCOMPARE(pinch->ymax(), 10.0);
QCOMPARE(xminSpy.count(),1);
QCOMPARE(xmaxSpy.count(),1);
QCOMPARE(yminSpy.count(),1);
QCOMPARE(ymaxSpy.count(),1);
pinch->setXmin(10);
pinch->setXmax(10);
pinch->setYmin(10);
pinch->setYmax(10);
QCOMPARE(xminSpy.count(),1);
QCOMPARE(xmaxSpy.count(),1);
QCOMPARE(yminSpy.count(),1);
QCOMPARE(ymaxSpy.count(),1);
// minimum and maximum scale properties
QSignalSpy scaleMinSpy(pinch, SIGNAL(minimumScaleChanged()));
QSignalSpy scaleMaxSpy(pinch, SIGNAL(maximumScaleChanged()));
QCOMPARE(pinch->minimumScale(), 1.0);
QCOMPARE(pinch->maximumScale(), 2.0);
pinch->setMinimumScale(0.5);
pinch->setMaximumScale(1.5);
QCOMPARE(pinch->minimumScale(), 0.5);
QCOMPARE(pinch->maximumScale(), 1.5);
QCOMPARE(scaleMinSpy.count(),1);
QCOMPARE(scaleMaxSpy.count(),1);
pinch->setMinimumScale(0.5);
pinch->setMaximumScale(1.5);
QCOMPARE(scaleMinSpy.count(),1);
QCOMPARE(scaleMaxSpy.count(),1);
// minimum and maximum rotation properties
QSignalSpy rotMinSpy(pinch, SIGNAL(minimumRotationChanged()));
QSignalSpy rotMaxSpy(pinch, SIGNAL(maximumRotationChanged()));
QCOMPARE(pinch->minimumRotation(), 0.0);
QCOMPARE(pinch->maximumRotation(), 90.0);
pinch->setMinimumRotation(-90.0);
pinch->setMaximumRotation(45.0);
QCOMPARE(pinch->minimumRotation(), -90.0);
QCOMPARE(pinch->maximumRotation(), 45.0);
QCOMPARE(rotMinSpy.count(),1);
QCOMPARE(rotMaxSpy.count(),1);
pinch->setMinimumRotation(-90.0);
pinch->setMaximumRotation(45.0);
QCOMPARE(rotMinSpy.count(),1);
QCOMPARE(rotMaxSpy.count(),1);
}
QTouchEvent::TouchPoint makeTouchPoint(int id, QPoint p, QQuickView *v, QQuickItem *i)
{
QTouchEvent::TouchPoint touchPoint(id);
touchPoint.setPos(i->mapFromScene(p));
touchPoint.setScreenPos(v->mapToGlobal(p));
touchPoint.setScenePos(p);
return touchPoint;
}
void tst_QQuickPinchArea::scale()
{
QQuickView *window = createView();
QScopedPointer<QQuickView> scope(window);
window->setSource(testFileUrl("pinchproperties.qml"));
window->show();
QVERIFY(QTest::qWaitForWindowExposed(window));
QVERIFY(window->rootObject() != nullptr);
qApp->processEvents();
QQuickPinchArea *pinchArea = window->rootObject()->findChild<QQuickPinchArea*>("pincharea");
QQuickPinch *pinch = pinchArea->pinch();
QVERIFY(pinchArea != nullptr);
QVERIFY(pinch != nullptr);
QQuickItem *root = qobject_cast<QQuickItem*>(window->rootObject());
QVERIFY(root != nullptr);
// target
QQuickItem *blackRect = window->rootObject()->findChild<QQuickItem*>("blackrect");
QVERIFY(blackRect != nullptr);
QPoint p1(80, 80);
QPoint p2(100, 100);
{
QTest::QTouchEventSequence pinchSequence = QTest::touchEvent(window, device);
pinchSequence.press(0, p1, window).commit();
QQuickTouchUtils::flush(window);
// In order for the stationary point to remember its previous position,
// we have to reuse the same pinchSequence object. Otherwise if we let it
// be destroyed and then start a new sequence, point 0 will default to being
// stationary at 0, 0, and PinchArea will filter out that touchpoint because
// it is outside its bounds.
pinchSequence.stationary(0).press(1, p2, window).commit();
QQuickTouchUtils::flush(window);
p1 -= QPoint(10,10);
p2 += QPoint(10,10);
pinchSequence.move(0, p1,window).move(1, p2,window).commit();
QQuickTouchUtils::flush(window);
QCOMPARE(root->property("scale").toReal(), 1.0);
QVERIFY(root->property("pinchActive").toBool());
p1 -= QPoint(10,10);
p2 += QPoint(10,10);
pinchSequence.move(0, p1,window).move(1, p2,window).commit();
QQuickTouchUtils::flush(window);
QCOMPARE(root->property("scale").toReal(), 1.5);
QCOMPARE(root->property("center").toPointF(), QPointF(40, 40)); // blackrect is at 50,50
QCOMPARE(blackRect->scale(), 1.5);
}
// scale beyond bound
p1 -= QPoint(50,50);
p2 += QPoint(50,50);
{
QTest::QTouchEventSequence pinchSequence = QTest::touchEvent(window, device);
pinchSequence.move(0, p1, window).move(1, p2, window).commit();
QQuickTouchUtils::flush(window);
QCOMPARE(blackRect->scale(), 2.0);
pinchSequence.release(0, p1, window).release(1, p2, window).commit();
QQuickTouchUtils::flush(window);
}
QVERIFY(!root->property("pinchActive").toBool());
}
void tst_QQuickPinchArea::pan()
{
QQuickView *window = createView();
QScopedPointer<QQuickView> scope(window);
window->setSource(testFileUrl("pinchproperties.qml"));
window->show();
QVERIFY(QTest::qWaitForWindowExposed(window));
QVERIFY(window->rootObject() != nullptr);
qApp->processEvents();
QQuickPinchArea *pinchArea = window->rootObject()->findChild<QQuickPinchArea*>("pincharea");
QQuickPinch *pinch = pinchArea->pinch();
QVERIFY(pinchArea != nullptr);
QVERIFY(pinch != nullptr);
QQuickItem *root = qobject_cast<QQuickItem*>(window->rootObject());
QVERIFY(root != nullptr);
// target
QQuickItem *blackRect = window->rootObject()->findChild<QQuickItem*>("blackrect");
QVERIFY(blackRect != nullptr);
QPoint p1(80, 80);
QPoint p2(100, 100);
{
const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();
QTest::QTouchEventSequence pinchSequence = QTest::touchEvent(window, device);
pinchSequence.press(0, p1, window).commit();
QQuickTouchUtils::flush(window);
// In order for the stationary point to remember its previous position,
// we have to reuse the same pinchSequence object.
pinchSequence.stationary(0).press(1, p2, window).commit();
QQuickTouchUtils::flush(window);
QVERIFY(!root->property("pinchActive").toBool());
QCOMPARE(root->property("scale").toReal(), -1.0);
p1 += QPoint(dragThreshold - 1, 0);
p2 += QPoint(dragThreshold - 1, 0);
pinchSequence.move(0, p1, window).move(1, p2, window).commit();
QQuickTouchUtils::flush(window);
// movement < dragThreshold: pinch not yet active
QVERIFY(!root->property("pinchActive").toBool());
QCOMPARE(root->property("scale").toReal(), -1.0);
// exactly the dragThreshold: pinch starts
p1 += QPoint(1, 0);
p2 += QPoint(1, 0);
pinchSequence.move(0, p1, window).move(1, p2, window).commit();
QQuickTouchUtils::flush(window);
QVERIFY(root->property("pinchActive").toBool());
QCOMPARE(root->property("scale").toReal(), 1.0);
// Calculation of the center point is tricky at first:
// center point of the two touch points in item coordinates:
// scene coordinates: (80, 80) + (dragThreshold, 0), (100, 100) + (dragThreshold, 0)
// = ((180+dT)/2, 180/2) = (90+dT, 90)
// item coordinates: (scene) - (50, 50) = (40+dT, 40)
QCOMPARE(root->property("center").toPointF(), QPointF(40 + dragThreshold, 40));
// pan started, but no actual movement registered yet:
// blackrect starts at 50,50
QCOMPARE(blackRect->x(), 50.0);
QCOMPARE(blackRect->y(), 50.0);
p1 += QPoint(10, 0);
p2 += QPoint(10, 0);
pinchSequence.move(0, p1, window).move(1, p2, window).commit();
QQuickTouchUtils::flush(window);
QCOMPARE(root->property("center").toPointF(), QPointF(40 + 10 + dragThreshold, 40));
QCOMPARE(blackRect->x(), 60.0);
QCOMPARE(blackRect->y(), 50.0);
p1 += QPoint(0, 10);
p2 += QPoint(0, 10);
pinchSequence.move(0, p1, window).move(1, p2, window).commit();
QQuickTouchUtils::flush(window);
// next big surprise: the center is in item local coordinates and the item was just
// moved 10 to the right... which offsets the center point 10 to the left
QCOMPARE(root->property("center").toPointF(), QPointF(40 + 10 - 10 + dragThreshold, 40 + 10));
QCOMPARE(blackRect->x(), 60.0);
QCOMPARE(blackRect->y(), 60.0);
p1 += QPoint(10, 10);
p2 += QPoint(10, 10);
pinchSequence.move(0, p1, window).move(1, p2, window).commit();
QQuickTouchUtils::flush(window);
// now the item moved again, thus the center point of the touch is moved in total by (10, 10)
QCOMPARE(root->property("center").toPointF(), QPointF(50 + dragThreshold, 50));
QCOMPARE(blackRect->x(), 70.0);
QCOMPARE(blackRect->y(), 70.0);
}
// pan x beyond bound
p1 += QPoint(100,100);
p2 += QPoint(100,100);
QTest::touchEvent(window, device).move(0, p1, window).move(1, p2, window);
QQuickTouchUtils::flush(window);
QCOMPARE(blackRect->x(), 140.0);
QCOMPARE(blackRect->y(), 170.0);
QTest::touchEvent(window, device).release(0, p1, window).release(1, p2, window);
QQuickTouchUtils::flush(window);
QVERIFY(!root->property("pinchActive").toBool());
}
// test pinch, release one point, touch again to continue pinch
void tst_QQuickPinchArea::retouch()
{
QQuickView *window = createView();
QScopedPointer<QQuickView> scope(window);
window->setSource(testFileUrl("pinchproperties.qml"));
window->show();
QVERIFY(QTest::qWaitForWindowExposed(window));
QVERIFY(window->rootObject() != nullptr);
qApp->processEvents();
QQuickPinchArea *pinchArea = window->rootObject()->findChild<QQuickPinchArea*>("pincharea");
QQuickPinch *pinch = pinchArea->pinch();
QVERIFY(pinchArea != nullptr);
QVERIFY(pinch != nullptr);
QQuickItem *root = qobject_cast<QQuickItem*>(window->rootObject());
QVERIFY(root != nullptr);
QSignalSpy startedSpy(pinchArea, SIGNAL(pinchStarted(QQuickPinchEvent*)));
QSignalSpy finishedSpy(pinchArea, SIGNAL(pinchFinished(QQuickPinchEvent*)));
// target
QQuickItem *blackRect = window->rootObject()->findChild<QQuickItem*>("blackrect");
QVERIFY(blackRect != nullptr);
QPoint p1(80, 80);
QPoint p2(100, 100);
{
QTest::QTouchEventSequence pinchSequence = QTest::touchEvent(window, device);
pinchSequence.press(0, p1, window).commit();
QQuickTouchUtils::flush(window);
// In order for the stationary point to remember its previous position,
// we have to reuse the same pinchSequence object.
pinchSequence.stationary(0).press(1, p2, window).commit();
QQuickTouchUtils::flush(window);
p1 -= QPoint(10,10);
p2 += QPoint(10,10);
pinchSequence.move(0, p1,window).move(1, p2,window).commit();
QQuickTouchUtils::flush(window);
QCOMPARE(root->property("scale").toReal(), 1.0);
QVERIFY(root->property("pinchActive").toBool());
p1 -= QPoint(10,10);
p2 += QPoint(10,10);
pinchSequence.move(0, p1,window).move(1, p2,window).commit();
QQuickTouchUtils::flush(window);
QCOMPARE(startedSpy.count(), 1);
QCOMPARE(root->property("scale").toReal(), 1.5);
QCOMPARE(root->property("center").toPointF(), QPointF(40, 40)); // blackrect is at 50,50
QCOMPARE(blackRect->scale(), 1.5);
QCOMPARE(window->rootObject()->property("pointCount").toInt(), 2);
QCOMPARE(startedSpy.count(), 1);
QCOMPARE(finishedSpy.count(), 0);
// Hold down the first finger but release the second one
pinchSequence.stationary(0).release(1, p2, window).commit();
QQuickTouchUtils::flush(window);
QCOMPARE(startedSpy.count(), 1);
QCOMPARE(finishedSpy.count(), 0);
QCOMPARE(window->rootObject()->property("pointCount").toInt(), 1);
// Keep holding down the first finger and re-touch the second one, then move them both
pinchSequence.stationary(0).press(1, p2, window).commit();
QQuickTouchUtils::flush(window);
p1 -= QPoint(10,10);
p2 += QPoint(10,10);
pinchSequence.move(0, p1, window).move(1, p2, window).commit();
QQuickTouchUtils::flush(window);
// Lifting and retouching results in onPinchStarted being called again
QCOMPARE(startedSpy.count(), 2);
QCOMPARE(finishedSpy.count(), 0);
QCOMPARE(window->rootObject()->property("pointCount").toInt(), 2);
pinchSequence.release(0, p1, window).release(1, p2, window).commit();
QQuickTouchUtils::flush(window);
QVERIFY(!root->property("pinchActive").toBool());
QCOMPARE(startedSpy.count(), 2);
QCOMPARE(finishedSpy.count(), 1);
}
}
void tst_QQuickPinchArea::cancel()
{
QQuickView *window = createView();
QScopedPointer<QQuickView> scope(window);
window->setSource(testFileUrl("pinchproperties.qml"));
window->show();
QVERIFY(QTest::qWaitForWindowExposed(window));
QVERIFY(window->rootObject() != nullptr);
qApp->processEvents();
QQuickPinchArea *pinchArea = window->rootObject()->findChild<QQuickPinchArea*>("pincharea");
QQuickPinch *pinch = pinchArea->pinch();
QVERIFY(pinchArea != nullptr);
QVERIFY(pinch != nullptr);
QQuickItem *root = qobject_cast<QQuickItem*>(window->rootObject());
QVERIFY(root != nullptr);
// target
QQuickItem *blackRect = window->rootObject()->findChild<QQuickItem*>("blackrect");
QVERIFY(blackRect != nullptr);
QPoint p1(80, 80);
QPoint p2(100, 100);
{
QTest::QTouchEventSequence pinchSequence = QTest::touchEvent(window, device);
pinchSequence.press(0, p1, window).commit();
QQuickTouchUtils::flush(window);
// In order for the stationary point to remember its previous position,
// we have to reuse the same pinchSequence object. Otherwise if we let it
// be destroyed and then start a new sequence, point 0 will default to being
// stationary at 0, 0, and PinchArea will filter out that touchpoint because
// it is outside its bounds.
pinchSequence.stationary(0).press(1, p2, window).commit();
QQuickTouchUtils::flush(window);
p1 -= QPoint(10,10);
p2 += QPoint(10,10);
pinchSequence.move(0, p1,window).move(1, p2,window).commit();
QQuickTouchUtils::flush(window);
QCOMPARE(root->property("scale").toReal(), 1.0);
QVERIFY(root->property("pinchActive").toBool());
p1 -= QPoint(10,10);
p2 += QPoint(10,10);
pinchSequence.move(0, p1,window).move(1, p2,window).commit();
QQuickTouchUtils::flush(window);
QCOMPARE(root->property("scale").toReal(), 1.5);
QCOMPARE(root->property("center").toPointF(), QPointF(40, 40)); // blackrect is at 50,50
QCOMPARE(blackRect->scale(), 1.5);
QTouchEvent cancelEvent(QEvent::TouchCancel);
cancelEvent.setDevice(device);
QCoreApplication::sendEvent(window, &cancelEvent);
QQuickTouchUtils::flush(window);
QCOMPARE(root->property("scale").toReal(), 1.0);
QCOMPARE(root->property("center").toPointF(), QPointF(40, 40)); // blackrect is at 50,50
QCOMPARE(blackRect->scale(), 1.0);
QVERIFY(!root->property("pinchActive").toBool());
}
}
void tst_QQuickPinchArea::transformedPinchArea_data()
{
QTest::addColumn<QPoint>("p1");
QTest::addColumn<QPoint>("p2");
QTest::addColumn<bool>("shouldPinch");
QTest::newRow("checking inner pinch 1")
<< QPoint(200, 140) << QPoint(200, 260) << true;
QTest::newRow("checking inner pinch 2")
<< QPoint(140, 200) << QPoint(200, 140) << true;
QTest::newRow("checking inner pinch 3")
<< QPoint(140, 200) << QPoint(260, 200) << true;
QTest::newRow("checking outer pinch 1")
<< QPoint(140, 140) << QPoint(260, 260) << false;
QTest::newRow("checking outer pinch 2")
<< QPoint(140, 140) << QPoint(200, 200) << false;
QTest::newRow("checking outer pinch 3")
<< QPoint(140, 260) << QPoint(260, 260) << false;
}
void tst_QQuickPinchArea::transformedPinchArea()
{
QFETCH(QPoint, p1);
QFETCH(QPoint, p2);
QFETCH(bool, shouldPinch);
QQuickView *view = createView();
QScopedPointer<QQuickView> scope(view);
view->setSource(testFileUrl("transformedPinchArea.qml"));
view->show();
QVERIFY(QTest::qWaitForWindowExposed(view));
QVERIFY(view->rootObject() != nullptr);
qApp->processEvents();
QQuickPinchArea *pinchArea = view->rootObject()->findChild<QQuickPinchArea*>("pinchArea");
QVERIFY(pinchArea != nullptr);
const int threshold = qApp->styleHints()->startDragDistance();
{
QTest::QTouchEventSequence pinchSequence = QTest::touchEvent(view, device);
// start pinch
pinchSequence.press(0, p1, view).commit();
QQuickTouchUtils::flush(view);
// In order for the stationary point to remember its previous position,
// we have to reuse the same pinchSequence object.
pinchSequence.stationary(0).press(1, p2, view).commit();
QQuickTouchUtils::flush(view);
pinchSequence.stationary(0).move(1, p2 + QPoint(threshold * 2, 0), view).commit();
QQuickTouchUtils::flush(view);
QCOMPARE(pinchArea->property("pinching").toBool(), shouldPinch);
// release pinch
pinchSequence.release(0, p1, view).release(1, p2, view).commit();
QQuickTouchUtils::flush(view);
QCOMPARE(pinchArea->property("pinching").toBool(), false);
}
}
QQuickView *tst_QQuickPinchArea::createView()
{
QQuickView *window = new QQuickView(nullptr);
window->setGeometry(0,0,240,320);
return window;
}
QTEST_MAIN(tst_QQuickPinchArea)
#include "tst_qquickpincharea.moc"