@@ -16,26 +16,42 @@
1616
1717#include <QtTest/QtTest>
1818#include <QtCore/QObject>
19−#include <qpa/qwindowsysteminterface.h>
2019#include <QtQuick/QQuickView>
2120#include <QtQml/QQmlEngine>
21+#include <QPointer>
2222
2323#include <DirectionalDragArea.h>
2424
25−class FakeTimer : public UbuntuGestures::AbstractTimer
25+#include "GestureTest.h"
26+
27+using namespace UbuntuGestures;
28+
29+class FakeTimer : public AbstractTimer
2630{
2731 Q_OBJECT
2832public:
29− FakeTimer(QObject *parent = 0)
30− : UbuntuGestures::AbstractTimer(parent)
33+ FakeTimer(const SharedTimeSource &timeSource, QObject *parent = 0)
34+ : UbuntuGestures::AbstractTimer(parent),
35+ m_timeSource(timeSource)
3136 {}
3237
33− virtual int interval() const { return m_duration; }
34− virtual void setInterval(int msecs) { m_duration = msecs; }
38+ int interval() const override { return m_interval; }
39+ void setInterval(int msecs) override { m_interval = msecs; }
40+ void start() override {
41+ AbstractTimer::start();
42+ m_nextTimeoutTime = m_timeSource->msecsSinceReference() + (qint64)m_interval;
43+ }
44+
45+ void emitTimeout() {
46+ m_nextTimeoutTime += m_interval;
47+ Q_EMIT timeout();
48+ }
3549
36− void emitTimeout() {Q_EMIT timeout();}
50+ qint64 nextTimeoutTime() const { return m_nextTimeoutTime; }
3751private:
38− int m_duration;
52+ int m_interval;
53+ SharedTimeSource m_timeSource;
54+ qint64 m_nextTimeoutTime;
3955};
4056
4157class FakeTimeSource : public UbuntuGestures::TimeSource
@@ -46,15 +62,12 @@ public:
4662 qint64 m_msecsSinceReference;
4763};
4864
49−class tst_DirectionalDragArea: public QObject
65+class tst_DirectionalDragArea: public GestureTest
5066{
5167 Q_OBJECT
5268public:
53− tst_DirectionalDragArea() : device(0) { }
69+ tst_DirectionalDragArea();
5470private Q_SLOTS:
55− void initTestCase(); // will be called before the first test function is executed
56− void cleanupTestCase(); // will be called after the last test function was executed.
57−
5871 void init(); // called right before each and every test function is executed
5972 void cleanup(); // called right after each and every test function is executed
6073
@@ -67,64 +80,54 @@ private Q_SLOTS:
6780 void maxSilenceTime();
6881 void sceneXAndX();
6982 void sceneYAndY();
70− void emitDraggingChangedOnDirectRecognition();
83+ void twoFingerTap();
84+ void movingDDA();
85+ void ignoreOldFinger();
7186
7287private:
73− QQuickView *createView();
74− QQuickView *view;
75− QTouchDevice *device;
88+ void passTime(qint64 timeSpan);
7689 FakeTimer *fakeTimer;
77− FakeTimeSource *fakeTimeSource;
90+ QSharedPointer<FakeTimeSource> fakeTimeSource;
7891};
7992
80−void tst_DirectionalDragArea::initTestCase()
81−{
82− if (!device) {
83− device = new QTouchDevice;
84− device->setType(QTouchDevice::TouchScreen);
85− QWindowSystemInterface::registerTouchDevice(device);
86− }
87−
88− view = 0;
89−}
90−
91−void tst_DirectionalDragArea::cleanupTestCase()
93+tst_DirectionalDragArea::tst_DirectionalDragArea()
94+ : GestureTest(QStringLiteral("tst_DirectionalDragArea.qml"))
9295{
9396}
9497
9598void tst_DirectionalDragArea::init()
9699{
97− view = createView();
98− view->setSource(QUrl::fromLocalFile("edgeDragExample.qml"));
99− view->show();
100− QVERIFY(QTest::qWaitForWindowExposed(view));
101− QVERIFY(view->rootObject() != 0);
102− qApp->processEvents();
103−
104− fakeTimer = new FakeTimer;
105− fakeTimeSource = new FakeTimeSource;
100+ GestureTest::init();
101+
102+ fakeTimeSource.reset(new FakeTimeSource);
103+ fakeTimer = new FakeTimer(fakeTimeSource);
106104}
107105
108106void tst_DirectionalDragArea::cleanup()
109107{
110− delete view;
111− view = 0;
112−
113108 delete fakeTimer;
114109 fakeTimer = 0;
115110
116− delete fakeTimeSource;
117− fakeTimeSource = 0;
111+ fakeTimeSource.reset();
112+
113+ GestureTest::cleanup();
118114}
119115
120−QQuickView *tst_DirectionalDragArea::createView()
116+void tst_DirectionalDragArea::passTime(qint64 timeSpan)
121117{
122− QQuickView *window = new QQuickView(0);
123− window->setResizeMode(QQuickView::SizeRootObjectToView);
124− window->resize(600, 600);
125− window->engine()->addImportPath(QLatin1String(UBUNTU_GESTURES_PLUGIN_DIR));
118+ qint64 finalTime = fakeTimeSource->m_msecsSinceReference + timeSpan;
119+
120+ if (fakeTimer->isRunning() && finalTime >= fakeTimer->nextTimeoutTime()) {
121+ fakeTimeSource->m_msecsSinceReference = fakeTimer->nextTimeoutTime();
122+ fakeTimer->emitTimeout();
126123
127− return window;
124+ qint64 timeSpanRemainder = finalTime - fakeTimeSource->m_msecsSinceReference;
125+ if (timeSpanRemainder > 0) {
126+ passTime(timeSpanRemainder);
127+ }
128+ } else {
129+ fakeTimeSource->m_msecsSinceReference = finalTime;
130+ }
128131}
129132
130133namespace {
@@ -180,25 +183,28 @@ void tst_DirectionalDragArea::edgeDrag()
180183 QFETCH(QString, dragAreaObjectName);
181184 QFETCH(qreal, wideningAngleMultiplier);
182185 QFETCH(qreal, dragDistanceFactor);
183− QFETCH(int, expectedGestureRecognition);
186+ QFETCH(bool, expectGestureRecognition);
184187
185188 DirectionalDragArea *edgeDragArea =
186− view->rootObject()->findChild<DirectionalDragArea*>(dragAreaObjectName);
189+ m_view->rootObject()->findChild<DirectionalDragArea*>(dragAreaObjectName);
187190 QVERIFY(edgeDragArea != 0);
188191 edgeDragArea->setRecognitionTimer(fakeTimer);
189192 edgeDragArea->setTimeSource(fakeTimeSource);
190193
191194 QSignalSpy draggingSpy(edgeDragArea, SIGNAL(draggingChanged(bool)));
192195
193− QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea, view);
196+ QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea, m_view);
194197 QPointF touchPoint = initialTouchPos;
195198
196199 qreal desiredDragDistance = edgeDragArea->distanceThreshold()*dragDistanceFactor;
197200 QPointF dragDirectionVector = calculateDirectionVector(edgeDragArea,
198201 wideningAngleMultiplier);
199− QPointF touchMovement = dragDirectionVector * (edgeDragArea->distanceThreshold() * 0.1f);
202+ qreal movementStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
203+ QPointF touchMovement = dragDirectionVector * movementStepDistance;
204+ int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
205+ int movementTimeStepMs = (edgeDragArea->compositionTime() * 1.5f) / totalMovementSteps;
200206
201− QTest::touchEvent(view, device).press(0, touchPoint.toPoint());
207+ QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
202208
203209 QCOMPARE(draggingSpy.count(), 1);
204210 QCOMPARE(edgeDragArea->dragging(), true);
@@ -208,23 +214,24 @@ void tst_DirectionalDragArea::edgeDrag()
208214 // in order to make it easier to leave it by dragging at an angle
209215 // slightly bigger than the widening angle
210216 touchPoint += createTouchDeviation(edgeDragArea);
211− QTest::touchEvent(view, device).move(0, touchPoint.toPoint());
217+ QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
212218 }
213219
214− do {
220+ for (int i = 0; i < totalMovementSteps; ++i) {
215221 touchPoint += touchMovement;
216− QTest::touchEvent(view, device).move(0, touchPoint.toPoint());
217− } while ((touchPoint - initialTouchPos).manhattanLength() < desiredDragDistance);
222+ passTime(movementTimeStepMs);
223+ QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
224+ }
218225
219− if (expectedGestureRecognition)
226+ if (expectGestureRecognition)
220227 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
221228
222− if (edgeDragArea->status() == DirectionalDragArea::Rejected) {
229+ if (edgeDragArea->status() == DirectionalDragArea::WaitingForTouch) {
223230 QCOMPARE(edgeDragArea->dragging(), false);
224231 QCOMPARE(draggingSpy.count(), 2);
225232 }
226233
227− QTest::touchEvent(view, device).release(0, touchPoint.toPoint());
234+ QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
228235
229236 QCOMPARE(draggingSpy.count(), 2);
230237 QCOMPARE(edgeDragArea->dragging(), false);
@@ -235,49 +242,49 @@ void tst_DirectionalDragArea::edgeDrag_data()
235242 QTest::addColumn<QString>("dragAreaObjectName");
236243 QTest::addColumn<qreal>("wideningAngleMultiplier");
237244 QTest::addColumn<qreal>("dragDistanceFactor");
238− QTest::addColumn<int>("expectedGestureRecognition");
245+ QTest::addColumn<bool>("expectGestureRecognition");
239246
240247 QTest::newRow("rightwards, tiny drag")
241− << "hpDragArea" << 0.0 << 0.2 << 0;
248+ << "hpDragArea" << 0.0 << 0.2 << false;
242249
243250 QTest::newRow("rightwards, straight drag")
244− << "hpDragArea" << 0.0 << 3.0 << 1;
251+ << "hpDragArea" << 0.0 << 3.0 << true;
245252
246253 QTest::newRow("rightwards, diagonal drag")
247− << "hpDragArea" << 0.9 << 3.0 << 1;
254+ << "hpDragArea" << 0.9 << 3.0 << true;
248255
249256 QTest::newRow("rightwards, overly diagonal drag")
250− << "hpDragArea" << 2.0 << 3.0 << 0;
257+ << "hpDragArea" << 2.0 << 3.0 << false;
251258
252259 QTest::newRow("leftwards, tiny drag")
253− << "hnDragArea" << 0.0 << 0.2 << 0;
260+ << "hnDragArea" << 0.0 << 0.2 << false;
254261
255262 QTest::newRow("leftwards, straight drag")
256− << "hnDragArea" << 0.0 << 3.0 << 1;
263+ << "hnDragArea" << 0.0 << 3.0 << true;
257264
258265 QTest::newRow("leftwards, diagonal drag")
259− << "hnDragArea" << 0.9 << 3.0 << 1;
266+ << "hnDragArea" << 0.9 << 3.0 << true;
260267
261268 QTest::newRow("downwards, tiny drag")
262− << "vpDragArea" << 0.0 << 0.2 << 0;
269+ << "vpDragArea" << 0.0 << 0.2 << false;
263270
264271 QTest::newRow("downwards, straight drag")
265− << "vpDragArea" << 0.0 << 3.0 << 1;
272+ << "vpDragArea" << 0.0 << 3.0 << true;
266273
267274 QTest::newRow("downwards, diagonal drag")
268− << "vpDragArea" << 0.9 << 3.0 << 1;
275+ << "vpDragArea" << 0.9 << 3.0 << true;
269276
270277 QTest::newRow("upwards, tiny drag")
271− << "vnDragArea" << 0.0 << 0.2 << 0;
278+ << "vnDragArea" << 0.0 << 0.2 << false;
272279
273280 QTest::newRow("upwards, straight drag")
274− << "vnDragArea" << 0.0 << 3.0 << 1;
281+ << "vnDragArea" << 0.0 << 3.0 << true;
275282
276283 QTest::newRow("upwards, diagonal drag")
277− << "vnDragArea" << 0.9 << 3.0 << 1;
284+ << "vnDragArea" << 0.9 << 3.0 << true;
278285
279286 QTest::newRow("upwards, overly diagonal drag")
280− << "vnDragArea" << 2.0 << 3.0 << 0;
287+ << "vnDragArea" << 2.0 << 3.0 << false;
281288}
282289
283290/*
@@ -288,39 +295,47 @@ void tst_DirectionalDragArea::edgeDrag_data()
288295void tst_DirectionalDragArea::dragWithShortDirectionChange()
289296{
290297 DirectionalDragArea *edgeDragArea =
291− view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
298+ m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
292299 QVERIFY(edgeDragArea != 0);
293300 edgeDragArea->setRecognitionTimer(fakeTimer);
294301 edgeDragArea->setTimeSource(fakeTimeSource);
295302
296− QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea, view);
303+ QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea, m_view);
297304 QPointF touchPoint = initialTouchPos;
298305
299306 qreal desiredDragDistance = edgeDragArea->distanceThreshold()*2.0;
300307 QPointF dragDirectionVector(1.0, 0.0);
301− QPointF touchMovement = dragDirectionVector * (edgeDragArea->distanceThreshold() * 0.1f);
308+ qreal touchStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
309+ // make sure we are above minimum speed
310+ int touchStepTimeMs = (touchStepDistance / (edgeDragArea->minSpeed() * 5.0f)) * 1000.0f;
311+ QPointF touchMovement = dragDirectionVector * touchStepDistance;
302312
303− QTest::touchEvent(view, device).press(0, touchPoint.toPoint());
313+ QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
304314
305315 // Move a bit in the proper direction
306316 for (int i=0; i < 3; ++i) {
307317 touchPoint += touchMovement;
308− QTest::touchEvent(view, device).move(0, touchPoint.toPoint());
318+ passTime(touchStepTimeMs);
319+ QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
309320 }
310321
311322 // Then a sudden and small movement to the opposite direction
312− touchPoint -= dragDirectionVector * (edgeDragArea->maxDeviation() * 0.7);
313− QTest::touchEvent(view, device).move(0, touchPoint.toPoint());
323+ touchPoint -= touchMovement*0.2;
324+ passTime(touchStepTimeMs*0.2);
325+ QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
314326
315− // And then resume movment in the correct direction until it crosses the distance threshold.
327+ // And then resume movment in the correct direction until it crosses the distance and time
328+ // thresholds.
316329 do {
317330 touchPoint += touchMovement;
318− QTest::touchEvent(view, device).move(0, touchPoint.toPoint());
319− } while ((touchPoint - initialTouchPos).manhattanLength() < desiredDragDistance);
331+ passTime(touchStepTimeMs);
332+ QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
333+ } while ((touchPoint - initialTouchPos).manhattanLength() < desiredDragDistance
334+ || fakeTimeSource->m_msecsSinceReference < (edgeDragArea->compositionTime() * 1.5f));
320335
321336 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
322337
323− QTest::touchEvent(view, device).release(0, touchPoint.toPoint());
338+ QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
324339}
325340
326341/*
@@ -329,53 +344,50 @@ void tst_DirectionalDragArea::dragWithShortDirectionChange()
329344 */
330345void tst_DirectionalDragArea::minSpeed()
331346{
332− QFETCH(int, minSpeedMsecsDeviation);
347+ QFETCH(qreal, minSpeed);
348+ QFETCH(qreal, speed);
333349 QFETCH(int, expectedStatusAfterSpeedCheck);
334350
335351 DirectionalDragArea *edgeDragArea =
336− view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
352+ m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
337353 QVERIFY(edgeDragArea != 0);
338354 edgeDragArea->setRecognitionTimer(fakeTimer);
339355 edgeDragArea->setTimeSource(fakeTimeSource);
340356
341− QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea, view);
357+ // A really long, unattainable, number. We don't want it getting recognized before
358+ // the speed checks we want have been performed
359+ edgeDragArea->setDistanceThreshold(500000);
360+
361+ edgeDragArea->setMinSpeed(minSpeed);
362+
363+ QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea, m_view);
342364 QPointF touchPoint = initialTouchPos;
343365
344366 QPointF dragDirectionVector(1.0, 0.0);
345− qreal distanceStep = edgeDragArea->distanceThreshold() * 0.1f;
367+ qint64 timeStepMsecs = 10;
368+ qreal distanceStep = (speed / 1000.0f) * timeStepMsecs;
346369 QPointF touchMovement = dragDirectionVector * distanceStep;
347− qreal minSpeedMsecs = edgeDragArea->minSpeed() / 1000.0;
348− qint64 timeStepMsecs = qFloor(distanceStep / minSpeedMsecs) + minSpeedMsecsDeviation;
349370
350− // if it fails it means the params set in the QML file are not in harmony with the values
351− // used in this test
352− Q_ASSERT(timeStepMsecs > 0);
371+ QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
353372
354− fakeTimeSource->m_msecsSinceReference = 0;
355− QTest::touchEvent(view, device).press(0, touchPoint.toPoint());
356−
357− // Move a bit in the proper direction
358− for (int i=0; i < 4; ++i) {
373+ // Move for a while to ensure our speed check is performed a couple of times
374+ for (int i=0; i < 20; ++i) {
359375 touchPoint += touchMovement;
360− fakeTimeSource->m_msecsSinceReference += timeStepMsecs;
361− QTest::touchEvent(view, device).move(0, touchPoint.toPoint());
376+ passTime(timeStepMsecs);
377+ QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
362378 }
363379
364− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
365−
366− // Force the periodic speed check.
367− fakeTimer->emitTimeout();
368−
369380 QCOMPARE((int)edgeDragArea->status(), expectedStatusAfterSpeedCheck);
370381}
371382
372383void tst_DirectionalDragArea::minSpeed_data()
373384{
374− QTest::addColumn<int>("minSpeedMsecsDeviation");
385+ QTest::addColumn<qreal>("minSpeed");
386+ QTest::addColumn<qreal>("speed");
375387 QTest::addColumn<int>("expectedStatusAfterSpeedCheck");
376388
377− QTest::newRow("slower than minSpeed") << 20 << (int)DirectionalDragArea::Rejected;
378− QTest::newRow("faster than minSpeed") << -20 << (int)DirectionalDragArea::Undecided;
389+ QTest::newRow("slower than minSpeed") << 100.0 << 50.0 << (int)DirectionalDragArea::WaitingForTouch;
390+ QTest::newRow("faster than minSpeed") << 100.0 << 150.0 << (int)DirectionalDragArea::Undecided;
379391}
380392
381393/*
@@ -386,37 +398,42 @@ void tst_DirectionalDragArea::minSpeed_data()
386398void tst_DirectionalDragArea::recognitionTimerUsage()
387399{
388400 DirectionalDragArea *edgeDragArea =
389− view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
401+ m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
390402 QVERIFY(edgeDragArea != 0);
391403 edgeDragArea->setRecognitionTimer(fakeTimer);
392404 edgeDragArea->setTimeSource(fakeTimeSource);
393405
394− QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea, view);
406+ // don't let it interfere with our test
407+ edgeDragArea->setMinSpeed(0.0);
408+
409+ int timeStepMs = 5; // some arbitrary small value.
410+
411+ QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea, m_view);
395412 QPointF touchPoint = initialTouchPos;
396413
397414 QPointF dragDirectionVector(1.0, 0.0);
398415 QPointF touchMovement = dragDirectionVector * (edgeDragArea->distanceThreshold() * 0.2f);
399416
417+ QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
400418 QVERIFY(!fakeTimer->isRunning());
401419
402− QTest::touchEvent(view, device).press(0, touchPoint.toPoint());
420+ QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
403421
422+ QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
404423 QVERIFY(fakeTimer->isRunning());
405424
406− // Move a bit in the proper direction
407− for (int i=0; i < 3; ++i) {
408− touchPoint += touchMovement;
409− QTest::touchEvent(view, device).move(0, touchPoint.toPoint());
410− }
425+ // Move beyond distance threshold and composition time to ensure recognition
426+ while (fakeTimeSource->m_msecsSinceReference <= edgeDragArea->compositionTime() ||
427+ (touchPoint - initialTouchPos).manhattanLength() <= edgeDragArea->distanceThreshold()) {
411428
412− QVERIFY(fakeTimer->isRunning());
429+ QCOMPARE(edgeDragArea->status() == DirectionalDragArea::Undecided, fakeTimer->isRunning());
413430
414− // Move beyond distance threshold
415− touchPoint += 3*touchMovement;
416− QTest::touchEvent(view, device).move(0, touchPoint.toPoint());
431+ touchPoint += touchMovement;
432+ passTime(timeStepMs);
433+ QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
434+ }
417435
418436 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
419−
420437 QVERIFY(!fakeTimer->isRunning());
421438}
422439
@@ -427,34 +444,28 @@ void tst_DirectionalDragArea::recognitionTimerUsage()
427444void tst_DirectionalDragArea::maxSilenceTime()
428445{
429446 DirectionalDragArea *edgeDragArea =
430− view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
447+ m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
431448 QVERIFY(edgeDragArea != 0);
432449 edgeDragArea->setRecognitionTimer(fakeTimer);
433450 edgeDragArea->setTimeSource(fakeTimeSource);
434451
435− QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea, view);
452+ // Make sure this property is not disabled
453+ edgeDragArea->setMaxSilenceTime(100);
454+
455+ QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea, m_view);
436456 QPointF touchPoint = initialTouchPos;
437457
438− fakeTimeSource->m_msecsSinceReference = 0;
439− QTest::touchEvent(view, device).press(0, touchPoint.toPoint());
458+ QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
440459
441460 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
461+ QVERIFY(fakeTimer->isRunning());
442462
443− int neededTimeouts = qCeil((qreal)edgeDragArea->maxSilenceTime() / (qreal)fakeTimer->interval());
444− if ((edgeDragArea->maxSilenceTime() % fakeTimer->interval()) == 0) {
445− ++neededTimeouts;
446− }
447−
448− // Force the periodic speed check.
449− for (int i = 0; i < neededTimeouts; ++i) {
450− fakeTimer->emitTimeout();
451−
452− if (i < neededTimeouts - 1) {
453− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
454− }
463+ // Force timer to timeout until after maxSilenceTime has been reached
464+ while (fakeTimeSource->m_msecsSinceReference < edgeDragArea->maxSilenceTime()) {
465+ passTime(fakeTimer->interval());
455466 }
456467
457− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Rejected);
468+ QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
458469}
459470
460471/*
@@ -464,21 +475,20 @@ void tst_DirectionalDragArea::maxSilenceTime()
464475void tst_DirectionalDragArea::sceneXAndX()
465476{
466477 DirectionalDragArea *edgeDragArea =
467− view->rootObject()->findChild<DirectionalDragArea*>("hnDragArea");
478+ m_view->rootObject()->findChild<DirectionalDragArea*>("hnDragArea");
468479 QVERIFY(edgeDragArea != 0);
469480 edgeDragArea->setRecognitionTimer(fakeTimer);
470481 edgeDragArea->setTimeSource(fakeTimeSource);
471482
472− QPointF touchScenePos(view->width() - (edgeDragArea->width()/2.0f), view->height()/2.0f);
483+ QPointF touchScenePos(m_view->width() - (edgeDragArea->width()/2.0f), m_view->height()/2.0f);
473484
474− fakeTimeSource->m_msecsSinceReference = 0;
475− QTest::touchEvent(view, device).press(0, touchScenePos.toPoint());
485+ QTest::touchEvent(m_view, m_device).press(0, touchScenePos.toPoint());
476486
477487 QSignalSpy touchXSpy(edgeDragArea, SIGNAL(touchXChanged(qreal)));
478488 QSignalSpy touchSceneXSpy(edgeDragArea, SIGNAL(touchSceneXChanged(qreal)));
479489
480− touchScenePos.rx() = view->width() / 2;
481− QTest::touchEvent(view, device).move(0, touchScenePos.toPoint());
490+ touchScenePos.rx() = m_view->width() / 2;
491+ QTest::touchEvent(m_view, m_device).move(0, touchScenePos.toPoint());
482492
483493 QCOMPARE(touchXSpy.count(), 1);
484494 QCOMPARE(touchSceneXSpy.count(), 1);
@@ -493,21 +503,20 @@ void tst_DirectionalDragArea::sceneXAndX()
493503void tst_DirectionalDragArea::sceneYAndY()
494504{
495505 DirectionalDragArea *edgeDragArea =
496− view->rootObject()->findChild<DirectionalDragArea*>("vnDragArea");
506+ m_view->rootObject()->findChild<DirectionalDragArea*>("vnDragArea");
497507 QVERIFY(edgeDragArea != 0);
498508 edgeDragArea->setRecognitionTimer(fakeTimer);
499509 edgeDragArea->setTimeSource(fakeTimeSource);
500510
501− QPointF touchScenePos(view->width()/2.0f, view->height() - (edgeDragArea->height()/2.0f));
511+ QPointF touchScenePos(m_view->width()/2.0f, m_view->height() - (edgeDragArea->height()/2.0f));
502512
503− fakeTimeSource->m_msecsSinceReference = 0;
504− QTest::touchEvent(view, device).press(0, touchScenePos.toPoint());
513+ QTest::touchEvent(m_view, m_device).press(0, touchScenePos.toPoint());
505514
506515 QSignalSpy touchYSpy(edgeDragArea, SIGNAL(touchYChanged(qreal)));
507516 QSignalSpy touchSceneYSpy(edgeDragArea, SIGNAL(touchSceneYChanged(qreal)));
508517
509− touchScenePos.ry() = view->height() / 2;
510− QTest::touchEvent(view, device).move(0, touchScenePos.toPoint());
518+ touchScenePos.ry() = m_view->height() / 2;
519+ QTest::touchEvent(m_view, m_device).move(0, touchScenePos.toPoint());
511520
512521 QCOMPARE(touchYSpy.count(), 1);
513522 QCOMPARE(touchSceneYSpy.count(), 1);
@@ -516,34 +525,183 @@ void tst_DirectionalDragArea::sceneYAndY()
516525}
517526
518527/*
519− Emit draggingChanged() when status change from WaitingForTouch directly to Recognized.
520− This happens when the gesture recognition is effectively disabled (e.g. by setting a
521− distanceThreshold of zero)
528+ Regression test for https://bugs.launchpad.net/bugs/1228336
529+
530+ Perform a quick two-finger double-tap and check that DirectionalDragArea properly
531+ rejects those touch points. In the bug above it got confused and crashed.
522532 */
523−void tst_DirectionalDragArea::emitDraggingChangedOnDirectRecognition()
533+void tst_DirectionalDragArea::twoFingerTap()
524534{
525535 DirectionalDragArea *edgeDragArea =
526− view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
536+ m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
527537 QVERIFY(edgeDragArea != 0);
528538 edgeDragArea->setRecognitionTimer(fakeTimer);
529539 edgeDragArea->setTimeSource(fakeTimeSource);
530540
531− QPointF touchPos = calculateInitialTouchPos(edgeDragArea, view);
541+ // Make touches evenly spaced along the edgeDragArea
542+ QPoint touchAPos(edgeDragArea->width()/2.0f, m_view->height()*0.33f);
543+ QPoint touchBPos(edgeDragArea->width()/2.0f, m_view->height()*0.66f);
532544
533− QSignalSpy draggingSpy(edgeDragArea, SIGNAL(draggingChanged(bool)));
545+ qint64 timeStepMsecs = 5; // some arbitrary, small value
546+
547+ // Perform the first two-finger tap
548+ // NB: using move() instead of stationary() becasue in the latter you cannot provide
549+ // the touch position and therefore it's left with some garbage numbers.
550+ QTest::touchEvent(m_view, m_device)
551+ .press(0, touchAPos);
552+
553+ QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
534554
535− // That will make it completetly skip the recognition phase ("Undecided" status).
536− edgeDragArea->setDistanceThreshold(0.0);
555+ passTime(timeStepMsecs);
556+ QTest::touchEvent(m_view, m_device)
557+ .move(0, touchAPos)
558+ .press(1, touchBPos);
559+
560+ // A second touch point appeared during recognition, reject immediately as this
561+ // can't be a single-touch gesture anymore.
562+ QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
563+
564+ passTime(timeStepMsecs);
565+ QTest::touchEvent(m_view, m_device)
566+ .release(0, touchAPos)
567+ .move(1, touchBPos);
568+
569+ QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
570+
571+ passTime(timeStepMsecs);
572+ QTest::touchEvent(m_view, m_device)
573+ .release(1, touchBPos);
537574
538575 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
539− QCOMPARE(edgeDragArea->dragging(), false);
540576
541− fakeTimeSource->m_msecsSinceReference = 0;
542− QTest::touchEvent(view, device).press(0, touchPos.toPoint());
577+ // Perform the second two-finger tap
578+
579+ passTime(timeStepMsecs);
580+ QTest::touchEvent(m_view, m_device)
581+ .press(0, touchAPos);
582+
583+ QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
584+
585+ passTime(timeStepMsecs);
586+ QTest::touchEvent(m_view, m_device)
587+ .move(0, touchAPos)
588+ .press(1, touchBPos);
589+
590+ QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
591+
592+ passTime(timeStepMsecs);
593+ QTest::touchEvent(m_view, m_device)
594+ .release(0, touchAPos)
595+ .move(1, touchBPos);
596+
597+ QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
598+
599+ passTime(timeStepMsecs);
600+ QTest::touchEvent(m_view, m_device)
601+ .release(1, touchBPos);
602+
603+ QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
604+}
605+
606+/*
607+ Tests that gesture recognition works normally even if the DirectionalDragArea moves
608+ during recognition.
609+ This effectively means that we have to do gesture recognition with scene coordinates
610+ instead of local item coordinates.
611+ */
612+void tst_DirectionalDragArea::movingDDA()
613+{
614+ QQuickItem *rightwardsLauncher = m_view->rootObject()->findChild<QQuickItem*>("rightwardsLauncher");
615+ Q_ASSERT(rightwardsLauncher != 0);
616+
617+ DirectionalDragArea *edgeDragArea =
618+ rightwardsLauncher->findChild<DirectionalDragArea*>("hpDragArea");
619+ Q_ASSERT(edgeDragArea != 0);
620+ edgeDragArea->setRecognitionTimer(fakeTimer);
621+ edgeDragArea->setTimeSource(fakeTimeSource);
622+
623+ QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea, m_view);
624+ QPointF touchPoint = initialTouchPos;
625+
626+ qreal desiredDragDistance = edgeDragArea->distanceThreshold()*2.0f;
627+ QPointF dragDirectionVector(1.0f, 0.0f);
628+
629+ qreal movementStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
630+ QPointF touchMovement = dragDirectionVector * movementStepDistance;
631+ int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
632+ int movementTimeStepMs = (edgeDragArea->compositionTime() * 1.5f) / totalMovementSteps;
633+
634+ QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
635+
636+ // Move it far ahead along the direction of the gesture
637+ // rightwardsLauncher is a parent of our DirectionalDragArea. So moving it will move our DDA
638+ rightwardsLauncher->setX(rightwardsLauncher->x() + edgeDragArea->distanceThreshold() * 5.0f);
639+
640+ for (int i = 0; i < totalMovementSteps; ++i) {
641+ touchPoint += touchMovement;
642+ passTime(movementTimeStepMs);
643+ QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
644+ }
543645
544646 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
545− QCOMPARE(draggingSpy.count(), 1);
546− QCOMPARE(edgeDragArea->dragging(), true);
647+
648+ QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
649+}
650+
651+/*
652+ The presence of and old, rejected, touch point lying on the DirectionalDragArea
653+ shouldn't impede the recognition of a gesture from a new touch point.
654+ */
655+void tst_DirectionalDragArea::ignoreOldFinger()
656+{
657+ DirectionalDragArea *edgeDragArea =
658+ m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
659+ Q_ASSERT(edgeDragArea != 0);
660+ edgeDragArea->setRecognitionTimer(fakeTimer);
661+ edgeDragArea->setTimeSource(fakeTimeSource);
662+
663+ // Make touches evenly spaced along the edgeDragArea
664+ QPoint touch0Pos(edgeDragArea->width()/2.0f, m_view->height()*0.33f);
665+ QPoint touch1Pos(edgeDragArea->width()/2.0f, m_view->height()*0.66f);
666+
667+ QTest::touchEvent(m_view, m_device).press(0, touch0Pos);
668+
669+ QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
670+
671+ // leave it lying around for some time
672+ passTime(qMax(edgeDragArea->maxSilenceTime(), edgeDragArea->compositionTime()) * 10);
673+
674+ QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
675+
676+ qreal desiredDragDistance = edgeDragArea->distanceThreshold()*2.0f;
677+ QPointF dragDirectionVector(1.0f, 0.0f);
678+
679+ qreal movementStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
680+ QPointF touchMovement = dragDirectionVector * movementStepDistance;
681+ int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
682+ int movementTimeStepMs = (edgeDragArea->compositionTime() * 1.5f) / totalMovementSteps;
683+
684+ QTest::touchEvent(m_view, m_device)
685+ .move(0, touch0Pos)
686+ .press(1, touch1Pos);
687+
688+ QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
689+
690+ for (int i = 0; i < totalMovementSteps; ++i) {
691+ touch1Pos += touchMovement.toPoint();
692+ passTime(movementTimeStepMs);
693+ QTest::touchEvent(m_view, m_device)
694+ .move(0, touch0Pos)
695+ .move(1, touch1Pos);
696+ }
697+
698+ QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
699+
700+ QTest::touchEvent(m_view, m_device)
701+ .move(0, touch0Pos)
702+ .release(1, touch1Pos);
703+
704+ QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
547705}
548706
549707QTEST_MAIN(tst_DirectionalDragArea)