Merge lp:~dandrader/unity8/drag-crash-1228336 into lp:unity8

Proposed by Daniel d'Andrada
Status: Merged
Approved by: Albert Astals Cid
Approved revision: 500
Merged at revision: 507
Proposed branch: lp:~dandrader/unity8/drag-crash-1228336
Merge into: lp:unity8
Diff against target: 2073 lines (+923/-335)
21 files modified
Components/DragHandle.qml (+21/-22)
Components/EdgeDragArea.qml (+12/-4)
Components/Stage.qml (+8/-7)
plugins/Ubuntu/Gestures/AxisVelocityCalculator.cpp (+9/-18)
plugins/Ubuntu/Gestures/AxisVelocityCalculator.h (+9/-16)
plugins/Ubuntu/Gestures/CMakeLists.txt (+1/-0)
plugins/Ubuntu/Gestures/DirectionalDragArea.cpp (+315/-79)
plugins/Ubuntu/Gestures/DirectionalDragArea.h (+50/-13)
plugins/Ubuntu/Gestures/TimeSource.cpp (+49/-0)
plugins/Ubuntu/Gestures/TimeSource.h (+54/-0)
tests/plugins/Ubuntu/Gestures/DownwardsLauncher.qml (+2/-10)
tests/plugins/Ubuntu/Gestures/LeftwardsLauncher.qml (+5/-13)
tests/plugins/Ubuntu/Gestures/RightwardsLauncher.qml (+3/-10)
tests/plugins/Ubuntu/Gestures/UpwardsLauncher.qml (+5/-13)
tests/plugins/Ubuntu/Gestures/edgeDragExample.qml (+7/-1)
tests/plugins/Ubuntu/Gestures/tst_AxisVelocityCalculator.cpp (+3/-3)
tests/plugins/Ubuntu/Gestures/tst_DirectionalDragArea.cpp (+297/-108)
tests/qmltests/Components/tst_DragHandle.cpp (+4/-5)
tests/qmltests/Panel/tst_Panel.qml (+0/-1)
tests/qmltests/tst_Shell.qml (+35/-12)
tests/utils/modules/Unity/Test/UnityTestCase.qml (+34/-0)
To merge this branch: bzr merge lp:~dandrader/unity8/drag-crash-1228336
Reviewer Review Type Date Requested Status
PS Jenkins bot (community) continuous-integration Approve
Albert Astals Cid (community) Approve
Michael Zanetti (community) Approve
Review via email: mp+192183@code.launchpad.net

Commit message

Improve DirectionalDragArea

- Removed Rejected status, simplifying state machine
- Added compositionTime property. Multi-finger scenarios are better handled now.
- Refactored TimeSource in Ubuntu.Gestures plugin
- Added an easy way to debug DirectionalDragArea by having switchable debug prints
- Updated tests to also simulate the passage of time
- Use touch point scene coordinates for gesture recognition so that moving the
  DirectionalDragArea (as in a hinting DragHandle) won't affect it

Description of the change

Make sure you have a fresh lp:qtubuntu (that includes revision 191) when testing.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

FAILED: Continuous integration, rev:477
http://jenkins.qa.ubuntu.com/job/unity8-ci/1476/
Executed test runs:
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-saucy/5135
    FAILURE: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-touch/3161/console
    UNSTABLE: http://jenkins.qa.ubuntu.com/job/unity-phablet-qmluitests-saucy/2348
    SUCCESS: http://jenkins.qa.ubuntu.com/job/unity8-saucy-amd64-ci/499
    SUCCESS: http://jenkins.qa.ubuntu.com/job/unity8-saucy-armhf-ci/1480
        deb: http://jenkins.qa.ubuntu.com/job/unity8-saucy-armhf-ci/1480/artifact/work/output/*zip*/output.zip
    SUCCESS: http://jenkins.qa.ubuntu.com/job/unity8-saucy-i386-ci/1475
    SUCCESS: http://jenkins.qa.ubuntu.com/job/autopilot-testrunner-otto-saucy/1298
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-saucy-amd64/1010
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-saucy-amd64/1010/artifact/work/output/*zip*/output.zip
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-saucy-armhf/3163
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-saucy-armhf/3163/artifact/work/output/*zip*/output.zip
    FAILURE: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-runner-maguro/2639/console
    FAILURE: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-runner-mako/2690/console
    SUCCESS: http://10.97.0.26:8080/job/touch-flash-device/312
    SUCCESS: http://10.97.0.26:8080/job/touch-flash-device/307

Click here to trigger a rebuild:
http://10.97.0.26:8080/job/unity8-ci/1476/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
Michael Zanetti (mzanetti) wrote :

381 + #if DIRECTIONALDRAGAREA_DEBUG
382 + qDebug() << "[DDA]" << m_timeSource->msecsSinceReference()
383 + << qPrintable(touchEventToString(event));
384 + #endif

I'd prefer using qCDebug instead of this. However, qCDebug will only be released with Qt 5.2. Maybe we should get this in for now with a TODO and add a task for cleaning up debug's once we have 5.2?

Revision history for this message
Daniel d'Andrada (dandrader) wrote :

> 381 + #if DIRECTIONALDRAGAREA_DEBUG
> 382 + qDebug() << "[DDA]" << m_timeSource->msecsSinceReference()
> 383 + << qPrintable(touchEventToString(event));
> 384 + #endif
>
> I'd prefer using qCDebug instead of this. However, qCDebug will only be
> released with Qt 5.2. Maybe we should get this in for now with a TODO and add
> a task for cleaning up debug's once we have 5.2?

done.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Michael Zanetti (mzanetti) wrote :

This looks really good. The crashes are definitely gone.

=======
can we const & all occurances of "SharedTimeSource timeSource" in method arguments?

=======
715 + // Estimate of the maximum number of active touches we might reach.
716 + // Not a problem if it ends up being an underestimate as this is just
717 + // an optimization.
718 + reserve(10);

If it's just an optimization, shouldn't we rather go for 3 or something that is more realistic? I think it happens max once a year that I touch the screen with all 10 fingers.

review: Needs Information
Revision history for this message
Daniel d'Andrada (dandrader) wrote :

> This looks really good. The crashes are definitely gone.
>
> =======
> can we const & all occurances of "SharedTimeSource timeSource" in method
> arguments?
>
> =======
> 715 + // Estimate of the maximum number of active touches we might reach.
> 716 + // Not a problem if it ends up being an underestimate as this is
> just
> 717 + // an optimization.
> 718 + reserve(10);
>
> If it's just an optimization, shouldn't we rather go for 3 or something that
> is more realistic? I think it happens max once a year that I touch the screen
> with all 10 fingers.

Done.

Revision history for this message
Michael Zanetti (mzanetti) wrote :

looks good to me now.

review: Approve
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Albert Astals Cid (aacid) wrote :

do you think we can make it so removeTimeConstraintsFromDirectionalDragAreas is invoked on the shell for all the tests?

review: Needs Information
Revision history for this message
Daniel d'Andrada (dandrader) wrote :

> do you think we can make it so removeTimeConstraintsFromDirectionalDragAreas
> is invoked on the shell for all the tests?

The only way I can think of is having only UnityTestCase defining initTestCase() and them doing a s/initTestCase/someOtherInitName in all instances of UnityTestCase.

So UnityTestCase would have

function initTestCase() {
   someOtherInitName();
   removeTimeConstraintsFromDirectionalDragAreas(foo);
}

But that still has the problem that I need access to the root item so that I can iterate through it. But maybe that can be done through some C++ code...

So all things considered, I think the current approach is the better one.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

FAILED: Continuous integration, rev:497
http://jenkins.qa.ubuntu.com/job/unity8-ci/1556/
Executed test runs:
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-trusty/372
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-trusty-touch/360
    UNSTABLE: http://jenkins.qa.ubuntu.com/job/unity-phablet-qmluitests-trusty/106
    SUCCESS: http://jenkins.qa.ubuntu.com/job/unity8-trusty-amd64-ci/80
    SUCCESS: http://jenkins.qa.ubuntu.com/job/unity8-trusty-armhf-ci/80
        deb: http://jenkins.qa.ubuntu.com/job/unity8-trusty-armhf-ci/80/artifact/work/output/*zip*/output.zip
    SUCCESS: http://jenkins.qa.ubuntu.com/job/unity8-trusty-i386-ci/80
    SUCCESS: http://jenkins.qa.ubuntu.com/job/autopilot-testrunner-otto-trusty/348
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-trusty-amd64/372
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-trusty-amd64/372/artifact/work/output/*zip*/output.zip
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-trusty-armhf/360
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-trusty-armhf/360/artifact/work/output/*zip*/output.zip
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-runner-maguro/2942
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-runner-mako/3026
    SUCCESS: http://10.97.0.26:8080/job/touch-flash-device/965
    SUCCESS: http://10.97.0.26:8080/job/touch-flash-device/962

Click here to trigger a rebuild:
http://10.97.0.26:8080/job/unity8-ci/1556/rebuild

review: Needs Fixing (continuous-integration)
498. By Daniel d'Andrada

Reorder things a bit

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

FAILED: Continuous integration, rev:498
http://jenkins.qa.ubuntu.com/job/unity8-ci/1559/
Executed test runs:
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-trusty/384
    FAILURE: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-trusty-touch/372/console
    SUCCESS: http://jenkins.qa.ubuntu.com/job/unity-phablet-qmluitests-trusty/110
    SUCCESS: http://jenkins.qa.ubuntu.com/job/unity8-trusty-amd64-ci/83
    SUCCESS: http://jenkins.qa.ubuntu.com/job/unity8-trusty-armhf-ci/83
        deb: http://jenkins.qa.ubuntu.com/job/unity8-trusty-armhf-ci/83/artifact/work/output/*zip*/output.zip
    SUCCESS: http://jenkins.qa.ubuntu.com/job/unity8-trusty-i386-ci/83
    SUCCESS: http://jenkins.qa.ubuntu.com/job/autopilot-testrunner-otto-trusty/359
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-trusty-amd64/384
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-trusty-amd64/384/artifact/work/output/*zip*/output.zip
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-trusty-armhf/372
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-trusty-armhf/372/artifact/work/output/*zip*/output.zip
    FAILURE: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-runner-maguro/2953/console
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-runner-mako/3037
    SUCCESS: http://10.97.0.26:8080/job/touch-flash-device/986
    SUCCESS: http://10.97.0.26:8080/job/touch-flash-device/985

Click here to trigger a rebuild:
http://10.97.0.26:8080/job/unity8-ci/1559/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

FAILED: Continuous integration, rev:498
http://jenkins.qa.ubuntu.com/job/unity8-ci/1560/
Executed test runs:
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-trusty/391
    FAILURE: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-trusty-touch/379/console
    UNSTABLE: http://jenkins.qa.ubuntu.com/job/unity-phablet-qmluitests-trusty/113
    SUCCESS: http://jenkins.qa.ubuntu.com/job/unity8-trusty-amd64-ci/84
    SUCCESS: http://jenkins.qa.ubuntu.com/job/unity8-trusty-armhf-ci/84
        deb: http://jenkins.qa.ubuntu.com/job/unity8-trusty-armhf-ci/84/artifact/work/output/*zip*/output.zip
    SUCCESS: http://jenkins.qa.ubuntu.com/job/unity8-trusty-i386-ci/84
    SUCCESS: http://jenkins.qa.ubuntu.com/job/autopilot-testrunner-otto-trusty/366
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-trusty-amd64/391
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-trusty-amd64/391/artifact/work/output/*zip*/output.zip
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-trusty-armhf/379
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-trusty-armhf/379/artifact/work/output/*zip*/output.zip
    FAILURE: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-runner-maguro/2960/console
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-runner-mako/3044
    SUCCESS: http://10.97.0.26:8080/job/touch-flash-device/999
    SUCCESS: http://10.97.0.26:8080/job/touch-flash-device/1000

Click here to trigger a rebuild:
http://10.97.0.26:8080/job/unity8-ci/1560/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

FAILED: Continuous integration, rev:498
http://jenkins.qa.ubuntu.com/job/unity8-ci/1562/
Executed test runs:
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-trusty/394
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-trusty-touch/382
    FAILURE: http://jenkins.qa.ubuntu.com/job/unity-phablet-qmluitests-trusty/116/console
    SUCCESS: http://jenkins.qa.ubuntu.com/job/unity8-trusty-amd64-ci/86
    SUCCESS: http://jenkins.qa.ubuntu.com/job/unity8-trusty-armhf-ci/86
        deb: http://jenkins.qa.ubuntu.com/job/unity8-trusty-armhf-ci/86/artifact/work/output/*zip*/output.zip
    SUCCESS: http://jenkins.qa.ubuntu.com/job/unity8-trusty-i386-ci/86
    SUCCESS: http://jenkins.qa.ubuntu.com/job/autopilot-testrunner-otto-trusty/369
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-trusty-amd64/394
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-trusty-amd64/394/artifact/work/output/*zip*/output.zip
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-trusty-armhf/382
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-trusty-armhf/382/artifact/work/output/*zip*/output.zip
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-runner-mako/3047
    SUCCESS: http://10.97.0.26:8080/job/touch-flash-device/1004

Click here to trigger a rebuild:
http://10.97.0.26:8080/job/unity8-ci/1562/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
Albert Astals Cid (aacid) wrote :

Can't we do Component.onCompleted on the UnityTestCase + recursion on parent until it's null to find the root?

review: Needs Information
499. By Daniel d'Andrada

Clean up tests. Following Albert's suggestion.

Revision history for this message
Daniel d'Andrada (dandrader) wrote :

> Can't we do Component.onCompleted on the UnityTestCase + recursion on parent
> until it's null to find the root?

Good idea. It works. Done it.

Now let's just hope Jenkins agrees with us.

500. By Daniel d'Andrada

Trying to make tst_Shell.initTestCase() less hacky and more robust

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Albert Astals Cid (aacid) wrote :

Looks good :-)

review: Approve
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'Components/DragHandle.qml'
2--- Components/DragHandle.qml 2013-10-11 11:41:27 +0000
3+++ Components/DragHandle.qml 2013-11-05 10:55:36 +0000
4@@ -48,9 +48,12 @@
5 id: dragArea
6 objectName: "dragHandle"
7
8- // Disable gesture detection by default when hinting is used.
9- // It doesn't make sense to have both.
10- distanceThreshold: hintDisplacement > 0 ? 0 : units.gu(1.5)
11+ // Disable most of the gesture recognition parameters by default when hinting is used as
12+ // it conflicts with the hinting idea.
13+ // The only part we keep in this situation is that it must be a single-finger gesture.
14+ distanceThreshold: hintDisplacement > 0 ? 0 : defaultDistanceThreshold
15+ maxSilenceTime: hintDisplacement > 0 ? 60*60*1000 : defaultMaxSilenceTime
16+ maxDeviation: hintDisplacement > 0 ? 999999 : defaultMaxDeviation
17
18 property bool stretch: false
19
20@@ -181,29 +184,25 @@
21 }
22 }
23
24- onDraggingChanged: {
25- if (dragging) {
26- if (hintDisplacement > 0) {
27- hintingAnimation.targetValue = d.startValue;
28- hintingAnimation.start();
29- }
30- } else {
31- hintingAnimation.stop();
32- }
33- }
34-
35 onStatusChanged: {
36 if (status === DirectionalDragArea.WaitingForTouch) {
37+ hintingAnimation.stop();
38 if (d.previousStatus === DirectionalDragArea.Recognized) {
39 d.onFinishedRecognizedGesture();
40- }
41- d.startValue = parent[d.targetProp];
42- }
43-
44- if (d.previousStatus === DirectionalDragArea.WaitingForTouch ||
45- d.previousStatus === undefined) {
46- dragEvaluator.reset();
47- d.startValue = parent[d.targetProp];
48+ } else /* d.previousStatus === DirectionalDragArea.Undecided */ {
49+ // Gesture was rejected.
50+ d.rollbackDrag();
51+ }
52+ } else /* Undecided || Recognized */ {
53+ if (d.previousStatus === DirectionalDragArea.WaitingForTouch ||
54+ d.previousStatus === undefined) {
55+ dragEvaluator.reset();
56+ d.startValue = parent[d.targetProp];
57+ }
58+ if (hintDisplacement > 0) {
59+ hintingAnimation.targetValue = d.startValue;
60+ hintingAnimation.start();
61+ }
62 }
63
64 d.previousStatus = status;
65
66=== modified file 'Components/EdgeDragArea.qml'
67--- Components/EdgeDragArea.qml 2013-06-28 19:32:16 +0000
68+++ Components/EdgeDragArea.qml 2013-11-05 10:55:36 +0000
69@@ -29,8 +29,16 @@
70 //
71 // The idea here is that it's better having lax rules than false negatives.
72 // False negatives are very frustrating to the user.
73- maxDeviation: units.gu(3)
74- wideningAngle: 50
75- distanceThreshold: units.gu(1.5)
76- minSpeed: units.gu(0) // some people were getting false negatives with it enabled.
77+ maxDeviation: defaultMaxDeviation
78+ wideningAngle: defaultWideningAngle
79+ distanceThreshold: defaultDistanceThreshold
80+ minSpeed: defaultMinSpeed
81+ maxSilenceTime: defaultMaxSilenceTime
82+
83+ readonly property real defaultMaxDeviation: units.gu(3)
84+ readonly property real defaultWideningAngle: 50
85+ readonly property real defaultDistanceThreshold: units.gu(1.5)
86+ // some people were getting false negatives with it enabled.
87+ readonly property real defaultMinSpeed: units.gu(0)
88+ readonly property int defaultMaxSilenceTime: 200
89 }
90
91=== modified file 'Components/Stage.qml'
92--- Components/Stage.qml 2013-10-11 10:24:30 +0000
93+++ Components/Stage.qml 2013-11-05 10:55:36 +0000
94@@ -482,19 +482,20 @@
95 switchToApplicationAnimationController.progress += 0.4 * delta
96 }
97
98- property bool gotRejected: false
99+ property int lastStatus: -1
100
101 onStatusChanged: {
102 if (status == DirectionalDragArea.Undecided) {
103- gotRejected = false
104 onUndecided();
105- } else if (status == DirectionalDragArea.Rejected) {
106- switchToApplicationAnimationController.completeToBeginningWithSignal();
107- gotRejected = true;
108 } else if (status == DirectionalDragArea.WaitingForTouch) {
109- if (!gotRejected)
110- onGestureEnded()
111+ if (lastStatus == DirectionalDragArea.Undecided) {
112+ // got rejected
113+ switchToApplicationAnimationController.completeToBeginningWithSignal();
114+ } else {
115+ onGestureEnded();
116+ }
117 }
118+ lastStatus = status;
119 }
120
121 function onUndecided() {
122
123=== modified file 'plugins/Ubuntu/Gestures/AxisVelocityCalculator.cpp'
124--- plugins/Ubuntu/Gestures/AxisVelocityCalculator.cpp 2013-06-26 16:27:04 +0000
125+++ plugins/Ubuntu/Gestures/AxisVelocityCalculator.cpp 2013-11-05 10:55:36 +0000
126@@ -21,25 +21,19 @@
127 #include "AxisVelocityCalculator.h"
128 #include <QtCore/QElapsedTimer>
129
130-class RealTimeSource : public UbuntuGestures::TimeSource {
131-public:
132- RealTimeSource()
133- : UbuntuGestures::TimeSource() {
134- m_timer.start();
135- }
136- virtual qint64 msecsSinceReference() {
137- return m_timer.elapsed();
138- }
139-private:
140- QElapsedTimer m_timer;
141-};
142+using namespace UbuntuGestures;
143
144 AxisVelocityCalculator::AxisVelocityCalculator(QObject *parent)
145+ : AxisVelocityCalculator(SharedTimeSource(new RealTimeSource), parent)
146+{
147+}
148+
149+AxisVelocityCalculator::AxisVelocityCalculator(const SharedTimeSource &timeSource,
150+ QObject *parent)
151 : QObject(parent)
152- , m_timeSource(new RealTimeSource)
153+ , m_timeSource(timeSource)
154 , m_trackedPosition(0.0)
155 {
156- m_timeSource->setParent(this);
157 reset();
158 }
159
160@@ -142,11 +136,8 @@
161 }
162 }
163
164-void AxisVelocityCalculator::setTimeSource(UbuntuGestures::TimeSource *timeSource)
165+void AxisVelocityCalculator::setTimeSource(const SharedTimeSource &timeSource)
166 {
167- if (m_timeSource && m_timeSource->parent() == this)
168- delete m_timeSource;
169-
170 m_timeSource = timeSource;
171
172 if (numSamples() > 0) {
173
174=== modified file 'plugins/Ubuntu/Gestures/AxisVelocityCalculator.h'
175--- plugins/Ubuntu/Gestures/AxisVelocityCalculator.h 2013-06-26 16:20:32 +0000
176+++ plugins/Ubuntu/Gestures/AxisVelocityCalculator.h 2013-11-05 10:55:36 +0000
177@@ -24,19 +24,7 @@
178 #include "UbuntuGesturesGlobal.h"
179 #include <stdint.h>
180 #include <QtCore/QObject>
181-
182-namespace UbuntuGestures {
183-/*
184- Interface for a time source.
185- */
186-class UBUNTUGESTURES_EXPORT TimeSource : public QObject {
187- Q_OBJECT
188-public:
189- virtual ~TimeSource() {}
190- /* Returns the current time in milliseconds since some reference time in the past. */
191- virtual qint64 msecsSinceReference() = 0;
192-};
193-}
194+#include "TimeSource.h"
195
196 /*
197 Estimates the current velocity of a finger based on recent movement along an axis
198@@ -73,10 +61,15 @@
199 public:
200
201 /*
202- Regular constructor
203+ Regular, simple, constructor
204 */
205 AxisVelocityCalculator(QObject *parent = 0);
206
207+ /*
208+ Constructor that takes a TimeSource
209+ */
210+ AxisVelocityCalculator(const UbuntuGestures::SharedTimeSource &timeSource, QObject *parent = 0);
211+
212 virtual ~AxisVelocityCalculator();
213
214 qreal trackedPosition() const;
215@@ -97,7 +90,7 @@
216 /*
217 Replaces the TimeSource with the given one. Useful for testing purposes.
218 */
219- void setTimeSource(UbuntuGestures::TimeSource *timeSource);
220+ void setTimeSource(const UbuntuGestures::SharedTimeSource &timeSource);
221
222 /*
223 The minimum amount of samples needed for a velocity calculation.
224@@ -145,7 +138,7 @@
225 int m_samplesRead; /* index of the oldest sample available. -1 if buffer is empty */
226 int m_samplesWrite; /* index where the next sample will be written */
227
228- UbuntuGestures::TimeSource *m_timeSource;
229+ UbuntuGestures::SharedTimeSource m_timeSource;
230
231 qreal m_trackedPosition;
232 };
233
234=== modified file 'plugins/Ubuntu/Gestures/CMakeLists.txt'
235--- plugins/Ubuntu/Gestures/CMakeLists.txt 2013-08-13 09:10:59 +0000
236+++ plugins/Ubuntu/Gestures/CMakeLists.txt 2013-11-05 10:55:36 +0000
237@@ -6,6 +6,7 @@
238 AxisVelocityCalculator.cpp
239 Direction.cpp
240 DirectionalDragArea.cpp
241+ TimeSource.cpp
242 )
243
244 add_definitions(-DUBUNTUGESTURES_LIBRARY)
245
246=== modified file 'plugins/Ubuntu/Gestures/DirectionalDragArea.cpp'
247--- plugins/Ubuntu/Gestures/DirectionalDragArea.cpp 2013-07-01 11:12:03 +0000
248+++ plugins/Ubuntu/Gestures/DirectionalDragArea.cpp 2013-11-05 10:55:36 +0000
249@@ -18,6 +18,75 @@
250
251 #include <QtCore/qmath.h>
252 #include <QtCore/QTimer>
253+#include <QDebug>
254+
255+using namespace UbuntuGestures;
256+
257+#define DIRECTIONALDRAGAREA_DEBUG 0
258+
259+#if DIRECTIONALDRAGAREA_DEBUG
260+#define DDA_DEBUG(msg) qDebug("[DDA] " msg)
261+namespace {
262+QString touchPointStateToString(Qt::TouchPointState state) {
263+ switch (state) {
264+ case Qt::TouchPointPressed:
265+ return QString("pressed");
266+ case Qt::TouchPointMoved:
267+ return QString("moved");
268+ case Qt::TouchPointStationary:
269+ return QString("stationary");
270+ default: // Qt::TouchPointReleased:
271+ return QString("released");
272+ }
273+}
274+QString touchEventToString(QTouchEvent *ev)
275+{
276+ QString message;
277+
278+ switch (ev->type()) {
279+ case QEvent::TouchBegin:
280+ message.append("TouchBegin ");
281+ break;
282+ case QEvent::TouchUpdate:
283+ message.append("TouchUpdate ");
284+ break;
285+ case QEvent::TouchEnd:
286+ message.append("TouchEnd ");
287+ break;
288+ default: //QEvent::TouchCancel
289+ message.append("TouchCancel ");
290+ }
291+
292+ for (int i=0; i < ev->touchPoints().size(); ++i) {
293+
294+ const QTouchEvent::TouchPoint& touchPoint = ev->touchPoints().at(i);
295+ message.append(
296+ QString("(id:%1, state:%2, scenePos:(%3,%4)) ")
297+ .arg(touchPoint.id())
298+ .arg(touchPointStateToString(touchPoint.state()))
299+ .arg(touchPoint.scenePos().x())
300+ .arg(touchPoint.scenePos().y())
301+ );
302+ }
303+
304+ return message;
305+}
306+
307+const char *statusToString(DirectionalDragArea::Status status)
308+{
309+ if (status == DirectionalDragArea::WaitingForTouch) {
310+ return "WaitingForTouch";
311+ } else if (status == DirectionalDragArea::Undecided) {
312+ return "Undecided";
313+ } else {
314+ return "Recognized";
315+ }
316+}
317+
318+} // namespace {
319+#else // DIRECTIONALDRAGAREA_DEBUG
320+#define DDA_DEBUG(msg) do{}while(0)
321+#endif // DIRECTIONALDRAGAREA_DEBUG
322
323 // Essentially a QTimer wrapper
324 class RecognitionTimer : public UbuntuGestures::AbstractTimer
325@@ -48,9 +117,12 @@
326 , m_minSpeed(0)
327 , m_maxSilenceTime(200)
328 , m_silenceTime(0)
329+ , m_compositionTime(60)
330 , m_numSamplesOnLastSpeedCheck(0)
331 , m_recognitionTimer(0)
332 , m_velocityCalculator(0)
333+ , m_timeSource(new RealTimeSource)
334+ , m_activeTouches(m_timeSource)
335 {
336 setRecognitionTimer(new RecognitionTimer(this));
337 m_recognitionTimer->setInterval(60);
338@@ -73,8 +145,8 @@
339
340 void DirectionalDragArea::setMaxDeviation(qreal value)
341 {
342- if (m_dampedPos.maxDelta() != value) {
343- m_dampedPos.setMaxDelta(value);
344+ if (m_dampedScenePos.maxDelta() != value) {
345+ m_dampedScenePos.setMaxDelta(value);
346 Q_EMIT maxDeviationChanged(value);
347 }
348 }
349@@ -118,6 +190,14 @@
350 }
351 }
352
353+void DirectionalDragArea::setCompositionTime(int value)
354+{
355+ if (m_compositionTime != value) {
356+ m_compositionTime = value;
357+ Q_EMIT compositionTimeChanged(value);
358+ }
359+}
360+
361 void DirectionalDragArea::setRecognitionTimer(UbuntuGestures::AbstractTimer *timer)
362 {
363 int interval = 0;
364@@ -141,9 +221,11 @@
365 }
366 }
367
368-void DirectionalDragArea::setTimeSource(UbuntuGestures::TimeSource *timeSource)
369+void DirectionalDragArea::setTimeSource(const SharedTimeSource &timeSource)
370 {
371+ m_timeSource = timeSource;
372 m_velocityCalculator->setTimeSource(timeSource);
373+ m_activeTouches.m_timeSource = timeSource;
374 }
375
376 qreal DirectionalDragArea::distance() const
377@@ -186,6 +268,12 @@
378
379 void DirectionalDragArea::touchEvent(QTouchEvent *event)
380 {
381+ #if DIRECTIONALDRAGAREA_DEBUG
382+ // TODO Consider using qCDebug() when available (Qt 5.2)
383+ qDebug() << "[DDA]" << m_timeSource->msecsSinceReference()
384+ << qPrintable(touchEventToString(event));
385+ #endif
386+
387 if (!isEnabled() || !isVisible()) {
388 QQuickItem::touchEvent(event);
389 return;
390@@ -198,76 +286,124 @@
391 case Undecided:
392 touchEvent_undecided(event);
393 break;
394- case Recognized:
395+ default: // Recognized:
396 touchEvent_recognized(event);
397 break;
398- default: // Rejected
399- touchEvent_rejected(event);
400- break;
401 }
402+
403+ m_activeTouches.update(event);
404 }
405
406 void DirectionalDragArea::touchEvent_absent(QTouchEvent *event)
407 {
408- if ((event->touchPointStates() && (Qt::TouchPointPressed || Qt::TouchPointMoved))
409- && event->touchPoints().count() == 1) {
410- const QTouchEvent::TouchPoint &touchPoint = event->touchPoints()[0];
411- m_startPos = touchPoint.pos();
412- m_startScenePos = touchPoint.scenePos();
413- m_touchId = touchPoint.id();
414- m_dampedPos.reset(m_startPos);
415- updateVelocityCalculator(m_startPos);
416- m_velocityCalculator->reset();
417- m_numSamplesOnLastSpeedCheck = 0;
418- m_silenceTime = 0;
419- setPreviousPos(m_startPos);
420- setPreviousScenePos(m_startScenePos);
421-
422- if (m_distanceThreshold > 0)
423- setStatus(Undecided);
424- else
425- setStatus(Recognized);
426- }
427+ if (!event->touchPointStates().testFlag(Qt::TouchPointPressed)) {
428+ // Nothing to see here. No touch starting in this event.
429+ return;
430+ }
431+
432+ if (isWithinTouchCompositionWindow()) {
433+ // too close to the last touch start. So we consider them as starting roughly at the same time.
434+ // Can't be a single-touch gesture.
435+ #if DIRECTIONALDRAGAREA_DEBUG
436+ qDebug("[DDA] A new touch point came in but we're still within time composition window. Ignoring it.");
437+ #endif
438+ return;
439+ }
440+
441+ const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
442+
443+ const QTouchEvent::TouchPoint *newTouchPoint = nullptr;
444+ for (int i = 0; i < touchPoints.count(); ++i) {
445+ const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i);
446+ if (touchPoint.state() == Qt::TouchPointPressed) {
447+ if (newTouchPoint) {
448+ // more than one touch starting in this QTouchEvent. Can't be a single-touch gesture
449+ return;
450+ } else {
451+ // that's our candidate
452+ m_touchId = touchPoint.id();
453+ newTouchPoint = &touchPoint;
454+ }
455+ }
456+ }
457+
458+ Q_ASSERT(newTouchPoint);
459+
460+ // If we have made this far, we are good to go to the next status.
461+
462+ m_startPos = newTouchPoint->pos();
463+ m_startScenePos = newTouchPoint->scenePos();
464+ m_touchId = newTouchPoint->id();
465+ m_dampedScenePos.reset(m_startScenePos);
466+ updateVelocityCalculator(m_startScenePos);
467+ m_velocityCalculator->reset();
468+ m_numSamplesOnLastSpeedCheck = 0;
469+ m_silenceTime = 0;
470+ setPreviousPos(m_startPos);
471+ setPreviousScenePos(m_startScenePos);
472+
473+ setStatus(Undecided);
474 }
475
476 void DirectionalDragArea::touchEvent_undecided(QTouchEvent *event)
477 {
478 const QTouchEvent::TouchPoint *touchPoint = fetchTargetTouchPoint(event);
479- const QPointF &touchPos = touchPoint->pos();
480+
481+ if (!touchPoint) {
482+ qCritical() << "DirectionalDragArea[status=Undecided]: touch " << m_touchId
483+ << "missing from QTouchEvent without first reaching state Qt::TouchPointReleased. "
484+ "Considering it as released.";
485+ setStatus(WaitingForTouch);
486+ return;
487+ }
488+
489+ const QPointF &touchScenePos = touchPoint->scenePos();
490
491 if (touchPoint->state() == Qt::TouchPointReleased) {
492 // touch has ended before recognition concluded
493+ DDA_DEBUG("Touch has ended before recognition concluded");
494 setStatus(WaitingForTouch);
495 return;
496 }
497
498- if (event->touchPointStates().testFlag(Qt::TouchPointPressed)
499- || event->touchPoints().count() > 1) {
500+ if (event->touchPointStates().testFlag(Qt::TouchPointPressed) && isWithinTouchCompositionWindow()) {
501 // multi-finger drags are not accepted
502- setStatus(Rejected);
503+ DDA_DEBUG("Multi-finger drags are not accepted");
504+ setStatus(WaitingForTouch);
505 return;
506 }
507
508- m_previousDampedPos.setX(m_dampedPos.x());
509- m_previousDampedPos.setY(m_dampedPos.y());
510- m_dampedPos.update(touchPos);
511- updateVelocityCalculator(touchPos);
512+ m_previousDampedScenePos.setX(m_dampedScenePos.x());
513+ m_previousDampedScenePos.setY(m_dampedScenePos.y());
514+ m_dampedScenePos.update(touchScenePos);
515+ updateVelocityCalculator(touchScenePos);
516
517 if (!pointInsideAllowedArea()) {
518- setStatus(Rejected);
519+ DDA_DEBUG("Rejecting gesture because touch point is outside allowed area.");
520+ setStatus(WaitingForTouch);
521 return;
522 }
523
524 if (!movingInRightDirection()) {
525- setStatus(Rejected);
526- return;
527- }
528-
529- setPreviousPos(touchPos);
530- setPreviousScenePos(touchPoint->scenePos());
531-
532- if (movedFarEnough(touchPos)) {
533+ DDA_DEBUG("Rejecting gesture becauuse touch point is moving in the wrong direction.");
534+ setStatus(WaitingForTouch);
535+ return;
536+ }
537+
538+ setPreviousPos(touchPoint->pos());
539+ setPreviousScenePos(touchScenePos);
540+
541+ if (isWithinTouchCompositionWindow()) {
542+ // There's still time for some new touch to appear and ruin our party as it would be combined
543+ // with our m_touchId one and therefore deny the possibility of a single-finger gesture.
544+ DDA_DEBUG("Sill within composition window. Let's wait more.");
545+ return;
546+ }
547+
548+ if (movedFarEnough(touchScenePos)) {
549 setStatus(Recognized);
550+ } else {
551+ DDA_DEBUG("Didn't move far enough yet. Let's wait more.");
552 }
553 }
554
555@@ -275,20 +411,18 @@
556 {
557 const QTouchEvent::TouchPoint *touchPoint = fetchTargetTouchPoint(event);
558
559- setPreviousPos(touchPoint->pos());
560- setPreviousScenePos(touchPoint->scenePos());
561-
562- if (touchPoint->state() == Qt::TouchPointReleased) {
563- setStatus(WaitingForTouch);
564- }
565-}
566-
567-void DirectionalDragArea::touchEvent_rejected(QTouchEvent *event)
568-{
569- const QTouchEvent::TouchPoint *touchPoint = fetchTargetTouchPoint(event);
570-
571- if (!touchPoint || touchPoint->state() == Qt::TouchPointReleased) {
572- setStatus(WaitingForTouch);
573+ if (!touchPoint) {
574+ qCritical() << "DirectionalDragArea[status=Recognized]: touch " << m_touchId
575+ << "missing from QTouchEvent without first reaching state Qt::TouchPointReleased. "
576+ "Considering it as released.";
577+ setStatus(WaitingForTouch);
578+ } else {
579+ setPreviousPos(touchPoint->pos());
580+ setPreviousScenePos(touchPoint->scenePos());
581+
582+ if (touchPoint->state() == Qt::TouchPointReleased) {
583+ setStatus(WaitingForTouch);
584+ }
585 }
586 }
587
588@@ -307,8 +441,8 @@
589
590 bool DirectionalDragArea::pointInsideAllowedArea() const
591 {
592- qreal dX = m_dampedPos.x() - m_startPos.x();
593- qreal dY = m_dampedPos.y() - m_startPos.y();
594+ qreal dX = m_dampedScenePos.x() - m_startScenePos.x();
595+ qreal dY = m_dampedScenePos.y() - m_startScenePos.y();
596
597 switch (m_direction) {
598 case Direction::Upwards:
599@@ -326,22 +460,26 @@
600 {
601 switch (m_direction) {
602 case Direction::Upwards:
603- return m_dampedPos.y() <= m_previousDampedPos.y();
604+ return m_dampedScenePos.y() <= m_previousDampedScenePos.y();
605 case Direction::Downwards:
606- return m_dampedPos.y() >= m_previousDampedPos.y();
607+ return m_dampedScenePos.y() >= m_previousDampedScenePos.y();
608 case Direction::Leftwards:
609- return m_dampedPos.x() <= m_previousDampedPos.x();
610+ return m_dampedScenePos.x() <= m_previousDampedScenePos.x();
611 default: // Direction::Rightwards:
612- return m_dampedPos.x() >= m_previousDampedPos.x();
613+ return m_dampedScenePos.x() >= m_previousDampedScenePos.x();
614 }
615 }
616
617 bool DirectionalDragArea::movedFarEnough(const QPointF &point) const
618 {
619- if (Direction::isHorizontal(m_direction))
620- return qFabs(point.x() - m_startPos.x()) > m_distanceThreshold;
621- else
622- return qFabs(point.y() - m_startPos.y()) > m_distanceThreshold;
623+ if (m_distanceThreshold > 0) {
624+ if (Direction::isHorizontal(m_direction))
625+ return qFabs(point.x() - m_startScenePos.x()) > m_distanceThreshold;
626+ else
627+ return qFabs(point.y() - m_startScenePos.y()) > m_distanceThreshold;
628+ } else {
629+ return true;
630+ }
631 }
632
633 void DirectionalDragArea::checkSpeed()
634@@ -351,7 +489,8 @@
635 qreal minSpeedMsecs = m_minSpeed / 1000.0;
636
637 if (speed < minSpeedMsecs) {
638- setStatus(Rejected);
639+ DDA_DEBUG("Rejecting gesture because it's below minimum speed.");
640+ setStatus(WaitingForTouch);
641 }
642 }
643
644@@ -359,7 +498,8 @@
645 m_silenceTime += m_recognitionTimer->interval();
646
647 if (m_silenceTime > m_maxSilenceTime) {
648- setStatus(Rejected);
649+ DDA_DEBUG("Rejecting gesture because it's silence time has been exceeded.");
650+ setStatus(WaitingForTouch);
651 }
652 } else {
653 m_silenceTime = 0;
654@@ -381,11 +521,13 @@
655 m_status = newStatus;
656 Q_EMIT statusChanged(m_status);
657
658+ #if DIRECTIONALDRAGAREA_DEBUG
659+ qDebug() << "[DDA]" << statusToString(oldStatus) << "->" << statusToString(newStatus);
660+ #endif
661+
662 switch (newStatus) {
663 case WaitingForTouch:
664- if (oldStatus != DirectionalDragArea::Rejected) {
665- Q_EMIT draggingChanged(false);
666- }
667+ Q_EMIT draggingChanged(false);
668 break;
669 case Undecided:
670 m_recognitionTimer->start();
671@@ -395,9 +537,6 @@
672 if (oldStatus == WaitingForTouch)
673 Q_EMIT draggingChanged(true);
674 break;
675- case Rejected:
676- Q_EMIT draggingChanged(false);
677- break;
678 default:
679 // no-op
680 break;
681@@ -406,8 +545,6 @@
682
683 void DirectionalDragArea::setPreviousPos(const QPointF &point)
684 {
685- Q_ASSERT(m_status != Rejected);
686-
687 bool xChanged = m_previousPos.x() != point.x();
688 bool yChanged = m_previousPos.y() != point.y();
689
690@@ -428,8 +565,6 @@
691
692 void DirectionalDragArea::setPreviousScenePos(const QPointF &point)
693 {
694- Q_ASSERT(m_status != Rejected);
695-
696 bool xChanged = m_previousScenePos.x() != point.x();
697 bool yChanged = m_previousScenePos.y() != point.y();
698
699@@ -457,5 +592,106 @@
700 }
701 }
702
703+bool DirectionalDragArea::isWithinTouchCompositionWindow()
704+{
705+ return !m_activeTouches.isEmpty() &&
706+ m_timeSource->msecsSinceReference() <=
707+ m_activeTouches.mostRecentStartTime() + (qint64)compositionTime();
708+}
709+
710+//************************** ActiveTouchesInfo **************************
711+
712+DirectionalDragArea::ActiveTouchesInfo::ActiveTouchesInfo(const SharedTimeSource &timeSource)
713+ : m_timeSource(timeSource), m_lastUsedIndex(-1)
714+{
715+ // Estimate of the maximum number of active touches we might reach.
716+ // Not a problem if it ends up being an underestimate as this is just
717+ // an optimization.
718+ m_vector.resize(3);
719+}
720+
721+void DirectionalDragArea::ActiveTouchesInfo::update(QTouchEvent *event)
722+{
723+ if (!(event->touchPointStates() & (Qt::TouchPointPressed | Qt::TouchPointReleased))) {
724+ // nothing to update
725+ return;
726+ }
727+
728+ const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
729+ for (int i = 0; i < touchPoints.count(); ++i) {
730+ const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i);
731+ if (touchPoint.state() == Qt::TouchPointPressed) {
732+ addTouchPoint(touchPoint);
733+ } else if (touchPoint.state() == Qt::TouchPointReleased) {
734+ removeTouchPoint(touchPoint);
735+ }
736+ }
737+}
738+
739+void DirectionalDragArea::ActiveTouchesInfo::addTouchPoint(const QTouchEvent::TouchPoint &touchPoint)
740+{
741+ ActiveTouchInfo &activeTouchInfo = getEmptySlot();
742+ activeTouchInfo.id = touchPoint.id();
743+ activeTouchInfo.startTime = m_timeSource->msecsSinceReference();
744+}
745+
746+void DirectionalDragArea::ActiveTouchesInfo::removeTouchPoint(const QTouchEvent::TouchPoint &touchPoint)
747+{
748+ for (int i = 0; i <= m_lastUsedIndex; ++i) {
749+ if (touchPoint.id() == m_vector.at(i).id) {
750+ freeSlot(i);
751+ return;
752+ }
753+ }
754+ Q_ASSERT(false); // shouldn't reach this point
755+}
756+
757+DirectionalDragArea::ActiveTouchInfo &DirectionalDragArea::ActiveTouchesInfo::getEmptySlot()
758+{
759+ Q_ASSERT(m_lastUsedIndex < m_vector.size());
760+
761+ // Look for an in-between vacancy first
762+ for (int i = 0; i < m_lastUsedIndex; ++i) {
763+ ActiveTouchInfo &activeTouchInfo = m_vector[i];
764+ if (!activeTouchInfo.isValid()) {
765+ return activeTouchInfo;
766+ }
767+ }
768+
769+ ++m_lastUsedIndex;
770+ if (m_lastUsedIndex >= m_vector.size()) {
771+ m_vector.resize(m_lastUsedIndex + 1);
772+ }
773+
774+ return m_vector[m_lastUsedIndex];
775+}
776+
777+void DirectionalDragArea::ActiveTouchesInfo::freeSlot(int index)
778+{
779+ m_vector[index].reset();
780+ if (index == m_lastUsedIndex) {
781+ do {
782+ --m_lastUsedIndex;
783+ } while (m_lastUsedIndex >= 0 && !m_vector.at(m_lastUsedIndex).isValid());
784+ }
785+}
786+
787+qint64 DirectionalDragArea::ActiveTouchesInfo::mostRecentStartTime()
788+{
789+ Q_ASSERT(m_lastUsedIndex >= 0);
790+
791+ qint64 highestStartTime = m_vector.at(0).startTime;
792+ int i = 1;
793+ do {
794+ const ActiveTouchInfo &activeTouchInfo = m_vector.at(i);
795+ if (activeTouchInfo.isValid() && activeTouchInfo.startTime > highestStartTime) {
796+ highestStartTime = activeTouchInfo.startTime;
797+ }
798+ ++i;
799+ } while (i < m_lastUsedIndex);
800+
801+ return highestStartTime;
802+}
803+
804 // Because we are defining a new QObject-based class (RecognitionTimer) here.
805 #include "DirectionalDragArea.moc"
806
807=== modified file 'plugins/Ubuntu/Gestures/DirectionalDragArea.h'
808--- plugins/Ubuntu/Gestures/DirectionalDragArea.h 2013-07-01 11:12:03 +0000
809+++ plugins/Ubuntu/Gestures/DirectionalDragArea.h 2013-11-05 10:55:36 +0000
810@@ -117,9 +117,17 @@
811 Q_PROPERTY(int maxSilenceTime READ maxSilenceTime
812 WRITE setMaxSilenceTime
813 NOTIFY maxSilenceTimeChanged)
814+
815 //
816 /////
817
818+ // Maximum time (in milliseconds) after the start of a given touch point where
819+ // subsequent touch starts are grouped with the first one into an N-touches gesture
820+ // (e.g. a two-fingers tap or drag).
821+ Q_PROPERTY(int compositionTime READ compositionTime
822+ WRITE setCompositionTime
823+ NOTIFY compositionTimeChanged)
824+
825 Q_ENUMS(Direction)
826 Q_ENUMS(Status)
827 public:
828@@ -130,26 +138,23 @@
829
830 // Describes the state of the directional drag gesture.
831 enum Status {
832- // There's no touch point over this area.
833+ // Waiting for a new touch point to land on this area. No gesture is being processed
834+ // or tracked.
835 WaitingForTouch,
836
837 // A touch point has landed on this area but it's not know yet whether it is
838 // performing a drag in the correct direction.
839+ // If it's decided that the touch point is not performing a directional drag gesture,
840+ // it will be rejected/ignored and status will return to WaitingForTouch.
841 Undecided, //Recognizing,
842
843 // There's a touch point in this area and it performed a drag in the correct
844 // direction.
845 //
846- // Once recognized, the gesture state will move back to Absent only once
847+ // Once recognized, the gesture state will move back to WaitingForTouch only once
848 // that touch point ends. The gesture will remain in the Recognized state even if
849 // the touch point starts moving in other directions or halts.
850 Recognized,
851-
852- // A gesture was performed but it wasn't a single-touch drag in the correct
853- // direction.
854- // It will remain in this state until there are no more touch points over this
855- // area, at which point it will move to Absent state.
856- Rejected
857 };
858 Status status() const { return m_status; }
859
860@@ -164,7 +169,7 @@
861
862 bool dragging() const { return (m_status == Undecided) || (m_status == Recognized); }
863
864- qreal maxDeviation() const { return m_dampedPos.maxDelta(); }
865+ qreal maxDeviation() const { return m_dampedScenePos.maxDelta(); }
866 void setMaxDeviation(qreal value);
867
868 qreal wideningAngle() const;
869@@ -179,13 +184,16 @@
870 int maxSilenceTime() const { return m_maxSilenceTime; }
871 void setMaxSilenceTime(int value);
872
873+ int compositionTime() const { return m_compositionTime; }
874+ void setCompositionTime(int value);
875+
876 // Replaces the existing Timer with the given one.
877 //
878 // Useful for providing a fake timer when testing.
879 void setRecognitionTimer(UbuntuGestures::AbstractTimer *timer);
880
881 // Useful for testing, where a fake time source can be supplied
882- void setTimeSource(UbuntuGestures::TimeSource *timeSource);
883+ void setTimeSource(const UbuntuGestures::SharedTimeSource &timeSource);
884
885 Q_SIGNALS:
886 void directionChanged(Direction::Type direction);
887@@ -198,6 +206,7 @@
888 void distanceThresholdChanged(qreal value);
889 void minSpeedChanged(qreal value);
890 void maxSilenceTimeChanged(int value);
891+ void compositionTimeChanged(int value);
892 void touchXChanged(qreal value);
893 void touchYChanged(qreal value);
894 void touchSceneXChanged(qreal value);
895@@ -213,7 +222,6 @@
896 void touchEvent_absent(QTouchEvent *event);
897 void touchEvent_undecided(QTouchEvent *event);
898 void touchEvent_recognized(QTouchEvent *event);
899- void touchEvent_rejected(QTouchEvent *event);
900 bool pointInsideAllowedArea() const;
901 bool movingInRightDirection() const;
902 bool movedFarEnough(const QPointF &point) const;
903@@ -222,6 +230,7 @@
904 void setPreviousPos(const QPointF &point);
905 void setPreviousScenePos(const QPointF &point);
906 void updateVelocityCalculator(const QPointF &point);
907+ bool isWithinTouchCompositionWindow();
908
909 Status m_status;
910
911@@ -233,8 +242,8 @@
912
913 // A movement damper is used in some of the gesture recognition calculations
914 // to get rid of noise or small oscillations in the touch position.
915- DampedPointF m_dampedPos;
916- QPointF m_previousDampedPos;
917+ DampedPointF m_dampedScenePos;
918+ QPointF m_previousDampedScenePos;
919
920 Direction::Type m_direction;
921 qreal m_wideningAngle; // in degrees
922@@ -243,9 +252,37 @@
923 qreal m_minSpeed;
924 int m_maxSilenceTime; // in milliseconds
925 int m_silenceTime; // in milliseconds
926+ int m_compositionTime; // in milliseconds
927 int m_numSamplesOnLastSpeedCheck;
928 UbuntuGestures::AbstractTimer *m_recognitionTimer;
929 AxisVelocityCalculator *m_velocityCalculator;
930+
931+ UbuntuGestures::SharedTimeSource m_timeSource;
932+
933+ // Information about an active touch point
934+ struct ActiveTouchInfo {
935+ ActiveTouchInfo() : id(-1), startTime(-1) {}
936+ bool isValid() const { return id != -1; }
937+ void reset() { id = -1; }
938+ int id;
939+ qint64 startTime;
940+ };
941+ class ActiveTouchesInfo {
942+ public:
943+ ActiveTouchesInfo(const UbuntuGestures::SharedTimeSource &timeSource);
944+ void update(QTouchEvent *event);
945+ ActiveTouchInfo &touchInfo(int id);
946+ qint64 mostRecentStartTime();
947+ UbuntuGestures::SharedTimeSource m_timeSource;
948+ bool isEmpty() const { return m_lastUsedIndex == -1; }
949+ private:
950+ void addTouchPoint(const QTouchEvent::TouchPoint &touchPoint);
951+ ActiveTouchInfo &getEmptySlot();
952+ void freeSlot(int index);
953+ void removeTouchPoint(const QTouchEvent::TouchPoint &touchPoint);
954+ QVector<struct ActiveTouchInfo> m_vector;
955+ int m_lastUsedIndex;
956+ } m_activeTouches;
957 };
958
959 #endif // DIRECTIONAL_DRAG_AREA_H
960
961=== added file 'plugins/Ubuntu/Gestures/TimeSource.cpp'
962--- plugins/Ubuntu/Gestures/TimeSource.cpp 1970-01-01 00:00:00 +0000
963+++ plugins/Ubuntu/Gestures/TimeSource.cpp 2013-11-05 10:55:36 +0000
964@@ -0,0 +1,49 @@
965+/*
966+ * Copyright (C) 2013 - Canonical Ltd.
967+ *
968+ * This program is free software: you can redistribute it and/or modify it
969+ * under the terms of the GNU Lesser General Public License, as
970+ * published by the Free Software Foundation; either version 2.1 or 3.0
971+ * of the License.
972+ *
973+ * This program is distributed in the hope that it will be useful, but
974+ * WITHOUT ANY WARRANTY; without even the implied warranties of
975+ * MERCHANTABILITY, SATISFACTORY QUALITY or FITNESS FOR A PARTICULAR
976+ * PURPOSE. See the applicable version of the GNU Lesser General Public
977+ * License for more details.
978+ *
979+ * You should have received a copy of both the GNU Lesser General Public
980+ * License along with this program. If not, see <http://www.gnu.org/licenses/>
981+ *
982+ * Authored by: Daniel d'Andrada <daniel.dandrada@canonical.com>
983+ */
984+
985+#include "TimeSource.h"
986+
987+#include <QElapsedTimer>
988+
989+namespace UbuntuGestures {
990+class RealTimeSourcePrivate {
991+public:
992+ QElapsedTimer timer;
993+};
994+}
995+
996+using namespace UbuntuGestures;
997+
998+RealTimeSource::RealTimeSource()
999+ : UbuntuGestures::TimeSource()
1000+ , d(new RealTimeSourcePrivate)
1001+{
1002+ d->timer.start();
1003+}
1004+
1005+RealTimeSource::~RealTimeSource()
1006+{
1007+ delete d;
1008+}
1009+
1010+qint64 RealTimeSource::msecsSinceReference()
1011+{
1012+ return d->timer.elapsed();
1013+}
1014
1015=== added file 'plugins/Ubuntu/Gestures/TimeSource.h'
1016--- plugins/Ubuntu/Gestures/TimeSource.h 1970-01-01 00:00:00 +0000
1017+++ plugins/Ubuntu/Gestures/TimeSource.h 2013-11-05 10:55:36 +0000
1018@@ -0,0 +1,54 @@
1019+/*
1020+ * Copyright (C) 2013 - Canonical Ltd.
1021+ *
1022+ * This program is free software: you can redistribute it and/or modify it
1023+ * under the terms of the GNU Lesser General Public License, as
1024+ * published by the Free Software Foundation; either version 2.1 or 3.0
1025+ * of the License.
1026+ *
1027+ * This program is distributed in the hope that it will be useful, but
1028+ * WITHOUT ANY WARRANTY; without even the implied warranties of
1029+ * MERCHANTABILITY, SATISFACTORY QUALITY or FITNESS FOR A PARTICULAR
1030+ * PURPOSE. See the applicable version of the GNU Lesser General Public
1031+ * License for more details.
1032+ *
1033+ * You should have received a copy of both the GNU Lesser General Public
1034+ * License along with this program. If not, see <http://www.gnu.org/licenses/>
1035+ *
1036+ * Authored by: Daniel d'Andrada <daniel.dandrada@canonical.com>
1037+ */
1038+
1039+#ifndef UBUNTUGESTURES_TIMESOURCE_H
1040+#define UBUNTUGESTURES_TIMESOURCE_H
1041+
1042+#include "UbuntuGesturesGlobal.h"
1043+#include <QSharedPointer>
1044+
1045+namespace UbuntuGestures {
1046+/*
1047+ Interface for a time source.
1048+ */
1049+class UBUNTUGESTURES_EXPORT TimeSource {
1050+public:
1051+ virtual ~TimeSource() {}
1052+ /* Returns the current time in milliseconds since some reference time in the past. */
1053+ virtual qint64 msecsSinceReference() = 0;
1054+};
1055+typedef QSharedPointer<TimeSource> SharedTimeSource;
1056+
1057+/*
1058+ Implementation of a time source
1059+ */
1060+class RealTimeSourcePrivate;
1061+class RealTimeSource : public TimeSource {
1062+public:
1063+ RealTimeSource();
1064+ virtual ~RealTimeSource();
1065+ qint64 msecsSinceReference() override;
1066+private:
1067+ RealTimeSourcePrivate *d;
1068+};
1069+
1070+} // namespace UbuntuGestures
1071+
1072+#endif // UBUNTUGESTURES_TIMESOURCE_H
1073
1074=== modified file 'tests/plugins/Ubuntu/Gestures/DownwardsLauncher.qml'
1075--- tests/plugins/Ubuntu/Gestures/DownwardsLauncher.qml 2013-06-14 15:52:02 +0000
1076+++ tests/plugins/Ubuntu/Gestures/DownwardsLauncher.qml 2013-11-05 10:55:36 +0000
1077@@ -31,10 +31,7 @@
1078 y: followDragArea()
1079
1080 function followDragArea() {
1081- if (dragArea.status === DirectionalDragArea.Rejected)
1082- return -height
1083- else
1084- return dragArea.distance < height ? -height + dragArea.distance : 0
1085+ return dragArea.distance < height ? -height + dragArea.distance : 0
1086 }
1087 }
1088
1089@@ -66,15 +63,10 @@
1090 dragAreaRect.opacity = 0.3
1091 launcher.y = Qt.binding(launcher.followDragArea)
1092 break;
1093- case DirectionalDragArea.Recognized:
1094+ defaut: // DirectionalDragArea.Recognized:
1095 dragAreaRect.color = "green"
1096 dragAreaRect.opacity = 0.5
1097 break;
1098- default: //case DirectionalDragArea.Rejected:
1099- dragAreaRect.color = "red"
1100- dragAreaRect.opacity = 0.5
1101- launcher.y = -launcher.height
1102- break;
1103 }
1104 }
1105
1106
1107=== modified file 'tests/plugins/Ubuntu/Gestures/LeftwardsLauncher.qml'
1108--- tests/plugins/Ubuntu/Gestures/LeftwardsLauncher.qml 2013-06-14 15:52:02 +0000
1109+++ tests/plugins/Ubuntu/Gestures/LeftwardsLauncher.qml 2013-11-05 10:55:36 +0000
1110@@ -32,13 +32,10 @@
1111 y: 0
1112
1113 function followDragArea() {
1114- if (dragArea.status === DirectionalDragArea.Rejected)
1115- return root.width
1116- else
1117- return dragArea.distance > -width ?
1118- root.width + dragArea.distance
1119- :
1120- root.width - width
1121+ return dragArea.distance > -width ?
1122+ root.width + dragArea.distance
1123+ :
1124+ root.width - width
1125 }
1126 }
1127
1128@@ -69,15 +66,10 @@
1129 dragAreaRect.opacity = 0.3
1130 launcher.x = Qt.binding(launcher.followDragArea)
1131 break;
1132- case DirectionalDragArea.Recognized:
1133+ default: //case DirectionalDragArea.Recognized:
1134 dragAreaRect.color = "green"
1135 dragAreaRect.opacity = 0.5
1136 break;
1137- default: //case DirectionalDragArea.Rejected:
1138- dragAreaRect.color = "red"
1139- dragAreaRect.opacity = 0.5
1140- launcher.x = -launcher.height
1141- break;
1142 }
1143 }
1144
1145
1146=== modified file 'tests/plugins/Ubuntu/Gestures/RightwardsLauncher.qml'
1147--- tests/plugins/Ubuntu/Gestures/RightwardsLauncher.qml 2013-06-14 15:52:02 +0000
1148+++ tests/plugins/Ubuntu/Gestures/RightwardsLauncher.qml 2013-11-05 10:55:36 +0000
1149@@ -19,6 +19,7 @@
1150 import Ubuntu.Components 0.1
1151
1152 Item {
1153+ objectName: "rightwardsLauncher"
1154
1155 function reset() { launcher.x = -launcher.width }
1156
1157@@ -31,10 +32,7 @@
1158 y: 0
1159
1160 function followDragArea() {
1161- if (dragArea.status === DirectionalDragArea.Rejected)
1162- return -width
1163- else
1164- return dragArea.distance < width ? -width + dragArea.distance : 0
1165+ return dragArea.distance < width ? -width + dragArea.distance : 0
1166 }
1167 }
1168
1169@@ -66,15 +64,10 @@
1170 dragAreaRect.opacity = 0.3
1171 launcher.x = Qt.binding(launcher.followDragArea)
1172 break;
1173- case DirectionalDragArea.Recognized:
1174+ default: //case DirectionalDragArea.Recognized:
1175 dragAreaRect.color = "green"
1176 dragAreaRect.opacity = 0.5
1177 break;
1178- default: //case DirectionalDragArea.Rejected:
1179- dragAreaRect.color = "red"
1180- dragAreaRect.opacity = 0.5
1181- launcher.x = -launcher.height
1182- break;
1183 }
1184 }
1185
1186
1187=== modified file 'tests/plugins/Ubuntu/Gestures/UpwardsLauncher.qml'
1188--- tests/plugins/Ubuntu/Gestures/UpwardsLauncher.qml 2013-06-14 15:52:02 +0000
1189+++ tests/plugins/Ubuntu/Gestures/UpwardsLauncher.qml 2013-11-05 10:55:36 +0000
1190@@ -32,13 +32,10 @@
1191 y: root.height
1192
1193 function followDragArea() {
1194- if (dragArea.status === DirectionalDragArea.Rejected)
1195- return root.height
1196- else
1197- return dragArea.distance > -height ?
1198- root.height + dragArea.distance
1199- :
1200- root.height - height
1201+ return dragArea.distance > -height ?
1202+ root.height + dragArea.distance
1203+ :
1204+ root.height - height
1205 }
1206 }
1207
1208@@ -69,15 +66,10 @@
1209 dragAreaRect.opacity = 0.3
1210 launcher.y = Qt.binding(launcher.followDragArea)
1211 break;
1212- case DirectionalDragArea.Recognized:
1213+ default: //case DirectionalDragArea.Recognized:
1214 dragAreaRect.color = "green"
1215 dragAreaRect.opacity = 0.5
1216 break;
1217- default: //case DirectionalDragArea.Rejected:
1218- dragAreaRect.color = "red"
1219- dragAreaRect.opacity = 0.5
1220- launcher.y = -launcher.height
1221- break;
1222 }
1223 }
1224
1225
1226=== modified file 'tests/plugins/Ubuntu/Gestures/edgeDragExample.qml'
1227--- tests/plugins/Ubuntu/Gestures/edgeDragExample.qml 2013-06-05 22:03:08 +0000
1228+++ tests/plugins/Ubuntu/Gestures/edgeDragExample.qml 2013-11-05 10:55:36 +0000
1229@@ -33,7 +33,13 @@
1230 }
1231 }
1232
1233- RightwardsLauncher { id: hpLauncher; anchors.fill: parent }
1234+ // NB: Do not anchor it as we will move it programmatically from the test
1235+ RightwardsLauncher {
1236+ id: hpLauncher;
1237+ width: parent.width
1238+ height: parent.height
1239+ }
1240+
1241 LeftwardsLauncher { id: hnLauncher; anchors.fill: parent }
1242 DownwardsLauncher { id: vpLauncher; anchors.fill: parent }
1243 UpwardsLauncher { id: vnLauncher; anchors.fill: parent }
1244
1245=== modified file 'tests/plugins/Ubuntu/Gestures/tst_AxisVelocityCalculator.cpp'
1246--- tests/plugins/Ubuntu/Gestures/tst_AxisVelocityCalculator.cpp 2013-06-19 08:30:17 +0000
1247+++ tests/plugins/Ubuntu/Gestures/tst_AxisVelocityCalculator.cpp 2013-11-05 10:55:36 +0000
1248@@ -54,12 +54,12 @@
1249
1250 private:
1251 AxisVelocityCalculator *velCalc;
1252- FakeTimeSource *fakeTimeSource;
1253+ QSharedPointer<FakeTimeSource> fakeTimeSource;
1254 };
1255
1256 void tst_AxisVelocityCalculator::init()
1257 {
1258- fakeTimeSource = new FakeTimeSource;
1259+ fakeTimeSource.reset(new FakeTimeSource);
1260
1261 velCalc = new AxisVelocityCalculator;
1262 velCalc->setTimeSource(fakeTimeSource);
1263@@ -68,7 +68,7 @@
1264 void tst_AxisVelocityCalculator::cleanup()
1265 {
1266 delete velCalc;
1267- delete fakeTimeSource;
1268+ fakeTimeSource.reset();
1269 }
1270
1271 void tst_AxisVelocityCalculator::simpleSamples()
1272
1273=== modified file 'tests/plugins/Ubuntu/Gestures/tst_DirectionalDragArea.cpp'
1274--- tests/plugins/Ubuntu/Gestures/tst_DirectionalDragArea.cpp 2013-06-28 16:18:53 +0000
1275+++ tests/plugins/Ubuntu/Gestures/tst_DirectionalDragArea.cpp 2013-11-05 10:55:36 +0000
1276@@ -19,23 +19,38 @@
1277 #include <qpa/qwindowsysteminterface.h>
1278 #include <QtQuick/QQuickView>
1279 #include <QtQml/QQmlEngine>
1280+#include <QPointer>
1281
1282 #include <DirectionalDragArea.h>
1283
1284-class FakeTimer : public UbuntuGestures::AbstractTimer
1285+using namespace UbuntuGestures;
1286+
1287+class FakeTimer : public AbstractTimer
1288 {
1289 Q_OBJECT
1290 public:
1291- FakeTimer(QObject *parent = 0)
1292- : UbuntuGestures::AbstractTimer(parent)
1293+ FakeTimer(const SharedTimeSource &timeSource, QObject *parent = 0)
1294+ : UbuntuGestures::AbstractTimer(parent),
1295+ m_timeSource(timeSource)
1296 {}
1297
1298- virtual int interval() const { return m_duration; }
1299- virtual void setInterval(int msecs) { m_duration = msecs; }
1300-
1301- void emitTimeout() {Q_EMIT timeout();}
1302+ int interval() const override { return m_interval; }
1303+ void setInterval(int msecs) override { m_interval = msecs; }
1304+ void start() override {
1305+ AbstractTimer::start();
1306+ m_nextTimeoutTime = m_timeSource->msecsSinceReference() + (qint64)m_interval;
1307+ }
1308+
1309+ void emitTimeout() {
1310+ m_nextTimeoutTime += m_interval;
1311+ Q_EMIT timeout();
1312+ }
1313+
1314+ qint64 nextTimeoutTime() const { return m_nextTimeoutTime; }
1315 private:
1316- int m_duration;
1317+ int m_interval;
1318+ SharedTimeSource m_timeSource;
1319+ qint64 m_nextTimeoutTime;
1320 };
1321
1322 class FakeTimeSource : public UbuntuGestures::TimeSource
1323@@ -67,14 +82,17 @@
1324 void maxSilenceTime();
1325 void sceneXAndX();
1326 void sceneYAndY();
1327- void emitDraggingChangedOnDirectRecognition();
1328+ void twoFingerTap();
1329+ void movingDDA();
1330+ void ignoreOldFinger();
1331
1332 private:
1333 QQuickView *createView();
1334+ void passTime(qint64 timeSpan);
1335 QQuickView *view;
1336 QTouchDevice *device;
1337 FakeTimer *fakeTimer;
1338- FakeTimeSource *fakeTimeSource;
1339+ QSharedPointer<FakeTimeSource> fakeTimeSource;
1340 };
1341
1342 void tst_DirectionalDragArea::initTestCase()
1343@@ -101,8 +119,8 @@
1344 QVERIFY(view->rootObject() != 0);
1345 qApp->processEvents();
1346
1347- fakeTimer = new FakeTimer;
1348- fakeTimeSource = new FakeTimeSource;
1349+ fakeTimeSource.reset(new FakeTimeSource);
1350+ fakeTimer = new FakeTimer(fakeTimeSource);
1351 }
1352
1353 void tst_DirectionalDragArea::cleanup()
1354@@ -113,8 +131,7 @@
1355 delete fakeTimer;
1356 fakeTimer = 0;
1357
1358- delete fakeTimeSource;
1359- fakeTimeSource = 0;
1360+ fakeTimeSource.reset();
1361 }
1362
1363 QQuickView *tst_DirectionalDragArea::createView()
1364@@ -127,6 +144,23 @@
1365 return window;
1366 }
1367
1368+void tst_DirectionalDragArea::passTime(qint64 timeSpan)
1369+{
1370+ qint64 finalTime = fakeTimeSource->m_msecsSinceReference + timeSpan;
1371+
1372+ if (fakeTimer->isRunning() && finalTime >= fakeTimer->nextTimeoutTime()) {
1373+ fakeTimeSource->m_msecsSinceReference = fakeTimer->nextTimeoutTime();
1374+ fakeTimer->emitTimeout();
1375+
1376+ qint64 timeSpanRemainder = finalTime - fakeTimeSource->m_msecsSinceReference;
1377+ if (timeSpanRemainder > 0) {
1378+ passTime(timeSpanRemainder);
1379+ }
1380+ } else {
1381+ fakeTimeSource->m_msecsSinceReference = finalTime;
1382+ }
1383+}
1384+
1385 namespace {
1386 QPointF calculateInitialTouchPos(DirectionalDragArea *edgeDragArea, QQuickView *view)
1387 {
1388@@ -180,7 +214,7 @@
1389 QFETCH(QString, dragAreaObjectName);
1390 QFETCH(qreal, wideningAngleMultiplier);
1391 QFETCH(qreal, dragDistanceFactor);
1392- QFETCH(int, expectedGestureRecognition);
1393+ QFETCH(bool, expectGestureRecognition);
1394
1395 DirectionalDragArea *edgeDragArea =
1396 view->rootObject()->findChild<DirectionalDragArea*>(dragAreaObjectName);
1397@@ -196,7 +230,10 @@
1398 qreal desiredDragDistance = edgeDragArea->distanceThreshold()*dragDistanceFactor;
1399 QPointF dragDirectionVector = calculateDirectionVector(edgeDragArea,
1400 wideningAngleMultiplier);
1401- QPointF touchMovement = dragDirectionVector * (edgeDragArea->distanceThreshold() * 0.1f);
1402+ qreal movementStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
1403+ QPointF touchMovement = dragDirectionVector * movementStepDistance;
1404+ int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
1405+ int movementTimeStepMs = (edgeDragArea->compositionTime() * 1.5f) / totalMovementSteps;
1406
1407 QTest::touchEvent(view, device).press(0, touchPoint.toPoint());
1408
1409@@ -211,15 +248,16 @@
1410 QTest::touchEvent(view, device).move(0, touchPoint.toPoint());
1411 }
1412
1413- do {
1414+ for (int i = 0; i < totalMovementSteps; ++i) {
1415 touchPoint += touchMovement;
1416+ passTime(movementTimeStepMs);
1417 QTest::touchEvent(view, device).move(0, touchPoint.toPoint());
1418- } while ((touchPoint - initialTouchPos).manhattanLength() < desiredDragDistance);
1419+ }
1420
1421- if (expectedGestureRecognition)
1422+ if (expectGestureRecognition)
1423 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
1424
1425- if (edgeDragArea->status() == DirectionalDragArea::Rejected) {
1426+ if (edgeDragArea->status() == DirectionalDragArea::WaitingForTouch) {
1427 QCOMPARE(edgeDragArea->dragging(), false);
1428 QCOMPARE(draggingSpy.count(), 2);
1429 }
1430@@ -235,49 +273,49 @@
1431 QTest::addColumn<QString>("dragAreaObjectName");
1432 QTest::addColumn<qreal>("wideningAngleMultiplier");
1433 QTest::addColumn<qreal>("dragDistanceFactor");
1434- QTest::addColumn<int>("expectedGestureRecognition");
1435+ QTest::addColumn<bool>("expectGestureRecognition");
1436
1437 QTest::newRow("rightwards, tiny drag")
1438- << "hpDragArea" << 0.0 << 0.2 << 0;
1439+ << "hpDragArea" << 0.0 << 0.2 << false;
1440
1441 QTest::newRow("rightwards, straight drag")
1442- << "hpDragArea" << 0.0 << 3.0 << 1;
1443+ << "hpDragArea" << 0.0 << 3.0 << true;
1444
1445 QTest::newRow("rightwards, diagonal drag")
1446- << "hpDragArea" << 0.9 << 3.0 << 1;
1447+ << "hpDragArea" << 0.9 << 3.0 << true;
1448
1449 QTest::newRow("rightwards, overly diagonal drag")
1450- << "hpDragArea" << 2.0 << 3.0 << 0;
1451+ << "hpDragArea" << 2.0 << 3.0 << false;
1452
1453 QTest::newRow("leftwards, tiny drag")
1454- << "hnDragArea" << 0.0 << 0.2 << 0;
1455+ << "hnDragArea" << 0.0 << 0.2 << false;
1456
1457 QTest::newRow("leftwards, straight drag")
1458- << "hnDragArea" << 0.0 << 3.0 << 1;
1459+ << "hnDragArea" << 0.0 << 3.0 << true;
1460
1461 QTest::newRow("leftwards, diagonal drag")
1462- << "hnDragArea" << 0.9 << 3.0 << 1;
1463+ << "hnDragArea" << 0.9 << 3.0 << true;
1464
1465 QTest::newRow("downwards, tiny drag")
1466- << "vpDragArea" << 0.0 << 0.2 << 0;
1467+ << "vpDragArea" << 0.0 << 0.2 << false;
1468
1469 QTest::newRow("downwards, straight drag")
1470- << "vpDragArea" << 0.0 << 3.0 << 1;
1471+ << "vpDragArea" << 0.0 << 3.0 << true;
1472
1473 QTest::newRow("downwards, diagonal drag")
1474- << "vpDragArea" << 0.9 << 3.0 << 1;
1475+ << "vpDragArea" << 0.9 << 3.0 << true;
1476
1477 QTest::newRow("upwards, tiny drag")
1478- << "vnDragArea" << 0.0 << 0.2 << 0;
1479+ << "vnDragArea" << 0.0 << 0.2 << false;
1480
1481 QTest::newRow("upwards, straight drag")
1482- << "vnDragArea" << 0.0 << 3.0 << 1;
1483+ << "vnDragArea" << 0.0 << 3.0 << true;
1484
1485 QTest::newRow("upwards, diagonal drag")
1486- << "vnDragArea" << 0.9 << 3.0 << 1;
1487+ << "vnDragArea" << 0.9 << 3.0 << true;
1488
1489 QTest::newRow("upwards, overly diagonal drag")
1490- << "vnDragArea" << 2.0 << 3.0 << 0;
1491+ << "vnDragArea" << 2.0 << 3.0 << false;
1492 }
1493
1494 /*
1495@@ -298,25 +336,33 @@
1496
1497 qreal desiredDragDistance = edgeDragArea->distanceThreshold()*2.0;
1498 QPointF dragDirectionVector(1.0, 0.0);
1499- QPointF touchMovement = dragDirectionVector * (edgeDragArea->distanceThreshold() * 0.1f);
1500+ qreal touchStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
1501+ // make sure we are above minimum speed
1502+ int touchStepTimeMs = (touchStepDistance / (edgeDragArea->minSpeed() * 5.0f)) * 1000.0f;
1503+ QPointF touchMovement = dragDirectionVector * touchStepDistance;
1504
1505 QTest::touchEvent(view, device).press(0, touchPoint.toPoint());
1506
1507 // Move a bit in the proper direction
1508 for (int i=0; i < 3; ++i) {
1509 touchPoint += touchMovement;
1510+ passTime(touchStepTimeMs);
1511 QTest::touchEvent(view, device).move(0, touchPoint.toPoint());
1512 }
1513
1514 // Then a sudden and small movement to the opposite direction
1515- touchPoint -= dragDirectionVector * (edgeDragArea->maxDeviation() * 0.7);
1516+ touchPoint -= touchMovement*0.2;
1517+ passTime(touchStepTimeMs*0.2);
1518 QTest::touchEvent(view, device).move(0, touchPoint.toPoint());
1519
1520- // And then resume movment in the correct direction until it crosses the distance threshold.
1521+ // And then resume movment in the correct direction until it crosses the distance and time
1522+ // thresholds.
1523 do {
1524 touchPoint += touchMovement;
1525+ passTime(touchStepTimeMs);
1526 QTest::touchEvent(view, device).move(0, touchPoint.toPoint());
1527- } while ((touchPoint - initialTouchPos).manhattanLength() < desiredDragDistance);
1528+ } while ((touchPoint - initialTouchPos).manhattanLength() < desiredDragDistance
1529+ || fakeTimeSource->m_msecsSinceReference < (edgeDragArea->compositionTime() * 1.5f));
1530
1531 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
1532
1533@@ -329,7 +375,8 @@
1534 */
1535 void tst_DirectionalDragArea::minSpeed()
1536 {
1537- QFETCH(int, minSpeedMsecsDeviation);
1538+ QFETCH(qreal, minSpeed);
1539+ QFETCH(qreal, speed);
1540 QFETCH(int, expectedStatusAfterSpeedCheck);
1541
1542 DirectionalDragArea *edgeDragArea =
1543@@ -338,44 +385,40 @@
1544 edgeDragArea->setRecognitionTimer(fakeTimer);
1545 edgeDragArea->setTimeSource(fakeTimeSource);
1546
1547+ // A really long, unattainable, number. We don't want it getting recognized before
1548+ // the speed checks we want have been performed
1549+ edgeDragArea->setDistanceThreshold(500000);
1550+
1551+ edgeDragArea->setMinSpeed(minSpeed);
1552+
1553 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea, view);
1554 QPointF touchPoint = initialTouchPos;
1555
1556 QPointF dragDirectionVector(1.0, 0.0);
1557- qreal distanceStep = edgeDragArea->distanceThreshold() * 0.1f;
1558+ qint64 timeStepMsecs = 10;
1559+ qreal distanceStep = (speed / 1000.0f) * timeStepMsecs;
1560 QPointF touchMovement = dragDirectionVector * distanceStep;
1561- qreal minSpeedMsecs = edgeDragArea->minSpeed() / 1000.0;
1562- qint64 timeStepMsecs = qFloor(distanceStep / minSpeedMsecs) + minSpeedMsecsDeviation;
1563-
1564- // if it fails it means the params set in the QML file are not in harmony with the values
1565- // used in this test
1566- Q_ASSERT(timeStepMsecs > 0);
1567-
1568- fakeTimeSource->m_msecsSinceReference = 0;
1569+
1570 QTest::touchEvent(view, device).press(0, touchPoint.toPoint());
1571
1572- // Move a bit in the proper direction
1573- for (int i=0; i < 4; ++i) {
1574+ // Move for a while to ensure our speed check is performed a couple of times
1575+ for (int i=0; i < 20; ++i) {
1576 touchPoint += touchMovement;
1577- fakeTimeSource->m_msecsSinceReference += timeStepMsecs;
1578+ passTime(timeStepMsecs);
1579 QTest::touchEvent(view, device).move(0, touchPoint.toPoint());
1580 }
1581
1582- QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
1583-
1584- // Force the periodic speed check.
1585- fakeTimer->emitTimeout();
1586-
1587 QCOMPARE((int)edgeDragArea->status(), expectedStatusAfterSpeedCheck);
1588 }
1589
1590 void tst_DirectionalDragArea::minSpeed_data()
1591 {
1592- QTest::addColumn<int>("minSpeedMsecsDeviation");
1593+ QTest::addColumn<qreal>("minSpeed");
1594+ QTest::addColumn<qreal>("speed");
1595 QTest::addColumn<int>("expectedStatusAfterSpeedCheck");
1596
1597- QTest::newRow("slower than minSpeed") << 20 << (int)DirectionalDragArea::Rejected;
1598- QTest::newRow("faster than minSpeed") << -20 << (int)DirectionalDragArea::Undecided;
1599+ QTest::newRow("slower than minSpeed") << 100.0 << 50.0 << (int)DirectionalDragArea::WaitingForTouch;
1600+ QTest::newRow("faster than minSpeed") << 100.0 << 150.0 << (int)DirectionalDragArea::Undecided;
1601 }
1602
1603 /*
1604@@ -391,32 +434,37 @@
1605 edgeDragArea->setRecognitionTimer(fakeTimer);
1606 edgeDragArea->setTimeSource(fakeTimeSource);
1607
1608+ // don't let it interfere with our test
1609+ edgeDragArea->setMinSpeed(0.0);
1610+
1611+ int timeStepMs = 5; // some arbitrary small value.
1612+
1613 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea, view);
1614 QPointF touchPoint = initialTouchPos;
1615
1616 QPointF dragDirectionVector(1.0, 0.0);
1617 QPointF touchMovement = dragDirectionVector * (edgeDragArea->distanceThreshold() * 0.2f);
1618
1619+ QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
1620 QVERIFY(!fakeTimer->isRunning());
1621
1622 QTest::touchEvent(view, device).press(0, touchPoint.toPoint());
1623
1624+ QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
1625 QVERIFY(fakeTimer->isRunning());
1626
1627- // Move a bit in the proper direction
1628- for (int i=0; i < 3; ++i) {
1629+ // Move beyond distance threshold and composition time to ensure recognition
1630+ while (fakeTimeSource->m_msecsSinceReference <= edgeDragArea->compositionTime() ||
1631+ (touchPoint - initialTouchPos).manhattanLength() <= edgeDragArea->distanceThreshold()) {
1632+
1633+ QCOMPARE(edgeDragArea->status() == DirectionalDragArea::Undecided, fakeTimer->isRunning());
1634+
1635 touchPoint += touchMovement;
1636+ passTime(timeStepMs);
1637 QTest::touchEvent(view, device).move(0, touchPoint.toPoint());
1638 }
1639
1640- QVERIFY(fakeTimer->isRunning());
1641-
1642- // Move beyond distance threshold
1643- touchPoint += 3*touchMovement;
1644- QTest::touchEvent(view, device).move(0, touchPoint.toPoint());
1645-
1646 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
1647-
1648 QVERIFY(!fakeTimer->isRunning());
1649 }
1650
1651@@ -432,29 +480,23 @@
1652 edgeDragArea->setRecognitionTimer(fakeTimer);
1653 edgeDragArea->setTimeSource(fakeTimeSource);
1654
1655+ // Make sure this property is not disabled
1656+ edgeDragArea->setMaxSilenceTime(100);
1657+
1658 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea, view);
1659 QPointF touchPoint = initialTouchPos;
1660
1661- fakeTimeSource->m_msecsSinceReference = 0;
1662 QTest::touchEvent(view, device).press(0, touchPoint.toPoint());
1663
1664 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
1665-
1666- int neededTimeouts = qCeil((qreal)edgeDragArea->maxSilenceTime() / (qreal)fakeTimer->interval());
1667- if ((edgeDragArea->maxSilenceTime() % fakeTimer->interval()) == 0) {
1668- ++neededTimeouts;
1669- }
1670-
1671- // Force the periodic speed check.
1672- for (int i = 0; i < neededTimeouts; ++i) {
1673- fakeTimer->emitTimeout();
1674-
1675- if (i < neededTimeouts - 1) {
1676- QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
1677- }
1678- }
1679-
1680- QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Rejected);
1681+ QVERIFY(fakeTimer->isRunning());
1682+
1683+ // Force timer to timeout until after maxSilenceTime has been reached
1684+ while (fakeTimeSource->m_msecsSinceReference < edgeDragArea->maxSilenceTime()) {
1685+ passTime(fakeTimer->interval());
1686+ }
1687+
1688+ QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
1689 }
1690
1691 /*
1692@@ -471,7 +513,6 @@
1693
1694 QPointF touchScenePos(view->width() - (edgeDragArea->width()/2.0f), view->height()/2.0f);
1695
1696- fakeTimeSource->m_msecsSinceReference = 0;
1697 QTest::touchEvent(view, device).press(0, touchScenePos.toPoint());
1698
1699 QSignalSpy touchXSpy(edgeDragArea, SIGNAL(touchXChanged(qreal)));
1700@@ -500,7 +541,6 @@
1701
1702 QPointF touchScenePos(view->width()/2.0f, view->height() - (edgeDragArea->height()/2.0f));
1703
1704- fakeTimeSource->m_msecsSinceReference = 0;
1705 QTest::touchEvent(view, device).press(0, touchScenePos.toPoint());
1706
1707 QSignalSpy touchYSpy(edgeDragArea, SIGNAL(touchYChanged(qreal)));
1708@@ -516,11 +556,12 @@
1709 }
1710
1711 /*
1712- Emit draggingChanged() when status change from WaitingForTouch directly to Recognized.
1713- This happens when the gesture recognition is effectively disabled (e.g. by setting a
1714- distanceThreshold of zero)
1715+ Regression test for https://bugs.launchpad.net/bugs/1228336
1716+
1717+ Perform a quick two-finger double-tap and check that DirectionalDragArea properly
1718+ rejects those touch points. In the bug above it got confused and crashed.
1719 */
1720-void tst_DirectionalDragArea::emitDraggingChangedOnDirectRecognition()
1721+void tst_DirectionalDragArea::twoFingerTap()
1722 {
1723 DirectionalDragArea *edgeDragArea =
1724 view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
1725@@ -528,22 +569,170 @@
1726 edgeDragArea->setRecognitionTimer(fakeTimer);
1727 edgeDragArea->setTimeSource(fakeTimeSource);
1728
1729- QPointF touchPos = calculateInitialTouchPos(edgeDragArea, view);
1730-
1731- QSignalSpy draggingSpy(edgeDragArea, SIGNAL(draggingChanged(bool)));
1732-
1733- // That will make it completetly skip the recognition phase ("Undecided" status).
1734- edgeDragArea->setDistanceThreshold(0.0);
1735-
1736- QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
1737- QCOMPARE(edgeDragArea->dragging(), false);
1738-
1739- fakeTimeSource->m_msecsSinceReference = 0;
1740- QTest::touchEvent(view, device).press(0, touchPos.toPoint());
1741-
1742- QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
1743- QCOMPARE(draggingSpy.count(), 1);
1744- QCOMPARE(edgeDragArea->dragging(), true);
1745+ // Make touches evenly spaced along the edgeDragArea
1746+ QPoint touchAPos(edgeDragArea->width()/2.0f, view->height()*0.33f);
1747+ QPoint touchBPos(edgeDragArea->width()/2.0f, view->height()*0.66f);
1748+
1749+ qint64 timeStepMsecs = 5; // some arbitrary, small value
1750+
1751+ // Perform the first two-finger tap
1752+ // NB: using move() instead of stationary() becasue in the latter you cannot provide
1753+ // the touch position and therefore it's left with some garbage numbers.
1754+ QTest::touchEvent(view, device)
1755+ .press(0, touchAPos);
1756+
1757+ QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
1758+
1759+ passTime(timeStepMsecs);
1760+ QTest::touchEvent(view, device)
1761+ .move(0, touchAPos)
1762+ .press(1, touchBPos);
1763+
1764+ // A second touch point appeared during recognition, reject immediately as this
1765+ // can't be a single-touch gesture anymore.
1766+ QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
1767+
1768+ passTime(timeStepMsecs);
1769+ QTest::touchEvent(view, device)
1770+ .release(0, touchAPos)
1771+ .move(1, touchBPos);
1772+
1773+ QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
1774+
1775+ passTime(timeStepMsecs);
1776+ QTest::touchEvent(view, device)
1777+ .release(1, touchBPos);
1778+
1779+ QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
1780+
1781+ // Perform the second two-finger tap
1782+
1783+ passTime(timeStepMsecs);
1784+ QTest::touchEvent(view, device)
1785+ .press(0, touchAPos);
1786+
1787+ QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
1788+
1789+ passTime(timeStepMsecs);
1790+ QTest::touchEvent(view, device)
1791+ .move(0, touchAPos)
1792+ .press(1, touchBPos);
1793+
1794+ QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
1795+
1796+ passTime(timeStepMsecs);
1797+ QTest::touchEvent(view, device)
1798+ .release(0, touchAPos)
1799+ .move(1, touchBPos);
1800+
1801+ QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
1802+
1803+ passTime(timeStepMsecs);
1804+ QTest::touchEvent(view, device)
1805+ .release(1, touchBPos);
1806+
1807+ QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
1808+}
1809+
1810+/*
1811+ Tests that gesture recognition works normally even if the DirectionalDragArea moves
1812+ during recognition.
1813+ This effectively means that we have to do gesture recognition with scene coordinates
1814+ instead of local item coordinates.
1815+ */
1816+void tst_DirectionalDragArea::movingDDA()
1817+{
1818+ QQuickItem *rightwardsLauncher = view->rootObject()->findChild<QQuickItem*>("rightwardsLauncher");
1819+ Q_ASSERT(rightwardsLauncher != 0);
1820+
1821+ DirectionalDragArea *edgeDragArea =
1822+ rightwardsLauncher->findChild<DirectionalDragArea*>("hpDragArea");
1823+ Q_ASSERT(edgeDragArea != 0);
1824+ edgeDragArea->setRecognitionTimer(fakeTimer);
1825+ edgeDragArea->setTimeSource(fakeTimeSource);
1826+
1827+ QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea, view);
1828+ QPointF touchPoint = initialTouchPos;
1829+
1830+ qreal desiredDragDistance = edgeDragArea->distanceThreshold()*2.0f;
1831+ QPointF dragDirectionVector(1.0f, 0.0f);
1832+
1833+ qreal movementStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
1834+ QPointF touchMovement = dragDirectionVector * movementStepDistance;
1835+ int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
1836+ int movementTimeStepMs = (edgeDragArea->compositionTime() * 1.5f) / totalMovementSteps;
1837+
1838+ QTest::touchEvent(view, device).press(0, touchPoint.toPoint());
1839+
1840+ // Move it far ahead along the direction of the gesture
1841+ // rightwardsLauncher is a parent of our DirectionalDragArea. So moving it will move our DDA
1842+ rightwardsLauncher->setX(rightwardsLauncher->x() + edgeDragArea->distanceThreshold() * 5.0f);
1843+
1844+ for (int i = 0; i < totalMovementSteps; ++i) {
1845+ touchPoint += touchMovement;
1846+ passTime(movementTimeStepMs);
1847+ QTest::touchEvent(view, device).move(0, touchPoint.toPoint());
1848+ }
1849+
1850+ QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
1851+
1852+ QTest::touchEvent(view, device).release(0, touchPoint.toPoint());
1853+}
1854+
1855+/*
1856+ The presence of and old, rejected, touch point lying on the DirectionalDragArea
1857+ shouldn't impede the recognition of a gesture from a new touch point.
1858+ */
1859+void tst_DirectionalDragArea::ignoreOldFinger()
1860+{
1861+ DirectionalDragArea *edgeDragArea =
1862+ view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
1863+ Q_ASSERT(edgeDragArea != 0);
1864+ edgeDragArea->setRecognitionTimer(fakeTimer);
1865+ edgeDragArea->setTimeSource(fakeTimeSource);
1866+
1867+ // Make touches evenly spaced along the edgeDragArea
1868+ QPoint touch0Pos(edgeDragArea->width()/2.0f, view->height()*0.33f);
1869+ QPoint touch1Pos(edgeDragArea->width()/2.0f, view->height()*0.66f);
1870+
1871+ QTest::touchEvent(view, device).press(0, touch0Pos);
1872+
1873+ QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
1874+
1875+ // leave it lying around for some time
1876+ passTime(qMax(edgeDragArea->maxSilenceTime(), edgeDragArea->compositionTime()) * 10);
1877+
1878+ QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
1879+
1880+ qreal desiredDragDistance = edgeDragArea->distanceThreshold()*2.0f;
1881+ QPointF dragDirectionVector(1.0f, 0.0f);
1882+
1883+ qreal movementStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
1884+ QPointF touchMovement = dragDirectionVector * movementStepDistance;
1885+ int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
1886+ int movementTimeStepMs = (edgeDragArea->compositionTime() * 1.5f) / totalMovementSteps;
1887+
1888+ QTest::touchEvent(view, device)
1889+ .move(0, touch0Pos)
1890+ .press(1, touch1Pos);
1891+
1892+ QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
1893+
1894+ for (int i = 0; i < totalMovementSteps; ++i) {
1895+ touch1Pos += touchMovement.toPoint();
1896+ passTime(movementTimeStepMs);
1897+ QTest::touchEvent(view, device)
1898+ .move(0, touch0Pos)
1899+ .move(1, touch1Pos);
1900+ }
1901+
1902+ QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
1903+
1904+ QTest::touchEvent(view, device)
1905+ .move(0, touch0Pos)
1906+ .release(1, touch1Pos);
1907+
1908+ QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
1909 }
1910
1911 QTEST_MAIN(tst_DirectionalDragArea)
1912
1913=== modified file 'tests/qmltests/Components/tst_DragHandle.cpp'
1914--- tests/qmltests/Components/tst_DragHandle.cpp 2013-07-01 16:32:00 +0000
1915+++ tests/qmltests/Components/tst_DragHandle.cpp 2013-11-05 10:55:36 +0000
1916@@ -41,7 +41,7 @@
1917 {
1918 public:
1919 FakeTimeSource() { m_msecsSinceReference = 0; }
1920- virtual qint64 msecsSinceReference() {return m_msecsSinceReference;}
1921+ qint64 msecsSinceReference() override {return m_msecsSinceReference;}
1922 qint64 m_msecsSinceReference;
1923 };
1924
1925@@ -73,7 +73,7 @@
1926 QQuickView *m_view;
1927 QTouchDevice *m_device;
1928 FakeTimer *m_fakeTimer;
1929- FakeTimeSource *m_fakeTimeSource;
1930+ QSharedPointer<FakeTimeSource> m_fakeTimeSource;
1931 };
1932
1933
1934@@ -102,7 +102,7 @@
1935 qApp->processEvents();
1936
1937 m_fakeTimer = new FakeTimer;
1938- m_fakeTimeSource = new FakeTimeSource;
1939+ m_fakeTimeSource.reset(new FakeTimeSource);
1940 }
1941
1942 void tst_DragHandle::cleanup()
1943@@ -113,8 +113,7 @@
1944 delete m_fakeTimer;
1945 m_fakeTimer = 0;
1946
1947- delete m_fakeTimeSource;
1948- m_fakeTimeSource = 0;
1949+ m_fakeTimeSource.reset();
1950 }
1951
1952 QQuickView *tst_DragHandle::createView()
1953
1954=== modified file 'tests/qmltests/Panel/tst_Panel.qml'
1955--- tests/qmltests/Panel/tst_Panel.qml 2013-08-22 18:37:09 +0000
1956+++ tests/qmltests/Panel/tst_Panel.qml 2013-11-05 10:55:36 +0000
1957@@ -52,7 +52,6 @@
1958 {tag: "fullscreen", fullscreenFlag: true }
1959 ]}
1960
1961-
1962 function init() {
1963 searchClicked = false;
1964 panel.indicators.hide();
1965
1966=== modified file 'tests/qmltests/tst_Shell.qml'
1967--- tests/qmltests/tst_Shell.qml 2013-10-09 20:01:12 +0000
1968+++ tests/qmltests/tst_Shell.qml 2013-11-05 10:55:36 +0000
1969@@ -55,19 +55,42 @@
1970 when: windowShown
1971
1972 function initTestCase() {
1973+ var ok = false;
1974+ var attempts = 0;
1975+ var maxAttempts = 1000;
1976+
1977+ // Qt loads a qml scene asynchronously. So early on, some findChild() calls made in
1978+ // tests may fail because the desired child item wasn't loaded yet.
1979+ // Thus here we try to ensure the scene has been fully loaded before proceeding with the tests.
1980+ // As I couldn't find an API in QQuickView & friends to tell me that the scene is 100% loaded
1981+ // (all items instantiated, etc), I resort to checking the existence of some key items until
1982+ // repeatedly until they're all there.
1983+ do {
1984+ var dashContentList = findChild(shell, "dashContentList");
1985+ waitForRendering(dashContentList);
1986+ var homeLoader = findChild(dashContentList, "home.scope loader");
1987+ ok = homeLoader !== undefined
1988+ && homeLoader.item !== undefined;
1989+
1990+ var greeter = findChild(shell, "greeter");
1991+ ok &= greeter !== undefined;
1992+
1993+ var launcherPanel = findChild(shell, "launcherPanel");
1994+ ok &= launcherPanel !== undefined;
1995+
1996+ attempts++;
1997+ if (!ok) {
1998+ console.log("Attempt " + attempts + " failed. Waiting a bit before trying again.");
1999+ // wait a bit before retrying
2000+ wait(100);
2001+ } else {
2002+ console.log("All seem fine after " + attempts + " attempts.");
2003+ }
2004+ } while (!ok && attempts <= maxAttempts);
2005+
2006+ verify(ok);
2007+
2008 swipeAwayGreeter();
2009-
2010- // Ensure DashHome is loaded before continuing
2011- var dashContentList = findChild(shell, "dashContentList");
2012- waitForRendering(dashContentList);
2013- var homeLoader = findChild(dashContentList, "home.scope loader");
2014- verify(homeLoader);
2015- tryCompareFunction(function() {return homeLoader.item !== undefined;}, true);
2016-
2017- // FIXME: Desperate measure to ensure Jenkins has the scene graph fully loaded
2018- // before it calls the test functions (otherwise findChild calls will fail).
2019- // The code above just didn't solve it.
2020- wait(2000);
2021 }
2022
2023 function cleanup() {
2024
2025=== modified file 'tests/utils/modules/Unity/Test/UnityTestCase.qml'
2026--- tests/utils/modules/Unity/Test/UnityTestCase.qml 2013-08-22 18:36:46 +0000
2027+++ tests/utils/modules/Unity/Test/UnityTestCase.qml 2013-11-05 10:55:36 +0000
2028@@ -14,6 +14,7 @@
2029 * along with this program. If not, see <http://www.gnu.org/licenses/>.
2030 */
2031
2032+import QtQuick 2.0
2033 import QtTest 1.0
2034 import Ubuntu.Components 0.1
2035 import Unity.Test 0.1 as UT
2036@@ -297,4 +298,37 @@
2037 event.release(0 /* touchId */, x, y)
2038 event.commit()
2039 }
2040+
2041+ Component.onCompleted: {
2042+ var rootItem = parent;
2043+ while (rootItem.parent != undefined) {
2044+ rootItem = rootItem.parent;
2045+ }
2046+ removeTimeConstraintsFromDirectionalDragAreas(rootItem);
2047+ }
2048+
2049+ /*
2050+ In qmltests, sequences of touch events are sent all at once, unlike in "real life".
2051+ Also qmltests might run really slowly, e.g. when run from inside virtual machines.
2052+ Thus to remove a variable that qmltests cannot really control, namely time, this
2053+ function removes all constraints from DirectionalDragAreas that are sensible to
2054+ elapsed time.
2055+
2056+ This effectively makes DirectionalDragAreas easier to fool.
2057+ */
2058+ function removeTimeConstraintsFromDirectionalDragAreas(item) {
2059+
2060+ // use duck-typing to identify a DirectionalDragArea
2061+ if (item.minSpeed != undefined
2062+ && item.maxSilenceTime != undefined
2063+ && item.compositionTime != undefined) {
2064+ item.minSpeed = 0;
2065+ item.maxSilenceTime = 60 * 60 * 1000;
2066+ item.compositionTime = 0;
2067+ } else {
2068+ for (var i in item.children) {
2069+ removeTimeConstraintsFromDirectionalDragAreas(item.children[i]);
2070+ }
2071+ }
2072+ }
2073 }

Subscribers

People subscribed via source and target branches