2 * Copyright (C) 2013 Canonical, Ltd.
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.
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.
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/>.
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
29 FakeTimer(QObject *parent = 0)
30 : UbuntuGestures::AbstractTimer(parent)
33 virtual int interval() const { return m_duration; }
34 virtual void setInterval(int msecs) { m_duration = msecs; }
36 void emitTimeout() {Q_EMIT timeout();}
41class FakeTimeSource : public AxisVelocityCalculator::TimeSource
44 FakeTimeSource() { m_msecsSinceReference = 0; }
45 virtual qint64 msecsSinceReference() {return m_msecsSinceReference;}
46 qint64 m_msecsSinceReference;
49class tst_DirectionalDragArea: public QObject
53 tst_DirectionalDragArea() : device(0) { }
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
63 void dragWithShortDirectionChange();
66 void recognitionTimerUsage();
67 void maxSilenceTime();
70 QQuickView *createView();
74 FakeTimeSource *fakeTimeSource;
75 AxisVelocityCalculator *velCalcWithFakeTiming;
78void tst_DirectionalDragArea::initTestCase()
81 device = new QTouchDevice;
82 device->setType(QTouchDevice::TouchScreen);
83 QWindowSystemInterface::registerTouchDevice(device);
89void tst_DirectionalDragArea::cleanupTestCase()
93void tst_DirectionalDragArea::init()
96 view->setSource(QUrl::fromLocalFile("edgeDragExample.qml"));
98 QVERIFY(QTest::qWaitForWindowExposed(view));
99 QVERIFY(view->rootObject() != 0);
100 qApp->processEvents();
102 fakeTimer = new FakeTimer;
103 fakeTimeSource = new FakeTimeSource;
104 velCalcWithFakeTiming = new AxisVelocityCalculator(fakeTimeSource);
107void tst_DirectionalDragArea::cleanup()
115 // will bring fakeTimeSource down with it
116 delete velCalcWithFakeTiming;
118 velCalcWithFakeTiming = 0;
121QQuickView *tst_DirectionalDragArea::createView()
123 QQuickView *window = new QQuickView(0);
124 window->setResizeMode(QQuickView::SizeRootObjectToView);
125 window->resize(600, 600);
126 window->engine()->addImportPath(QLatin1String(UBUNTU_GESTURES_PLUGIN_DIR));
132QPointF calculateInitialTouchPos(DirectionalDragArea *edgeDragArea, QQuickView *view)
134 switch (edgeDragArea->direction()) {
135 case DirectionalDragArea::Upwards:
136 return QPointF(view->width()/2.0f, view->height() - (edgeDragArea->height()/2.0f));
137 case DirectionalDragArea::Downwards:
138 return QPointF(view->width()/2.0f, edgeDragArea->height()/2.0f);
139 case DirectionalDragArea::Leftwards:
140 return QPointF(view->width() - (edgeDragArea->width()/2.0f), view->height()/2.0f);
141 default: // DirectionalDragArea::Rightwards:
142 return QPointF(edgeDragArea->width()/2.0f, view->height()/2.0f);
146QPointF calculateDirectionVector(DirectionalDragArea *edgeDragArea,
147 qreal wideningAngleMultiplier)
149 qreal angleRadians = edgeDragArea->wideningAngle() * wideningAngleMultiplier
152 qreal angleCos = qCos(angleRadians);
153 qreal angleSin = qSin(angleRadians);
155 switch (edgeDragArea->direction()) {
156 case DirectionalDragArea::Upwards:
157 return QPointF(angleSin, -angleCos);
158 case DirectionalDragArea::Downwards:
159 return QPointF(angleSin, angleCos);
160 case DirectionalDragArea::Leftwards:
161 return QPointF(-angleCos, angleSin);
162 default: // DirectionalDragArea::Rightwards:
163 return QPointF(angleCos, angleSin);
167QPointF createTouchDeviation(DirectionalDragArea *edgeDragArea)
169 qreal deviation = edgeDragArea->maxDeviation() * 0.8;
171 if (edgeDragArea->direction() == DirectionalDragArea::Leftwards
172 || edgeDragArea->direction() == DirectionalDragArea::Rightwards) {
173 return QPointF(0, deviation);
175 return QPointF(deviation, 0);
180void tst_DirectionalDragArea::edgeDrag()
182 QFETCH(QString, dragAreaObjectName);
183 QFETCH(qreal, wideningAngleMultiplier);
184 QFETCH(qreal, dragDistanceFactor);
185 QFETCH(int, expectedGestureRecognition);
187 DirectionalDragArea *edgeDragArea =
188 view->rootObject()->findChild<DirectionalDragArea*>(dragAreaObjectName);
189 QVERIFY(edgeDragArea != 0);
190 edgeDragArea->setRecognitionTimer(fakeTimer);
191 edgeDragArea->setAxisVelocityCalculator(velCalcWithFakeTiming);
193 QSignalSpy draggingSpy(edgeDragArea, SIGNAL(draggingChanged(bool)));
195 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea, view);
196 QPointF touchPoint = initialTouchPos;
198 qreal desiredDragDistance = edgeDragArea->distanceThreshold()*dragDistanceFactor;
199 QPointF dragDirectionVector = calculateDirectionVector(edgeDragArea,
200 wideningAngleMultiplier);
201 QPointF touchMovement = dragDirectionVector * (edgeDragArea->distanceThreshold() * 0.1f);
203 QTest::touchEvent(view, device).press(0, touchPoint.toPoint());
205 QCOMPARE(draggingSpy.count(), 1);
206 QCOMPARE(edgeDragArea->dragging(), true);
208 if (wideningAngleMultiplier > 0) {
209 // go close to the border of the valid area for this touch point
210 // in order to make it easier to leave it by dragging at an angle
211 // slightly bigger than the widening angle
212 touchPoint += createTouchDeviation(edgeDragArea);
213 QTest::touchEvent(view, device).move(0, touchPoint.toPoint());
217 touchPoint += touchMovement;
218 QTest::touchEvent(view, device).move(0, touchPoint.toPoint());
219 } while ((touchPoint - initialTouchPos).manhattanLength() < desiredDragDistance);
221 if (expectedGestureRecognition)
222 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
224 if (edgeDragArea->status() == DirectionalDragArea::Rejected) {
225 QCOMPARE(edgeDragArea->dragging(), false);
226 QCOMPARE(draggingSpy.count(), 2);
229 QTest::touchEvent(view, device).release(0, touchPoint.toPoint());
231 QCOMPARE(draggingSpy.count(), 2);
232 QCOMPARE(edgeDragArea->dragging(), false);
235void tst_DirectionalDragArea::edgeDrag_data()
237 QTest::addColumn<QString>("dragAreaObjectName");
238 QTest::addColumn<qreal>("wideningAngleMultiplier");
239 QTest::addColumn<qreal>("dragDistanceFactor");
240 QTest::addColumn<int>("expectedGestureRecognition");
242 QTest::newRow("rightwards, tiny drag")
243 << "hpDragArea" << 0.0 << 0.2 << 0;
245 QTest::newRow("rightwards, straight drag")
246 << "hpDragArea" << 0.0 << 3.0 << 1;
248 QTest::newRow("rightwards, diagonal drag")
249 << "hpDragArea" << 0.9 << 3.0 << 1;
251 QTest::newRow("rightwards, overly diagonal drag")
252 << "hpDragArea" << 2.0 << 3.0 << 0;
254 QTest::newRow("leftwards, tiny drag")
255 << "hnDragArea" << 0.0 << 0.2 << 0;
257 QTest::newRow("leftwards, straight drag")
258 << "hnDragArea" << 0.0 << 3.0 << 1;
260 QTest::newRow("leftwards, diagonal drag")
261 << "hnDragArea" << 0.9 << 3.0 << 1;
263 QTest::newRow("downwards, tiny drag")
264 << "vpDragArea" << 0.0 << 0.2 << 0;
266 QTest::newRow("downwards, straight drag")
267 << "vpDragArea" << 0.0 << 3.0 << 1;
269 QTest::newRow("downwards, diagonal drag")
270 << "vpDragArea" << 0.9 << 3.0 << 1;
272 QTest::newRow("upwards, tiny drag")
273 << "vnDragArea" << 0.0 << 0.2 << 0;
275 QTest::newRow("upwards, straight drag")
276 << "vnDragArea" << 0.0 << 3.0 << 1;
278 QTest::newRow("upwards, diagonal drag")
279 << "vnDragArea" << 0.9 << 3.0 << 1;
281 QTest::newRow("upwards, overly diagonal drag")
282 << "vnDragArea" << 2.0 << 3.0 << 0;
286 A directional drag should still be recognized if there is a momentaneous, small,
287 change in the direction of a drag. That should be accounted as input noise and
290void tst_DirectionalDragArea::dragWithShortDirectionChange()
292 DirectionalDragArea *edgeDragArea =
293 view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
294 QVERIFY(edgeDragArea != 0);
295 edgeDragArea->setRecognitionTimer(fakeTimer);
296 edgeDragArea->setAxisVelocityCalculator(velCalcWithFakeTiming);
298 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea, view);
299 QPointF touchPoint = initialTouchPos;
301 qreal desiredDragDistance = edgeDragArea->distanceThreshold()*2.0;
302 QPointF dragDirectionVector(1.0, 0.0);
303 QPointF touchMovement = dragDirectionVector * (edgeDragArea->distanceThreshold() * 0.1f);
305 QTest::touchEvent(view, device).press(0, touchPoint.toPoint());
307 // Move a bit in the proper direction
308 for (int i=0; i < 3; ++i) {
309 touchPoint += touchMovement;
310 QTest::touchEvent(view, device).move(0, touchPoint.toPoint());
313 // Then a sudden and small movement to the opposite direction
314 touchPoint -= dragDirectionVector * (edgeDragArea->maxDeviation() * 0.7);
315 QTest::touchEvent(view, device).move(0, touchPoint.toPoint());
317 // And then resume movment in the correct direction until it crosses the distance threshold.
319 touchPoint += touchMovement;
320 QTest::touchEvent(view, device).move(0, touchPoint.toPoint());
321 } while ((touchPoint - initialTouchPos).manhattanLength() < desiredDragDistance);
323 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
325 QTest::touchEvent(view, device).release(0, touchPoint.toPoint());
329 Checks that a gesture will be rejected if it's slower than minSpeed while
332void tst_DirectionalDragArea::minSpeed()
334 QFETCH(int, minSpeedMsecsDeviation);
335 QFETCH(int, expectedStatusAfterSpeedCheck);
337 DirectionalDragArea *edgeDragArea =
338 view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
339 QVERIFY(edgeDragArea != 0);
340 edgeDragArea->setRecognitionTimer(fakeTimer);
341 edgeDragArea->setAxisVelocityCalculator(velCalcWithFakeTiming);
343 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea, view);
344 QPointF touchPoint = initialTouchPos;
346 QPointF dragDirectionVector(1.0, 0.0);
347 qreal distanceStep = edgeDragArea->distanceThreshold() * 0.1f;
348 QPointF touchMovement = dragDirectionVector * distanceStep;
349 qreal minSpeedMsecs = edgeDragArea->minSpeed() / 1000.0;
350 qint64 timeStepMsecs = qFloor(distanceStep / minSpeedMsecs) + minSpeedMsecsDeviation;
352 // if it fails it means the params set in the QML file are not in harmony with the values
354 Q_ASSERT(timeStepMsecs > 0);
356 fakeTimeSource->m_msecsSinceReference = 0;
357 QTest::touchEvent(view, device).press(0, touchPoint.toPoint());
359 // Move a bit in the proper direction
360 for (int i=0; i < 4; ++i) {
361 touchPoint += touchMovement;
362 fakeTimeSource->m_msecsSinceReference += timeStepMsecs;
363 QTest::touchEvent(view, device).move(0, touchPoint.toPoint());
366 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
368 // Force the periodic speed check.
369 fakeTimer->emitTimeout();
371 QCOMPARE((int)edgeDragArea->status(), expectedStatusAfterSpeedCheck);
374void tst_DirectionalDragArea::minSpeed_data()
376 QTest::addColumn<int>("minSpeedMsecsDeviation");
377 QTest::addColumn<int>("expectedStatusAfterSpeedCheck");
379 QTest::newRow("slower than minSpeed") << 20 << (int)DirectionalDragArea::Rejected;
380 QTest::newRow("faster than minSpeed") << -20 << (int)DirectionalDragArea::Undecided;
384 Checks that the recognition timer is started and stopped appropriately.
385 I.e., check that it's running only while gesture recognition is taking place
386 (status == Undecided)
388void tst_DirectionalDragArea::recognitionTimerUsage()
390 DirectionalDragArea *edgeDragArea =
391 view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
392 QVERIFY(edgeDragArea != 0);
393 edgeDragArea->setRecognitionTimer(fakeTimer);
394 edgeDragArea->setAxisVelocityCalculator(velCalcWithFakeTiming);
396 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea, view);
397 QPointF touchPoint = initialTouchPos;
399 QPointF dragDirectionVector(1.0, 0.0);
400 QPointF touchMovement = dragDirectionVector * (edgeDragArea->distanceThreshold() * 0.2f);
402 QVERIFY(!fakeTimer->isRunning());
404 QTest::touchEvent(view, device).press(0, touchPoint.toPoint());
406 QVERIFY(fakeTimer->isRunning());
408 // Move a bit in the proper direction
409 for (int i=0; i < 3; ++i) {
410 touchPoint += touchMovement;
411 QTest::touchEvent(view, device).move(0, touchPoint.toPoint());
414 QVERIFY(fakeTimer->isRunning());
416 // Move beyond distance threshold
417 touchPoint += 3*touchMovement;
418 QTest::touchEvent(view, device).move(0, touchPoint.toPoint());
420 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
422 QVERIFY(!fakeTimer->isRunning());
426 A gesture should be rejected if too much time has passed without any new input
429void tst_DirectionalDragArea::maxSilenceTime()
431 DirectionalDragArea *edgeDragArea =
432 view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
433 QVERIFY(edgeDragArea != 0);
434 edgeDragArea->setRecognitionTimer(fakeTimer);
435 edgeDragArea->setAxisVelocityCalculator(velCalcWithFakeTiming);
437 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea, view);
438 QPointF touchPoint = initialTouchPos;
440 fakeTimeSource->m_msecsSinceReference = 0;
441 QTest::touchEvent(view, device).press(0, touchPoint.toPoint());
443 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
445 int neededTimeouts = qCeil((qreal)edgeDragArea->maxSilenceTime() / (qreal)fakeTimer->interval());
446 if ((edgeDragArea->maxSilenceTime() % fakeTimer->interval()) == 0) {
450 // Force the periodic speed check.
451 for (int i = 0; i < neededTimeouts; ++i) {
452 fakeTimer->emitTimeout();
454 if (i < neededTimeouts - 1) {
455 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
459 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Rejected);
462QTEST_MAIN(tst_DirectionalDragArea)
464#include "tst_DirectionalDragArea.moc"
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list