on xenial help sign in
DirectionalDragArea: added minSpeed and maxSilenceTime constraints.
60de064Daniel d'Andrada
Components/Stage.qml+2−1
@@ -441,7 +441,8 @@ Showable {
441 direction: DirectionalDragArea.Leftwards
441 direction: DirectionalDragArea.Leftwards
442 maxDeviation: units.gu(1)
442 maxDeviation: units.gu(1)
443 wideningAngle: 20
443 wideningAngle: 20
444− distanceThreshold: units.gu(2)
444+ distanceThreshold: units.gu(3)
445+ minSpeed: units.gu(5)
445
446
446 onDistanceChanged: {
447 onDistanceChanged: {
447 if (status !== DirectionalDragArea.Recognized)
448 if (status !== DirectionalDragArea.Recognized)
Launcher/Launcher.qml+3−2
@@ -188,9 +188,10 @@ Item {
188
188
189 // values to be tweaked and later removed from here and set in stone as defaults
189 // values to be tweaked and later removed from here and set in stone as defaults
190 // once we are confident it's all good.
190 // once we are confident it's all good.
191− maxDeviation: units.gu(1.5)
191+ maxDeviation: units.gu(1)
192 wideningAngle: 30
192 wideningAngle: 30
193− distanceThreshold: units.gu(4)
193+ distanceThreshold: units.gu(3)
194+ minSpeed: units.gu(5)
194
195
195 enabled: root.available && root.state !== "visible"
196 enabled: root.available && root.state !== "visible"
196 width: root.dragAreaWidth
197 width: root.dragAreaWidth
plugins/Ubuntu/Gestures/AxisVelocityCalculator.cpp+1−1
@@ -85,7 +85,7 @@ void AxisVelocityCalculator::processMovement(qreal movement)
85
85
86qreal AxisVelocityCalculator::calculate() const
86qreal AxisVelocityCalculator::calculate() const
87{
87{
88− if (numSamples() < 2) {
88+ if (numSamples() < MIN_SAMPLES_NEEDED) {
89 return 0.0;
89 return 0.0;
90 }
90 }
91
91
plugins/Ubuntu/Gestures/AxisVelocityCalculator.h+7−2
@@ -95,6 +95,13 @@ public:
95 */
95 */
96 Q_INVOKABLE void reset();
96 Q_INVOKABLE void reset();
97
97
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+
98 /*
105 /*
99 Maximum number of movement samples stored
106 Maximum number of movement samples stored
100 */
107 */
@@ -115,8 +122,6 @@ private:
115 */
122 */
116 void processMovement(qreal movement);
123 void processMovement(qreal movement);
117
124
118− int numSamples() const;
119−
120 class Sample
125 class Sample
121 {
126 {
122 public:
127 public:
plugins/Ubuntu/Gestures/CMakeLists.txt+3
@@ -16,6 +16,9 @@ add_library(UbuntuGestureQml MODULE ${UbuntuGestureQml_SOURCES})
16
16
17qt5_use_modules(UbuntuGestureQml Core Quick)
17qt5_use_modules(UbuntuGestureQml Core Quick)
18
18
19+# So that Foo.cpp can #include "Foo.moc"
20+include_directories(${CMAKE_CURRENT_BINARY_DIR})
21+
19# copy files into build directory for shadow builds
22# copy files into build directory for shadow builds
20add_custom_target(UbuntuGestureQmlDirFile ALL
23add_custom_target(UbuntuGestureQmlDirFile ALL
21 COMMAND cp "${CMAKE_CURRENT_SOURCE_DIR}/qmldir" ${CMAKE_CURRENT_BINARY_DIR}
24 COMMAND cp "${CMAKE_CURRENT_SOURCE_DIR}/qmldir" ${CMAKE_CURRENT_BINARY_DIR}
plugins/Ubuntu/Gestures/DirectionalDragArea.cpp+120−1
@@ -17,6 +17,25 @@
17#include "DirectionalDragArea.h"
17#include "DirectionalDragArea.h"
18
18
19#include <QtCore/qmath.h>
19#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+};
20
39
21DirectionalDragArea::DirectionalDragArea(QQuickItem *parent)
40DirectionalDragArea::DirectionalDragArea(QQuickItem *parent)
22 : QQuickItem(parent)
41 : QQuickItem(parent)
@@ -25,8 +44,18 @@ DirectionalDragArea::DirectionalDragArea(QQuickItem *parent)
25 , m_direction(DirectionalDragArea::Rightwards)
44 , m_direction(DirectionalDragArea::Rightwards)
26 , m_wideningAngle(0)
45 , m_wideningAngle(0)
27 , m_wideningFactor(0)
46 , 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)
29{
54{
55+ setRecognitionTimer(new RecognitionTimer(this));
56+ m_recognitionTimer->setInterval(60);
57+
58+ m_velocityCalculator = new AxisVelocityCalculator(this);
30}
59}
31
60
32DirectionalDragArea::Direction DirectionalDragArea::direction() const
61DirectionalDragArea::Direction DirectionalDragArea::direction() const
@@ -73,6 +102,51 @@ void DirectionalDragArea::setDistanceThreshold(qreal value)
73 }
102 }
74}
103}
75
104
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+
76qreal DirectionalDragArea::distance() const
150qreal DirectionalDragArea::distance() const
77{
151{
78 if (directionIsHorizontal()) {
152 if (directionIsHorizontal()) {
@@ -122,6 +196,10 @@ void DirectionalDragArea::touchEvent_absent(QTouchEvent *event)
122 m_startPos = event->touchPoints()[0].pos();
196 m_startPos = event->touchPoints()[0].pos();
123 m_touchId = event->touchPoints()[0].id();
197 m_touchId = event->touchPoints()[0].id();
124 m_dampedPos.reset(m_startPos);
198 m_dampedPos.reset(m_startPos);
199+ updateVelocityCalculator(m_startPos);
200+ m_velocityCalculator->reset();
201+ m_numSamplesOnLastSpeedCheck = 0;
202+ m_silenceTime = 0;
125 setPreviousPos(m_startPos);
203 setPreviousPos(m_startPos);
126 setStatus(Undecided);
204 setStatus(Undecided);
127 }
205 }
@@ -148,6 +226,7 @@ void DirectionalDragArea::touchEvent_undecided(QTouchEvent *event)
148 m_previousDampedPos.setX(m_dampedPos.x());
226 m_previousDampedPos.setX(m_dampedPos.x());
149 m_previousDampedPos.setY(m_dampedPos.y());
227 m_previousDampedPos.setY(m_dampedPos.y());
150 m_dampedPos.update(touchPos);
228 m_dampedPos.update(touchPos);
229+ updateVelocityCalculator(touchPos);
151
230
152 if (!pointInsideAllowedArea()) {
231 if (!pointInsideAllowedArea()) {
153 setStatus(Rejected);
232 setStatus(Rejected);
@@ -238,6 +317,29 @@ bool DirectionalDragArea::movedFarEnough(const QPointF &point) const
238 return qFabs(point.y() - m_startPos.y()) > m_distanceThreshold;
317 return qFabs(point.y() - m_startPos.y()) > m_distanceThreshold;
239}
318}
240
319
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+
241void DirectionalDragArea::setStatus(DirectionalDragArea::Status newStatus)
343void DirectionalDragArea::setStatus(DirectionalDragArea::Status newStatus)
242{
344{
243 if (newStatus == m_status)
345 if (newStatus == m_status)
@@ -245,6 +347,10 @@ void DirectionalDragArea::setStatus(DirectionalDragArea::Status newStatus)
245
347
246 DirectionalDragArea::Status oldStatus = m_status;
348 DirectionalDragArea::Status oldStatus = m_status;
247
349
350+ if (oldStatus == Undecided) {
351+ m_recognitionTimer->stop();
352+ }
353+
248 m_status = newStatus;
354 m_status = newStatus;
249 Q_EMIT statusChanged(m_status);
355 Q_EMIT statusChanged(m_status);
250
356
@@ -255,6 +361,7 @@ void DirectionalDragArea::setStatus(DirectionalDragArea::Status newStatus)
255 }
361 }
256 break;
362 break;
257 case Undecided:
363 case Undecided:
364+ m_recognitionTimer->start();
258 Q_EMIT draggingChanged(true);
365 Q_EMIT draggingChanged(true);
259 break;
366 break;
260 case Rejected:
367 case Rejected:
@@ -288,6 +395,15 @@ void DirectionalDragArea::setPreviousPos(QPointF point)
288 }
395 }
289}
396}
290
397
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+
291bool DirectionalDragArea::directionIsHorizontal() const
407bool DirectionalDragArea::directionIsHorizontal() const
292{
408{
293 return m_direction == Leftwards || m_direction == Rightwards;
409 return m_direction == Leftwards || m_direction == Rightwards;
@@ -297,3 +413,6 @@ bool DirectionalDragArea::directionIsVertical() const
297{
413{
298 return m_direction == Upwards || m_direction == Downwards;
414 return m_direction == Upwards || m_direction == Downwards;
299}
415}
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 @@
18#define DIRECTIONAL_DRAG_AREA_H
18#define DIRECTIONAL_DRAG_AREA_H
19
19
20#include <QtQuick/QQuickItem>
20#include <QtQuick/QQuickItem>
21+#include "AxisVelocityCalculator.h"
21#include "UbuntuGesturesGlobal.h"
22#include "UbuntuGesturesGlobal.h"
22#include "Damper.h"
23#include "Damper.h"
23
24
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+
24/*
40/*
25 An area that detects axis-aligned single-finger drag gestures
41 An area that detects axis-aligned single-finger drag gestures
26
42
@@ -74,6 +90,19 @@ class UBUNTUGESTURES_EXPORT DirectionalDragArea : public QQuickItem {
74 Q_PROPERTY(qreal distanceThreshold READ distanceThreshold WRITE setDistanceThreshold
90 Q_PROPERTY(qreal distanceThreshold READ distanceThreshold WRITE setDistanceThreshold
75 NOTIFY distanceThresholdChanged)
91 NOTIFY distanceThresholdChanged)
76
92
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)
77 //
106 //
78 /////
107 /////
79
108
@@ -130,6 +159,20 @@ public:
130 qreal distanceThreshold() const { return m_distanceThreshold; }
159 qreal distanceThreshold() const { return m_distanceThreshold; }
131 void setDistanceThreshold(qreal value);
160 void setDistanceThreshold(qreal value);
132
161
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+
133Q_SIGNALS:
176Q_SIGNALS:
134 void directionChanged(Direction direction);
177 void directionChanged(Direction direction);
135 void statusChanged(Status value);
178 void statusChanged(Status value);
@@ -138,12 +181,17 @@ Q_SIGNALS:
138 void maxDeviationChanged(qreal value);
181 void maxDeviationChanged(qreal value);
139 void wideningAngleChanged(qreal value);
182 void wideningAngleChanged(qreal value);
140 void distanceThresholdChanged(qreal value);
183 void distanceThresholdChanged(qreal value);
184+ void minSpeedChanged(qreal value);
185+ void maxSilenceTimeChanged(int value);
141 void touchXChanged(qreal value);
186 void touchXChanged(qreal value);
142 void touchYChanged(qreal value);
187 void touchYChanged(qreal value);
143
188
144protected:
189protected:
145 virtual void touchEvent(QTouchEvent *event);
190 virtual void touchEvent(QTouchEvent *event);
146
191
192+private Q_SLOTS:
193+ void checkSpeed();
194+
147private:
195private:
148 void touchEvent_absent(QTouchEvent *event);
196 void touchEvent_absent(QTouchEvent *event);
149 void touchEvent_undecided(QTouchEvent *event);
197 void touchEvent_undecided(QTouchEvent *event);
@@ -155,6 +203,7 @@ private:
155 const QTouchEvent::TouchPoint *fetchTargetTouchPoint(QTouchEvent *event);
203 const QTouchEvent::TouchPoint *fetchTargetTouchPoint(QTouchEvent *event);
156 void setStatus(Status newStatus);
204 void setStatus(Status newStatus);
157 void setPreviousPos(QPointF point);
205 void setPreviousPos(QPointF point);
206+ void updateVelocityCalculator(QPointF point);
158
207
159 // convenience functions
208 // convenience functions
160 bool directionIsHorizontal() const;
209 bool directionIsHorizontal() const;
@@ -175,6 +224,12 @@ private:
175 qreal m_wideningAngle; // in degrees
224 qreal m_wideningAngle; // in degrees
176 qreal m_wideningFactor; // it's tan(degreesToRadian(m_wideningAngle))
225 qreal m_wideningFactor; // it's tan(degreesToRadian(m_wideningAngle))
177 qreal m_distanceThreshold;
226 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;
178};
233};
179
234
180#endif // DIRECTIONAL_DRAG_AREA_H
235#endif // DIRECTIONAL_DRAG_AREA_H
tests/plugins/Ubuntu/Gestures/RightwardsLauncher.qml+1
@@ -54,6 +54,7 @@ Item {
54 maxDeviation: units.gu(2)
54 maxDeviation: units.gu(2)
55 wideningAngle: 10
55 wideningAngle: 10
56 distanceThreshold: units.gu(4)
56 distanceThreshold: units.gu(4)
57+ minSpeed: 50
57
58
58 onStatusChanged: {
59 onStatusChanged: {
59 switch (status) {
60 switch (status) {
tests/plugins/Ubuntu/Gestures/tst_DirectionalDragArea.cpp+208−16
@@ -22,6 +22,35 @@
22
22
23#include <DirectionalDragArea.h>
23#include <DirectionalDragArea.h>
24
24
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+
25class tst_DirectionalDragArea: public QObject
54class tst_DirectionalDragArea: public QObject
26{
55{
27 Q_OBJECT
56 Q_OBJECT
@@ -31,13 +60,24 @@ private Q_SLOTS:
31 void initTestCase(); // will be called before the first test function is executed
60 void initTestCase(); // will be called before the first test function is executed
32 void cleanupTestCase(); // will be called after the last test function was executed.
61 void cleanupTestCase(); // will be called after the last test function was executed.
33
62
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+
34 void edgeDrag();
66 void edgeDrag();
35 void edgeDrag_data();
67 void edgeDrag_data();
36 void dragWithShortDirectionChange();
68 void dragWithShortDirectionChange();
69+ void minSpeed();
70+ void minSpeed_data();
71+ void recognitionTimerUsage();
72+ void maxSilenceTime();
37
73
38private:
74private:
39 QQuickView *createView();
75 QQuickView *createView();
76+ QQuickView *view;
40 QTouchDevice *device;
77 QTouchDevice *device;
78+ FakeTimer *fakeTimer;
79+ FakeTimeSource *fakeTimeSource;
80+ AxisVelocityCalculator *velCalcWithFakeTiming;
41};
81};
42
82
43void tst_DirectionalDragArea::initTestCase()
83void tst_DirectionalDragArea::initTestCase()
@@ -47,12 +87,42 @@ void tst_DirectionalDragArea::initTestCase()
47 device->setType(QTouchDevice::TouchScreen);
87 device->setType(QTouchDevice::TouchScreen);
48 QWindowSystemInterface::registerTouchDevice(device);
88 QWindowSystemInterface::registerTouchDevice(device);
49 }
89 }
90+
91+ view = 0;
50}
92}
51
93
52void tst_DirectionalDragArea::cleanupTestCase()
94void tst_DirectionalDragArea::cleanupTestCase()
53{
95{
54}
96}
55
97
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+
56QQuickView *tst_DirectionalDragArea::createView()
126QQuickView *tst_DirectionalDragArea::createView()
57{
127{
58 QQuickView *window = new QQuickView(0);
128 QQuickView *window = new QQuickView(0);
@@ -119,17 +189,11 @@ void tst_DirectionalDragArea::edgeDrag()
119 QFETCH(qreal, dragDistanceFactor);
189 QFETCH(qreal, dragDistanceFactor);
120 QFETCH(int, expectedGestureRecognition);
190 QFETCH(int, expectedGestureRecognition);
121
191
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−
130 DirectionalDragArea *edgeDragArea =
192 DirectionalDragArea *edgeDragArea =
131 view->rootObject()->findChild<DirectionalDragArea*>(dragAreaObjectName);
193 view->rootObject()->findChild<DirectionalDragArea*>(dragAreaObjectName);
132 QVERIFY(edgeDragArea != 0);
194 QVERIFY(edgeDragArea != 0);
195+ edgeDragArea->setRecognitionTimer(fakeTimer);
196+ edgeDragArea->setAxisVelocityCalculator(velCalcWithFakeTiming);
133
197
134 QSignalSpy draggingSpy(edgeDragArea, SIGNAL(draggingChanged(bool)));
198 QSignalSpy draggingSpy(edgeDragArea, SIGNAL(draggingChanged(bool)));
135
199
@@ -230,17 +294,11 @@ void tst_DirectionalDragArea::edgeDrag_data()
230 */
294 */
231void tst_DirectionalDragArea::dragWithShortDirectionChange()
295void tst_DirectionalDragArea::dragWithShortDirectionChange()
232{
296{
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−
241 DirectionalDragArea *edgeDragArea =
297 DirectionalDragArea *edgeDragArea =
242 view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
298 view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
243 QVERIFY(edgeDragArea != 0);
299 QVERIFY(edgeDragArea != 0);
300+ edgeDragArea->setRecognitionTimer(fakeTimer);
301+ edgeDragArea->setAxisVelocityCalculator(velCalcWithFakeTiming);
244
302
245 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea, view);
303 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea, view);
246 QPointF touchPoint = initialTouchPos;
304 QPointF touchPoint = initialTouchPos;
@@ -272,6 +330,140 @@ void tst_DirectionalDragArea::dragWithShortDirectionChange()
272 QTest::touchEvent(view, device).release(0, touchPoint.toPoint());
330 QTest::touchEvent(view, device).release(0, touchPoint.toPoint());
273}
331}
274
332
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+
275QTEST_MAIN(tst_DirectionalDragArea)
467QTEST_MAIN(tst_DirectionalDragArea)
276
468
277#include "tst_DirectionalDragArea.moc"
469#include "tst_DirectionalDragArea.moc"