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

Reject gestures that are too slow or stationary. Approved by Michael Zanetti, PS Jenkins bot.

commit0ffb04f3cf5c13dac19aea3432a06f64be49d779 authorDaniel d'Andrada <daniel.dandrada@canonical.com> dateWed 12 Jun 2013, 09:13 (13 years ago) parent320fda2ffa9e4a
10 files changed+406−25
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+121 plugins/Ubuntu/Gestures/DirectionalDragArea.h+60 tests/plugins/Ubuntu/Gestures/CMakeLists.txt+5−3 tests/plugins/Ubuntu/Gestures/RightwardsLauncher.qml+1 tests/plugins/Ubuntu/Gestures/tst_DirectionalDragArea.cpp+203−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+121
@@ -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(); UbuntuGestures::AbstractTimer::start(); }
35+ virtual void stop() { m_timer.stop(); UbuntuGestures::AbstractTimer::stop(); }
36+private:
37+ QTimer m_timer;
38+};
2039
2140DirectionalDragArea::DirectionalDragArea(QQuickItem *parent)
2241 : QQuickItem(parent)
@@ -26,7 +45,17 @@ DirectionalDragArea::DirectionalDragArea(QQuickItem *parent)
2645 , m_wideningAngle(0)
2746 , m_wideningFactor(0)
2847 , 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,53 @@ 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+ int interval = 0;
124+ bool timerWasRunning = false;
125+
126+ // can be null when called from the constructor
127+ if (m_recognitionTimer) {
128+ interval = m_recognitionTimer->interval();
129+ timerWasRunning = m_recognitionTimer->isRunning();
130+ if (m_recognitionTimer->parent() == this) {
131+ delete m_recognitionTimer;
132+ }
133+ }
134+
135+ m_recognitionTimer = timer;
136+ timer->setInterval(interval);
137+ connect(timer, &UbuntuGestures::AbstractTimer::timeout,
138+ this, &DirectionalDragArea::checkSpeed);
139+ if (timerWasRunning) {
140+ m_recognitionTimer->start();
141+ }
142+}
143+
144+void DirectionalDragArea::setAxisVelocityCalculator(AxisVelocityCalculator *newVelCalc)
145+{
146+ if (m_velocityCalculator->parent() == this)
147+ delete m_velocityCalculator;
148+
149+ m_velocityCalculator = newVelCalc;
150+}
151+
76152qreal DirectionalDragArea::distance() const
77153{
78154 if (directionIsHorizontal()) {
@@ -122,6 +198,10 @@ void DirectionalDragArea::touchEvent_absent(QTouchEvent *event)
122198 m_startPos = event->touchPoints()[0].pos();
123199 m_touchId = event->touchPoints()[0].id();
124200 m_dampedPos.reset(m_startPos);
201+ updateVelocityCalculator(m_startPos);
202+ m_velocityCalculator->reset();
203+ m_numSamplesOnLastSpeedCheck = 0;
204+ m_silenceTime = 0;
125205 setPreviousPos(m_startPos);
126206 setStatus(Undecided);
127207 }
@@ -148,6 +228,7 @@ void DirectionalDragArea::touchEvent_undecided(QTouchEvent *event)
148228 m_previousDampedPos.setX(m_dampedPos.x());
149229 m_previousDampedPos.setY(m_dampedPos.y());
150230 m_dampedPos.update(touchPos);
231+ updateVelocityCalculator(touchPos);
151232
152233 if (!pointInsideAllowedArea()) {
153234 setStatus(Rejected);
@@ -238,6 +319,29 @@ bool DirectionalDragArea::movedFarEnough(const QPointF &point) const
238319 return qFabs(point.y() - m_startPos.y()) > m_distanceThreshold;
239320}
240321
322+void DirectionalDragArea::checkSpeed()
323+{
324+ if (m_velocityCalculator->numSamples() >= AxisVelocityCalculator::MIN_SAMPLES_NEEDED) {
325+ qreal speed = qFabs(m_velocityCalculator->calculate());
326+ qreal minSpeedMsecs = m_minSpeed / 1000.0;
327+
328+ if (speed < minSpeedMsecs) {
329+ setStatus(Rejected);
330+ }
331+ }
332+
333+ if (m_velocityCalculator->numSamples() == m_numSamplesOnLastSpeedCheck) {
334+ m_silenceTime += m_recognitionTimer->interval();
335+
336+ if (m_silenceTime > m_maxSilenceTime) {
337+ setStatus(Rejected);
338+ }
339+ } else {
340+ m_silenceTime = 0;
341+ }
342+ m_numSamplesOnLastSpeedCheck = m_velocityCalculator->numSamples();
343+}
344+
241345void DirectionalDragArea::setStatus(DirectionalDragArea::Status newStatus)
242346{
243347 if (newStatus == m_status)
@@ -245,6 +349,10 @@ void DirectionalDragArea::setStatus(DirectionalDragArea::Status newStatus)
245349
246350 DirectionalDragArea::Status oldStatus = m_status;
247351
352+ if (oldStatus == Undecided) {
353+ m_recognitionTimer->stop();
354+ }
355+
248356 m_status = newStatus;
249357 Q_EMIT statusChanged(m_status);
250358
@@ -255,6 +363,7 @@ void DirectionalDragArea::setStatus(DirectionalDragArea::Status newStatus)
255363 }
256364 break;
257365 case Undecided:
366+ m_recognitionTimer->start();
258367 Q_EMIT draggingChanged(true);
259368 break;
260369 case Rejected:
@@ -288,6 +397,15 @@ void DirectionalDragArea::setPreviousPos(QPointF point)
288397 }
289398}
290399
400+void DirectionalDragArea::updateVelocityCalculator(QPointF point)
401+{
402+ if (directionIsHorizontal()) {
403+ m_velocityCalculator->setTrackedPosition(point.x());
404+ } else {
405+ m_velocityCalculator->setTrackedPosition(point.y());
406+ }
407+}
408+
291409bool DirectionalDragArea::directionIsHorizontal() const
292410{
293411 return m_direction == Leftwards || m_direction == Rightwards;
@@ -297,3 +415,6 @@ bool DirectionalDragArea::directionIsVertical() const
297415{
298416 return m_direction == Upwards || m_direction == Downwards;
299417}
418+
419+// Because we are defining a new QObject-based class (RecognitionTimer) here.
420+#include "DirectionalDragArea.moc"
plugins/Ubuntu/Gestures/DirectionalDragArea.h+60
@@ -18,9 +18,28 @@
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), m_isRunning(false) {}
31+ virtual int interval() const = 0;
32+ virtual void setInterval(int msecs) = 0;
33+ virtual void start() { m_isRunning = true; };
34+ virtual void stop() { m_isRunning = false; }
35+ bool isRunning() const { return m_isRunning; }
36+Q_SIGNALS:
37+ void timeout();
38+private:
39+ bool m_isRunning;
40+};
41+}
42+
2443/*
2544 An area that detects axis-aligned single-finger drag gestures
2645
@@ -74,6 +93,21 @@ class UBUNTUGESTURES_EXPORT DirectionalDragArea : public QQuickItem {
7493 Q_PROPERTY(qreal distanceThreshold READ distanceThreshold WRITE setDistanceThreshold
7594 NOTIFY distanceThresholdChanged)
7695
96+ // Minimum speed a gesture needs to have in order to be recognized as a
97+ // directional drag.
98+ // In pixels per second
99+ Q_PROPERTY(qreal minSpeed READ minSpeed WRITE setMinSpeed NOTIFY minSpeedChanged)
100+
101+ // A gesture will be rejected if more than maxSilenceTime milliseconds has
102+ // passed since we last got an input event from it (during Undecided state).
103+ //
104+ // Silence (i.e., lack of new input events) doesn't necessarily mean that the user's
105+ // finger is still (zero drag speed). In some cases the finger might be moving but
106+ // the driver's high noise filtering might cause those silence periods, specially
107+ // in the moments succeeding a press (talking about Galaxy Nexus here).
108+ Q_PROPERTY(int maxSilenceTime READ maxSilenceTime
109+ WRITE setMaxSilenceTime
110+ NOTIFY maxSilenceTimeChanged)
77111 //
78112 /////
79113
@@ -130,6 +164,20 @@ public:
130164 qreal distanceThreshold() const { return m_distanceThreshold; }
131165 void setDistanceThreshold(qreal value);
132166
167+ qreal minSpeed() const { return m_minSpeed; }
168+ void setMinSpeed(qreal value);
169+
170+ int maxSilenceTime() const { return m_maxSilenceTime; }
171+ void setMaxSilenceTime(int value);
172+
173+ // Replaces the existing Timer with the given one.
174+ //
175+ // Useful for providing a fake timer when testing.
176+ void setRecognitionTimer(UbuntuGestures::AbstractTimer *timer);
177+
178+ // Useful for testing, where a calculator with a fake time source can be supplied
179+ void setAxisVelocityCalculator(AxisVelocityCalculator *velCalc);
180+
133181Q_SIGNALS:
134182 void directionChanged(Direction direction);
135183 void statusChanged(Status value);
@@ -138,12 +186,17 @@ Q_SIGNALS:
138186 void maxDeviationChanged(qreal value);
139187 void wideningAngleChanged(qreal value);
140188 void distanceThresholdChanged(qreal value);
189+ void minSpeedChanged(qreal value);
190+ void maxSilenceTimeChanged(int value);
141191 void touchXChanged(qreal value);
142192 void touchYChanged(qreal value);
143193
144194protected:
145195 virtual void touchEvent(QTouchEvent *event);
146196
197+private Q_SLOTS:
198+ void checkSpeed();
199+
147200private:
148201 void touchEvent_absent(QTouchEvent *event);
149202 void touchEvent_undecided(QTouchEvent *event);
@@ -155,6 +208,7 @@ private:
155208 const QTouchEvent::TouchPoint *fetchTargetTouchPoint(QTouchEvent *event);
156209 void setStatus(Status newStatus);
157210 void setPreviousPos(QPointF point);
211+ void updateVelocityCalculator(QPointF point);
158212
159213 // convenience functions
160214 bool directionIsHorizontal() const;
@@ -175,6 +229,12 @@ private:
175229 qreal m_wideningAngle; // in degrees
176230 qreal m_wideningFactor; // it's tan(degreesToRadian(m_wideningAngle))
177231 qreal m_distanceThreshold;
232+ qreal m_minSpeed;
233+ int m_maxSilenceTime; // in milliseconds
234+ int m_silenceTime; // in milliseconds
235+ int m_numSamplesOnLastSpeedCheck;
236+ UbuntuGestures::AbstractTimer *m_recognitionTimer;
237+ AxisVelocityCalculator *m_velocityCalculator;
178238};
179239
180240#endif // DIRECTIONAL_DRAG_AREA_H
tests/plugins/Ubuntu/Gestures/CMakeLists.txt+5−3
@@ -11,9 +11,9 @@ set(testCommand
1111 -o ${CMAKE_BINARY_DIR}/DirectionalDragAreaTest.xml,xunitxml
1212 -o -,txt)
1313
14−add_custom_target(DirectionalDragAreaTest ${testCommand})
15−add_dependencies(qmluitests DirectionalDragAreaTest)
16−add_dependencies(DirectionalDragAreaTest DirectionalDragAreaTestExec UbutunGesturesTestQmlFiles)
14+add_custom_target(testDirectionalDragArea ${testCommand})
15+add_dependencies(qmluitests testDirectionalDragArea)
16+add_dependencies(testDirectionalDragArea DirectionalDragAreaTestExec UbutunGesturesTestQmlFiles)
1717
1818add_executable(DirectionalDragAreaTestExec tst_DirectionalDragArea.cpp)
1919qt5_use_modules(DirectionalDragAreaTestExec Test Core Qml Gui Quick)
@@ -27,6 +27,8 @@ add_custom_target(UbuntuGesturesTestQmlFiles ALL
2727 DEPENDS ${qmlFiles}
2828)
2929
30+########## other tests
31+
3032macro(add_gesture_test CLASSNAME)
3133 set(testCommand
3234 ${CLASSNAME}TestExec -o ${CMAKE_BINARY_DIR}/test${CLASSNAME}.xml,xunitxml -o -,txt)
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+203−16
@@ -22,6 +22,30 @@
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+ {}
32+
33+ virtual int interval() const { return m_duration; }
34+ virtual void setInterval(int msecs) { m_duration = msecs; }
35+
36+ void emitTimeout() {Q_EMIT timeout();}
37+private:
38+ int m_duration;
39+};
40+
41+class FakeTimeSource : public AxisVelocityCalculator::TimeSource
42+{
43+public:
44+ FakeTimeSource() { m_msecsSinceReference = 0; }
45+ virtual qint64 msecsSinceReference() {return m_msecsSinceReference;}
46+ qint64 m_msecsSinceReference;
47+};
48+
2549class tst_DirectionalDragArea: public QObject
2650{
2751 Q_OBJECT
@@ -31,13 +55,24 @@ private Q_SLOTS:
3155 void initTestCase(); // will be called before the first test function is executed
3256 void cleanupTestCase(); // will be called after the last test function was executed.
3357
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
60+
3461 void edgeDrag();
3562 void edgeDrag_data();
3663 void dragWithShortDirectionChange();
64+ void minSpeed();
65+ void minSpeed_data();
66+ void recognitionTimerUsage();
67+ void maxSilenceTime();
3768
3869private:
3970 QQuickView *createView();
71+ QQuickView *view;
4072 QTouchDevice *device;
73+ FakeTimer *fakeTimer;
74+ FakeTimeSource *fakeTimeSource;
75+ AxisVelocityCalculator *velCalcWithFakeTiming;
4176};
4277
4378void tst_DirectionalDragArea::initTestCase()
@@ -47,12 +82,42 @@ void tst_DirectionalDragArea::initTestCase()
4782 device->setType(QTouchDevice::TouchScreen);
4883 QWindowSystemInterface::registerTouchDevice(device);
4984 }
85+
86+ view = 0;
5087}
5188
5289void tst_DirectionalDragArea::cleanupTestCase()
5390{
5491}
5592
93+void tst_DirectionalDragArea::init()
94+{
95+ view = createView();
96+ view->setSource(QUrl::fromLocalFile("edgeDragExample.qml"));
97+ view->show();
98+ QVERIFY(QTest::qWaitForWindowExposed(view));
99+ QVERIFY(view->rootObject() != 0);
100+ qApp->processEvents();
101+
102+ fakeTimer = new FakeTimer;
103+ fakeTimeSource = new FakeTimeSource;
104+ velCalcWithFakeTiming = new AxisVelocityCalculator(fakeTimeSource);
105+}
106+
107+void tst_DirectionalDragArea::cleanup()
108+{
109+ delete view;
110+ view = 0;
111+
112+ delete fakeTimer;
113+ fakeTimer = 0;
114+
115+ // will bring fakeTimeSource down with it
116+ delete velCalcWithFakeTiming;
117+ fakeTimeSource = 0;
118+ velCalcWithFakeTiming = 0;
119+}
120+
56121QQuickView *tst_DirectionalDragArea::createView()
57122{
58123 QQuickView *window = new QQuickView(0);
@@ -119,17 +184,11 @@ void tst_DirectionalDragArea::edgeDrag()
119184 QFETCH(qreal, dragDistanceFactor);
120185 QFETCH(int, expectedGestureRecognition);
121186
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−
130187 DirectionalDragArea *edgeDragArea =
131188 view->rootObject()->findChild<DirectionalDragArea*>(dragAreaObjectName);
132189 QVERIFY(edgeDragArea != 0);
190+ edgeDragArea->setRecognitionTimer(fakeTimer);
191+ edgeDragArea->setAxisVelocityCalculator(velCalcWithFakeTiming);
133192
134193 QSignalSpy draggingSpy(edgeDragArea, SIGNAL(draggingChanged(bool)));
135194
@@ -230,17 +289,11 @@ void tst_DirectionalDragArea::edgeDrag_data()
230289 */
231290void tst_DirectionalDragArea::dragWithShortDirectionChange()
232291{
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−
241292 DirectionalDragArea *edgeDragArea =
242293 view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
243294 QVERIFY(edgeDragArea != 0);
295+ edgeDragArea->setRecognitionTimer(fakeTimer);
296+ edgeDragArea->setAxisVelocityCalculator(velCalcWithFakeTiming);
244297
245298 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea, view);
246299 QPointF touchPoint = initialTouchPos;
@@ -272,6 +325,140 @@ void tst_DirectionalDragArea::dragWithShortDirectionChange()
272325 QTest::touchEvent(view, device).release(0, touchPoint.toPoint());
273326}
274327
328+/*
329+ Checks that a gesture will be rejected if it's slower than minSpeed while
330+ status is Undecided.
331+ */
332+void tst_DirectionalDragArea::minSpeed()
333+{
334+ QFETCH(int, minSpeedMsecsDeviation);
335+ QFETCH(int, expectedStatusAfterSpeedCheck);
336+
337+ DirectionalDragArea *edgeDragArea =
338+ view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
339+ QVERIFY(edgeDragArea != 0);
340+ edgeDragArea->setRecognitionTimer(fakeTimer);
341+ edgeDragArea->setAxisVelocityCalculator(velCalcWithFakeTiming);
342+
343+ QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea, view);
344+ QPointF touchPoint = initialTouchPos;
345+
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;
351+
352+ // if it fails it means the params set in the QML file are not in harmony with the values
353+ // used in this test
354+ Q_ASSERT(timeStepMsecs > 0);
355+
356+ fakeTimeSource->m_msecsSinceReference = 0;
357+ QTest::touchEvent(view, device).press(0, touchPoint.toPoint());
358+
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());
364+ }
365+
366+ QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
367+
368+ // Force the periodic speed check.
369+ fakeTimer->emitTimeout();
370+
371+ QCOMPARE((int)edgeDragArea->status(), expectedStatusAfterSpeedCheck);
372+}
373+
374+void tst_DirectionalDragArea::minSpeed_data()
375+{
376+ QTest::addColumn<int>("minSpeedMsecsDeviation");
377+ QTest::addColumn<int>("expectedStatusAfterSpeedCheck");
378+
379+ QTest::newRow("slower than minSpeed") << 20 << (int)DirectionalDragArea::Rejected;
380+ QTest::newRow("faster than minSpeed") << -20 << (int)DirectionalDragArea::Undecided;
381+}
382+
383+/*
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)
387+ */
388+void tst_DirectionalDragArea::recognitionTimerUsage()
389+{
390+ DirectionalDragArea *edgeDragArea =
391+ view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
392+ QVERIFY(edgeDragArea != 0);
393+ edgeDragArea->setRecognitionTimer(fakeTimer);
394+ edgeDragArea->setAxisVelocityCalculator(velCalcWithFakeTiming);
395+
396+ QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea, view);
397+ QPointF touchPoint = initialTouchPos;
398+
399+ QPointF dragDirectionVector(1.0, 0.0);
400+ QPointF touchMovement = dragDirectionVector * (edgeDragArea->distanceThreshold() * 0.2f);
401+
402+ QVERIFY(!fakeTimer->isRunning());
403+
404+ QTest::touchEvent(view, device).press(0, touchPoint.toPoint());
405+
406+ QVERIFY(fakeTimer->isRunning());
407+
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());
412+ }
413+
414+ QVERIFY(fakeTimer->isRunning());
415+
416+ // Move beyond distance threshold
417+ touchPoint += 3*touchMovement;
418+ QTest::touchEvent(view, device).move(0, touchPoint.toPoint());
419+
420+ QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
421+
422+ QVERIFY(!fakeTimer->isRunning());
423+}
424+
425+/*
426+ A gesture should be rejected if too much time has passed without any new input
427+ events from it.
428+ */
429+void tst_DirectionalDragArea::maxSilenceTime()
430+{
431+ DirectionalDragArea *edgeDragArea =
432+ view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
433+ QVERIFY(edgeDragArea != 0);
434+ edgeDragArea->setRecognitionTimer(fakeTimer);
435+ edgeDragArea->setAxisVelocityCalculator(velCalcWithFakeTiming);
436+
437+ QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea, view);
438+ QPointF touchPoint = initialTouchPos;
439+
440+ fakeTimeSource->m_msecsSinceReference = 0;
441+ QTest::touchEvent(view, device).press(0, touchPoint.toPoint());
442+
443+ QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
444+
445+ int neededTimeouts = qCeil((qreal)edgeDragArea->maxSilenceTime() / (qreal)fakeTimer->interval());
446+ if ((edgeDragArea->maxSilenceTime() % fakeTimer->interval()) == 0) {
447+ ++neededTimeouts;
448+ }
449+
450+ // Force the periodic speed check.
451+ for (int i = 0; i < neededTimeouts; ++i) {
452+ fakeTimer->emitTimeout();
453+
454+ if (i < neededTimeouts - 1) {
455+ QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
456+ }
457+ }
458+
459+ QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Rejected);
460+}
461+
275462QTEST_MAIN(tst_DirectionalDragArea)
276463
277464#include "tst_DirectionalDragArea.moc"