on 76c06df help sign in
unity8/tests/plugins/Ubuntu/Gestures/tst_DirectionalDragArea.cpp
551 lines 19 kB history raw
1/*
2 * Copyright (C) 2013 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
17#include <QtTest/QtTest>
18#include <QtCore/QObject>
19#include <qpa/qwindowsysteminterface.h>
20#include <QtQuick/QQuickView>
21#include <QtQml/QQmlEngine>
23#include <DirectionalDragArea.h>
25class FakeTimer : public UbuntuGestures::AbstractTimer
27 Q_OBJECT
28public:
29 FakeTimer(QObject *parent = 0)
30 : UbuntuGestures::AbstractTimer(parent)
31 {}
33 virtual int interval() const { return m_duration; }
34 virtual void setInterval(int msecs) { m_duration = msecs; }
36 void emitTimeout() {Q_EMIT timeout();}
37private:
38 int m_duration;
39};
41class FakeTimeSource : public UbuntuGestures::TimeSource
43public:
44 FakeTimeSource() { m_msecsSinceReference = 0; }
45 virtual qint64 msecsSinceReference() {return m_msecsSinceReference;}
46 qint64 m_msecsSinceReference;
47};
49class tst_DirectionalDragArea: public QObject
51 Q_OBJECT
52public:
53 tst_DirectionalDragArea() : device(0) { }
54private 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.
58 void init(); // called right before each and every test function is executed
59 void cleanup(); // called right after each and every test function is executed
61 void edgeDrag();
62 void edgeDrag_data();
63 void dragWithShortDirectionChange();
64 void minSpeed();
65 void minSpeed_data();
66 void recognitionTimerUsage();
67 void maxSilenceTime();
68 void sceneXAndX();
69 void sceneYAndY();
70 void emitDraggingChangedOnDirectRecognition();
72private:
73 QQuickView *createView();
74 QQuickView *view;
75 QTouchDevice *device;
76 FakeTimer *fakeTimer;
77 FakeTimeSource *fakeTimeSource;
78};
80void tst_DirectionalDragArea::initTestCase()
82 if (!device) {
83 device = new QTouchDevice;
84 device->setType(QTouchDevice::TouchScreen);
85 QWindowSystemInterface::registerTouchDevice(device);
86 }
88 view = 0;
91void tst_DirectionalDragArea::cleanupTestCase()
95void tst_DirectionalDragArea::init()
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();
104 fakeTimer = new FakeTimer;
105 fakeTimeSource = new FakeTimeSource;
108void tst_DirectionalDragArea::cleanup()
110 delete view;
111 view = 0;
113 delete fakeTimer;
114 fakeTimer = 0;
116 delete fakeTimeSource;
117 fakeTimeSource = 0;
120QQuickView *tst_DirectionalDragArea::createView()
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));
127 return window;
130namespace {
131QPointF calculateInitialTouchPos(DirectionalDragArea *edgeDragArea, QQuickView *view)
133 switch (edgeDragArea->direction()) {
134 case Direction::Upwards:
135 return QPointF(view->width()/2.0f, view->height() - (edgeDragArea->height()/2.0f));
136 case Direction::Downwards:
137 return QPointF(view->width()/2.0f, edgeDragArea->height()/2.0f);
138 case Direction::Leftwards:
139 return QPointF(view->width() - (edgeDragArea->width()/2.0f), view->height()/2.0f);
140 default: // Direction::Rightwards:
141 return QPointF(edgeDragArea->width()/2.0f, view->height()/2.0f);
142 }
145QPointF calculateDirectionVector(DirectionalDragArea *edgeDragArea,
146 qreal wideningAngleMultiplier)
148 qreal angleRadians = edgeDragArea->wideningAngle() * wideningAngleMultiplier
149 * M_PI / 180.0;
151 qreal angleCos = qCos(angleRadians);
152 qreal angleSin = qSin(angleRadians);
154 switch (edgeDragArea->direction()) {
155 case Direction::Upwards:
156 return QPointF(angleSin, -angleCos);
157 case Direction::Downwards:
158 return QPointF(angleSin, angleCos);
159 case Direction::Leftwards:
160 return QPointF(-angleCos, angleSin);
161 default: // Direction::Rightwards:
162 return QPointF(angleCos, angleSin);
163 }
166QPointF createTouchDeviation(DirectionalDragArea *edgeDragArea)
168 qreal deviation = edgeDragArea->maxDeviation() * 0.8;
170 if (Direction::isHorizontal(edgeDragArea->direction())) {
171 return QPointF(0, deviation);
172 } else {
173 return QPointF(deviation, 0);
174 }
178void tst_DirectionalDragArea::edgeDrag()
180 QFETCH(QString, dragAreaObjectName);
181 QFETCH(qreal, wideningAngleMultiplier);
182 QFETCH(qreal, dragDistanceFactor);
183 QFETCH(int, expectedGestureRecognition);
185 DirectionalDragArea *edgeDragArea =
186 view->rootObject()->findChild<DirectionalDragArea*>(dragAreaObjectName);
187 QVERIFY(edgeDragArea != 0);
188 edgeDragArea->setRecognitionTimer(fakeTimer);
189 edgeDragArea->setTimeSource(fakeTimeSource);
191 QSignalSpy draggingSpy(edgeDragArea, SIGNAL(draggingChanged(bool)));
193 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea, view);
194 QPointF touchPoint = initialTouchPos;
196 qreal desiredDragDistance = edgeDragArea->distanceThreshold()*dragDistanceFactor;
197 QPointF dragDirectionVector = calculateDirectionVector(edgeDragArea,
198 wideningAngleMultiplier);
199 QPointF touchMovement = dragDirectionVector * (edgeDragArea->distanceThreshold() * 0.1f);
201 QTest::touchEvent(view, device).press(0, touchPoint.toPoint());
203 QCOMPARE(draggingSpy.count(), 1);
204 QCOMPARE(edgeDragArea->dragging(), true);
206 if (wideningAngleMultiplier > 0) {
207 // go close to the border of the valid area for this touch point
208 // in order to make it easier to leave it by dragging at an angle
209 // slightly bigger than the widening angle
210 touchPoint += createTouchDeviation(edgeDragArea);
211 QTest::touchEvent(view, device).move(0, touchPoint.toPoint());
212 }
214 do {
215 touchPoint += touchMovement;
216 QTest::touchEvent(view, device).move(0, touchPoint.toPoint());
217 } while ((touchPoint - initialTouchPos).manhattanLength() < desiredDragDistance);
219 if (expectedGestureRecognition)
220 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
222 if (edgeDragArea->status() == DirectionalDragArea::Rejected) {
223 QCOMPARE(edgeDragArea->dragging(), false);
224 QCOMPARE(draggingSpy.count(), 2);
225 }
227 QTest::touchEvent(view, device).release(0, touchPoint.toPoint());
229 QCOMPARE(draggingSpy.count(), 2);
230 QCOMPARE(edgeDragArea->dragging(), false);
233void tst_DirectionalDragArea::edgeDrag_data()
235 QTest::addColumn<QString>("dragAreaObjectName");
236 QTest::addColumn<qreal>("wideningAngleMultiplier");
237 QTest::addColumn<qreal>("dragDistanceFactor");
238 QTest::addColumn<int>("expectedGestureRecognition");
240 QTest::newRow("rightwards, tiny drag")
241 << "hpDragArea" << 0.0 << 0.2 << 0;
243 QTest::newRow("rightwards, straight drag")
244 << "hpDragArea" << 0.0 << 3.0 << 1;
246 QTest::newRow("rightwards, diagonal drag")
247 << "hpDragArea" << 0.9 << 3.0 << 1;
249 QTest::newRow("rightwards, overly diagonal drag")
250 << "hpDragArea" << 2.0 << 3.0 << 0;
252 QTest::newRow("leftwards, tiny drag")
253 << "hnDragArea" << 0.0 << 0.2 << 0;
255 QTest::newRow("leftwards, straight drag")
256 << "hnDragArea" << 0.0 << 3.0 << 1;
258 QTest::newRow("leftwards, diagonal drag")
259 << "hnDragArea" << 0.9 << 3.0 << 1;
261 QTest::newRow("downwards, tiny drag")
262 << "vpDragArea" << 0.0 << 0.2 << 0;
264 QTest::newRow("downwards, straight drag")
265 << "vpDragArea" << 0.0 << 3.0 << 1;
267 QTest::newRow("downwards, diagonal drag")
268 << "vpDragArea" << 0.9 << 3.0 << 1;
270 QTest::newRow("upwards, tiny drag")
271 << "vnDragArea" << 0.0 << 0.2 << 0;
273 QTest::newRow("upwards, straight drag")
274 << "vnDragArea" << 0.0 << 3.0 << 1;
276 QTest::newRow("upwards, diagonal drag")
277 << "vnDragArea" << 0.9 << 3.0 << 1;
279 QTest::newRow("upwards, overly diagonal drag")
280 << "vnDragArea" << 2.0 << 3.0 << 0;
283/*
284 A directional drag should still be recognized if there is a momentaneous, small,
285 change in the direction of a drag. That should be accounted as input noise and
286 therefore ignored.
287 */
288void tst_DirectionalDragArea::dragWithShortDirectionChange()
290 DirectionalDragArea *edgeDragArea =
291 view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
292 QVERIFY(edgeDragArea != 0);
293 edgeDragArea->setRecognitionTimer(fakeTimer);
294 edgeDragArea->setTimeSource(fakeTimeSource);
296 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea, view);
297 QPointF touchPoint = initialTouchPos;
299 qreal desiredDragDistance = edgeDragArea->distanceThreshold()*2.0;
300 QPointF dragDirectionVector(1.0, 0.0);
301 QPointF touchMovement = dragDirectionVector * (edgeDragArea->distanceThreshold() * 0.1f);
303 QTest::touchEvent(view, device).press(0, touchPoint.toPoint());
305 // Move a bit in the proper direction
306 for (int i=0; i < 3; ++i) {
307 touchPoint += touchMovement;
308 QTest::touchEvent(view, device).move(0, touchPoint.toPoint());
309 }
311 // 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());
315 // And then resume movment in the correct direction until it crosses the distance threshold.
316 do {
317 touchPoint += touchMovement;
318 QTest::touchEvent(view, device).move(0, touchPoint.toPoint());
319 } while ((touchPoint - initialTouchPos).manhattanLength() < desiredDragDistance);
321 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
323 QTest::touchEvent(view, device).release(0, touchPoint.toPoint());
326/*
327 Checks that a gesture will be rejected if it's slower than minSpeed while
328 status is Undecided.
329 */
330void tst_DirectionalDragArea::minSpeed()
332 QFETCH(int, minSpeedMsecsDeviation);
333 QFETCH(int, expectedStatusAfterSpeedCheck);
335 DirectionalDragArea *edgeDragArea =
336 view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
337 QVERIFY(edgeDragArea != 0);
338 edgeDragArea->setRecognitionTimer(fakeTimer);
339 edgeDragArea->setTimeSource(fakeTimeSource);
341 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea, view);
342 QPointF touchPoint = initialTouchPos;
344 QPointF dragDirectionVector(1.0, 0.0);
345 qreal distanceStep = edgeDragArea->distanceThreshold() * 0.1f;
346 QPointF touchMovement = dragDirectionVector * distanceStep;
347 qreal minSpeedMsecs = edgeDragArea->minSpeed() / 1000.0;
348 qint64 timeStepMsecs = qFloor(distanceStep / minSpeedMsecs) + minSpeedMsecsDeviation;
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);
354 fakeTimeSource->m_msecsSinceReference = 0;
355 QTest::touchEvent(view, device).press(0, touchPoint.toPoint());
357 // Move a bit in the proper direction
358 for (int i=0; i < 4; ++i) {
359 touchPoint += touchMovement;
360 fakeTimeSource->m_msecsSinceReference += timeStepMsecs;
361 QTest::touchEvent(view, device).move(0, touchPoint.toPoint());
362 }
364 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
366 // Force the periodic speed check.
367 fakeTimer->emitTimeout();
369 QCOMPARE((int)edgeDragArea->status(), expectedStatusAfterSpeedCheck);
372void tst_DirectionalDragArea::minSpeed_data()
374 QTest::addColumn<int>("minSpeedMsecsDeviation");
375 QTest::addColumn<int>("expectedStatusAfterSpeedCheck");
377 QTest::newRow("slower than minSpeed") << 20 << (int)DirectionalDragArea::Rejected;
378 QTest::newRow("faster than minSpeed") << -20 << (int)DirectionalDragArea::Undecided;
381/*
382 Checks that the recognition timer is started and stopped appropriately.
383 I.e., check that it's running only while gesture recognition is taking place
384 (status == Undecided)
385 */
386void tst_DirectionalDragArea::recognitionTimerUsage()
388 DirectionalDragArea *edgeDragArea =
389 view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
390 QVERIFY(edgeDragArea != 0);
391 edgeDragArea->setRecognitionTimer(fakeTimer);
392 edgeDragArea->setTimeSource(fakeTimeSource);
394 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea, view);
395 QPointF touchPoint = initialTouchPos;
397 QPointF dragDirectionVector(1.0, 0.0);
398 QPointF touchMovement = dragDirectionVector * (edgeDragArea->distanceThreshold() * 0.2f);
400 QVERIFY(!fakeTimer->isRunning());
402 QTest::touchEvent(view, device).press(0, touchPoint.toPoint());
404 QVERIFY(fakeTimer->isRunning());
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 }
412 QVERIFY(fakeTimer->isRunning());
414 // Move beyond distance threshold
415 touchPoint += 3*touchMovement;
416 QTest::touchEvent(view, device).move(0, touchPoint.toPoint());
418 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
420 QVERIFY(!fakeTimer->isRunning());
423/*
424 A gesture should be rejected if too much time has passed without any new input
425 events from it.
426 */
427void tst_DirectionalDragArea::maxSilenceTime()
429 DirectionalDragArea *edgeDragArea =
430 view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
431 QVERIFY(edgeDragArea != 0);
432 edgeDragArea->setRecognitionTimer(fakeTimer);
433 edgeDragArea->setTimeSource(fakeTimeSource);
435 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea, view);
436 QPointF touchPoint = initialTouchPos;
438 fakeTimeSource->m_msecsSinceReference = 0;
439 QTest::touchEvent(view, device).press(0, touchPoint.toPoint());
441 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
443 int neededTimeouts = qCeil((qreal)edgeDragArea->maxSilenceTime() / (qreal)fakeTimer->interval());
444 if ((edgeDragArea->maxSilenceTime() % fakeTimer->interval()) == 0) {
445 ++neededTimeouts;
446 }
448 // Force the periodic speed check.
449 for (int i = 0; i < neededTimeouts; ++i) {
450 fakeTimer->emitTimeout();
452 if (i < neededTimeouts - 1) {
453 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
454 }
455 }
457 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Rejected);
460/*
461 Checks that it informs the X coordinate of the touch point in local and scene coordinates
462 correctly.
463 */
464void tst_DirectionalDragArea::sceneXAndX()
466 DirectionalDragArea *edgeDragArea =
467 view->rootObject()->findChild<DirectionalDragArea*>("hnDragArea");
468 QVERIFY(edgeDragArea != 0);
469 edgeDragArea->setRecognitionTimer(fakeTimer);
470 edgeDragArea->setTimeSource(fakeTimeSource);
472 QPointF touchScenePos(view->width() - (edgeDragArea->width()/2.0f), view->height()/2.0f);
474 fakeTimeSource->m_msecsSinceReference = 0;
475 QTest::touchEvent(view, device).press(0, touchScenePos.toPoint());
477 QSignalSpy touchXSpy(edgeDragArea, SIGNAL(touchXChanged(qreal)));
478 QSignalSpy touchSceneXSpy(edgeDragArea, SIGNAL(touchSceneXChanged(qreal)));
480 touchScenePos.rx() = view->width() / 2;
481 QTest::touchEvent(view, device).move(0, touchScenePos.toPoint());
483 QCOMPARE(touchXSpy.count(), 1);
484 QCOMPARE(touchSceneXSpy.count(), 1);
485 QCOMPARE(edgeDragArea->touchX(), touchScenePos.x() - edgeDragArea->x());
486 QCOMPARE(edgeDragArea->touchSceneX(), touchScenePos.x());
489/*
490 Checks that it informs the Y coordinate of the touch point in local and scene coordinates
491 correctly.
492 */
493void tst_DirectionalDragArea::sceneYAndY()
495 DirectionalDragArea *edgeDragArea =
496 view->rootObject()->findChild<DirectionalDragArea*>("vnDragArea");
497 QVERIFY(edgeDragArea != 0);
498 edgeDragArea->setRecognitionTimer(fakeTimer);
499 edgeDragArea->setTimeSource(fakeTimeSource);
501 QPointF touchScenePos(view->width()/2.0f, view->height() - (edgeDragArea->height()/2.0f));
503 fakeTimeSource->m_msecsSinceReference = 0;
504 QTest::touchEvent(view, device).press(0, touchScenePos.toPoint());
506 QSignalSpy touchYSpy(edgeDragArea, SIGNAL(touchYChanged(qreal)));
507 QSignalSpy touchSceneYSpy(edgeDragArea, SIGNAL(touchSceneYChanged(qreal)));
509 touchScenePos.ry() = view->height() / 2;
510 QTest::touchEvent(view, device).move(0, touchScenePos.toPoint());
512 QCOMPARE(touchYSpy.count(), 1);
513 QCOMPARE(touchSceneYSpy.count(), 1);
514 QCOMPARE(edgeDragArea->touchY(), touchScenePos.y() - edgeDragArea->y());
515 QCOMPARE(edgeDragArea->touchSceneY(), touchScenePos.y());
518/*
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)
522 */
523void tst_DirectionalDragArea::emitDraggingChangedOnDirectRecognition()
525 DirectionalDragArea *edgeDragArea =
526 view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
527 QVERIFY(edgeDragArea != 0);
528 edgeDragArea->setRecognitionTimer(fakeTimer);
529 edgeDragArea->setTimeSource(fakeTimeSource);
531 QPointF touchPos = calculateInitialTouchPos(edgeDragArea, view);
533 QSignalSpy draggingSpy(edgeDragArea, SIGNAL(draggingChanged(bool)));
535 // That will make it completetly skip the recognition phase ("Undecided" status).
536 edgeDragArea->setDistanceThreshold(0.0);
538 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
539 QCOMPARE(edgeDragArea->dragging(), false);
541 fakeTimeSource->m_msecsSinceReference = 0;
542 QTest::touchEvent(view, device).press(0, touchPos.toPoint());
544 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
545 QCOMPARE(draggingSpy.count(), 1);
546 QCOMPARE(edgeDragArea->dragging(), true);
549QTEST_MAIN(tst_DirectionalDragArea)
551#include "tst_DirectionalDragArea.moc"