on 3e4155e help sign in
unity8/tests/plugins/Ubuntu/Gestures/tst_DirectionalDragArea.cpp
459 lines 15 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();
69private:
70 QQuickView *createView();
71 QQuickView *view;
72 QTouchDevice *device;
73 FakeTimer *fakeTimer;
74 FakeTimeSource *fakeTimeSource;
75};
77void tst_DirectionalDragArea::initTestCase()
79 if (!device) {
80 device = new QTouchDevice;
81 device->setType(QTouchDevice::TouchScreen);
82 QWindowSystemInterface::registerTouchDevice(device);
83 }
85 view = 0;
88void tst_DirectionalDragArea::cleanupTestCase()
92void tst_DirectionalDragArea::init()
94 view = createView();
95 view->setSource(QUrl::fromLocalFile("edgeDragExample.qml"));
96 view->show();
97 QVERIFY(QTest::qWaitForWindowExposed(view));
98 QVERIFY(view->rootObject() != 0);
99 qApp->processEvents();
101 fakeTimer = new FakeTimer;
102 fakeTimeSource = new FakeTimeSource;
105void tst_DirectionalDragArea::cleanup()
107 delete view;
108 view = 0;
110 delete fakeTimer;
111 fakeTimer = 0;
113 delete fakeTimeSource;
114 fakeTimeSource = 0;
117QQuickView *tst_DirectionalDragArea::createView()
119 QQuickView *window = new QQuickView(0);
120 window->setResizeMode(QQuickView::SizeRootObjectToView);
121 window->resize(600, 600);
122 window->engine()->addImportPath(QLatin1String(UBUNTU_GESTURES_PLUGIN_DIR));
124 return window;
127namespace {
128QPointF calculateInitialTouchPos(DirectionalDragArea *edgeDragArea, QQuickView *view)
130 switch (edgeDragArea->direction()) {
131 case Direction::Upwards:
132 return QPointF(view->width()/2.0f, view->height() - (edgeDragArea->height()/2.0f));
133 case Direction::Downwards:
134 return QPointF(view->width()/2.0f, edgeDragArea->height()/2.0f);
135 case Direction::Leftwards:
136 return QPointF(view->width() - (edgeDragArea->width()/2.0f), view->height()/2.0f);
137 default: // Direction::Rightwards:
138 return QPointF(edgeDragArea->width()/2.0f, view->height()/2.0f);
139 }
142QPointF calculateDirectionVector(DirectionalDragArea *edgeDragArea,
143 qreal wideningAngleMultiplier)
145 qreal angleRadians = edgeDragArea->wideningAngle() * wideningAngleMultiplier
146 * M_PI / 180.0;
148 qreal angleCos = qCos(angleRadians);
149 qreal angleSin = qSin(angleRadians);
151 switch (edgeDragArea->direction()) {
152 case Direction::Upwards:
153 return QPointF(angleSin, -angleCos);
154 case Direction::Downwards:
155 return QPointF(angleSin, angleCos);
156 case Direction::Leftwards:
157 return QPointF(-angleCos, angleSin);
158 default: // Direction::Rightwards:
159 return QPointF(angleCos, angleSin);
160 }
163QPointF createTouchDeviation(DirectionalDragArea *edgeDragArea)
165 qreal deviation = edgeDragArea->maxDeviation() * 0.8;
167 if (Direction::isHorizontal(edgeDragArea->direction())) {
168 return QPointF(0, deviation);
169 } else {
170 return QPointF(deviation, 0);
171 }
175void tst_DirectionalDragArea::edgeDrag()
177 QFETCH(QString, dragAreaObjectName);
178 QFETCH(qreal, wideningAngleMultiplier);
179 QFETCH(qreal, dragDistanceFactor);
180 QFETCH(int, expectedGestureRecognition);
182 DirectionalDragArea *edgeDragArea =
183 view->rootObject()->findChild<DirectionalDragArea*>(dragAreaObjectName);
184 QVERIFY(edgeDragArea != 0);
185 edgeDragArea->setRecognitionTimer(fakeTimer);
186 edgeDragArea->setTimeSource(fakeTimeSource);
188 QSignalSpy draggingSpy(edgeDragArea, SIGNAL(draggingChanged(bool)));
190 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea, view);
191 QPointF touchPoint = initialTouchPos;
193 qreal desiredDragDistance = edgeDragArea->distanceThreshold()*dragDistanceFactor;
194 QPointF dragDirectionVector = calculateDirectionVector(edgeDragArea,
195 wideningAngleMultiplier);
196 QPointF touchMovement = dragDirectionVector * (edgeDragArea->distanceThreshold() * 0.1f);
198 QTest::touchEvent(view, device).press(0, touchPoint.toPoint());
200 QCOMPARE(draggingSpy.count(), 1);
201 QCOMPARE(edgeDragArea->dragging(), true);
203 if (wideningAngleMultiplier > 0) {
204 // go close to the border of the valid area for this touch point
205 // in order to make it easier to leave it by dragging at an angle
206 // slightly bigger than the widening angle
207 touchPoint += createTouchDeviation(edgeDragArea);
208 QTest::touchEvent(view, device).move(0, touchPoint.toPoint());
209 }
211 do {
212 touchPoint += touchMovement;
213 QTest::touchEvent(view, device).move(0, touchPoint.toPoint());
214 } while ((touchPoint - initialTouchPos).manhattanLength() < desiredDragDistance);
216 if (expectedGestureRecognition)
217 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
219 if (edgeDragArea->status() == DirectionalDragArea::Rejected) {
220 QCOMPARE(edgeDragArea->dragging(), false);
221 QCOMPARE(draggingSpy.count(), 2);
222 }
224 QTest::touchEvent(view, device).release(0, touchPoint.toPoint());
226 QCOMPARE(draggingSpy.count(), 2);
227 QCOMPARE(edgeDragArea->dragging(), false);
230void tst_DirectionalDragArea::edgeDrag_data()
232 QTest::addColumn<QString>("dragAreaObjectName");
233 QTest::addColumn<qreal>("wideningAngleMultiplier");
234 QTest::addColumn<qreal>("dragDistanceFactor");
235 QTest::addColumn<int>("expectedGestureRecognition");
237 QTest::newRow("rightwards, tiny drag")
238 << "hpDragArea" << 0.0 << 0.2 << 0;
240 QTest::newRow("rightwards, straight drag")
241 << "hpDragArea" << 0.0 << 3.0 << 1;
243 QTest::newRow("rightwards, diagonal drag")
244 << "hpDragArea" << 0.9 << 3.0 << 1;
246 QTest::newRow("rightwards, overly diagonal drag")
247 << "hpDragArea" << 2.0 << 3.0 << 0;
249 QTest::newRow("leftwards, tiny drag")
250 << "hnDragArea" << 0.0 << 0.2 << 0;
252 QTest::newRow("leftwards, straight drag")
253 << "hnDragArea" << 0.0 << 3.0 << 1;
255 QTest::newRow("leftwards, diagonal drag")
256 << "hnDragArea" << 0.9 << 3.0 << 1;
258 QTest::newRow("downwards, tiny drag")
259 << "vpDragArea" << 0.0 << 0.2 << 0;
261 QTest::newRow("downwards, straight drag")
262 << "vpDragArea" << 0.0 << 3.0 << 1;
264 QTest::newRow("downwards, diagonal drag")
265 << "vpDragArea" << 0.9 << 3.0 << 1;
267 QTest::newRow("upwards, tiny drag")
268 << "vnDragArea" << 0.0 << 0.2 << 0;
270 QTest::newRow("upwards, straight drag")
271 << "vnDragArea" << 0.0 << 3.0 << 1;
273 QTest::newRow("upwards, diagonal drag")
274 << "vnDragArea" << 0.9 << 3.0 << 1;
276 QTest::newRow("upwards, overly diagonal drag")
277 << "vnDragArea" << 2.0 << 3.0 << 0;
280/*
281 A directional drag should still be recognized if there is a momentaneous, small,
282 change in the direction of a drag. That should be accounted as input noise and
283 therefore ignored.
284 */
285void tst_DirectionalDragArea::dragWithShortDirectionChange()
287 DirectionalDragArea *edgeDragArea =
288 view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
289 QVERIFY(edgeDragArea != 0);
290 edgeDragArea->setRecognitionTimer(fakeTimer);
291 edgeDragArea->setTimeSource(fakeTimeSource);
293 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea, view);
294 QPointF touchPoint = initialTouchPos;
296 qreal desiredDragDistance = edgeDragArea->distanceThreshold()*2.0;
297 QPointF dragDirectionVector(1.0, 0.0);
298 QPointF touchMovement = dragDirectionVector * (edgeDragArea->distanceThreshold() * 0.1f);
300 QTest::touchEvent(view, device).press(0, touchPoint.toPoint());
302 // Move a bit in the proper direction
303 for (int i=0; i < 3; ++i) {
304 touchPoint += touchMovement;
305 QTest::touchEvent(view, device).move(0, touchPoint.toPoint());
306 }
308 // Then a sudden and small movement to the opposite direction
309 touchPoint -= dragDirectionVector * (edgeDragArea->maxDeviation() * 0.7);
310 QTest::touchEvent(view, device).move(0, touchPoint.toPoint());
312 // And then resume movment in the correct direction until it crosses the distance threshold.
313 do {
314 touchPoint += touchMovement;
315 QTest::touchEvent(view, device).move(0, touchPoint.toPoint());
316 } while ((touchPoint - initialTouchPos).manhattanLength() < desiredDragDistance);
318 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
320 QTest::touchEvent(view, device).release(0, touchPoint.toPoint());
323/*
324 Checks that a gesture will be rejected if it's slower than minSpeed while
325 status is Undecided.
326 */
327void tst_DirectionalDragArea::minSpeed()
329 QFETCH(int, minSpeedMsecsDeviation);
330 QFETCH(int, expectedStatusAfterSpeedCheck);
332 DirectionalDragArea *edgeDragArea =
333 view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
334 QVERIFY(edgeDragArea != 0);
335 edgeDragArea->setRecognitionTimer(fakeTimer);
336 edgeDragArea->setTimeSource(fakeTimeSource);
338 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea, view);
339 QPointF touchPoint = initialTouchPos;
341 QPointF dragDirectionVector(1.0, 0.0);
342 qreal distanceStep = edgeDragArea->distanceThreshold() * 0.1f;
343 QPointF touchMovement = dragDirectionVector * distanceStep;
344 qreal minSpeedMsecs = edgeDragArea->minSpeed() / 1000.0;
345 qint64 timeStepMsecs = qFloor(distanceStep / minSpeedMsecs) + minSpeedMsecsDeviation;
347 // if it fails it means the params set in the QML file are not in harmony with the values
348 // used in this test
349 Q_ASSERT(timeStepMsecs > 0);
351 fakeTimeSource->m_msecsSinceReference = 0;
352 QTest::touchEvent(view, device).press(0, touchPoint.toPoint());
354 // Move a bit in the proper direction
355 for (int i=0; i < 4; ++i) {
356 touchPoint += touchMovement;
357 fakeTimeSource->m_msecsSinceReference += timeStepMsecs;
358 QTest::touchEvent(view, device).move(0, touchPoint.toPoint());
359 }
361 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
363 // Force the periodic speed check.
364 fakeTimer->emitTimeout();
366 QCOMPARE((int)edgeDragArea->status(), expectedStatusAfterSpeedCheck);
369void tst_DirectionalDragArea::minSpeed_data()
371 QTest::addColumn<int>("minSpeedMsecsDeviation");
372 QTest::addColumn<int>("expectedStatusAfterSpeedCheck");
374 QTest::newRow("slower than minSpeed") << 20 << (int)DirectionalDragArea::Rejected;
375 QTest::newRow("faster than minSpeed") << -20 << (int)DirectionalDragArea::Undecided;
378/*
379 Checks that the recognition timer is started and stopped appropriately.
380 I.e., check that it's running only while gesture recognition is taking place
381 (status == Undecided)
382 */
383void tst_DirectionalDragArea::recognitionTimerUsage()
385 DirectionalDragArea *edgeDragArea =
386 view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
387 QVERIFY(edgeDragArea != 0);
388 edgeDragArea->setRecognitionTimer(fakeTimer);
389 edgeDragArea->setTimeSource(fakeTimeSource);
391 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea, view);
392 QPointF touchPoint = initialTouchPos;
394 QPointF dragDirectionVector(1.0, 0.0);
395 QPointF touchMovement = dragDirectionVector * (edgeDragArea->distanceThreshold() * 0.2f);
397 QVERIFY(!fakeTimer->isRunning());
399 QTest::touchEvent(view, device).press(0, touchPoint.toPoint());
401 QVERIFY(fakeTimer->isRunning());
403 // Move a bit in the proper direction
404 for (int i=0; i < 3; ++i) {
405 touchPoint += touchMovement;
406 QTest::touchEvent(view, device).move(0, touchPoint.toPoint());
407 }
409 QVERIFY(fakeTimer->isRunning());
411 // Move beyond distance threshold
412 touchPoint += 3*touchMovement;
413 QTest::touchEvent(view, device).move(0, touchPoint.toPoint());
415 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
417 QVERIFY(!fakeTimer->isRunning());
420/*
421 A gesture should be rejected if too much time has passed without any new input
422 events from it.
423 */
424void tst_DirectionalDragArea::maxSilenceTime()
426 DirectionalDragArea *edgeDragArea =
427 view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
428 QVERIFY(edgeDragArea != 0);
429 edgeDragArea->setRecognitionTimer(fakeTimer);
430 edgeDragArea->setTimeSource(fakeTimeSource);
432 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea, view);
433 QPointF touchPoint = initialTouchPos;
435 fakeTimeSource->m_msecsSinceReference = 0;
436 QTest::touchEvent(view, device).press(0, touchPoint.toPoint());
438 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
440 int neededTimeouts = qCeil((qreal)edgeDragArea->maxSilenceTime() / (qreal)fakeTimer->interval());
441 if ((edgeDragArea->maxSilenceTime() % fakeTimer->interval()) == 0) {
442 ++neededTimeouts;
443 }
445 // Force the periodic speed check.
446 for (int i = 0; i < neededTimeouts; ++i) {
447 fakeTimer->emitTimeout();
449 if (i < neededTimeouts - 1) {
450 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
451 }
452 }
454 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Rejected);
457QTEST_MAIN(tst_DirectionalDragArea)
459#include "tst_DirectionalDragArea.moc"