Merge lp:~dandrader/unity8/ddaImprovements into lp:unity8

Proposed by Daniel d'Andrada on 2015-04-10
Status: Merged
Approved by: Albert Astals Cid on 2015-04-27
Approved revision: 1718
Merged at revision: 1758
Proposed branch: lp:~dandrader/unity8/ddaImprovements
Merge into: lp:unity8
Diff against target: 5591 lines (+1769/-1521)
49 files modified
libs/UbuntuGestures/CMakeLists.txt (+1/-0)
libs/UbuntuGestures/CandidateInactivityTimer.h (+1/-1)
libs/UbuntuGestures/TimeSource.h (+14/-4)
libs/UbuntuGestures/Timer.cpp (+67/-14)
libs/UbuntuGestures/Timer.h (+15/-3)
libs/UbuntuGestures/TouchRegistry.cpp (+12/-14)
libs/UbuntuGestures/TouchRegistry.h (+18/-4)
plugins/Ubuntu/Gestures/CMakeLists.txt (+1/-1)
plugins/Ubuntu/Gestures/Damper.cpp (+24/-0)
plugins/Ubuntu/Gestures/Damper.h (+2/-0)
plugins/Ubuntu/Gestures/DirectionalDragArea.cpp (+362/-363)
plugins/Ubuntu/Gestures/DirectionalDragArea.h (+34/-207)
plugins/Ubuntu/Gestures/DirectionalDragArea_p.h (+167/-0)
plugins/Ubuntu/Gestures/TouchDispatcher.cpp (+88/-53)
plugins/Ubuntu/Gestures/TouchDispatcher.h (+16/-9)
plugins/Ubuntu/Gestures/TouchGate.cpp (+87/-63)
plugins/Ubuntu/Gestures/TouchGate.h (+18/-5)
qml/Components/DragHandle.qml (+20/-35)
qml/Components/EdgeDragArea.qml (+0/-46)
qml/Dash/Dash.qml (+5/-12)
qml/Greeter/CoverPage.qml (+3/-2)
qml/Launcher/Launcher.qml (+7/-15)
qml/Panel/IndicatorsMenu.qml (+12/-2)
qml/Stages/PhoneStage.qml (+33/-43)
qml/Stages/TabletStage.qml (+19/-21)
qml/Tutorial/TutorialBottom.qml (+1/-1)
tests/libs/UbuntuGestures/tst_TouchRegistry.cpp (+68/-2)
tests/plugins/Ubuntu/Gestures/CMakeLists.txt (+3/-1)
tests/plugins/Ubuntu/Gestures/DownwardsLauncher.qml (+5/-19)
tests/plugins/Ubuntu/Gestures/GestureTest.cpp (+13/-3)
tests/plugins/Ubuntu/Gestures/GestureTest.h (+4/-0)
tests/plugins/Ubuntu/Gestures/LeftwardsLauncher.qml (+6/-19)
tests/plugins/Ubuntu/Gestures/RightwardsLauncher.qml (+5/-19)
tests/plugins/Ubuntu/Gestures/UpwardsLauncher.qml (+6/-19)
tests/plugins/Ubuntu/Gestures/tst_DirectionalDragArea.cpp (+496/-461)
tests/plugins/Ubuntu/Gestures/tst_DirectionalDragArea.qml (+8/-1)
tests/plugins/Ubuntu/Gestures/tst_TouchDispatcher.cpp (+5/-6)
tests/plugins/Ubuntu/Gestures/tst_TouchGate.cpp (+56/-1)
tests/qmltests/Components/CMakeLists.txt (+3/-1)
tests/qmltests/Components/tst_DragHandle.cpp (+15/-13)
tests/qmltests/Components/tst_DragHandle.qml (+7/-0)
tests/qmltests/Components/tst_DragHandle/BidirectionalShowable.qml (+2/-2)
tests/qmltests/Components/tst_DragHandle/HorizontalShowable.qml (+4/-4)
tests/qmltests/Components/tst_DragHandle/VerticalShowable.qml (+4/-4)
tests/qmltests/Stages/tst_PhoneStage.qml (+8/-8)
tests/qmltests/Tutorial/tst_Tutorial.qml (+5/-1)
tests/qmltests/tst_Shell.qml (+14/-10)
tests/qmltests/tst_ShellWithPin.qml (+2/-2)
tests/utils/modules/Unity/Test/UnityTestCase.qml (+3/-7)
To merge this branch: bzr merge lp:~dandrader/unity8/ddaImprovements
Reviewer Review Type Date Requested Status
Michael Zanetti (community) 2015-04-10 Approve on 2015-04-24
Albert Astals Cid (community) 2015-04-10 Approve on 2015-04-17
PS Jenkins bot continuous-integration 2015-04-10 Needs Fixing on 2015-04-16
Michał Sawicz 2015-04-10 Pending
Review via email: mp+255896@code.launchpad.net

This proposal supersedes a proposal from 2015-04-01.

Commit Message

DirectionalDragArea: improvements & API grooming

-Simplified DirectionalDragArea gesture recognition
 * The widening angle property was dropped. Didn't really work
   as in real life swipes might start up in very different directions
   before finally turning to the final overall direction. That's specially
   bad when you have to conclude the recognition in a short amount
   of time so that you can still play animations to follow the user's finger
 * minSpeed as well. Went for a simpler way of expressing and evaluating it
   which is having maxTime and maxDistance.

-EdgeDragArea got absorbed by DirectionalDragArea

-Privatized all gesture recognition parameters.

-Privatized DirectionalDragArea.status property. All apps need is
 DirectionalDragArea.dragging. Modified qml code using DDA accordingly.

-Modified DirectionalDragArea.dragging semantics a bit. It goes to true only
 once a gesture is recognized

-Moved code that smooths out touch movement into DirectionalDragArea so all users
 get it for free.

-Cleaned up DirectionalDragArea.h of all implementation details, getting it ready
 to be moved out of unity8, into the SDK.

-Removed DirectionalDragArea.tapped() signal as it doesn't belong to it.

-Fine-tuned gesture recognition parameters and made them based on physical size.

-Added tests with input from real gestures to catch those annoying
  false-negatives and false-positives introduced by badly set recognition parameters

-Improved debug output

-UnownedTouchEvents are no longer sent to interim owners

-Fixed issue when TouchGate got disabled while holding an active touch

-Made "make tryDirectionalDragArea" and "make tryDragHandle" work with mouse again.

Description of the Change

Fixes bug 1417920.
The way parameters are currently set in EdgeDragArea, a gesture gets recognized regardless of its direction, it only have to be dragged far enough.

* Are there any related MPs required for this MP to build/function as expected? Please list.
No.
* Did you perform an exploratory manual test run of your code change and any related functionality?
Yes.
* Did you make sure that your branch does not contain spurious tags?
Yes.
* If you changed the packaging (debian), did you subscribe the ubuntu-unity team to this MP?
Not applicable.
* If you changed the UI, has there been a design review?
Not applicable.

To post a comment you must log in.
Michał Sawicz (saviq) wrote : Posted in a previous version of this proposal

Failed QML tests:
    qmltestrunner.Launcher::test_clickFlick
    qmltestrunner.PhoneStage::test_shortFlick
    qmltestrunner.ShellWithPin::test_shortLeftEdgeSwipeMakesLauncherStayVisible

review: Needs Fixing
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

> Failed QML tests:
> qmltestrunner.Launcher::test_clickFlick
> qmltestrunner.PhoneStage::test_shortFlick
> qmltestrunner.ShellWithPin::test_shortLeftEdgeSwipeMakesLauncherStayVisible

Fixed.

Michał Sawicz (saviq) : Posted in a previous version of this proposal
review: Abstain
Albert Astals Cid (aacid) wrote : Posted in a previous version of this proposal

This does not actually fix the linked bug right?

Any way to test this change?

review: Needs Information
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

> This does not actually fix the linked bug right?
>
> Any way to test this change?

It does, but you have to make Launcher's EdgeDragArea cover the whole launcher in order to notice it.

=== modified file 'qml/Launcher/Launcher.qml'
--- qml/Launcher/Launcher.qml 2015-03-12 13:04:36 +0000
+++ qml/Launcher/Launcher.qml 2015-04-07 12:19:02 +0000
@@ -280,7 +280,7 @@ Item {

         enabled: root.available
         x: -root.x // so if launcher is adjusted relative to screen, we stay put (like tutorial does when teasing)
- width: root.dragAreaWidth
+ width: root.panelWidth
         height: root.height

         onTouchXChanged: {

Apply diff the diff above to trunk and try to scroll the launcher icons. It won't work.

Then merge this branch and try again. It will work.

And check that all DDAs still work normally
  - show and hide indicators panel
  - right-edge drag to show the spread
  - left-edge drag to show launcher
  - horizontal drag to dismiss the greeter
  - bottom edge drag to show the "manage dash" UI.

Albert Astals Cid (aacid) wrote : Posted in a previous version of this proposal

Can you confirm that doing http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-vivid-armhf/2103/artifact/work/output/*zip*/output.zip + width: root.panelWidth in /usr/share/unity8/Launcher/Launcher.qml in the phone it works for you?

I just tried and it still closes when i swipe vertically over an open Launcher.

review: Needs Information
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

On 07/04/15 11:08, Albert Astals Cid wrote:
> Review: Needs Information
>
> Can you confirm that doing http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-vivid-armhf/2103/artifact/work/output/*zip*/output.zip + width: root.panelWidth in /usr/share/unity8/Launcher/Launcher.qml in the phone it works for you?
>
> I just tried and it still closes when i swipe vertically over an open Launcher.

Sorry, this is the full patch:

"""
=== modified file 'qml/Launcher/Launcher.qml'
--- qml/Launcher/Launcher.qml 2015-03-12 13:04:36 +0000
+++ qml/Launcher/Launcher.qml 2015-04-07 15:13:15 +0000
@@ -280,7 +280,7 @@ Item {

         enabled: root.available
         x: -root.x // so if launcher is adjusted relative to screen, we
stay put (like tutorial does when teasing)
- width: root.dragAreaWidth
+ width: root.panelWidth
         height: root.height

         onTouchXChanged: {
@@ -306,8 +306,9 @@ Item {
                     if (distance > minimizeDistance) {
                         root.dash();
                     }
- } else {
- root.switchToNextState("")
+ } else if (root.state === "") {
+ // didn't drag far enough. rollback
+ root.switchToNextState("");
                 }
             }
         }
"""

And you don't have to do it on the phone. Doing it in "make tryShell" is
enough. Apply this patch in trunk and you won't be able to scroll the
launcher as the DDA in front of it is recognizing even those vertical
swiped. Apply it on top of ddaImprovements branch and you will see you
can scroll launcher icons just fine as the DDA rejects vertical drags.

Albert Astals Cid (aacid) wrote : Posted in a previous version of this proposal

Hmmm, the code looks ok, can't see anything wrong, but i think some parameter changes need some tweaking, with the old code i can show the launcher with a small drag and it'll show the margin of the launcher and very few of the icons, with this i need to do a bigger drag and it'll in consequence show more part of the launcher.

On the other hand not sure this is problematic either. Can you try it yourself and see what you think?

review: Needs Information
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

On 08/04/15 08:18, Albert Astals Cid wrote:
> Review: Needs Information
>
> Hmmm, the code looks ok, can't see anything wrong, but i think some parameter changes need some tweaking, with the old code i can show the launcher with a small drag and it'll show the margin of the launcher and very few of the icons, with this i need to do a bigger drag and it'll in consequence show more part of the launcher.
>
> On the other hand not sure this is problematic either. Can you try it yourself and see what you think?
I get what you mean. But this is not relevant as you only do those very
short drags when you're testing the edge drag feature, ie when you're
checking how much drag it needs to kick in etc. In real usage no one
would do a very short edge drag expose the launcher just a little bit. :)

In any case I reduced the distance threshold a bit so that the gesture
kicks in a bit sooner, if that makes you feel better. :) But it does
help smoothing out the very beginning of the right edge drag (to show
the spread) as it is not animated like the left edge one. It just jumps
to the current drag position instead of animating towards it. Which
makes the larger drag threshold noticeable. But that's an issue with the
"show the spread with right-edge gesture" qml code, not with DDA per se.

Albert Astals Cid (aacid) wrote : Posted in a previous version of this proposal

Approving since code looks good and the behaviour change is acceptable to me but i'd like someone else to give it a try on the phone before top approving.

 * Did you perform an exploratory manual test run of the code change and any related functionality?
Yes

 * Did CI run pass?
known autopilot fixed elsewhere

 * Did you make sure that the branch does not contain spurious tags?
Yes

review: Approve
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

right-edge animation start is now smooth as silk

Michał Sawicz (saviq) wrote : Posted in a previous version of this proposal

Broken tests again:
    qmltestrunner.PhoneStage::test_enterSpread
    qmltestrunner.Tutorial::test_bottomShortDrag
    qmltestrunner.Tutorial::test_walkthrough

Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

On 09/04/15 15:35, Michał Sawicz wrote:
> Broken tests again:
> qmltestrunner.PhoneStage::test_enterSpread
> qmltestrunner.Tutorial::test_bottomShortDrag
> qmltestrunner.Tutorial::test_walkthrough
Fixed

Michael Zanetti (mzanetti) wrote : Posted in a previous version of this proposal

> right-edge animation start is now smooth as silk

If this would just be unity8, I'd be totally happy with it (Except that you're missing TabletStage.qml). But given that the DDA should be an upstream component, I'm hesitant to make all the app developers do that magic as you added in the Phone stage. They won't, and as a result we'll have the jumpiness every time an app uses the DDA.

Daniel d'Andrada (dandrader) wrote :

>> right-edge animation start is now smooth as silk
> If this would just be unity8, I'd be totally happy with it (Except that you're missing TabletStage.qml). But given that the DDA should be an upstream component, I'm hesitant to make all the app developers do that magic as you added in the Phone stage. They won't, and as a result we'll have the jumpiness every time an app uses the DDA.

Ok, you asked for it: Put all the DDA improvements into this branch.

lp:~dandrader/unity8/ddaImprovements updated on 2015-04-13
1712. By Daniel d'Andrada on 2015-04-13

Fix whitespace issues

Michael Zanetti (mzanetti) wrote :

2838- if (status !== DirectionalDragArea.Recognized || launcher.state == "visible")
2839+ if (!dragging || launcher.state == "visible")

This seems to make the launcher jump a little... also design wants to see the launcher's shadow immediately when you touch the edge.

My suggestion would be a "pressed" property in the DirectionalDragArea that could be used for that.

lp:~dandrader/unity8/ddaImprovements updated on 2015-04-15
1713. By Daniel d'Andrada on 2015-04-15

Fix UnityTestCase.removeTimeConstraintsFromDirectionalDragAreas()

1714. By Daniel d'Andrada on 2015-04-15

Fix tst_Shell::test_leftEdgeDragFullscreen

1715. By Daniel d'Andrada on 2015-04-15

fix shell tests

1716. By Daniel d'Andrada on 2015-04-15

Added DirectionalDragArea.pressed property

1717. By Daniel d'Andrada on 2015-04-15

Fix tst_ShellWithPin

1718. By Daniel d'Andrada on 2015-04-15

Tweak launcher animation

Daniel d'Andrada (dandrader) wrote :

> 2838- if (status !== DirectionalDragArea.Recognized ||
> launcher.state == "visible")
> 2839+ if (!dragging || launcher.state == "visible")
>
> This seems to make the launcher jump a little... also design wants to see the
> launcher's shadow immediately when you touch the edge.
>
> My suggestion would be a "pressed" property in the DirectionalDragArea that
> could be used for that.

Added the pressed property. It's needed in the Greeter.
Made Launcher use it although I don't think it makes a difference. What did solve Launcher's jumpiness was making it use DDA.distance instead of DDA.touchX, since the former starts from 0 whereas the latter starts from the X position from the the drag started (eg: touchStartPos.x())

Daniel d'Andrada (dandrader) wrote :

qmltests are also passing now.

Albert Astals Cid (aacid) wrote :

Had another look at this and for what my manual testing and looking and the code says it's fine, next time i would really appreciate if you do not rebase everything into a single commit since it makes reviewing the changes from the last time i did a review much harder since basically i have to look at it all.

Leaving top un-approved to let others have a look.

 * Did you perform an exploratory manual test run of the code change and any related functionality?
Yes

 * Did CI run pass?
Yes, to the extend it passes everywhere else

 * Did you make sure that the branch does not contain spurious tags?
Yes

review: Approve
Michael Zanetti (mzanetti) wrote :

Ack. My concerns have been addressed. If Albert is fine with it now, so am I.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'libs/UbuntuGestures/CMakeLists.txt'
2--- libs/UbuntuGestures/CMakeLists.txt 2014-10-01 13:20:32 +0000
3+++ libs/UbuntuGestures/CMakeLists.txt 2015-04-15 20:21:35 +0000
4@@ -5,6 +5,7 @@
5 CandidateInactivityTimer.cpp
6 DebugHelpers.cpp
7 Timer.cpp
8+ TimeSource.cpp
9 TouchOwnershipEvent.cpp
10 TouchRegistry.cpp
11 UnownedTouchEvent.cpp
12
13=== modified file 'libs/UbuntuGestures/CandidateInactivityTimer.h'
14--- libs/UbuntuGestures/CandidateInactivityTimer.h 2014-10-01 13:20:32 +0000
15+++ libs/UbuntuGestures/CandidateInactivityTimer.h 2015-04-15 20:21:35 +0000
16@@ -32,7 +32,7 @@
17 AbstractTimerFactory &timerFactory,
18 QObject *parent = nullptr);
19
20- const int durationMs = 350;
21+ const int durationMs = 1000;
22
23 Q_SIGNALS:
24 void candidateDefaulted(int touchId, QQuickItem *candidate);
25
26=== renamed file 'plugins/Ubuntu/Gestures/TimeSource.cpp' => 'libs/UbuntuGestures/TimeSource.cpp'
27=== renamed file 'plugins/Ubuntu/Gestures/TimeSource.h' => 'libs/UbuntuGestures/TimeSource.h'
28--- plugins/Ubuntu/Gestures/TimeSource.h 2014-10-01 13:20:32 +0000
29+++ libs/UbuntuGestures/TimeSource.h 2015-04-15 20:21:35 +0000
30@@ -1,5 +1,5 @@
31 /*
32- * Copyright (C) 2013 - Canonical Ltd.
33+ * Copyright (C) 2013,2015 Canonical Ltd.
34 *
35 * This program is free software: you can redistribute it and/or modify it
36 * under the terms of the GNU Lesser General Public License, as
37@@ -21,14 +21,14 @@
38 #ifndef UBUNTUGESTURES_TIMESOURCE_H
39 #define UBUNTUGESTURES_TIMESOURCE_H
40
41-#include "UbuntuGesturesQmlGlobal.h"
42+#include "UbuntuGesturesGlobal.h"
43 #include <QSharedPointer>
44
45 namespace UbuntuGestures {
46 /*
47 Interface for a time source.
48 */
49-class UBUNTUGESTURESQML_EXPORT TimeSource {
50+class UBUNTUGESTURES_EXPORT TimeSource {
51 public:
52 virtual ~TimeSource() {}
53 /* Returns the current time in milliseconds since some reference time in the past. */
54@@ -40,7 +40,7 @@
55 Implementation of a time source
56 */
57 class RealTimeSourcePrivate;
58-class RealTimeSource : public TimeSource {
59+class UBUNTUGESTURES_EXPORT RealTimeSource : public TimeSource {
60 public:
61 RealTimeSource();
62 virtual ~RealTimeSource();
63@@ -49,6 +49,16 @@
64 RealTimeSourcePrivate *d;
65 };
66
67+/*
68+ A fake time source, useful for tests
69+ */
70+class FakeTimeSource : public TimeSource {
71+public:
72+ FakeTimeSource() { m_msecsSinceReference = 0; }
73+ qint64 msecsSinceReference() override { return m_msecsSinceReference; }
74+ qint64 m_msecsSinceReference;
75+};
76+
77 } // namespace UbuntuGestures
78
79 #endif // UBUNTUGESTURES_TIMESOURCE_H
80
81=== modified file 'libs/UbuntuGestures/Timer.cpp'
82--- libs/UbuntuGestures/Timer.cpp 2014-10-01 13:20:32 +0000
83+++ libs/UbuntuGestures/Timer.cpp 2015-04-15 20:21:35 +0000
84@@ -1,5 +1,5 @@
85 /*
86- * Copyright (C) 2014 Canonical, Ltd.
87+ * Copyright (C) 2014-2015 Canonical, Ltd.
88 *
89 * This program is free software; you can redistribute it and/or modify
90 * it under the terms of the GNU General Public License as published by
91@@ -58,11 +58,34 @@
92
93 /////////////////////////////////// FakeTimer //////////////////////////////////
94
95-FakeTimer::FakeTimer(QObject *parent)
96+FakeTimer::FakeTimer(const SharedTimeSource &timeSource, QObject *parent)
97 : UbuntuGestures::AbstractTimer(parent)
98 , m_interval(0)
99 , m_singleShot(false)
100-{
101+ , m_timeSource(timeSource)
102+{
103+}
104+
105+void FakeTimer::update()
106+{
107+ if (!isRunning()) {
108+ return;
109+ }
110+
111+ if (m_nextTimeoutTime <= m_timeSource->msecsSinceReference()) {
112+ if (isSingleShot()) {
113+ stop();
114+ } else {
115+ m_nextTimeoutTime += interval();
116+ }
117+ Q_EMIT timeout();
118+ }
119+}
120+
121+void FakeTimer::start()
122+{
123+ AbstractTimer::start();
124+ m_nextTimeoutTime = m_timeSource->msecsSinceReference() + (qint64)interval();
125 }
126
127 int FakeTimer::interval() const
128@@ -87,23 +110,53 @@
129
130 /////////////////////////////////// FakeTimerFactory //////////////////////////////////
131
132+FakeTimerFactory::FakeTimerFactory()
133+{
134+ m_timeSource.reset(new FakeTimeSource);
135+}
136+
137+FakeTimerFactory::~FakeTimerFactory()
138+{
139+ for (int i = 0; i < timers.count(); ++i) {
140+ FakeTimer *timer = timers[i].data();
141+ if (timer) {
142+ delete timer;
143+ }
144+ }
145+}
146+
147+void FakeTimerFactory::updateTime(qint64 targetTime)
148+{
149+ qint64 minTimeoutTime = targetTime;
150+
151+ for (int i = 0; i < timers.count(); ++i) {
152+ FakeTimer *timer = timers[i].data();
153+ if (timer && timer->isRunning() && timer->nextTimeoutTime() < minTimeoutTime) {
154+ minTimeoutTime = timer->nextTimeoutTime();
155+ }
156+ }
157+
158+ m_timeSource->m_msecsSinceReference = minTimeoutTime;
159+
160+ for (int i = 0; i < timers.count(); ++i) {
161+ FakeTimer *timer = timers[i].data();
162+ if (timer) {
163+ timer->update();
164+ }
165+ }
166+
167+ if (m_timeSource->msecsSinceReference() < targetTime) {
168+ updateTime(targetTime);
169+ }
170+}
171+
172 AbstractTimer *FakeTimerFactory::createTimer(QObject *parent)
173 {
174- FakeTimer *fakeTimer = new FakeTimer(parent);
175+ FakeTimer *fakeTimer = new FakeTimer(m_timeSource, parent);
176
177 timers.append(fakeTimer);
178
179 return fakeTimer;
180 }
181
182-void FakeTimerFactory::makeRunningTimersTimeout()
183-{
184- for (int i = 0; i < timers.count(); ++i) {
185- FakeTimer *timer = timers[i].data();
186- if (timer && timer->isRunning()) {
187- timer->emitTimeout();
188- }
189- }
190-}
191-
192 } // namespace UbuntuGestures
193
194=== modified file 'libs/UbuntuGestures/Timer.h'
195--- libs/UbuntuGestures/Timer.h 2014-10-01 13:20:32 +0000
196+++ libs/UbuntuGestures/Timer.h 2015-04-15 20:21:35 +0000
197@@ -18,6 +18,7 @@
198 #define UBUNTUGESTURES_TIMER_H
199
200 #include "UbuntuGesturesGlobal.h"
201+#include "TimeSource.h"
202
203 #include <QObject>
204 #include <QPointer>
205@@ -66,17 +67,21 @@
206 {
207 Q_OBJECT
208 public:
209- FakeTimer(QObject *parent = nullptr);
210+ FakeTimer(const SharedTimeSource &timeSource, QObject *parent = nullptr);
211
212- virtual void emitTimeout() { Q_EMIT timeout(); }
213+ void update();
214+ qint64 nextTimeoutTime() const { return m_nextTimeoutTime; }
215
216 int interval() const override;
217 void setInterval(int msecs) override;
218+ void start() override;
219 bool isSingleShot() const override;
220 void setSingleShot(bool value) override;
221 private:
222 int m_interval;
223 bool m_singleShot;
224+ SharedTimeSource m_timeSource;
225+ qint64 m_nextTimeoutTime;
226 };
227
228 class UBUNTUGESTURES_EXPORT AbstractTimerFactory
229@@ -95,9 +100,16 @@
230 class UBUNTUGESTURES_EXPORT FakeTimerFactory : public AbstractTimerFactory
231 {
232 public:
233+ FakeTimerFactory();
234+ virtual ~FakeTimerFactory();
235+
236+ void updateTime(qint64 msecsSinceReference);
237+ QSharedPointer<TimeSource> timeSource() { return m_timeSource; }
238+
239 AbstractTimer *createTimer(QObject *parent = nullptr) override;
240- void makeRunningTimersTimeout();
241 QList<QPointer<FakeTimer>> timers;
242+private:
243+ QSharedPointer<FakeTimeSource> m_timeSource;
244 };
245
246 } // namespace UbuntuGestures
247
248=== modified file 'libs/UbuntuGestures/TouchRegistry.cpp'
249--- libs/UbuntuGestures/TouchRegistry.cpp 2014-10-01 13:20:32 +0000
250+++ libs/UbuntuGestures/TouchRegistry.cpp 2015-04-15 20:21:35 +0000
251@@ -1,5 +1,5 @@
252 /*
253- * Copyright (C) 2014 Canonical, Ltd.
254+ * Copyright (C) 2014-2015 Canonical, Ltd.
255 *
256 * This program is free software; you can redistribute it and/or modify
257 * it under the terms of the GNU General Public License as published by
258@@ -95,10 +95,6 @@
259 // for each point and there should not be many active points at any given moment.
260 // But having three nested for-loops does scare.
261
262- // TODO: Don't send it to the object that is already receiving the regular event
263- // because QQuickWindow is sending it to him (i.e., he's the touch owner from Qt's point of view)
264- // Problem is, we cannnot easily get this information.
265-
266 const QList<QTouchEvent::TouchPoint> &updatedTouchPoints = event->touchPoints();
267
268 // Maps an item to the touches in this event he should be informed about.
269@@ -118,7 +114,9 @@
270 for (int i = 0; i < touchInfo->candidates.count(); ++i) {
271 CandidateInfo &candidate = touchInfo->candidates[i];
272 Q_ASSERT(!candidate.item.isNull());
273- touchIdsForItems[candidate.item.data()].append(touchInfo->id);
274+ if (candidate.state != CandidateInfo::InterimOwner) {
275+ touchIdsForItems[candidate.item.data()].append(touchInfo->id);
276+ }
277 }
278 }
279
280@@ -260,7 +258,7 @@
281 // TODO: Check if candidate already exists
282
283 CandidateInfo candidateInfo;
284- candidateInfo.undecided = true;
285+ candidateInfo.state = CandidateInfo::Undecided;
286 candidateInfo.item = candidate;
287 candidateInfo.inactivityTimer = new CandidateInactivityTimer(id, candidate,
288 *m_timerFactory,
289@@ -301,8 +299,8 @@
290 for (int i = 0; i < touchInfo->candidates.count() && indexRemoved == -1; ++i) {
291 CandidateInfo &candidateInfo = touchInfo->candidates[i];
292 if (candidateInfo.item == candidate) {
293- Q_ASSERT(i > 0 || candidateInfo.undecided);
294- if (i == 0 && !candidateInfo.undecided) {
295+ Q_ASSERT(i > 0 || candidateInfo.state == CandidateInfo::Undecided);
296+ if (i == 0 && candidateInfo.state != CandidateInfo::Undecided) {
297 qCritical("TouchRegistry: touch owner is being removed.");
298 }
299 delete candidateInfo.inactivityTimer;
300@@ -340,7 +338,7 @@
301 for (int i = 0; i < touchInfo->candidates.count(); ++i) {
302 CandidateInfo &candidateInfo = touchInfo->candidates[i];
303 if (candidateInfo.item == candidate) {
304- candidateInfo.undecided = false;
305+ candidateInfo.state = CandidateInfo::Requested;
306 delete candidateInfo.inactivityTimer;
307 candidateInfo.inactivityTimer = nullptr;
308 candidateIndex = i;
309@@ -351,7 +349,7 @@
310 // add it as a candidate if not present yet
311 if (candidateIndex < 0) {
312 CandidateInfo candidateInfo;
313- candidateInfo.undecided = false;
314+ candidateInfo.state = CandidateInfo::InterimOwner;
315 candidateInfo.item = candidate;
316 candidateInfo.inactivityTimer = nullptr;
317 touchInfo->candidates.append(candidateInfo);
318@@ -407,8 +405,8 @@
319 for (int i = 0; i < touchInfo->candidates.count() && rejectedCandidateIndex == -1; ++i) {
320 CandidateInfo &candidateInfo = touchInfo->candidates[i];
321 if (candidateInfo.item == candidate) {
322- Q_ASSERT(i > 0 || candidateInfo.undecided);
323- if (i == 0 && !candidateInfo.undecided) {
324+ Q_ASSERT(i > 0 || candidateInfo.state == CandidateInfo::Undecided);
325+ if (i == 0 && candidateInfo.state != CandidateInfo::Undecided) {
326 qCritical() << "TouchRegistry: Can't reject item (" << (void*)candidate
327 << ") as it already owns touch" << id;
328 return;
329@@ -467,7 +465,7 @@
330
331 bool TouchRegistry::TouchInfo::isOwned() const
332 {
333- return !candidates.isEmpty() && !candidates.first().undecided;
334+ return !candidates.isEmpty() && candidates.first().state != CandidateInfo::Undecided;
335 }
336
337 bool TouchRegistry::TouchInfo::ended() const
338
339=== modified file 'libs/UbuntuGestures/TouchRegistry.h'
340--- libs/UbuntuGestures/TouchRegistry.h 2014-10-02 12:47:07 +0000
341+++ libs/UbuntuGestures/TouchRegistry.h 2015-04-15 20:21:35 +0000
342@@ -79,9 +79,11 @@
343 If an item wants ownership over touches as soon as he receives the TouchBegin for them, his step 1
344 would be instead:
345 TouchRegistry::instance()->requestTouchOwnership(touchId, this);
346- return true;
347- He would then be notified once ownership has been granted to him, from which point onwards he could
348- safely assume other TouchRegistry users wouldn't snatch this touch away from him.
349+ touchEvent->accept();
350+ He won't get any UnownedTouchEvent for that touch as he is already the interim owner (ie, QQuickWindow
351+ will keep sending touch updates to him already). Eventually he will be notified once ownership has
352+ been granted to him (from TouchRegistry perspective), from which point onwards he could safely assume
353+ other TouchRegistry users wouldn't snatch this touch away from him.
354
355 Items oblivious to TouchRegistry will lose their touch points without warning, just like in plain Qt.
356
357@@ -132,7 +134,19 @@
358 private:
359 class CandidateInfo {
360 public:
361- bool undecided;
362+ enum {
363+ // A candidate owner that doesn't yet know for sure whether he wants the touch point
364+ // (gesture recognition is stilll going on)
365+ Undecided = 0,
366+ // A candidate owner that wants the touch but hasn't been granted it yet,
367+ // most likely because there's an undecided candidate with higher priority
368+ Requested = 1,
369+ // An item that is the interim owner of the touch, receiving QTouchEvents of it
370+ // from QQuickWindow. Ie, it's the actual touch owner from Qt's point of view.
371+ // It wants to keep its touch ownership but hasn't been granted it by TouchRegistry
372+ // yet because of undecided candidates higher up.
373+ InterimOwner = 2
374+ } state;
375 // TODO: Prune candidates that become null and resolve ownership accordingly.
376 QPointer<QQuickItem> item;
377 QPointer<UbuntuGestures::CandidateInactivityTimer> inactivityTimer;
378
379=== modified file 'plugins/Ubuntu/Gestures/CMakeLists.txt'
380--- plugins/Ubuntu/Gestures/CMakeLists.txt 2014-10-17 11:01:53 +0000
381+++ plugins/Ubuntu/Gestures/CMakeLists.txt 2015-04-15 20:21:35 +0000
382@@ -4,10 +4,10 @@
383 set(UbuntuGesturesQml_SOURCES
384 plugin.cpp
385 AxisVelocityCalculator.cpp
386+ Damper.cpp
387 Direction.cpp
388 DirectionalDragArea.cpp
389 PressedOutsideNotifier.cpp
390- TimeSource.cpp
391 TouchDispatcher.cpp
392 TouchGate.cpp
393 )
394
395=== added file 'plugins/Ubuntu/Gestures/Damper.cpp'
396--- plugins/Ubuntu/Gestures/Damper.cpp 1970-01-01 00:00:00 +0000
397+++ plugins/Ubuntu/Gestures/Damper.cpp 2015-04-15 20:21:35 +0000
398@@ -0,0 +1,24 @@
399+/*
400+ * Copyright (C) 2015 Canonical, Ltd.
401+ *
402+ * This program is free software; you can redistribute it and/or modify
403+ * it under the terms of the GNU General Public License as published by
404+ * the Free Software Foundation; version 3.
405+ *
406+ * This program is distributed in the hope that it will be useful,
407+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
408+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
409+ * GNU General Public License for more details.
410+ *
411+ * You should have received a copy of the GNU General Public License
412+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
413+ */
414+
415+#include "Damper.h"
416+#include <QDebug>
417+
418+QDebug operator<<(QDebug dbg, const DampedPointF &p)
419+{
420+ dbg.nospace() << "(" << p.x() << ", " << p.y() << ")";
421+ return dbg.space();
422+}
423
424=== modified file 'plugins/Ubuntu/Gestures/Damper.h'
425--- plugins/Ubuntu/Gestures/Damper.h 2013-08-06 13:18:34 +0000
426+++ plugins/Ubuntu/Gestures/Damper.h 2015-04-15 20:21:35 +0000
427@@ -84,4 +84,6 @@
428 Damper<qreal> m_y;
429 };
430
431+QDebug operator<<(QDebug dbg, const DampedPointF &p);
432+
433 #endif // UBUNTU_GESTURES_DAMPER_H
434
435=== modified file 'plugins/Ubuntu/Gestures/DirectionalDragArea.cpp'
436--- plugins/Ubuntu/Gestures/DirectionalDragArea.cpp 2014-12-09 11:00:37 +0000
437+++ plugins/Ubuntu/Gestures/DirectionalDragArea.cpp 2015-04-15 20:21:35 +0000
438@@ -1,5 +1,5 @@
439 /*
440- * Copyright (C) 2013-2014 Canonical, Ltd.
441+ * Copyright (C) 2013-2015 Canonical, Ltd.
442 *
443 * This program is free software; you can redistribute it and/or modify
444 * it under the terms of the GNU General Public License as published by
445@@ -21,6 +21,7 @@
446
447 #include <QQuickWindow>
448 #include <QtCore/qmath.h>
449+#include <QScreen>
450 #include <QDebug>
451
452 #pragma GCC diagnostic push
453@@ -33,6 +34,8 @@
454 #include "TouchRegistry.h"
455 #include "UnownedTouchEvent.h"
456
457+#include "DirectionalDragArea_p.h"
458+
459 using namespace UbuntuGestures;
460
461 #if DIRECTIONALDRAGAREA_DEBUG
462@@ -40,11 +43,11 @@
463 #include "DebugHelpers.h"
464
465 namespace {
466-const char *statusToString(DirectionalDragArea::Status status)
467+const char *statusToString(DirectionalDragAreaPrivate::Status status)
468 {
469- if (status == DirectionalDragArea::WaitingForTouch) {
470+ if (status == DirectionalDragAreaPrivate::WaitingForTouch) {
471 return "WaitingForTouch";
472- } else if (status == DirectionalDragArea::Undecided) {
473+ } else if (status == DirectionalDragAreaPrivate::Undecided) {
474 return "Undecided";
475 } else {
476 return "Recognized";
477@@ -56,205 +59,168 @@
478 #define ddaDebug(params) ((void)0)
479 #endif // DIRECTIONALDRAGAREA_DEBUG
480
481-
482 DirectionalDragArea::DirectionalDragArea(QQuickItem *parent)
483 : QQuickItem(parent)
484- , m_status(WaitingForTouch)
485- , m_sceneDistance(0)
486- , m_touchId(-1)
487- , m_direction(Direction::Rightwards)
488- , m_wideningAngle(0)
489- , m_wideningFactor(0)
490- , m_distanceThreshold(0)
491- , m_distanceThresholdSquared(0.)
492- , m_minSpeed(0)
493- , m_maxSilenceTime(200)
494- , m_silenceTime(0)
495- , m_compositionTime(60)
496- , m_numSamplesOnLastSpeedCheck(0)
497- , m_recognitionTimer(0)
498- , m_velocityCalculator(0)
499- , m_timeSource(new RealTimeSource)
500- , m_activeTouches(m_timeSource)
501+ , d(new DirectionalDragAreaPrivate(this))
502 {
503- setRecognitionTimer(new Timer(this));
504- m_recognitionTimer->setInterval(60);
505- m_recognitionTimer->setSingleShot(false);
506-
507- m_velocityCalculator = new AxisVelocityCalculator(this);
508-
509- connect(this, &QQuickItem::enabledChanged, this, &DirectionalDragArea::giveUpIfDisabledOrInvisible);
510- connect(this, &QQuickItem::visibleChanged, this, &DirectionalDragArea::giveUpIfDisabledOrInvisible);
511+ d->setRecognitionTimer(new Timer(this));
512+ d->recognitionTimer->setInterval(d->maxTime);
513+ d->recognitionTimer->setSingleShot(true);
514+
515+ connect(this, &QQuickItem::enabledChanged, d, &DirectionalDragAreaPrivate::giveUpIfDisabledOrInvisible);
516+ connect(this, &QQuickItem::visibleChanged, d, &DirectionalDragAreaPrivate::giveUpIfDisabledOrInvisible);
517 }
518
519 Direction::Type DirectionalDragArea::direction() const
520 {
521- return m_direction;
522+ return d->direction;
523 }
524
525 void DirectionalDragArea::setDirection(Direction::Type direction)
526 {
527- if (direction != m_direction) {
528- m_direction = direction;
529- Q_EMIT directionChanged(m_direction);
530- }
531-}
532-
533-void DirectionalDragArea::setMaxDeviation(qreal value)
534-{
535- if (m_dampedScenePos.maxDelta() != value) {
536- m_dampedScenePos.setMaxDelta(value);
537- Q_EMIT maxDeviationChanged(value);
538- }
539-}
540-
541-qreal DirectionalDragArea::wideningAngle() const
542-{
543- return m_wideningAngle;
544-}
545-
546-void DirectionalDragArea::setWideningAngle(qreal angle)
547-{
548- if (angle == m_wideningAngle)
549- return;
550-
551- m_wideningAngle = angle;
552-
553- // wideningFactor = pow(cosine(angle), 2)
554- {
555- qreal angleRadians = angle * M_PI / 180.0;
556- m_wideningFactor = qCos(angleRadians);
557- m_wideningFactor = m_wideningFactor * m_wideningFactor;
558- }
559-
560- Q_EMIT wideningAngleChanged(angle);
561-}
562-
563-void DirectionalDragArea::setDistanceThreshold(qreal value)
564-{
565- if (m_distanceThreshold != value) {
566- m_distanceThreshold = value;
567- m_distanceThresholdSquared = m_distanceThreshold * m_distanceThreshold;
568- Q_EMIT distanceThresholdChanged(value);
569- }
570-}
571-
572-void DirectionalDragArea::setMinSpeed(qreal value)
573-{
574- if (m_minSpeed != value) {
575- m_minSpeed = value;
576- Q_EMIT minSpeedChanged(value);
577- }
578-}
579-
580-void DirectionalDragArea::setMaxSilenceTime(int value)
581-{
582- if (m_maxSilenceTime != value) {
583- m_maxSilenceTime = value;
584- Q_EMIT maxSilenceTimeChanged(value);
585- }
586-}
587-
588-void DirectionalDragArea::setCompositionTime(int value)
589-{
590- if (m_compositionTime != value) {
591- m_compositionTime = value;
592- Q_EMIT compositionTimeChanged(value);
593- }
594-}
595-
596-void DirectionalDragArea::setRecognitionTimer(UbuntuGestures::AbstractTimer *timer)
597+ if (direction != d->direction) {
598+ d->direction = direction;
599+ Q_EMIT directionChanged(d->direction);
600+ }
601+}
602+
603+void DirectionalDragAreaPrivate::setDistanceThreshold(qreal value)
604+{
605+ if (distanceThreshold != value) {
606+ distanceThreshold = value;
607+ distanceThresholdSquared = distanceThreshold * distanceThreshold;
608+ }
609+}
610+
611+void DirectionalDragAreaPrivate::setMaxTime(int value)
612+{
613+ if (maxTime != value) {
614+ maxTime = value;
615+ recognitionTimer->setInterval(maxTime);
616+ }
617+}
618+
619+void DirectionalDragAreaPrivate::setRecognitionTimer(UbuntuGestures::AbstractTimer *timer)
620 {
621 int interval = 0;
622 bool timerWasRunning = false;
623 bool wasSingleShot = false;
624
625 // can be null when called from the constructor
626- if (m_recognitionTimer) {
627- interval = m_recognitionTimer->interval();
628- timerWasRunning = m_recognitionTimer->isRunning();
629- if (m_recognitionTimer->parent() == this) {
630- delete m_recognitionTimer;
631+ if (recognitionTimer) {
632+ interval = recognitionTimer->interval();
633+ timerWasRunning = recognitionTimer->isRunning();
634+ if (recognitionTimer->parent() == this) {
635+ delete recognitionTimer;
636 }
637 }
638
639- m_recognitionTimer = timer;
640+ recognitionTimer = timer;
641 timer->setInterval(interval);
642 timer->setSingleShot(wasSingleShot);
643 connect(timer, &UbuntuGestures::AbstractTimer::timeout,
644- this, &DirectionalDragArea::checkSpeed);
645+ this, &DirectionalDragAreaPrivate::rejectGesture);
646 if (timerWasRunning) {
647- m_recognitionTimer->start();
648+ recognitionTimer->start();
649 }
650 }
651
652-void DirectionalDragArea::setTimeSource(const SharedTimeSource &timeSource)
653+void DirectionalDragAreaPrivate::setTimeSource(const SharedTimeSource &timeSource)
654 {
655- m_timeSource = timeSource;
656- m_velocityCalculator->setTimeSource(timeSource);
657- m_activeTouches.m_timeSource = timeSource;
658+ this->timeSource = timeSource;
659+ activeTouches.m_timeSource = timeSource;
660 }
661
662 qreal DirectionalDragArea::distance() const
663 {
664- if (Direction::isHorizontal(m_direction)) {
665- return m_previousPos.x() - m_startPos.x();
666+ if (Direction::isHorizontal(d->direction)) {
667+ return d->publicPos.x() - d->startPos.x();
668 } else {
669- return m_previousPos.y() - m_startPos.y();
670+ return d->publicPos.y() - d->startPos.y();
671 }
672 }
673
674-void DirectionalDragArea::updateSceneDistance()
675+void DirectionalDragAreaPrivate::updateSceneDistance()
676 {
677- QPointF totalMovement = m_previousScenePos - m_startScenePos;
678- m_sceneDistance = projectOntoDirectionVector(totalMovement);
679+ QPointF totalMovement = publicScenePos - startScenePos;
680+ sceneDistance = projectOntoDirectionVector(totalMovement);
681 }
682
683 qreal DirectionalDragArea::sceneDistance() const
684 {
685- return m_sceneDistance;
686+ return d->sceneDistance;
687 }
688
689 qreal DirectionalDragArea::touchX() const
690 {
691- return m_previousPos.x();
692+ return d->publicPos.x();
693 }
694
695 qreal DirectionalDragArea::touchY() const
696 {
697- return m_previousPos.y();
698+ return d->publicPos.y();
699 }
700
701 qreal DirectionalDragArea::touchSceneX() const
702 {
703- return m_previousScenePos.x();
704+ return d->publicScenePos.x();
705 }
706
707 qreal DirectionalDragArea::touchSceneY() const
708 {
709- return m_previousScenePos.y();
710+ return d->publicScenePos.y();
711+}
712+
713+bool DirectionalDragArea::dragging() const
714+{
715+ return d->status == DirectionalDragAreaPrivate::Recognized;
716+}
717+
718+bool DirectionalDragArea::pressed() const
719+{
720+ return d->status != DirectionalDragAreaPrivate::WaitingForTouch;
721+}
722+
723+bool DirectionalDragArea::immediateRecognition() const
724+{
725+ return d->immediateRecognition;
726+}
727+
728+void DirectionalDragArea::setImmediateRecognition(bool enabled)
729+{
730+ if (d->immediateRecognition != enabled) {
731+ d->immediateRecognition = enabled;
732+ Q_EMIT immediateRecognitionChanged(enabled);
733+ }
734+}
735+
736+void DirectionalDragArea::removeTimeConstraints()
737+{
738+ d->setMaxTime(60 * 60 * 1000);
739+ d->compositionTime = 0;
740+ ddaDebug("removed time constraints");
741 }
742
743 bool DirectionalDragArea::event(QEvent *event)
744 {
745 if (event->type() == TouchOwnershipEvent::touchOwnershipEventType()) {
746- touchOwnershipEvent(static_cast<TouchOwnershipEvent *>(event));
747+ d->touchOwnershipEvent(static_cast<TouchOwnershipEvent *>(event));
748 return true;
749 } else if (event->type() == UnownedTouchEvent::unownedTouchEventType()) {
750- unownedTouchEvent(static_cast<UnownedTouchEvent *>(event));
751+ d->unownedTouchEvent(static_cast<UnownedTouchEvent *>(event));
752 return true;
753 } else {
754 return QQuickItem::event(event);
755 }
756 }
757
758-void DirectionalDragArea::touchOwnershipEvent(TouchOwnershipEvent *event)
759+void DirectionalDragAreaPrivate::touchOwnershipEvent(TouchOwnershipEvent *event)
760 {
761 if (event->gained()) {
762 QVector<int> ids;
763 ids.append(event->touchId());
764 ddaDebug("grabbing touch");
765- grabTouchPoints(ids);
766+ q->grabTouchPoints(ids);
767
768 // Work around for Qt bug. If we grab a touch that is being used for mouse pointer
769 // emulation it will cause the emulation logic to go nuts.
770@@ -262,35 +228,35 @@
771 //
772 // The fix for this bug has landed in Qt 5.4 (https://codereview.qt-project.org/96887)
773 // TODO: Remove this workaround once we start using Qt 5.4
774- if (window()) {
775- QQuickWindowPrivate *windowPrivate = QQuickWindowPrivate::get(window());
776- if (windowPrivate->touchMouseId == event->touchId() && window()->mouseGrabberItem()) {
777+ if (q->window()) {
778+ QQuickWindowPrivate *windowPrivate = QQuickWindowPrivate::get(q->window());
779+ if (windowPrivate->touchMouseId == event->touchId() && q->window()->mouseGrabberItem()) {
780 ddaDebug("removing mouse grabber");
781- window()->mouseGrabberItem()->ungrabMouse();
782+ q->window()->mouseGrabberItem()->ungrabMouse();
783 }
784 }
785 } else {
786 // We still wanna know when it ends for keeping the composition time window up-to-date
787- TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
788+ TouchRegistry::instance()->addTouchWatcher(touchId, q);
789
790 setStatus(WaitingForTouch);
791 }
792 }
793
794-void DirectionalDragArea::unownedTouchEvent(UnownedTouchEvent *unownedTouchEvent)
795+void DirectionalDragAreaPrivate::unownedTouchEvent(UnownedTouchEvent *unownedTouchEvent)
796 {
797 QTouchEvent *event = unownedTouchEvent->touchEvent();
798
799 Q_ASSERT(!event->touchPointStates().testFlag(Qt::TouchPointPressed));
800
801- ddaDebug("Unowned " << m_timeSource->msecsSinceReference() << " " << qPrintable(touchEventToString(event)));
802+ ddaDebug("Unowned " << timeSource->msecsSinceReference() << " " << qPrintable(touchEventToString(event)));
803
804- switch (m_status) {
805+ switch (status) {
806 case WaitingForTouch:
807 // do nothing
808 break;
809 case Undecided:
810- Q_ASSERT(isEnabled() && isVisible());
811+ Q_ASSERT(q->isEnabled() && q->isVisible());
812 unownedTouchEvent_undecided(unownedTouchEvent);
813 break;
814 default: // Recognized:
815@@ -298,18 +264,18 @@
816 break;
817 }
818
819- m_activeTouches.update(event);
820+ activeTouches.update(event);
821 }
822
823-void DirectionalDragArea::unownedTouchEvent_undecided(UnownedTouchEvent *unownedTouchEvent)
824+void DirectionalDragAreaPrivate::unownedTouchEvent_undecided(UnownedTouchEvent *unownedTouchEvent)
825 {
826 const QTouchEvent::TouchPoint *touchPoint = fetchTargetTouchPoint(unownedTouchEvent->touchEvent());
827 if (!touchPoint) {
828- qCritical() << "DirectionalDragArea[status=Undecided]: touch " << m_touchId
829+ qCritical() << "DirectionalDragArea[status=Undecided]: touch " << touchId
830 << "missing from UnownedTouchEvent without first reaching state Qt::TouchPointReleased. "
831 "Considering it as released.";
832
833- TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
834+ TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
835 setStatus(WaitingForTouch);
836 return;
837 }
838@@ -319,48 +285,42 @@
839 if (touchPoint->state() == Qt::TouchPointReleased) {
840 // touch has ended before recognition concluded
841 ddaDebug("Touch has ended before recognition concluded");
842- TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
843- emitSignalIfTapped();
844- setStatus(WaitingForTouch);
845- return;
846- }
847-
848- m_previousDampedScenePos.setX(m_dampedScenePos.x());
849- m_previousDampedScenePos.setY(m_dampedScenePos.y());
850- m_dampedScenePos.update(touchScenePos);
851- updateVelocityCalculator(touchScenePos);
852-
853- if (!pointInsideAllowedArea()) {
854- ddaDebug("Rejecting gesture because touch point is outside allowed area.");
855- TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
856- // We still wanna know when it ends for keeping the composition time window up-to-date
857- TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
858- setStatus(WaitingForTouch);
859- return;
860- }
861+ TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
862+ setStatus(WaitingForTouch);
863+ return;
864+ }
865+
866+ previousDampedScenePos.setX(dampedScenePos.x());
867+ previousDampedScenePos.setY(dampedScenePos.y());
868+ dampedScenePos.update(touchScenePos);
869
870 if (!movingInRightDirection()) {
871 ddaDebug("Rejecting gesture because touch point is moving in the wrong direction.");
872- TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
873+ TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
874 // We still wanna know when it ends for keeping the composition time window up-to-date
875- TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
876+ TouchRegistry::instance()->addTouchWatcher(touchId, q);
877 setStatus(WaitingForTouch);
878 return;
879 }
880
881- setPreviousPos(touchPoint->pos());
882- setPreviousScenePos(touchScenePos);
883-
884 if (isWithinTouchCompositionWindow()) {
885 // There's still time for some new touch to appear and ruin our party as it would be combined
886- // with our m_touchId one and therefore deny the possibility of a single-finger gesture.
887+ // with our touchId one and therefore deny the possibility of a single-finger gesture.
888 ddaDebug("Sill within composition window. Let's wait more.");
889 return;
890 }
891
892- if (movedFarEnough(touchScenePos)) {
893- TouchRegistry::instance()->requestTouchOwnership(m_touchId, this);
894+ if (movedFarEnoughAlongGestureAxis()) {
895+ TouchRegistry::instance()->requestTouchOwnership(touchId, q);
896 setStatus(Recognized);
897+ setPublicPos(touchPoint->pos());
898+ setPublicScenePos(touchScenePos);
899+ } else if (isPastMaxDistance()) {
900+ ddaDebug("Rejecting gesture because it went farther than maxDistance without getting recognized.");
901+ TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
902+ // We still wanna know when it ends for keeping the composition time window up-to-date
903+ TouchRegistry::instance()->addTouchWatcher(touchId, q);
904+ setStatus(WaitingForTouch);
905 } else {
906 ddaDebug("Didn't move far enough yet. Let's wait more.");
907 }
908@@ -371,29 +331,29 @@
909 // TODO: Consider when more than one touch starts in the same event (although it's not possible
910 // with Mir's android-input). Have to track them all. Consider it a plus/bonus.
911
912- ddaDebug(m_timeSource->msecsSinceReference() << " " << qPrintable(touchEventToString(event)));
913+ ddaDebug(d->timeSource->msecsSinceReference() << " " << qPrintable(touchEventToString(event)));
914
915 if (!isEnabled() || !isVisible()) {
916 QQuickItem::touchEvent(event);
917 return;
918 }
919
920- switch (m_status) {
921- case WaitingForTouch:
922- touchEvent_absent(event);
923+ switch (d->status) {
924+ case DirectionalDragAreaPrivate::WaitingForTouch:
925+ d->touchEvent_absent(event);
926 break;
927- case Undecided:
928- touchEvent_undecided(event);
929+ case DirectionalDragAreaPrivate::Undecided:
930+ d->touchEvent_undecided(event);
931 break;
932 default: // Recognized:
933- touchEvent_recognized(event);
934+ d->touchEvent_recognized(event);
935 break;
936 }
937
938- m_activeTouches.update(event);
939+ d->activeTouches.update(event);
940 }
941
942-void DirectionalDragArea::touchEvent_absent(QTouchEvent *event)
943+void DirectionalDragAreaPrivate::touchEvent_absent(QTouchEvent *event)
944 {
945 // TODO: accept/reject is for the whole event, not per touch id. See how that affects us.
946
947@@ -423,36 +383,40 @@
948 allGood = false;
949 } else {
950 // that's our candidate
951- m_touchId = touchPoint.id();
952 newTouchPoint = &touchPoint;
953 }
954 }
955 }
956
957 if (allGood) {
958+ allGood = sanityCheckRecognitionProperties();
959+ if (!allGood) {
960+ qWarning("DirectionalDragArea: recognition properties are wrongly set. Gesture recognition"
961+ " is impossible");
962+ }
963+ }
964+
965+ if (allGood) {
966 Q_ASSERT(newTouchPoint);
967
968- m_startPos = newTouchPoint->pos();
969- m_startScenePos = newTouchPoint->scenePos();
970- m_touchId = newTouchPoint->id();
971- m_dampedScenePos.reset(m_startScenePos);
972- m_velocityCalculator->setTrackedPosition(0.);
973- m_velocityCalculator->reset();
974- m_numSamplesOnLastSpeedCheck = 0;
975- m_silenceTime = 0;
976- setPreviousPos(m_startPos);
977- setPreviousScenePos(m_startScenePos);
978+ startPos = newTouchPoint->pos();
979+ startScenePos = newTouchPoint->scenePos();
980+ touchId = newTouchPoint->id();
981+ dampedScenePos.reset(startScenePos);
982+ setPublicPos(startPos);
983+
984+ setPublicScenePos(startScenePos);
985 updateSceneDirectionVector();
986
987 if (recognitionIsDisabled()) {
988 // Behave like a dumb TouchArea
989 ddaDebug("Gesture recognition is disabled. Requesting touch ownership immediately.");
990- TouchRegistry::instance()->requestTouchOwnership(m_touchId, this);
991+ TouchRegistry::instance()->requestTouchOwnership(touchId, q);
992 setStatus(Recognized);
993 event->accept();
994 } else {
995 // just monitor the touch points for now.
996- TouchRegistry::instance()->addCandidateOwnerForTouch(m_touchId, this);
997+ TouchRegistry::instance()->addCandidateOwnerForTouch(touchId, q);
998
999 setStatus(Undecided);
1000 // Let the item below have it. We will monitor it and grab it later if a gesture
1001@@ -465,12 +429,11 @@
1002 }
1003 }
1004
1005-void DirectionalDragArea::touchEvent_undecided(QTouchEvent *event)
1006+void DirectionalDragAreaPrivate::touchEvent_undecided(QTouchEvent *event)
1007 {
1008- Q_ASSERT(event->type() == QEvent::TouchBegin);
1009 Q_ASSERT(fetchTargetTouchPoint(event) == nullptr);
1010
1011- // We're not interested in new touch points. We already have our candidate (m_touchId).
1012+ // We're not interested in new touch points. We already have our candidate (touchId).
1013 // But we do want to know when those new touches end for keeping the composition time
1014 // window up-to-date
1015 event->ignore();
1016@@ -480,63 +443,60 @@
1017 // multi-finger drags are not accepted
1018 ddaDebug("Multi-finger drags are not accepted");
1019
1020- TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
1021+ TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
1022 // We still wanna know when it ends for keeping the composition time window up-to-date
1023- TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
1024+ TouchRegistry::instance()->addTouchWatcher(touchId, q);
1025
1026 setStatus(WaitingForTouch);
1027 }
1028 }
1029
1030-void DirectionalDragArea::touchEvent_recognized(QTouchEvent *event)
1031+void DirectionalDragAreaPrivate::touchEvent_recognized(QTouchEvent *event)
1032 {
1033 const QTouchEvent::TouchPoint *touchPoint = fetchTargetTouchPoint(event);
1034
1035 if (!touchPoint) {
1036- qCritical() << "DirectionalDragArea[status=Recognized]: touch " << m_touchId
1037+ qCritical() << "DirectionalDragArea[status=Recognized]: touch " << touchId
1038 << "missing from QTouchEvent without first reaching state Qt::TouchPointReleased. "
1039 "Considering it as released.";
1040 setStatus(WaitingForTouch);
1041 } else {
1042- setPreviousPos(touchPoint->pos());
1043- setPreviousScenePos(touchPoint->scenePos());
1044+ setPublicPos(touchPoint->pos());
1045+ setPublicScenePos(touchPoint->scenePos());
1046
1047 if (touchPoint->state() == Qt::TouchPointReleased) {
1048- emitSignalIfTapped();
1049 setStatus(WaitingForTouch);
1050 }
1051 }
1052 }
1053
1054-void DirectionalDragArea::watchPressedTouchPoints(const QList<QTouchEvent::TouchPoint> &touchPoints)
1055+void DirectionalDragAreaPrivate::watchPressedTouchPoints(const QList<QTouchEvent::TouchPoint> &touchPoints)
1056 {
1057 for (int i = 0; i < touchPoints.count(); ++i) {
1058 const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i);
1059 if (touchPoint.state() == Qt::TouchPointPressed) {
1060- TouchRegistry::instance()->addTouchWatcher(touchPoint.id(), this);
1061+ TouchRegistry::instance()->addTouchWatcher(touchPoint.id(), q);
1062 }
1063 }
1064 }
1065
1066-bool DirectionalDragArea::recognitionIsDisabled() const
1067-{
1068- return distanceThreshold() <= 0 && compositionTime() <= 0;
1069-}
1070-
1071-void DirectionalDragArea::emitSignalIfTapped()
1072-{
1073- qint64 touchDuration = m_timeSource->msecsSinceReference() - m_activeTouches.touchStartTime(m_touchId);
1074- if (touchDuration <= maxTapDuration()) {
1075- Q_EMIT tapped();
1076- }
1077-}
1078-
1079-const QTouchEvent::TouchPoint *DirectionalDragArea::fetchTargetTouchPoint(QTouchEvent *event)
1080+bool DirectionalDragAreaPrivate::recognitionIsDisabled() const
1081+{
1082+ return immediateRecognition || (distanceThreshold <= 0 && compositionTime <= 0);
1083+}
1084+
1085+bool DirectionalDragAreaPrivate::sanityCheckRecognitionProperties()
1086+{
1087+ return recognitionIsDisabled()
1088+ || (distanceThreshold < maxDistance && compositionTime < maxTime);
1089+}
1090+
1091+const QTouchEvent::TouchPoint *DirectionalDragAreaPrivate::fetchTargetTouchPoint(QTouchEvent *event)
1092 {
1093 const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
1094 const QTouchEvent::TouchPoint *touchPoint = 0;
1095 for (int i = 0; i < touchPoints.size(); ++i) {
1096- if (touchPoints.at(i).id() == m_touchId) {
1097+ if (touchPoints.at(i).id() == touchId) {
1098 touchPoint = &touchPoints.at(i);
1099 break;
1100 }
1101@@ -544,39 +504,13 @@
1102 return touchPoint;
1103 }
1104
1105-bool DirectionalDragArea::pointInsideAllowedArea() const
1106-{
1107- // NB: Using squared values to avoid computing the square root to find
1108- // the length totalMovement
1109-
1110- QPointF totalMovement(m_dampedScenePos.x() - m_startScenePos.x(),
1111- m_dampedScenePos.y() - m_startScenePos.y());
1112-
1113- qreal squaredTotalMovSize = totalMovement.x() * totalMovement.x() +
1114- totalMovement.y() * totalMovement.y();
1115-
1116- if (squaredTotalMovSize == 0.) {
1117- // didn't move
1118- return true;
1119- }
1120-
1121- qreal projectedMovement = projectOntoDirectionVector(totalMovement);
1122-
1123-
1124- qreal cosineAngleSquared = (projectedMovement * projectedMovement) / squaredTotalMovSize;
1125-
1126- // Same as:
1127- // angle_between_movement_vector_and_gesture_direction_vector <= widening_angle
1128- return cosineAngleSquared >= m_wideningFactor;
1129-}
1130-
1131-bool DirectionalDragArea::movingInRightDirection() const
1132-{
1133- if (m_direction == Direction::Horizontal) {
1134+bool DirectionalDragAreaPrivate::movingInRightDirection() const
1135+{
1136+ if (direction == Direction::Horizontal) {
1137 return true;
1138 } else {
1139- QPointF movementVector(m_dampedScenePos.x() - m_previousDampedScenePos.x(),
1140- m_dampedScenePos.y() - m_previousDampedScenePos.y());
1141+ QPointF movementVector(dampedScenePos.x() - previousDampedScenePos.x(),
1142+ dampedScenePos.y() - previousDampedScenePos.y());
1143
1144 qreal scalarProjection = projectOntoDirectionVector(movementVector);
1145
1146@@ -584,96 +518,95 @@
1147 }
1148 }
1149
1150-bool DirectionalDragArea::movedFarEnough(const QPointF &point) const
1151+bool DirectionalDragAreaPrivate::movedFarEnoughAlongGestureAxis() const
1152 {
1153- if (m_distanceThreshold <= 0.) {
1154+ if (distanceThreshold <= 0.) {
1155 // distance threshold check is disabled
1156 return true;
1157 } else {
1158- QPointF totalMovement(point.x() - m_startScenePos.x(),
1159- point.y() - m_startScenePos.y());
1160-
1161- qreal squaredTotalMovSize = totalMovement.x() * totalMovement.x() +
1162- totalMovement.y() * totalMovement.y();
1163-
1164- return squaredTotalMovSize > m_distanceThresholdSquared;
1165- }
1166-}
1167-
1168-void DirectionalDragArea::checkSpeed()
1169-{
1170- Q_ASSERT(m_status == Undecided);
1171-
1172- if (m_velocityCalculator->numSamples() >= AxisVelocityCalculator::MIN_SAMPLES_NEEDED) {
1173- qreal speed = qFabs(m_velocityCalculator->calculate());
1174- qreal minSpeedMsecs = m_minSpeed / 1000.0;
1175-
1176- if (speed < minSpeedMsecs) {
1177- ddaDebug("Rejecting gesture because it's below minimum speed.");
1178- TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
1179- TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
1180- setStatus(WaitingForTouch);
1181- }
1182- }
1183-
1184- if (m_velocityCalculator->numSamples() == m_numSamplesOnLastSpeedCheck) {
1185- m_silenceTime += m_recognitionTimer->interval();
1186-
1187- if (m_silenceTime > m_maxSilenceTime) {
1188- ddaDebug("Rejecting gesture because its silence time has been exceeded.");
1189- TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
1190- TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
1191- setStatus(WaitingForTouch);
1192- }
1193- } else {
1194- m_silenceTime = 0;
1195- }
1196- m_numSamplesOnLastSpeedCheck = m_velocityCalculator->numSamples();
1197-}
1198-
1199-void DirectionalDragArea::giveUpIfDisabledOrInvisible()
1200-{
1201- if (!isEnabled() || !isVisible()) {
1202- if (m_status == Undecided) {
1203- TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
1204+ QPointF totalMovement(dampedScenePos.x() - startScenePos.x(),
1205+ dampedScenePos.y() - startScenePos.y());
1206+
1207+ qreal scalarProjection = projectOntoDirectionVector(totalMovement);
1208+
1209+ ddaDebug(" movedFarEnoughAlongGestureAxis: scalarProjection=" << scalarProjection
1210+ << ", distanceThreshold=" << distanceThreshold);
1211+
1212+ if (direction == Direction::Horizontal) {
1213+ return qAbs(scalarProjection) > distanceThreshold;
1214+ } else {
1215+ return scalarProjection > distanceThreshold;
1216+ }
1217+ }
1218+}
1219+
1220+bool DirectionalDragAreaPrivate::isPastMaxDistance() const
1221+{
1222+ QPointF totalMovement(dampedScenePos.x() - startScenePos.x(),
1223+ dampedScenePos.y() - startScenePos.y());
1224+
1225+ qreal squaredDistance = totalMovement.x()*totalMovement.x() + totalMovement.y()*totalMovement.y();
1226+ return squaredDistance > maxDistance*maxDistance;
1227+}
1228+
1229+void DirectionalDragAreaPrivate::giveUpIfDisabledOrInvisible()
1230+{
1231+ if (!q->isEnabled() || !q->isVisible()) {
1232+ if (status == Undecided) {
1233+ TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
1234 // We still wanna know when it ends for keeping the composition time window up-to-date
1235- TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
1236+ TouchRegistry::instance()->addTouchWatcher(touchId, q);
1237 }
1238
1239- if (m_status != WaitingForTouch) {
1240+ if (status != WaitingForTouch) {
1241 ddaDebug("Resetting status because got disabled or made invisible");
1242 setStatus(WaitingForTouch);
1243 }
1244 }
1245 }
1246
1247-void DirectionalDragArea::setStatus(DirectionalDragArea::Status newStatus)
1248-{
1249- if (newStatus == m_status)
1250+void DirectionalDragAreaPrivate::rejectGesture()
1251+{
1252+ if (status == Undecided) {
1253+ ddaDebug("Rejecting gesture because it's taking too long to drag beyond the threshold.");
1254+
1255+ TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
1256+ // We still wanna know when it ends for keeping the composition time window up-to-date
1257+ TouchRegistry::instance()->addTouchWatcher(touchId, q);
1258+
1259+ setStatus(WaitingForTouch);
1260+ }
1261+}
1262+
1263+void DirectionalDragAreaPrivate::setStatus(Status newStatus)
1264+{
1265+ if (newStatus == status)
1266 return;
1267
1268- DirectionalDragArea::Status oldStatus = m_status;
1269+ Status oldStatus = status;
1270
1271 if (oldStatus == Undecided) {
1272- m_recognitionTimer->stop();
1273+ recognitionTimer->stop();
1274 }
1275
1276- m_status = newStatus;
1277- Q_EMIT statusChanged(m_status);
1278+ status = newStatus;
1279+ Q_EMIT statusChanged(status);
1280
1281 ddaDebug(statusToString(oldStatus) << " -> " << statusToString(newStatus));
1282
1283 switch (newStatus) {
1284 case WaitingForTouch:
1285- Q_EMIT draggingChanged(false);
1286+ if (oldStatus == Recognized) {
1287+ Q_EMIT q->draggingChanged(false);
1288+ }
1289+ Q_EMIT q->pressedChanged(false);
1290 break;
1291 case Undecided:
1292- m_recognitionTimer->start();
1293- Q_EMIT draggingChanged(true);
1294+ recognitionTimer->start();
1295+ Q_EMIT q->pressedChanged(true);
1296 break;
1297 case Recognized:
1298- if (oldStatus == WaitingForTouch)
1299- Q_EMIT draggingChanged(true);
1300+ Q_EMIT q->draggingChanged(true);
1301 break;
1302 default:
1303 // no-op
1304@@ -681,77 +614,126 @@
1305 }
1306 }
1307
1308-void DirectionalDragArea::setPreviousPos(const QPointF &point)
1309+void DirectionalDragAreaPrivate::setPublicPos(const QPointF &point)
1310 {
1311- bool xChanged = m_previousPos.x() != point.x();
1312- bool yChanged = m_previousPos.y() != point.y();
1313-
1314- m_previousPos = point;
1315+ bool xChanged = publicPos.x() != point.x();
1316+ bool yChanged = publicPos.y() != point.y();
1317+
1318+ // Public position should not get updated while the gesture is still being recognized
1319+ // (ie, Undecided status).
1320+ Q_ASSERT(status == WaitingForTouch || status == Recognized);
1321+
1322+ if (status == Recognized && !recognitionIsDisabled()) {
1323+ // When the gesture finally gets recognized, the finger will likely be
1324+ // reasonably far from the edge. If we made the contentX immediately
1325+ // follow the finger position it would be visually unpleasant as it
1326+ // would appear right next to the user's finger out of nowhere (ie,
1327+ // it would jump). Instead, we make contentX go towards the user's
1328+ // finger in several steps. ie., in an animated way.
1329+ QPointF delta = point - publicPos;
1330+ // the trick is not to go all the way (1.0) as it would cause a sudden jump
1331+ publicPos.rx() += 0.4 * delta.x();
1332+ publicPos.ry() += 0.4 * delta.y();
1333+ } else {
1334+ // no smoothing when initializing or if gesture recognition was immediate as there will
1335+ // be no jump.
1336+ publicPos = point;
1337+ }
1338
1339 if (xChanged) {
1340- Q_EMIT touchXChanged(point.x());
1341- if (Direction::isHorizontal(m_direction))
1342- Q_EMIT distanceChanged(distance());
1343+ Q_EMIT q->touchXChanged(publicPos.x());
1344+ if (Direction::isHorizontal(direction))
1345+ Q_EMIT q->distanceChanged(q->distance());
1346 }
1347
1348 if (yChanged) {
1349- Q_EMIT touchYChanged(point.y());
1350- if (Direction::isVertical(m_direction))
1351- Q_EMIT distanceChanged(distance());
1352+ Q_EMIT q->touchYChanged(publicPos.y());
1353+ if (Direction::isVertical(direction))
1354+ Q_EMIT q->distanceChanged(q->distance());
1355 }
1356 }
1357
1358-void DirectionalDragArea::setPreviousScenePos(const QPointF &point)
1359+void DirectionalDragAreaPrivate::setPublicScenePos(const QPointF &point)
1360 {
1361- bool xChanged = m_previousScenePos.x() != point.x();
1362- bool yChanged = m_previousScenePos.y() != point.y();
1363+ bool xChanged = publicScenePos.x() != point.x();
1364+ bool yChanged = publicScenePos.y() != point.y();
1365
1366 if (!xChanged && !yChanged)
1367 return;
1368
1369- qreal oldSceneDistance = sceneDistance();
1370- m_previousScenePos = point;
1371+ // Public position should not get updated while the gesture is still being recognized
1372+ // (ie, Undecided status).
1373+ Q_ASSERT(status == WaitingForTouch || status == Recognized);
1374+
1375+ qreal oldSceneDistance = sceneDistance;
1376+
1377+ if (status == Recognized && !recognitionIsDisabled()) {
1378+ // When the gesture finally gets recognized, the finger will likely be
1379+ // reasonably far from the edge. If we made the contentX immediately
1380+ // follow the finger position it would be visually unpleasant as it
1381+ // would appear right next to the user's finger out of nowhere (ie,
1382+ // it would jump). Instead, we make contentX go towards the user's
1383+ // finger in several steps. ie., in an animated way.
1384+ QPointF delta = point - publicScenePos;
1385+ // the trick is not to go all the way (1.0) as it would cause a sudden jump
1386+ publicScenePos.rx() += 0.4 * delta.x();
1387+ publicScenePos.ry() += 0.4 * delta.y();
1388+ } else {
1389+ // no smoothing when initializing or if gesture recognition was immediate as there will
1390+ // be no jump.
1391+ publicScenePos = point;
1392+ }
1393+
1394 updateSceneDistance();
1395
1396- if (oldSceneDistance != sceneDistance()) {
1397- Q_EMIT sceneDistanceChanged(sceneDistance());
1398+ if (oldSceneDistance != sceneDistance) {
1399+ Q_EMIT q->sceneDistanceChanged(sceneDistance);
1400 }
1401
1402 if (xChanged) {
1403- Q_EMIT touchSceneXChanged(point.x());
1404+ Q_EMIT q->touchSceneXChanged(publicScenePos.x());
1405 }
1406
1407 if (yChanged) {
1408- Q_EMIT touchSceneYChanged(point.y());
1409+ Q_EMIT q->touchSceneYChanged(publicScenePos.y());
1410 }
1411 }
1412
1413-void DirectionalDragArea::updateVelocityCalculator(const QPointF &scenePos)
1414-{
1415- QPointF totalSceneMovement = scenePos - m_startScenePos;
1416-
1417- qreal scalarProjection = projectOntoDirectionVector(totalSceneMovement);
1418-
1419- m_velocityCalculator->setTrackedPosition(scalarProjection);
1420-}
1421-
1422-bool DirectionalDragArea::isWithinTouchCompositionWindow()
1423+bool DirectionalDragAreaPrivate::isWithinTouchCompositionWindow()
1424 {
1425 return
1426- compositionTime() > 0 &&
1427- !m_activeTouches.isEmpty() &&
1428- m_timeSource->msecsSinceReference() <=
1429- m_activeTouches.mostRecentStartTime() + (qint64)compositionTime();
1430+ compositionTime > 0 &&
1431+ !activeTouches.isEmpty() &&
1432+ timeSource->msecsSinceReference() <=
1433+ activeTouches.mostRecentStartTime() + (qint64)compositionTime;
1434+}
1435+
1436+void DirectionalDragArea::itemChange(ItemChange change, const ItemChangeData &value)
1437+{
1438+ if (change == QQuickItem::ItemSceneChange) {
1439+ if (value.window != nullptr) {
1440+ // TODO: Handle window->screen() changes (ie window changing screens)
1441+ qreal pixelsPerMm = value.window->screen()->physicalDotsPerInch() / 25.4;
1442+ d->setPixelsPerMm(pixelsPerMm);
1443+ }
1444+ }
1445+}
1446+
1447+void DirectionalDragAreaPrivate::setPixelsPerMm(qreal pixelsPerMm)
1448+{
1449+ dampedScenePos.setMaxDelta(1. * pixelsPerMm);
1450+ setDistanceThreshold(4. * pixelsPerMm);
1451+ maxDistance = 10. * pixelsPerMm;
1452 }
1453
1454 //************************** ActiveTouchesInfo **************************
1455
1456-DirectionalDragArea::ActiveTouchesInfo::ActiveTouchesInfo(const SharedTimeSource &timeSource)
1457+ActiveTouchesInfo::ActiveTouchesInfo(const SharedTimeSource &timeSource)
1458 : m_timeSource(timeSource)
1459 {
1460 }
1461
1462-void DirectionalDragArea::ActiveTouchesInfo::update(QTouchEvent *event)
1463+void ActiveTouchesInfo::update(QTouchEvent *event)
1464 {
1465 if (!(event->touchPointStates() & (Qt::TouchPointPressed | Qt::TouchPointReleased))) {
1466 // nothing to update
1467@@ -773,7 +755,7 @@
1468 }
1469
1470 #if ACTIVETOUCHESINFO_DEBUG
1471-QString DirectionalDragArea::ActiveTouchesInfo::toString()
1472+QString ActiveTouchesInfo::toString()
1473 {
1474 QString string = "(";
1475
1476@@ -791,7 +773,7 @@
1477 }
1478 #endif // ACTIVETOUCHESINFO_DEBUG
1479
1480-void DirectionalDragArea::ActiveTouchesInfo::addTouchPoint(int touchId)
1481+void ActiveTouchesInfo::addTouchPoint(int touchId)
1482 {
1483 ActiveTouchInfo &activeTouchInfo = m_touchInfoPool.getEmptySlot();
1484 activeTouchInfo.id = touchId;
1485@@ -802,7 +784,7 @@
1486 #endif
1487 }
1488
1489-qint64 DirectionalDragArea::ActiveTouchesInfo::touchStartTime(int touchId)
1490+qint64 ActiveTouchesInfo::touchStartTime(int touchId)
1491 {
1492 qint64 result = -1;
1493
1494@@ -819,7 +801,7 @@
1495 return result;
1496 }
1497
1498-void DirectionalDragArea::ActiveTouchesInfo::removeTouchPoint(int touchId)
1499+void ActiveTouchesInfo::removeTouchPoint(int touchId)
1500 {
1501 m_touchInfoPool.forEach([&](Pool<ActiveTouchInfo>::Iterator &touchInfo) {
1502 if (touchId == touchInfo->id) {
1503@@ -835,7 +817,7 @@
1504 #endif
1505 }
1506
1507-qint64 DirectionalDragArea::ActiveTouchesInfo::mostRecentStartTime()
1508+qint64 ActiveTouchesInfo::mostRecentStartTime()
1509 {
1510 Q_ASSERT(!m_touchInfoPool.isEmpty());
1511
1512@@ -851,11 +833,11 @@
1513 return highestStartTime;
1514 }
1515
1516-void DirectionalDragArea::updateSceneDirectionVector()
1517+void DirectionalDragAreaPrivate::updateSceneDirectionVector()
1518 {
1519 QPointF localOrigin(0., 0.);
1520 QPointF localDirection;
1521- switch (m_direction) {
1522+ switch (direction) {
1523 case Direction::Upwards:
1524 localDirection.rx() = 0.;
1525 localDirection.ry() = -1.;
1526@@ -873,14 +855,31 @@
1527 localDirection.ry() = 0.;
1528 break;
1529 }
1530- QPointF sceneOrigin = mapToScene(localOrigin);
1531- QPointF sceneDirection = mapToScene(localDirection);
1532- m_sceneDirectionVector = sceneDirection - sceneOrigin;
1533-}
1534-
1535-qreal DirectionalDragArea::projectOntoDirectionVector(const QPointF &sceneVector) const
1536-{
1537- // same as dot product as m_sceneDirectionVector is a unit vector
1538- return sceneVector.x() * m_sceneDirectionVector.x() +
1539- sceneVector.y() * m_sceneDirectionVector.y();
1540+ QPointF sceneOrigin = q->mapToScene(localOrigin);
1541+ QPointF sceneDirection = q->mapToScene(localDirection);
1542+ sceneDirectionVector = sceneDirection - sceneOrigin;
1543+}
1544+
1545+qreal DirectionalDragAreaPrivate::projectOntoDirectionVector(const QPointF &sceneVector) const
1546+{
1547+ // same as dot product as sceneDirectionVector is a unit vector
1548+ return sceneVector.x() * sceneDirectionVector.x() +
1549+ sceneVector.y() * sceneDirectionVector.y();
1550+}
1551+
1552+DirectionalDragAreaPrivate::DirectionalDragAreaPrivate(DirectionalDragArea *q)
1553+ : q(q)
1554+ , status(WaitingForTouch)
1555+ , sceneDistance(0)
1556+ , touchId(-1)
1557+ , direction(Direction::Rightwards)
1558+ , distanceThreshold(0)
1559+ , distanceThresholdSquared(0.)
1560+ , maxTime(400)
1561+ , compositionTime(60)
1562+ , immediateRecognition(false)
1563+ , recognitionTimer(nullptr)
1564+ , timeSource(new RealTimeSource)
1565+ , activeTouches(timeSource)
1566+{
1567 }
1568
1569=== modified file 'plugins/Ubuntu/Gestures/DirectionalDragArea.h'
1570--- plugins/Ubuntu/Gestures/DirectionalDragArea.h 2014-10-01 13:20:32 +0000
1571+++ plugins/Ubuntu/Gestures/DirectionalDragArea.h 2015-04-15 20:21:35 +0000
1572@@ -18,7 +18,6 @@
1573 #define DIRECTIONAL_DRAG_AREA_H
1574
1575 #include <QtQuick/QQuickItem>
1576-#include "AxisVelocityCalculator.h"
1577 #include "UbuntuGesturesQmlGlobal.h"
1578 #include "Damper.h"
1579 #include "Direction.h"
1580@@ -29,6 +28,7 @@
1581
1582 class TouchOwnershipEvent;
1583 class UnownedTouchEvent;
1584+class DirectionalDragAreaPrivate;
1585
1586 /*
1587 An area that detects axis-aligned single-finger drag gestures
1588@@ -61,95 +61,31 @@
1589 Q_PROPERTY(qreal touchSceneX READ touchSceneX NOTIFY touchSceneXChanged)
1590 Q_PROPERTY(qreal touchSceneY READ touchSceneY NOTIFY touchSceneYChanged)
1591
1592- // The current status of the directional drag gesture area.
1593- Q_PROPERTY(Status status READ status NOTIFY statusChanged)
1594-
1595 // Whether a drag gesture is taking place
1596- // This will be true as long as status is Undecided or Recognized
1597- // When a gesture gets rejected, dragging turns to false.
1598 Q_PROPERTY(bool dragging READ dragging NOTIFY draggingChanged)
1599
1600- /////
1601- // stuff that will be set in stone at some point
1602-
1603- // How far the touch point can move away from its expected position before
1604- // it causes a rejection in the gesture recognition. This is to compensate
1605- // for both noise in the touch input signal and for the natural irregularities
1606- // in the finger movement.
1607- // Proper value is likely device-specific.
1608- Q_PROPERTY(qreal maxDeviation READ maxDeviation WRITE setMaxDeviation NOTIFY maxDeviationChanged)
1609-
1610- // Widening angle, in degrees
1611- // It's roughly the maximum angle a touch point can make relative to the
1612- // axis defined by the compoment's direction for it to be recognized as a
1613- // directional drag.
1614- Q_PROPERTY(qreal wideningAngle READ wideningAngle WRITE setWideningAngle
1615- NOTIFY wideningAngleChanged)
1616-
1617- // How far a touch point has to move from its initial position in order for
1618- // it to be recognized as a directional drag.
1619- Q_PROPERTY(qreal distanceThreshold READ distanceThreshold WRITE setDistanceThreshold
1620- NOTIFY distanceThresholdChanged)
1621-
1622- // Minimum speed a gesture needs to have in order to be recognized as a
1623- // directional drag.
1624- // In pixels per second
1625- Q_PROPERTY(qreal minSpeed READ minSpeed WRITE setMinSpeed NOTIFY minSpeedChanged)
1626-
1627- // A gesture will be rejected if more than maxSilenceTime milliseconds has
1628- // passed since we last got an input event from it (during Undecided state).
1629- //
1630- // Silence (i.e., lack of new input events) doesn't necessarily mean that the user's
1631- // finger is still (zero drag speed). In some cases the finger might be moving but
1632- // the driver's high noise filtering might cause those silence periods, specially
1633- // in the moments succeeding a press (talking about Galaxy Nexus here).
1634- Q_PROPERTY(int maxSilenceTime READ maxSilenceTime
1635- WRITE setMaxSilenceTime
1636- NOTIFY maxSilenceTimeChanged)
1637-
1638- //
1639- /////
1640-
1641- // Maximum time (in milliseconds) after the start of a given touch point where
1642- // subsequent touch starts are grouped with the first one into an N-touches gesture
1643- // (e.g. a two-fingers tap or drag).
1644- Q_PROPERTY(int compositionTime READ compositionTime
1645- WRITE setCompositionTime
1646- NOTIFY compositionTimeChanged)
1647+ // Whether the drag area is pressed.
1648+ Q_PROPERTY(bool pressed READ pressed NOTIFY pressedChanged)
1649+
1650+ // Whether a gesture should be Recognized as soon a touch lands on the area.
1651+ // With this property enabled it will work pretty much like a MultiPointTouchArea,
1652+ // just with a different API.
1653+ //
1654+ // It's false by default. In most cases you will not want that enabled.
1655+ Q_PROPERTY(bool immediateRecognition
1656+ READ immediateRecognition
1657+ WRITE setImmediateRecognition
1658+ NOTIFY immediateRecognitionChanged)
1659
1660 Q_ENUMS(Direction)
1661- Q_ENUMS(Status)
1662 public:
1663 DirectionalDragArea(QQuickItem *parent = 0);
1664
1665 Direction::Type direction() const;
1666 void setDirection(Direction::Type);
1667
1668- // Describes the state of the directional drag gesture.
1669- enum Status {
1670- // Waiting for a new touch point to land on this area. No gesture is being processed
1671- // or tracked.
1672- WaitingForTouch,
1673-
1674- // A touch point has landed on this area but it's not know yet whether it is
1675- // performing a drag in the correct direction.
1676- // If it's decided that the touch point is not performing a directional drag gesture,
1677- // it will be rejected/ignored and status will return to WaitingForTouch.
1678- Undecided, //Recognizing,
1679-
1680- // There's a touch point in this area and it performed a drag in the correct
1681- // direction.
1682- //
1683- // Once recognized, the gesture state will move back to WaitingForTouch only once
1684- // that touch point ends. The gesture will remain in the Recognized state even if
1685- // the touch point starts moving in other directions or halts.
1686- Recognized,
1687- };
1688- Status status() const { return m_status; }
1689-
1690 qreal distance() const;
1691 qreal sceneDistance() const;
1692- void updateSceneDistance();
1693
1694 qreal touchX() const;
1695 qreal touchY() const;
1696@@ -157,152 +93,43 @@
1697 qreal touchSceneX() const;
1698 qreal touchSceneY() const;
1699
1700- bool dragging() const { return (m_status == Undecided) || (m_status == Recognized); }
1701-
1702- qreal maxDeviation() const { return m_dampedScenePos.maxDelta(); }
1703- void setMaxDeviation(qreal value);
1704-
1705- qreal wideningAngle() const;
1706- void setWideningAngle(qreal value);
1707-
1708- qreal distanceThreshold() const { return m_distanceThreshold; }
1709- void setDistanceThreshold(qreal value);
1710-
1711- qreal minSpeed() const { return m_minSpeed; }
1712- void setMinSpeed(qreal value);
1713-
1714- int maxSilenceTime() const { return m_maxSilenceTime; }
1715- void setMaxSilenceTime(int value);
1716-
1717- int compositionTime() const { return m_compositionTime; }
1718- void setCompositionTime(int value);
1719-
1720- // Replaces the existing Timer with the given one.
1721- //
1722- // Useful for providing a fake timer when testing.
1723- void setRecognitionTimer(UbuntuGestures::AbstractTimer *timer);
1724-
1725- // Useful for testing, where a fake time source can be supplied
1726- void setTimeSource(const UbuntuGestures::SharedTimeSource &timeSource);
1727+ bool dragging() const;
1728+
1729+ bool pressed() const;
1730+
1731+ bool immediateRecognition() const;
1732+ void setImmediateRecognition(bool enabled);
1733
1734 bool event(QEvent *e) override;
1735
1736- // Maximum time, in milliseconds, between a press and a release, for a touch
1737- // sequence to be considered a tap.
1738- int maxTapDuration() const { return 300; }
1739+ /*
1740+ In qmltests, sequences of touch events are sent all at once, unlike in "real life".
1741+ Also qmltests might run really slowly, e.g. when run from inside virtual machines.
1742+ Thus to remove a variable that qmltests cannot really control, namely time, this
1743+ function removes all constraints that are sensible to elapsed time.
1744+
1745+ This effectively makes the DirectionalDragArea easier to fool.
1746+ */
1747+ Q_INVOKABLE void removeTimeConstraints();
1748
1749 Q_SIGNALS:
1750 void directionChanged(Direction::Type direction);
1751- void statusChanged(Status value);
1752 void draggingChanged(bool value);
1753+ void pressedChanged(bool value);
1754 void distanceChanged(qreal value);
1755 void sceneDistanceChanged(qreal value);
1756- void maxDeviationChanged(qreal value);
1757- void wideningAngleChanged(qreal value);
1758- void distanceThresholdChanged(qreal value);
1759- void minSpeedChanged(qreal value);
1760- void maxSilenceTimeChanged(int value);
1761- void compositionTimeChanged(int value);
1762 void touchXChanged(qreal value);
1763 void touchYChanged(qreal value);
1764 void touchSceneXChanged(qreal value);
1765 void touchSceneYChanged(qreal value);
1766-
1767- // TODO: I would rather not have such signal as it has nothing to do with drag gestures.
1768- // Remove when no longer used or move its implementation to the QML code that uses it
1769- // See maxTapDuration()
1770- void tapped();
1771+ void immediateRecognitionChanged(bool value);
1772
1773 protected:
1774 virtual void touchEvent(QTouchEvent *event);
1775-
1776-private Q_SLOTS:
1777- void checkSpeed();
1778- void giveUpIfDisabledOrInvisible();
1779-
1780-private:
1781- void touchEvent_absent(QTouchEvent *event);
1782- void touchEvent_undecided(QTouchEvent *event);
1783- void touchEvent_recognized(QTouchEvent *event);
1784- bool pointInsideAllowedArea() const;
1785- bool movingInRightDirection() const;
1786- bool movedFarEnough(const QPointF &point) const;
1787- const QTouchEvent::TouchPoint *fetchTargetTouchPoint(QTouchEvent *event);
1788- void setStatus(Status newStatus);
1789- void setPreviousPos(const QPointF &point);
1790- void setPreviousScenePos(const QPointF &point);
1791- void updateVelocityCalculator(const QPointF &point);
1792- bool isWithinTouchCompositionWindow();
1793- void updateSceneDirectionVector();
1794- // returns the scalar projection between the given vector (in scene coordinates)
1795- // and m_sceneDirectionVector
1796- qreal projectOntoDirectionVector(const QPointF &sceneVector) const;
1797- void touchOwnershipEvent(TouchOwnershipEvent *event);
1798- void unownedTouchEvent(UnownedTouchEvent *event);
1799- void unownedTouchEvent_undecided(UnownedTouchEvent *unownedTouchEvent);
1800- void watchPressedTouchPoints(const QList<QTouchEvent::TouchPoint> &touchPoints);
1801- bool recognitionIsDisabled() const;
1802- void emitSignalIfTapped();
1803-
1804- Status m_status;
1805-
1806- QPointF m_startPos;
1807- QPointF m_startScenePos;
1808- QPointF m_previousPos;
1809- QPointF m_previousScenePos;
1810- qreal m_sceneDistance;
1811- int m_touchId;
1812-
1813- // A movement damper is used in some of the gesture recognition calculations
1814- // to get rid of noise or small oscillations in the touch position.
1815- DampedPointF m_dampedScenePos;
1816- QPointF m_previousDampedScenePos;
1817-
1818- // Unit vector in scene coordinates describing the direction of the gesture recognition
1819- QPointF m_sceneDirectionVector;
1820-
1821- Direction::Type m_direction;
1822- qreal m_wideningAngle; // in degrees
1823- qreal m_wideningFactor; // it's pow(cosine(m_wideningAngle), 2)
1824- qreal m_distanceThreshold;
1825- qreal m_distanceThresholdSquared; // it's pow(m_distanceThreshold, 2)
1826- qreal m_minSpeed;
1827- int m_maxSilenceTime; // in milliseconds
1828- int m_silenceTime; // in milliseconds
1829- int m_compositionTime; // in milliseconds
1830- int m_numSamplesOnLastSpeedCheck;
1831- UbuntuGestures::AbstractTimer *m_recognitionTimer;
1832- AxisVelocityCalculator *m_velocityCalculator;
1833-
1834- UbuntuGestures::SharedTimeSource m_timeSource;
1835-
1836- // Information about an active touch point
1837- struct ActiveTouchInfo {
1838- ActiveTouchInfo() : id(-1), startTime(-1) {}
1839- bool isValid() const { return id != -1; }
1840- void reset() { id = -1; }
1841- int id;
1842- qint64 startTime;
1843- };
1844- class ActiveTouchesInfo {
1845- public:
1846- ActiveTouchesInfo(const UbuntuGestures::SharedTimeSource &timeSource);
1847- void update(QTouchEvent *event);
1848- qint64 touchStartTime(int id);
1849- bool isEmpty() const { return m_touchInfoPool.isEmpty(); }
1850- qint64 mostRecentStartTime();
1851- UbuntuGestures::SharedTimeSource m_timeSource;
1852- private:
1853- void addTouchPoint(int touchId);
1854- void removeTouchPoint(int touchId);
1855- #if ACTIVETOUCHESINFO_DEBUG
1856- QString toString();
1857- #endif
1858-
1859- Pool<ActiveTouchInfo> m_touchInfoPool;
1860- } m_activeTouches;
1861-
1862- friend class tst_DirectionalDragArea;
1863+ virtual void itemChange(ItemChange change, const ItemChangeData &value);
1864+
1865+public: // so tests can access it
1866+ DirectionalDragAreaPrivate *d;
1867 };
1868
1869 #endif // DIRECTIONAL_DRAG_AREA_H
1870
1871=== added file 'plugins/Ubuntu/Gestures/DirectionalDragArea_p.h'
1872--- plugins/Ubuntu/Gestures/DirectionalDragArea_p.h 1970-01-01 00:00:00 +0000
1873+++ plugins/Ubuntu/Gestures/DirectionalDragArea_p.h 2015-04-15 20:21:35 +0000
1874@@ -0,0 +1,167 @@
1875+/*
1876+ * Copyright (C) 2015 Canonical, Ltd.
1877+ *
1878+ * This program is free software; you can redistribute it and/or modify
1879+ * it under the terms of the GNU General Public License as published by
1880+ * the Free Software Foundation; version 3.
1881+ *
1882+ * This program is distributed in the hope that it will be useful,
1883+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1884+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1885+ * GNU General Public License for more details.
1886+ *
1887+ * You should have received a copy of the GNU General Public License
1888+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1889+ */
1890+
1891+#ifndef DIRECTIONAL_DRAG_AREA_PRIV_H
1892+#define DIRECTIONAL_DRAG_AREA_PRIV_H
1893+
1894+// Information about an active touch point
1895+struct UBUNTUGESTURESQML_EXPORT ActiveTouchInfo {
1896+ ActiveTouchInfo() : id(-1), startTime(-1) {}
1897+ bool isValid() const { return id != -1; }
1898+ void reset() { id = -1; }
1899+ int id;
1900+ qint64 startTime;
1901+};
1902+class UBUNTUGESTURESQML_EXPORT ActiveTouchesInfo {
1903+public:
1904+ ActiveTouchesInfo(const UbuntuGestures::SharedTimeSource &timeSource);
1905+ void update(QTouchEvent *event);
1906+ qint64 touchStartTime(int id);
1907+ bool isEmpty() const { return m_touchInfoPool.isEmpty(); }
1908+ qint64 mostRecentStartTime();
1909+ UbuntuGestures::SharedTimeSource m_timeSource;
1910+private:
1911+ void addTouchPoint(int touchId);
1912+ void removeTouchPoint(int touchId);
1913+ #if ACTIVETOUCHESINFO_DEBUG
1914+ QString toString();
1915+ #endif
1916+
1917+ Pool<ActiveTouchInfo> m_touchInfoPool;
1918+};
1919+
1920+class UBUNTUGESTURESQML_EXPORT DirectionalDragAreaPrivate : public QObject {
1921+ Q_OBJECT
1922+
1923+ Q_ENUMS(Status)
1924+public:
1925+ DirectionalDragAreaPrivate(DirectionalDragArea *q);
1926+
1927+public Q_SLOTS:
1928+ void giveUpIfDisabledOrInvisible();
1929+ void rejectGesture();
1930+
1931+public:
1932+ // Describes the state of the directional drag gesture.
1933+ enum Status {
1934+ // Waiting for a new touch point to land on this area. No gesture is being processed
1935+ // or tracked.
1936+ WaitingForTouch,
1937+
1938+ // A touch point has landed on this area but it's not know yet whether it is
1939+ // performing a drag in the correct direction.
1940+ // If it's decided that the touch point is not performing a directional drag gesture,
1941+ // it will be rejected/ignored and status will return to WaitingForTouch.
1942+ Undecided, //Recognizing,
1943+
1944+ // There's a touch point in this area and it performed a drag in the correct
1945+ // direction.
1946+ //
1947+ // Once recognized, the gesture state will move back to WaitingForTouch only once
1948+ // that touch point ends. The gesture will remain in the Recognized state even if
1949+ // the touch point starts moving in other directions or halts.
1950+ Recognized,
1951+ };
1952+
1953+ void touchEvent_absent(QTouchEvent *event);
1954+ void touchEvent_undecided(QTouchEvent *event);
1955+ void touchEvent_recognized(QTouchEvent *event);
1956+ bool movingInRightDirection() const;
1957+ bool movedFarEnoughAlongGestureAxis() const;
1958+ bool isPastMaxDistance() const;
1959+ const QTouchEvent::TouchPoint *fetchTargetTouchPoint(QTouchEvent *event);
1960+ void setStatus(Status newStatus);
1961+ void setPublicPos(const QPointF &point);
1962+ void setPublicScenePos(const QPointF &point);
1963+ bool isWithinTouchCompositionWindow();
1964+ void updateSceneDirectionVector();
1965+ // returns the scalar projection between the given vector (in scene coordinates)
1966+ // and m_sceneDirectionVector
1967+ qreal projectOntoDirectionVector(const QPointF &sceneVector) const;
1968+ void touchOwnershipEvent(TouchOwnershipEvent *event);
1969+ void unownedTouchEvent(UnownedTouchEvent *event);
1970+ void unownedTouchEvent_undecided(UnownedTouchEvent *unownedTouchEvent);
1971+ void watchPressedTouchPoints(const QList<QTouchEvent::TouchPoint> &touchPoints);
1972+ bool recognitionIsDisabled() const;
1973+ bool sanityCheckRecognitionProperties();
1974+ void updateSceneDistance();
1975+ void setMaxTime(int value);
1976+ void setDistanceThreshold(qreal value);
1977+ void setPixelsPerMm(qreal pixelsPerMm);
1978+ QString objectName() const { return q->objectName(); }
1979+
1980+ // Replaces the existing Timer with the given one.
1981+ //
1982+ // Useful for providing a fake timer when testing.
1983+ void setRecognitionTimer(UbuntuGestures::AbstractTimer *timer);
1984+
1985+ // Useful for testing, where a fake time source can be supplied
1986+ void setTimeSource(const UbuntuGestures::SharedTimeSource &timeSource);
1987+
1988+ DirectionalDragArea *q;
1989+
1990+ // The current status of the directional drag gesture area.
1991+ Status status;
1992+
1993+ QPointF startPos;
1994+ QPointF startScenePos;
1995+ qreal sceneDistance;
1996+ int touchId;
1997+
1998+ // The touch position exposed in the public API.
1999+ // It only starts to move once the gesture gets recognized.
2000+ QPointF publicPos;
2001+ QPointF publicScenePos;
2002+
2003+ // A movement damper is used in some of the gesture recognition calculations
2004+ // to get rid of noise or small oscillations in the touch position.
2005+ DampedPointF dampedScenePos;
2006+ QPointF previousDampedScenePos;
2007+
2008+ // Unit vector in scene coordinates describing the direction of the gesture recognition
2009+ QPointF sceneDirectionVector;
2010+
2011+ Direction::Type direction;
2012+
2013+ // How far a touch point has to move from its initial position along the gesture axis in order
2014+ // for it to be recognized as a directional drag.
2015+ qreal distanceThreshold;
2016+ qreal distanceThresholdSquared; // it's pow(distanceThreshold, 2)
2017+
2018+ // Maximum time (in milliseconds) the gesture can take to go beyond the distance threshold
2019+ int maxTime;
2020+
2021+ // Maximum distance the gesture can go without crossing the axis-aligned distance threshold
2022+ qreal maxDistance;
2023+
2024+ // Maximum time (in milliseconds) after the start of a given touch point where
2025+ // subsequent touch starts are grouped with the first one into an N-touches gesture
2026+ // (e.g. a two-fingers tap or drag).
2027+ int compositionTime;
2028+
2029+ bool immediateRecognition;
2030+
2031+ UbuntuGestures::AbstractTimer *recognitionTimer;
2032+
2033+ UbuntuGestures::SharedTimeSource timeSource;
2034+
2035+ ActiveTouchesInfo activeTouches;
2036+
2037+Q_SIGNALS:
2038+ void statusChanged(Status value);
2039+};
2040+
2041+#endif // DIRECTIONAL_DRAG_AREA_PRIV_H
2042
2043=== modified file 'plugins/Ubuntu/Gestures/TouchDispatcher.cpp'
2044--- plugins/Ubuntu/Gestures/TouchDispatcher.cpp 2014-11-03 15:21:46 +0000
2045+++ plugins/Ubuntu/Gestures/TouchDispatcher.cpp 2015-04-15 20:21:35 +0000
2046@@ -1,5 +1,5 @@
2047 /*
2048- * Copyright (C) 2014 Canonical, Ltd.
2049+ * Copyright (C) 2014-2015 Canonical, Ltd.
2050 *
2051 * This program is free software; you can redistribute it and/or modify
2052 * it under the terms of the GNU General Public License as published by
2053@@ -28,8 +28,11 @@
2054 #define TOUCHDISPATCHER_DEBUG 0
2055
2056 #if TOUCHDISPATCHER_DEBUG
2057+#define ugDebug(params) qDebug().nospace() << "[TouchDispatcher(" << this << ")] " << params
2058 #include <DebugHelpers.h>
2059-#endif
2060+#else // TOUCHDISPATCHER_DEBUG
2061+#define ugDebug(params) ((void)0)
2062+#endif // TOUCHDISPATCHER_DEBUG
2063
2064 TouchDispatcher::TouchDispatcher()
2065 : m_status(NoActiveTouch)
2066@@ -44,13 +47,12 @@
2067 m_targetItem = target;
2068 if (m_status != NoActiveTouch) {
2069 qWarning("[TouchDispatcher] Changing target item in the middle of a touch stream");
2070- m_status = TargetRejectedTouches;
2071+ setStatus(TargetRejectedTouches);
2072 }
2073 }
2074 }
2075
2076-void TouchDispatcher::dispatch(QEvent::Type eventType,
2077- QTouchDevice *device,
2078+void TouchDispatcher::dispatch(QTouchDevice *device,
2079 Qt::KeyboardModifiers modifiers,
2080 const QList<QTouchEvent::TouchPoint> &touchPoints,
2081 QWindow *window,
2082@@ -61,6 +63,8 @@
2083 return;
2084 }
2085
2086+ QEvent::Type eventType = resolveEventType(touchPoints);
2087+
2088 if (eventType == QEvent::TouchBegin) {
2089 dispatchTouchBegin(device, modifiers, touchPoints, window, timestamp);
2090
2091@@ -72,15 +76,13 @@
2092 dispatchAsMouse(device, modifiers, touchPoints, timestamp);
2093 } else {
2094 Q_ASSERT(m_status == TargetRejectedTouches);
2095- #if TOUCHDISPATCHER_DEBUG
2096- qDebug() << "[TouchDispatcher] Not dispatching touch event to" << m_targetItem.data()
2097- << "because it already rejected the touch stream.";
2098- #endif
2099+ ugDebug("Not dispatching touch event to " << m_targetItem.data()
2100+ << "because it already rejected the touch stream.");
2101 // Do nothing
2102 }
2103
2104 if (eventType == QEvent::TouchEnd) {
2105- m_status = NoActiveTouch;
2106+ setStatus(NoActiveTouch);
2107 m_touchMouseId = -1;
2108 }
2109
2110@@ -103,10 +105,7 @@
2111 QQuickItem *targetItem = m_targetItem.data();
2112
2113 if (!targetItem->isEnabled() || !targetItem->isVisible()) {
2114- #if TOUCHDISPATCHER_DEBUG
2115- qDebug() << "[TouchDispatcher] Cannot dispatch touch event to" << targetItem
2116- << "because it's disabled or invisible.";
2117- #endif
2118+ ugDebug("Cannot dispatch touch event to " << targetItem << " because it's disabled or invisible.");
2119 return;
2120 }
2121
2122@@ -118,62 +117,46 @@
2123 createQTouchEvent(QEvent::TouchBegin, device, modifiers, targetTouchPoints, window, timestamp));
2124
2125
2126- #if TOUCHDISPATCHER_DEBUG
2127- qDebug() << "[TouchDispatcher] dispatching" << qPrintable(touchEventToString(touchEvent.data()))
2128- << "to" << targetItem;
2129- #endif
2130+ ugDebug("dispatching " << qPrintable(touchEventToString(touchEvent.data()))
2131+ << " to " << targetItem);
2132 QCoreApplication::sendEvent(targetItem, touchEvent.data());
2133
2134
2135 if (touchEvent->isAccepted()) {
2136- #if TOUCHDISPATCHER_DEBUG
2137- qDebug() << "[TouchDispatcher] Item accepted the touch event.";
2138- #endif
2139- m_status = DeliveringTouchEvents;
2140+ ugDebug("Item accepted the touch event.");
2141+ setStatus(DeliveringTouchEvents);
2142 } else if (targetItem->acceptedMouseButtons() & Qt::LeftButton) {
2143- #if TOUCHDISPATCHER_DEBUG
2144- qDebug() << "[TouchDispatcher] Item rejected the touch event. Trying a QMouseEvent";
2145- #endif
2146+ ugDebug("Item rejected the touch event. Trying a QMouseEvent");
2147 // NB: Arbitrarily chose the first touch point to emulate the mouse pointer
2148 QScopedPointer<QMouseEvent> mouseEvent(
2149 touchToMouseEvent(QEvent::MouseButtonPress, targetTouchPoints.at(0), timestamp,
2150 modifiers, false /* transformNeeded */));
2151 Q_ASSERT(targetTouchPoints.at(0).state() == Qt::TouchPointPressed);
2152
2153- #if TOUCHDISPATCHER_DEBUG
2154- qDebug() << "[TouchDispatcher] dispatching" << qPrintable(mouseEventToString(mouseEvent.data()))
2155- << "to" << m_targetItem.data();
2156- #endif
2157+ ugDebug("dispatching " << qPrintable(mouseEventToString(mouseEvent.data()))
2158+ << " to " << m_targetItem.data());
2159 QCoreApplication::sendEvent(targetItem, mouseEvent.data());
2160 if (mouseEvent->isAccepted()) {
2161- #if TOUCHDISPATCHER_DEBUG
2162- qDebug() << "[TouchDispatcher] Item accepted the QMouseEvent.";
2163- #endif
2164- m_status = DeliveringMouseEvents;
2165+ ugDebug("Item accepted the QMouseEvent.");
2166+ setStatus(DeliveringMouseEvents);
2167 m_touchMouseId = targetTouchPoints.at(0).id();
2168
2169 if (checkIfDoubleClicked(timestamp)) {
2170 QScopedPointer<QMouseEvent> doubleClickEvent(
2171 touchToMouseEvent(QEvent::MouseButtonDblClick, targetTouchPoints.at(0), timestamp,
2172 modifiers, false /* transformNeeded */));
2173- #if TOUCHDISPATCHER_DEBUG
2174- qDebug() << "[TouchDispatcher] dispatching" << qPrintable(mouseEventToString(doubleClickEvent.data()))
2175- << "to" << m_targetItem.data();
2176- #endif
2177+ ugDebug("dispatching " << qPrintable(mouseEventToString(doubleClickEvent.data()))
2178+ << " to " << m_targetItem.data());
2179 QCoreApplication::sendEvent(targetItem, doubleClickEvent.data());
2180 }
2181
2182 } else {
2183- #if TOUCHDISPATCHER_DEBUG
2184- qDebug() << "[TouchDispatcher] Item rejected the QMouseEvent.";
2185- #endif
2186- m_status = TargetRejectedTouches;
2187+ ugDebug("Item rejected the QMouseEvent.");
2188+ setStatus(TargetRejectedTouches);
2189 }
2190 } else {
2191- #if TOUCHDISPATCHER_DEBUG
2192- qDebug() << "[TouchDispatcher] Item rejected the touch event and does not accept mouse buttons.";
2193- #endif
2194- m_status = TargetRejectedTouches;
2195+ ugDebug("Item rejected the touch event and does not accept mouse buttons.");
2196+ setStatus(TargetRejectedTouches);
2197 }
2198 }
2199
2200@@ -194,10 +177,8 @@
2201 createQTouchEvent(eventType, device, modifiers, targetTouchPoints, window, timestamp));
2202
2203
2204- #if TOUCHDISPATCHER_DEBUG
2205- qDebug() << "[TouchDispatcher] dispatching" << qPrintable(touchEventToString(eventForTargetItem.data()))
2206- << "to" << targetItem;
2207- #endif
2208+ ugDebug("dispatching " << qPrintable(touchEventToString(eventForTargetItem.data()))
2209+ << " to " << targetItem);
2210 QCoreApplication::sendEvent(targetItem, eventForTargetItem.data());
2211 }
2212
2213@@ -254,10 +235,8 @@
2214 QScopedPointer<QMouseEvent> mouseEvent(touchToMouseEvent(eventType, *touchMouse, timestamp, modifiers,
2215 true /* transformNeeded */));
2216
2217- #if TOUCHDISPATCHER_DEBUG
2218- qDebug() << "[TouchDispatcher] dispatching" << qPrintable(mouseEventToString(mouseEvent.data()))
2219- << "to" << m_targetItem.data();
2220- #endif
2221+ ugDebug("dispatching " << qPrintable(mouseEventToString(mouseEvent.data()))
2222+ << " to " << m_targetItem.data());
2223 QCoreApplication::sendEvent(m_targetItem.data(), mouseEvent.data());
2224 }
2225 }
2226@@ -365,3 +344,59 @@
2227
2228 return doubleClicked;
2229 }
2230+
2231+void TouchDispatcher::setStatus(Status status)
2232+{
2233+ if (status != m_status) {
2234+ #if TOUCHDISPATCHER_DEBUG
2235+ switch (status) {
2236+ case NoActiveTouch:
2237+ ugDebug("status = NoActiveTouch");
2238+ break;
2239+ case DeliveringTouchEvents:
2240+ ugDebug("status = DeliveringTouchEvents");
2241+ break;
2242+ case DeliveringMouseEvents:
2243+ ugDebug("status = DeliveringMouseEvents");
2244+ break;
2245+ case TargetRejectedTouches:
2246+ ugDebug("status = TargetRejectedTouches");
2247+ break;
2248+ default:
2249+ ugDebug("status = " << status);
2250+ break;
2251+ }
2252+ #endif
2253+ m_status = status;
2254+ }
2255+}
2256+
2257+void TouchDispatcher::reset()
2258+{
2259+ setStatus(NoActiveTouch);
2260+ m_touchMouseId = -1;
2261+ m_touchMousePressTimestamp =0;
2262+}
2263+
2264+QEvent::Type TouchDispatcher::resolveEventType(const QList<QTouchEvent::TouchPoint> &touchPoints)
2265+{
2266+ QEvent::Type eventType;
2267+
2268+ Qt::TouchPointStates eventStates = 0;
2269+ for (int i = 0; i < touchPoints.count(); i++)
2270+ eventStates |= touchPoints[i].state();
2271+
2272+ switch (eventStates) {
2273+ case Qt::TouchPointPressed:
2274+ eventType = QEvent::TouchBegin;
2275+ break;
2276+ case Qt::TouchPointReleased:
2277+ eventType = QEvent::TouchEnd;
2278+ break;
2279+ default:
2280+ eventType = QEvent::TouchUpdate;
2281+ break;
2282+ }
2283+
2284+ return eventType;
2285+}
2286
2287=== modified file 'plugins/Ubuntu/Gestures/TouchDispatcher.h'
2288--- plugins/Ubuntu/Gestures/TouchDispatcher.h 2014-11-03 15:21:46 +0000
2289+++ plugins/Ubuntu/Gestures/TouchDispatcher.h 2015-04-15 20:21:35 +0000
2290@@ -1,5 +1,5 @@
2291 /*
2292- * Copyright (C) 2014 Canonical, Ltd.
2293+ * Copyright (C) 2014-2015 Canonical, Ltd.
2294 *
2295 * This program is free software; you can redistribute it and/or modify
2296 * it under the terms of the GNU General Public License as published by
2297@@ -36,12 +36,20 @@
2298 void setTargetItem(QQuickItem *target);
2299 QQuickItem *targetItem() { return m_targetItem; }
2300
2301- void dispatch(QEvent::Type eventType,
2302- QTouchDevice *device,
2303+ void dispatch(QTouchDevice *device,
2304 Qt::KeyboardModifiers modifiers,
2305 const QList<QTouchEvent::TouchPoint> &touchPoints,
2306 QWindow *window,
2307 ulong timestamp);
2308+
2309+ void reset();
2310+
2311+ enum Status {
2312+ NoActiveTouch,
2313+ DeliveringTouchEvents,
2314+ DeliveringMouseEvents,
2315+ TargetRejectedTouches
2316+ };
2317 private:
2318 void dispatchTouchBegin(
2319 QTouchDevice *device,
2320@@ -73,14 +81,13 @@
2321
2322 bool checkIfDoubleClicked(ulong newPressEventTimestamp);
2323
2324+ void setStatus(Status status);
2325+
2326+ static QEvent::Type resolveEventType(const QList<QTouchEvent::TouchPoint> &touchPoints);
2327+
2328 QPointer<QQuickItem> m_targetItem;
2329
2330- enum {
2331- NoActiveTouch,
2332- DeliveringTouchEvents,
2333- DeliveringMouseEvents,
2334- TargetRejectedTouches
2335- } m_status;
2336+ Status m_status;
2337
2338 int m_touchMouseId;
2339 ulong m_touchMousePressTimestamp;
2340
2341=== modified file 'plugins/Ubuntu/Gestures/TouchGate.cpp'
2342--- plugins/Ubuntu/Gestures/TouchGate.cpp 2015-03-12 13:52:35 +0000
2343+++ plugins/Ubuntu/Gestures/TouchGate.cpp 2015-04-15 20:21:35 +0000
2344@@ -1,5 +1,5 @@
2345 /*
2346- * Copyright (C) 2014 Canonical, Ltd.
2347+ * Copyright (C) 2014-2015 Canonical, Ltd.
2348 *
2349 * This program is free software; you can redistribute it and/or modify
2350 * it under the terms of the GNU General Public License as published by
2351@@ -23,8 +23,18 @@
2352 #include <TouchRegistry.h>
2353
2354 #if TOUCHGATE_DEBUG
2355+#define ugDebug(params) qDebug().nospace() << "[TouchGate(" << (void*)this << ")] " << params
2356 #include <DebugHelpers.h>
2357-#endif
2358+#else // TOUCHGATE_DEBUG
2359+#define ugDebug(params) ((void)0)
2360+#endif // TOUCHGATE_DEBUG
2361+
2362+TouchGate::TouchGate(QQuickItem *parent)
2363+ : QQuickItem(parent)
2364+{
2365+ connect(this, &QQuickItem::enabledChanged,
2366+ this, &TouchGate::onEnabledChanged);
2367+}
2368
2369 bool TouchGate::event(QEvent *e)
2370 {
2371@@ -38,13 +48,12 @@
2372
2373 void TouchGate::touchEvent(QTouchEvent *event)
2374 {
2375- #if TOUCHGATE_DEBUG
2376- qDebug() << "[TouchGate] got touch event" << qPrintable(touchEventToString(event));
2377- #endif
2378+ ugDebug("got touch event" << qPrintable(touchEventToString(event)));
2379 event->accept();
2380
2381 const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
2382- bool goodToGo = true;
2383+ QList<QTouchEvent::TouchPoint> validTouchPoints;
2384+ bool ownsAllTouches = true;
2385 for (int i = 0; i < touchPoints.count(); ++i) {
2386 const QTouchEvent::TouchPoint &touchPoint = touchPoints[i];
2387
2388@@ -55,31 +64,41 @@
2389 TouchRegistry::instance()->requestTouchOwnership(touchPoint.id(), this);
2390 }
2391
2392- goodToGo &= m_touchInfoMap.contains(touchPoint.id())
2393- && m_touchInfoMap[touchPoint.id()].ownership == OwnershipGranted;
2394-
2395- if (touchPoint.state() == Qt::TouchPointReleased && m_touchInfoMap.contains(touchPoint.id())) {
2396- m_touchInfoMap[touchPoint.id()].ended = true;
2397+ if (m_touchInfoMap.contains(touchPoint.id())) {
2398+ validTouchPoints.append(touchPoint);
2399+
2400+ ownsAllTouches &= m_touchInfoMap[touchPoint.id()].ownership == OwnershipGranted;
2401+
2402+ if (touchPoint.state() == Qt::TouchPointReleased) {
2403+ m_touchInfoMap[touchPoint.id()].ended = true;
2404+ }
2405 }
2406
2407 }
2408
2409- if (goodToGo) {
2410+ if (validTouchPoints.isEmpty()) {
2411+ // nothing to do.
2412+ return;
2413+ }
2414+
2415+ if (ownsAllTouches) {
2416 if (m_storedEvents.isEmpty()) {
2417 // let it pass through
2418- dispatchTouchEventToTarget(event);
2419+ removeTouchInfoForEndedTouches(validTouchPoints);
2420+ m_dispatcher.dispatch(event->device(), event->modifiers(), validTouchPoints,
2421+ event->window(), event->timestamp());
2422 } else {
2423 // Retain the event to ensure TouchGate dispatches them in order.
2424 // Otherwise the current event would come before the stored ones, which are older.
2425- #if TOUCHGATE_DEBUG
2426- qDebug("[TouchGate] Storing event because thouches %s are still pending ownership.",
2427- qPrintable(oldestPendingTouchIdsString()));
2428- #endif
2429- storeTouchEvent(event);
2430+ ugDebug("Storing event because thouches " << qPrintable(oldestPendingTouchIdsString())
2431+ << " are still pending ownership.");
2432+ storeTouchEvent(event->device(), event->modifiers(), validTouchPoints,
2433+ event->window(), event->timestamp());
2434 }
2435 } else {
2436 // Retain events that have unowned touches
2437- storeTouchEvent(event);
2438+ storeTouchEvent(event->device(), event->modifiers(), validTouchPoints,
2439+ event->window(), event->timestamp());
2440 }
2441 }
2442
2443@@ -88,24 +107,23 @@
2444 // TODO: Optimization: batch those actions as TouchOwnershipEvents
2445 // might come one right after the other.
2446
2447- Q_ASSERT(m_touchInfoMap.contains(event->touchId()));
2448-
2449- TouchInfo &touchInfo = m_touchInfoMap[event->touchId()];
2450-
2451- if (event->gained()) {
2452- #if TOUCHGATE_DEBUG
2453- qDebug() << "[TouchGate] Got ownership of touch " << event->touchId();
2454- #endif
2455- touchInfo.ownership = OwnershipGranted;
2456+ if (m_touchInfoMap.contains(event->touchId())) {
2457+ TouchInfo &touchInfo = m_touchInfoMap[event->touchId()];
2458+
2459+ if (event->gained()) {
2460+ ugDebug("Got ownership of touch " << event->touchId());
2461+ touchInfo.ownership = OwnershipGranted;
2462+ } else {
2463+ ugDebug("Lost ownership of touch " << event->touchId());
2464+ m_touchInfoMap.remove(event->touchId());
2465+ removeTouchFromStoredEvents(event->touchId());
2466+ }
2467+
2468+ dispatchFullyOwnedEvents();
2469 } else {
2470- #if TOUCHGATE_DEBUG
2471- qDebug() << "[TouchGate] Lost ownership of touch " << event->touchId();
2472- #endif
2473- m_touchInfoMap.remove(event->touchId());
2474- removeTouchFromStoredEvents(event->touchId());
2475+ // Ignore it. It probably happened because the TouchGate got disabled
2476+ // between the time it requested ownership and the time it got it.
2477 }
2478-
2479- dispatchFullyOwnedEvents();
2480 }
2481
2482 bool TouchGate::isTouchPointOwned(int touchId) const
2483@@ -113,14 +131,15 @@
2484 return m_touchInfoMap[touchId].ownership == OwnershipGranted;
2485 }
2486
2487-void TouchGate::storeTouchEvent(const QTouchEvent *event)
2488+void TouchGate::storeTouchEvent(QTouchDevice *device,
2489+ Qt::KeyboardModifiers modifiers,
2490+ const QList<QTouchEvent::TouchPoint> &touchPoints,
2491+ QWindow *window,
2492+ ulong timestamp)
2493 {
2494- #if TOUCHGATE_DEBUG
2495- qDebug() << "[TouchGate] Storing" << qPrintable(touchEventToString(event));
2496- #endif
2497-
2498- TouchEvent clonedEvent(event);
2499- m_storedEvents.append(std::move(clonedEvent));
2500+ ugDebug("Storing" << touchPoints);
2501+ TouchEvent event(device, modifiers, touchPoints, window, timestamp);
2502+ m_storedEvents.append(std::move(event));
2503 }
2504
2505 void TouchGate::removeTouchFromStoredEvents(int touchId)
2506@@ -193,25 +212,13 @@
2507 void TouchGate::dispatchTouchEventToTarget(const TouchEvent &event)
2508 {
2509 removeTouchInfoForEndedTouches(event.touchPoints);
2510- m_dispatcher.dispatch(event.eventType,
2511- event.device,
2512+ m_dispatcher.dispatch(event.device,
2513 event.modifiers,
2514 event.touchPoints,
2515 event.window,
2516 event.timestamp);
2517 }
2518
2519-void TouchGate::dispatchTouchEventToTarget(QTouchEvent* event)
2520-{
2521- removeTouchInfoForEndedTouches(event->touchPoints());
2522- m_dispatcher.dispatch(event->type(),
2523- event->device(),
2524- event->modifiers(),
2525- event->touchPoints(),
2526- event->window(),
2527- event->timestamp());
2528-}
2529-
2530 void TouchGate::removeTouchInfoForEndedTouches(const QList<QTouchEvent::TouchPoint> &touchPoints)
2531 {
2532 for (int i = 0; i < touchPoints.size(); ++i) {\
2533@@ -226,14 +233,31 @@
2534 }
2535 }
2536
2537-TouchGate::TouchEvent::TouchEvent(const QTouchEvent *event)
2538- : eventType(event->type())
2539- , device(event->device())
2540- , modifiers(event->modifiers())
2541- , touchPoints(event->touchPoints())
2542- , target(qobject_cast<QQuickItem*>(event->target()))
2543- , window(event->window())
2544- , timestamp(event->timestamp())
2545+void TouchGate::onEnabledChanged()
2546+{
2547+ ugDebug(" enabled = " << isEnabled());
2548+ if (!isEnabled()) {
2549+ reset();
2550+ }
2551+}
2552+
2553+void TouchGate::reset()
2554+{
2555+ m_storedEvents.clear();
2556+ m_touchInfoMap.clear();
2557+ m_dispatcher.reset();
2558+}
2559+
2560+TouchGate::TouchEvent::TouchEvent(QTouchDevice *device,
2561+ Qt::KeyboardModifiers modifiers,
2562+ const QList<QTouchEvent::TouchPoint> &touchPoints,
2563+ QWindow *window,
2564+ ulong timestamp)
2565+ : device(device)
2566+ , modifiers(modifiers)
2567+ , touchPoints(touchPoints)
2568+ , window(window)
2569+ , timestamp(timestamp)
2570 {
2571 }
2572
2573
2574=== modified file 'plugins/Ubuntu/Gestures/TouchGate.h'
2575--- plugins/Ubuntu/Gestures/TouchGate.h 2015-03-12 13:52:35 +0000
2576+++ plugins/Ubuntu/Gestures/TouchGate.h 2015-04-15 20:21:35 +0000
2577@@ -45,6 +45,8 @@
2578 Q_PROPERTY(QQuickItem* targetItem READ targetItem WRITE setTargetItem NOTIFY targetItemChanged)
2579
2580 public:
2581+ TouchGate(QQuickItem *parent = nullptr);
2582+
2583 bool event(QEvent *e) override;
2584
2585 QQuickItem *targetItem() { return m_dispatcher.targetItem(); }
2586@@ -55,31 +57,42 @@
2587
2588 protected:
2589 void touchEvent(QTouchEvent *event) override;
2590+
2591+private Q_SLOTS:
2592+ void onEnabledChanged();
2593+
2594 private:
2595+ void reset();
2596+
2597 class TouchEvent {
2598 public:
2599- TouchEvent(const QTouchEvent *event);
2600+ TouchEvent(QTouchDevice *device,
2601+ Qt::KeyboardModifiers modifiers,
2602+ const QList<QTouchEvent::TouchPoint> &touchPoints,
2603+ QWindow *window,
2604+ ulong timestamp);
2605
2606 bool removeTouch(int touchId);
2607
2608- QEvent::Type eventType;
2609 QTouchDevice *device;
2610 Qt::KeyboardModifiers modifiers;
2611 QList<QTouchEvent::TouchPoint> touchPoints;
2612- QQuickItem *target;
2613 QWindow *window;
2614 ulong timestamp;
2615 };
2616
2617 void touchOwnershipEvent(TouchOwnershipEvent *event);
2618 bool isTouchPointOwned(int touchId) const;
2619- void storeTouchEvent(const QTouchEvent *event);
2620+ void storeTouchEvent(QTouchDevice *device,
2621+ Qt::KeyboardModifiers modifiers,
2622+ const QList<QTouchEvent::TouchPoint> &touchPoints,
2623+ QWindow *window,
2624+ ulong timestamp);
2625 void removeTouchFromStoredEvents(int touchId);
2626 void dispatchFullyOwnedEvents();
2627 bool eventIsFullyOwned(const TouchEvent &event) const;
2628
2629 void dispatchTouchEventToTarget(const TouchEvent &event);
2630- void dispatchTouchEventToTarget(QTouchEvent* event);
2631
2632 void removeTouchInfoForEndedTouches(const QList<QTouchEvent::TouchPoint> &touchPoints);
2633
2634
2635=== modified file 'qml/Components/DragHandle.qml'
2636--- qml/Components/DragHandle.qml 2014-12-05 17:06:36 +0000
2637+++ qml/Components/DragHandle.qml 2015-04-15 20:21:35 +0000
2638@@ -43,16 +43,8 @@
2639 }
2640
2641 */
2642-EdgeDragArea {
2643+DirectionalDragArea {
2644 id: dragArea
2645- objectName: "dragHandle"
2646-
2647- // Disable gesture recognition by default when hinting is used as
2648- // it conflicts with the hinting idea.
2649- distanceThreshold: hintDisplacement > 0 ? 0 : defaultDistanceThreshold
2650- maxSilenceTime: hintDisplacement > 0 ? 60*60*1000 : defaultMaxSilenceTime
2651- maxDeviation: hintDisplacement > 0 ? 999999 : defaultMaxDeviation
2652- compositionTime: hintDisplacement > 0 ? 0 : defaultCompositionTime
2653
2654 property bool stretch: false
2655
2656@@ -68,6 +60,9 @@
2657 }
2658
2659 property real hintDisplacement: 0
2660+
2661+ immediateRecognition: hintDisplacement > 0
2662+
2663 property var overrideStartValue: undefined
2664 SmoothedAnimation {
2665 id: hintingAnimation
2666@@ -98,7 +93,6 @@
2667 // Private stuff
2668 QtObject {
2669 id: d
2670- property var previousStatus: DirectionalDragArea.WaitingForTouch
2671 property real startValue
2672 property real minValue: {
2673 if (direction == Direction.Horizontal) {
2674@@ -183,7 +177,7 @@
2675 }
2676
2677 onDistanceChanged: {
2678- if (status === DirectionalDragArea.Recognized) {
2679+ if (dragging) {
2680 // don't go the whole distance in order to smooth out the movement
2681 var step = distance * 0.3;
2682
2683@@ -193,31 +187,22 @@
2684 }
2685 }
2686
2687- onStatusChanged: {
2688- if (status === DirectionalDragArea.WaitingForTouch) {
2689+ onDraggingChanged: {
2690+ if (dragging) {
2691+ dragEvaluator.reset();
2692+ if (overrideStartValue !== undefined) {
2693+ d.startValue = overrideStartValue;
2694+ } else {
2695+ d.startValue = parent[d.targetProp];
2696+ }
2697+
2698+ if (hintDisplacement > 0) {
2699+ hintingAnimation.targetValue = d.startValue;
2700+ hintingAnimation.start();
2701+ }
2702+ } else {
2703 hintingAnimation.stop();
2704- if (d.previousStatus === DirectionalDragArea.Recognized) {
2705- d.onFinishedRecognizedGesture();
2706- } else /* d.previousStatus === DirectionalDragArea.Undecided */ {
2707- // Gesture was rejected.
2708- d.rollbackDrag();
2709- }
2710- } else /* Undecided || Recognized */ {
2711- if (d.previousStatus === DirectionalDragArea.WaitingForTouch) {
2712- dragEvaluator.reset();
2713- if (overrideStartValue !== undefined) {
2714- d.startValue = overrideStartValue;
2715- } else {
2716- d.startValue = parent[d.targetProp];
2717- }
2718-
2719- if (hintDisplacement > 0) {
2720- hintingAnimation.targetValue = d.startValue;
2721- hintingAnimation.start();
2722- }
2723- }
2724+ d.onFinishedRecognizedGesture();
2725 }
2726-
2727- d.previousStatus = status;
2728 }
2729 }
2730
2731=== removed file 'qml/Components/EdgeDragArea.qml'
2732--- qml/Components/EdgeDragArea.qml 2014-10-01 13:20:32 +0000
2733+++ qml/Components/EdgeDragArea.qml 1970-01-01 00:00:00 +0000
2734@@ -1,46 +0,0 @@
2735-/*
2736- * Copyright (C) 2013 Canonical, Ltd.
2737- *
2738- * This program is free software; you can redistribute it and/or modify
2739- * it under the terms of the GNU General Public License as published by
2740- * the Free Software Foundation; version 3.
2741- *
2742- * This program is distributed in the hope that it will be useful,
2743- * but WITHOUT ANY WARRANTY; without even the implied warranty of
2744- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2745- * GNU General Public License for more details.
2746- *
2747- * You should have received a copy of the GNU General Public License
2748- * along with this program. If not, see <http://www.gnu.org/licenses/>.
2749- */
2750-
2751-import QtQuick 2.0
2752-import Ubuntu.Components 0.1
2753-import Ubuntu.Gestures 0.1
2754-
2755-/*
2756- A DirectionalDragArea wrapper that provides some well-chosen defaults
2757- for its gesture recognition parameters.
2758-*/
2759-DirectionalDragArea {
2760-
2761- // TODO: Re-evaluate those or even the recognition heuristics itself once
2762- // we have gesture cancelling/forwarding in place.
2763- //
2764- // The idea here is that it's better having lax rules than false negatives.
2765- // False negatives are very frustrating to the user.
2766- maxDeviation: defaultMaxDeviation
2767- wideningAngle: defaultWideningAngle
2768- distanceThreshold: defaultDistanceThreshold
2769- minSpeed: defaultMinSpeed
2770- maxSilenceTime: defaultMaxSilenceTime
2771- compositionTime: defaultCompositionTime
2772-
2773- readonly property real defaultMaxDeviation: units.gu(3)
2774- readonly property real defaultWideningAngle: 50
2775- readonly property real defaultDistanceThreshold: units.gu(1.5)
2776- // some people were getting false negatives with it enabled.
2777- readonly property real defaultMinSpeed: units.gu(0)
2778- readonly property int defaultMaxSilenceTime: 200
2779- readonly property int defaultCompositionTime: 60
2780-}
2781
2782=== modified file 'qml/Dash/Dash.qml'
2783--- qml/Dash/Dash.qml 2015-04-09 14:00:37 +0000
2784+++ qml/Dash/Dash.qml 2015-04-15 20:21:35 +0000
2785@@ -153,7 +153,7 @@
2786 // (as expected) but would also cause the dash content flickable to move a bit, because
2787 // that flickable was getting the touch events while overviewDragHandle was still undecided
2788 // about whether that touch was indeed performing a directional drag gesture.
2789- forceNonInteractive: overviewDragHandle.status != DirectionalDragArea.WaitingForTouch
2790+ forceNonInteractive: overviewDragHandle.dragging
2791
2792 enabled: bottomEdgeController.progress == 0
2793 }
2794@@ -330,7 +330,7 @@
2795 }
2796 }
2797
2798- EdgeDragArea {
2799+ DirectionalDragArea {
2800 id: overviewDragHandle
2801 objectName: "overviewDragHandle"
2802 z: 1
2803@@ -346,21 +346,14 @@
2804 height: units.gu(2)
2805
2806 onSceneDistanceChanged: {
2807- if (status == DirectionalDragArea.Recognized) {
2808+ if (dragging) {
2809 bottomEdgeController.enableAnimation = false;
2810 bottomEdgeController.progress = Math.max(0, Math.min(1, sceneDistance / fullMovement));
2811 }
2812 }
2813
2814- property int previousStatus: -1
2815- property int currentStatus: DirectionalDragArea.WaitingForTouch
2816-
2817- onStatusChanged: {
2818- previousStatus = currentStatus;
2819- currentStatus = status;
2820-
2821- if (status == DirectionalDragArea.WaitingForTouch &&
2822- previousStatus == DirectionalDragArea.Recognized) {
2823+ onDraggingChanged: {
2824+ if (!dragging) {
2825 bottomEdgeController.enableAnimation = true;
2826 bottomEdgeController.progress = (bottomEdgeController.progress > 0.2) ? 1 : 0;
2827 }
2828
2829=== modified file 'qml/Greeter/CoverPage.qml'
2830--- qml/Greeter/CoverPage.qml 2015-02-17 15:54:17 +0000
2831+++ qml/Greeter/CoverPage.qml 2015-04-15 20:21:35 +0000
2832@@ -129,13 +129,14 @@
2833
2834 DragHandle {
2835 id: dragHandle
2836+ objectName: "coverPageDragHandle"
2837 anchors.fill: parent
2838 anchors.leftMargin: root.dragHandleLeftMargin
2839 enabled: root.draggable
2840 direction: Direction.Horizontal
2841
2842- onDraggingChanged: {
2843- if (dragging) {
2844+ onPressedChanged: {
2845+ if (pressed) {
2846 root.tease();
2847 showLabelAnimation.start();
2848 }
2849
2850=== modified file 'qml/Launcher/Launcher.qml'
2851--- qml/Launcher/Launcher.qml 2015-03-12 13:04:36 +0000
2852+++ qml/Launcher/Launcher.qml 2015-04-15 20:21:35 +0000
2853@@ -210,7 +210,7 @@
2854 bottom: parent.bottom
2855 }
2856 x: -width
2857- visible: root.x > 0 || x > -width || dragArea.status === DirectionalDragArea.Undecided
2858+ visible: root.x > 0 || x > -width || dragArea.pressed
2859 model: LauncherModel
2860
2861 property bool animate: true
2862@@ -272,7 +272,7 @@
2863 }
2864 }
2865
2866- EdgeDragArea {
2867+ DirectionalDragArea {
2868 id: dragArea
2869 objectName: "launcherDragArea"
2870
2871@@ -283,20 +283,11 @@
2872 width: root.dragAreaWidth
2873 height: root.height
2874
2875- onTouchXChanged: {
2876- if (status !== DirectionalDragArea.Recognized || launcher.state == "visible")
2877+ onDistanceChanged: {
2878+ if (!dragging || launcher.state == "visible")
2879 return;
2880
2881- // When the gesture finally gets recognized, the finger will likely be
2882- // reasonably far from the edge. If we made the panel immediately
2883- // follow the finger position it would be visually unpleasant as it
2884- // would appear right next to the user's finger out of nowhere.
2885- // Instead, we make the panel go towards the user's finger in several
2886- // steps. ie., in an animated way.
2887- var targetPanelX = Math.min(0, touchX - panel.width) - root.x
2888- var delta = targetPanelX - panel.x
2889- // the trick is not to go all the way (1.0) as it would cause a sudden jump
2890- panel.x += 0.4 * delta
2891+ panel.x = -panel.width + Math.min(Math.max(0, distance), panel.width);
2892 }
2893
2894 onDraggingChanged: {
2895@@ -306,7 +297,8 @@
2896 if (distance > minimizeDistance) {
2897 root.dash();
2898 }
2899- } else {
2900+ } else if (root.state === "") {
2901+ // didn't drag far enough. rollback
2902 root.switchToNextState("")
2903 }
2904 }
2905
2906=== modified file 'qml/Panel/IndicatorsMenu.qml'
2907--- qml/Panel/IndicatorsMenu.qml 2015-04-02 15:29:10 +0000
2908+++ qml/Panel/IndicatorsMenu.qml 2015-04-15 20:21:35 +0000
2909@@ -186,9 +186,18 @@
2910 enabled: !root.shown && root.available
2911 autoCompleteDragThreshold: maxTotalDragDistance / 2
2912 stretch: true
2913- distanceThreshold: enableHint ? 0 : minimizedPanelHeight
2914
2915- onTapped: showTapped(Qt.point(touchSceneX, touchSceneY));
2916+ onPressedChanged: {
2917+ if (pressed) {
2918+ touchPressTime = new Date().getTime();
2919+ } else {
2920+ var touchReleaseTime = new Date().getTime();
2921+ if (touchReleaseTime - touchPressTime <= 300) {
2922+ root.showTapped(Qt.point(touchSceneX, touchSceneY));
2923+ }
2924+ }
2925+ }
2926+ property var touchPressTime
2927
2928 // using hint regulates minimum to hint displacement, but in fullscreen mode, we need to do it manually.
2929 overrideStartValue: enableHint ? minimizedPanelHeight : expandedPanelHeight + handle.height
2930@@ -204,6 +213,7 @@
2931
2932 DragHandle {
2933 id: __hideDragHandle
2934+ objectName: "hideDragHandle"
2935 anchors.fill: handle
2936 direction: Direction.Upwards
2937 enabled: root.shown && root.available
2938
2939=== modified file 'qml/Stages/PhoneStage.qml'
2940--- qml/Stages/PhoneStage.qml 2015-02-18 18:29:03 +0000
2941+++ qml/Stages/PhoneStage.qml 2015-04-15 20:21:35 +0000
2942@@ -146,14 +146,13 @@
2943 id: spreadView
2944 objectName: "spreadView"
2945 anchors.fill: parent
2946- interactive: (spreadDragArea.status == DirectionalDragArea.Recognized || phase > 1)
2947- && draggedDelegateCount === 0
2948+ interactive: (spreadDragArea.dragging || phase > 1) && draggedDelegateCount === 0
2949 contentWidth: spreadRow.width - shift
2950 contentX: -shift
2951
2952 // This indicates when the spreadView is active. That means, all the animations
2953 // are activated and tiles need to line up for the spread.
2954- readonly property bool active: shiftedContentX > 0 || spreadDragArea.status === DirectionalDragArea.Recognized || !root.focusFirstApp
2955+ readonly property bool active: shiftedContentX > 0 || spreadDragArea.dragging || !root.focusFirstApp
2956
2957 // The flickable needs to fill the screen in order to get touch events all over.
2958 // However, we don't want to the user to be able to scroll back all the way. For
2959@@ -450,7 +449,7 @@
2960 }
2961 }
2962
2963- EdgeDragArea {
2964+ DirectionalDragArea {
2965 id: spreadDragArea
2966 objectName: "spreadDragArea"
2967 direction: Direction.Leftwards
2968@@ -462,59 +461,50 @@
2969 property var gesturePoints: new Array()
2970
2971 onTouchXChanged: {
2972- if (!dragging) {
2973- // Initial touch. Let's reset the spreadView to the starting position.
2974- spreadView.phase = 0;
2975- spreadView.contentX = -spreadView.shift;
2976- }
2977- if (dragging && status == DirectionalDragArea.Recognized) {
2978+ if (dragging) {
2979 // Gesture recognized. Let's move the spreadView with the finger
2980 var dragX = Math.min(touchX + width, width); // Prevent dragging rightwards
2981 dragX = -dragX + spreadDragArea.width - spreadView.shift;
2982 // Don't allow dragging further than the animation crossing with phase2's animation
2983 var maxMovement = spreadView.width * spreadView.positionMarker4 - spreadView.shift;
2984+
2985 spreadView.contentX = Math.min(dragX, maxMovement);
2986+ } else {
2987+ // Initial touch. Let's reset the spreadView to the starting position.
2988+ spreadView.phase = 0;
2989+ spreadView.contentX = -spreadView.shift;
2990 }
2991+
2992 gesturePoints.push(touchX);
2993 }
2994
2995- property int previousStatus: -1
2996- property int currentStatus: DirectionalDragArea.WaitingForTouch
2997-
2998- onStatusChanged: {
2999- previousStatus = currentStatus;
3000- currentStatus = status;
3001- }
3002-
3003 onDraggingChanged: {
3004 if (dragging) {
3005 // A potential edge-drag gesture has started. Start recording it
3006 gesturePoints = [];
3007- return;
3008- }
3009-
3010- // Ok. The user released. Find out if it was a one-way movement.
3011- var oneWayFlick = true;
3012- var smallestX = spreadDragArea.width;
3013- for (var i = 0; i < gesturePoints.length; i++) {
3014- if (gesturePoints[i] >= smallestX) {
3015- oneWayFlick = false;
3016- break;
3017- }
3018- smallestX = gesturePoints[i];
3019- }
3020- gesturePoints = [];
3021-
3022- if (previousStatus == DirectionalDragArea.Recognized &&
3023- oneWayFlick && spreadView.shiftedContentX > units.gu(2) &&
3024- spreadView.shiftedContentX < spreadView.positionMarker1 * spreadView.width) {
3025- // If it was a short one-way movement, do the Alt+Tab switch
3026- // no matter if we didn't cross positionMarker1 yet.
3027- spreadView.snapTo(1);
3028- } else if (!dragging) {
3029- // otherwise snap to the closest snap position we can find
3030- // (might be back to start, to app 1 or to spread)
3031- spreadView.snap();
3032+ } else {
3033+ // Ok. The user released. Find out if it was a one-way movement.
3034+ var oneWayFlick = true;
3035+ var smallestX = spreadDragArea.width;
3036+ for (var i = 0; i < gesturePoints.length; i++) {
3037+ if (gesturePoints[i] >= smallestX) {
3038+ oneWayFlick = false;
3039+ break;
3040+ }
3041+ smallestX = gesturePoints[i];
3042+ }
3043+ gesturePoints = [];
3044+
3045+ if (oneWayFlick && spreadView.shiftedContentX > units.gu(2) &&
3046+ spreadView.shiftedContentX < spreadView.positionMarker1 * spreadView.width) {
3047+ // If it was a short one-way movement, do the Alt+Tab switch
3048+ // no matter if we didn't cross positionMarker1 yet.
3049+ spreadView.snapTo(1);
3050+ } else if (!dragging) {
3051+ // otherwise snap to the closest snap position we can find
3052+ // (might be back to start, to app 1 or to spread)
3053+ spreadView.snap();
3054+ }
3055 }
3056 }
3057 }
3058
3059=== modified file 'qml/Stages/TabletStage.qml'
3060--- qml/Stages/TabletStage.qml 2015-02-02 16:28:03 +0000
3061+++ qml/Stages/TabletStage.qml 2015-04-15 20:21:35 +0000
3062@@ -170,8 +170,7 @@
3063 Flickable {
3064 id: spreadView
3065 anchors.fill: parent
3066- interactive: (spreadDragArea.status == DirectionalDragArea.Recognized || phase > 1)
3067- && draggedDelegateCount === 0
3068+ interactive: (spreadDragArea.dragging || phase > 1) && draggedDelegateCount === 0
3069 contentWidth: spreadRow.width - shift
3070 contentX: -shift
3071
3072@@ -598,7 +597,7 @@
3073 }
3074 }
3075
3076- EdgeDragArea {
3077+ DirectionalDragArea {
3078 id: spreadDragArea
3079 anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
3080 width: root.dragAreaWidth
3081@@ -624,26 +623,25 @@
3082 if (dragging) {
3083 // Gesture recognized. Start recording this gesture
3084 gesturePoints = [];
3085- return;
3086- }
3087-
3088- // Ok. The user released. Find out if it was a one-way movement.
3089- var oneWayFlick = priv.evaluateOneWayFlick(gesturePoints);
3090- gesturePoints = [];
3091-
3092- if (oneWayFlick && spreadView.shiftedContentX < spreadView.positionMarker1 * spreadView.width) {
3093- // If it was a short one-way movement, do the Alt+Tab switch
3094- // no matter if we didn't cross positionMarker1 yet.
3095- spreadView.snapTo(spreadView.nextInStack);
3096- } else if (!dragging) {
3097- if (spreadView.shiftedContentX < spreadView.width * spreadView.positionMarker1) {
3098- spreadView.snap();
3099- } else if (spreadView.shiftedContentX < spreadView.width * spreadView.positionMarker2) {
3100+ } else {
3101+ // Ok. The user released. Find out if it was a one-way movement.
3102+ var oneWayFlick = priv.evaluateOneWayFlick(gesturePoints);
3103+ gesturePoints = [];
3104+
3105+ if (oneWayFlick && spreadView.shiftedContentX < spreadView.positionMarker1 * spreadView.width) {
3106+ // If it was a short one-way movement, do the Alt+Tab switch
3107+ // no matter if we didn't cross positionMarker1 yet.
3108 spreadView.snapTo(spreadView.nextInStack);
3109 } else {
3110- // otherwise snap to the closest snap position we can find
3111- // (might be back to start, to app 1 or to spread)
3112- spreadView.snap();
3113+ if (spreadView.shiftedContentX < spreadView.width * spreadView.positionMarker1) {
3114+ spreadView.snap();
3115+ } else if (spreadView.shiftedContentX < spreadView.width * spreadView.positionMarker2) {
3116+ spreadView.snapTo(spreadView.nextInStack);
3117+ } else {
3118+ // otherwise snap to the closest snap position we can find
3119+ // (might be back to start, to app 1 or to spread)
3120+ spreadView.snap();
3121+ }
3122 }
3123 }
3124 }
3125
3126=== modified file 'qml/Tutorial/TutorialBottom.qml'
3127--- qml/Tutorial/TutorialBottom.qml 2015-02-01 22:39:25 +0000
3128+++ qml/Tutorial/TutorialBottom.qml 2015-04-15 20:21:35 +0000
3129@@ -73,7 +73,7 @@
3130 ]
3131 }
3132
3133- EdgeDragArea {
3134+ DirectionalDragArea {
3135 id: dragArea
3136 direction: Direction.Upwards
3137 anchors {
3138
3139=== modified file 'tests/libs/UbuntuGestures/tst_TouchRegistry.cpp'
3140--- tests/libs/UbuntuGestures/tst_TouchRegistry.cpp 2014-10-01 13:20:32 +0000
3141+++ tests/libs/UbuntuGestures/tst_TouchRegistry.cpp 2015-04-15 20:21:35 +0000
3142@@ -1,5 +1,5 @@
3143 /*
3144- * Copyright (C) 2014 Canonical, Ltd.
3145+ * Copyright (C) 2014-2015 Canonical, Ltd.
3146 *
3147 * This program is free software; you can redistribute it and/or modify
3148 * it under the terms of the GNU General Public License as published by
3149@@ -66,6 +66,7 @@
3150 void candidatesAndWatchers_2();
3151 void rejectingTouchfterItsEnd();
3152 void removeOldUndecidedCandidates();
3153+ void interimOwnerWontGetUnownedTouchEvents();
3154 };
3155
3156 void tst_TouchRegistry::requestWithNoCandidates()
3157@@ -742,7 +743,7 @@
3158
3159 // Simulate that enough time has passed to cause the CandidateInactivityTimer to timeout,
3160 // making TouchRegistry consider that undecidedCantidate defaulted.
3161- fakeTimerFactory->makeRunningTimersTimeout();
3162+ fakeTimerFactory->updateTime(10000);
3163
3164 QVERIFY(undecidedCandidate.ownedTouches.isEmpty());
3165 QVERIFY(undecidedCandidate.lostTouches.contains(0));
3166@@ -752,6 +753,71 @@
3167 delete touchRegistry;
3168 }
3169
3170+/*
3171+ An item that calls requestTouchOwnership() without first having called addCandidateOwnerForTouch()
3172+ is assumed to be the interim owner of the touch point, thus there's no point in sending
3173+ UnownedTouchEvents to him as he already gets proper QTouchEvents from QQuickWindow because
3174+ he didn't ignore the first QTouchEvent with that touch point.
3175+ */
3176+void tst_TouchRegistry::interimOwnerWontGetUnownedTouchEvents()
3177+{
3178+ FakeTimerFactory *fakeTimerFactory = new FakeTimerFactory;
3179+ TouchRegistry *touchRegistry = new TouchRegistry(nullptr, fakeTimerFactory);
3180+
3181+ DummyCandidate undecidedCandidate;
3182+ undecidedCandidate.setObjectName("undecided");
3183+
3184+ DummyCandidate interimOwner;
3185+ interimOwner.setObjectName("interimOwner");
3186+
3187+ {
3188+ QList<QTouchEvent::TouchPoint> touchPoints;
3189+ touchPoints.append(QTouchEvent::TouchPoint(0));
3190+ touchPoints[0].setState(Qt::TouchPointPressed);
3191+ QTouchEvent touchEvent(QEvent::TouchBegin,
3192+ 0 /* device */,
3193+ Qt::NoModifier,
3194+ Qt::TouchPointPressed,
3195+ touchPoints);
3196+ touchRegistry->update(&touchEvent);
3197+ }
3198+
3199+ touchRegistry->addCandidateOwnerForTouch(0, &undecidedCandidate);
3200+ touchRegistry->requestTouchOwnership(0, &interimOwner);
3201+
3202+ {
3203+ QList<QTouchEvent::TouchPoint> touchPoints;
3204+ touchPoints.append(QTouchEvent::TouchPoint(0));
3205+ touchPoints[0].setState(Qt::TouchPointMoved);
3206+ QTouchEvent touchEvent(QEvent::TouchUpdate,
3207+ 0 /* device */,
3208+ Qt::NoModifier,
3209+ Qt::TouchPointMoved,
3210+ touchPoints);
3211+ touchRegistry->update(&touchEvent);
3212+ }
3213+
3214+ QCOMPARE(undecidedCandidate.unownedTouchEvents.count(), 1);
3215+ QCOMPARE(interimOwner.unownedTouchEvents.count(), 0);
3216+
3217+ {
3218+ QList<QTouchEvent::TouchPoint> touchPoints;
3219+ touchPoints.append(QTouchEvent::TouchPoint(0));
3220+ touchPoints[0].setState(Qt::TouchPointMoved);
3221+ QTouchEvent touchEvent(QEvent::TouchUpdate,
3222+ 0 /* device */,
3223+ Qt::NoModifier,
3224+ Qt::TouchPointMoved,
3225+ touchPoints);
3226+ touchRegistry->update(&touchEvent);
3227+ }
3228+
3229+ QCOMPARE(undecidedCandidate.unownedTouchEvents.count(), 2);
3230+ QCOMPARE(interimOwner.unownedTouchEvents.count(), 0);
3231+
3232+ delete touchRegistry;
3233+}
3234+
3235 ////////////// TouchMemento //////////
3236
3237 TouchMemento::TouchMemento(const QTouchEvent *touchEvent)
3238
3239=== modified file 'tests/plugins/Ubuntu/Gestures/CMakeLists.txt'
3240--- tests/plugins/Ubuntu/Gestures/CMakeLists.txt 2014-10-17 11:01:53 +0000
3241+++ tests/plugins/Ubuntu/Gestures/CMakeLists.txt 2015-04-15 20:21:35 +0000
3242@@ -19,6 +19,7 @@
3243 )
3244
3245 add_definitions(-DUBUNTU_GESTURES_PLUGIN_DIR="${CMAKE_BINARY_DIR}/plugins")
3246+add_definitions(-DTESTS_UTILS_MODULES_DIR="${CMAKE_BINARY_DIR}/tests/utils/modules")
3247
3248 macro(add_gesture_ui_test CLASSNAME)
3249 add_executable(${CLASSNAME}TestExec tst_${CLASSNAME}.cpp GestureTest.cpp)
3250@@ -26,7 +27,8 @@
3251 target_link_libraries(${CLASSNAME}TestExec UbuntuGesturesQml UbuntuGestures)
3252
3253 add_binary_qml_test(${CLASSNAME} ${CMAKE_BINARY_DIR}/plugins/Ubuntu/Gestures UbuntuGesturesTestQmlFiles "")
3254- add_manual_qml_test(. ${CLASSNAME} IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins)
3255+ add_manual_qml_test(. ${CLASSNAME} IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins
3256+ ${CMAKE_BINARY_DIR}/tests/utils/modules)
3257 endmacro(add_gesture_ui_test)
3258
3259 macro(add_gesture_test CLASSNAME)
3260
3261=== modified file 'tests/plugins/Ubuntu/Gestures/DownwardsLauncher.qml'
3262--- tests/plugins/Ubuntu/Gestures/DownwardsLauncher.qml 2014-02-11 20:21:24 +0000
3263+++ tests/plugins/Ubuntu/Gestures/DownwardsLauncher.qml 2015-04-15 20:21:35 +0000
3264@@ -37,8 +37,8 @@
3265
3266 Rectangle {
3267 id: dragAreaRect
3268- color: "yellow"
3269- opacity: 0.0
3270+ opacity: dragArea.dragging ? 0.5 : 0.0
3271+ color: "green"
3272 anchors.fill: dragArea
3273 }
3274
3275@@ -49,24 +49,10 @@
3276 height: units.gu(5)
3277
3278 direction: Direction.Downwards
3279- maxDeviation: units.gu(2)
3280- wideningAngle: 10
3281- distanceThreshold: units.gu(4)
3282
3283- onStatusChanged: {
3284- switch (status) {
3285- case DirectionalDragArea.WaitingForTouch:
3286- dragAreaRect.opacity = 0.0
3287- break;
3288- case DirectionalDragArea.Undecided:
3289- dragAreaRect.color = "yellow"
3290- dragAreaRect.opacity = 0.3
3291- launcher.y = Qt.binding(launcher.followDragArea)
3292- break;
3293- default: // DirectionalDragArea.Recognized:
3294- dragAreaRect.color = "green"
3295- dragAreaRect.opacity = 0.5
3296- break;
3297+ onDraggingChanged: {
3298+ if (dragging) {
3299+ launcher.y = Qt.binding(launcher.followDragArea)
3300 }
3301 }
3302
3303
3304=== modified file 'tests/plugins/Ubuntu/Gestures/GestureTest.cpp'
3305--- tests/plugins/Ubuntu/Gestures/GestureTest.cpp 2014-11-03 15:21:46 +0000
3306+++ tests/plugins/Ubuntu/Gestures/GestureTest.cpp 2015-04-15 20:21:35 +0000
3307@@ -1,5 +1,5 @@
3308 /*
3309- * Copyright (C) 2013 Canonical, Ltd.
3310+ * Copyright (C) 2013,2015 Canonical, Ltd.
3311 *
3312 * This program is free software; you can redistribute it and/or modify
3313 * it under the terms of the GNU General Public License as published by
3314@@ -21,8 +21,11 @@
3315 #include <QQuickView>
3316 #include <QtTest>
3317
3318+#include <Timer.h>
3319 #include <TouchRegistry.h>
3320
3321+using namespace UbuntuGestures;
3322+
3323 GestureTest::GestureTest(const QString &qmlFilename)
3324 : QObject(), m_device(nullptr), m_view(nullptr), m_qmlFilename(qmlFilename)
3325 {
3326@@ -40,14 +43,17 @@
3327 void GestureTest::init()
3328 {
3329 m_view = new QQuickView;
3330- m_view->setResizeMode(QQuickView::SizeViewToRootObject);
3331+ m_view->setResizeMode(QQuickView::SizeRootObjectToView);
3332 m_view->engine()->addImportPath(QStringLiteral(UBUNTU_GESTURES_PLUGIN_DIR));
3333+ m_view->engine()->addImportPath(QStringLiteral(TESTS_UTILS_MODULES_DIR));
3334 m_view->setSource(QUrl::fromLocalFile(m_qmlFilename));
3335 m_view->show();
3336 QVERIFY(QTest::qWaitForWindowExposed(m_view));
3337 QVERIFY(m_view->rootObject() != 0);
3338
3339- m_touchRegistry = new TouchRegistry;
3340+ m_fakeTimerFactory = new FakeTimerFactory;
3341+
3342+ m_touchRegistry = new TouchRegistry(nullptr, m_fakeTimerFactory);
3343 m_view->installEventFilter(m_touchRegistry);
3344
3345 qApp->processEvents();
3346@@ -59,6 +65,10 @@
3347 delete m_touchRegistry;
3348 m_touchRegistry = nullptr;
3349
3350+ // TouchRegistry will take down the timer factory along with him
3351+ // delete m_fakeTimerFactory;
3352+ m_fakeTimerFactory = nullptr;
3353+
3354 delete m_view;
3355 m_view = nullptr;
3356 }
3357
3358=== modified file 'tests/plugins/Ubuntu/Gestures/GestureTest.h'
3359--- tests/plugins/Ubuntu/Gestures/GestureTest.h 2014-11-03 15:21:46 +0000
3360+++ tests/plugins/Ubuntu/Gestures/GestureTest.h 2015-04-15 20:21:35 +0000
3361@@ -23,6 +23,9 @@
3362 class QQuickView;
3363 class QTouchDevice;
3364
3365+namespace UbuntuGestures {
3366+ class FakeTimerFactory;
3367+}
3368 class TouchRegistry;
3369
3370 // C++ std lib
3371@@ -81,6 +84,7 @@
3372 QTouchDevice *m_device;
3373 QQuickView *m_view;
3374 TouchRegistry *m_touchRegistry;
3375+ UbuntuGestures::FakeTimerFactory *m_fakeTimerFactory;
3376 QString m_qmlFilename;
3377 };
3378
3379
3380=== modified file 'tests/plugins/Ubuntu/Gestures/LeftwardsLauncher.qml'
3381--- tests/plugins/Ubuntu/Gestures/LeftwardsLauncher.qml 2014-02-11 20:21:24 +0000
3382+++ tests/plugins/Ubuntu/Gestures/LeftwardsLauncher.qml 2015-04-15 20:21:35 +0000
3383@@ -21,7 +21,7 @@
3384 Item {
3385 id: root
3386
3387- function reset() { launcher.x = root.width }
3388+ function reset() { launcher.x = Qt.binding(function(){return root.width;}); }
3389
3390 Rectangle {
3391 id: launcher
3392@@ -41,7 +41,8 @@
3393
3394 Rectangle {
3395 id: dragAreaRect
3396- opacity: 0.0
3397+ opacity: dragArea.dragging ? 0.5 : 0.0
3398+ color: "green"
3399 anchors.fill: dragArea
3400 }
3401
3402@@ -52,24 +53,10 @@
3403 width: units.gu(5)
3404
3405 direction: Direction.Leftwards
3406- maxDeviation: units.gu(2)
3407- wideningAngle: 10
3408- distanceThreshold: units.gu(4)
3409
3410- onStatusChanged: {
3411- switch (status) {
3412- case DirectionalDragArea.WaitingForTouch:
3413- dragAreaRect.opacity = 0.0
3414- break;
3415- case DirectionalDragArea.Undecided:
3416- dragAreaRect.color = "yellow"
3417- dragAreaRect.opacity = 0.3
3418- launcher.x = Qt.binding(launcher.followDragArea)
3419- break;
3420- default: //case DirectionalDragArea.Recognized:
3421- dragAreaRect.color = "green"
3422- dragAreaRect.opacity = 0.5
3423- break;
3424+ onDraggingChanged: {
3425+ if (dragging) {
3426+ launcher.x = Qt.binding(launcher.followDragArea)
3427 }
3428 }
3429
3430
3431=== modified file 'tests/plugins/Ubuntu/Gestures/RightwardsLauncher.qml'
3432--- tests/plugins/Ubuntu/Gestures/RightwardsLauncher.qml 2014-10-01 13:20:32 +0000
3433+++ tests/plugins/Ubuntu/Gestures/RightwardsLauncher.qml 2015-04-15 20:21:35 +0000
3434@@ -38,7 +38,8 @@
3435
3436 Rectangle {
3437 id: dragAreaRect
3438- opacity: 0.0
3439+ opacity: dragArea.dragging ? 0.5 : 0.0
3440+ color: "green"
3441 anchors.fill: dragArea
3442 }
3443
3444@@ -52,25 +53,10 @@
3445 width: units.gu(5)
3446
3447 direction: Direction.Rightwards
3448- maxDeviation: units.gu(2)
3449- wideningAngle: 10
3450- distanceThreshold: units.gu(4)
3451- minSpeed: 50
3452
3453- onStatusChanged: {
3454- switch (status) {
3455- case DirectionalDragArea.WaitingForTouch:
3456- dragAreaRect.opacity = 0.0
3457- break;
3458- case DirectionalDragArea.Undecided:
3459- dragAreaRect.color = "yellow"
3460- dragAreaRect.opacity = 0.3
3461- launcher.x = Qt.binding(launcher.followDragArea)
3462- break;
3463- default: //case DirectionalDragArea.Recognized:
3464- dragAreaRect.color = "green"
3465- dragAreaRect.opacity = 0.5
3466- break;
3467+ onDraggingChanged: {
3468+ if (dragging) {
3469+ launcher.x = Qt.binding(launcher.followDragArea)
3470 }
3471 }
3472
3473
3474=== modified file 'tests/plugins/Ubuntu/Gestures/UpwardsLauncher.qml'
3475--- tests/plugins/Ubuntu/Gestures/UpwardsLauncher.qml 2014-02-11 20:21:24 +0000
3476+++ tests/plugins/Ubuntu/Gestures/UpwardsLauncher.qml 2015-04-15 20:21:35 +0000
3477@@ -21,7 +21,7 @@
3478 Item {
3479 id: root
3480
3481- function reset() { launcher.y = root.height }
3482+ function reset() { launcher.y = Qt.binding(function(){return root.height;}); }
3483
3484 Rectangle {
3485 id: launcher
3486@@ -41,7 +41,8 @@
3487
3488 Rectangle {
3489 id: dragAreaRect
3490- opacity: 0.0
3491+ opacity: dragArea.dragging ? 0.5 : 0.0
3492+ color: "green"
3493 anchors.fill: dragArea
3494 }
3495
3496@@ -52,24 +53,10 @@
3497 height: units.gu(5)
3498
3499 direction: Direction.Upwards
3500- maxDeviation: units.gu(2)
3501- wideningAngle: 10
3502- distanceThreshold: units.gu(4)
3503
3504- onStatusChanged: {
3505- switch (status) {
3506- case DirectionalDragArea.WaitingForTouch:
3507- dragAreaRect.opacity = 0.0
3508- break;
3509- case DirectionalDragArea.Undecided:
3510- dragAreaRect.color = "yellow"
3511- dragAreaRect.opacity = 0.3
3512- launcher.y = Qt.binding(launcher.followDragArea)
3513- break;
3514- default: //case DirectionalDragArea.Recognized:
3515- dragAreaRect.color = "green"
3516- dragAreaRect.opacity = 0.5
3517- break;
3518+ onDraggingChanged: {
3519+ if (dragging) {
3520+ launcher.y = Qt.binding(launcher.followDragArea)
3521 }
3522 }
3523
3524
3525=== modified file 'tests/plugins/Ubuntu/Gestures/tst_DirectionalDragArea.cpp'
3526--- tests/plugins/Ubuntu/Gestures/tst_DirectionalDragArea.cpp 2015-02-18 13:51:39 +0000
3527+++ tests/plugins/Ubuntu/Gestures/tst_DirectionalDragArea.cpp 2015-04-15 20:21:35 +0000
3528@@ -1,5 +1,5 @@
3529 /*
3530- * Copyright (C) 2013-2014 Canonical, Ltd.
3531+ * Copyright (C) 2013-2015 Canonical, Ltd.
3532 *
3533 * This program is free software; you can redistribute it and/or modify
3534 * it under the terms of the GNU General Public License as published by
3535@@ -20,51 +20,38 @@
3536 #include <QtQml/QQmlEngine>
3537 #include <QPointer>
3538 #include <private/qquickmousearea_p.h>
3539-#if (QT_VERSION >= QT_VERSION_CHECK(5, 4, 0))
3540- #include <private/qquickwindow_p.h>
3541-#endif
3542+#include <private/qquickwindow_p.h>
3543
3544
3545 #include <DirectionalDragArea.h>
3546+#include <DirectionalDragArea_p.h>
3547 #include <TouchRegistry.h>
3548
3549 #include "GestureTest.h"
3550
3551 using namespace UbuntuGestures;
3552
3553-
3554-class ComplexFakeTimer : public FakeTimer
3555-{
3556+// Because QSignalSpy(directionalDragArea, SIGNAL(DirectionalDragArea::Status)) simply
3557+// doesn't work
3558+class StatusSpy : public QObject {
3559 Q_OBJECT
3560 public:
3561- ComplexFakeTimer(const SharedTimeSource &timeSource, QObject *parent = 0)
3562- : FakeTimer(parent),
3563- m_timeSource(timeSource)
3564- {}
3565-
3566- void start() override {
3567- AbstractTimer::start();
3568- m_nextTimeoutTime = m_timeSource->msecsSinceReference() + (qint64)interval();
3569- }
3570-
3571- void emitTimeout() {
3572- m_nextTimeoutTime += interval();
3573- Q_EMIT timeout();
3574- }
3575-
3576- qint64 nextTimeoutTime() const { return m_nextTimeoutTime; }
3577+ StatusSpy(DirectionalDragArea *edgeDragArea) {
3578+ m_recognized = false;
3579+ connect(edgeDragArea->d, &DirectionalDragAreaPrivate::statusChanged,
3580+ this, &StatusSpy::onStatusChanged);
3581+ }
3582+ bool recognized() {
3583+ return m_recognized;
3584+ }
3585+
3586+private Q_SLOTS:
3587+ void onStatusChanged(DirectionalDragAreaPrivate::Status status) {
3588+ m_recognized |= status == DirectionalDragAreaPrivate::Recognized;
3589+ }
3590
3591 private:
3592- SharedTimeSource m_timeSource;
3593- qint64 m_nextTimeoutTime;
3594-};
3595-
3596-class FakeTimeSource : public UbuntuGestures::TimeSource
3597-{
3598-public:
3599- FakeTimeSource() { m_msecsSinceReference = 0; }
3600- virtual qint64 msecsSinceReference() {return m_msecsSinceReference;}
3601- qint64 m_msecsSinceReference;
3602+ bool m_recognized;
3603 };
3604
3605 /*
3606@@ -98,15 +85,9 @@
3607 tst_DirectionalDragArea();
3608 private Q_SLOTS:
3609 void init(); // called right before each and every test function is executed
3610- void cleanup(); // called right after each and every test function is executed
3611
3612- void edgeDrag();
3613- void edgeDrag_data();
3614 void dragWithShortDirectionChange();
3615- void minSpeed();
3616- void minSpeed_data();
3617 void recognitionTimerUsage();
3618- void maxSilenceTime();
3619 void sceneXAndX();
3620 void sceneYAndY();
3621 void twoFingerTap();
3622@@ -122,14 +103,24 @@
3623 void immediateRecognitionWhenConstraintsDisabled();
3624 void withdrawTouchOwnershipCandidacyIfDisabledDuringRecognition();
3625 void withdrawTouchOwnershipCandidacyIfDisabledDuringRecognition_data();
3626- void tappedSignal();
3627- void tappedSignal_data();
3628 void gettingTouchOwnershipMakesMouseAreaBehindGetCanceled();
3629+ void interleavedTouches();
3630+ void makoRightEdgeDrag();
3631+ void makoRightEdgeDrag_verticalDownwards();
3632+ void makoLeftEdgeDrag_slowStart();
3633+ void makoLeftEdgeDrag_movesSlightlyBackwardsOnStart();
3634
3635 private:
3636+ // QTest::touchEvent takes QPoint instead of QPointF and I don't want to
3637+ // lose precision due to rounding.
3638+ // Besides, those helper functions lead to more compact code.
3639+ void sendTouchPress(qint64 timestamp, int id, QPointF pos);
3640+ void sendTouchUpdate(qint64 timestamp, int id, QPointF pos);
3641+ void sendTouchRelease(qint64 timestamp, int id, QPointF pos);
3642+ void sendTouch(qint64 timestamp, int id, QPointF pos,
3643+ Qt::TouchPointState pointState, QEvent::Type eventType);
3644+
3645 void passTime(qint64 timeSpanMs);
3646- ComplexFakeTimer *fakeTimer;
3647- QSharedPointer<FakeTimeSource> fakeTimeSource;
3648 };
3649
3650 tst_DirectionalDragArea::tst_DirectionalDragArea()
3651@@ -148,36 +139,49 @@
3652 m_view->resize(m_view->rootObject()->width(), m_view->rootObject()->height());
3653 QTRY_COMPARE(m_view->width(), (int)m_view->rootObject()->width());
3654 QTRY_COMPARE(m_view->height(), (int)m_view->rootObject()->height());
3655-
3656- fakeTimeSource.reset(new FakeTimeSource);
3657- fakeTimer = new ComplexFakeTimer(fakeTimeSource);
3658-}
3659-
3660-void tst_DirectionalDragArea::cleanup()
3661-{
3662- delete fakeTimer;
3663- fakeTimer = 0;
3664-
3665- fakeTimeSource.reset();
3666-
3667- GestureTest::cleanup();
3668+}
3669+
3670+void tst_DirectionalDragArea::sendTouchPress(qint64 timestamp, int id, QPointF pos)
3671+{
3672+ sendTouch(timestamp, id, pos, Qt::TouchPointPressed, QEvent::TouchBegin);
3673+}
3674+
3675+void tst_DirectionalDragArea::sendTouchUpdate(qint64 timestamp, int id, QPointF pos)
3676+{
3677+ sendTouch(timestamp, id, pos, Qt::TouchPointMoved, QEvent::TouchUpdate);
3678+}
3679+
3680+void tst_DirectionalDragArea::sendTouchRelease(qint64 timestamp, int id, QPointF pos)
3681+{
3682+ sendTouch(timestamp, id, pos, Qt::TouchPointReleased, QEvent::TouchEnd);
3683+}
3684+
3685+void tst_DirectionalDragArea::sendTouch(qint64 timestamp, int id, QPointF pos,
3686+ Qt::TouchPointState pointState, QEvent::Type eventType)
3687+{
3688+ m_fakeTimerFactory->updateTime(timestamp);
3689+
3690+ QTouchEvent::TouchPoint point;
3691+
3692+ point.setState(pointState);
3693+ point.setId(id);
3694+ point.setScenePos(pos);
3695+ point.setPos(pos);
3696+
3697+ QList<QTouchEvent::TouchPoint> points;
3698+ points << point;
3699+
3700+ QTouchEvent touchEvent(eventType, m_device, Qt::NoModifier, Qt::TouchPointPressed, points);
3701+ QCoreApplication::sendEvent(m_view, &touchEvent);
3702+
3703+ QQuickWindowPrivate *windowPrivate = QQuickWindowPrivate::get(m_view);
3704+ windowPrivate->flushDelayedTouchEvent();
3705 }
3706
3707 void tst_DirectionalDragArea::passTime(qint64 timeSpanMs)
3708 {
3709- qint64 finalTime = fakeTimeSource->m_msecsSinceReference + timeSpanMs;
3710-
3711- if (fakeTimer->isRunning() && finalTime >= fakeTimer->nextTimeoutTime()) {
3712- fakeTimeSource->m_msecsSinceReference = fakeTimer->nextTimeoutTime();
3713- fakeTimer->emitTimeout();
3714-
3715- qint64 timeSpanRemainder = finalTime - fakeTimeSource->m_msecsSinceReference;
3716- if (timeSpanRemainder > 0) {
3717- passTime(timeSpanRemainder);
3718- }
3719- } else {
3720- fakeTimeSource->m_msecsSinceReference = finalTime;
3721- }
3722+ qint64 finalTime = m_fakeTimerFactory->timeSource()->msecsSinceReference() + timeSpanMs;
3723+ m_fakeTimerFactory->updateTime(finalTime);
3724 }
3725
3726 namespace {
3727@@ -186,147 +190,6 @@
3728 QPointF localCenter(edgeDragArea->width() / 2., edgeDragArea->height() / 2.);
3729 return edgeDragArea->mapToScene(localCenter);
3730 }
3731-
3732-QPointF calculateDirectionVector(DirectionalDragArea *edgeDragArea,
3733- qreal wideningAngleMultiplier)
3734-{
3735- qreal angleRadians = edgeDragArea->wideningAngle() * wideningAngleMultiplier
3736- * M_PI / 180.0;
3737-
3738- qreal angleCos = qCos(angleRadians);
3739- qreal angleSin = qSin(angleRadians);
3740-
3741- switch (edgeDragArea->direction()) {
3742- case Direction::Upwards:
3743- return QPointF(angleSin, -angleCos);
3744- case Direction::Downwards:
3745- return QPointF(angleSin, angleCos);
3746- case Direction::Leftwards:
3747- return QPointF(-angleCos, angleSin);
3748- default: // Direction::Rightwards:
3749- return QPointF(angleCos, angleSin);
3750- }
3751-}
3752-
3753-QPointF createTouchDeviation(DirectionalDragArea *edgeDragArea)
3754-{
3755- qreal deviation = edgeDragArea->maxDeviation() * 0.8;
3756-
3757- if (Direction::isHorizontal(edgeDragArea->direction())) {
3758- return QPointF(0, deviation);
3759- } else {
3760- return QPointF(deviation, 0);
3761- }
3762-}
3763-}
3764-
3765-void tst_DirectionalDragArea::edgeDrag()
3766-{
3767- QFETCH(QString, dragAreaObjectName);
3768- QFETCH(qreal, wideningAngleMultiplier);
3769- QFETCH(qreal, dragDistanceFactor);
3770- QFETCH(bool, expectGestureRecognition);
3771-
3772- DirectionalDragArea *edgeDragArea =
3773- m_view->rootObject()->findChild<DirectionalDragArea*>(dragAreaObjectName);
3774- QVERIFY(edgeDragArea != 0);
3775- edgeDragArea->setRecognitionTimer(fakeTimer);
3776- edgeDragArea->setTimeSource(fakeTimeSource);
3777-
3778- QSignalSpy draggingSpy(edgeDragArea, SIGNAL(draggingChanged(bool)));
3779-
3780- QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
3781- QPointF touchPoint = initialTouchPos;
3782-
3783- qreal desiredDragDistance = edgeDragArea->distanceThreshold()*dragDistanceFactor;
3784- QPointF dragDirectionVector = calculateDirectionVector(edgeDragArea,
3785- wideningAngleMultiplier);
3786- qreal movementStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
3787- QPointF touchMovement = dragDirectionVector * movementStepDistance;
3788- int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
3789- int movementTimeStepMs = (edgeDragArea->compositionTime() * 1.5f) / totalMovementSteps;
3790-
3791- QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
3792-
3793- QCOMPARE(draggingSpy.count(), 1);
3794- QCOMPARE(edgeDragArea->dragging(), true);
3795-
3796- if (wideningAngleMultiplier > 0) {
3797- // go close to the border of the valid area for this touch point
3798- // in order to make it easier to leave it by dragging at an angle
3799- // slightly bigger than the widening angle
3800- touchPoint += createTouchDeviation(edgeDragArea);
3801- QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
3802- }
3803-
3804- for (int i = 0; i < totalMovementSteps; ++i) {
3805- touchPoint += touchMovement;
3806- passTime(movementTimeStepMs);
3807- QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
3808- }
3809-
3810- if (expectGestureRecognition)
3811- QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
3812-
3813- if (edgeDragArea->status() == DirectionalDragArea::WaitingForTouch) {
3814- QCOMPARE(edgeDragArea->dragging(), false);
3815- QCOMPARE(draggingSpy.count(), 2);
3816- }
3817-
3818- QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
3819-
3820- QCOMPARE(draggingSpy.count(), 2);
3821- QCOMPARE(edgeDragArea->dragging(), false);
3822-}
3823-
3824-void tst_DirectionalDragArea::edgeDrag_data()
3825-{
3826- QTest::addColumn<QString>("dragAreaObjectName");
3827- QTest::addColumn<qreal>("wideningAngleMultiplier");
3828- QTest::addColumn<qreal>("dragDistanceFactor");
3829- QTest::addColumn<bool>("expectGestureRecognition");
3830-
3831- QTest::newRow("rightwards, tiny drag")
3832- << "hpDragArea" << 0.0 << 0.2 << false;
3833-
3834- QTest::newRow("rightwards, straight drag")
3835- << "hpDragArea" << 0.0 << 3.0 << true;
3836-
3837- QTest::newRow("rightwards, diagonal drag")
3838- << "hpDragArea" << 0.9 << 3.0 << true;
3839-
3840- QTest::newRow("rightwards, overly diagonal drag")
3841- << "hpDragArea" << 2.0 << 3.0 << false;
3842-
3843- QTest::newRow("leftwards, tiny drag")
3844- << "hnDragArea" << 0.0 << 0.2 << false;
3845-
3846- QTest::newRow("leftwards, straight drag")
3847- << "hnDragArea" << 0.0 << 3.0 << true;
3848-
3849- QTest::newRow("leftwards, diagonal drag")
3850- << "hnDragArea" << 0.9 << 3.0 << true;
3851-
3852- QTest::newRow("downwards, tiny drag")
3853- << "vpDragArea" << 0.0 << 0.2 << false;
3854-
3855- QTest::newRow("downwards, straight drag")
3856- << "vpDragArea" << 0.0 << 3.0 << true;
3857-
3858- QTest::newRow("downwards, diagonal drag")
3859- << "vpDragArea" << 0.9 << 3.0 << true;
3860-
3861- QTest::newRow("upwards, tiny drag")
3862- << "vnDragArea" << 0.0 << 0.2 << false;
3863-
3864- QTest::newRow("upwards, straight drag")
3865- << "vnDragArea" << 0.0 << 3.0 << true;
3866-
3867- QTest::newRow("upwards, diagonal drag")
3868- << "vnDragArea" << 0.9 << 3.0 << true;
3869-
3870- QTest::newRow("upwards, overly diagonal drag")
3871- << "vnDragArea" << 2.0 << 3.0 << false;
3872 }
3873
3874 /*
3875@@ -339,17 +202,17 @@
3876 DirectionalDragArea *edgeDragArea =
3877 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
3878 QVERIFY(edgeDragArea != 0);
3879- edgeDragArea->setRecognitionTimer(fakeTimer);
3880- edgeDragArea->setTimeSource(fakeTimeSource);
3881+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
3882+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
3883
3884 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
3885 QPointF touchPoint = initialTouchPos;
3886
3887- qreal desiredDragDistance = edgeDragArea->distanceThreshold()*2.0;
3888+ qreal desiredDragDistance = edgeDragArea->d->distanceThreshold * 2.0;
3889 QPointF dragDirectionVector(1.0, 0.0);
3890- qreal touchStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
3891- // make sure we are above minimum speed
3892- int touchStepTimeMs = (touchStepDistance / (edgeDragArea->minSpeed() * 5.0f)) * 1000.0f;
3893+ qreal touchStepDistance = edgeDragArea->d->distanceThreshold * 0.1f;
3894+ // make sure we are above maximum time
3895+ int touchStepTimeMs = edgeDragArea->d->maxTime / 20. ;
3896 QPointF touchMovement = dragDirectionVector * touchStepDistance;
3897
3898 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
3899@@ -373,66 +236,14 @@
3900 passTime(touchStepTimeMs);
3901 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
3902 } while ((touchPoint - initialTouchPos).manhattanLength() < desiredDragDistance
3903- || fakeTimeSource->m_msecsSinceReference < (edgeDragArea->compositionTime() * 1.5f));
3904+ || m_fakeTimerFactory->timeSource()->msecsSinceReference() < (edgeDragArea->d->compositionTime * 1.5f));
3905
3906- QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
3907+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Recognized);
3908
3909 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
3910 }
3911
3912 /*
3913- Checks that a gesture will be rejected if it's slower than minSpeed while
3914- status is Undecided.
3915- */
3916-void tst_DirectionalDragArea::minSpeed()
3917-{
3918- QFETCH(qreal, minSpeed);
3919- QFETCH(qreal, speed);
3920- QFETCH(int, expectedStatusAfterSpeedCheck);
3921-
3922- DirectionalDragArea *edgeDragArea =
3923- m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
3924- QVERIFY(edgeDragArea != 0);
3925- edgeDragArea->setRecognitionTimer(fakeTimer);
3926- edgeDragArea->setTimeSource(fakeTimeSource);
3927-
3928- // A really long, unattainable, number. We don't want it getting recognized before
3929- // the speed checks we want have been performed
3930- edgeDragArea->setDistanceThreshold(500000);
3931-
3932- edgeDragArea->setMinSpeed(minSpeed);
3933-
3934- QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
3935- QPointF touchPoint = initialTouchPos;
3936-
3937- QPointF dragDirectionVector(1.0, 0.0);
3938- qint64 timeStepMsecs = 10;
3939- qreal distanceStep = (speed / 1000.0f) * timeStepMsecs;
3940- QPointF touchMovement = dragDirectionVector * distanceStep;
3941-
3942- QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
3943-
3944- // Move for a while to ensure our speed check is performed a couple of times
3945- for (int i=0; i < 20; ++i) {
3946- touchPoint += touchMovement;
3947- passTime(timeStepMsecs);
3948- QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
3949- }
3950-
3951- QCOMPARE((int)edgeDragArea->status(), expectedStatusAfterSpeedCheck);
3952-}
3953-
3954-void tst_DirectionalDragArea::minSpeed_data()
3955-{
3956- QTest::addColumn<qreal>("minSpeed");
3957- QTest::addColumn<qreal>("speed");
3958- QTest::addColumn<int>("expectedStatusAfterSpeedCheck");
3959-
3960- QTest::newRow("slower than minSpeed") << 100.0 << 50.0 << (int)DirectionalDragArea::WaitingForTouch;
3961- QTest::newRow("faster than minSpeed") << 100.0 << 150.0 << (int)DirectionalDragArea::Undecided;
3962-}
3963-
3964-/*
3965 Checks that the recognition timer is started and stopped appropriately.
3966 I.e., check that it's running only while gesture recognition is taking place
3967 (status == Undecided)
3968@@ -442,11 +253,9 @@
3969 DirectionalDragArea *edgeDragArea =
3970 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
3971 QVERIFY(edgeDragArea != 0);
3972- edgeDragArea->setRecognitionTimer(fakeTimer);
3973- edgeDragArea->setTimeSource(fakeTimeSource);
3974-
3975- // don't let it interfere with our test
3976- edgeDragArea->setMinSpeed(0.0);
3977+ AbstractTimer *fakeTimer = m_fakeTimerFactory->createTimer();
3978+ edgeDragArea->d->setRecognitionTimer(fakeTimer);
3979+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
3980
3981 int timeStepMs = 5; // some arbitrary small value.
3982
3983@@ -454,63 +263,32 @@
3984 QPointF touchPoint = initialTouchPos;
3985
3986 QPointF dragDirectionVector(1.0, 0.0);
3987- QPointF touchMovement = dragDirectionVector * (edgeDragArea->distanceThreshold() * 0.2f);
3988+ QPointF touchMovement = dragDirectionVector * (edgeDragArea->d->distanceThreshold * 0.2f);
3989
3990- QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
3991+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
3992 QVERIFY(!fakeTimer->isRunning());
3993
3994 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
3995
3996- QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
3997+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Undecided);
3998 QVERIFY(fakeTimer->isRunning());
3999
4000 // Move beyond distance threshold and composition time to ensure recognition
4001- while (fakeTimeSource->m_msecsSinceReference <= edgeDragArea->compositionTime() ||
4002- (touchPoint - initialTouchPos).manhattanLength() <= edgeDragArea->distanceThreshold()) {
4003+ while (m_fakeTimerFactory->timeSource()->msecsSinceReference() <= edgeDragArea->d->compositionTime ||
4004+ (touchPoint - initialTouchPos).manhattanLength() <= edgeDragArea->d->distanceThreshold) {
4005
4006- QCOMPARE(edgeDragArea->status() == DirectionalDragArea::Undecided, fakeTimer->isRunning());
4007+ QCOMPARE(edgeDragArea->d->status == DirectionalDragAreaPrivate::Undecided, fakeTimer->isRunning());
4008
4009 touchPoint += touchMovement;
4010 passTime(timeStepMs);
4011 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
4012 }
4013
4014- QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
4015+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Recognized);
4016 QVERIFY(!fakeTimer->isRunning());
4017 }
4018
4019 /*
4020- A gesture should be rejected if too much time has passed without any new input
4021- events from it.
4022- */
4023-void tst_DirectionalDragArea::maxSilenceTime()
4024-{
4025- DirectionalDragArea *edgeDragArea =
4026- m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
4027- QVERIFY(edgeDragArea != 0);
4028- edgeDragArea->setRecognitionTimer(fakeTimer);
4029- edgeDragArea->setTimeSource(fakeTimeSource);
4030-
4031- // Make sure this property is not disabled
4032- edgeDragArea->setMaxSilenceTime(100);
4033-
4034- QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
4035- QPointF touchPoint = initialTouchPos;
4036-
4037- QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
4038-
4039- QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
4040- QVERIFY(fakeTimer->isRunning());
4041-
4042- // Force timer to timeout until after maxSilenceTime has been reached
4043- while (fakeTimeSource->m_msecsSinceReference < edgeDragArea->maxSilenceTime()) {
4044- passTime(fakeTimer->interval());
4045- }
4046-
4047- QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
4048-}
4049-
4050-/*
4051 Checks that it informs the X coordinate of the touch point in local and scene coordinates
4052 correctly.
4053 */
4054@@ -519,18 +297,19 @@
4055 DirectionalDragArea *edgeDragArea =
4056 m_view->rootObject()->findChild<DirectionalDragArea*>("hnDragArea");
4057 QVERIFY(edgeDragArea != 0);
4058- edgeDragArea->setRecognitionTimer(fakeTimer);
4059- edgeDragArea->setTimeSource(fakeTimeSource);
4060+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
4061+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
4062+ edgeDragArea->setImmediateRecognition(true);
4063
4064 QPointF touchScenePos(m_view->width() - (edgeDragArea->width()/2.0f), m_view->height()/2.0f);
4065
4066- QTest::touchEvent(m_view, m_device).press(0, touchScenePos.toPoint());
4067+ sendTouchPress(0 /* timestamp */, 0 /* id */, touchScenePos);
4068
4069 QSignalSpy touchXSpy(edgeDragArea, SIGNAL(touchXChanged(qreal)));
4070 QSignalSpy touchSceneXSpy(edgeDragArea, SIGNAL(touchSceneXChanged(qreal)));
4071
4072 touchScenePos.rx() = m_view->width() / 2;
4073- QTest::touchEvent(m_view, m_device).move(0, touchScenePos.toPoint());
4074+ sendTouchUpdate(50 /* timestamp */, 0 /* id */, touchScenePos);
4075
4076 QCOMPARE(touchXSpy.count(), 1);
4077 QCOMPARE(touchSceneXSpy.count(), 1);
4078@@ -547,18 +326,19 @@
4079 DirectionalDragArea *edgeDragArea =
4080 m_view->rootObject()->findChild<DirectionalDragArea*>("vnDragArea");
4081 QVERIFY(edgeDragArea != 0);
4082- edgeDragArea->setRecognitionTimer(fakeTimer);
4083- edgeDragArea->setTimeSource(fakeTimeSource);
4084+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
4085+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
4086+ edgeDragArea->setImmediateRecognition(true);
4087
4088 QPointF touchScenePos(m_view->width()/2.0f, m_view->height() - (edgeDragArea->height()/2.0f));
4089
4090- QTest::touchEvent(m_view, m_device).press(0, touchScenePos.toPoint());
4091+ sendTouchPress(0 /* timestamp */, 0 /* id */, touchScenePos);
4092
4093 QSignalSpy touchYSpy(edgeDragArea, SIGNAL(touchYChanged(qreal)));
4094 QSignalSpy touchSceneYSpy(edgeDragArea, SIGNAL(touchSceneYChanged(qreal)));
4095
4096 touchScenePos.ry() = m_view->height() / 2;
4097- QTest::touchEvent(m_view, m_device).move(0, touchScenePos.toPoint());
4098+ sendTouchUpdate(50 /* timestamp */, 0 /* id */, touchScenePos);
4099
4100 QCOMPARE(touchYSpy.count(), 1);
4101 QCOMPARE(touchSceneYSpy.count(), 1);
4102@@ -577,8 +357,8 @@
4103 DirectionalDragArea *edgeDragArea =
4104 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
4105 QVERIFY(edgeDragArea != 0);
4106- edgeDragArea->setRecognitionTimer(fakeTimer);
4107- edgeDragArea->setTimeSource(fakeTimeSource);
4108+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
4109+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
4110
4111 // Make touches evenly spaced along the edgeDragArea
4112 QPoint touchAPos(edgeDragArea->width()/2.0f, m_view->height()*0.33f);
4113@@ -592,7 +372,7 @@
4114 QTest::touchEvent(m_view, m_device)
4115 .press(0, touchAPos);
4116
4117- QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
4118+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Undecided);
4119
4120 passTime(timeStepMsecs);
4121 QTest::touchEvent(m_view, m_device)
4122@@ -601,20 +381,20 @@
4123
4124 // A second touch point appeared during recognition, reject immediately as this
4125 // can't be a single-touch gesture anymore.
4126- QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
4127+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
4128
4129 passTime(timeStepMsecs);
4130 QTest::touchEvent(m_view, m_device)
4131 .release(0, touchAPos)
4132 .move(1, touchBPos);
4133
4134- QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
4135+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
4136
4137 passTime(timeStepMsecs);
4138 QTest::touchEvent(m_view, m_device)
4139 .release(1, touchBPos);
4140
4141- QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
4142+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
4143
4144 // Perform the second two-finger tap
4145
4146@@ -622,27 +402,27 @@
4147 QTest::touchEvent(m_view, m_device)
4148 .press(0, touchAPos);
4149
4150- QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
4151+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Undecided);
4152
4153 passTime(timeStepMsecs);
4154 QTest::touchEvent(m_view, m_device)
4155 .move(0, touchAPos)
4156 .press(1, touchBPos);
4157
4158- QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
4159+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
4160
4161 passTime(timeStepMsecs);
4162 QTest::touchEvent(m_view, m_device)
4163 .release(0, touchAPos)
4164 .move(1, touchBPos);
4165
4166- QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
4167+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
4168
4169 passTime(timeStepMsecs);
4170 QTest::touchEvent(m_view, m_device)
4171 .release(1, touchBPos);
4172
4173- QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
4174+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
4175 }
4176
4177 /*
4178@@ -659,25 +439,25 @@
4179 DirectionalDragArea *edgeDragArea =
4180 rightwardsLauncher->findChild<DirectionalDragArea*>("hpDragArea");
4181 Q_ASSERT(edgeDragArea != 0);
4182- edgeDragArea->setRecognitionTimer(fakeTimer);
4183- edgeDragArea->setTimeSource(fakeTimeSource);
4184+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
4185+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
4186
4187 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
4188 QPointF touchPoint = initialTouchPos;
4189
4190- qreal desiredDragDistance = edgeDragArea->distanceThreshold()*2.0f;
4191+ qreal desiredDragDistance = edgeDragArea->d->distanceThreshold * 2.0f;
4192 QPointF dragDirectionVector(1.0f, 0.0f);
4193
4194- qreal movementStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
4195+ qreal movementStepDistance = edgeDragArea->d->distanceThreshold * 0.1f;
4196 QPointF touchMovement = dragDirectionVector * movementStepDistance;
4197 int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
4198- int movementTimeStepMs = (edgeDragArea->compositionTime() * 1.5f) / totalMovementSteps;
4199+ int movementTimeStepMs = (edgeDragArea->d->compositionTime * 1.5f) / totalMovementSteps;
4200
4201 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
4202
4203 // Move it far ahead along the direction of the gesture
4204 // rightwardsLauncher is a parent of our DirectionalDragArea. So moving it will move our DDA
4205- rightwardsLauncher->setX(rightwardsLauncher->x() + edgeDragArea->distanceThreshold() * 5.0f);
4206+ rightwardsLauncher->setX(rightwardsLauncher->x() + edgeDragArea->d->distanceThreshold * 5.0f);
4207
4208 for (int i = 0; i < totalMovementSteps; ++i) {
4209 touchPoint += touchMovement;
4210@@ -685,7 +465,7 @@
4211 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
4212 }
4213
4214- QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
4215+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Recognized);
4216
4217 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
4218 }
4219@@ -699,8 +479,8 @@
4220 DirectionalDragArea *edgeDragArea =
4221 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
4222 Q_ASSERT(edgeDragArea != 0);
4223- edgeDragArea->setRecognitionTimer(fakeTimer);
4224- edgeDragArea->setTimeSource(fakeTimeSource);
4225+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
4226+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
4227
4228 // Make touches evenly spaced along the edgeDragArea
4229 QPoint touch0Pos(edgeDragArea->width()/2.0f, m_view->height()*0.33f);
4230@@ -708,26 +488,26 @@
4231
4232 QTest::touchEvent(m_view, m_device).press(0, touch0Pos);
4233
4234- QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
4235+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Undecided);
4236
4237 // leave it lying around for some time
4238- passTime(qMax(edgeDragArea->maxSilenceTime(), edgeDragArea->compositionTime()) * 10);
4239-
4240- QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
4241-
4242- qreal desiredDragDistance = edgeDragArea->distanceThreshold()*2.0f;
4243+ passTime(edgeDragArea->d->maxTime * 10);
4244+
4245+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
4246+
4247+ qreal desiredDragDistance = edgeDragArea->d->distanceThreshold * 2.0f;
4248 QPointF dragDirectionVector(1.0f, 0.0f);
4249
4250- qreal movementStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
4251+ qreal movementStepDistance = edgeDragArea->d->distanceThreshold * 0.1f;
4252 QPointF touchMovement = dragDirectionVector * movementStepDistance;
4253 int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
4254- int movementTimeStepMs = (edgeDragArea->compositionTime() * 1.5f) / totalMovementSteps;
4255+ int movementTimeStepMs = (edgeDragArea->d->compositionTime * 1.5f) / totalMovementSteps;
4256
4257 QTest::touchEvent(m_view, m_device)
4258 .move(0, touch0Pos)
4259 .press(1, touch1Pos);
4260
4261- QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
4262+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Undecided);
4263
4264 for (int i = 0; i < totalMovementSteps; ++i) {
4265 touch1Pos += touchMovement.toPoint();
4266@@ -737,13 +517,13 @@
4267 .move(1, touch1Pos);
4268 }
4269
4270- QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
4271+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Recognized);
4272
4273 QTest::touchEvent(m_view, m_device)
4274 .move(0, touch0Pos)
4275 .release(1, touch1Pos);
4276
4277- QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
4278+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
4279 }
4280
4281 /*
4282@@ -762,19 +542,19 @@
4283 DirectionalDragArea *edgeDragArea =
4284 rightwardsLauncher->findChild<DirectionalDragArea*>("hpDragArea");
4285 Q_ASSERT(edgeDragArea != 0);
4286- edgeDragArea->setRecognitionTimer(fakeTimer);
4287- edgeDragArea->setTimeSource(fakeTimeSource);
4288+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
4289+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
4290
4291 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
4292 QPointF touchPoint = initialTouchPos;
4293
4294- qreal desiredDragDistance = edgeDragArea->distanceThreshold()*2.0f;
4295+ qreal desiredDragDistance = edgeDragArea->d->distanceThreshold * 2.0f;
4296 QPointF dragDirectionVector(0.0f, 1.0f);
4297
4298- qreal movementStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
4299+ qreal movementStepDistance = edgeDragArea->d->distanceThreshold * 0.1f;
4300 QPointF touchMovement = dragDirectionVector * movementStepDistance;
4301 int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
4302- int movementTimeStepMs = (edgeDragArea->compositionTime() * 1.5f) / totalMovementSteps;
4303+ int movementTimeStepMs = (edgeDragArea->d->compositionTime * 1.5f) / totalMovementSteps;
4304
4305 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
4306
4307@@ -784,7 +564,7 @@
4308 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
4309 }
4310
4311- QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
4312+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Recognized);
4313
4314 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
4315 }
4316@@ -802,31 +582,30 @@
4317 DirectionalDragArea *edgeDragArea =
4318 rightwardsLauncher->findChild<DirectionalDragArea*>("hpDragArea");
4319 Q_ASSERT(edgeDragArea != 0);
4320- edgeDragArea->setRecognitionTimer(fakeTimer);
4321- edgeDragArea->setTimeSource(fakeTimeSource);
4322+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
4323+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
4324+
4325+ // to disable the position smoothing so that we can more easily check sceneDistance values
4326+ edgeDragArea->setImmediateRecognition(true);
4327
4328 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
4329 QPointF touchPoint = initialTouchPos;
4330
4331- qreal desiredDragDistance = edgeDragArea->distanceThreshold()*2.0f;
4332+ qreal desiredDragDistance = edgeDragArea->d->distanceThreshold * 2.0f;
4333
4334- qreal movementStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
4335+ qreal movementStepDistance = edgeDragArea->d->distanceThreshold * 0.1f;
4336 QPointF touchMovement = dragDirectionVector * movementStepDistance;
4337 int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
4338- int movementTimeStepMs = (edgeDragArea->compositionTime() * 1.5f) / totalMovementSteps;
4339-
4340- QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
4341-
4342-#if (QT_VERSION >= QT_VERSION_CHECK(5, 4, 0))
4343- QQuickWindowPrivate *wp = QQuickWindowPrivate::get(m_view);
4344-#endif
4345+ int movementTimeStepMs = (edgeDragArea->d->compositionTime * 1.5f) / totalMovementSteps;
4346+
4347+ qint64 timestamp = 0;
4348+
4349+ sendTouchPress(timestamp, 0, touchPoint);
4350+
4351 for (int i = 0; i < totalMovementSteps; ++i) {
4352 touchPoint += touchMovement;
4353- passTime(movementTimeStepMs);
4354- QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
4355-#if (QT_VERSION >= QT_VERSION_CHECK(5, 4, 0))
4356- wp->flushDelayedTouchEvent();
4357-#endif
4358+ timestamp += movementTimeStepMs;
4359+ sendTouchUpdate(timestamp, 0, touchPoint);
4360 }
4361
4362 qreal actualDragDistance = ((qreal)totalMovementSteps) * movementStepDistance;
4363@@ -836,7 +615,8 @@
4364 // NB: qFuzzyCompare(), used internally by QCOMPARE(), is broken.
4365 QVERIFY(qAbs(edgeDragArea->sceneDistance() - actualDragDistance) < 0.001);
4366
4367- QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
4368+ timestamp += movementTimeStepMs;
4369+ sendTouchRelease(timestamp, 0, touchPoint);
4370 }
4371
4372 void tst_DirectionalDragArea::sceneDistance_data()
4373@@ -860,18 +640,18 @@
4374 DirectionalDragArea *edgeDragArea =
4375 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
4376 Q_ASSERT(edgeDragArea != 0);
4377- edgeDragArea->setRecognitionTimer(fakeTimer);
4378- edgeDragArea->setTimeSource(fakeTimeSource);
4379+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
4380+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
4381
4382 QPointF touchPoint = calculateInitialTouchPos(edgeDragArea);
4383
4384- qreal desiredDragDistance = edgeDragArea->distanceThreshold()*2.0f;
4385+ qreal desiredDragDistance = edgeDragArea->d->distanceThreshold * 2.0f;
4386 QPointF dragDirectionVector(1., 0.); // horizontal positive
4387
4388- qreal movementStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
4389+ qreal movementStepDistance = edgeDragArea->d->distanceThreshold * 0.1f;
4390 QPointF touchMovement = dragDirectionVector * movementStepDistance;
4391 int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
4392- int movementTimeStepMs = (edgeDragArea->compositionTime() * 1.5f) / totalMovementSteps;
4393+ int movementTimeStepMs = (edgeDragArea->d->compositionTime * 1.5f) / totalMovementSteps;
4394
4395 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
4396
4397@@ -881,13 +661,13 @@
4398 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
4399 }
4400
4401- QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
4402+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Recognized);
4403 QCOMPARE(edgeDragArea->dragging(), true);
4404
4405 // disable the dragArea while it's being dragged.
4406 edgeDragArea->setEnabled(false);
4407
4408- QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
4409+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
4410 QCOMPARE(edgeDragArea->dragging(), false);
4411
4412 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
4413@@ -898,15 +678,14 @@
4414 DirectionalDragArea *edgeDragArea =
4415 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
4416 Q_ASSERT(edgeDragArea != 0);
4417- edgeDragArea->setRecognitionTimer(fakeTimer);
4418- edgeDragArea->setTimeSource(fakeTimeSource);
4419+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
4420+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
4421
4422 // Disable some constraints we're not interested in
4423- edgeDragArea->setMaxSilenceTime(60 * 1000);
4424- edgeDragArea->setMinSpeed(0);
4425+ edgeDragArea->d->setMaxTime(60 * 1000);
4426
4427 // And ensure others have the values we want
4428- edgeDragArea->setCompositionTime(60);
4429+ edgeDragArea->d->compositionTime = 60;
4430
4431 // Put an item right behind edgeDragArea to receive the touches ignored by it
4432 DummyItem *dummyItem = new DummyItem;
4433@@ -923,10 +702,10 @@
4434
4435 QTest::touchEvent(m_view, m_device).press(0, touch0Pos);
4436
4437- QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
4438+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Undecided);
4439
4440 // We are now going to be way beyond compositionTime
4441- passTime(edgeDragArea->compositionTime()*3);
4442+ passTime(edgeDragArea->d->compositionTime*3);
4443
4444 QTest::touchEvent(m_view, m_device)
4445 .move(0, touch0Pos)
4446@@ -948,7 +727,7 @@
4447 .move(0, touch0Pos)
4448 .move(1, touch1Pos);
4449
4450- QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
4451+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Undecided);
4452
4453 passTime(5);
4454
4455@@ -956,7 +735,7 @@
4456 .release(0, touch0Pos)
4457 .move(1, touch1Pos);
4458
4459- QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
4460+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
4461
4462 passTime(5);
4463
4464@@ -964,7 +743,7 @@
4465 .release(1, touch1Pos);
4466
4467 // Shouldn't be keepping info about touches that no longer exist or interest us
4468- QVERIFY(edgeDragArea->m_activeTouches.isEmpty());
4469+ QVERIFY(edgeDragArea->d->activeTouches.isEmpty());
4470
4471 delete dummyItem;
4472 }
4473@@ -974,12 +753,11 @@
4474 DirectionalDragArea *edgeDragArea =
4475 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
4476 Q_ASSERT(edgeDragArea != 0);
4477- edgeDragArea->setRecognitionTimer(fakeTimer);
4478- edgeDragArea->setTimeSource(fakeTimeSource);
4479+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
4480+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
4481
4482 // Disable some constraints we're not interested in
4483- edgeDragArea->setMaxSilenceTime(60 * 1000);
4484- edgeDragArea->setMinSpeed(0);
4485+ edgeDragArea->d->setMaxTime(60 * 1000);
4486
4487 // Put an item right in front of edgeDragArea
4488 DummyItem *dummyItem = new DummyItem(edgeDragArea->parentItem());
4489@@ -998,11 +776,11 @@
4490
4491 QTest::touchEvent(m_view, m_device).press(0, touchPos);
4492
4493- QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
4494+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Undecided);
4495
4496 m_touchRegistry->requestTouchOwnership(0, dummyItem);
4497
4498- QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
4499+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
4500
4501 dummyItem->grabTouchPoints({0});
4502 dummyItem->touchEventHandler = [&](QTouchEvent *event) { event->accept(); };
4503@@ -1010,9 +788,9 @@
4504 passTime(5);
4505 QTest::touchEvent(m_view, m_device).release(0, touchPos);
4506
4507- QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
4508+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
4509
4510- QVERIFY(edgeDragArea->m_activeTouches.isEmpty());
4511+ QVERIFY(edgeDragArea->d->activeTouches.isEmpty());
4512 }
4513
4514 void tst_DirectionalDragArea::threeFingerDrag()
4515@@ -1020,15 +798,14 @@
4516 DirectionalDragArea *edgeDragArea =
4517 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
4518 Q_ASSERT(edgeDragArea != 0);
4519- edgeDragArea->setRecognitionTimer(fakeTimer);
4520- edgeDragArea->setTimeSource(fakeTimeSource);
4521+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
4522+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
4523
4524 // Disable some constraints we're not interested in
4525- edgeDragArea->setMaxSilenceTime(60 * 1000);
4526- edgeDragArea->setMinSpeed(0);
4527+ edgeDragArea->d->setMaxTime(60 * 1000);
4528
4529 // And ensure others have the values we want
4530- edgeDragArea->setCompositionTime(60);
4531+ edgeDragArea->d->compositionTime = 60;
4532
4533 // Make touches evenly spaced along the edgeDragArea
4534 QPoint touch0Pos(edgeDragArea->width()/2.0f, m_view->height()*0.25f);
4535@@ -1038,14 +815,14 @@
4536 QTest::touchEvent(m_view, m_device)
4537 .press(0, touch0Pos);
4538
4539- QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
4540+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Undecided);
4541
4542 passTime(5);
4543 QTest::touchEvent(m_view, m_device)
4544 .move(0, touch0Pos)
4545 .press(1, touch1Pos);
4546
4547- QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
4548+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
4549
4550 passTime(5);
4551 QTest::touchEvent(m_view, m_device)
4552@@ -1053,7 +830,7 @@
4553 .move(1, touch1Pos)
4554 .press(2, touch2Pos);
4555
4556- QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
4557+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
4558
4559 passTime(10);
4560 QTest::touchEvent(m_view, m_device)
4561@@ -1077,7 +854,7 @@
4562 .release(0, touch0Pos);
4563
4564 // Shouldn't be keepping info about touches that no longer exist or interest us
4565- QVERIFY(edgeDragArea->m_activeTouches.isEmpty());
4566+ QVERIFY(edgeDragArea->d->activeTouches.isEmpty());
4567 }
4568
4569 /*
4570@@ -1090,12 +867,12 @@
4571 DirectionalDragArea *edgeDragArea =
4572 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
4573 Q_ASSERT(edgeDragArea != 0);
4574- edgeDragArea->setRecognitionTimer(fakeTimer);
4575- edgeDragArea->setTimeSource(fakeTimeSource);
4576+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
4577+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
4578
4579 // Disable the minimum amount of constraints to ensure immediate recognition
4580- edgeDragArea->setDistanceThreshold(0);
4581- edgeDragArea->setCompositionTime(0);
4582+ edgeDragArea->d->setDistanceThreshold(0);
4583+ edgeDragArea->d->compositionTime = 0;
4584
4585 // Put an item right behind edgeDragArea to receive the touches ignored by it
4586 DummyItem *dummyItem = new DummyItem;
4587@@ -1111,7 +888,7 @@
4588 QTest::touchEvent(m_view, m_device).press(0, touch0Pos);
4589
4590 // check for immediate recognition
4591- QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
4592+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Recognized);
4593
4594 // and therefore it should have immediately grabbed the touch point,
4595 // not letting it leak to items behind him.
4596@@ -1123,19 +900,19 @@
4597 DirectionalDragArea *edgeDragArea =
4598 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
4599 Q_ASSERT(edgeDragArea != 0);
4600- edgeDragArea->setRecognitionTimer(fakeTimer);
4601- edgeDragArea->setTimeSource(fakeTimeSource);
4602+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
4603+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
4604
4605 QPointF touchPoint = calculateInitialTouchPos(edgeDragArea);
4606
4607 // Move less than the minimum needed for the drag gesture recognition
4608- qreal desiredDragDistance = edgeDragArea->distanceThreshold()*0.5f;
4609+ qreal desiredDragDistance = edgeDragArea->d->distanceThreshold * 0.5f;
4610 QPointF dragDirectionVector(1., 0.); // horizontal positive
4611
4612- qreal movementStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
4613+ qreal movementStepDistance = edgeDragArea->d->distanceThreshold * 0.1f;
4614 QPointF touchMovement = dragDirectionVector * movementStepDistance;
4615 int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
4616- int movementTimeStepMs = (edgeDragArea->compositionTime() * 0.8f) / totalMovementSteps;
4617+ int movementTimeStepMs = (edgeDragArea->d->compositionTime * 0.8f) / totalMovementSteps;
4618
4619 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
4620
4621@@ -1145,14 +922,14 @@
4622 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
4623 }
4624
4625- QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
4626+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Undecided);
4627
4628 // edgeDragArea should be an undecided candidate
4629 {
4630 auto touchInfo = m_touchRegistry->findTouchInfo(0);
4631 QCOMPARE(touchInfo->candidates.size(), 1);
4632 QCOMPARE(touchInfo->candidates.at(0).item.data(), edgeDragArea);
4633- QCOMPARE(touchInfo->candidates.at(0).undecided, true);
4634+ QCOMPARE(touchInfo->candidates.at(0).state, TouchRegistry::CandidateInfo::Undecided);
4635 }
4636
4637 // disable the dragArea while it's still recognizing a possible drag gesture.
4638@@ -1169,7 +946,7 @@
4639 QCOMPARE(touchInfo->candidates.size(), 0);
4640 }
4641
4642- QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
4643+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
4644
4645 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
4646 }
4647@@ -1182,47 +959,13 @@
4648 QTest::newRow("invisible") << false;
4649 }
4650
4651-void tst_DirectionalDragArea::tappedSignal()
4652-{
4653- DirectionalDragArea *edgeDragArea =
4654- m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
4655- QVERIFY(edgeDragArea != 0);
4656- edgeDragArea->setRecognitionTimer(fakeTimer);
4657- edgeDragArea->setTimeSource(fakeTimeSource);
4658-
4659- QFETCH(bool, immediateGestureRecognition);
4660- if (immediateGestureRecognition) {
4661- // Disable the minimum amount of constraints to ensure immediate recognition
4662- edgeDragArea->setDistanceThreshold(0);
4663- edgeDragArea->setCompositionTime(0);
4664- }
4665-
4666- QPoint touch0Pos(edgeDragArea->width()/2.0f, m_view->height()/2.0f);
4667-
4668- QSignalSpy tappedSpy(edgeDragArea, SIGNAL(tapped()));
4669-
4670- QTest::touchEvent(m_view, m_device).press(0, touch0Pos);
4671- passTime(edgeDragArea->maxTapDuration() / 2);
4672- QTest::touchEvent(m_view, m_device).release(0, touch0Pos);
4673-
4674- QCOMPARE(tappedSpy.count(), 1);
4675-}
4676-
4677-void tst_DirectionalDragArea::tappedSignal_data()
4678-{
4679- QTest::addColumn<bool>("immediateGestureRecognition");
4680-
4681- QTest::newRow("immediate gesture recognition") << true;
4682- QTest::newRow("default gesture recognition") << false;
4683-}
4684-
4685 void tst_DirectionalDragArea::gettingTouchOwnershipMakesMouseAreaBehindGetCanceled()
4686 {
4687 DirectionalDragArea *edgeDragArea =
4688 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
4689 QVERIFY(edgeDragArea != nullptr);
4690- edgeDragArea->setRecognitionTimer(fakeTimer);
4691- edgeDragArea->setTimeSource(fakeTimeSource);
4692+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
4693+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
4694
4695 QQuickMouseArea *mouseArea =
4696 m_view->rootObject()->findChild<QQuickMouseArea*>("mouseArea");
4697@@ -1233,12 +976,12 @@
4698 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
4699 QPointF touchPoint = initialTouchPos;
4700
4701- qreal desiredDragDistance = edgeDragArea->distanceThreshold()*2;
4702+ qreal desiredDragDistance = edgeDragArea->d->distanceThreshold * 2;
4703 QPointF dragDirectionVector(1.0f, 0.0f); // rightwards
4704- qreal movementStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
4705+ qreal movementStepDistance = edgeDragArea->d->distanceThreshold * 0.1f;
4706 QPointF touchMovement = dragDirectionVector * movementStepDistance;
4707 int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
4708- int movementTimeStepMs = (edgeDragArea->compositionTime() * 1.5f) / totalMovementSteps;
4709+ int movementTimeStepMs = (edgeDragArea->d->compositionTime * 1.5f) / totalMovementSteps;
4710
4711 QCOMPARE(mouseArea->pressed(), false);
4712
4713@@ -1259,13 +1002,305 @@
4714 // As the DirectionalDragArea recognizes the gesture, it grabs the touch from the MouseArea,
4715 // which should make the MouseArea get a cancelation event, which will then cause it to
4716 // reset its state (going back to "unpressed"/"released").
4717- QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
4718+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Recognized);
4719 QCOMPARE(mouseArea->pressed(), false);
4720 QCOMPARE(mouseAreaSpy.canceledCount, 1);
4721
4722 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
4723 }
4724
4725+void tst_DirectionalDragArea::interleavedTouches()
4726+{
4727+ DirectionalDragArea *edgeDragArea =
4728+ m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
4729+ QVERIFY(edgeDragArea != 0);
4730+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
4731+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
4732+
4733+ QPointF touch0 = edgeDragArea->mapToScene(
4734+ QPointF(edgeDragArea->width()*0.5, edgeDragArea->height()*0.3));
4735+
4736+ qreal desiredDragDistance = edgeDragArea->d->distanceThreshold * 2;
4737+ QPointF dragDirectionVector(1.0f, 0.0f); // rightwards
4738+ qreal movementStepDistance = edgeDragArea->d->distanceThreshold * 0.1f;
4739+ QPointF touchMovement = dragDirectionVector * movementStepDistance;
4740+ int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
4741+ int movementTimeStepMs = (edgeDragArea->d->maxTime * 0.4f) / totalMovementSteps;
4742+
4743+ QTest::touchEvent(m_view, m_device).press(0, touch0.toPoint());
4744+ for (int i = 0; i < totalMovementSteps; ++i) {
4745+ touch0 += touchMovement;
4746+ passTime(movementTimeStepMs);
4747+ QTest::touchEvent(m_view, m_device).move(0, touch0.toPoint());
4748+ }
4749+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Recognized);
4750+
4751+ QPointF touch1 = edgeDragArea->mapToScene(
4752+ QPointF(edgeDragArea->width()*0.5, edgeDragArea->height()*0.6));
4753+
4754+ QTest::touchEvent(m_view, m_device)
4755+ .move(0, touch0.toPoint())
4756+ .press(1, touch1.toPoint());
4757+
4758+ touch1 += touchMovement;
4759+ passTime(movementTimeStepMs);
4760+ QTest::touchEvent(m_view, m_device)
4761+ .move(0, touch0.toPoint())
4762+ .move(1, touch1.toPoint());
4763+
4764+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Recognized);
4765+
4766+ QTest::touchEvent(m_view, m_device)
4767+ .release(0, touch0.toPoint())
4768+ .move(1, touch1.toPoint());
4769+
4770+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
4771+
4772+ touch1 += touchMovement;
4773+ passTime(movementTimeStepMs);
4774+ QTest::touchEvent(m_view, m_device)
4775+ .move(1, touch1.toPoint());
4776+
4777+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
4778+
4779+ QPointF touch2 = edgeDragArea->mapToScene(
4780+ QPointF(edgeDragArea->width()*0.5, edgeDragArea->height()*0.9));
4781+
4782+ passTime(edgeDragArea->d->compositionTime + movementTimeStepMs);
4783+ QTest::touchEvent(m_view, m_device)
4784+ .move(1, touch1.toPoint())
4785+ .press(2, touch2.toPoint());
4786+
4787+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Undecided);
4788+ QCOMPARE(edgeDragArea->d->touchId, 2);
4789+
4790+ touch2 += touchMovement;
4791+ passTime(movementTimeStepMs);
4792+ QTest::touchEvent(m_view, m_device)
4793+ .move(1, touch1.toPoint())
4794+ .move(2, touch2.toPoint());
4795+
4796+ touch1 += touchMovement;
4797+ passTime(movementTimeStepMs);
4798+ QTest::touchEvent(m_view, m_device)
4799+ .move(1, touch1.toPoint())
4800+ .move(2, touch2.toPoint());
4801+
4802+ passTime(movementTimeStepMs);
4803+ QTest::touchEvent(m_view, m_device)
4804+ .release(1, touch1.toPoint())
4805+ .move(2, touch2.toPoint());
4806+
4807+ passTime(movementTimeStepMs);
4808+ QTest::touchEvent(m_view, m_device)
4809+ .release(2, touch2.toPoint());
4810+
4811+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
4812+}
4813+
4814+/*
4815+ A valid right-edge drag performed on mako
4816+ */
4817+void tst_DirectionalDragArea::makoRightEdgeDrag()
4818+{
4819+ m_view->resize(768, 1280);
4820+ QTest::qWait(300);
4821+
4822+ DirectionalDragArea *edgeDragArea =
4823+ m_view->rootObject()->findChild<DirectionalDragArea*>("hnDragArea");
4824+ QVERIFY(edgeDragArea != nullptr);
4825+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
4826+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
4827+
4828+ StatusSpy *statusSpy = new StatusSpy(edgeDragArea);
4829+
4830+ edgeDragArea->d->setPixelsPerMm(320.0 /*mako ppi*/ * 0.03937 /* inches per mm*/);
4831+
4832+ sendTouchPress(319744, 0, QPointF(767.001, 719.719));
4833+ sendTouchUpdate(319765, 0, QPointF(765.744,729.973));
4834+ sendTouchUpdate(319784, 0, QPointF(740.879,752.182));
4835+ sendTouchUpdate(319803, 0, QPointF(689.698,795.795));
4836+ sendTouchUpdate(319826, 0, QPointF(616.978,856.212));
4837+ sendTouchUpdate(319845, 0, QPointF(558.769,906.157));
4838+ sendTouchUpdate(319859, 0, QPointF(513.219,945.266));
4839+ sendTouchUpdate(319878, 0, QPointF(481.31,975.496));
4840+ sendTouchUpdate(319902, 0, QPointF(460.016,997.439));
4841+ sendTouchUpdate(319920, 0, QPointF(449.761,1008.6));
4842+ sendTouchUpdate(319929, 0, QPointF(445.891,1012.42));
4843+ sendTouchUpdate(319947, 0, QPointF(444.884,1013.93));
4844+ sendTouchUpdate(319965, 0, QPointF(444.461,1014.35));
4845+ sendTouchUpdate(320057, 0, QPointF(444.71,1013.56));
4846+ sendTouchUpdate(320138, 0, QPointF(445.434,1013.6));
4847+ sendTouchUpdate(320154, 0, QPointF(446.338,1012.98));
4848+ sendTouchUpdate(320171, 0, QPointF(447.232,1012.08));
4849+ sendTouchRelease(320171, 0, QPointF(447.232,1012.08));
4850+
4851+ QCOMPARE(statusSpy->recognized(), true);
4852+
4853+ delete statusSpy;
4854+}
4855+
4856+/*
4857+ A vertical, downwards swipe performed on mako near its right edge.
4858+
4859+ The DirectionalDragArea on the right edge must not recognize this
4860+ gesture.
4861+ */
4862+void tst_DirectionalDragArea::makoRightEdgeDrag_verticalDownwards()
4863+{
4864+ m_view->resize(768, 1280);
4865+ QTest::qWait(300);
4866+
4867+ DirectionalDragArea *edgeDragArea =
4868+ m_view->rootObject()->findChild<DirectionalDragArea*>("hnDragArea");
4869+ QVERIFY(edgeDragArea != nullptr);
4870+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
4871+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
4872+
4873+ edgeDragArea->d->setPixelsPerMm(320.0 /*mako ppi*/ * 0.03937 /* inches per mm*/);
4874+
4875+ StatusSpy *statusSpy = new StatusSpy(edgeDragArea);
4876+
4877+ sendTouchPress(12012445, 26, QPointF(767.001,461.82));
4878+ sendTouchUpdate(12012472, 26, QPointF(767.001,462.569));
4879+ sendTouchUpdate(12012528, 26, QPointF(767.001,463.334));
4880+ sendTouchUpdate(12012546, 26, QPointF(767.001,466.856));
4881+ sendTouchUpdate(12012571, 26, QPointF(767.001,473.291));
4882+ sendTouchUpdate(12012587, 26, QPointF(767.001,487.31));
4883+ sendTouchUpdate(12012604, 26, QPointF(765.364,507.521));
4884+ sendTouchUpdate(12012618, 26, QPointF(765.364,507.521));
4885+ sendTouchUpdate(12012627, 26, QPointF(762.642,534.317));
4886+ sendTouchUpdate(12012655, 26, QPointF(760.846,573.406));
4887+ sendTouchUpdate(12012667, 26, QPointF(759.838,625.295));
4888+ sendTouchUpdate(12012675, 26, QPointF(758.875,703.207));
4889+ sendTouchUpdate(12012696, 26, QPointF(761.52,777.015));
4890+ sendTouchUpdate(12012713, 26, QPointF(765.659,835.591));
4891+ sendTouchUpdate(12012731, 26, QPointF(766.778,883.206));
4892+ sendTouchUpdate(12012748, 26, QPointF(767.001,922.937));
4893+ sendTouchUpdate(12012779, 26, QPointF(767.001,967.558));
4894+ sendTouchUpdate(12012798, 26, QPointF(767.001,1006.12));
4895+ sendTouchUpdate(12012809, 26, QPointF(767.001,1033.1));
4896+ sendTouchRelease(12012810, 26, QPointF(767.001,1033.1));
4897+
4898+ QCOMPARE(statusSpy->recognized(), false);
4899+
4900+ delete statusSpy;
4901+}
4902+
4903+/*
4904+ A valid left-edge drag performed on mako. This one starts a bit slow than speeds up
4905+ */
4906+void tst_DirectionalDragArea::makoLeftEdgeDrag_slowStart()
4907+{
4908+ m_view->resize(768, 1280);
4909+ QTest::qWait(300);
4910+
4911+ DirectionalDragArea *edgeDragArea =
4912+ m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
4913+ QVERIFY(edgeDragArea != nullptr);
4914+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
4915+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
4916+
4917+ edgeDragArea->d->setPixelsPerMm(320.0 /*mako ppi*/ * 0.03937 /* inches per mm*/);
4918+
4919+ StatusSpy *statusSpy = new StatusSpy(edgeDragArea);
4920+
4921+ sendTouchPress(4002267, 77, QPointF(0,885.154));
4922+ sendTouchUpdate(4002275, 77, QPointF(0,886.214));
4923+ sendTouchUpdate(4002311, 77, QPointF(1.09568,887.75));
4924+ sendTouchUpdate(4002329, 77, QPointF(3.53647,890.191));
4925+ sendTouchUpdate(4002347, 77, QPointF(7.87434,892.879));
4926+ sendTouchUpdate(4002366, 77, QPointF(12.3036,895.075));
4927+ sendTouchUpdate(4002384, 77, QPointF(15.8885,896.849));
4928+ sendTouchUpdate(4002406, 77, QPointF(18.4504,897.88));
4929+ sendTouchUpdate(4002420, 77, QPointF(20.2429,898.149));
4930+ sendTouchUpdate(4002439, 77, QPointF(20.9945,898.149));
4931+ sendTouchUpdate(4002457, 77, QPointF(21.8819,898.149));
4932+ sendTouchUpdate(4002480, 77, QPointF(22.7454,897.389));
4933+ sendTouchUpdate(4002493, 77, QPointF(23.5456,896.589));
4934+ sendTouchUpdate(4002511, 77, QPointF(24.5435,895.031));
4935+ sendTouchUpdate(4002529, 77, QPointF(25.4271,892.32));
4936+ sendTouchUpdate(4002548, 77, QPointF(26.3145,889.658));
4937+ sendTouchUpdate(4002566, 77, QPointF(27.2004,886.999));
4938+ sendTouchUpdate(4002584, 77, QPointF(28.035,885.048));
4939+ sendTouchUpdate(4002603, 77, QPointF(29.9684,883.167));
4940+ sendTouchUpdate(4002620, 77, QPointF(33.3591,881.403));
4941+ sendTouchUpdate(4002639, 77, QPointF(44.1017,879.642));
4942+ sendTouchUpdate(4002657, 77, QPointF(64.828,878.502));
4943+ sendTouchUpdate(4002675, 77, QPointF(87.9486,878.157));
4944+ sendTouchUpdate(4002693, 77, QPointF(112.96,877.742));
4945+ sendTouchUpdate(4002711, 77, QPointF(138.903,877.157));
4946+ sendTouchUpdate(4002729, 77, QPointF(163.204,877.157));
4947+ sendTouchUpdate(4002747, 77, QPointF(182.127,877.157));
4948+ sendTouchUpdate(4002765, 77, QPointF(194.478,877.657));
4949+ sendTouchUpdate(4002785, 77, QPointF(201.474,878.508));
4950+ sendTouchUpdate(4002803, 77, QPointF(204.855,879.401));
4951+ sendTouchUpdate(4002822, 77, QPointF(206.616,880.281));
4952+ sendTouchUpdate(4002839, 77, QPointF(207.115,880.906));
4953+ sendTouchUpdate(4002894, 77, QPointF(206.865,881.184));
4954+ sendTouchUpdate(4002912, 77, QPointF(206.865,882.143));
4955+ sendTouchUpdate(4002930, 77, QPointF(206.865,883.106));
4956+ sendTouchUpdate(4002949, 77, QPointF(206.526,883.994));
4957+ sendTouchUpdate(4002967, 77, QPointF(205.866,884.88));
4958+ sendTouchUpdate(4002985, 77, QPointF(205.866,885.766));
4959+ sendTouchUpdate(4003005, 77, QPointF(205.866,886.654));
4960+ sendTouchUpdate(4003021, 77, QPointF(205.366,887.537));
4961+ sendTouchUpdate(4003039, 77, QPointF(204.592,888.428));
4962+ sendTouchUpdate(4003050, 77, QPointF(204.367,888.653));
4963+ sendTouchRelease(4003050, 77, QPointF(204.367,888.653));
4964+
4965+ QCOMPARE(statusSpy->recognized(), true);
4966+
4967+ delete statusSpy;
4968+}
4969+
4970+void tst_DirectionalDragArea::makoLeftEdgeDrag_movesSlightlyBackwardsOnStart()
4971+{
4972+ m_view->resize(768, 1280);
4973+ QTest::qWait(300);
4974+
4975+ DirectionalDragArea *edgeDragArea =
4976+ m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
4977+ QVERIFY(edgeDragArea != nullptr);
4978+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
4979+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
4980+
4981+ edgeDragArea->d->setPixelsPerMm(320.0 /*mako ppi*/ * 0.03937 /* inches per mm*/);
4982+
4983+ StatusSpy *statusSpy = new StatusSpy(edgeDragArea);
4984+
4985+ sendTouchPress(41097, 24, QPointF(13.9909,827.177));
4986+ sendTouchUpdate(41120, 24, QPointF(19.2375,825.677));
4987+ sendTouchUpdate(41138, 24, QPointF(18.4057,826.177));
4988+ sendTouchUpdate(41161, 24, QPointF(20.1067,825.867));
4989+ sendTouchUpdate(41177, 24, QPointF(21.8869,824.977));
4990+ sendTouchUpdate(41193, 24, QPointF(24.7603,823.494));
4991+ sendTouchUpdate(41211, 24, QPointF(28.3889,821.725));
4992+ sendTouchUpdate(41229, 24, QPointF(32.2909,819.955));
4993+ sendTouchUpdate(41247, 24, QPointF(38.2251,817.431));
4994+ sendTouchUpdate(41266, 24, QPointF(52.4182,814.223));
4995+ sendTouchUpdate(41284, 24, QPointF(85.8465,809.483));
4996+ sendTouchUpdate(41302, 24, QPointF(126.091,802.741));
4997+ sendTouchUpdate(41320, 24, QPointF(153.171,797.977));
4998+ sendTouchUpdate(41338, 24, QPointF(170.565,795.077));
4999+ sendTouchUpdate(41356, 24, QPointF(178.685,794.101));
5000+ sendTouchUpdate(41375, 24, QPointF(183.706,793.225));
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches