on xenial help sign in
DirectionalDragArea: added minSpeed and maxSilenceTime constraints.

Reject gestures that are too slow or stationary.

commit60de064edeaf1f41bed153512fd057d63f8bac7a authorDaniel d'Andrada <daniel.dandrada@canonical.com> dateFri 7 Jun 2013, 15:01 (13 years ago) parentfe16363
9 files changed+400−23
Components/Stage.qml+2−1 Launcher/Launcher.qml+3−2 plugins/Ubuntu/Gestures/AxisVelocityCalculator.cpp+1−1 plugins/Ubuntu/Gestures/AxisVelocityCalculator.h+7−2 plugins/Ubuntu/Gestures/CMakeLists.txt+3 plugins/Ubuntu/Gestures/DirectionalDragArea.cpp+120−1 plugins/Ubuntu/Gestures/DirectionalDragArea.h+55 tests/plugins/Ubuntu/Gestures/RightwardsLauncher.qml+1 tests/plugins/Ubuntu/Gestures/tst_DirectionalDragArea.cpp+208−16
Components/Stage.qml+2−1
@@ -441,7 +441,8 @@ Showable {
441441 direction: DirectionalDragArea.Leftwards
442442 maxDeviation: units.gu(1)
443443 wideningAngle: 20
444− distanceThreshold: units.gu(2)
444+ distanceThreshold: units.gu(3)
445+ minSpeed: units.gu(5)
445446
446447 onDistanceChanged: {
447448 if (status !== DirectionalDragArea.Recognized)
Launcher/Launcher.qml+3−2
@@ -188,9 +188,10 @@ Item {
188188
189189 // values to be tweaked and later removed from here and set in stone as defaults
190190 // once we are confident it's all good.
191− maxDeviation: units.gu(1.5)
191+ maxDeviation: units.gu(1)
192192 wideningAngle: 30
193− distanceThreshold: units.gu(4)
193+ distanceThreshold: units.gu(3)
194+ minSpeed: units.gu(5)
194195
195196 enabled: root.available && root.state !== "visible"
196197 width: root.dragAreaWidth
plugins/Ubuntu/Gestures/AxisVelocityCalculator.cpp+1−1
@@ -85,7 +85,7 @@ void AxisVelocityCalculator::processMovement(qreal movement)
8585
8686qreal AxisVelocityCalculator::calculate() const
8787{
88− if (numSamples() < 2) {
88+ if (numSamples() < MIN_SAMPLES_NEEDED) {
8989 return 0.0;
9090 }
9191
plugins/Ubuntu/Gestures/AxisVelocityCalculator.h+7−2
@@ -95,6 +95,13 @@ public:
9595 */
9696 Q_INVOKABLE void reset();
9797
98+ int numSamples() const;
99+
100+ /*
101+ The minimum amount of samples needed for a velocity calculation.
102+ */
103+ static const int MIN_SAMPLES_NEEDED = 2;
104+
98105 /*
99106 Maximum number of movement samples stored
100107 */
@@ -115,8 +122,6 @@ private:
115122 */
116123 void processMovement(qreal movement);
117124
118− int numSamples() const;
119−
120125 class Sample
121126 {
122127 public:
plugins/Ubuntu/Gestures/CMakeLists.txt+3
@@ -16,6 +16,9 @@ add_library(UbuntuGestureQml MODULE ${UbuntuGestureQml_SOURCES})
1616
1717qt5_use_modules(UbuntuGestureQml Core Quick)
1818
19+# So that Foo.cpp can #include "Foo.moc"
20+include_directories(${CMAKE_CURRENT_BINARY_DIR})
21+
1922# copy files into build directory for shadow builds
2023add_custom_target(UbuntuGestureQmlDirFile ALL
2124 COMMAND cp "${CMAKE_CURRENT_SOURCE_DIR}/qmldir" ${CMAKE_CURRENT_BINARY_DIR}
plugins/Ubuntu/Gestures/DirectionalDragArea.cpp+120−1
@@ -17,6 +17,25 @@
1717#include "DirectionalDragArea.h"
1818
1919#include <QtCore/qmath.h>
20+#include <QtCore/QTimer>
21+
22+// Essentially a QTimer wrapper
23+class RecognitionTimer : public UbuntuGestures::AbstractTimer
24+{
25+ Q_OBJECT
26+public:
27+ RecognitionTimer(QObject *parent) : UbuntuGestures::AbstractTimer(parent) {
28+ m_timer.setSingleShot(false);
29+ connect(&m_timer, &QTimer::timeout,
30+ this, &UbuntuGestures::AbstractTimer::timeout);
31+ }
32+ virtual int interval() const { return m_timer.interval(); }
33+ virtual void setInterval(int msecs) { m_timer.setInterval(msecs); }
34+ virtual void start() { m_timer.start(); }
35+ virtual void stop() { m_timer.stop(); }
36+private:
37+ QTimer m_timer;
38+};
2039
2140DirectionalDragArea::DirectionalDragArea(QQuickItem *parent)
2241 : QQuickItem(parent)
@@ -25,8 +44,18 @@ DirectionalDragArea::DirectionalDragArea(QQuickItem *parent)
2544 , m_direction(DirectionalDragArea::Rightwards)
2645 , m_wideningAngle(0)
2746 , m_wideningFactor(0)
28− , m_distanceThreshold(0)
47+ , m_distanceThreshold(0)
48+ , m_minSpeed(0)
49+ , m_maxSilenceTime(200)
50+ , m_silenceTime(0)
51+ , m_numSamplesOnLastSpeedCheck(0)
52+ , m_recognitionTimer(0)
53+ , m_velocityCalculator(0)
2954{
55+ setRecognitionTimer(new RecognitionTimer(this));
56+ m_recognitionTimer->setInterval(60);
57+
58+ m_velocityCalculator = new AxisVelocityCalculator(this);
3059}
3160
3261DirectionalDragArea::Direction DirectionalDragArea::direction() const
@@ -73,6 +102,51 @@ void DirectionalDragArea::setDistanceThreshold(qreal value)
73102 }
74103}
75104
105+void DirectionalDragArea::setMinSpeed(qreal value)
106+{
107+ if (m_minSpeed != value) {
108+ m_minSpeed = value;
109+ Q_EMIT minSpeedChanged(value);
110+ }
111+}
112+
113+void DirectionalDragArea::setMaxSilenceTime(int value)
114+{
115+ if (m_maxSilenceTime != value) {
116+ m_maxSilenceTime = value;
117+ Q_EMIT maxSilenceTimeChanged(value);
118+ }
119+}
120+
121+void DirectionalDragArea::setRecognitionTimer(UbuntuGestures::AbstractTimer *timer)
122+{
123+ // NB: We assume test code (the only client of this method) won't call it while
124+ // the current timer is active
125+
126+ int interval = 0;
127+
128+ // can be null when called from the constructor
129+ if (m_recognitionTimer) {
130+ interval = m_recognitionTimer->interval();
131+ if (m_recognitionTimer->parent() == this) {
132+ delete m_recognitionTimer;
133+ }
134+ }
135+
136+ m_recognitionTimer = timer;
137+ timer->setInterval(interval);
138+ connect(timer, &UbuntuGestures::AbstractTimer::timeout,
139+ this, &DirectionalDragArea::checkSpeed);
140+}
141+
142+void DirectionalDragArea::setAxisVelocityCalculator(AxisVelocityCalculator *newVelCalc)
143+{
144+ if (m_velocityCalculator->parent() == this)
145+ delete m_velocityCalculator;
146+
147+ m_velocityCalculator = newVelCalc;
148+}
149+
76150qreal DirectionalDragArea::distance() const
77151{
78152 if (directionIsHorizontal()) {
@@ -122,6 +196,10 @@ void DirectionalDragArea::touchEvent_absent(QTouchEvent *event)
122196 m_startPos = event->touchPoints()[0].pos();
123197 m_touchId = event->touchPoints()[0].id();
124198 m_dampedPos.reset(m_startPos);
199+ updateVelocityCalculator(m_startPos);
200+ m_velocityCalculator->reset();
201+ m_numSamplesOnLastSpeedCheck = 0;
202+ m_silenceTime = 0;
125203 setPreviousPos(m_startPos);
126204 setStatus(Undecided);
127205 }
@@ -148,6 +226,7 @@ void DirectionalDragArea::touchEvent_undecided(QTouchEvent *event)
148226 m_previousDampedPos.setX(m_dampedPos.x());
149227 m_previousDampedPos.setY(m_dampedPos.y());
150228 m_dampedPos.update(touchPos);
229+ updateVelocityCalculator(touchPos);
151230
152231 if (!pointInsideAllowedArea()) {
153232 setStatus(Rejected);
@@ -238,6 +317,29 @@ bool DirectionalDragArea::movedFarEnough(const QPointF &point) const
238317 return qFabs(point.y() - m_startPos.y()) > m_distanceThreshold;
239318}
240319
320+void DirectionalDragArea::checkSpeed()
321+{
322+ if (m_velocityCalculator->numSamples() >= AxisVelocityCalculator::MIN_SAMPLES_NEEDED) {
323+ qreal speed = qFabs(m_velocityCalculator->calculate());
324+ qreal minSpeedMsecs = m_minSpeed / 1000.0;
325+
326+ if (speed < minSpeedMsecs) {
327+ setStatus(Rejected);
328+ }
329+ }
330+
331+ if (m_velocityCalculator->numSamples() == m_numSamplesOnLastSpeedCheck) {
332+ m_silenceTime += m_recognitionTimer->interval();
333+
334+ if (m_silenceTime > m_maxSilenceTime) {
335+ setStatus(Rejected);
336+ }
337+ } else {
338+ m_silenceTime = 0;
339+ }
340+ m_numSamplesOnLastSpeedCheck = m_velocityCalculator->numSamples();
341+}
342+
241343void DirectionalDragArea::setStatus(DirectionalDragArea::Status newStatus)
242344{
243345 if (newStatus == m_status)
@@ -245,6 +347,10 @@ void DirectionalDragArea::setStatus(DirectionalDragArea::Status newStatus)
245347
246348 DirectionalDragArea::Status oldStatus = m_status;
247349
350+ if (oldStatus == Undecided) {
351+ m_recognitionTimer->stop();
352+ }
353+
248354 m_status = newStatus;
249355 Q_EMIT statusChanged(m_status);
250356
@@ -255,6 +361,7 @@ void DirectionalDragArea::setStatus(DirectionalDragArea::Status newStatus)
255361 }
256362 break;
257363 case Undecided:
364+ m_recognitionTimer->start();
258365 Q_EMIT draggingChanged(true);
259366 break;
260367 case Rejected:
@@ -288,6 +395,15 @@ void DirectionalDragArea::setPreviousPos(QPointF point)
288395 }
289396}
290397
398+void DirectionalDragArea::updateVelocityCalculator(QPointF point)
399+{
400+ if (directionIsHorizontal()) {
401+ m_velocityCalculator->setTrackedPosition(point.x());
402+ } else {
403+ m_velocityCalculator->setTrackedPosition(point.y());
404+ }
405+}
406+
291407bool DirectionalDragArea::directionIsHorizontal() const
292408{
293409 return m_direction == Leftwards || m_direction == Rightwards;
@@ -297,3 +413,6 @@ bool DirectionalDragArea::directionIsVertical() const
297413{
298414 return m_direction == Upwards || m_direction == Downwards;
299415}
416+
417+// Because we are defining a new QObject-based class (RecognitionTimer) here.
418+#include "DirectionalDragArea.moc"
plugins/Ubuntu/Gestures/DirectionalDragArea.h+55
@@ -18,9 +18,25 @@
1818#define DIRECTIONAL_DRAG_AREA_H
1919
2020#include <QtQuick/QQuickItem>
21+#include "AxisVelocityCalculator.h"
2122#include "UbuntuGesturesGlobal.h"
2223#include "Damper.h"
2324
25+namespace UbuntuGestures {
26+/* Defines an interface for a Timer. */
27+class UBUNTUGESTURES_EXPORT AbstractTimer : public QObject {
28+ Q_OBJECT
29+public:
30+ AbstractTimer(QObject *parent) : QObject(parent) {}
31+ virtual int interval() const = 0;
32+ virtual void setInterval(int msecs) = 0;
33+ virtual void start() = 0;
34+ virtual void stop() = 0;
35+Q_SIGNALS:
36+ void timeout();
37+};
38+}
39+
2440/*
2541 An area that detects axis-aligned single-finger drag gestures
2642
@@ -74,6 +90,19 @@ class UBUNTUGESTURES_EXPORT DirectionalDragArea : public QQuickItem {
7490 Q_PROPERTY(qreal distanceThreshold READ distanceThreshold WRITE setDistanceThreshold
7591 NOTIFY distanceThresholdChanged)
7692
93+ // In units per second
94+ Q_PROPERTY(qreal minSpeed READ minSpeed WRITE setMinSpeed NOTIFY minSpeedChanged)
95+
96+ // A gesture will be rejected if more than maxSilenceTime milliseconds has
97+ // passed since we last got an input event from it (during Undecided state).
98+ //
99+ // Silence (i.e., lack of new input events) doesn't necessarily mean that the user's
100+ // finger is still (zero drag speed). In some cases the finger might be moving but
101+ // the driver's high noise filtering might cause those silence periods, specially
102+ // in the moments succeeding a press (talking about Galaxy Nexus here).
103+ Q_PROPERTY(int maxSilenceTime READ maxSilenceTime
104+ WRITE setMaxSilenceTime
105+ NOTIFY maxSilenceTimeChanged)
77106 //
78107 /////
79108
@@ -130,6 +159,20 @@ public:
130159 qreal distanceThreshold() const { return m_distanceThreshold; }
131160 void setDistanceThreshold(qreal value);
132161
162+ qreal minSpeed() const { return m_minSpeed; }
163+ void setMinSpeed(qreal value);
164+
165+ int maxSilenceTime() const { return m_maxSilenceTime; }
166+ void setMaxSilenceTime(int value);
167+
168+ // Replaces the existing Timer with the given one.
169+ //
170+ // Useful for providing a fake timer when testing.
171+ void setRecognitionTimer(UbuntuGestures::AbstractTimer *timer);
172+
173+ // Useful for testing, where a calculator with a fake time source can be supplied
174+ void setAxisVelocityCalculator(AxisVelocityCalculator *velCalc);
175+
133176Q_SIGNALS:
134177 void directionChanged(Direction direction);
135178 void statusChanged(Status value);
@@ -138,12 +181,17 @@ Q_SIGNALS:
138181 void maxDeviationChanged(qreal value);
139182 void wideningAngleChanged(qreal value);
140183 void distanceThresholdChanged(qreal value);
184+ void minSpeedChanged(qreal value);
185+ void maxSilenceTimeChanged(int value);
141186 void touchXChanged(qreal value);
142187 void touchYChanged(qreal value);
143188
144189protected:
145190 virtual void touchEvent(QTouchEvent *event);
146191
192+private Q_SLOTS:
193+ void checkSpeed();
194+
147195private:
148196 void touchEvent_absent(QTouchEvent *event);
149197 void touchEvent_undecided(QTouchEvent *event);
@@ -155,6 +203,7 @@ private:
155203 const QTouchEvent::TouchPoint *fetchTargetTouchPoint(QTouchEvent *event);
156204 void setStatus(Status newStatus);
157205 void setPreviousPos(QPointF point);
206+ void updateVelocityCalculator(QPointF point);
158207
159208 // convenience functions
160209 bool directionIsHorizontal() const;
@@ -175,6 +224,12 @@ private:
175224 qreal m_wideningAngle; // in degrees
176225 qreal m_wideningFactor; // it's tan(degreesToRadian(m_wideningAngle))
177226 qreal m_distanceThreshold;
227+ qreal m_minSpeed;
228+ int m_maxSilenceTime; // in milliseconds
229+ int m_silenceTime; // in milliseconds
230+ int m_numSamplesOnLastSpeedCheck;
231+ UbuntuGestures::AbstractTimer *m_recognitionTimer;
232+ AxisVelocityCalculator *m_velocityCalculator;
178233};
179234
180235#endif // DIRECTIONAL_DRAG_AREA_H
tests/plugins/Ubuntu/Gestures/RightwardsLauncher.qml+1
@@ -54,6 +54,7 @@ Item {
5454 maxDeviation: units.gu(2)
5555 wideningAngle: 10
5656 distanceThreshold: units.gu(4)
57+ minSpeed: 50
5758
5859 onStatusChanged: {
5960 switch (status) {
tests/plugins/Ubuntu/Gestures/tst_DirectionalDragArea.cpp+208−16
@@ -22,6 +22,35 @@
2222
2323#include <DirectionalDragArea.h>
2424
25+class FakeTimer : public UbuntuGestures::AbstractTimer
26+{
27+ Q_OBJECT
28+public:
29+ FakeTimer(QObject *parent = 0)
30+ : UbuntuGestures::AbstractTimer(parent),
31+ m_isRunning(false)
32+ {}
33+
34+ virtual int interval() const { return m_duration; }
35+ virtual void setInterval(int msecs) { m_duration = msecs; }
36+ virtual void start() { m_isRunning = true; }
37+ virtual void stop() { m_isRunning = false; }
38+
39+ bool isRunning() const { return m_isRunning; }
40+ void emitTimeout() {Q_EMIT timeout();}
41+private:
42+ int m_duration;
43+ bool m_isRunning;
44+};
45+
46+class FakeTimeSource : public AxisVelocityCalculator::TimeSource
47+{
48+public:
49+ FakeTimeSource() { m_msecsSinceReference = 0; }
50+ virtual qint64 msecsSinceReference() {return m_msecsSinceReference;}
51+ qint64 m_msecsSinceReference;
52+};
53+
2554class tst_DirectionalDragArea: public QObject
2655{
2756 Q_OBJECT
@@ -31,13 +60,24 @@ private Q_SLOTS:
3160 void initTestCase(); // will be called before the first test function is executed
3261 void cleanupTestCase(); // will be called after the last test function was executed.
3362
63+ void init(); // called right before each and every test function is executed
64+ void cleanup(); // called right after each and every test function is executed
65+
3466 void edgeDrag();
3567 void edgeDrag_data();
3668 void dragWithShortDirectionChange();
69+ void minSpeed();
70+ void minSpeed_data();
71+ void recognitionTimerUsage();
72+ void maxSilenceTime();
3773
3874private:
3975 QQuickView *createView();
76+ QQuickView *view;
4077 QTouchDevice *device;
78+ FakeTimer *fakeTimer;
79+ FakeTimeSource *fakeTimeSource;
80+ AxisVelocityCalculator *velCalcWithFakeTiming;
4181};
4282
4383void tst_DirectionalDragArea::initTestCase()
@@ -47,12 +87,42 @@ void tst_DirectionalDragArea::initTestCase()
4787 device->setType(QTouchDevice::TouchScreen);
4888 QWindowSystemInterface::registerTouchDevice(device);
4989 }
90+
91+ view = 0;
5092}
5193
5294void tst_DirectionalDragArea::cleanupTestCase()
5395{
5496}
5597
98+void tst_DirectionalDragArea::init()
99+{
100+ view = createView();
101+ view->setSource(QUrl::fromLocalFile("edgeDragExample.qml"));
102+ view->show();
103+ QVERIFY(QTest::qWaitForWindowExposed(view));
104+ QVERIFY(view->rootObject() != 0);
105+ qApp->processEvents();
106+
107+ fakeTimer = new FakeTimer;
108+ fakeTimeSource = new FakeTimeSource;
109+ velCalcWithFakeTiming = new AxisVelocityCalculator(fakeTimeSource);
110+}
111+
112+void tst_DirectionalDragArea::cleanup()
113+{
114+ delete view;
115+ view = 0;
116+
117+ delete fakeTimer;
118+ fakeTimer = 0;
119+
120+ // will bring fakeTimeSource down with it
121+ delete velCalcWithFakeTiming;
122+ fakeTimeSource = 0;
123+ velCalcWithFakeTiming = 0;
124+}
125+
56126QQuickView *tst_DirectionalDragArea::createView()
57127{
58128 QQuickView *window = new QQuickView(0);
@@ -119,17 +189,11 @@ void tst_DirectionalDragArea::edgeDrag()
119189 QFETCH(qreal, dragDistanceFactor);
120190 QFETCH(int, expectedGestureRecognition);
121191
122− QQuickView *view = createView();
123− QScopedPointer<QQuickView> scope(view);
124− view->setSource(QUrl::fromLocalFile("edgeDragExample.qml"));
125− view->show();
126− QVERIFY(QTest::qWaitForWindowExposed(view));
127− QVERIFY(view->rootObject() != 0);
128− qApp->processEvents();
129−
130192 DirectionalDragArea *edgeDragArea =
131193 view->rootObject()->findChild<DirectionalDragArea*>(dragAreaObjectName);
132194 QVERIFY(edgeDragArea != 0);
195+ edgeDragArea->setRecognitionTimer(fakeTimer);
196+ edgeDragArea->setAxisVelocityCalculator(velCalcWithFakeTiming);
133197
134198 QSignalSpy draggingSpy(edgeDragArea, SIGNAL(draggingChanged(bool)));
135199
@@ -230,17 +294,11 @@ void tst_DirectionalDragArea::edgeDrag_data()
230294 */
231295void tst_DirectionalDragArea::dragWithShortDirectionChange()
232296{
233− QQuickView *view = createView();
234− QScopedPointer<QQuickView> scope(view);
235− view->setSource(QUrl::fromLocalFile("edgeDragExample.qml"));
236− view->show();
237− QVERIFY(QTest::qWaitForWindowExposed(view));
238− QVERIFY(view->rootObject() != 0);
239− qApp->processEvents();
240−
241297 DirectionalDragArea *edgeDragArea =
242298 view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
243299 QVERIFY(edgeDragArea != 0);
300+ edgeDragArea->setRecognitionTimer(fakeTimer);
301+ edgeDragArea->setAxisVelocityCalculator(velCalcWithFakeTiming);
244302
245303 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea, view);
246304 QPointF touchPoint = initialTouchPos;
@@ -272,6 +330,140 @@ void tst_DirectionalDragArea::dragWithShortDirectionChange()
272330 QTest::touchEvent(view, device).release(0, touchPoint.toPoint());
273331}
274332
333+/*
334+ Checks that a gesture will be rejected if it's slower than minSpeed while
335+ status is Undecided.
336+ */
337+void tst_DirectionalDragArea::minSpeed()
338+{
339+ QFETCH(int, minSpeedMsecsDeviation);
340+ QFETCH(int, expectedStatusAfterSpeedCheck);
341+
342+ DirectionalDragArea *edgeDragArea =
343+ view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
344+ QVERIFY(edgeDragArea != 0);
345+ edgeDragArea->setRecognitionTimer(fakeTimer);
346+ edgeDragArea->setAxisVelocityCalculator(velCalcWithFakeTiming);
347+
348+ QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea, view);
349+ QPointF touchPoint = initialTouchPos;
350+
351+ QPointF dragDirectionVector(1.0, 0.0);
352+ qreal distanceStep = edgeDragArea->distanceThreshold() * 0.1f;
353+ QPointF touchMovement = dragDirectionVector * distanceStep;
354+ qreal minSpeedMsecs = edgeDragArea->minSpeed() / 1000.0;
355+ qint64 timeStepMsecs = qFloor(distanceStep / minSpeedMsecs) + minSpeedMsecsDeviation;
356+
357+ // if it fails it means the params set in the QML file are not in harmony with the values
358+ // used in this test
359+ Q_ASSERT(timeStepMsecs > 0);
360+
361+ fakeTimeSource->m_msecsSinceReference = 0;
362+ QTest::touchEvent(view, device).press(0, touchPoint.toPoint());
363+
364+ // Move a bit in the proper direction
365+ for (int i=0; i < 4; ++i) {
366+ touchPoint += touchMovement;
367+ fakeTimeSource->m_msecsSinceReference += timeStepMsecs;
368+ QTest::touchEvent(view, device).move(0, touchPoint.toPoint());
369+ }
370+
371+ QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
372+
373+ // Force the periodic speed check.
374+ fakeTimer->emitTimeout();
375+
376+ QCOMPARE((int)edgeDragArea->status(), expectedStatusAfterSpeedCheck);
377+}
378+
379+void tst_DirectionalDragArea::minSpeed_data()
380+{
381+ QTest::addColumn<int>("minSpeedMsecsDeviation");
382+ QTest::addColumn<int>("expectedStatusAfterSpeedCheck");
383+
384+ QTest::newRow("slower than minSpeed") << 20 << (int)DirectionalDragArea::Rejected;
385+ QTest::newRow("faster than minSpeed") << -20 << (int)DirectionalDragArea::Undecided;
386+}
387+
388+/*
389+ Checks that the recognition timer is started and stopped appropriately.
390+ I.e., check that it's running only while gesture recognition is taking place
391+ (status == Undecided)
392+ */
393+void tst_DirectionalDragArea::recognitionTimerUsage()
394+{
395+ DirectionalDragArea *edgeDragArea =
396+ view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
397+ QVERIFY(edgeDragArea != 0);
398+ edgeDragArea->setRecognitionTimer(fakeTimer);
399+ edgeDragArea->setAxisVelocityCalculator(velCalcWithFakeTiming);
400+
401+ QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea, view);
402+ QPointF touchPoint = initialTouchPos;
403+
404+ QPointF dragDirectionVector(1.0, 0.0);
405+ QPointF touchMovement = dragDirectionVector * (edgeDragArea->distanceThreshold() * 0.2f);
406+
407+ QVERIFY(!fakeTimer->isRunning());
408+
409+ QTest::touchEvent(view, device).press(0, touchPoint.toPoint());
410+
411+ QVERIFY(fakeTimer->isRunning());
412+
413+ // Move a bit in the proper direction
414+ for (int i=0; i < 3; ++i) {
415+ touchPoint += touchMovement;
416+ QTest::touchEvent(view, device).move(0, touchPoint.toPoint());
417+ }
418+
419+ QVERIFY(fakeTimer->isRunning());
420+
421+ // Move beyond distance threshold
422+ touchPoint += 3*touchMovement;
423+ QTest::touchEvent(view, device).move(0, touchPoint.toPoint());
424+
425+ QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
426+
427+ QVERIFY(!fakeTimer->isRunning());
428+}
429+
430+/*
431+ A gesture should be rejected if too much time has passed without any new input
432+ events from it.
433+ */
434+void tst_DirectionalDragArea::maxSilenceTime()
435+{
436+ DirectionalDragArea *edgeDragArea =
437+ view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
438+ QVERIFY(edgeDragArea != 0);
439+ edgeDragArea->setRecognitionTimer(fakeTimer);
440+ edgeDragArea->setAxisVelocityCalculator(velCalcWithFakeTiming);
441+
442+ QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea, view);
443+ QPointF touchPoint = initialTouchPos;
444+
445+ fakeTimeSource->m_msecsSinceReference = 0;
446+ QTest::touchEvent(view, device).press(0, touchPoint.toPoint());
447+
448+ QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
449+
450+ int neededTimeouts = qCeil((qreal)edgeDragArea->maxSilenceTime() / (qreal)fakeTimer->interval());
451+ if ((edgeDragArea->maxSilenceTime() % fakeTimer->interval()) == 0) {
452+ ++neededTimeouts;
453+ }
454+
455+ // Force the periodic speed check.
456+ for (int i = 0; i < neededTimeouts; ++i) {
457+ fakeTimer->emitTimeout();
458+
459+ if (i < neededTimeouts - 1) {
460+ QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
461+ }
462+ }
463+
464+ QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Rejected);
465+}
466+
275467QTEST_MAIN(tst_DirectionalDragArea)
276468
277469#include "tst_DirectionalDragArea.moc"