on xenial help sign in
Merged ddaImprovements
commit3628f62968b6f6ad7531d3b01fa384b80393b548 authorDaniel d'Andrada <daniel.dandrada@canonical.com> dateFri 17 Apr 2015, 13:51 (12 years ago) parent7478eb8ee22bb3
50 files changed+1696−1448
libs/UbuntuGestures/CMakeLists.txt+1 libs/UbuntuGestures/CandidateInactivityTimer.h+1−1 plugins/Ubuntu/Gestures/TimeSource.cpp → libs/UbuntuGestures/TimeSource.cpp plugins/Ubuntu/Gestures/TimeSource.h → libs/UbuntuGestures/TimeSource.h+14−4 libs/UbuntuGestures/Timer.cpp+63−10 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 plugins/Ubuntu/Gestures/Damper.h+2 plugins/Ubuntu/Gestures/DirectionalDragArea.cpp+322−323 plugins/Ubuntu/Gestures/DirectionalDragArea.h+29−202 plugins/Ubuntu/Gestures/DirectionalDragArea_p.h+167 plugins/Ubuntu/Gestures/TouchDispatcher.cpp+88−53 plugins/Ubuntu/Gestures/TouchDispatcher.h+16−9 plugins/Ubuntu/Gestures/TouchGate.cpp+84−60 plugins/Ubuntu/Gestures/TouchGate.h+18−5 qml/Components/DragHandle.qml+18−33 qml/Components/EdgeDragArea.qml−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+31−41 qml/Stages/TabletStage.qml+18−20 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 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+480−445 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 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
libs/UbuntuGestures/CMakeLists.txt+1
@@ -5,6 +5,7 @@ set(UbuntuGestures_SOURCES
55 CandidateInactivityTimer.cpp
66 DebugHelpers.cpp
77 Timer.cpp
8+ TimeSource.cpp
89 TouchOwnershipEvent.cpp
910 TouchRegistry.cpp
1011 UnownedTouchEvent.cpp
libs/UbuntuGestures/CandidateInactivityTimer.h+1−1
@@ -32,7 +32,7 @@ public:
3232 AbstractTimerFactory &timerFactory,
3333 QObject *parent = nullptr);
3434
35− const int durationMs = 350;
35+ const int durationMs = 1000;
3636
3737Q_SIGNALS:
3838 void candidateDefaulted(int touchId, QQuickItem *candidate);
plugins/Ubuntu/Gestures/TimeSource.cpp → libs/UbuntuGestures/TimeSource.cpprenamed

The content is the same.

plugins/Ubuntu/Gestures/TimeSource.h → libs/UbuntuGestures/TimeSource.hrenamed+14−4
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013 - Canonical Ltd.
2+ * Copyright (C) 2013,2015 Canonical Ltd.
33 *
44 * This program is free software: you can redistribute it and/or modify it
55 * under the terms of the GNU Lesser General Public License, as
@@ -21,14 +21,14 @@
2121#ifndef UBUNTUGESTURES_TIMESOURCE_H
2222#define UBUNTUGESTURES_TIMESOURCE_H
2323
24−#include "UbuntuGesturesQmlGlobal.h"
24+#include "UbuntuGesturesGlobal.h"
2525#include <QSharedPointer>
2626
2727namespace UbuntuGestures {
2828/*
2929 Interface for a time source.
3030 */
31−class UBUNTUGESTURESQML_EXPORT TimeSource {
31+class UBUNTUGESTURES_EXPORT TimeSource {
3232public:
3333 virtual ~TimeSource() {}
3434 /* Returns the current time in milliseconds since some reference time in the past. */
@@ -40,7 +40,7 @@ typedef QSharedPointer<TimeSource> SharedTimeSource;
4040 Implementation of a time source
4141 */
4242class RealTimeSourcePrivate;
43−class RealTimeSource : public TimeSource {
43+class UBUNTUGESTURES_EXPORT RealTimeSource : public TimeSource {
4444public:
4545 RealTimeSource();
4646 virtual ~RealTimeSource();
@@ -49,6 +49,16 @@ private:
4949 RealTimeSourcePrivate *d;
5050};
5151
52+/*
53+ A fake time source, useful for tests
54+ */
55+class FakeTimeSource : public TimeSource {
56+public:
57+ FakeTimeSource() { m_msecsSinceReference = 0; }
58+ qint64 msecsSinceReference() override { return m_msecsSinceReference; }
59+ qint64 m_msecsSinceReference;
60+};
61+
5262} // namespace UbuntuGestures
5363
5464#endif // UBUNTUGESTURES_TIMESOURCE_H
libs/UbuntuGestures/Timer.cpp+63−10
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2014 Canonical, Ltd.
2+ * Copyright (C) 2014-2015 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -58,13 +58,36 @@ void Timer::setSingleShot(bool value)
5858
5959/////////////////////////////////// FakeTimer //////////////////////////////////
6060
61−FakeTimer::FakeTimer(QObject *parent)
61+FakeTimer::FakeTimer(const SharedTimeSource &timeSource, QObject *parent)
6262 : UbuntuGestures::AbstractTimer(parent)
6363 , m_interval(0)
6464 , m_singleShot(false)
65+ , m_timeSource(timeSource)
6566{
6667}
6768
69+void FakeTimer::update()
70+{
71+ if (!isRunning()) {
72+ return;
73+ }
74+
75+ if (m_nextTimeoutTime <= m_timeSource->msecsSinceReference()) {
76+ if (isSingleShot()) {
77+ stop();
78+ } else {
79+ m_nextTimeoutTime += interval();
80+ }
81+ Q_EMIT timeout();
82+ }
83+}
84+
85+void FakeTimer::start()
86+{
87+ AbstractTimer::start();
88+ m_nextTimeoutTime = m_timeSource->msecsSinceReference() + (qint64)interval();
89+}
90+
6891int FakeTimer::interval() const
6992{
7093 return m_interval;
@@ -87,23 +110,53 @@ void FakeTimer::setSingleShot(bool value)
87110
88111/////////////////////////////////// FakeTimerFactory //////////////////////////////////
89112
90−AbstractTimer *FakeTimerFactory::createTimer(QObject *parent)
113+FakeTimerFactory::FakeTimerFactory()
91114{
92− FakeTimer *fakeTimer = new FakeTimer(parent);
93−
94− timers.append(fakeTimer);
115+ m_timeSource.reset(new FakeTimeSource);
116+}
95117
96− return fakeTimer;
118+FakeTimerFactory::~FakeTimerFactory()
119+{
120+ for (int i = 0; i < timers.count(); ++i) {
121+ FakeTimer *timer = timers[i].data();
122+ if (timer) {
123+ delete timer;
124+ }
125+ }
97126}
98127
99−void FakeTimerFactory::makeRunningTimersTimeout()
128+void FakeTimerFactory::updateTime(qint64 targetTime)
100129{
130+ qint64 minTimeoutTime = targetTime;
131+
132+ for (int i = 0; i < timers.count(); ++i) {
133+ FakeTimer *timer = timers[i].data();
134+ if (timer && timer->isRunning() && timer->nextTimeoutTime() < minTimeoutTime) {
135+ minTimeoutTime = timer->nextTimeoutTime();
136+ }
137+ }
138+
139+ m_timeSource->m_msecsSinceReference = minTimeoutTime;
140+
101141 for (int i = 0; i < timers.count(); ++i) {
102142 FakeTimer *timer = timers[i].data();
103− if (timer && timer->isRunning()) {
104− timer->emitTimeout();
143+ if (timer) {
144+ timer->update();
105145 }
106146 }
147+
148+ if (m_timeSource->msecsSinceReference() < targetTime) {
149+ updateTime(targetTime);
150+ }
151+}
152+
153+AbstractTimer *FakeTimerFactory::createTimer(QObject *parent)
154+{
155+ FakeTimer *fakeTimer = new FakeTimer(m_timeSource, parent);
156+
157+ timers.append(fakeTimer);
158+
159+ return fakeTimer;
107160}
108161
109162} // namespace UbuntuGestures
libs/UbuntuGestures/Timer.h+15−3
@@ -18,6 +18,7 @@
1818#define UBUNTUGESTURES_TIMER_H
1919
2020#include "UbuntuGesturesGlobal.h"
21+#include "TimeSource.h"
2122
2223#include <QObject>
2324#include <QPointer>
@@ -66,17 +67,21 @@ class UBUNTUGESTURES_EXPORT FakeTimer : public AbstractTimer
6667{
6768 Q_OBJECT
6869public:
69− FakeTimer(QObject *parent = nullptr);
70+ FakeTimer(const SharedTimeSource &timeSource, QObject *parent = nullptr);
7071
71− virtual void emitTimeout() { Q_EMIT timeout(); }
72+ void update();
73+ qint64 nextTimeoutTime() const { return m_nextTimeoutTime; }
7274
7375 int interval() const override;
7476 void setInterval(int msecs) override;
77+ void start() override;
7578 bool isSingleShot() const override;
7679 void setSingleShot(bool value) override;
7780private:
7881 int m_interval;
7982 bool m_singleShot;
83+ SharedTimeSource m_timeSource;
84+ qint64 m_nextTimeoutTime;
8085};
8186
8287class UBUNTUGESTURES_EXPORT AbstractTimerFactory
@@ -95,9 +100,16 @@ public:
95100class UBUNTUGESTURES_EXPORT FakeTimerFactory : public AbstractTimerFactory
96101{
97102public:
103+ FakeTimerFactory();
104+ virtual ~FakeTimerFactory();
105+
106+ void updateTime(qint64 msecsSinceReference);
107+ QSharedPointer<TimeSource> timeSource() { return m_timeSource; }
108+
98109 AbstractTimer *createTimer(QObject *parent = nullptr) override;
99− void makeRunningTimersTimeout();
100110 QList<QPointer<FakeTimer>> timers;
111+private:
112+ QSharedPointer<FakeTimeSource> m_timeSource;
101113};
102114
103115} // namespace UbuntuGestures
libs/UbuntuGestures/TouchRegistry.cpp+12−14
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2014 Canonical, Ltd.
2+ * Copyright (C) 2014-2015 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -95,10 +95,6 @@ void TouchRegistry::deliverTouchUpdatesToUndecidedCandidatesAndWatchers(const QT
9595 // for each point and there should not be many active points at any given moment.
9696 // But having three nested for-loops does scare.
9797
98− // TODO: Don't send it to the object that is already receiving the regular event
99− // because QQuickWindow is sending it to him (i.e., he's the touch owner from Qt's point of view)
100− // Problem is, we cannnot easily get this information.
101−
10298 const QList<QTouchEvent::TouchPoint> &updatedTouchPoints = event->touchPoints();
10399
104100 // Maps an item to the touches in this event he should be informed about.
@@ -118,7 +114,9 @@ void TouchRegistry::deliverTouchUpdatesToUndecidedCandidatesAndWatchers(const QT
118114 for (int i = 0; i < touchInfo->candidates.count(); ++i) {
119115 CandidateInfo &candidate = touchInfo->candidates[i];
120116 Q_ASSERT(!candidate.item.isNull());
121− touchIdsForItems[candidate.item.data()].append(touchInfo->id);
117+ if (candidate.state != CandidateInfo::InterimOwner) {
118+ touchIdsForItems[candidate.item.data()].append(touchInfo->id);
119+ }
122120 }
123121 }
124122
@@ -260,7 +258,7 @@ void TouchRegistry::addCandidateOwnerForTouch(int id, QQuickItem *candidate)
260258 // TODO: Check if candidate already exists
261259
262260 CandidateInfo candidateInfo;
263− candidateInfo.undecided = true;
261+ candidateInfo.state = CandidateInfo::Undecided;
264262 candidateInfo.item = candidate;
265263 candidateInfo.inactivityTimer = new CandidateInactivityTimer(id, candidate,
266264 *m_timerFactory,
@@ -301,8 +299,8 @@ void TouchRegistry::removeCandidateOwnerForTouch(int id, QQuickItem *candidate)
301299 for (int i = 0; i < touchInfo->candidates.count() && indexRemoved == -1; ++i) {
302300 CandidateInfo &candidateInfo = touchInfo->candidates[i];
303301 if (candidateInfo.item == candidate) {
304− Q_ASSERT(i > 0 || candidateInfo.undecided);
305− if (i == 0 && !candidateInfo.undecided) {
302+ Q_ASSERT(i > 0 || candidateInfo.state == CandidateInfo::Undecided);
303+ if (i == 0 && candidateInfo.state != CandidateInfo::Undecided) {
306304 qCritical("TouchRegistry: touch owner is being removed.");
307305 }
308306 delete candidateInfo.inactivityTimer;
@@ -340,7 +338,7 @@ void TouchRegistry::requestTouchOwnership(int id, QQuickItem *candidate)
340338 for (int i = 0; i < touchInfo->candidates.count(); ++i) {
341339 CandidateInfo &candidateInfo = touchInfo->candidates[i];
342340 if (candidateInfo.item == candidate) {
343− candidateInfo.undecided = false;
341+ candidateInfo.state = CandidateInfo::Requested;
344342 delete candidateInfo.inactivityTimer;
345343 candidateInfo.inactivityTimer = nullptr;
346344 candidateIndex = i;
@@ -351,7 +349,7 @@ void TouchRegistry::requestTouchOwnership(int id, QQuickItem *candidate)
351349 // add it as a candidate if not present yet
352350 if (candidateIndex < 0) {
353351 CandidateInfo candidateInfo;
354− candidateInfo.undecided = false;
352+ candidateInfo.state = CandidateInfo::InterimOwner;
355353 candidateInfo.item = candidate;
356354 candidateInfo.inactivityTimer = nullptr;
357355 touchInfo->candidates.append(candidateInfo);
@@ -407,8 +405,8 @@ void TouchRegistry::rejectCandidateOwnerForTouch(int id, QQuickItem *candidate)
407405 for (int i = 0; i < touchInfo->candidates.count() && rejectedCandidateIndex == -1; ++i) {
408406 CandidateInfo &candidateInfo = touchInfo->candidates[i];
409407 if (candidateInfo.item == candidate) {
410− Q_ASSERT(i > 0 || candidateInfo.undecided);
411− if (i == 0 && !candidateInfo.undecided) {
408+ Q_ASSERT(i > 0 || candidateInfo.state == CandidateInfo::Undecided);
409+ if (i == 0 && candidateInfo.state != CandidateInfo::Undecided) {
412410 qCritical() << "TouchRegistry: Can't reject item (" << (void*)candidate
413411 << ") as it already owns touch" << id;
414412 return;
@@ -467,7 +465,7 @@ void TouchRegistry::TouchInfo::init(int id)
467465
468466bool TouchRegistry::TouchInfo::isOwned() const
469467{
470− return !candidates.isEmpty() && !candidates.first().undecided;
468+ return !candidates.isEmpty() && candidates.first().state != CandidateInfo::Undecided;
471469}
472470
473471bool TouchRegistry::TouchInfo::ended() const
libs/UbuntuGestures/TouchRegistry.h+18−4
@@ -79,9 +79,11 @@ namespace UbuntuGestures {
7979 If an item wants ownership over touches as soon as he receives the TouchBegin for them, his step 1
8080 would be instead:
8181 TouchRegistry::instance()->requestTouchOwnership(touchId, this);
82− return true;
83− He would then be notified once ownership has been granted to him, from which point onwards he could
84− safely assume other TouchRegistry users wouldn't snatch this touch away from him.
82+ touchEvent->accept();
83+ He won't get any UnownedTouchEvent for that touch as he is already the interim owner (ie, QQuickWindow
84+ will keep sending touch updates to him already). Eventually he will be notified once ownership has
85+ been granted to him (from TouchRegistry perspective), from which point onwards he could safely assume
86+ other TouchRegistry users wouldn't snatch this touch away from him.
8587
8688 Items oblivious to TouchRegistry will lose their touch points without warning, just like in plain Qt.
8789
@@ -132,7 +134,19 @@ private Q_SLOTS:
132134private:
133135 class CandidateInfo {
134136 public:
135− bool undecided;
137+ enum {
138+ // A candidate owner that doesn't yet know for sure whether he wants the touch point
139+ // (gesture recognition is stilll going on)
140+ Undecided = 0,
141+ // A candidate owner that wants the touch but hasn't been granted it yet,
142+ // most likely because there's an undecided candidate with higher priority
143+ Requested = 1,
144+ // An item that is the interim owner of the touch, receiving QTouchEvents of it
145+ // from QQuickWindow. Ie, it's the actual touch owner from Qt's point of view.
146+ // It wants to keep its touch ownership but hasn't been granted it by TouchRegistry
147+ // yet because of undecided candidates higher up.
148+ InterimOwner = 2
149+ } state;
136150 // TODO: Prune candidates that become null and resolve ownership accordingly.
137151 QPointer<QQuickItem> item;
138152 QPointer<UbuntuGestures::CandidateInactivityTimer> inactivityTimer;
plugins/Ubuntu/Gestures/CMakeLists.txt+1−1
@@ -4,10 +4,10 @@ remove_definitions(-DQT_NO_KEYWORDS)
44set(UbuntuGesturesQml_SOURCES
55 plugin.cpp
66 AxisVelocityCalculator.cpp
7+ Damper.cpp
78 Direction.cpp
89 DirectionalDragArea.cpp
910 PressedOutsideNotifier.cpp
10− TimeSource.cpp
1111 TouchDispatcher.cpp
1212 TouchGate.cpp
1313)
plugins/Ubuntu/Gestures/Damper.cppnew file+24
@@ -0,0 +1,24 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include "Damper.h"
18+#include <QDebug>
19+
20+QDebug operator<<(QDebug dbg, const DampedPointF &p)
21+{
22+ dbg.nospace() << "(" << p.x() << ", " << p.y() << ")";
23+ return dbg.space();
24+}
plugins/Ubuntu/Gestures/Damper.h+2
@@ -84,4 +84,6 @@ private:
8484 Damper<qreal> m_y;
8585};
8686
87+QDebug operator<<(QDebug dbg, const DampedPointF &p);
88+
8789#endif // UBUNTU_GESTURES_DAMPER_H
plugins/Ubuntu/Gestures/DirectionalDragArea.cpp+322−323
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013-2014 Canonical, Ltd.
2+ * Copyright (C) 2013-2015 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -21,6 +21,7 @@
2121
2222#include <QQuickWindow>
2323#include <QtCore/qmath.h>
24+#include <QScreen>
2425#include <QDebug>
2526
2627#pragma GCC diagnostic push
@@ -33,6 +34,8 @@
3334#include "TouchRegistry.h"
3435#include "UnownedTouchEvent.h"
3536
37+#include "DirectionalDragArea_p.h"
38+
3639using namespace UbuntuGestures;
3740
3841#if DIRECTIONALDRAGAREA_DEBUG
@@ -40,11 +43,11 @@ using namespace UbuntuGestures;
4043#include "DebugHelpers.h"
4144
4245namespace {
43−const char *statusToString(DirectionalDragArea::Status status)
46+const char *statusToString(DirectionalDragAreaPrivate::Status status)
4447{
45− if (status == DirectionalDragArea::WaitingForTouch) {
48+ if (status == DirectionalDragAreaPrivate::WaitingForTouch) {
4649 return "WaitingForTouch";
47− } else if (status == DirectionalDragArea::Undecided) {
50+ } else if (status == DirectionalDragAreaPrivate::Undecided) {
4851 return "Undecided";
4952 } else {
5053 return "Recognized";
@@ -56,205 +59,168 @@ const char *statusToString(DirectionalDragArea::Status status)
5659#define ddaDebug(params) ((void)0)
5760#endif // DIRECTIONALDRAGAREA_DEBUG
5861
59−
6062DirectionalDragArea::DirectionalDragArea(QQuickItem *parent)
6163 : QQuickItem(parent)
62− , m_status(WaitingForTouch)
63− , m_sceneDistance(0)
64− , m_touchId(-1)
65− , m_direction(Direction::Rightwards)
66− , m_wideningAngle(0)
67− , m_wideningFactor(0)
68− , m_distanceThreshold(0)
69− , m_distanceThresholdSquared(0.)
70− , m_minSpeed(0)
71− , m_maxSilenceTime(200)
72− , m_silenceTime(0)
73− , m_compositionTime(60)
74− , m_numSamplesOnLastSpeedCheck(0)
75− , m_recognitionTimer(0)
76− , m_velocityCalculator(0)
77− , m_timeSource(new RealTimeSource)
78− , m_activeTouches(m_timeSource)
79−{
80− setRecognitionTimer(new Timer(this));
81− m_recognitionTimer->setInterval(60);
82− m_recognitionTimer->setSingleShot(false);
83−
84− m_velocityCalculator = new AxisVelocityCalculator(this);
85−
86− connect(this, &QQuickItem::enabledChanged, this, &DirectionalDragArea::giveUpIfDisabledOrInvisible);
87− connect(this, &QQuickItem::visibleChanged, this, &DirectionalDragArea::giveUpIfDisabledOrInvisible);
88−}
89−
90−Direction::Type DirectionalDragArea::direction() const
91−{
92− return m_direction;
93−}
94−
95−void DirectionalDragArea::setDirection(Direction::Type direction)
96−{
97− if (direction != m_direction) {
98− m_direction = direction;
99− Q_EMIT directionChanged(m_direction);
100− }
101−}
102−
103−void DirectionalDragArea::setMaxDeviation(qreal value)
104−{
105− if (m_dampedScenePos.maxDelta() != value) {
106− m_dampedScenePos.setMaxDelta(value);
107− Q_EMIT maxDeviationChanged(value);
108− }
109−}
110−
111−qreal DirectionalDragArea::wideningAngle() const
64+ , d(new DirectionalDragAreaPrivate(this))
11265{
113− return m_wideningAngle;
114−}
115−
116−void DirectionalDragArea::setWideningAngle(qreal angle)
117−{
118− if (angle == m_wideningAngle)
119− return;
66+ d->setRecognitionTimer(new Timer(this));
67+ d->recognitionTimer->setInterval(d->maxTime);
68+ d->recognitionTimer->setSingleShot(true);
12069
121− m_wideningAngle = angle;
122−
123− // wideningFactor = pow(cosine(angle), 2)
124− {
125− qreal angleRadians = angle * M_PI / 180.0;
126− m_wideningFactor = qCos(angleRadians);
127− m_wideningFactor = m_wideningFactor * m_wideningFactor;
128− }
129−
130− Q_EMIT wideningAngleChanged(angle);
70+ connect(this, &QQuickItem::enabledChanged, d, &DirectionalDragAreaPrivate::giveUpIfDisabledOrInvisible);
71+ connect(this, &QQuickItem::visibleChanged, d, &DirectionalDragAreaPrivate::giveUpIfDisabledOrInvisible);
13172}
13273
133−void DirectionalDragArea::setDistanceThreshold(qreal value)
74+Direction::Type DirectionalDragArea::direction() const
13475{
135− if (m_distanceThreshold != value) {
136− m_distanceThreshold = value;
137− m_distanceThresholdSquared = m_distanceThreshold * m_distanceThreshold;
138− Q_EMIT distanceThresholdChanged(value);
139− }
76+ return d->direction;
14077}
14178
142−void DirectionalDragArea::setMinSpeed(qreal value)
79+void DirectionalDragArea::setDirection(Direction::Type direction)
14380{
144− if (m_minSpeed != value) {
145− m_minSpeed = value;
146− Q_EMIT minSpeedChanged(value);
81+ if (direction != d->direction) {
82+ d->direction = direction;
83+ Q_EMIT directionChanged(d->direction);
14784 }
14885}
14986
150−void DirectionalDragArea::setMaxSilenceTime(int value)
87+void DirectionalDragAreaPrivate::setDistanceThreshold(qreal value)
15188{
152− if (m_maxSilenceTime != value) {
153− m_maxSilenceTime = value;
154− Q_EMIT maxSilenceTimeChanged(value);
89+ if (distanceThreshold != value) {
90+ distanceThreshold = value;
91+ distanceThresholdSquared = distanceThreshold * distanceThreshold;
15592 }
15693}
15794
158−void DirectionalDragArea::setCompositionTime(int value)
95+void DirectionalDragAreaPrivate::setMaxTime(int value)
15996{
160− if (m_compositionTime != value) {
161− m_compositionTime = value;
162− Q_EMIT compositionTimeChanged(value);
97+ if (maxTime != value) {
98+ maxTime = value;
99+ recognitionTimer->setInterval(maxTime);
163100 }
164101}
165102
166−void DirectionalDragArea::setRecognitionTimer(UbuntuGestures::AbstractTimer *timer)
103+void DirectionalDragAreaPrivate::setRecognitionTimer(UbuntuGestures::AbstractTimer *timer)
167104{
168105 int interval = 0;
169106 bool timerWasRunning = false;
170107 bool wasSingleShot = false;
171108
172109 // can be null when called from the constructor
173− if (m_recognitionTimer) {
174− interval = m_recognitionTimer->interval();
175− timerWasRunning = m_recognitionTimer->isRunning();
176− if (m_recognitionTimer->parent() == this) {
177− delete m_recognitionTimer;
110+ if (recognitionTimer) {
111+ interval = recognitionTimer->interval();
112+ timerWasRunning = recognitionTimer->isRunning();
113+ if (recognitionTimer->parent() == this) {
114+ delete recognitionTimer;
178115 }
179116 }
180117
181− m_recognitionTimer = timer;
118+ recognitionTimer = timer;
182119 timer->setInterval(interval);
183120 timer->setSingleShot(wasSingleShot);
184121 connect(timer, &UbuntuGestures::AbstractTimer::timeout,
185− this, &DirectionalDragArea::checkSpeed);
122+ this, &DirectionalDragAreaPrivate::rejectGesture);
186123 if (timerWasRunning) {
187− m_recognitionTimer->start();
124+ recognitionTimer->start();
188125 }
189126}
190127
191−void DirectionalDragArea::setTimeSource(const SharedTimeSource &timeSource)
128+void DirectionalDragAreaPrivate::setTimeSource(const SharedTimeSource &timeSource)
192129{
193− m_timeSource = timeSource;
194− m_velocityCalculator->setTimeSource(timeSource);
195− m_activeTouches.m_timeSource = timeSource;
130+ this->timeSource = timeSource;
131+ activeTouches.m_timeSource = timeSource;
196132}
197133
198134qreal DirectionalDragArea::distance() const
199135{
200− if (Direction::isHorizontal(m_direction)) {
201− return m_previousPos.x() - m_startPos.x();
136+ if (Direction::isHorizontal(d->direction)) {
137+ return d->publicPos.x() - d->startPos.x();
202138 } else {
203− return m_previousPos.y() - m_startPos.y();
139+ return d->publicPos.y() - d->startPos.y();
204140 }
205141}
206142
207−void DirectionalDragArea::updateSceneDistance()
143+void DirectionalDragAreaPrivate::updateSceneDistance()
208144{
209− QPointF totalMovement = m_previousScenePos - m_startScenePos;
210− m_sceneDistance = projectOntoDirectionVector(totalMovement);
145+ QPointF totalMovement = publicScenePos - startScenePos;
146+ sceneDistance = projectOntoDirectionVector(totalMovement);
211147}
212148
213149qreal DirectionalDragArea::sceneDistance() const
214150{
215− return m_sceneDistance;
151+ return d->sceneDistance;
216152}
217153
218154qreal DirectionalDragArea::touchX() const
219155{
220− return m_previousPos.x();
156+ return d->publicPos.x();
221157}
222158
223159qreal DirectionalDragArea::touchY() const
224160{
225− return m_previousPos.y();
161+ return d->publicPos.y();
226162}
227163
228164qreal DirectionalDragArea::touchSceneX() const
229165{
230− return m_previousScenePos.x();
166+ return d->publicScenePos.x();
231167}
232168
233169qreal DirectionalDragArea::touchSceneY() const
234170{
235− return m_previousScenePos.y();
171+ return d->publicScenePos.y();
172+}
173+
174+bool DirectionalDragArea::dragging() const
175+{
176+ return d->status == DirectionalDragAreaPrivate::Recognized;
177+}
178+
179+bool DirectionalDragArea::pressed() const
180+{
181+ return d->status != DirectionalDragAreaPrivate::WaitingForTouch;
182+}
183+
184+bool DirectionalDragArea::immediateRecognition() const
185+{
186+ return d->immediateRecognition;
187+}
188+
189+void DirectionalDragArea::setImmediateRecognition(bool enabled)
190+{
191+ if (d->immediateRecognition != enabled) {
192+ d->immediateRecognition = enabled;
193+ Q_EMIT immediateRecognitionChanged(enabled);
194+ }
195+}
196+
197+void DirectionalDragArea::removeTimeConstraints()
198+{
199+ d->setMaxTime(60 * 60 * 1000);
200+ d->compositionTime = 0;
201+ ddaDebug("removed time constraints");
236202}
237203
238204bool DirectionalDragArea::event(QEvent *event)
239205{
240206 if (event->type() == TouchOwnershipEvent::touchOwnershipEventType()) {
241− touchOwnershipEvent(static_cast<TouchOwnershipEvent *>(event));
207+ d->touchOwnershipEvent(static_cast<TouchOwnershipEvent *>(event));
242208 return true;
243209 } else if (event->type() == UnownedTouchEvent::unownedTouchEventType()) {
244− unownedTouchEvent(static_cast<UnownedTouchEvent *>(event));
210+ d->unownedTouchEvent(static_cast<UnownedTouchEvent *>(event));
245211 return true;
246212 } else {
247213 return QQuickItem::event(event);
248214 }
249215}
250216
251−void DirectionalDragArea::touchOwnershipEvent(TouchOwnershipEvent *event)
217+void DirectionalDragAreaPrivate::touchOwnershipEvent(TouchOwnershipEvent *event)
252218{
253219 if (event->gained()) {
254220 QVector<int> ids;
255221 ids.append(event->touchId());
256222 ddaDebug("grabbing touch");
257− grabTouchPoints(ids);
223+ q->grabTouchPoints(ids);
258224
259225 // Work around for Qt bug. If we grab a touch that is being used for mouse pointer
260226 // emulation it will cause the emulation logic to go nuts.
@@ -262,35 +228,35 @@ void DirectionalDragArea::touchOwnershipEvent(TouchOwnershipEvent *event)
262228 //
263229 // The fix for this bug has landed in Qt 5.4 (https://codereview.qt-project.org/96887)
264230 // TODO: Remove this workaround once we start using Qt 5.4
265− if (window()) {
266− QQuickWindowPrivate *windowPrivate = QQuickWindowPrivate::get(window());
267− if (windowPrivate->touchMouseId == event->touchId() && window()->mouseGrabberItem()) {
231+ if (q->window()) {
232+ QQuickWindowPrivate *windowPrivate = QQuickWindowPrivate::get(q->window());
233+ if (windowPrivate->touchMouseId == event->touchId() && q->window()->mouseGrabberItem()) {
268234 ddaDebug("removing mouse grabber");
269− window()->mouseGrabberItem()->ungrabMouse();
235+ q->window()->mouseGrabberItem()->ungrabMouse();
270236 }
271237 }
272238 } else {
273239 // We still wanna know when it ends for keeping the composition time window up-to-date
274− TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
240+ TouchRegistry::instance()->addTouchWatcher(touchId, q);
275241
276242 setStatus(WaitingForTouch);
277243 }
278244}
279245
280−void DirectionalDragArea::unownedTouchEvent(UnownedTouchEvent *unownedTouchEvent)
246+void DirectionalDragAreaPrivate::unownedTouchEvent(UnownedTouchEvent *unownedTouchEvent)
281247{
282248 QTouchEvent *event = unownedTouchEvent->touchEvent();
283249
284250 Q_ASSERT(!event->touchPointStates().testFlag(Qt::TouchPointPressed));
285251
286− ddaDebug("Unowned " << m_timeSource->msecsSinceReference() << " " << qPrintable(touchEventToString(event)));
252+ ddaDebug("Unowned " << timeSource->msecsSinceReference() << " " << qPrintable(touchEventToString(event)));
287253
288− switch (m_status) {
254+ switch (status) {
289255 case WaitingForTouch:
290256 // do nothing
291257 break;
292258 case Undecided:
293− Q_ASSERT(isEnabled() && isVisible());
259+ Q_ASSERT(q->isEnabled() && q->isVisible());
294260 unownedTouchEvent_undecided(unownedTouchEvent);
295261 break;
296262 default: // Recognized:
@@ -298,18 +264,18 @@ void DirectionalDragArea::unownedTouchEvent(UnownedTouchEvent *unownedTouchEvent
298264 break;
299265 }
300266
301− m_activeTouches.update(event);
267+ activeTouches.update(event);
302268}
303269
304−void DirectionalDragArea::unownedTouchEvent_undecided(UnownedTouchEvent *unownedTouchEvent)
270+void DirectionalDragAreaPrivate::unownedTouchEvent_undecided(UnownedTouchEvent *unownedTouchEvent)
305271{
306272 const QTouchEvent::TouchPoint *touchPoint = fetchTargetTouchPoint(unownedTouchEvent->touchEvent());
307273 if (!touchPoint) {
308− qCritical() << "DirectionalDragArea[status=Undecided]: touch " << m_touchId
274+ qCritical() << "DirectionalDragArea[status=Undecided]: touch " << touchId
309275 << "missing from UnownedTouchEvent without first reaching state Qt::TouchPointReleased. "
310276 "Considering it as released.";
311277
312− TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
278+ TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
313279 setStatus(WaitingForTouch);
314280 return;
315281 }
@@ -319,48 +285,42 @@ void DirectionalDragArea::unownedTouchEvent_undecided(UnownedTouchEvent *unowned
319285 if (touchPoint->state() == Qt::TouchPointReleased) {
320286 // touch has ended before recognition concluded
321287 ddaDebug("Touch has ended before recognition concluded");
322− TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
323− emitSignalIfTapped();
288+ TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
324289 setStatus(WaitingForTouch);
325290 return;
326291 }
327292
328− m_previousDampedScenePos.setX(m_dampedScenePos.x());
329− m_previousDampedScenePos.setY(m_dampedScenePos.y());
330− m_dampedScenePos.update(touchScenePos);
331− updateVelocityCalculator(touchScenePos);
332−
333− if (!pointInsideAllowedArea()) {
334− ddaDebug("Rejecting gesture because touch point is outside allowed area.");
335− TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
336− // We still wanna know when it ends for keeping the composition time window up-to-date
337− TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
338− setStatus(WaitingForTouch);
339− return;
340− }
293+ previousDampedScenePos.setX(dampedScenePos.x());
294+ previousDampedScenePos.setY(dampedScenePos.y());
295+ dampedScenePos.update(touchScenePos);
341296
342297 if (!movingInRightDirection()) {
343298 ddaDebug("Rejecting gesture because touch point is moving in the wrong direction.");
344− TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
299+ TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
345300 // We still wanna know when it ends for keeping the composition time window up-to-date
346− TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
301+ TouchRegistry::instance()->addTouchWatcher(touchId, q);
347302 setStatus(WaitingForTouch);
348303 return;
349304 }
350305
351− setPreviousPos(touchPoint->pos());
352− setPreviousScenePos(touchScenePos);
353−
354306 if (isWithinTouchCompositionWindow()) {
355307 // There's still time for some new touch to appear and ruin our party as it would be combined
356− // with our m_touchId one and therefore deny the possibility of a single-finger gesture.
308+ // with our touchId one and therefore deny the possibility of a single-finger gesture.
357309 ddaDebug("Sill within composition window. Let's wait more.");
358310 return;
359311 }
360312
361− if (movedFarEnough(touchScenePos)) {
362− TouchRegistry::instance()->requestTouchOwnership(m_touchId, this);
313+ if (movedFarEnoughAlongGestureAxis()) {
314+ TouchRegistry::instance()->requestTouchOwnership(touchId, q);
363315 setStatus(Recognized);
316+ setPublicPos(touchPoint->pos());
317+ setPublicScenePos(touchScenePos);
318+ } else if (isPastMaxDistance()) {
319+ ddaDebug("Rejecting gesture because it went farther than maxDistance without getting recognized.");
320+ TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
321+ // We still wanna know when it ends for keeping the composition time window up-to-date
322+ TouchRegistry::instance()->addTouchWatcher(touchId, q);
323+ setStatus(WaitingForTouch);
364324 } else {
365325 ddaDebug("Didn't move far enough yet. Let's wait more.");
366326 }
@@ -371,29 +331,29 @@ void DirectionalDragArea::touchEvent(QTouchEvent *event)
371331 // TODO: Consider when more than one touch starts in the same event (although it's not possible
372332 // with Mir's android-input). Have to track them all. Consider it a plus/bonus.
373333
374− ddaDebug(m_timeSource->msecsSinceReference() << " " << qPrintable(touchEventToString(event)));
334+ ddaDebug(d->timeSource->msecsSinceReference() << " " << qPrintable(touchEventToString(event)));
375335
376336 if (!isEnabled() || !isVisible()) {
377337 QQuickItem::touchEvent(event);
378338 return;
379339 }
380340
381− switch (m_status) {
382− case WaitingForTouch:
383− touchEvent_absent(event);
341+ switch (d->status) {
342+ case DirectionalDragAreaPrivate::WaitingForTouch:
343+ d->touchEvent_absent(event);
384344 break;
385− case Undecided:
386− touchEvent_undecided(event);
345+ case DirectionalDragAreaPrivate::Undecided:
346+ d->touchEvent_undecided(event);
387347 break;
388348 default: // Recognized:
389− touchEvent_recognized(event);
349+ d->touchEvent_recognized(event);
390350 break;
391351 }
392352
393− m_activeTouches.update(event);
353+ d->activeTouches.update(event);
394354}
395355
396−void DirectionalDragArea::touchEvent_absent(QTouchEvent *event)
356+void DirectionalDragAreaPrivate::touchEvent_absent(QTouchEvent *event)
397357{
398358 // TODO: accept/reject is for the whole event, not per touch id. See how that affects us.
399359
@@ -423,36 +383,40 @@ void DirectionalDragArea::touchEvent_absent(QTouchEvent *event)
423383 allGood = false;
424384 } else {
425385 // that's our candidate
426− m_touchId = touchPoint.id();
427386 newTouchPoint = &touchPoint;
428387 }
429388 }
430389 }
431390
391+ if (allGood) {
392+ allGood = sanityCheckRecognitionProperties();
393+ if (!allGood) {
394+ qWarning("DirectionalDragArea: recognition properties are wrongly set. Gesture recognition"
395+ " is impossible");
396+ }
397+ }
398+
432399 if (allGood) {
433400 Q_ASSERT(newTouchPoint);
434401
435− m_startPos = newTouchPoint->pos();
436− m_startScenePos = newTouchPoint->scenePos();
437− m_touchId = newTouchPoint->id();
438− m_dampedScenePos.reset(m_startScenePos);
439− m_velocityCalculator->setTrackedPosition(0.);
440− m_velocityCalculator->reset();
441− m_numSamplesOnLastSpeedCheck = 0;
442− m_silenceTime = 0;
443− setPreviousPos(m_startPos);
444− setPreviousScenePos(m_startScenePos);
402+ startPos = newTouchPoint->pos();
403+ startScenePos = newTouchPoint->scenePos();
404+ touchId = newTouchPoint->id();
405+ dampedScenePos.reset(startScenePos);
406+ setPublicPos(startPos);
407+
408+ setPublicScenePos(startScenePos);
445409 updateSceneDirectionVector();
446410
447411 if (recognitionIsDisabled()) {
448412 // Behave like a dumb TouchArea
449413 ddaDebug("Gesture recognition is disabled. Requesting touch ownership immediately.");
450− TouchRegistry::instance()->requestTouchOwnership(m_touchId, this);
414+ TouchRegistry::instance()->requestTouchOwnership(touchId, q);
451415 setStatus(Recognized);
452416 event->accept();
453417 } else {
454418 // just monitor the touch points for now.
455− TouchRegistry::instance()->addCandidateOwnerForTouch(m_touchId, this);
419+ TouchRegistry::instance()->addCandidateOwnerForTouch(touchId, q);
456420
457421 setStatus(Undecided);
458422 // Let the item below have it. We will monitor it and grab it later if a gesture
@@ -465,12 +429,11 @@ void DirectionalDragArea::touchEvent_absent(QTouchEvent *event)
465429 }
466430}
467431
468−void DirectionalDragArea::touchEvent_undecided(QTouchEvent *event)
432+void DirectionalDragAreaPrivate::touchEvent_undecided(QTouchEvent *event)
469433{
470− Q_ASSERT(event->type() == QEvent::TouchBegin);
471434 Q_ASSERT(fetchTargetTouchPoint(event) == nullptr);
472435
473− // We're not interested in new touch points. We already have our candidate (m_touchId).
436+ // We're not interested in new touch points. We already have our candidate (touchId).
474437 // But we do want to know when those new touches end for keeping the composition time
475438 // window up-to-date
476439 event->ignore();
@@ -480,63 +443,60 @@ void DirectionalDragArea::touchEvent_undecided(QTouchEvent *event)
480443 // multi-finger drags are not accepted
481444 ddaDebug("Multi-finger drags are not accepted");
482445
483− TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
446+ TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
484447 // We still wanna know when it ends for keeping the composition time window up-to-date
485− TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
448+ TouchRegistry::instance()->addTouchWatcher(touchId, q);
486449
487450 setStatus(WaitingForTouch);
488451 }
489452}
490453
491−void DirectionalDragArea::touchEvent_recognized(QTouchEvent *event)
454+void DirectionalDragAreaPrivate::touchEvent_recognized(QTouchEvent *event)
492455{
493456 const QTouchEvent::TouchPoint *touchPoint = fetchTargetTouchPoint(event);
494457
495458 if (!touchPoint) {
496− qCritical() << "DirectionalDragArea[status=Recognized]: touch " << m_touchId
459+ qCritical() << "DirectionalDragArea[status=Recognized]: touch " << touchId
497460 << "missing from QTouchEvent without first reaching state Qt::TouchPointReleased. "
498461 "Considering it as released.";
499462 setStatus(WaitingForTouch);
500463 } else {
501− setPreviousPos(touchPoint->pos());
502− setPreviousScenePos(touchPoint->scenePos());
464+ setPublicPos(touchPoint->pos());
465+ setPublicScenePos(touchPoint->scenePos());
503466
504467 if (touchPoint->state() == Qt::TouchPointReleased) {
505− emitSignalIfTapped();
506468 setStatus(WaitingForTouch);
507469 }
508470 }
509471}
510472
511−void DirectionalDragArea::watchPressedTouchPoints(const QList<QTouchEvent::TouchPoint> &touchPoints)
473+void DirectionalDragAreaPrivate::watchPressedTouchPoints(const QList<QTouchEvent::TouchPoint> &touchPoints)
512474{
513475 for (int i = 0; i < touchPoints.count(); ++i) {
514476 const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i);
515477 if (touchPoint.state() == Qt::TouchPointPressed) {
516− TouchRegistry::instance()->addTouchWatcher(touchPoint.id(), this);
478+ TouchRegistry::instance()->addTouchWatcher(touchPoint.id(), q);
517479 }
518480 }
519481}
520482
521−bool DirectionalDragArea::recognitionIsDisabled() const
483+bool DirectionalDragAreaPrivate::recognitionIsDisabled() const
522484{
523− return distanceThreshold() <= 0 && compositionTime() <= 0;
485+ return immediateRecognition || (distanceThreshold <= 0 && compositionTime <= 0);
524486}
525487
526−void DirectionalDragArea::emitSignalIfTapped()
488+bool DirectionalDragAreaPrivate::sanityCheckRecognitionProperties()
527489{
528− qint64 touchDuration = m_timeSource->msecsSinceReference() - m_activeTouches.touchStartTime(m_touchId);
529− if (touchDuration <= maxTapDuration()) {
530− Q_EMIT tapped();
531− }
490+ return recognitionIsDisabled()
491+ || (distanceThreshold < maxDistance && compositionTime < maxTime);
532492}
533493
534−const QTouchEvent::TouchPoint *DirectionalDragArea::fetchTargetTouchPoint(QTouchEvent *event)
494+const QTouchEvent::TouchPoint *DirectionalDragAreaPrivate::fetchTargetTouchPoint(QTouchEvent *event)
535495{
536496 const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
537497 const QTouchEvent::TouchPoint *touchPoint = 0;
538498 for (int i = 0; i < touchPoints.size(); ++i) {
539− if (touchPoints.at(i).id() == m_touchId) {
499+ if (touchPoints.at(i).id() == touchId) {
540500 touchPoint = &touchPoints.at(i);
541501 break;
542502 }
@@ -544,39 +504,13 @@ const QTouchEvent::TouchPoint *DirectionalDragArea::fetchTargetTouchPoint(QTouch
544504 return touchPoint;
545505}
546506
547−bool DirectionalDragArea::pointInsideAllowedArea() const
507+bool DirectionalDragAreaPrivate::movingInRightDirection() const
548508{
549− // NB: Using squared values to avoid computing the square root to find
550− // the length totalMovement
551−
552− QPointF totalMovement(m_dampedScenePos.x() - m_startScenePos.x(),
553− m_dampedScenePos.y() - m_startScenePos.y());
554−
555− qreal squaredTotalMovSize = totalMovement.x() * totalMovement.x() +
556− totalMovement.y() * totalMovement.y();
557−
558− if (squaredTotalMovSize == 0.) {
559− // didn't move
560− return true;
561− }
562−
563− qreal projectedMovement = projectOntoDirectionVector(totalMovement);
564−
565−
566− qreal cosineAngleSquared = (projectedMovement * projectedMovement) / squaredTotalMovSize;
567−
568− // Same as:
569− // angle_between_movement_vector_and_gesture_direction_vector <= widening_angle
570− return cosineAngleSquared >= m_wideningFactor;
571−}
572−
573−bool DirectionalDragArea::movingInRightDirection() const
574−{
575− if (m_direction == Direction::Horizontal) {
509+ if (direction == Direction::Horizontal) {
576510 return true;
577511 } else {
578− QPointF movementVector(m_dampedScenePos.x() - m_previousDampedScenePos.x(),
579− m_dampedScenePos.y() - m_previousDampedScenePos.y());
512+ QPointF movementVector(dampedScenePos.x() - previousDampedScenePos.x(),
513+ dampedScenePos.y() - previousDampedScenePos.y());
580514
581515 qreal scalarProjection = projectOntoDirectionVector(movementVector);
582516
@@ -584,96 +518,95 @@ bool DirectionalDragArea::movingInRightDirection() const
584518 }
585519}
586520
587−bool DirectionalDragArea::movedFarEnough(const QPointF &point) const
521+bool DirectionalDragAreaPrivate::movedFarEnoughAlongGestureAxis() const
588522{
589− if (m_distanceThreshold <= 0.) {
523+ if (distanceThreshold <= 0.) {
590524 // distance threshold check is disabled
591525 return true;
592526 } else {
593− QPointF totalMovement(point.x() - m_startScenePos.x(),
594− point.y() - m_startScenePos.y());
527+ QPointF totalMovement(dampedScenePos.x() - startScenePos.x(),
528+ dampedScenePos.y() - startScenePos.y());
529+
530+ qreal scalarProjection = projectOntoDirectionVector(totalMovement);
595531
596− qreal squaredTotalMovSize = totalMovement.x() * totalMovement.x() +
597− totalMovement.y() * totalMovement.y();
532+ ddaDebug(" movedFarEnoughAlongGestureAxis: scalarProjection=" << scalarProjection
533+ << ", distanceThreshold=" << distanceThreshold);
598534
599− return squaredTotalMovSize > m_distanceThresholdSquared;
535+ if (direction == Direction::Horizontal) {
536+ return qAbs(scalarProjection) > distanceThreshold;
537+ } else {
538+ return scalarProjection > distanceThreshold;
539+ }
600540 }
601541}
602542
603−void DirectionalDragArea::checkSpeed()
543+bool DirectionalDragAreaPrivate::isPastMaxDistance() const
604544{
605− Q_ASSERT(m_status == Undecided);
545+ QPointF totalMovement(dampedScenePos.x() - startScenePos.x(),
546+ dampedScenePos.y() - startScenePos.y());
606547
607− if (m_velocityCalculator->numSamples() >= AxisVelocityCalculator::MIN_SAMPLES_NEEDED) {
608− qreal speed = qFabs(m_velocityCalculator->calculate());
609− qreal minSpeedMsecs = m_minSpeed / 1000.0;
548+ qreal squaredDistance = totalMovement.x()*totalMovement.x() + totalMovement.y()*totalMovement.y();
549+ return squaredDistance > maxDistance*maxDistance;
550+}
610551
611− if (speed < minSpeedMsecs) {
612− ddaDebug("Rejecting gesture because it's below minimum speed.");
613− TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
614− TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
615− setStatus(WaitingForTouch);
552+void DirectionalDragAreaPrivate::giveUpIfDisabledOrInvisible()
553+{
554+ if (!q->isEnabled() || !q->isVisible()) {
555+ if (status == Undecided) {
556+ TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
557+ // We still wanna know when it ends for keeping the composition time window up-to-date
558+ TouchRegistry::instance()->addTouchWatcher(touchId, q);
616559 }
617− }
618−
619− if (m_velocityCalculator->numSamples() == m_numSamplesOnLastSpeedCheck) {
620− m_silenceTime += m_recognitionTimer->interval();
621560
622− if (m_silenceTime > m_maxSilenceTime) {
623− ddaDebug("Rejecting gesture because its silence time has been exceeded.");
624− TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
625− TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
561+ if (status != WaitingForTouch) {
562+ ddaDebug("Resetting status because got disabled or made invisible");
626563 setStatus(WaitingForTouch);
627564 }
628− } else {
629− m_silenceTime = 0;
630565 }
631− m_numSamplesOnLastSpeedCheck = m_velocityCalculator->numSamples();
632566}
633567
634−void DirectionalDragArea::giveUpIfDisabledOrInvisible()
568+void DirectionalDragAreaPrivate::rejectGesture()
635569{
636− if (!isEnabled() || !isVisible()) {
637− if (m_status == Undecided) {
638− TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
639− // We still wanna know when it ends for keeping the composition time window up-to-date
640− TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
641− }
570+ if (status == Undecided) {
571+ ddaDebug("Rejecting gesture because it's taking too long to drag beyond the threshold.");
642572
643− if (m_status != WaitingForTouch) {
644− ddaDebug("Resetting status because got disabled or made invisible");
645− setStatus(WaitingForTouch);
646− }
573+ TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
574+ // We still wanna know when it ends for keeping the composition time window up-to-date
575+ TouchRegistry::instance()->addTouchWatcher(touchId, q);
576+
577+ setStatus(WaitingForTouch);
647578 }
648579}
649580
650−void DirectionalDragArea::setStatus(DirectionalDragArea::Status newStatus)
581+void DirectionalDragAreaPrivate::setStatus(Status newStatus)
651582{
652− if (newStatus == m_status)
583+ if (newStatus == status)
653584 return;
654585
655− DirectionalDragArea::Status oldStatus = m_status;
586+ Status oldStatus = status;
656587
657588 if (oldStatus == Undecided) {
658− m_recognitionTimer->stop();
589+ recognitionTimer->stop();
659590 }
660591
661− m_status = newStatus;
662− Q_EMIT statusChanged(m_status);
592+ status = newStatus;
593+ Q_EMIT statusChanged(status);
663594
664595 ddaDebug(statusToString(oldStatus) << " -> " << statusToString(newStatus));
665596
666597 switch (newStatus) {
667598 case WaitingForTouch:
668− Q_EMIT draggingChanged(false);
599+ if (oldStatus == Recognized) {
600+ Q_EMIT q->draggingChanged(false);
601+ }
602+ Q_EMIT q->pressedChanged(false);
669603 break;
670604 case Undecided:
671− m_recognitionTimer->start();
672− Q_EMIT draggingChanged(true);
605+ recognitionTimer->start();
606+ Q_EMIT q->pressedChanged(true);
673607 break;
674608 case Recognized:
675− if (oldStatus == WaitingForTouch)
676− Q_EMIT draggingChanged(true);
609+ Q_EMIT q->draggingChanged(true);
677610 break;
678611 default:
679612 // no-op
@@ -681,77 +614,126 @@ void DirectionalDragArea::setStatus(DirectionalDragArea::Status newStatus)
681614 }
682615}
683616
684−void DirectionalDragArea::setPreviousPos(const QPointF &point)
617+void DirectionalDragAreaPrivate::setPublicPos(const QPointF &point)
685618{
686− bool xChanged = m_previousPos.x() != point.x();
687− bool yChanged = m_previousPos.y() != point.y();
619+ bool xChanged = publicPos.x() != point.x();
620+ bool yChanged = publicPos.y() != point.y();
688621
689− m_previousPos = point;
622+ // Public position should not get updated while the gesture is still being recognized
623+ // (ie, Undecided status).
624+ Q_ASSERT(status == WaitingForTouch || status == Recognized);
625+
626+ if (status == Recognized && !recognitionIsDisabled()) {
627+ // When the gesture finally gets recognized, the finger will likely be
628+ // reasonably far from the edge. If we made the contentX immediately
629+ // follow the finger position it would be visually unpleasant as it
630+ // would appear right next to the user's finger out of nowhere (ie,
631+ // it would jump). Instead, we make contentX go towards the user's
632+ // finger in several steps. ie., in an animated way.
633+ QPointF delta = point - publicPos;
634+ // the trick is not to go all the way (1.0) as it would cause a sudden jump
635+ publicPos.rx() += 0.4 * delta.x();
636+ publicPos.ry() += 0.4 * delta.y();
637+ } else {
638+ // no smoothing when initializing or if gesture recognition was immediate as there will
639+ // be no jump.
640+ publicPos = point;
641+ }
690642
691643 if (xChanged) {
692− Q_EMIT touchXChanged(point.x());
693− if (Direction::isHorizontal(m_direction))
694− Q_EMIT distanceChanged(distance());
644+ Q_EMIT q->touchXChanged(publicPos.x());
645+ if (Direction::isHorizontal(direction))
646+ Q_EMIT q->distanceChanged(q->distance());
695647 }
696648
697649 if (yChanged) {
698− Q_EMIT touchYChanged(point.y());
699− if (Direction::isVertical(m_direction))
700− Q_EMIT distanceChanged(distance());
650+ Q_EMIT q->touchYChanged(publicPos.y());
651+ if (Direction::isVertical(direction))
652+ Q_EMIT q->distanceChanged(q->distance());
701653 }
702654}
703655
704−void DirectionalDragArea::setPreviousScenePos(const QPointF &point)
656+void DirectionalDragAreaPrivate::setPublicScenePos(const QPointF &point)
705657{
706− bool xChanged = m_previousScenePos.x() != point.x();
707− bool yChanged = m_previousScenePos.y() != point.y();
658+ bool xChanged = publicScenePos.x() != point.x();
659+ bool yChanged = publicScenePos.y() != point.y();
708660
709661 if (!xChanged && !yChanged)
710662 return;
711663
712− qreal oldSceneDistance = sceneDistance();
713− m_previousScenePos = point;
664+ // Public position should not get updated while the gesture is still being recognized
665+ // (ie, Undecided status).
666+ Q_ASSERT(status == WaitingForTouch || status == Recognized);
667+
668+ qreal oldSceneDistance = sceneDistance;
669+
670+ if (status == Recognized && !recognitionIsDisabled()) {
671+ // When the gesture finally gets recognized, the finger will likely be
672+ // reasonably far from the edge. If we made the contentX immediately
673+ // follow the finger position it would be visually unpleasant as it
674+ // would appear right next to the user's finger out of nowhere (ie,
675+ // it would jump). Instead, we make contentX go towards the user's
676+ // finger in several steps. ie., in an animated way.
677+ QPointF delta = point - publicScenePos;
678+ // the trick is not to go all the way (1.0) as it would cause a sudden jump
679+ publicScenePos.rx() += 0.4 * delta.x();
680+ publicScenePos.ry() += 0.4 * delta.y();
681+ } else {
682+ // no smoothing when initializing or if gesture recognition was immediate as there will
683+ // be no jump.
684+ publicScenePos = point;
685+ }
686+
714687 updateSceneDistance();
715688
716− if (oldSceneDistance != sceneDistance()) {
717− Q_EMIT sceneDistanceChanged(sceneDistance());
689+ if (oldSceneDistance != sceneDistance) {
690+ Q_EMIT q->sceneDistanceChanged(sceneDistance);
718691 }
719692
720693 if (xChanged) {
721− Q_EMIT touchSceneXChanged(point.x());
694+ Q_EMIT q->touchSceneXChanged(publicScenePos.x());
722695 }
723696
724697 if (yChanged) {
725− Q_EMIT touchSceneYChanged(point.y());
698+ Q_EMIT q->touchSceneYChanged(publicScenePos.y());
726699 }
727700}
728701
729−void DirectionalDragArea::updateVelocityCalculator(const QPointF &scenePos)
702+bool DirectionalDragAreaPrivate::isWithinTouchCompositionWindow()
730703{
731− QPointF totalSceneMovement = scenePos - m_startScenePos;
732−
733− qreal scalarProjection = projectOntoDirectionVector(totalSceneMovement);
704+ return
705+ compositionTime > 0 &&
706+ !activeTouches.isEmpty() &&
707+ timeSource->msecsSinceReference() <=
708+ activeTouches.mostRecentStartTime() + (qint64)compositionTime;
709+}
734710
735− m_velocityCalculator->setTrackedPosition(scalarProjection);
711+void DirectionalDragArea::itemChange(ItemChange change, const ItemChangeData &value)
712+{
713+ if (change == QQuickItem::ItemSceneChange) {
714+ if (value.window != nullptr) {
715+ // TODO: Handle window->screen() changes (ie window changing screens)
716+ qreal pixelsPerMm = value.window->screen()->physicalDotsPerInch() / 25.4;
717+ d->setPixelsPerMm(pixelsPerMm);
718+ }
719+ }
736720}
737721
738−bool DirectionalDragArea::isWithinTouchCompositionWindow()
722+void DirectionalDragAreaPrivate::setPixelsPerMm(qreal pixelsPerMm)
739723{
740− return
741− compositionTime() > 0 &&
742− !m_activeTouches.isEmpty() &&
743− m_timeSource->msecsSinceReference() <=
744− m_activeTouches.mostRecentStartTime() + (qint64)compositionTime();
724+ dampedScenePos.setMaxDelta(1. * pixelsPerMm);
725+ setDistanceThreshold(4. * pixelsPerMm);
726+ maxDistance = 10. * pixelsPerMm;
745727}
746728
747729//************************** ActiveTouchesInfo **************************
748730
749−DirectionalDragArea::ActiveTouchesInfo::ActiveTouchesInfo(const SharedTimeSource &timeSource)
731+ActiveTouchesInfo::ActiveTouchesInfo(const SharedTimeSource &timeSource)
750732 : m_timeSource(timeSource)
751733{
752734}
753735
754−void DirectionalDragArea::ActiveTouchesInfo::update(QTouchEvent *event)
736+void ActiveTouchesInfo::update(QTouchEvent *event)
755737{
756738 if (!(event->touchPointStates() & (Qt::TouchPointPressed | Qt::TouchPointReleased))) {
757739 // nothing to update
@@ -773,7 +755,7 @@ void DirectionalDragArea::ActiveTouchesInfo::update(QTouchEvent *event)
773755}
774756
775757#if ACTIVETOUCHESINFO_DEBUG
776−QString DirectionalDragArea::ActiveTouchesInfo::toString()
758+QString ActiveTouchesInfo::toString()
777759{
778760 QString string = "(";
779761
@@ -791,7 +773,7 @@ QString DirectionalDragArea::ActiveTouchesInfo::toString()
791773}
792774#endif // ACTIVETOUCHESINFO_DEBUG
793775
794−void DirectionalDragArea::ActiveTouchesInfo::addTouchPoint(int touchId)
776+void ActiveTouchesInfo::addTouchPoint(int touchId)
795777{
796778 ActiveTouchInfo &activeTouchInfo = m_touchInfoPool.getEmptySlot();
797779 activeTouchInfo.id = touchId;
@@ -802,7 +784,7 @@ void DirectionalDragArea::ActiveTouchesInfo::addTouchPoint(int touchId)
802784 #endif
803785}
804786
805−qint64 DirectionalDragArea::ActiveTouchesInfo::touchStartTime(int touchId)
787+qint64 ActiveTouchesInfo::touchStartTime(int touchId)
806788{
807789 qint64 result = -1;
808790
@@ -819,7 +801,7 @@ qint64 DirectionalDragArea::ActiveTouchesInfo::touchStartTime(int touchId)
819801 return result;
820802}
821803
822−void DirectionalDragArea::ActiveTouchesInfo::removeTouchPoint(int touchId)
804+void ActiveTouchesInfo::removeTouchPoint(int touchId)
823805{
824806 m_touchInfoPool.forEach([&](Pool<ActiveTouchInfo>::Iterator &touchInfo) {
825807 if (touchId == touchInfo->id) {
@@ -835,7 +817,7 @@ void DirectionalDragArea::ActiveTouchesInfo::removeTouchPoint(int touchId)
835817 #endif
836818}
837819
838−qint64 DirectionalDragArea::ActiveTouchesInfo::mostRecentStartTime()
820+qint64 ActiveTouchesInfo::mostRecentStartTime()
839821{
840822 Q_ASSERT(!m_touchInfoPool.isEmpty());
841823
@@ -851,11 +833,11 @@ qint64 DirectionalDragArea::ActiveTouchesInfo::mostRecentStartTime()
851833 return highestStartTime;
852834}
853835
854−void DirectionalDragArea::updateSceneDirectionVector()
836+void DirectionalDragAreaPrivate::updateSceneDirectionVector()
855837{
856838 QPointF localOrigin(0., 0.);
857839 QPointF localDirection;
858− switch (m_direction) {
840+ switch (direction) {
859841 case Direction::Upwards:
860842 localDirection.rx() = 0.;
861843 localDirection.ry() = -1.;
@@ -873,14 +855,31 @@ void DirectionalDragArea::updateSceneDirectionVector()
873855 localDirection.ry() = 0.;
874856 break;
875857 }
876− QPointF sceneOrigin = mapToScene(localOrigin);
877− QPointF sceneDirection = mapToScene(localDirection);
878− m_sceneDirectionVector = sceneDirection - sceneOrigin;
858+ QPointF sceneOrigin = q->mapToScene(localOrigin);
859+ QPointF sceneDirection = q->mapToScene(localDirection);
860+ sceneDirectionVector = sceneDirection - sceneOrigin;
861+}
862+
863+qreal DirectionalDragAreaPrivate::projectOntoDirectionVector(const QPointF &sceneVector) const
864+{
865+ // same as dot product as sceneDirectionVector is a unit vector
866+ return sceneVector.x() * sceneDirectionVector.x() +
867+ sceneVector.y() * sceneDirectionVector.y();
879868}
880869
881−qreal DirectionalDragArea::projectOntoDirectionVector(const QPointF &sceneVector) const
870+DirectionalDragAreaPrivate::DirectionalDragAreaPrivate(DirectionalDragArea *q)
871+ : q(q)
872+ , status(WaitingForTouch)
873+ , sceneDistance(0)
874+ , touchId(-1)
875+ , direction(Direction::Rightwards)
876+ , distanceThreshold(0)
877+ , distanceThresholdSquared(0.)
878+ , maxTime(400)
879+ , compositionTime(60)
880+ , immediateRecognition(false)
881+ , recognitionTimer(nullptr)
882+ , timeSource(new RealTimeSource)
883+ , activeTouches(timeSource)
882884{
883− // same as dot product as m_sceneDirectionVector is a unit vector
884− return sceneVector.x() * m_sceneDirectionVector.x() +
885− sceneVector.y() * m_sceneDirectionVector.y();
886885}
plugins/Ubuntu/Gestures/DirectionalDragArea.h+29−202
@@ -18,7 +18,6 @@
1818#define DIRECTIONAL_DRAG_AREA_H
1919
2020#include <QtQuick/QQuickItem>
21−#include "AxisVelocityCalculator.h"
2221#include "UbuntuGesturesQmlGlobal.h"
2322#include "Damper.h"
2423#include "Direction.h"
@@ -29,6 +28,7 @@
2928
3029class TouchOwnershipEvent;
3130class UnownedTouchEvent;
31+class DirectionalDragAreaPrivate;
3232
3333/*
3434 An area that detects axis-aligned single-finger drag gestures
@@ -61,95 +61,31 @@ class UBUNTUGESTURESQML_EXPORT DirectionalDragArea : public QQuickItem {
6161 Q_PROPERTY(qreal touchSceneX READ touchSceneX NOTIFY touchSceneXChanged)
6262 Q_PROPERTY(qreal touchSceneY READ touchSceneY NOTIFY touchSceneYChanged)
6363
64− // The current status of the directional drag gesture area.
65− Q_PROPERTY(Status status READ status NOTIFY statusChanged)
66−
6764 // Whether a drag gesture is taking place
68− // This will be true as long as status is Undecided or Recognized
69− // When a gesture gets rejected, dragging turns to false.
7065 Q_PROPERTY(bool dragging READ dragging NOTIFY draggingChanged)
7166
72− /////
73− // stuff that will be set in stone at some point
74−
75− // How far the touch point can move away from its expected position before
76− // it causes a rejection in the gesture recognition. This is to compensate
77− // for both noise in the touch input signal and for the natural irregularities
78− // in the finger movement.
79− // Proper value is likely device-specific.
80− Q_PROPERTY(qreal maxDeviation READ maxDeviation WRITE setMaxDeviation NOTIFY maxDeviationChanged)
81−
82− // Widening angle, in degrees
83− // It's roughly the maximum angle a touch point can make relative to the
84− // axis defined by the compoment's direction for it to be recognized as a
85− // directional drag.
86− Q_PROPERTY(qreal wideningAngle READ wideningAngle WRITE setWideningAngle
87− NOTIFY wideningAngleChanged)
88−
89− // How far a touch point has to move from its initial position in order for
90− // it to be recognized as a directional drag.
91− Q_PROPERTY(qreal distanceThreshold READ distanceThreshold WRITE setDistanceThreshold
92− NOTIFY distanceThresholdChanged)
93−
94− // Minimum speed a gesture needs to have in order to be recognized as a
95− // directional drag.
96− // In pixels per second
97− Q_PROPERTY(qreal minSpeed READ minSpeed WRITE setMinSpeed NOTIFY minSpeedChanged)
98−
99− // A gesture will be rejected if more than maxSilenceTime milliseconds has
100− // passed since we last got an input event from it (during Undecided state).
101− //
102− // Silence (i.e., lack of new input events) doesn't necessarily mean that the user's
103− // finger is still (zero drag speed). In some cases the finger might be moving but
104− // the driver's high noise filtering might cause those silence periods, specially
105− // in the moments succeeding a press (talking about Galaxy Nexus here).
106− Q_PROPERTY(int maxSilenceTime READ maxSilenceTime
107− WRITE setMaxSilenceTime
108− NOTIFY maxSilenceTimeChanged)
67+ // Whether the drag area is pressed.
68+ Q_PROPERTY(bool pressed READ pressed NOTIFY pressedChanged)
10969
70+ // Whether a gesture should be Recognized as soon a touch lands on the area.
71+ // With this property enabled it will work pretty much like a MultiPointTouchArea,
72+ // just with a different API.
11073 //
111− /////
112−
113− // Maximum time (in milliseconds) after the start of a given touch point where
114− // subsequent touch starts are grouped with the first one into an N-touches gesture
115− // (e.g. a two-fingers tap or drag).
116− Q_PROPERTY(int compositionTime READ compositionTime
117− WRITE setCompositionTime
118− NOTIFY compositionTimeChanged)
74+ // It's false by default. In most cases you will not want that enabled.
75+ Q_PROPERTY(bool immediateRecognition
76+ READ immediateRecognition
77+ WRITE setImmediateRecognition
78+ NOTIFY immediateRecognitionChanged)
11979
12080 Q_ENUMS(Direction)
121− Q_ENUMS(Status)
12281public:
12382 DirectionalDragArea(QQuickItem *parent = 0);
12483
12584 Direction::Type direction() const;
12685 void setDirection(Direction::Type);
12786
128− // Describes the state of the directional drag gesture.
129− enum Status {
130− // Waiting for a new touch point to land on this area. No gesture is being processed
131− // or tracked.
132− WaitingForTouch,
133−
134− // A touch point has landed on this area but it's not know yet whether it is
135− // performing a drag in the correct direction.
136− // If it's decided that the touch point is not performing a directional drag gesture,
137− // it will be rejected/ignored and status will return to WaitingForTouch.
138− Undecided, //Recognizing,
139−
140− // There's a touch point in this area and it performed a drag in the correct
141− // direction.
142− //
143− // Once recognized, the gesture state will move back to WaitingForTouch only once
144− // that touch point ends. The gesture will remain in the Recognized state even if
145− // the touch point starts moving in other directions or halts.
146− Recognized,
147− };
148− Status status() const { return m_status; }
149−
15087 qreal distance() const;
15188 qreal sceneDistance() const;
152− void updateSceneDistance();
15389
15490 qreal touchX() const;
15591 qreal touchY() const;
@@ -157,152 +93,43 @@ public:
15793 qreal touchSceneX() const;
15894 qreal touchSceneY() const;
15995
160− bool dragging() const { return (m_status == Undecided) || (m_status == Recognized); }
161−
162− qreal maxDeviation() const { return m_dampedScenePos.maxDelta(); }
163− void setMaxDeviation(qreal value);
164−
165− qreal wideningAngle() const;
166− void setWideningAngle(qreal value);
167−
168− qreal distanceThreshold() const { return m_distanceThreshold; }
169− void setDistanceThreshold(qreal value);
170−
171− qreal minSpeed() const { return m_minSpeed; }
172− void setMinSpeed(qreal value);
96+ bool dragging() const;
17397
174− int maxSilenceTime() const { return m_maxSilenceTime; }
175− void setMaxSilenceTime(int value);
98+ bool pressed() const;
17699
177− int compositionTime() const { return m_compositionTime; }
178− void setCompositionTime(int value);
179−
180− // Replaces the existing Timer with the given one.
181− //
182− // Useful for providing a fake timer when testing.
183− void setRecognitionTimer(UbuntuGestures::AbstractTimer *timer);
184−
185− // Useful for testing, where a fake time source can be supplied
186− void setTimeSource(const UbuntuGestures::SharedTimeSource &timeSource);
100+ bool immediateRecognition() const;
101+ void setImmediateRecognition(bool enabled);
187102
188103 bool event(QEvent *e) override;
189104
190− // Maximum time, in milliseconds, between a press and a release, for a touch
191− // sequence to be considered a tap.
192− int maxTapDuration() const { return 300; }
105+ /*
106+ In qmltests, sequences of touch events are sent all at once, unlike in "real life".
107+ Also qmltests might run really slowly, e.g. when run from inside virtual machines.
108+ Thus to remove a variable that qmltests cannot really control, namely time, this
109+ function removes all constraints that are sensible to elapsed time.
110+
111+ This effectively makes the DirectionalDragArea easier to fool.
112+ */
113+ Q_INVOKABLE void removeTimeConstraints();
193114
194115Q_SIGNALS:
195116 void directionChanged(Direction::Type direction);
196− void statusChanged(Status value);
197117 void draggingChanged(bool value);
118+ void pressedChanged(bool value);
198119 void distanceChanged(qreal value);
199120 void sceneDistanceChanged(qreal value);
200− void maxDeviationChanged(qreal value);
201− void wideningAngleChanged(qreal value);
202− void distanceThresholdChanged(qreal value);
203− void minSpeedChanged(qreal value);
204− void maxSilenceTimeChanged(int value);
205− void compositionTimeChanged(int value);
206121 void touchXChanged(qreal value);
207122 void touchYChanged(qreal value);
208123 void touchSceneXChanged(qreal value);
209124 void touchSceneYChanged(qreal value);
210−
211− // TODO: I would rather not have such signal as it has nothing to do with drag gestures.
212− // Remove when no longer used or move its implementation to the QML code that uses it
213− // See maxTapDuration()
214− void tapped();
125+ void immediateRecognitionChanged(bool value);
215126
216127protected:
217128 virtual void touchEvent(QTouchEvent *event);
129+ virtual void itemChange(ItemChange change, const ItemChangeData &value);
218130
219−private Q_SLOTS:
220− void checkSpeed();
221− void giveUpIfDisabledOrInvisible();
222−
223−private:
224− void touchEvent_absent(QTouchEvent *event);
225− void touchEvent_undecided(QTouchEvent *event);
226− void touchEvent_recognized(QTouchEvent *event);
227− bool pointInsideAllowedArea() const;
228− bool movingInRightDirection() const;
229− bool movedFarEnough(const QPointF &point) const;
230− const QTouchEvent::TouchPoint *fetchTargetTouchPoint(QTouchEvent *event);
231− void setStatus(Status newStatus);
232− void setPreviousPos(const QPointF &point);
233− void setPreviousScenePos(const QPointF &point);
234− void updateVelocityCalculator(const QPointF &point);
235− bool isWithinTouchCompositionWindow();
236− void updateSceneDirectionVector();
237− // returns the scalar projection between the given vector (in scene coordinates)
238− // and m_sceneDirectionVector
239− qreal projectOntoDirectionVector(const QPointF &sceneVector) const;
240− void touchOwnershipEvent(TouchOwnershipEvent *event);
241− void unownedTouchEvent(UnownedTouchEvent *event);
242− void unownedTouchEvent_undecided(UnownedTouchEvent *unownedTouchEvent);
243− void watchPressedTouchPoints(const QList<QTouchEvent::TouchPoint> &touchPoints);
244− bool recognitionIsDisabled() const;
245− void emitSignalIfTapped();
246−
247− Status m_status;
248−
249− QPointF m_startPos;
250− QPointF m_startScenePos;
251− QPointF m_previousPos;
252− QPointF m_previousScenePos;
253− qreal m_sceneDistance;
254− int m_touchId;
255−
256− // A movement damper is used in some of the gesture recognition calculations
257− // to get rid of noise or small oscillations in the touch position.
258− DampedPointF m_dampedScenePos;
259− QPointF m_previousDampedScenePos;
260−
261− // Unit vector in scene coordinates describing the direction of the gesture recognition
262− QPointF m_sceneDirectionVector;
263−
264− Direction::Type m_direction;
265− qreal m_wideningAngle; // in degrees
266− qreal m_wideningFactor; // it's pow(cosine(m_wideningAngle), 2)
267− qreal m_distanceThreshold;
268− qreal m_distanceThresholdSquared; // it's pow(m_distanceThreshold, 2)
269− qreal m_minSpeed;
270− int m_maxSilenceTime; // in milliseconds
271− int m_silenceTime; // in milliseconds
272− int m_compositionTime; // in milliseconds
273− int m_numSamplesOnLastSpeedCheck;
274− UbuntuGestures::AbstractTimer *m_recognitionTimer;
275− AxisVelocityCalculator *m_velocityCalculator;
276−
277− UbuntuGestures::SharedTimeSource m_timeSource;
278−
279− // Information about an active touch point
280− struct ActiveTouchInfo {
281− ActiveTouchInfo() : id(-1), startTime(-1) {}
282− bool isValid() const { return id != -1; }
283− void reset() { id = -1; }
284− int id;
285− qint64 startTime;
286− };
287− class ActiveTouchesInfo {
288− public:
289− ActiveTouchesInfo(const UbuntuGestures::SharedTimeSource &timeSource);
290− void update(QTouchEvent *event);
291− qint64 touchStartTime(int id);
292− bool isEmpty() const { return m_touchInfoPool.isEmpty(); }
293− qint64 mostRecentStartTime();
294− UbuntuGestures::SharedTimeSource m_timeSource;
295− private:
296− void addTouchPoint(int touchId);
297− void removeTouchPoint(int touchId);
298− #if ACTIVETOUCHESINFO_DEBUG
299− QString toString();
300− #endif
301−
302− Pool<ActiveTouchInfo> m_touchInfoPool;
303− } m_activeTouches;
304−
305− friend class tst_DirectionalDragArea;
131+public: // so tests can access it
132+ DirectionalDragAreaPrivate *d;
306133};
307134
308135#endif // DIRECTIONAL_DRAG_AREA_H
plugins/Ubuntu/Gestures/DirectionalDragArea_p.hnew file+167
@@ -0,0 +1,167 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef DIRECTIONAL_DRAG_AREA_PRIV_H
18+#define DIRECTIONAL_DRAG_AREA_PRIV_H
19+
20+// Information about an active touch point
21+struct UBUNTUGESTURESQML_EXPORT ActiveTouchInfo {
22+ ActiveTouchInfo() : id(-1), startTime(-1) {}
23+ bool isValid() const { return id != -1; }
24+ void reset() { id = -1; }
25+ int id;
26+ qint64 startTime;
27+};
28+class UBUNTUGESTURESQML_EXPORT ActiveTouchesInfo {
29+public:
30+ ActiveTouchesInfo(const UbuntuGestures::SharedTimeSource &timeSource);
31+ void update(QTouchEvent *event);
32+ qint64 touchStartTime(int id);
33+ bool isEmpty() const { return m_touchInfoPool.isEmpty(); }
34+ qint64 mostRecentStartTime();
35+ UbuntuGestures::SharedTimeSource m_timeSource;
36+private:
37+ void addTouchPoint(int touchId);
38+ void removeTouchPoint(int touchId);
39+ #if ACTIVETOUCHESINFO_DEBUG
40+ QString toString();
41+ #endif
42+
43+ Pool<ActiveTouchInfo> m_touchInfoPool;
44+};
45+
46+class UBUNTUGESTURESQML_EXPORT DirectionalDragAreaPrivate : public QObject {
47+ Q_OBJECT
48+
49+ Q_ENUMS(Status)
50+public:
51+ DirectionalDragAreaPrivate(DirectionalDragArea *q);
52+
53+public Q_SLOTS:
54+ void giveUpIfDisabledOrInvisible();
55+ void rejectGesture();
56+
57+public:
58+ // Describes the state of the directional drag gesture.
59+ enum Status {
60+ // Waiting for a new touch point to land on this area. No gesture is being processed
61+ // or tracked.
62+ WaitingForTouch,
63+
64+ // A touch point has landed on this area but it's not know yet whether it is
65+ // performing a drag in the correct direction.
66+ // If it's decided that the touch point is not performing a directional drag gesture,
67+ // it will be rejected/ignored and status will return to WaitingForTouch.
68+ Undecided, //Recognizing,
69+
70+ // There's a touch point in this area and it performed a drag in the correct
71+ // direction.
72+ //
73+ // Once recognized, the gesture state will move back to WaitingForTouch only once
74+ // that touch point ends. The gesture will remain in the Recognized state even if
75+ // the touch point starts moving in other directions or halts.
76+ Recognized,
77+ };
78+
79+ void touchEvent_absent(QTouchEvent *event);
80+ void touchEvent_undecided(QTouchEvent *event);
81+ void touchEvent_recognized(QTouchEvent *event);
82+ bool movingInRightDirection() const;
83+ bool movedFarEnoughAlongGestureAxis() const;
84+ bool isPastMaxDistance() const;
85+ const QTouchEvent::TouchPoint *fetchTargetTouchPoint(QTouchEvent *event);
86+ void setStatus(Status newStatus);
87+ void setPublicPos(const QPointF &point);
88+ void setPublicScenePos(const QPointF &point);
89+ bool isWithinTouchCompositionWindow();
90+ void updateSceneDirectionVector();
91+ // returns the scalar projection between the given vector (in scene coordinates)
92+ // and m_sceneDirectionVector
93+ qreal projectOntoDirectionVector(const QPointF &sceneVector) const;
94+ void touchOwnershipEvent(TouchOwnershipEvent *event);
95+ void unownedTouchEvent(UnownedTouchEvent *event);
96+ void unownedTouchEvent_undecided(UnownedTouchEvent *unownedTouchEvent);
97+ void watchPressedTouchPoints(const QList<QTouchEvent::TouchPoint> &touchPoints);
98+ bool recognitionIsDisabled() const;
99+ bool sanityCheckRecognitionProperties();
100+ void updateSceneDistance();
101+ void setMaxTime(int value);
102+ void setDistanceThreshold(qreal value);
103+ void setPixelsPerMm(qreal pixelsPerMm);
104+ QString objectName() const { return q->objectName(); }
105+
106+ // Replaces the existing Timer with the given one.
107+ //
108+ // Useful for providing a fake timer when testing.
109+ void setRecognitionTimer(UbuntuGestures::AbstractTimer *timer);
110+
111+ // Useful for testing, where a fake time source can be supplied
112+ void setTimeSource(const UbuntuGestures::SharedTimeSource &timeSource);
113+
114+ DirectionalDragArea *q;
115+
116+ // The current status of the directional drag gesture area.
117+ Status status;
118+
119+ QPointF startPos;
120+ QPointF startScenePos;
121+ qreal sceneDistance;
122+ int touchId;
123+
124+ // The touch position exposed in the public API.
125+ // It only starts to move once the gesture gets recognized.
126+ QPointF publicPos;
127+ QPointF publicScenePos;
128+
129+ // A movement damper is used in some of the gesture recognition calculations
130+ // to get rid of noise or small oscillations in the touch position.
131+ DampedPointF dampedScenePos;
132+ QPointF previousDampedScenePos;
133+
134+ // Unit vector in scene coordinates describing the direction of the gesture recognition
135+ QPointF sceneDirectionVector;
136+
137+ Direction::Type direction;
138+
139+ // How far a touch point has to move from its initial position along the gesture axis in order
140+ // for it to be recognized as a directional drag.
141+ qreal distanceThreshold;
142+ qreal distanceThresholdSquared; // it's pow(distanceThreshold, 2)
143+
144+ // Maximum time (in milliseconds) the gesture can take to go beyond the distance threshold
145+ int maxTime;
146+
147+ // Maximum distance the gesture can go without crossing the axis-aligned distance threshold
148+ qreal maxDistance;
149+
150+ // Maximum time (in milliseconds) after the start of a given touch point where
151+ // subsequent touch starts are grouped with the first one into an N-touches gesture
152+ // (e.g. a two-fingers tap or drag).
153+ int compositionTime;
154+
155+ bool immediateRecognition;
156+
157+ UbuntuGestures::AbstractTimer *recognitionTimer;
158+
159+ UbuntuGestures::SharedTimeSource timeSource;
160+
161+ ActiveTouchesInfo activeTouches;
162+
163+Q_SIGNALS:
164+ void statusChanged(Status value);
165+};
166+
167+#endif // DIRECTIONAL_DRAG_AREA_PRIV_H
plugins/Ubuntu/Gestures/TouchDispatcher.cpp+88−53
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2014 Canonical, Ltd.
2+ * Copyright (C) 2014-2015 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -28,8 +28,11 @@
2828#define TOUCHDISPATCHER_DEBUG 0
2929
3030#if TOUCHDISPATCHER_DEBUG
31+#define ugDebug(params) qDebug().nospace() << "[TouchDispatcher(" << this << ")] " << params
3132#include <DebugHelpers.h>
32−#endif
33+#else // TOUCHDISPATCHER_DEBUG
34+#define ugDebug(params) ((void)0)
35+#endif // TOUCHDISPATCHER_DEBUG
3336
3437TouchDispatcher::TouchDispatcher()
3538 : m_status(NoActiveTouch)
@@ -44,13 +47,12 @@ void TouchDispatcher::setTargetItem(QQuickItem *target)
4447 m_targetItem = target;
4548 if (m_status != NoActiveTouch) {
4649 qWarning("[TouchDispatcher] Changing target item in the middle of a touch stream");
47− m_status = TargetRejectedTouches;
50+ setStatus(TargetRejectedTouches);
4851 }
4952 }
5053}
5154
52−void TouchDispatcher::dispatch(QEvent::Type eventType,
53− QTouchDevice *device,
55+void TouchDispatcher::dispatch(QTouchDevice *device,
5456 Qt::KeyboardModifiers modifiers,
5557 const QList<QTouchEvent::TouchPoint> &touchPoints,
5658 QWindow *window,
@@ -61,6 +63,8 @@ void TouchDispatcher::dispatch(QEvent::Type eventType,
6163 return;
6264 }
6365
66+ QEvent::Type eventType = resolveEventType(touchPoints);
67+
6468 if (eventType == QEvent::TouchBegin) {
6569 dispatchTouchBegin(device, modifiers, touchPoints, window, timestamp);
6670
@@ -72,15 +76,13 @@ void TouchDispatcher::dispatch(QEvent::Type eventType,
7276 dispatchAsMouse(device, modifiers, touchPoints, timestamp);
7377 } else {
7478 Q_ASSERT(m_status == TargetRejectedTouches);
75− #if TOUCHDISPATCHER_DEBUG
76− qDebug() << "[TouchDispatcher] Not dispatching touch event to" << m_targetItem.data()
77− << "because it already rejected the touch stream.";
78− #endif
79+ ugDebug("Not dispatching touch event to " << m_targetItem.data()
80+ << "because it already rejected the touch stream.");
7981 // Do nothing
8082 }
8183
8284 if (eventType == QEvent::TouchEnd) {
83− m_status = NoActiveTouch;
85+ setStatus(NoActiveTouch);
8486 m_touchMouseId = -1;
8587 }
8688
@@ -103,10 +105,7 @@ void TouchDispatcher::dispatchTouchBegin(
103105 QQuickItem *targetItem = m_targetItem.data();
104106
105107 if (!targetItem->isEnabled() || !targetItem->isVisible()) {
106− #if TOUCHDISPATCHER_DEBUG
107− qDebug() << "[TouchDispatcher] Cannot dispatch touch event to" << targetItem
108− << "because it's disabled or invisible.";
109− #endif
108+ ugDebug("Cannot dispatch touch event to " << targetItem << " because it's disabled or invisible.");
110109 return;
111110 }
112111
@@ -118,62 +117,46 @@ void TouchDispatcher::dispatchTouchBegin(
118117 createQTouchEvent(QEvent::TouchBegin, device, modifiers, targetTouchPoints, window, timestamp));
119118
120119
121− #if TOUCHDISPATCHER_DEBUG
122− qDebug() << "[TouchDispatcher] dispatching" << qPrintable(touchEventToString(touchEvent.data()))
123− << "to" << targetItem;
124− #endif
120+ ugDebug("dispatching " << qPrintable(touchEventToString(touchEvent.data()))
121+ << " to " << targetItem);
125122 QCoreApplication::sendEvent(targetItem, touchEvent.data());
126123
127124
128125 if (touchEvent->isAccepted()) {
129− #if TOUCHDISPATCHER_DEBUG
130− qDebug() << "[TouchDispatcher] Item accepted the touch event.";
131− #endif
132− m_status = DeliveringTouchEvents;
126+ ugDebug("Item accepted the touch event.");
127+ setStatus(DeliveringTouchEvents);
133128 } else if (targetItem->acceptedMouseButtons() & Qt::LeftButton) {
134− #if TOUCHDISPATCHER_DEBUG
135− qDebug() << "[TouchDispatcher] Item rejected the touch event. Trying a QMouseEvent";
136− #endif
129+ ugDebug("Item rejected the touch event. Trying a QMouseEvent");
137130 // NB: Arbitrarily chose the first touch point to emulate the mouse pointer
138131 QScopedPointer<QMouseEvent> mouseEvent(
139132 touchToMouseEvent(QEvent::MouseButtonPress, targetTouchPoints.at(0), timestamp,
140133 modifiers, false /* transformNeeded */));
141134 Q_ASSERT(targetTouchPoints.at(0).state() == Qt::TouchPointPressed);
142135
143− #if TOUCHDISPATCHER_DEBUG
144− qDebug() << "[TouchDispatcher] dispatching" << qPrintable(mouseEventToString(mouseEvent.data()))
145− << "to" << m_targetItem.data();
146− #endif
136+ ugDebug("dispatching " << qPrintable(mouseEventToString(mouseEvent.data()))
137+ << " to " << m_targetItem.data());
147138 QCoreApplication::sendEvent(targetItem, mouseEvent.data());
148139 if (mouseEvent->isAccepted()) {
149− #if TOUCHDISPATCHER_DEBUG
150− qDebug() << "[TouchDispatcher] Item accepted the QMouseEvent.";
151− #endif
152− m_status = DeliveringMouseEvents;
140+ ugDebug("Item accepted the QMouseEvent.");
141+ setStatus(DeliveringMouseEvents);
153142 m_touchMouseId = targetTouchPoints.at(0).id();
154143
155144 if (checkIfDoubleClicked(timestamp)) {
156145 QScopedPointer<QMouseEvent> doubleClickEvent(
157146 touchToMouseEvent(QEvent::MouseButtonDblClick, targetTouchPoints.at(0), timestamp,
158147 modifiers, false /* transformNeeded */));
159− #if TOUCHDISPATCHER_DEBUG
160− qDebug() << "[TouchDispatcher] dispatching" << qPrintable(mouseEventToString(doubleClickEvent.data()))
161− << "to" << m_targetItem.data();
162− #endif
148+ ugDebug("dispatching " << qPrintable(mouseEventToString(doubleClickEvent.data()))
149+ << " to " << m_targetItem.data());
163150 QCoreApplication::sendEvent(targetItem, doubleClickEvent.data());
164151 }
165152
166153 } else {
167− #if TOUCHDISPATCHER_DEBUG
168− qDebug() << "[TouchDispatcher] Item rejected the QMouseEvent.";
169− #endif
170− m_status = TargetRejectedTouches;
154+ ugDebug("Item rejected the QMouseEvent.");
155+ setStatus(TargetRejectedTouches);
171156 }
172157 } else {
173− #if TOUCHDISPATCHER_DEBUG
174− qDebug() << "[TouchDispatcher] Item rejected the touch event and does not accept mouse buttons.";
175− #endif
176− m_status = TargetRejectedTouches;
158+ ugDebug("Item rejected the touch event and does not accept mouse buttons.");
159+ setStatus(TargetRejectedTouches);
177160 }
178161}
179162
@@ -194,10 +177,8 @@ void TouchDispatcher::dispatchAsTouch(QEvent::Type eventType,
194177 createQTouchEvent(eventType, device, modifiers, targetTouchPoints, window, timestamp));
195178
196179
197− #if TOUCHDISPATCHER_DEBUG
198− qDebug() << "[TouchDispatcher] dispatching" << qPrintable(touchEventToString(eventForTargetItem.data()))
199− << "to" << targetItem;
200− #endif
180+ ugDebug("dispatching " << qPrintable(touchEventToString(eventForTargetItem.data()))
181+ << " to " << targetItem);
201182 QCoreApplication::sendEvent(targetItem, eventForTargetItem.data());
202183}
203184
@@ -254,10 +235,8 @@ void TouchDispatcher::dispatchAsMouse(
254235 QScopedPointer<QMouseEvent> mouseEvent(touchToMouseEvent(eventType, *touchMouse, timestamp, modifiers,
255236 true /* transformNeeded */));
256237
257− #if TOUCHDISPATCHER_DEBUG
258− qDebug() << "[TouchDispatcher] dispatching" << qPrintable(mouseEventToString(mouseEvent.data()))
259− << "to" << m_targetItem.data();
260− #endif
238+ ugDebug("dispatching " << qPrintable(mouseEventToString(mouseEvent.data()))
239+ << " to " << m_targetItem.data());
261240 QCoreApplication::sendEvent(m_targetItem.data(), mouseEvent.data());
262241 }
263242}
@@ -365,3 +344,59 @@ bool TouchDispatcher::checkIfDoubleClicked(ulong newPressEventTimestamp)
365344
366345 return doubleClicked;
367346}
347+
348+void TouchDispatcher::setStatus(Status status)
349+{
350+ if (status != m_status) {
351+ #if TOUCHDISPATCHER_DEBUG
352+ switch (status) {
353+ case NoActiveTouch:
354+ ugDebug("status = NoActiveTouch");
355+ break;
356+ case DeliveringTouchEvents:
357+ ugDebug("status = DeliveringTouchEvents");
358+ break;
359+ case DeliveringMouseEvents:
360+ ugDebug("status = DeliveringMouseEvents");
361+ break;
362+ case TargetRejectedTouches:
363+ ugDebug("status = TargetRejectedTouches");
364+ break;
365+ default:
366+ ugDebug("status = " << status);
367+ break;
368+ }
369+ #endif
370+ m_status = status;
371+ }
372+}
373+
374+void TouchDispatcher::reset()
375+{
376+ setStatus(NoActiveTouch);
377+ m_touchMouseId = -1;
378+ m_touchMousePressTimestamp =0;
379+}
380+
381+QEvent::Type TouchDispatcher::resolveEventType(const QList<QTouchEvent::TouchPoint> &touchPoints)
382+{
383+ QEvent::Type eventType;
384+
385+ Qt::TouchPointStates eventStates = 0;
386+ for (int i = 0; i < touchPoints.count(); i++)
387+ eventStates |= touchPoints[i].state();
388+
389+ switch (eventStates) {
390+ case Qt::TouchPointPressed:
391+ eventType = QEvent::TouchBegin;
392+ break;
393+ case Qt::TouchPointReleased:
394+ eventType = QEvent::TouchEnd;
395+ break;
396+ default:
397+ eventType = QEvent::TouchUpdate;
398+ break;
399+ }
400+
401+ return eventType;
402+}
plugins/Ubuntu/Gestures/TouchDispatcher.h+16−9
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2014 Canonical, Ltd.
2+ * Copyright (C) 2014-2015 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -36,12 +36,20 @@ public:
3636 void setTargetItem(QQuickItem *target);
3737 QQuickItem *targetItem() { return m_targetItem; }
3838
39− void dispatch(QEvent::Type eventType,
40− QTouchDevice *device,
39+ void dispatch(QTouchDevice *device,
4140 Qt::KeyboardModifiers modifiers,
4241 const QList<QTouchEvent::TouchPoint> &touchPoints,
4342 QWindow *window,
4443 ulong timestamp);
44+
45+ void reset();
46+
47+ enum Status {
48+ NoActiveTouch,
49+ DeliveringTouchEvents,
50+ DeliveringMouseEvents,
51+ TargetRejectedTouches
52+ };
4553private:
4654 void dispatchTouchBegin(
4755 QTouchDevice *device,
@@ -73,14 +81,13 @@ private:
7381
7482 bool checkIfDoubleClicked(ulong newPressEventTimestamp);
7583
84+ void setStatus(Status status);
85+
86+ static QEvent::Type resolveEventType(const QList<QTouchEvent::TouchPoint> &touchPoints);
87+
7688 QPointer<QQuickItem> m_targetItem;
7789
78− enum {
79− NoActiveTouch,
80− DeliveringTouchEvents,
81− DeliveringMouseEvents,
82− TargetRejectedTouches
83− } m_status;
90+ Status m_status;
8491
8592 int m_touchMouseId;
8693 ulong m_touchMousePressTimestamp;
plugins/Ubuntu/Gestures/TouchGate.cpp+84−60
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2014 Canonical, Ltd.
2+ * Copyright (C) 2014-2015 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -23,8 +23,18 @@
2323#include <TouchRegistry.h>
2424
2525#if TOUCHGATE_DEBUG
26+#define ugDebug(params) qDebug().nospace() << "[TouchGate(" << (void*)this << ")] " << params
2627#include <DebugHelpers.h>
27−#endif
28+#else // TOUCHGATE_DEBUG
29+#define ugDebug(params) ((void)0)
30+#endif // TOUCHGATE_DEBUG
31+
32+TouchGate::TouchGate(QQuickItem *parent)
33+ : QQuickItem(parent)
34+{
35+ connect(this, &QQuickItem::enabledChanged,
36+ this, &TouchGate::onEnabledChanged);
37+}
2838
2939bool TouchGate::event(QEvent *e)
3040{
@@ -38,13 +48,12 @@ bool TouchGate::event(QEvent *e)
3848
3949void TouchGate::touchEvent(QTouchEvent *event)
4050{
41− #if TOUCHGATE_DEBUG
42− qDebug() << "[TouchGate] got touch event" << qPrintable(touchEventToString(event));
43− #endif
51+ ugDebug("got touch event" << qPrintable(touchEventToString(event)));
4452 event->accept();
4553
4654 const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
47− bool goodToGo = true;
55+ QList<QTouchEvent::TouchPoint> validTouchPoints;
56+ bool ownsAllTouches = true;
4857 for (int i = 0; i < touchPoints.count(); ++i) {
4958 const QTouchEvent::TouchPoint &touchPoint = touchPoints[i];
5059
@@ -55,31 +64,41 @@ void TouchGate::touchEvent(QTouchEvent *event)
5564 TouchRegistry::instance()->requestTouchOwnership(touchPoint.id(), this);
5665 }
5766
58− goodToGo &= m_touchInfoMap.contains(touchPoint.id())
59− && m_touchInfoMap[touchPoint.id()].ownership == OwnershipGranted;
67+ if (m_touchInfoMap.contains(touchPoint.id())) {
68+ validTouchPoints.append(touchPoint);
69+
70+ ownsAllTouches &= m_touchInfoMap[touchPoint.id()].ownership == OwnershipGranted;
6071
61− if (touchPoint.state() == Qt::TouchPointReleased && m_touchInfoMap.contains(touchPoint.id())) {
62− m_touchInfoMap[touchPoint.id()].ended = true;
72+ if (touchPoint.state() == Qt::TouchPointReleased) {
73+ m_touchInfoMap[touchPoint.id()].ended = true;
74+ }
6375 }
6476
6577 }
6678
67− if (goodToGo) {
79+ if (validTouchPoints.isEmpty()) {
80+ // nothing to do.
81+ return;
82+ }
83+
84+ if (ownsAllTouches) {
6885 if (m_storedEvents.isEmpty()) {
6986 // let it pass through
70− dispatchTouchEventToTarget(event);
87+ removeTouchInfoForEndedTouches(validTouchPoints);
88+ m_dispatcher.dispatch(event->device(), event->modifiers(), validTouchPoints,
89+ event->window(), event->timestamp());
7190 } else {
7291 // Retain the event to ensure TouchGate dispatches them in order.
7392 // Otherwise the current event would come before the stored ones, which are older.
74− #if TOUCHGATE_DEBUG
75− qDebug("[TouchGate] Storing event because thouches %s are still pending ownership.",
76− qPrintable(oldestPendingTouchIdsString()));
77− #endif
78− storeTouchEvent(event);
93+ ugDebug("Storing event because thouches " << qPrintable(oldestPendingTouchIdsString())
94+ << " are still pending ownership.");
95+ storeTouchEvent(event->device(), event->modifiers(), validTouchPoints,
96+ event->window(), event->timestamp());
7997 }
8098 } else {
8199 // Retain events that have unowned touches
82− storeTouchEvent(event);
100+ storeTouchEvent(event->device(), event->modifiers(), validTouchPoints,
101+ event->window(), event->timestamp());
83102 }
84103}
85104
@@ -88,24 +107,23 @@ void TouchGate::touchOwnershipEvent(TouchOwnershipEvent *event)
88107 // TODO: Optimization: batch those actions as TouchOwnershipEvents
89108 // might come one right after the other.
90109
91− Q_ASSERT(m_touchInfoMap.contains(event->touchId()));
110+ if (m_touchInfoMap.contains(event->touchId())) {
111+ TouchInfo &touchInfo = m_touchInfoMap[event->touchId()];
92112
93− TouchInfo &touchInfo = m_touchInfoMap[event->touchId()];
113+ if (event->gained()) {
114+ ugDebug("Got ownership of touch " << event->touchId());
115+ touchInfo.ownership = OwnershipGranted;
116+ } else {
117+ ugDebug("Lost ownership of touch " << event->touchId());
118+ m_touchInfoMap.remove(event->touchId());
119+ removeTouchFromStoredEvents(event->touchId());
120+ }
94121
95− if (event->gained()) {
96− #if TOUCHGATE_DEBUG
97− qDebug() << "[TouchGate] Got ownership of touch " << event->touchId();
98− #endif
99− touchInfo.ownership = OwnershipGranted;
122+ dispatchFullyOwnedEvents();
100123 } else {
101− #if TOUCHGATE_DEBUG
102− qDebug() << "[TouchGate] Lost ownership of touch " << event->touchId();
103− #endif
104− m_touchInfoMap.remove(event->touchId());
105− removeTouchFromStoredEvents(event->touchId());
124+ // Ignore it. It probably happened because the TouchGate got disabled
125+ // between the time it requested ownership and the time it got it.
106126 }
107−
108− dispatchFullyOwnedEvents();
109127}
110128
111129bool TouchGate::isTouchPointOwned(int touchId) const
@@ -113,14 +131,15 @@ bool TouchGate::isTouchPointOwned(int touchId) const
113131 return m_touchInfoMap[touchId].ownership == OwnershipGranted;
114132}
115133
116−void TouchGate::storeTouchEvent(const QTouchEvent *event)
134+void TouchGate::storeTouchEvent(QTouchDevice *device,
135+ Qt::KeyboardModifiers modifiers,
136+ const QList<QTouchEvent::TouchPoint> &touchPoints,
137+ QWindow *window,
138+ ulong timestamp)
117139{
118− #if TOUCHGATE_DEBUG
119− qDebug() << "[TouchGate] Storing" << qPrintable(touchEventToString(event));
120− #endif
121−
122− TouchEvent clonedEvent(event);
123− m_storedEvents.append(std::move(clonedEvent));
140+ ugDebug("Storing" << touchPoints);
141+ TouchEvent event(device, modifiers, touchPoints, window, timestamp);
142+ m_storedEvents.append(std::move(event));
124143}
125144
126145void TouchGate::removeTouchFromStoredEvents(int touchId)
@@ -193,25 +212,13 @@ void TouchGate::setTargetItem(QQuickItem *item)
193212void TouchGate::dispatchTouchEventToTarget(const TouchEvent &event)
194213{
195214 removeTouchInfoForEndedTouches(event.touchPoints);
196− m_dispatcher.dispatch(event.eventType,
197− event.device,
215+ m_dispatcher.dispatch(event.device,
198216 event.modifiers,
199217 event.touchPoints,
200218 event.window,
201219 event.timestamp);
202220}
203221
204−void TouchGate::dispatchTouchEventToTarget(QTouchEvent* event)
205−{
206− removeTouchInfoForEndedTouches(event->touchPoints());
207− m_dispatcher.dispatch(event->type(),
208− event->device(),
209− event->modifiers(),
210− event->touchPoints(),
211− event->window(),
212− event->timestamp());
213−}
214−
215222void TouchGate::removeTouchInfoForEndedTouches(const QList<QTouchEvent::TouchPoint> &touchPoints)
216223{
217224 for (int i = 0; i < touchPoints.size(); ++i) {\
@@ -226,14 +233,31 @@ void TouchGate::removeTouchInfoForEndedTouches(const QList<QTouchEvent::TouchPoi
226233 }
227234}
228235
229−TouchGate::TouchEvent::TouchEvent(const QTouchEvent *event)
230− : eventType(event->type())
231− , device(event->device())
232− , modifiers(event->modifiers())
233− , touchPoints(event->touchPoints())
234− , target(qobject_cast<QQuickItem*>(event->target()))
235− , window(event->window())
236− , timestamp(event->timestamp())
236+void TouchGate::onEnabledChanged()
237+{
238+ ugDebug(" enabled = " << isEnabled());
239+ if (!isEnabled()) {
240+ reset();
241+ }
242+}
243+
244+void TouchGate::reset()
245+{
246+ m_storedEvents.clear();
247+ m_touchInfoMap.clear();
248+ m_dispatcher.reset();
249+}
250+
251+TouchGate::TouchEvent::TouchEvent(QTouchDevice *device,
252+ Qt::KeyboardModifiers modifiers,
253+ const QList<QTouchEvent::TouchPoint> &touchPoints,
254+ QWindow *window,
255+ ulong timestamp)
256+ : device(device)
257+ , modifiers(modifiers)
258+ , touchPoints(touchPoints)
259+ , window(window)
260+ , timestamp(timestamp)
237261{
238262}
239263
plugins/Ubuntu/Gestures/TouchGate.h+18−5
@@ -45,6 +45,8 @@ class UBUNTUGESTURESQML_EXPORT TouchGate : public QQuickItem {
4545 Q_PROPERTY(QQuickItem* targetItem READ targetItem WRITE setTargetItem NOTIFY targetItemChanged)
4646
4747public:
48+ TouchGate(QQuickItem *parent = nullptr);
49+
4850 bool event(QEvent *e) override;
4951
5052 QQuickItem *targetItem() { return m_dispatcher.targetItem(); }
@@ -55,31 +57,42 @@ Q_SIGNALS:
5557
5658protected:
5759 void touchEvent(QTouchEvent *event) override;
60+
61+private Q_SLOTS:
62+ void onEnabledChanged();
63+
5864private:
65+ void reset();
66+
5967 class TouchEvent {
6068 public:
61− TouchEvent(const QTouchEvent *event);
69+ TouchEvent(QTouchDevice *device,
70+ Qt::KeyboardModifiers modifiers,
71+ const QList<QTouchEvent::TouchPoint> &touchPoints,
72+ QWindow *window,
73+ ulong timestamp);
6274
6375 bool removeTouch(int touchId);
6476
65− QEvent::Type eventType;
6677 QTouchDevice *device;
6778 Qt::KeyboardModifiers modifiers;
6879 QList<QTouchEvent::TouchPoint> touchPoints;
69− QQuickItem *target;
7080 QWindow *window;
7181 ulong timestamp;
7282 };
7383
7484 void touchOwnershipEvent(TouchOwnershipEvent *event);
7585 bool isTouchPointOwned(int touchId) const;
76− void storeTouchEvent(const QTouchEvent *event);
86+ void storeTouchEvent(QTouchDevice *device,
87+ Qt::KeyboardModifiers modifiers,
88+ const QList<QTouchEvent::TouchPoint> &touchPoints,
89+ QWindow *window,
90+ ulong timestamp);
7791 void removeTouchFromStoredEvents(int touchId);
7892 void dispatchFullyOwnedEvents();
7993 bool eventIsFullyOwned(const TouchEvent &event) const;
8094
8195 void dispatchTouchEventToTarget(const TouchEvent &event);
82− void dispatchTouchEventToTarget(QTouchEvent* event);
8396
8497 void removeTouchInfoForEndedTouches(const QList<QTouchEvent::TouchPoint> &touchPoints);
8598
qml/Components/DragHandle.qml+18−33
@@ -43,16 +43,8 @@ import Ubuntu.Gestures 0.1
4343 }
4444
4545 */
46−EdgeDragArea {
46+DirectionalDragArea {
4747 id: dragArea
48− objectName: "dragHandle"
49−
50− // Disable gesture recognition by default when hinting is used as
51− // it conflicts with the hinting idea.
52− distanceThreshold: hintDisplacement > 0 ? 0 : defaultDistanceThreshold
53− maxSilenceTime: hintDisplacement > 0 ? 60*60*1000 : defaultMaxSilenceTime
54− maxDeviation: hintDisplacement > 0 ? 999999 : defaultMaxDeviation
55− compositionTime: hintDisplacement > 0 ? 0 : defaultCompositionTime
5648
5749 property bool stretch: false
5850
@@ -68,6 +60,9 @@ EdgeDragArea {
6860 }
6961
7062 property real hintDisplacement: 0
63+
64+ immediateRecognition: hintDisplacement > 0
65+
7166 property var overrideStartValue: undefined
7267 SmoothedAnimation {
7368 id: hintingAnimation
@@ -98,7 +93,6 @@ EdgeDragArea {
9893 // Private stuff
9994 QtObject {
10095 id: d
101− property var previousStatus: DirectionalDragArea.WaitingForTouch
10296 property real startValue
10397 property real minValue: {
10498 if (direction == Direction.Horizontal) {
@@ -183,7 +177,7 @@ EdgeDragArea {
183177 }
184178
185179 onDistanceChanged: {
186− if (status === DirectionalDragArea.Recognized) {
180+ if (dragging) {
187181 // don't go the whole distance in order to smooth out the movement
188182 var step = distance * 0.3;
189183
@@ -193,31 +187,22 @@ EdgeDragArea {
193187 }
194188 }
195189
196− onStatusChanged: {
197− if (status === DirectionalDragArea.WaitingForTouch) {
198− hintingAnimation.stop();
199− if (d.previousStatus === DirectionalDragArea.Recognized) {
200− d.onFinishedRecognizedGesture();
201− } else /* d.previousStatus === DirectionalDragArea.Undecided */ {
202− // Gesture was rejected.
203− d.rollbackDrag();
190+ onDraggingChanged: {
191+ if (dragging) {
192+ dragEvaluator.reset();
193+ if (overrideStartValue !== undefined) {
194+ d.startValue = overrideStartValue;
195+ } else {
196+ d.startValue = parent[d.targetProp];
204197 }
205− } else /* Undecided || Recognized */ {
206− if (d.previousStatus === DirectionalDragArea.WaitingForTouch) {
207− dragEvaluator.reset();
208− if (overrideStartValue !== undefined) {
209− d.startValue = overrideStartValue;
210− } else {
211− d.startValue = parent[d.targetProp];
212− }
213198
214− if (hintDisplacement > 0) {
215− hintingAnimation.targetValue = d.startValue;
216− hintingAnimation.start();
217− }
199+ if (hintDisplacement > 0) {
200+ hintingAnimation.targetValue = d.startValue;
201+ hintingAnimation.start();
218202 }
203+ } else {
204+ hintingAnimation.stop();
205+ d.onFinishedRecognizedGesture();
219206 }
220−
221− d.previousStatus = status;
222207 }
223208}
qml/Components/EdgeDragArea.qmldeleted−46
@@ -1,46 +0,0 @@
1−/*
2− * Copyright (C) 2013 Canonical, Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import QtQuick 2.0
18−import Ubuntu.Components 0.1
19−import Ubuntu.Gestures 0.1
20−
21−/*
22− A DirectionalDragArea wrapper that provides some well-chosen defaults
23− for its gesture recognition parameters.
24−*/
25−DirectionalDragArea {
26−
27− // TODO: Re-evaluate those or even the recognition heuristics itself once
28− // we have gesture cancelling/forwarding in place.
29− //
30− // The idea here is that it's better having lax rules than false negatives.
31− // False negatives are very frustrating to the user.
32− maxDeviation: defaultMaxDeviation
33− wideningAngle: defaultWideningAngle
34− distanceThreshold: defaultDistanceThreshold
35− minSpeed: defaultMinSpeed
36− maxSilenceTime: defaultMaxSilenceTime
37− compositionTime: defaultCompositionTime
38−
39− readonly property real defaultMaxDeviation: units.gu(3)
40− readonly property real defaultWideningAngle: 50
41− readonly property real defaultDistanceThreshold: units.gu(1.5)
42− // some people were getting false negatives with it enabled.
43− readonly property real defaultMinSpeed: units.gu(0)
44− readonly property int defaultMaxSilenceTime: 200
45− readonly property int defaultCompositionTime: 60
46−}
qml/Dash/Dash.qml+5−12
@@ -153,7 +153,7 @@ Showable {
153153 // (as expected) but would also cause the dash content flickable to move a bit, because
154154 // that flickable was getting the touch events while overviewDragHandle was still undecided
155155 // about whether that touch was indeed performing a directional drag gesture.
156− forceNonInteractive: overviewDragHandle.status != DirectionalDragArea.WaitingForTouch
156+ forceNonInteractive: overviewDragHandle.dragging
157157
158158 enabled: bottomEdgeController.progress == 0
159159 }
@@ -330,7 +330,7 @@ Showable {
330330 }
331331 }
332332
333− EdgeDragArea {
333+ DirectionalDragArea {
334334 id: overviewDragHandle
335335 objectName: "overviewDragHandle"
336336 z: 1
@@ -346,21 +346,14 @@ Showable {
346346 height: units.gu(2)
347347
348348 onSceneDistanceChanged: {
349− if (status == DirectionalDragArea.Recognized) {
349+ if (dragging) {
350350 bottomEdgeController.enableAnimation = false;
351351 bottomEdgeController.progress = Math.max(0, Math.min(1, sceneDistance / fullMovement));
352352 }
353353 }
354354
355− property int previousStatus: -1
356− property int currentStatus: DirectionalDragArea.WaitingForTouch
357−
358− onStatusChanged: {
359− previousStatus = currentStatus;
360− currentStatus = status;
361−
362− if (status == DirectionalDragArea.WaitingForTouch &&
363− previousStatus == DirectionalDragArea.Recognized) {
355+ onDraggingChanged: {
356+ if (!dragging) {
364357 bottomEdgeController.enableAnimation = true;
365358 bottomEdgeController.progress = (bottomEdgeController.progress > 0.2) ? 1 : 0;
366359 }
qml/Greeter/CoverPage.qml+3−2
@@ -129,13 +129,14 @@ Showable {
129129
130130 DragHandle {
131131 id: dragHandle
132+ objectName: "coverPageDragHandle"
132133 anchors.fill: parent
133134 anchors.leftMargin: root.dragHandleLeftMargin
134135 enabled: root.draggable
135136 direction: Direction.Horizontal
136137
137− onDraggingChanged: {
138− if (dragging) {
138+ onPressedChanged: {
139+ if (pressed) {
139140 root.tease();
140141 showLabelAnimation.start();
141142 }
qml/Launcher/Launcher.qml+7−15
@@ -210,7 +210,7 @@ Item {
210210 bottom: parent.bottom
211211 }
212212 x: -width
213− visible: root.x > 0 || x > -width || dragArea.status === DirectionalDragArea.Undecided
213+ visible: root.x > 0 || x > -width || dragArea.pressed
214214 model: LauncherModel
215215
216216 property bool animate: true
@@ -272,7 +272,7 @@ Item {
272272 }
273273 }
274274
275− EdgeDragArea {
275+ DirectionalDragArea {
276276 id: dragArea
277277 objectName: "launcherDragArea"
278278
@@ -283,20 +283,11 @@ Item {
283283 width: root.dragAreaWidth
284284 height: root.height
285285
286− onTouchXChanged: {
287− if (status !== DirectionalDragArea.Recognized || launcher.state == "visible")
286+ onDistanceChanged: {
287+ if (!dragging || launcher.state == "visible")
288288 return;
289289
290− // When the gesture finally gets recognized, the finger will likely be
291− // reasonably far from the edge. If we made the panel immediately
292− // follow the finger position it would be visually unpleasant as it
293− // would appear right next to the user's finger out of nowhere.
294− // Instead, we make the panel go towards the user's finger in several
295− // steps. ie., in an animated way.
296− var targetPanelX = Math.min(0, touchX - panel.width) - root.x
297− var delta = targetPanelX - panel.x
298− // the trick is not to go all the way (1.0) as it would cause a sudden jump
299− panel.x += 0.4 * delta
290+ panel.x = -panel.width + Math.min(Math.max(0, distance), panel.width);
300291 }
301292
302293 onDraggingChanged: {
@@ -306,7 +297,8 @@ Item {
306297 if (distance > minimizeDistance) {
307298 root.dash();
308299 }
309− } else {
300+ } else if (root.state === "") {
301+ // didn't drag far enough. rollback
310302 root.switchToNextState("")
311303 }
312304 }
qml/Panel/IndicatorsMenu.qml+12−2
@@ -186,9 +186,18 @@ Showable {
186186 enabled: !root.shown && root.available
187187 autoCompleteDragThreshold: maxTotalDragDistance / 2
188188 stretch: true
189− distanceThreshold: enableHint ? 0 : minimizedPanelHeight
190189
191− onTapped: showTapped(Qt.point(touchSceneX, touchSceneY));
190+ onPressedChanged: {
191+ if (pressed) {
192+ touchPressTime = new Date().getTime();
193+ } else {
194+ var touchReleaseTime = new Date().getTime();
195+ if (touchReleaseTime - touchPressTime <= 300) {
196+ root.showTapped(Qt.point(touchSceneX, touchSceneY));
197+ }
198+ }
199+ }
200+ property var touchPressTime
192201
193202 // using hint regulates minimum to hint displacement, but in fullscreen mode, we need to do it manually.
194203 overrideStartValue: enableHint ? minimizedPanelHeight : expandedPanelHeight + handle.height
@@ -204,6 +213,7 @@ Showable {
204213
205214 DragHandle {
206215 id: __hideDragHandle
216+ objectName: "hideDragHandle"
207217 anchors.fill: handle
208218 direction: Direction.Upwards
209219 enabled: root.shown && root.available
qml/Stages/PhoneStage.qml+31−41
@@ -146,14 +146,13 @@ Rectangle {
146146 id: spreadView
147147 objectName: "spreadView"
148148 anchors.fill: parent
149− interactive: (spreadDragArea.status == DirectionalDragArea.Recognized || phase > 1)
150− && draggedDelegateCount === 0
149+ interactive: (spreadDragArea.dragging || phase > 1) && draggedDelegateCount === 0
151150 contentWidth: spreadRow.width - shift
152151 contentX: -shift
153152
154153 // This indicates when the spreadView is active. That means, all the animations
155154 // are activated and tiles need to line up for the spread.
156− readonly property bool active: shiftedContentX > 0 || spreadDragArea.status === DirectionalDragArea.Recognized || !root.focusFirstApp
155+ readonly property bool active: shiftedContentX > 0 || spreadDragArea.dragging || !root.focusFirstApp
157156
158157 // The flickable needs to fill the screen in order to get touch events all over.
159158 // However, we don't want to the user to be able to scroll back all the way. For
@@ -450,7 +449,7 @@ Rectangle {
450449 }
451450 }
452451
453− EdgeDragArea {
452+ DirectionalDragArea {
454453 id: spreadDragArea
455454 objectName: "spreadDragArea"
456455 direction: Direction.Leftwards
@@ -462,59 +461,50 @@ Rectangle {
462461 property var gesturePoints: new Array()
463462
464463 onTouchXChanged: {
465− if (!dragging) {
466− // Initial touch. Let's reset the spreadView to the starting position.
467− spreadView.phase = 0;
468− spreadView.contentX = -spreadView.shift;
469− }
470− if (dragging && status == DirectionalDragArea.Recognized) {
464+ if (dragging) {
471465 // Gesture recognized. Let's move the spreadView with the finger
472466 var dragX = Math.min(touchX + width, width); // Prevent dragging rightwards
473467 dragX = -dragX + spreadDragArea.width - spreadView.shift;
474468 // Don't allow dragging further than the animation crossing with phase2's animation
475469 var maxMovement = spreadView.width * spreadView.positionMarker4 - spreadView.shift;
470+
476471 spreadView.contentX = Math.min(dragX, maxMovement);
472+ } else {
473+ // Initial touch. Let's reset the spreadView to the starting position.
474+ spreadView.phase = 0;
475+ spreadView.contentX = -spreadView.shift;
477476 }
478− gesturePoints.push(touchX);
479− }
480−
481− property int previousStatus: -1
482− property int currentStatus: DirectionalDragArea.WaitingForTouch
483477
484− onStatusChanged: {
485− previousStatus = currentStatus;
486− currentStatus = status;
478+ gesturePoints.push(touchX);
487479 }
488480
489481 onDraggingChanged: {
490482 if (dragging) {
491483 // A potential edge-drag gesture has started. Start recording it
492484 gesturePoints = [];
493− return;
494− }
485+ } else {
486+ // Ok. The user released. Find out if it was a one-way movement.
487+ var oneWayFlick = true;
488+ var smallestX = spreadDragArea.width;
489+ for (var i = 0; i < gesturePoints.length; i++) {
490+ if (gesturePoints[i] >= smallestX) {
491+ oneWayFlick = false;
492+ break;
493+ }
494+ smallestX = gesturePoints[i];
495+ }
496+ gesturePoints = [];
495497
496− // Ok. The user released. Find out if it was a one-way movement.
497− var oneWayFlick = true;
498− var smallestX = spreadDragArea.width;
499− for (var i = 0; i < gesturePoints.length; i++) {
500− if (gesturePoints[i] >= smallestX) {
501− oneWayFlick = false;
502− break;
498+ if (oneWayFlick && spreadView.shiftedContentX > units.gu(2) &&
499+ spreadView.shiftedContentX < spreadView.positionMarker1 * spreadView.width) {
500+ // If it was a short one-way movement, do the Alt+Tab switch
501+ // no matter if we didn't cross positionMarker1 yet.
502+ spreadView.snapTo(1);
503+ } else if (!dragging) {
504+ // otherwise snap to the closest snap position we can find
505+ // (might be back to start, to app 1 or to spread)
506+ spreadView.snap();
503507 }
504− smallestX = gesturePoints[i];
505− }
506− gesturePoints = [];
507−
508− if (previousStatus == DirectionalDragArea.Recognized &&
509− oneWayFlick && spreadView.shiftedContentX > units.gu(2) &&
510− spreadView.shiftedContentX < spreadView.positionMarker1 * spreadView.width) {
511− // If it was a short one-way movement, do the Alt+Tab switch
512− // no matter if we didn't cross positionMarker1 yet.
513− spreadView.snapTo(1);
514− } else if (!dragging) {
515− // otherwise snap to the closest snap position we can find
516− // (might be back to start, to app 1 or to spread)
517− spreadView.snap();
518508 }
519509 }
520510 }
qml/Stages/TabletStage.qml+18−20
@@ -170,8 +170,7 @@ Rectangle {
170170 Flickable {
171171 id: spreadView
172172 anchors.fill: parent
173− interactive: (spreadDragArea.status == DirectionalDragArea.Recognized || phase > 1)
174− && draggedDelegateCount === 0
173+ interactive: (spreadDragArea.dragging || phase > 1) && draggedDelegateCount === 0
175174 contentWidth: spreadRow.width - shift
176175 contentX: -shift
177176
@@ -598,7 +597,7 @@ Rectangle {
598597 }
599598 }
600599
601− EdgeDragArea {
600+ DirectionalDragArea {
602601 id: spreadDragArea
603602 anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
604603 width: root.dragAreaWidth
@@ -624,26 +623,25 @@ Rectangle {
624623 if (dragging) {
625624 // Gesture recognized. Start recording this gesture
626625 gesturePoints = [];
627− return;
628− }
629−
630− // Ok. The user released. Find out if it was a one-way movement.
631− var oneWayFlick = priv.evaluateOneWayFlick(gesturePoints);
632− gesturePoints = [];
626+ } else {
627+ // Ok. The user released. Find out if it was a one-way movement.
628+ var oneWayFlick = priv.evaluateOneWayFlick(gesturePoints);
629+ gesturePoints = [];
633630
634− if (oneWayFlick && spreadView.shiftedContentX < spreadView.positionMarker1 * spreadView.width) {
635− // If it was a short one-way movement, do the Alt+Tab switch
636− // no matter if we didn't cross positionMarker1 yet.
637− spreadView.snapTo(spreadView.nextInStack);
638− } else if (!dragging) {
639− if (spreadView.shiftedContentX < spreadView.width * spreadView.positionMarker1) {
640− spreadView.snap();
641− } else if (spreadView.shiftedContentX < spreadView.width * spreadView.positionMarker2) {
631+ if (oneWayFlick && spreadView.shiftedContentX < spreadView.positionMarker1 * spreadView.width) {
632+ // If it was a short one-way movement, do the Alt+Tab switch
633+ // no matter if we didn't cross positionMarker1 yet.
642634 spreadView.snapTo(spreadView.nextInStack);
643635 } else {
644− // otherwise snap to the closest snap position we can find
645− // (might be back to start, to app 1 or to spread)
646− spreadView.snap();
636+ if (spreadView.shiftedContentX < spreadView.width * spreadView.positionMarker1) {
637+ spreadView.snap();
638+ } else if (spreadView.shiftedContentX < spreadView.width * spreadView.positionMarker2) {
639+ spreadView.snapTo(spreadView.nextInStack);
640+ } else {
641+ // otherwise snap to the closest snap position we can find
642+ // (might be back to start, to app 1 or to spread)
643+ spreadView.snap();
644+ }
647645 }
648646 }
649647 }
qml/Tutorial/TutorialBottom.qml+1−1
@@ -73,7 +73,7 @@ TutorialPage {
7373 ]
7474 }
7575
76− EdgeDragArea {
76+ DirectionalDragArea {
7777 id: dragArea
7878 direction: Direction.Upwards
7979 anchors {
tests/libs/UbuntuGestures/tst_TouchRegistry.cpp+68−2
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2014 Canonical, Ltd.
2+ * Copyright (C) 2014-2015 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -66,6 +66,7 @@ private Q_SLOTS:
6666 void candidatesAndWatchers_2();
6767 void rejectingTouchfterItsEnd();
6868 void removeOldUndecidedCandidates();
69+ void interimOwnerWontGetUnownedTouchEvents();
6970};
7071
7172void tst_TouchRegistry::requestWithNoCandidates()
@@ -742,7 +743,7 @@ void tst_TouchRegistry::removeOldUndecidedCandidates()
742743
743744 // Simulate that enough time has passed to cause the CandidateInactivityTimer to timeout,
744745 // making TouchRegistry consider that undecidedCantidate defaulted.
745− fakeTimerFactory->makeRunningTimersTimeout();
746+ fakeTimerFactory->updateTime(10000);
746747
747748 QVERIFY(undecidedCandidate.ownedTouches.isEmpty());
748749 QVERIFY(undecidedCandidate.lostTouches.contains(0));
@@ -752,6 +753,71 @@ void tst_TouchRegistry::removeOldUndecidedCandidates()
752753 delete touchRegistry;
753754}
754755
756+/*
757+ An item that calls requestTouchOwnership() without first having called addCandidateOwnerForTouch()
758+ is assumed to be the interim owner of the touch point, thus there's no point in sending
759+ UnownedTouchEvents to him as he already gets proper QTouchEvents from QQuickWindow because
760+ he didn't ignore the first QTouchEvent with that touch point.
761+ */
762+void tst_TouchRegistry::interimOwnerWontGetUnownedTouchEvents()
763+{
764+ FakeTimerFactory *fakeTimerFactory = new FakeTimerFactory;
765+ TouchRegistry *touchRegistry = new TouchRegistry(nullptr, fakeTimerFactory);
766+
767+ DummyCandidate undecidedCandidate;
768+ undecidedCandidate.setObjectName("undecided");
769+
770+ DummyCandidate interimOwner;
771+ interimOwner.setObjectName("interimOwner");
772+
773+ {
774+ QList<QTouchEvent::TouchPoint> touchPoints;
775+ touchPoints.append(QTouchEvent::TouchPoint(0));
776+ touchPoints[0].setState(Qt::TouchPointPressed);
777+ QTouchEvent touchEvent(QEvent::TouchBegin,
778+ 0 /* device */,
779+ Qt::NoModifier,
780+ Qt::TouchPointPressed,
781+ touchPoints);
782+ touchRegistry->update(&touchEvent);
783+ }
784+
785+ touchRegistry->addCandidateOwnerForTouch(0, &undecidedCandidate);
786+ touchRegistry->requestTouchOwnership(0, &interimOwner);
787+
788+ {
789+ QList<QTouchEvent::TouchPoint> touchPoints;
790+ touchPoints.append(QTouchEvent::TouchPoint(0));
791+ touchPoints[0].setState(Qt::TouchPointMoved);
792+ QTouchEvent touchEvent(QEvent::TouchUpdate,
793+ 0 /* device */,
794+ Qt::NoModifier,
795+ Qt::TouchPointMoved,
796+ touchPoints);
797+ touchRegistry->update(&touchEvent);
798+ }
799+
800+ QCOMPARE(undecidedCandidate.unownedTouchEvents.count(), 1);
801+ QCOMPARE(interimOwner.unownedTouchEvents.count(), 0);
802+
803+ {
804+ QList<QTouchEvent::TouchPoint> touchPoints;
805+ touchPoints.append(QTouchEvent::TouchPoint(0));
806+ touchPoints[0].setState(Qt::TouchPointMoved);
807+ QTouchEvent touchEvent(QEvent::TouchUpdate,
808+ 0 /* device */,
809+ Qt::NoModifier,
810+ Qt::TouchPointMoved,
811+ touchPoints);
812+ touchRegistry->update(&touchEvent);
813+ }
814+
815+ QCOMPARE(undecidedCandidate.unownedTouchEvents.count(), 2);
816+ QCOMPARE(interimOwner.unownedTouchEvents.count(), 0);
817+
818+ delete touchRegistry;
819+}
820+
755821////////////// TouchMemento //////////
756822
757823TouchMemento::TouchMemento(const QTouchEvent *touchEvent)
tests/plugins/Ubuntu/Gestures/CMakeLists.txt+3−1
@@ -19,6 +19,7 @@ add_custom_target(UbuntuGesturesTestQmlFiles ALL
1919)
2020
2121add_definitions(-DUBUNTU_GESTURES_PLUGIN_DIR="${CMAKE_BINARY_DIR}/plugins")
22+add_definitions(-DTESTS_UTILS_MODULES_DIR="${CMAKE_BINARY_DIR}/tests/utils/modules")
2223
2324macro(add_gesture_ui_test CLASSNAME)
2425 add_executable(${CLASSNAME}TestExec tst_${CLASSNAME}.cpp GestureTest.cpp)
@@ -26,7 +27,8 @@ macro(add_gesture_ui_test CLASSNAME)
2627 target_link_libraries(${CLASSNAME}TestExec UbuntuGesturesQml UbuntuGestures)
2728
2829 add_binary_qml_test(${CLASSNAME} ${CMAKE_BINARY_DIR}/plugins/Ubuntu/Gestures UbuntuGesturesTestQmlFiles "")
29− add_manual_qml_test(. ${CLASSNAME} IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins)
30+ add_manual_qml_test(. ${CLASSNAME} IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins
31+ ${CMAKE_BINARY_DIR}/tests/utils/modules)
3032endmacro(add_gesture_ui_test)
3133
3234macro(add_gesture_test CLASSNAME)
tests/plugins/Ubuntu/Gestures/DownwardsLauncher.qml+5−19
@@ -37,8 +37,8 @@ Item {
3737
3838 Rectangle {
3939 id: dragAreaRect
40− color: "yellow"
41− opacity: 0.0
40+ opacity: dragArea.dragging ? 0.5 : 0.0
41+ color: "green"
4242 anchors.fill: dragArea
4343 }
4444
@@ -49,24 +49,10 @@ Item {
4949 height: units.gu(5)
5050
5151 direction: Direction.Downwards
52− maxDeviation: units.gu(2)
53− wideningAngle: 10
54− distanceThreshold: units.gu(4)
5552
56− onStatusChanged: {
57− switch (status) {
58− case DirectionalDragArea.WaitingForTouch:
59− dragAreaRect.opacity = 0.0
60− break;
61− case DirectionalDragArea.Undecided:
62− dragAreaRect.color = "yellow"
63− dragAreaRect.opacity = 0.3
64− launcher.y = Qt.binding(launcher.followDragArea)
65− break;
66− default: // DirectionalDragArea.Recognized:
67− dragAreaRect.color = "green"
68− dragAreaRect.opacity = 0.5
69− break;
53+ onDraggingChanged: {
54+ if (dragging) {
55+ launcher.y = Qt.binding(launcher.followDragArea)
7056 }
7157 }
7258
tests/plugins/Ubuntu/Gestures/GestureTest.cpp+13−3
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013,2015 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -21,8 +21,11 @@
2121#include <QQuickView>
2222#include <QtTest>
2323
24+#include <Timer.h>
2425#include <TouchRegistry.h>
2526
27+using namespace UbuntuGestures;
28+
2629GestureTest::GestureTest(const QString &qmlFilename)
2730 : QObject(), m_device(nullptr), m_view(nullptr), m_qmlFilename(qmlFilename)
2831{
@@ -40,14 +43,17 @@ void GestureTest::initTestCase()
4043void GestureTest::init()
4144{
4245 m_view = new QQuickView;
43− m_view->setResizeMode(QQuickView::SizeViewToRootObject);
46+ m_view->setResizeMode(QQuickView::SizeRootObjectToView);
4447 m_view->engine()->addImportPath(QStringLiteral(UBUNTU_GESTURES_PLUGIN_DIR));
48+ m_view->engine()->addImportPath(QStringLiteral(TESTS_UTILS_MODULES_DIR));
4549 m_view->setSource(QUrl::fromLocalFile(m_qmlFilename));
4650 m_view->show();
4751 QVERIFY(QTest::qWaitForWindowExposed(m_view));
4852 QVERIFY(m_view->rootObject() != 0);
4953
50− m_touchRegistry = new TouchRegistry;
54+ m_fakeTimerFactory = new FakeTimerFactory;
55+
56+ m_touchRegistry = new TouchRegistry(nullptr, m_fakeTimerFactory);
5157 m_view->installEventFilter(m_touchRegistry);
5258
5359 qApp->processEvents();
@@ -59,6 +65,10 @@ void GestureTest::cleanup()
5965 delete m_touchRegistry;
6066 m_touchRegistry = nullptr;
6167
68+ // TouchRegistry will take down the timer factory along with him
69+ // delete m_fakeTimerFactory;
70+ m_fakeTimerFactory = nullptr;
71+
6272 delete m_view;
6373 m_view = nullptr;
6474}
tests/plugins/Ubuntu/Gestures/GestureTest.h+4
@@ -23,6 +23,9 @@
2323class QQuickView;
2424class QTouchDevice;
2525
26+namespace UbuntuGestures {
27+ class FakeTimerFactory;
28+}
2629class TouchRegistry;
2730
2831// C++ std lib
@@ -81,6 +84,7 @@ protected:
8184 QTouchDevice *m_device;
8285 QQuickView *m_view;
8386 TouchRegistry *m_touchRegistry;
87+ UbuntuGestures::FakeTimerFactory *m_fakeTimerFactory;
8488 QString m_qmlFilename;
8589};
8690
tests/plugins/Ubuntu/Gestures/LeftwardsLauncher.qml+6−19
@@ -21,7 +21,7 @@ import Ubuntu.Components 0.1
2121Item {
2222 id: root
2323
24− function reset() { launcher.x = root.width }
24+ function reset() { launcher.x = Qt.binding(function(){return root.width;}); }
2525
2626 Rectangle {
2727 id: launcher
@@ -41,7 +41,8 @@ Item {
4141
4242 Rectangle {
4343 id: dragAreaRect
44− opacity: 0.0
44+ opacity: dragArea.dragging ? 0.5 : 0.0
45+ color: "green"
4546 anchors.fill: dragArea
4647 }
4748
@@ -52,24 +53,10 @@ Item {
5253 width: units.gu(5)
5354
5455 direction: Direction.Leftwards
55− maxDeviation: units.gu(2)
56− wideningAngle: 10
57− distanceThreshold: units.gu(4)
5856
59− onStatusChanged: {
60− switch (status) {
61− case DirectionalDragArea.WaitingForTouch:
62− dragAreaRect.opacity = 0.0
63− break;
64− case DirectionalDragArea.Undecided:
65− dragAreaRect.color = "yellow"
66− dragAreaRect.opacity = 0.3
67− launcher.x = Qt.binding(launcher.followDragArea)
68− break;
69− default: //case DirectionalDragArea.Recognized:
70− dragAreaRect.color = "green"
71− dragAreaRect.opacity = 0.5
72− break;
57+ onDraggingChanged: {
58+ if (dragging) {
59+ launcher.x = Qt.binding(launcher.followDragArea)
7360 }
7461 }
7562
tests/plugins/Ubuntu/Gestures/RightwardsLauncher.qml+5−19
@@ -38,7 +38,8 @@ Item {
3838
3939 Rectangle {
4040 id: dragAreaRect
41− opacity: 0.0
41+ opacity: dragArea.dragging ? 0.5 : 0.0
42+ color: "green"
4243 anchors.fill: dragArea
4344 }
4445
@@ -52,25 +53,10 @@ Item {
5253 width: units.gu(5)
5354
5455 direction: Direction.Rightwards
55− maxDeviation: units.gu(2)
56− wideningAngle: 10
57− distanceThreshold: units.gu(4)
58− minSpeed: 50
5956
60− onStatusChanged: {
61− switch (status) {
62− case DirectionalDragArea.WaitingForTouch:
63− dragAreaRect.opacity = 0.0
64− break;
65− case DirectionalDragArea.Undecided:
66− dragAreaRect.color = "yellow"
67− dragAreaRect.opacity = 0.3
68− launcher.x = Qt.binding(launcher.followDragArea)
69− break;
70− default: //case DirectionalDragArea.Recognized:
71− dragAreaRect.color = "green"
72− dragAreaRect.opacity = 0.5
73− break;
57+ onDraggingChanged: {
58+ if (dragging) {
59+ launcher.x = Qt.binding(launcher.followDragArea)
7460 }
7561 }
7662
tests/plugins/Ubuntu/Gestures/UpwardsLauncher.qml+6−19
@@ -21,7 +21,7 @@ import Ubuntu.Components 0.1
2121Item {
2222 id: root
2323
24− function reset() { launcher.y = root.height }
24+ function reset() { launcher.y = Qt.binding(function(){return root.height;}); }
2525
2626 Rectangle {
2727 id: launcher
@@ -41,7 +41,8 @@ Item {
4141
4242 Rectangle {
4343 id: dragAreaRect
44− opacity: 0.0
44+ opacity: dragArea.dragging ? 0.5 : 0.0
45+ color: "green"
4546 anchors.fill: dragArea
4647 }
4748
@@ -52,24 +53,10 @@ Item {
5253 height: units.gu(5)
5354
5455 direction: Direction.Upwards
55− maxDeviation: units.gu(2)
56− wideningAngle: 10
57− distanceThreshold: units.gu(4)
5856
59− onStatusChanged: {
60− switch (status) {
61− case DirectionalDragArea.WaitingForTouch:
62− dragAreaRect.opacity = 0.0
63− break;
64− case DirectionalDragArea.Undecided:
65− dragAreaRect.color = "yellow"
66− dragAreaRect.opacity = 0.3
67− launcher.y = Qt.binding(launcher.followDragArea)
68− break;
69− default: //case DirectionalDragArea.Recognized:
70− dragAreaRect.color = "green"
71− dragAreaRect.opacity = 0.5
72− break;
57+ onDraggingChanged: {
58+ if (dragging) {
59+ launcher.y = Qt.binding(launcher.followDragArea)
7360 }
7461 }
7562
tests/plugins/Ubuntu/Gestures/tst_DirectionalDragArea.cpp+480−445
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013-2014 Canonical, Ltd.
2+ * Copyright (C) 2013-2015 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -20,51 +20,38 @@
2020#include <QtQml/QQmlEngine>
2121#include <QPointer>
2222#include <private/qquickmousearea_p.h>
23−#if (QT_VERSION >= QT_VERSION_CHECK(5, 4, 0))
24− #include <private/qquickwindow_p.h>
25−#endif
23+#include <private/qquickwindow_p.h>
2624
2725
2826#include <DirectionalDragArea.h>
27+#include <DirectionalDragArea_p.h>
2928#include <TouchRegistry.h>
3029
3130#include "GestureTest.h"
3231
3332using namespace UbuntuGestures;
3433
35−
36−class ComplexFakeTimer : public FakeTimer
37−{
34+// Because QSignalSpy(directionalDragArea, SIGNAL(DirectionalDragArea::Status)) simply
35+// doesn't work
36+class StatusSpy : public QObject {
3837 Q_OBJECT
3938public:
40− ComplexFakeTimer(const SharedTimeSource &timeSource, QObject *parent = 0)
41− : FakeTimer(parent),
42− m_timeSource(timeSource)
43− {}
44−
45− void start() override {
46− AbstractTimer::start();
47− m_nextTimeoutTime = m_timeSource->msecsSinceReference() + (qint64)interval();
39+ StatusSpy(DirectionalDragArea *edgeDragArea) {
40+ m_recognized = false;
41+ connect(edgeDragArea->d, &DirectionalDragAreaPrivate::statusChanged,
42+ this, &StatusSpy::onStatusChanged);
4843 }
49−
50− void emitTimeout() {
51− m_nextTimeoutTime += interval();
52− Q_EMIT timeout();
44+ bool recognized() {
45+ return m_recognized;
5346 }
5447
55− qint64 nextTimeoutTime() const { return m_nextTimeoutTime; }
48+private Q_SLOTS:
49+ void onStatusChanged(DirectionalDragAreaPrivate::Status status) {
50+ m_recognized |= status == DirectionalDragAreaPrivate::Recognized;
51+ }
5652
5753private:
58− SharedTimeSource m_timeSource;
59− qint64 m_nextTimeoutTime;
60−};
61−
62−class FakeTimeSource : public UbuntuGestures::TimeSource
63−{
64−public:
65− FakeTimeSource() { m_msecsSinceReference = 0; }
66− virtual qint64 msecsSinceReference() {return m_msecsSinceReference;}
67− qint64 m_msecsSinceReference;
54+ bool m_recognized;
6855};
6956
7057/*
@@ -98,15 +85,9 @@ public:
9885 tst_DirectionalDragArea();
9986private Q_SLOTS:
10087 void init(); // called right before each and every test function is executed
101− void cleanup(); // called right after each and every test function is executed
10288
103− void edgeDrag();
104− void edgeDrag_data();
10589 void dragWithShortDirectionChange();
106− void minSpeed();
107− void minSpeed_data();
10890 void recognitionTimerUsage();
109− void maxSilenceTime();
11091 void sceneXAndX();
11192 void sceneYAndY();
11293 void twoFingerTap();
@@ -122,14 +103,24 @@ private Q_SLOTS:
122103 void immediateRecognitionWhenConstraintsDisabled();
123104 void withdrawTouchOwnershipCandidacyIfDisabledDuringRecognition();
124105 void withdrawTouchOwnershipCandidacyIfDisabledDuringRecognition_data();
125− void tappedSignal();
126− void tappedSignal_data();
127106 void gettingTouchOwnershipMakesMouseAreaBehindGetCanceled();
107+ void interleavedTouches();
108+ void makoRightEdgeDrag();
109+ void makoRightEdgeDrag_verticalDownwards();
110+ void makoLeftEdgeDrag_slowStart();
111+ void makoLeftEdgeDrag_movesSlightlyBackwardsOnStart();
128112
129113private:
114+ // QTest::touchEvent takes QPoint instead of QPointF and I don't want to
115+ // lose precision due to rounding.
116+ // Besides, those helper functions lead to more compact code.
117+ void sendTouchPress(qint64 timestamp, int id, QPointF pos);
118+ void sendTouchUpdate(qint64 timestamp, int id, QPointF pos);
119+ void sendTouchRelease(qint64 timestamp, int id, QPointF pos);
120+ void sendTouch(qint64 timestamp, int id, QPointF pos,
121+ Qt::TouchPointState pointState, QEvent::Type eventType);
122+
130123 void passTime(qint64 timeSpanMs);
131− ComplexFakeTimer *fakeTimer;
132− QSharedPointer<FakeTimeSource> fakeTimeSource;
133124};
134125
135126tst_DirectionalDragArea::tst_DirectionalDragArea()
@@ -148,185 +139,57 @@ void tst_DirectionalDragArea::init()
148139 m_view->resize(m_view->rootObject()->width(), m_view->rootObject()->height());
149140 QTRY_COMPARE(m_view->width(), (int)m_view->rootObject()->width());
150141 QTRY_COMPARE(m_view->height(), (int)m_view->rootObject()->height());
151−
152− fakeTimeSource.reset(new FakeTimeSource);
153− fakeTimer = new ComplexFakeTimer(fakeTimeSource);
154−}
155−
156−void tst_DirectionalDragArea::cleanup()
157−{
158− delete fakeTimer;
159− fakeTimer = 0;
160−
161− fakeTimeSource.reset();
162−
163− GestureTest::cleanup();
164−}
165−
166−void tst_DirectionalDragArea::passTime(qint64 timeSpanMs)
167−{
168− qint64 finalTime = fakeTimeSource->m_msecsSinceReference + timeSpanMs;
169−
170− if (fakeTimer->isRunning() && finalTime >= fakeTimer->nextTimeoutTime()) {
171− fakeTimeSource->m_msecsSinceReference = fakeTimer->nextTimeoutTime();
172− fakeTimer->emitTimeout();
173−
174− qint64 timeSpanRemainder = finalTime - fakeTimeSource->m_msecsSinceReference;
175− if (timeSpanRemainder > 0) {
176− passTime(timeSpanRemainder);
177− }
178− } else {
179− fakeTimeSource->m_msecsSinceReference = finalTime;
180− }
181142}
182143
183−namespace {
184−QPointF calculateInitialTouchPos(DirectionalDragArea *edgeDragArea)
144+void tst_DirectionalDragArea::sendTouchPress(qint64 timestamp, int id, QPointF pos)
185145{
186− QPointF localCenter(edgeDragArea->width() / 2., edgeDragArea->height() / 2.);
187− return edgeDragArea->mapToScene(localCenter);
146+ sendTouch(timestamp, id, pos, Qt::TouchPointPressed, QEvent::TouchBegin);
188147}
189148
190−QPointF calculateDirectionVector(DirectionalDragArea *edgeDragArea,
191− qreal wideningAngleMultiplier)
149+void tst_DirectionalDragArea::sendTouchUpdate(qint64 timestamp, int id, QPointF pos)
192150{
193− qreal angleRadians = edgeDragArea->wideningAngle() * wideningAngleMultiplier
194− * M_PI / 180.0;
195−
196− qreal angleCos = qCos(angleRadians);
197− qreal angleSin = qSin(angleRadians);
198−
199− switch (edgeDragArea->direction()) {
200− case Direction::Upwards:
201− return QPointF(angleSin, -angleCos);
202− case Direction::Downwards:
203− return QPointF(angleSin, angleCos);
204− case Direction::Leftwards:
205− return QPointF(-angleCos, angleSin);
206− default: // Direction::Rightwards:
207− return QPointF(angleCos, angleSin);
208− }
151+ sendTouch(timestamp, id, pos, Qt::TouchPointMoved, QEvent::TouchUpdate);
209152}
210153
211−QPointF createTouchDeviation(DirectionalDragArea *edgeDragArea)
154+void tst_DirectionalDragArea::sendTouchRelease(qint64 timestamp, int id, QPointF pos)
212155{
213− qreal deviation = edgeDragArea->maxDeviation() * 0.8;
214−
215− if (Direction::isHorizontal(edgeDragArea->direction())) {
216− return QPointF(0, deviation);
217− } else {
218− return QPointF(deviation, 0);
219− }
220−}
156+ sendTouch(timestamp, id, pos, Qt::TouchPointReleased, QEvent::TouchEnd);
221157}
222158
223−void tst_DirectionalDragArea::edgeDrag()
159+void tst_DirectionalDragArea::sendTouch(qint64 timestamp, int id, QPointF pos,
160+ Qt::TouchPointState pointState, QEvent::Type eventType)
224161{
225− QFETCH(QString, dragAreaObjectName);
226− QFETCH(qreal, wideningAngleMultiplier);
227− QFETCH(qreal, dragDistanceFactor);
228− QFETCH(bool, expectGestureRecognition);
229−
230− DirectionalDragArea *edgeDragArea =
231− m_view->rootObject()->findChild<DirectionalDragArea*>(dragAreaObjectName);
232− QVERIFY(edgeDragArea != 0);
233− edgeDragArea->setRecognitionTimer(fakeTimer);
234− edgeDragArea->setTimeSource(fakeTimeSource);
235−
236− QSignalSpy draggingSpy(edgeDragArea, SIGNAL(draggingChanged(bool)));
237−
238− QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
239− QPointF touchPoint = initialTouchPos;
240−
241− qreal desiredDragDistance = edgeDragArea->distanceThreshold()*dragDistanceFactor;
242− QPointF dragDirectionVector = calculateDirectionVector(edgeDragArea,
243− wideningAngleMultiplier);
244− qreal movementStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
245− QPointF touchMovement = dragDirectionVector * movementStepDistance;
246− int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
247− int movementTimeStepMs = (edgeDragArea->compositionTime() * 1.5f) / totalMovementSteps;
248−
249− QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
250−
251− QCOMPARE(draggingSpy.count(), 1);
252− QCOMPARE(edgeDragArea->dragging(), true);
253−
254− if (wideningAngleMultiplier > 0) {
255− // go close to the border of the valid area for this touch point
256− // in order to make it easier to leave it by dragging at an angle
257− // slightly bigger than the widening angle
258− touchPoint += createTouchDeviation(edgeDragArea);
259− QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
260− }
162+ m_fakeTimerFactory->updateTime(timestamp);
261163
262− for (int i = 0; i < totalMovementSteps; ++i) {
263− touchPoint += touchMovement;
264− passTime(movementTimeStepMs);
265− QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
266− }
164+ QTouchEvent::TouchPoint point;
267165
268− if (expectGestureRecognition)
269− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
166+ point.setState(pointState);
167+ point.setId(id);
168+ point.setScenePos(pos);
169+ point.setPos(pos);
270170
271− if (edgeDragArea->status() == DirectionalDragArea::WaitingForTouch) {
272− QCOMPARE(edgeDragArea->dragging(), false);
273− QCOMPARE(draggingSpy.count(), 2);
274− }
171+ QList<QTouchEvent::TouchPoint> points;
172+ points << point;
275173
276− QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
174+ QTouchEvent touchEvent(eventType, m_device, Qt::NoModifier, Qt::TouchPointPressed, points);
175+ QCoreApplication::sendEvent(m_view, &touchEvent);
277176
278− QCOMPARE(draggingSpy.count(), 2);
279− QCOMPARE(edgeDragArea->dragging(), false);
177+ QQuickWindowPrivate *windowPrivate = QQuickWindowPrivate::get(m_view);
178+ windowPrivate->flushDelayedTouchEvent();
280179}
281180
282−void tst_DirectionalDragArea::edgeDrag_data()
181+void tst_DirectionalDragArea::passTime(qint64 timeSpanMs)
283182{
284− QTest::addColumn<QString>("dragAreaObjectName");
285− QTest::addColumn<qreal>("wideningAngleMultiplier");
286− QTest::addColumn<qreal>("dragDistanceFactor");
287− QTest::addColumn<bool>("expectGestureRecognition");
288−
289− QTest::newRow("rightwards, tiny drag")
290− << "hpDragArea" << 0.0 << 0.2 << false;
291−
292− QTest::newRow("rightwards, straight drag")
293− << "hpDragArea" << 0.0 << 3.0 << true;
294−
295− QTest::newRow("rightwards, diagonal drag")
296− << "hpDragArea" << 0.9 << 3.0 << true;
297−
298− QTest::newRow("rightwards, overly diagonal drag")
299− << "hpDragArea" << 2.0 << 3.0 << false;
300−
301− QTest::newRow("leftwards, tiny drag")
302− << "hnDragArea" << 0.0 << 0.2 << false;
303−
304− QTest::newRow("leftwards, straight drag")
305− << "hnDragArea" << 0.0 << 3.0 << true;
306−
307− QTest::newRow("leftwards, diagonal drag")
308− << "hnDragArea" << 0.9 << 3.0 << true;
309−
310− QTest::newRow("downwards, tiny drag")
311− << "vpDragArea" << 0.0 << 0.2 << false;
312−
313− QTest::newRow("downwards, straight drag")
314− << "vpDragArea" << 0.0 << 3.0 << true;
315−
316− QTest::newRow("downwards, diagonal drag")
317− << "vpDragArea" << 0.9 << 3.0 << true;
318−
319− QTest::newRow("upwards, tiny drag")
320− << "vnDragArea" << 0.0 << 0.2 << false;
321−
322− QTest::newRow("upwards, straight drag")
323− << "vnDragArea" << 0.0 << 3.0 << true;
324−
325− QTest::newRow("upwards, diagonal drag")
326− << "vnDragArea" << 0.9 << 3.0 << true;
183+ qint64 finalTime = m_fakeTimerFactory->timeSource()->msecsSinceReference() + timeSpanMs;
184+ m_fakeTimerFactory->updateTime(finalTime);
185+}
327186
328− QTest::newRow("upwards, overly diagonal drag")
329− << "vnDragArea" << 2.0 << 3.0 << false;
187+namespace {
188+QPointF calculateInitialTouchPos(DirectionalDragArea *edgeDragArea)
189+{
190+ QPointF localCenter(edgeDragArea->width() / 2., edgeDragArea->height() / 2.);
191+ return edgeDragArea->mapToScene(localCenter);
192+}
330193}
331194
332195/*
@@ -339,17 +202,17 @@ void tst_DirectionalDragArea::dragWithShortDirectionChange()
339202 DirectionalDragArea *edgeDragArea =
340203 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
341204 QVERIFY(edgeDragArea != 0);
342− edgeDragArea->setRecognitionTimer(fakeTimer);
343− edgeDragArea->setTimeSource(fakeTimeSource);
205+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
206+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
344207
345208 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
346209 QPointF touchPoint = initialTouchPos;
347210
348− qreal desiredDragDistance = edgeDragArea->distanceThreshold()*2.0;
211+ qreal desiredDragDistance = edgeDragArea->d->distanceThreshold * 2.0;
349212 QPointF dragDirectionVector(1.0, 0.0);
350− qreal touchStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
351− // make sure we are above minimum speed
352− int touchStepTimeMs = (touchStepDistance / (edgeDragArea->minSpeed() * 5.0f)) * 1000.0f;
213+ qreal touchStepDistance = edgeDragArea->d->distanceThreshold * 0.1f;
214+ // make sure we are above maximum time
215+ int touchStepTimeMs = edgeDragArea->d->maxTime / 20. ;
353216 QPointF touchMovement = dragDirectionVector * touchStepDistance;
354217
355218 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
@@ -373,65 +236,13 @@ void tst_DirectionalDragArea::dragWithShortDirectionChange()
373236 passTime(touchStepTimeMs);
374237 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
375238 } while ((touchPoint - initialTouchPos).manhattanLength() < desiredDragDistance
376− || fakeTimeSource->m_msecsSinceReference < (edgeDragArea->compositionTime() * 1.5f));
239+ || m_fakeTimerFactory->timeSource()->msecsSinceReference() < (edgeDragArea->d->compositionTime * 1.5f));
377240
378− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
241+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Recognized);
379242
380243 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
381244}
382245
383−/*
384− Checks that a gesture will be rejected if it's slower than minSpeed while
385− status is Undecided.
386− */
387−void tst_DirectionalDragArea::minSpeed()
388−{
389− QFETCH(qreal, minSpeed);
390− QFETCH(qreal, speed);
391− QFETCH(int, expectedStatusAfterSpeedCheck);
392−
393− DirectionalDragArea *edgeDragArea =
394− m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
395− QVERIFY(edgeDragArea != 0);
396− edgeDragArea->setRecognitionTimer(fakeTimer);
397− edgeDragArea->setTimeSource(fakeTimeSource);
398−
399− // A really long, unattainable, number. We don't want it getting recognized before
400− // the speed checks we want have been performed
401− edgeDragArea->setDistanceThreshold(500000);
402−
403− edgeDragArea->setMinSpeed(minSpeed);
404−
405− QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
406− QPointF touchPoint = initialTouchPos;
407−
408− QPointF dragDirectionVector(1.0, 0.0);
409− qint64 timeStepMsecs = 10;
410− qreal distanceStep = (speed / 1000.0f) * timeStepMsecs;
411− QPointF touchMovement = dragDirectionVector * distanceStep;
412−
413− QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
414−
415− // Move for a while to ensure our speed check is performed a couple of times
416− for (int i=0; i < 20; ++i) {
417− touchPoint += touchMovement;
418− passTime(timeStepMsecs);
419− QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
420− }
421−
422− QCOMPARE((int)edgeDragArea->status(), expectedStatusAfterSpeedCheck);
423−}
424−
425−void tst_DirectionalDragArea::minSpeed_data()
426−{
427− QTest::addColumn<qreal>("minSpeed");
428− QTest::addColumn<qreal>("speed");
429− QTest::addColumn<int>("expectedStatusAfterSpeedCheck");
430−
431− QTest::newRow("slower than minSpeed") << 100.0 << 50.0 << (int)DirectionalDragArea::WaitingForTouch;
432− QTest::newRow("faster than minSpeed") << 100.0 << 150.0 << (int)DirectionalDragArea::Undecided;
433−}
434−
435246/*
436247 Checks that the recognition timer is started and stopped appropriately.
437248 I.e., check that it's running only while gesture recognition is taking place
@@ -442,11 +253,9 @@ void tst_DirectionalDragArea::recognitionTimerUsage()
442253 DirectionalDragArea *edgeDragArea =
443254 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
444255 QVERIFY(edgeDragArea != 0);
445− edgeDragArea->setRecognitionTimer(fakeTimer);
446− edgeDragArea->setTimeSource(fakeTimeSource);
447−
448− // don't let it interfere with our test
449− edgeDragArea->setMinSpeed(0.0);
256+ AbstractTimer *fakeTimer = m_fakeTimerFactory->createTimer();
257+ edgeDragArea->d->setRecognitionTimer(fakeTimer);
258+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
450259
451260 int timeStepMs = 5; // some arbitrary small value.
452261
@@ -454,62 +263,31 @@ void tst_DirectionalDragArea::recognitionTimerUsage()
454263 QPointF touchPoint = initialTouchPos;
455264
456265 QPointF dragDirectionVector(1.0, 0.0);
457− QPointF touchMovement = dragDirectionVector * (edgeDragArea->distanceThreshold() * 0.2f);
266+ QPointF touchMovement = dragDirectionVector * (edgeDragArea->d->distanceThreshold * 0.2f);
458267
459− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
268+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
460269 QVERIFY(!fakeTimer->isRunning());
461270
462271 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
463272
464− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
273+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Undecided);
465274 QVERIFY(fakeTimer->isRunning());
466275
467276 // Move beyond distance threshold and composition time to ensure recognition
468− while (fakeTimeSource->m_msecsSinceReference <= edgeDragArea->compositionTime() ||
469− (touchPoint - initialTouchPos).manhattanLength() <= edgeDragArea->distanceThreshold()) {
277+ while (m_fakeTimerFactory->timeSource()->msecsSinceReference() <= edgeDragArea->d->compositionTime ||
278+ (touchPoint - initialTouchPos).manhattanLength() <= edgeDragArea->d->distanceThreshold) {
470279
471− QCOMPARE(edgeDragArea->status() == DirectionalDragArea::Undecided, fakeTimer->isRunning());
280+ QCOMPARE(edgeDragArea->d->status == DirectionalDragAreaPrivate::Undecided, fakeTimer->isRunning());
472281
473282 touchPoint += touchMovement;
474283 passTime(timeStepMs);
475284 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
476285 }
477286
478− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
287+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Recognized);
479288 QVERIFY(!fakeTimer->isRunning());
480289}
481290
482−/*
483− A gesture should be rejected if too much time has passed without any new input
484− events from it.
485− */
486−void tst_DirectionalDragArea::maxSilenceTime()
487−{
488− DirectionalDragArea *edgeDragArea =
489− m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
490− QVERIFY(edgeDragArea != 0);
491− edgeDragArea->setRecognitionTimer(fakeTimer);
492− edgeDragArea->setTimeSource(fakeTimeSource);
493−
494− // Make sure this property is not disabled
495− edgeDragArea->setMaxSilenceTime(100);
496−
497− QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
498− QPointF touchPoint = initialTouchPos;
499−
500− QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
501−
502− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
503− QVERIFY(fakeTimer->isRunning());
504−
505− // Force timer to timeout until after maxSilenceTime has been reached
506− while (fakeTimeSource->m_msecsSinceReference < edgeDragArea->maxSilenceTime()) {
507− passTime(fakeTimer->interval());
508− }
509−
510− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
511−}
512−
513291/*
514292 Checks that it informs the X coordinate of the touch point in local and scene coordinates
515293 correctly.
@@ -519,18 +297,19 @@ void tst_DirectionalDragArea::sceneXAndX()
519297 DirectionalDragArea *edgeDragArea =
520298 m_view->rootObject()->findChild<DirectionalDragArea*>("hnDragArea");
521299 QVERIFY(edgeDragArea != 0);
522− edgeDragArea->setRecognitionTimer(fakeTimer);
523− edgeDragArea->setTimeSource(fakeTimeSource);
300+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
301+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
302+ edgeDragArea->setImmediateRecognition(true);
524303
525304 QPointF touchScenePos(m_view->width() - (edgeDragArea->width()/2.0f), m_view->height()/2.0f);
526305
527− QTest::touchEvent(m_view, m_device).press(0, touchScenePos.toPoint());
306+ sendTouchPress(0 /* timestamp */, 0 /* id */, touchScenePos);
528307
529308 QSignalSpy touchXSpy(edgeDragArea, SIGNAL(touchXChanged(qreal)));
530309 QSignalSpy touchSceneXSpy(edgeDragArea, SIGNAL(touchSceneXChanged(qreal)));
531310
532311 touchScenePos.rx() = m_view->width() / 2;
533− QTest::touchEvent(m_view, m_device).move(0, touchScenePos.toPoint());
312+ sendTouchUpdate(50 /* timestamp */, 0 /* id */, touchScenePos);
534313
535314 QCOMPARE(touchXSpy.count(), 1);
536315 QCOMPARE(touchSceneXSpy.count(), 1);
@@ -547,18 +326,19 @@ void tst_DirectionalDragArea::sceneYAndY()
547326 DirectionalDragArea *edgeDragArea =
548327 m_view->rootObject()->findChild<DirectionalDragArea*>("vnDragArea");
549328 QVERIFY(edgeDragArea != 0);
550− edgeDragArea->setRecognitionTimer(fakeTimer);
551− edgeDragArea->setTimeSource(fakeTimeSource);
329+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
330+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
331+ edgeDragArea->setImmediateRecognition(true);
552332
553333 QPointF touchScenePos(m_view->width()/2.0f, m_view->height() - (edgeDragArea->height()/2.0f));
554334
555− QTest::touchEvent(m_view, m_device).press(0, touchScenePos.toPoint());
335+ sendTouchPress(0 /* timestamp */, 0 /* id */, touchScenePos);
556336
557337 QSignalSpy touchYSpy(edgeDragArea, SIGNAL(touchYChanged(qreal)));
558338 QSignalSpy touchSceneYSpy(edgeDragArea, SIGNAL(touchSceneYChanged(qreal)));
559339
560340 touchScenePos.ry() = m_view->height() / 2;
561− QTest::touchEvent(m_view, m_device).move(0, touchScenePos.toPoint());
341+ sendTouchUpdate(50 /* timestamp */, 0 /* id */, touchScenePos);
562342
563343 QCOMPARE(touchYSpy.count(), 1);
564344 QCOMPARE(touchSceneYSpy.count(), 1);
@@ -577,8 +357,8 @@ void tst_DirectionalDragArea::twoFingerTap()
577357 DirectionalDragArea *edgeDragArea =
578358 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
579359 QVERIFY(edgeDragArea != 0);
580− edgeDragArea->setRecognitionTimer(fakeTimer);
581− edgeDragArea->setTimeSource(fakeTimeSource);
360+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
361+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
582362
583363 // Make touches evenly spaced along the edgeDragArea
584364 QPoint touchAPos(edgeDragArea->width()/2.0f, m_view->height()*0.33f);
@@ -592,7 +372,7 @@ void tst_DirectionalDragArea::twoFingerTap()
592372 QTest::touchEvent(m_view, m_device)
593373 .press(0, touchAPos);
594374
595− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
375+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Undecided);
596376
597377 passTime(timeStepMsecs);
598378 QTest::touchEvent(m_view, m_device)
@@ -601,20 +381,20 @@ void tst_DirectionalDragArea::twoFingerTap()
601381
602382 // A second touch point appeared during recognition, reject immediately as this
603383 // can't be a single-touch gesture anymore.
604− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
384+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
605385
606386 passTime(timeStepMsecs);
607387 QTest::touchEvent(m_view, m_device)
608388 .release(0, touchAPos)
609389 .move(1, touchBPos);
610390
611− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
391+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
612392
613393 passTime(timeStepMsecs);
614394 QTest::touchEvent(m_view, m_device)
615395 .release(1, touchBPos);
616396
617− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
397+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
618398
619399 // Perform the second two-finger tap
620400
@@ -622,27 +402,27 @@ void tst_DirectionalDragArea::twoFingerTap()
622402 QTest::touchEvent(m_view, m_device)
623403 .press(0, touchAPos);
624404
625− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
405+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Undecided);
626406
627407 passTime(timeStepMsecs);
628408 QTest::touchEvent(m_view, m_device)
629409 .move(0, touchAPos)
630410 .press(1, touchBPos);
631411
632− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
412+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
633413
634414 passTime(timeStepMsecs);
635415 QTest::touchEvent(m_view, m_device)
636416 .release(0, touchAPos)
637417 .move(1, touchBPos);
638418
639− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
419+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
640420
641421 passTime(timeStepMsecs);
642422 QTest::touchEvent(m_view, m_device)
643423 .release(1, touchBPos);
644424
645− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
425+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
646426}
647427
648428/*
@@ -659,25 +439,25 @@ void tst_DirectionalDragArea::movingDDA()
659439 DirectionalDragArea *edgeDragArea =
660440 rightwardsLauncher->findChild<DirectionalDragArea*>("hpDragArea");
661441 Q_ASSERT(edgeDragArea != 0);
662− edgeDragArea->setRecognitionTimer(fakeTimer);
663− edgeDragArea->setTimeSource(fakeTimeSource);
442+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
443+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
664444
665445 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
666446 QPointF touchPoint = initialTouchPos;
667447
668− qreal desiredDragDistance = edgeDragArea->distanceThreshold()*2.0f;
448+ qreal desiredDragDistance = edgeDragArea->d->distanceThreshold * 2.0f;
669449 QPointF dragDirectionVector(1.0f, 0.0f);
670450
671− qreal movementStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
451+ qreal movementStepDistance = edgeDragArea->d->distanceThreshold * 0.1f;
672452 QPointF touchMovement = dragDirectionVector * movementStepDistance;
673453 int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
674− int movementTimeStepMs = (edgeDragArea->compositionTime() * 1.5f) / totalMovementSteps;
454+ int movementTimeStepMs = (edgeDragArea->d->compositionTime * 1.5f) / totalMovementSteps;
675455
676456 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
677457
678458 // Move it far ahead along the direction of the gesture
679459 // rightwardsLauncher is a parent of our DirectionalDragArea. So moving it will move our DDA
680− rightwardsLauncher->setX(rightwardsLauncher->x() + edgeDragArea->distanceThreshold() * 5.0f);
460+ rightwardsLauncher->setX(rightwardsLauncher->x() + edgeDragArea->d->distanceThreshold * 5.0f);
681461
682462 for (int i = 0; i < totalMovementSteps; ++i) {
683463 touchPoint += touchMovement;
@@ -685,7 +465,7 @@ void tst_DirectionalDragArea::movingDDA()
685465 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
686466 }
687467
688− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
468+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Recognized);
689469
690470 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
691471}
@@ -699,8 +479,8 @@ void tst_DirectionalDragArea::ignoreOldFinger()
699479 DirectionalDragArea *edgeDragArea =
700480 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
701481 Q_ASSERT(edgeDragArea != 0);
702− edgeDragArea->setRecognitionTimer(fakeTimer);
703− edgeDragArea->setTimeSource(fakeTimeSource);
482+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
483+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
704484
705485 // Make touches evenly spaced along the edgeDragArea
706486 QPoint touch0Pos(edgeDragArea->width()/2.0f, m_view->height()*0.33f);
@@ -708,26 +488,26 @@ void tst_DirectionalDragArea::ignoreOldFinger()
708488
709489 QTest::touchEvent(m_view, m_device).press(0, touch0Pos);
710490
711− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
491+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Undecided);
712492
713493 // leave it lying around for some time
714− passTime(qMax(edgeDragArea->maxSilenceTime(), edgeDragArea->compositionTime()) * 10);
494+ passTime(edgeDragArea->d->maxTime * 10);
715495
716− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
496+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
717497
718− qreal desiredDragDistance = edgeDragArea->distanceThreshold()*2.0f;
498+ qreal desiredDragDistance = edgeDragArea->d->distanceThreshold * 2.0f;
719499 QPointF dragDirectionVector(1.0f, 0.0f);
720500
721− qreal movementStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
501+ qreal movementStepDistance = edgeDragArea->d->distanceThreshold * 0.1f;
722502 QPointF touchMovement = dragDirectionVector * movementStepDistance;
723503 int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
724− int movementTimeStepMs = (edgeDragArea->compositionTime() * 1.5f) / totalMovementSteps;
504+ int movementTimeStepMs = (edgeDragArea->d->compositionTime * 1.5f) / totalMovementSteps;
725505
726506 QTest::touchEvent(m_view, m_device)
727507 .move(0, touch0Pos)
728508 .press(1, touch1Pos);
729509
730− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
510+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Undecided);
731511
732512 for (int i = 0; i < totalMovementSteps; ++i) {
733513 touch1Pos += touchMovement.toPoint();
@@ -737,13 +517,13 @@ void tst_DirectionalDragArea::ignoreOldFinger()
737517 .move(1, touch1Pos);
738518 }
739519
740− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
520+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Recognized);
741521
742522 QTest::touchEvent(m_view, m_device)
743523 .move(0, touch0Pos)
744524 .release(1, touch1Pos);
745525
746− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
526+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
747527}
748528
749529/*
@@ -762,19 +542,19 @@ void tst_DirectionalDragArea::rotated()
762542 DirectionalDragArea *edgeDragArea =
763543 rightwardsLauncher->findChild<DirectionalDragArea*>("hpDragArea");
764544 Q_ASSERT(edgeDragArea != 0);
765− edgeDragArea->setRecognitionTimer(fakeTimer);
766− edgeDragArea->setTimeSource(fakeTimeSource);
545+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
546+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
767547
768548 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
769549 QPointF touchPoint = initialTouchPos;
770550
771− qreal desiredDragDistance = edgeDragArea->distanceThreshold()*2.0f;
551+ qreal desiredDragDistance = edgeDragArea->d->distanceThreshold * 2.0f;
772552 QPointF dragDirectionVector(0.0f, 1.0f);
773553
774− qreal movementStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
554+ qreal movementStepDistance = edgeDragArea->d->distanceThreshold * 0.1f;
775555 QPointF touchMovement = dragDirectionVector * movementStepDistance;
776556 int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
777− int movementTimeStepMs = (edgeDragArea->compositionTime() * 1.5f) / totalMovementSteps;
557+ int movementTimeStepMs = (edgeDragArea->d->compositionTime * 1.5f) / totalMovementSteps;
778558
779559 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
780560
@@ -784,7 +564,7 @@ void tst_DirectionalDragArea::rotated()
784564 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
785565 }
786566
787− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
567+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Recognized);
788568
789569 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
790570}
@@ -802,31 +582,30 @@ void tst_DirectionalDragArea::sceneDistance()
802582 DirectionalDragArea *edgeDragArea =
803583 rightwardsLauncher->findChild<DirectionalDragArea*>("hpDragArea");
804584 Q_ASSERT(edgeDragArea != 0);
805− edgeDragArea->setRecognitionTimer(fakeTimer);
806− edgeDragArea->setTimeSource(fakeTimeSource);
585+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
586+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
587+
588+ // to disable the position smoothing so that we can more easily check sceneDistance values
589+ edgeDragArea->setImmediateRecognition(true);
807590
808591 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
809592 QPointF touchPoint = initialTouchPos;
810593
811− qreal desiredDragDistance = edgeDragArea->distanceThreshold()*2.0f;
594+ qreal desiredDragDistance = edgeDragArea->d->distanceThreshold * 2.0f;
812595
813− qreal movementStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
596+ qreal movementStepDistance = edgeDragArea->d->distanceThreshold * 0.1f;
814597 QPointF touchMovement = dragDirectionVector * movementStepDistance;
815598 int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
816− int movementTimeStepMs = (edgeDragArea->compositionTime() * 1.5f) / totalMovementSteps;
599+ int movementTimeStepMs = (edgeDragArea->d->compositionTime * 1.5f) / totalMovementSteps;
817600
818− QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
601+ qint64 timestamp = 0;
602+
603+ sendTouchPress(timestamp, 0, touchPoint);
819604
820−#if (QT_VERSION >= QT_VERSION_CHECK(5, 4, 0))
821− QQuickWindowPrivate *wp = QQuickWindowPrivate::get(m_view);
822−#endif
823605 for (int i = 0; i < totalMovementSteps; ++i) {
824606 touchPoint += touchMovement;
825− passTime(movementTimeStepMs);
826− QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
827−#if (QT_VERSION >= QT_VERSION_CHECK(5, 4, 0))
828− wp->flushDelayedTouchEvent();
829−#endif
607+ timestamp += movementTimeStepMs;
608+ sendTouchUpdate(timestamp, 0, touchPoint);
830609 }
831610
832611 qreal actualDragDistance = ((qreal)totalMovementSteps) * movementStepDistance;
@@ -836,7 +615,8 @@ void tst_DirectionalDragArea::sceneDistance()
836615 // NB: qFuzzyCompare(), used internally by QCOMPARE(), is broken.
837616 QVERIFY(qAbs(edgeDragArea->sceneDistance() - actualDragDistance) < 0.001);
838617
839− QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
618+ timestamp += movementTimeStepMs;
619+ sendTouchRelease(timestamp, 0, touchPoint);
840620}
841621
842622void tst_DirectionalDragArea::sceneDistance_data()
@@ -860,18 +640,18 @@ void tst_DirectionalDragArea::disabledWhileDragging()
860640 DirectionalDragArea *edgeDragArea =
861641 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
862642 Q_ASSERT(edgeDragArea != 0);
863− edgeDragArea->setRecognitionTimer(fakeTimer);
864− edgeDragArea->setTimeSource(fakeTimeSource);
643+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
644+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
865645
866646 QPointF touchPoint = calculateInitialTouchPos(edgeDragArea);
867647
868− qreal desiredDragDistance = edgeDragArea->distanceThreshold()*2.0f;
648+ qreal desiredDragDistance = edgeDragArea->d->distanceThreshold * 2.0f;
869649 QPointF dragDirectionVector(1., 0.); // horizontal positive
870650
871− qreal movementStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
651+ qreal movementStepDistance = edgeDragArea->d->distanceThreshold * 0.1f;
872652 QPointF touchMovement = dragDirectionVector * movementStepDistance;
873653 int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
874− int movementTimeStepMs = (edgeDragArea->compositionTime() * 1.5f) / totalMovementSteps;
654+ int movementTimeStepMs = (edgeDragArea->d->compositionTime * 1.5f) / totalMovementSteps;
875655
876656 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
877657
@@ -881,13 +661,13 @@ void tst_DirectionalDragArea::disabledWhileDragging()
881661 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
882662 }
883663
884− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
664+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Recognized);
885665 QCOMPARE(edgeDragArea->dragging(), true);
886666
887667 // disable the dragArea while it's being dragged.
888668 edgeDragArea->setEnabled(false);
889669
890− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
670+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
891671 QCOMPARE(edgeDragArea->dragging(), false);
892672
893673 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
@@ -898,15 +678,14 @@ void tst_DirectionalDragArea::oneFingerDownFollowedByLateSecondFingerDown()
898678 DirectionalDragArea *edgeDragArea =
899679 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
900680 Q_ASSERT(edgeDragArea != 0);
901− edgeDragArea->setRecognitionTimer(fakeTimer);
902− edgeDragArea->setTimeSource(fakeTimeSource);
681+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
682+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
903683
904684 // Disable some constraints we're not interested in
905− edgeDragArea->setMaxSilenceTime(60 * 1000);
906− edgeDragArea->setMinSpeed(0);
685+ edgeDragArea->d->setMaxTime(60 * 1000);
907686
908687 // And ensure others have the values we want
909− edgeDragArea->setCompositionTime(60);
688+ edgeDragArea->d->compositionTime = 60;
910689
911690 // Put an item right behind edgeDragArea to receive the touches ignored by it
912691 DummyItem *dummyItem = new DummyItem;
@@ -923,10 +702,10 @@ void tst_DirectionalDragArea::oneFingerDownFollowedByLateSecondFingerDown()
923702
924703 QTest::touchEvent(m_view, m_device).press(0, touch0Pos);
925704
926− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
705+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Undecided);
927706
928707 // We are now going to be way beyond compositionTime
929− passTime(edgeDragArea->compositionTime()*3);
708+ passTime(edgeDragArea->d->compositionTime*3);
930709
931710 QTest::touchEvent(m_view, m_device)
932711 .move(0, touch0Pos)
@@ -948,7 +727,7 @@ void tst_DirectionalDragArea::oneFingerDownFollowedByLateSecondFingerDown()
948727 .move(0, touch0Pos)
949728 .move(1, touch1Pos);
950729
951− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
730+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Undecided);
952731
953732 passTime(5);
954733
@@ -956,7 +735,7 @@ void tst_DirectionalDragArea::oneFingerDownFollowedByLateSecondFingerDown()
956735 .release(0, touch0Pos)
957736 .move(1, touch1Pos);
958737
959− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
738+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
960739
961740 passTime(5);
962741
@@ -964,7 +743,7 @@ void tst_DirectionalDragArea::oneFingerDownFollowedByLateSecondFingerDown()
964743 .release(1, touch1Pos);
965744
966745 // Shouldn't be keepping info about touches that no longer exist or interest us
967− QVERIFY(edgeDragArea->m_activeTouches.isEmpty());
746+ QVERIFY(edgeDragArea->d->activeTouches.isEmpty());
968747
969748 delete dummyItem;
970749}
@@ -974,12 +753,11 @@ void tst_DirectionalDragArea::givesUpWhenLosesTouch()
974753 DirectionalDragArea *edgeDragArea =
975754 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
976755 Q_ASSERT(edgeDragArea != 0);
977− edgeDragArea->setRecognitionTimer(fakeTimer);
978− edgeDragArea->setTimeSource(fakeTimeSource);
756+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
757+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
979758
980759 // Disable some constraints we're not interested in
981− edgeDragArea->setMaxSilenceTime(60 * 1000);
982− edgeDragArea->setMinSpeed(0);
760+ edgeDragArea->d->setMaxTime(60 * 1000);
983761
984762 // Put an item right in front of edgeDragArea
985763 DummyItem *dummyItem = new DummyItem(edgeDragArea->parentItem());
@@ -998,11 +776,11 @@ void tst_DirectionalDragArea::givesUpWhenLosesTouch()
998776
999777 QTest::touchEvent(m_view, m_device).press(0, touchPos);
1000778
1001− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
779+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Undecided);
1002780
1003781 m_touchRegistry->requestTouchOwnership(0, dummyItem);
1004782
1005− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
783+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
1006784
1007785 dummyItem->grabTouchPoints({0});
1008786 dummyItem->touchEventHandler = [&](QTouchEvent *event) { event->accept(); };
@@ -1010,9 +788,9 @@ void tst_DirectionalDragArea::givesUpWhenLosesTouch()
1010788 passTime(5);
1011789 QTest::touchEvent(m_view, m_device).release(0, touchPos);
1012790
1013− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
791+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
1014792
1015− QVERIFY(edgeDragArea->m_activeTouches.isEmpty());
793+ QVERIFY(edgeDragArea->d->activeTouches.isEmpty());
1016794}
1017795
1018796void tst_DirectionalDragArea::threeFingerDrag()
@@ -1020,15 +798,14 @@ void tst_DirectionalDragArea::threeFingerDrag()
1020798 DirectionalDragArea *edgeDragArea =
1021799 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
1022800 Q_ASSERT(edgeDragArea != 0);
1023− edgeDragArea->setRecognitionTimer(fakeTimer);
1024− edgeDragArea->setTimeSource(fakeTimeSource);
801+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
802+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
1025803
1026804 // Disable some constraints we're not interested in
1027− edgeDragArea->setMaxSilenceTime(60 * 1000);
1028− edgeDragArea->setMinSpeed(0);
805+ edgeDragArea->d->setMaxTime(60 * 1000);
1029806
1030807 // And ensure others have the values we want
1031− edgeDragArea->setCompositionTime(60);
808+ edgeDragArea->d->compositionTime = 60;
1032809
1033810 // Make touches evenly spaced along the edgeDragArea
1034811 QPoint touch0Pos(edgeDragArea->width()/2.0f, m_view->height()*0.25f);
@@ -1038,14 +815,14 @@ void tst_DirectionalDragArea::threeFingerDrag()
1038815 QTest::touchEvent(m_view, m_device)
1039816 .press(0, touch0Pos);
1040817
1041− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
818+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Undecided);
1042819
1043820 passTime(5);
1044821 QTest::touchEvent(m_view, m_device)
1045822 .move(0, touch0Pos)
1046823 .press(1, touch1Pos);
1047824
1048− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
825+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
1049826
1050827 passTime(5);
1051828 QTest::touchEvent(m_view, m_device)
@@ -1053,7 +830,7 @@ void tst_DirectionalDragArea::threeFingerDrag()
1053830 .move(1, touch1Pos)
1054831 .press(2, touch2Pos);
1055832
1056− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
833+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
1057834
1058835 passTime(10);
1059836 QTest::touchEvent(m_view, m_device)
@@ -1077,7 +854,7 @@ void tst_DirectionalDragArea::threeFingerDrag()
1077854 .release(0, touch0Pos);
1078855
1079856 // Shouldn't be keepping info about touches that no longer exist or interest us
1080− QVERIFY(edgeDragArea->m_activeTouches.isEmpty());
857+ QVERIFY(edgeDragArea->d->activeTouches.isEmpty());
1081858}
1082859
1083860/*
@@ -1090,12 +867,12 @@ void tst_DirectionalDragArea::immediateRecognitionWhenConstraintsDisabled()
1090867 DirectionalDragArea *edgeDragArea =
1091868 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
1092869 Q_ASSERT(edgeDragArea != 0);
1093− edgeDragArea->setRecognitionTimer(fakeTimer);
1094− edgeDragArea->setTimeSource(fakeTimeSource);
870+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
871+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
1095872
1096873 // Disable the minimum amount of constraints to ensure immediate recognition
1097− edgeDragArea->setDistanceThreshold(0);
1098− edgeDragArea->setCompositionTime(0);
874+ edgeDragArea->d->setDistanceThreshold(0);
875+ edgeDragArea->d->compositionTime = 0;
1099876
1100877 // Put an item right behind edgeDragArea to receive the touches ignored by it
1101878 DummyItem *dummyItem = new DummyItem;
@@ -1111,7 +888,7 @@ void tst_DirectionalDragArea::immediateRecognitionWhenConstraintsDisabled()
1111888 QTest::touchEvent(m_view, m_device).press(0, touch0Pos);
1112889
1113890 // check for immediate recognition
1114− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
891+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Recognized);
1115892
1116893 // and therefore it should have immediately grabbed the touch point,
1117894 // not letting it leak to items behind him.
@@ -1123,19 +900,19 @@ void tst_DirectionalDragArea::withdrawTouchOwnershipCandidacyIfDisabledDuringRec
1123900 DirectionalDragArea *edgeDragArea =
1124901 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
1125902 Q_ASSERT(edgeDragArea != 0);
1126− edgeDragArea->setRecognitionTimer(fakeTimer);
1127− edgeDragArea->setTimeSource(fakeTimeSource);
903+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
904+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
1128905
1129906 QPointF touchPoint = calculateInitialTouchPos(edgeDragArea);
1130907
1131908 // Move less than the minimum needed for the drag gesture recognition
1132− qreal desiredDragDistance = edgeDragArea->distanceThreshold()*0.5f;
909+ qreal desiredDragDistance = edgeDragArea->d->distanceThreshold * 0.5f;
1133910 QPointF dragDirectionVector(1., 0.); // horizontal positive
1134911
1135− qreal movementStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
912+ qreal movementStepDistance = edgeDragArea->d->distanceThreshold * 0.1f;
1136913 QPointF touchMovement = dragDirectionVector * movementStepDistance;
1137914 int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
1138− int movementTimeStepMs = (edgeDragArea->compositionTime() * 0.8f) / totalMovementSteps;
915+ int movementTimeStepMs = (edgeDragArea->d->compositionTime * 0.8f) / totalMovementSteps;
1139916
1140917 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
1141918
@@ -1145,14 +922,14 @@ void tst_DirectionalDragArea::withdrawTouchOwnershipCandidacyIfDisabledDuringRec
1145922 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
1146923 }
1147924
1148− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
925+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Undecided);
1149926
1150927 // edgeDragArea should be an undecided candidate
1151928 {
1152929 auto touchInfo = m_touchRegistry->findTouchInfo(0);
1153930 QCOMPARE(touchInfo->candidates.size(), 1);
1154931 QCOMPARE(touchInfo->candidates.at(0).item.data(), edgeDragArea);
1155− QCOMPARE(touchInfo->candidates.at(0).undecided, true);
932+ QCOMPARE(touchInfo->candidates.at(0).state, TouchRegistry::CandidateInfo::Undecided);
1156933 }
1157934
1158935 // disable the dragArea while it's still recognizing a possible drag gesture.
@@ -1169,7 +946,7 @@ void tst_DirectionalDragArea::withdrawTouchOwnershipCandidacyIfDisabledDuringRec
1169946 QCOMPARE(touchInfo->candidates.size(), 0);
1170947 }
1171948
1172− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
949+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
1173950
1174951 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
1175952}
@@ -1182,47 +959,13 @@ void tst_DirectionalDragArea::withdrawTouchOwnershipCandidacyIfDisabledDuringRec
1182959 QTest::newRow("invisible") << false;
1183960}
1184961
1185−void tst_DirectionalDragArea::tappedSignal()
1186−{
1187− DirectionalDragArea *edgeDragArea =
1188− m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
1189− QVERIFY(edgeDragArea != 0);
1190− edgeDragArea->setRecognitionTimer(fakeTimer);
1191− edgeDragArea->setTimeSource(fakeTimeSource);
1192−
1193− QFETCH(bool, immediateGestureRecognition);
1194− if (immediateGestureRecognition) {
1195− // Disable the minimum amount of constraints to ensure immediate recognition
1196− edgeDragArea->setDistanceThreshold(0);
1197− edgeDragArea->setCompositionTime(0);
1198− }
1199−
1200− QPoint touch0Pos(edgeDragArea->width()/2.0f, m_view->height()/2.0f);
1201−
1202− QSignalSpy tappedSpy(edgeDragArea, SIGNAL(tapped()));
1203−
1204− QTest::touchEvent(m_view, m_device).press(0, touch0Pos);
1205− passTime(edgeDragArea->maxTapDuration() / 2);
1206− QTest::touchEvent(m_view, m_device).release(0, touch0Pos);
1207−
1208− QCOMPARE(tappedSpy.count(), 1);
1209−}
1210−
1211−void tst_DirectionalDragArea::tappedSignal_data()
1212−{
1213− QTest::addColumn<bool>("immediateGestureRecognition");
1214−
1215− QTest::newRow("immediate gesture recognition") << true;
1216− QTest::newRow("default gesture recognition") << false;
1217−}
1218−
1219962void tst_DirectionalDragArea::gettingTouchOwnershipMakesMouseAreaBehindGetCanceled()
1220963{
1221964 DirectionalDragArea *edgeDragArea =
1222965 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
1223966 QVERIFY(edgeDragArea != nullptr);
1224− edgeDragArea->setRecognitionTimer(fakeTimer);
1225− edgeDragArea->setTimeSource(fakeTimeSource);
967+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
968+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
1226969
1227970 QQuickMouseArea *mouseArea =
1228971 m_view->rootObject()->findChild<QQuickMouseArea*>("mouseArea");
@@ -1233,12 +976,12 @@ void tst_DirectionalDragArea::gettingTouchOwnershipMakesMouseAreaBehindGetCancel
1233976 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
1234977 QPointF touchPoint = initialTouchPos;
1235978
1236− qreal desiredDragDistance = edgeDragArea->distanceThreshold()*2;
979+ qreal desiredDragDistance = edgeDragArea->d->distanceThreshold * 2;
1237980 QPointF dragDirectionVector(1.0f, 0.0f); // rightwards
1238− qreal movementStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
981+ qreal movementStepDistance = edgeDragArea->d->distanceThreshold * 0.1f;
1239982 QPointF touchMovement = dragDirectionVector * movementStepDistance;
1240983 int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
1241− int movementTimeStepMs = (edgeDragArea->compositionTime() * 1.5f) / totalMovementSteps;
984+ int movementTimeStepMs = (edgeDragArea->d->compositionTime * 1.5f) / totalMovementSteps;
1242985
1243986 QCOMPARE(mouseArea->pressed(), false);
1244987
@@ -1259,13 +1002,305 @@ void tst_DirectionalDragArea::gettingTouchOwnershipMakesMouseAreaBehindGetCancel
12591002 // As the DirectionalDragArea recognizes the gesture, it grabs the touch from the MouseArea,
12601003 // which should make the MouseArea get a cancelation event, which will then cause it to
12611004 // reset its state (going back to "unpressed"/"released").
1262− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
1005+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Recognized);
12631006 QCOMPARE(mouseArea->pressed(), false);
12641007 QCOMPARE(mouseAreaSpy.canceledCount, 1);
12651008
12661009 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
12671010}
12681011
1012+void tst_DirectionalDragArea::interleavedTouches()
1013+{
1014+ DirectionalDragArea *edgeDragArea =
1015+ m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
1016+ QVERIFY(edgeDragArea != 0);
1017+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
1018+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
1019+
1020+ QPointF touch0 = edgeDragArea->mapToScene(
1021+ QPointF(edgeDragArea->width()*0.5, edgeDragArea->height()*0.3));
1022+
1023+ qreal desiredDragDistance = edgeDragArea->d->distanceThreshold * 2;
1024+ QPointF dragDirectionVector(1.0f, 0.0f); // rightwards
1025+ qreal movementStepDistance = edgeDragArea->d->distanceThreshold * 0.1f;
1026+ QPointF touchMovement = dragDirectionVector * movementStepDistance;
1027+ int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
1028+ int movementTimeStepMs = (edgeDragArea->d->maxTime * 0.4f) / totalMovementSteps;
1029+
1030+ QTest::touchEvent(m_view, m_device).press(0, touch0.toPoint());
1031+ for (int i = 0; i < totalMovementSteps; ++i) {
1032+ touch0 += touchMovement;
1033+ passTime(movementTimeStepMs);
1034+ QTest::touchEvent(m_view, m_device).move(0, touch0.toPoint());
1035+ }
1036+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Recognized);
1037+
1038+ QPointF touch1 = edgeDragArea->mapToScene(
1039+ QPointF(edgeDragArea->width()*0.5, edgeDragArea->height()*0.6));
1040+
1041+ QTest::touchEvent(m_view, m_device)
1042+ .move(0, touch0.toPoint())
1043+ .press(1, touch1.toPoint());
1044+
1045+ touch1 += touchMovement;
1046+ passTime(movementTimeStepMs);
1047+ QTest::touchEvent(m_view, m_device)
1048+ .move(0, touch0.toPoint())
1049+ .move(1, touch1.toPoint());
1050+
1051+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Recognized);
1052+
1053+ QTest::touchEvent(m_view, m_device)
1054+ .release(0, touch0.toPoint())
1055+ .move(1, touch1.toPoint());
1056+
1057+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
1058+
1059+ touch1 += touchMovement;
1060+ passTime(movementTimeStepMs);
1061+ QTest::touchEvent(m_view, m_device)
1062+ .move(1, touch1.toPoint());
1063+
1064+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
1065+
1066+ QPointF touch2 = edgeDragArea->mapToScene(
1067+ QPointF(edgeDragArea->width()*0.5, edgeDragArea->height()*0.9));
1068+
1069+ passTime(edgeDragArea->d->compositionTime + movementTimeStepMs);
1070+ QTest::touchEvent(m_view, m_device)
1071+ .move(1, touch1.toPoint())
1072+ .press(2, touch2.toPoint());
1073+
1074+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Undecided);
1075+ QCOMPARE(edgeDragArea->d->touchId, 2);
1076+
1077+ touch2 += touchMovement;
1078+ passTime(movementTimeStepMs);
1079+ QTest::touchEvent(m_view, m_device)
1080+ .move(1, touch1.toPoint())
1081+ .move(2, touch2.toPoint());
1082+
1083+ touch1 += touchMovement;
1084+ passTime(movementTimeStepMs);
1085+ QTest::touchEvent(m_view, m_device)
1086+ .move(1, touch1.toPoint())
1087+ .move(2, touch2.toPoint());
1088+
1089+ passTime(movementTimeStepMs);
1090+ QTest::touchEvent(m_view, m_device)
1091+ .release(1, touch1.toPoint())
1092+ .move(2, touch2.toPoint());
1093+
1094+ passTime(movementTimeStepMs);
1095+ QTest::touchEvent(m_view, m_device)
1096+ .release(2, touch2.toPoint());
1097+
1098+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
1099+}
1100+
1101+/*
1102+ A valid right-edge drag performed on mako
1103+ */
1104+void tst_DirectionalDragArea::makoRightEdgeDrag()
1105+{
1106+ m_view->resize(768, 1280);
1107+ QTest::qWait(300);
1108+
1109+ DirectionalDragArea *edgeDragArea =
1110+ m_view->rootObject()->findChild<DirectionalDragArea*>("hnDragArea");
1111+ QVERIFY(edgeDragArea != nullptr);
1112+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
1113+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
1114+
1115+ StatusSpy *statusSpy = new StatusSpy(edgeDragArea);
1116+
1117+ edgeDragArea->d->setPixelsPerMm(320.0 /*mako ppi*/ * 0.03937 /* inches per mm*/);
1118+
1119+ sendTouchPress(319744, 0, QPointF(767.001, 719.719));
1120+ sendTouchUpdate(319765, 0, QPointF(765.744,729.973));
1121+ sendTouchUpdate(319784, 0, QPointF(740.879,752.182));
1122+ sendTouchUpdate(319803, 0, QPointF(689.698,795.795));
1123+ sendTouchUpdate(319826, 0, QPointF(616.978,856.212));
1124+ sendTouchUpdate(319845, 0, QPointF(558.769,906.157));
1125+ sendTouchUpdate(319859, 0, QPointF(513.219,945.266));
1126+ sendTouchUpdate(319878, 0, QPointF(481.31,975.496));
1127+ sendTouchUpdate(319902, 0, QPointF(460.016,997.439));
1128+ sendTouchUpdate(319920, 0, QPointF(449.761,1008.6));
1129+ sendTouchUpdate(319929, 0, QPointF(445.891,1012.42));
1130+ sendTouchUpdate(319947, 0, QPointF(444.884,1013.93));
1131+ sendTouchUpdate(319965, 0, QPointF(444.461,1014.35));
1132+ sendTouchUpdate(320057, 0, QPointF(444.71,1013.56));
1133+ sendTouchUpdate(320138, 0, QPointF(445.434,1013.6));
1134+ sendTouchUpdate(320154, 0, QPointF(446.338,1012.98));
1135+ sendTouchUpdate(320171, 0, QPointF(447.232,1012.08));
1136+ sendTouchRelease(320171, 0, QPointF(447.232,1012.08));
1137+
1138+ QCOMPARE(statusSpy->recognized(), true);
1139+
1140+ delete statusSpy;
1141+}
1142+
1143+/*
1144+ A vertical, downwards swipe performed on mako near its right edge.
1145+
1146+ The DirectionalDragArea on the right edge must not recognize this
1147+ gesture.
1148+ */
1149+void tst_DirectionalDragArea::makoRightEdgeDrag_verticalDownwards()
1150+{
1151+ m_view->resize(768, 1280);
1152+ QTest::qWait(300);
1153+
1154+ DirectionalDragArea *edgeDragArea =
1155+ m_view->rootObject()->findChild<DirectionalDragArea*>("hnDragArea");
1156+ QVERIFY(edgeDragArea != nullptr);
1157+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
1158+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
1159+
1160+ edgeDragArea->d->setPixelsPerMm(320.0 /*mako ppi*/ * 0.03937 /* inches per mm*/);
1161+
1162+ StatusSpy *statusSpy = new StatusSpy(edgeDragArea);
1163+
1164+ sendTouchPress(12012445, 26, QPointF(767.001,461.82));
1165+ sendTouchUpdate(12012472, 26, QPointF(767.001,462.569));
1166+ sendTouchUpdate(12012528, 26, QPointF(767.001,463.334));
1167+ sendTouchUpdate(12012546, 26, QPointF(767.001,466.856));
1168+ sendTouchUpdate(12012571, 26, QPointF(767.001,473.291));
1169+ sendTouchUpdate(12012587, 26, QPointF(767.001,487.31));
1170+ sendTouchUpdate(12012604, 26, QPointF(765.364,507.521));
1171+ sendTouchUpdate(12012618, 26, QPointF(765.364,507.521));
1172+ sendTouchUpdate(12012627, 26, QPointF(762.642,534.317));
1173+ sendTouchUpdate(12012655, 26, QPointF(760.846,573.406));
1174+ sendTouchUpdate(12012667, 26, QPointF(759.838,625.295));
1175+ sendTouchUpdate(12012675, 26, QPointF(758.875,703.207));
1176+ sendTouchUpdate(12012696, 26, QPointF(761.52,777.015));
1177+ sendTouchUpdate(12012713, 26, QPointF(765.659,835.591));
1178+ sendTouchUpdate(12012731, 26, QPointF(766.778,883.206));
1179+ sendTouchUpdate(12012748, 26, QPointF(767.001,922.937));
1180+ sendTouchUpdate(12012779, 26, QPointF(767.001,967.558));
1181+ sendTouchUpdate(12012798, 26, QPointF(767.001,1006.12));
1182+ sendTouchUpdate(12012809, 26, QPointF(767.001,1033.1));
1183+ sendTouchRelease(12012810, 26, QPointF(767.001,1033.1));
1184+
1185+ QCOMPARE(statusSpy->recognized(), false);
1186+
1187+ delete statusSpy;
1188+}
1189+
1190+/*
1191+ A valid left-edge drag performed on mako. This one starts a bit slow than speeds up
1192+ */
1193+void tst_DirectionalDragArea::makoLeftEdgeDrag_slowStart()
1194+{
1195+ m_view->resize(768, 1280);
1196+ QTest::qWait(300);
1197+
1198+ DirectionalDragArea *edgeDragArea =
1199+ m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
1200+ QVERIFY(edgeDragArea != nullptr);
1201+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
1202+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
1203+
1204+ edgeDragArea->d->setPixelsPerMm(320.0 /*mako ppi*/ * 0.03937 /* inches per mm*/);
1205+
1206+ StatusSpy *statusSpy = new StatusSpy(edgeDragArea);
1207+
1208+ sendTouchPress(4002267, 77, QPointF(0,885.154));
1209+ sendTouchUpdate(4002275, 77, QPointF(0,886.214));
1210+ sendTouchUpdate(4002311, 77, QPointF(1.09568,887.75));
1211+ sendTouchUpdate(4002329, 77, QPointF(3.53647,890.191));
1212+ sendTouchUpdate(4002347, 77, QPointF(7.87434,892.879));
1213+ sendTouchUpdate(4002366, 77, QPointF(12.3036,895.075));
1214+ sendTouchUpdate(4002384, 77, QPointF(15.8885,896.849));
1215+ sendTouchUpdate(4002406, 77, QPointF(18.4504,897.88));
1216+ sendTouchUpdate(4002420, 77, QPointF(20.2429,898.149));
1217+ sendTouchUpdate(4002439, 77, QPointF(20.9945,898.149));
1218+ sendTouchUpdate(4002457, 77, QPointF(21.8819,898.149));
1219+ sendTouchUpdate(4002480, 77, QPointF(22.7454,897.389));
1220+ sendTouchUpdate(4002493, 77, QPointF(23.5456,896.589));
1221+ sendTouchUpdate(4002511, 77, QPointF(24.5435,895.031));
1222+ sendTouchUpdate(4002529, 77, QPointF(25.4271,892.32));
1223+ sendTouchUpdate(4002548, 77, QPointF(26.3145,889.658));
1224+ sendTouchUpdate(4002566, 77, QPointF(27.2004,886.999));
1225+ sendTouchUpdate(4002584, 77, QPointF(28.035,885.048));
1226+ sendTouchUpdate(4002603, 77, QPointF(29.9684,883.167));
1227+ sendTouchUpdate(4002620, 77, QPointF(33.3591,881.403));
1228+ sendTouchUpdate(4002639, 77, QPointF(44.1017,879.642));
1229+ sendTouchUpdate(4002657, 77, QPointF(64.828,878.502));
1230+ sendTouchUpdate(4002675, 77, QPointF(87.9486,878.157));
1231+ sendTouchUpdate(4002693, 77, QPointF(112.96,877.742));
1232+ sendTouchUpdate(4002711, 77, QPointF(138.903,877.157));
1233+ sendTouchUpdate(4002729, 77, QPointF(163.204,877.157));
1234+ sendTouchUpdate(4002747, 77, QPointF(182.127,877.157));
1235+ sendTouchUpdate(4002765, 77, QPointF(194.478,877.657));
1236+ sendTouchUpdate(4002785, 77, QPointF(201.474,878.508));
1237+ sendTouchUpdate(4002803, 77, QPointF(204.855,879.401));
1238+ sendTouchUpdate(4002822, 77, QPointF(206.616,880.281));
1239+ sendTouchUpdate(4002839, 77, QPointF(207.115,880.906));
1240+ sendTouchUpdate(4002894, 77, QPointF(206.865,881.184));
1241+ sendTouchUpdate(4002912, 77, QPointF(206.865,882.143));
1242+ sendTouchUpdate(4002930, 77, QPointF(206.865,883.106));
1243+ sendTouchUpdate(4002949, 77, QPointF(206.526,883.994));
1244+ sendTouchUpdate(4002967, 77, QPointF(205.866,884.88));
1245+ sendTouchUpdate(4002985, 77, QPointF(205.866,885.766));
1246+ sendTouchUpdate(4003005, 77, QPointF(205.866,886.654));
1247+ sendTouchUpdate(4003021, 77, QPointF(205.366,887.537));
1248+ sendTouchUpdate(4003039, 77, QPointF(204.592,888.428));
1249+ sendTouchUpdate(4003050, 77, QPointF(204.367,888.653));
1250+ sendTouchRelease(4003050, 77, QPointF(204.367,888.653));
1251+
1252+ QCOMPARE(statusSpy->recognized(), true);
1253+
1254+ delete statusSpy;
1255+}
1256+
1257+void tst_DirectionalDragArea::makoLeftEdgeDrag_movesSlightlyBackwardsOnStart()
1258+{
1259+ m_view->resize(768, 1280);
1260+ QTest::qWait(300);
1261+
1262+ DirectionalDragArea *edgeDragArea =
1263+ m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
1264+ QVERIFY(edgeDragArea != nullptr);
1265+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
1266+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
1267+
1268+ edgeDragArea->d->setPixelsPerMm(320.0 /*mako ppi*/ * 0.03937 /* inches per mm*/);
1269+
1270+ StatusSpy *statusSpy = new StatusSpy(edgeDragArea);
1271+
1272+ sendTouchPress(41097, 24, QPointF(13.9909,827.177));
1273+ sendTouchUpdate(41120, 24, QPointF(19.2375,825.677));
1274+ sendTouchUpdate(41138, 24, QPointF(18.4057,826.177));
1275+ sendTouchUpdate(41161, 24, QPointF(20.1067,825.867));
1276+ sendTouchUpdate(41177, 24, QPointF(21.8869,824.977));
1277+ sendTouchUpdate(41193, 24, QPointF(24.7603,823.494));
1278+ sendTouchUpdate(41211, 24, QPointF(28.3889,821.725));
1279+ sendTouchUpdate(41229, 24, QPointF(32.2909,819.955));
1280+ sendTouchUpdate(41247, 24, QPointF(38.2251,817.431));
1281+ sendTouchUpdate(41266, 24, QPointF(52.4182,814.223));
1282+ sendTouchUpdate(41284, 24, QPointF(85.8465,809.483));
1283+ sendTouchUpdate(41302, 24, QPointF(126.091,802.741));
1284+ sendTouchUpdate(41320, 24, QPointF(153.171,797.977));
1285+ sendTouchUpdate(41338, 24, QPointF(170.565,795.077));
1286+ sendTouchUpdate(41356, 24, QPointF(178.685,794.101));
1287+ sendTouchUpdate(41375, 24, QPointF(183.706,793.225));
1288+ sendTouchUpdate(41393, 24, QPointF(186.112,793.19));
1289+ sendTouchUpdate(41411, 24, QPointF(187.634,793.19));
1290+ sendTouchUpdate(41429, 24, QPointF(188.505,793.19));
1291+ sendTouchUpdate(41532, 24, QPointF(187.816,793.19));
1292+ sendTouchUpdate(41538, 24, QPointF(186.902,793.19));
1293+ sendTouchUpdate(41557, 24, QPointF(186.01,793.19));
1294+ sendTouchUpdate(41575, 24, QPointF(185.125,793.444));
1295+ sendTouchUpdate(41593, 24, QPointF(184.229,793.69));
1296+ sendTouchUpdate(41605, 24, QPointF(183.88,793.69));
1297+ sendTouchRelease(41607, 24, QPointF(183.88,793.69));
1298+
1299+ QCOMPARE(statusSpy->recognized(), true);
1300+
1301+ delete statusSpy;
1302+}
1303+
12691304QTEST_MAIN(tst_DirectionalDragArea)
12701305
12711306#include "tst_DirectionalDragArea.moc"
tests/plugins/Ubuntu/Gestures/tst_DirectionalDragArea.qml+8−1
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013,2015 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -16,12 +16,19 @@
1616
1717import QtQuick 2.0
1818import Ubuntu.Components 0.1
19+import Unity.Test 0.1
1920
2021Rectangle {
2122 width: units.gu(60)
2223 height: units.gu(60)
2324 color: "white"
2425
26+ Binding {
27+ target: MouseTouchAdaptor
28+ property: "enabled"
29+ value: true
30+ }
31+
2532 MouseArea {
2633 id: mouseArea
2734 objectName: "mouseArea"
tests/plugins/Ubuntu/Gestures/tst_TouchDispatcher.cpp+5−6
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2014 Canonical, Ltd.
2+ * Copyright (C) 2014-2015 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -81,8 +81,7 @@ void tst_TouchDispatcher::sendMouseEventIfTouchIgnored()
8181 event->accept();
8282 };
8383
84− touchDispatcher.dispatch(QEvent::TouchBegin, m_device, Qt::NoModifier, touchPoints, m_view,
85− 12345 /* timestamp */);
84+ touchDispatcher.dispatch(m_device, Qt::NoModifier, touchPoints, m_view, 12345 /* timestamp */);
8685
8786 QVERIFY(gotTouchEvent);
8887 QCOMPARE(gotMousePressEvent, shouldGetMousePress);
@@ -128,18 +127,18 @@ void tst_TouchDispatcher::mouseDoubleClick()
128127 }
129128 ulong timestamp = 12345;
130129
131− touchDispatcher.dispatch(QEvent::TouchBegin, m_device, Qt::NoModifier, touchPoints, m_view, timestamp);
130+ touchDispatcher.dispatch(m_device, Qt::NoModifier, touchPoints, m_view, timestamp);
132131
133132 touchPoints[0].setState(Qt::TouchPointReleased);
134133 timestamp += doubleClickInterval / 10;
135134
136− touchDispatcher.dispatch(QEvent::TouchEnd, m_device, Qt::NoModifier, touchPoints, m_view, timestamp);
135+ touchDispatcher.dispatch(m_device, Qt::NoModifier, touchPoints, m_view, timestamp);
137136
138137 touchPoints[0].setId(1);
139138 touchPoints[0].setState(Qt::TouchPointPressed);
140139 timestamp += timeBetweenClicks;
141140
142− touchDispatcher.dispatch(QEvent::TouchBegin, m_device, Qt::NoModifier, touchPoints, m_view, timestamp);
141+ touchDispatcher.dispatch(m_device, Qt::NoModifier, touchPoints, m_view, timestamp);
143142
144143 QCOMPARE(gotDoubleClickEvent, shouldSendDoubleClick);
145144}
tests/plugins/Ubuntu/Gestures/tst_TouchGate.cpp+56−1
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2014 Canonical, Ltd.
2+ * Copyright (C) 2014-2015 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -63,6 +63,7 @@ private Q_SLOTS:
6363 void init(); // called right before each and every test function is executed
6464 void cleanup(); // called right after each and every test function is executed
6565
66+private:
6667 void onlyCandidate_passThrough();
6768 void holdsEventsUntilGainsOwnership();
6869 void holdsEventsUntilGainsOwnership_data();
@@ -70,6 +71,9 @@ private Q_SLOTS:
7071 void dontDispacthToDisabledOrInvisibleTarget();
7172 void dontDispacthToDisabledOrInvisibleTarget_data();
7273
74+private Q_SLOTS:
75+ void disabledWhileHoldingTouch();
76+
7377private:
7478 QQuickView *createView();
7579 TouchRegistry *touchRegistry;
@@ -326,6 +330,57 @@ void tst_TouchGate::dontDispacthToDisabledOrInvisibleTarget_data()
326330 QTest::newRow("disabled invisible") << false << false;
327331}
328332
333+void tst_TouchGate::disabledWhileHoldingTouch()
334+{
335+ TouchGate *touchGate = view->rootObject()->findChild<TouchGate*>("touchGate");
336+ Q_ASSERT(touchGate);
337+
338+ TestItem *testItem = new TestItem;
339+ testItem->setWidth(touchGate->width());
340+ testItem->setHeight(touchGate->height());
341+ testItem->setParentItem(view->rootObject());
342+ testItem->setZ(0.0);
343+
344+ touchGate->setZ(1.0);
345+ touchGate->setTargetItem(testItem);
346+
347+ QTest::touchEvent(view, device)
348+ .press(0, QPoint(100,100));
349+
350+ QCOMPARE(testItem->touchEventsReceived.count(), 1);
351+ testItem->touchEventsReceived.clear();
352+
353+ touchGate->setEnabled(false);
354+
355+ QTest::touchEvent(view, device)
356+ .move(0, QPoint(101,101));
357+
358+ // Nothing new came as TouchGate didn't even get it because it's disabled
359+ QCOMPARE(testItem->touchEventsReceived.count(), 0);
360+
361+ touchGate->setEnabled(true);
362+
363+ QTest::touchEvent(view, device)
364+ .move(0, QPoint(102,102));
365+
366+ QTest::touchEvent(view, device)
367+ .release(0, QPoint(102,102));
368+
369+ // Nothing new came because TouchGate has already discarded touch 0
370+ QCOMPARE(testItem->touchEventsReceived.count(), 0);
371+
372+ QTest::touchEvent(view, device)
373+ .press(1, QPoint(200,200));
374+
375+ QCOMPARE(testItem->touchEventsReceived.count(), 1);
376+ {
377+ // it got only the new touch point.
378+ QSharedPointer<QTouchEvent> touchEvent = testItem->touchEventsReceived[0];
379+ QCOMPARE(touchEvent->touchPoints().count(), 1);
380+ QCOMPARE(touchEvent->touchPoints()[0].id(), 1);
381+ }
382+}
383+
329384///////////// TestItem /////////////////////////////////////////////////////////////
330385
331386void TestItem::touchEvent(QTouchEvent *event)
tests/qmltests/Components/CMakeLists.txt+3−1
@@ -5,6 +5,7 @@ include_directories(
55 ${CMAKE_SOURCE_DIR}/libs/UbuntuGestures
66 ${CMAKE_CURRENT_BINARY_DIR}
77 ${Qt5Gui_PRIVATE_INCLUDE_DIRS}
8+ ${Qt5Quick_PRIVATE_INCLUDE_DIRS}
89 )
910
1011add_executable(DragHandleTestExec tst_DragHandle.cpp)
@@ -12,7 +13,8 @@ qt5_use_modules(DragHandleTestExec Test Core Qml Gui Quick)
1213target_link_libraries(DragHandleTestExec UbuntuGesturesQml UbuntuGestures)
1314
1415add_binary_qml_test(DragHandle ${CMAKE_BINARY_DIR}/plugins/Ubuntu/Gestures "" "")
15−add_manual_qml_test(. DragHandle IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins)
16+add_manual_qml_test(. DragHandle IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins ${CMAKE_BINARY_DIR}/tests/utils/modules)
1617
1718add_definitions(-DUBUNTU_GESTURES_PLUGIN_DIR="${CMAKE_BINARY_DIR}/plugins")
1819add_definitions(-DTEST_DIR="${CMAKE_CURRENT_LIST_DIR}")
20+add_definitions(-DTESTS_UTILS_MODULES_DIR="${CMAKE_BINARY_DIR}/tests/utils/modules")
tests/qmltests/Components/tst_DragHandle.cpp+15−13
@@ -20,21 +20,16 @@
2020#include <qpa/qwindowsysteminterface.h>
2121#include <QtQuick/QQuickView>
2222#include <QtQml/QQmlEngine>
23+#include <private/qquickwindow_p.h>
2324
25+#include <AxisVelocityCalculator.h>
2426#include <DirectionalDragArea.h>
27+#include <DirectionalDragArea_p.h>
2528#include <Timer.h>
2629#include <TouchRegistry.h>
2730
2831using namespace UbuntuGestures;
2932
30−class FakeTimeSource : public TimeSource
31−{
32−public:
33− FakeTimeSource() { m_msecsSinceReference = 0; }
34− qint64 msecsSinceReference() override {return m_msecsSinceReference;}
35− qint64 m_msecsSinceReference;
36−};
37−
3833class tst_DragHandle: public QObject
3934{
4035 Q_OBJECT
@@ -56,7 +51,6 @@ private Q_SLOTS:
5651 void hintingAnimation();
5752 void hintingAnimation_dontRestartAfterFinishedAndStillPressed();
5853
59−
6054private:
6155 void flickAndHold(DirectionalDragArea *dragHandle, qreal distance);
6256 void drag(QPointF &touchPoint, const QPointF& direction, qreal distance,
@@ -105,8 +99,8 @@ void tst_DragHandle::init()
10599 QQuickItem *controls = m_view->rootObject()->findChild<QQuickItem*>("controls");
106100 controls->setVisible(false);
107101
108− m_fakeTimer = new FakeTimer;
109102 m_fakeTimeSource.reset(new FakeTimeSource);
103+ m_fakeTimer = new FakeTimer(m_fakeTimeSource);
110104}
111105
112106void tst_DragHandle::cleanup()
@@ -129,6 +123,7 @@ QQuickView *tst_DragHandle::createView()
129123 window->setResizeMode(QQuickView::SizeRootObjectToView);
130124 window->engine()->addImportPath(QLatin1String(UBUNTU_GESTURES_PLUGIN_DIR));
131125 window->engine()->addImportPath(QLatin1String(TEST_DIR));
126+ window->engine()->addImportPath(QStringLiteral(TESTS_UTILS_MODULES_DIR));
132127
133128 return window;
134129}
@@ -190,17 +185,24 @@ void tst_DragHandle::flickAndHold(DirectionalDragArea *dragHandle,
190185 // speed.
191186 m_fakeTimeSource->m_msecsSinceReference += 5000;
192187 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
188+
189+ QQuickWindowPrivate *windowPrivate = QQuickWindowPrivate::get(m_view);
190+ windowPrivate->flushDelayedTouchEvent();
193191}
194192
195193void tst_DragHandle::drag(QPointF &touchPoint, const QPointF& direction, qreal distance,
196194 int numSteps, qint64 timeMs)
197195{
196+ QQuickWindowPrivate *windowPrivate = QQuickWindowPrivate::get(m_view);
197+
198198 qint64 timeStep = timeMs / numSteps;
199199 QPointF touchMovement = direction * (distance / (qreal)numSteps);
200200 for (int i = 0; i < numSteps; ++i) {
201201 touchPoint += touchMovement;
202202 m_fakeTimeSource->m_msecsSinceReference += timeStep;
203203 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
204+
205+ windowPrivate->flushDelayedTouchEvent();
204206 }
205207}
206208
@@ -209,8 +211,8 @@ DirectionalDragArea *tst_DragHandle::fetchAndSetupDragHandle(const char *objectN
209211 DirectionalDragArea *dragHandle =
210212 m_view->rootObject()->findChild<DirectionalDragArea*>(objectName);
211213 Q_ASSERT(dragHandle != 0);
212− dragHandle->setRecognitionTimer(m_fakeTimer);
213− dragHandle->setTimeSource(m_fakeTimeSource);
214+ dragHandle->d->setRecognitionTimer(m_fakeTimer);
215+ dragHandle->d->setTimeSource(m_fakeTimeSource);
214216
215217 AxisVelocityCalculator *edgeDragEvaluator =
216218 dragHandle->findChild<AxisVelocityCalculator*>("edgeDragEvaluator");
@@ -242,6 +244,7 @@ void tst_DragHandle::dragThreshold_horizontal()
242244 baseItem->setRotation(rotation);
243245
244246 DirectionalDragArea *dragHandle = fetchAndSetupDragHandle("rightwardsDragHandle");
247+ QQuickItem *parentItem = dragHandle->parentItem();
245248
246249 qreal dragThreshold = fetchDragThreshold(dragHandle);
247250
@@ -249,7 +252,6 @@ void tst_DragHandle::dragThreshold_horizontal()
249252 flickAndHold(dragHandle, dragThreshold * 0.7);
250253
251254 // should rollback
252− QQuickItem *parentItem = dragHandle->parentItem();
253255 tryCompare([&](){ return parentItem->x(); }, -parentItem->width());
254256 QCOMPARE(parentItem->property("shown").toBool(), false);
255257
tests/qmltests/Components/tst_DragHandle.qml+7
@@ -19,6 +19,7 @@ import "../../../qml/Components"
1919import "tst_DragHandle"
2020import Ubuntu.Components 0.1
2121import Ubuntu.Gestures 0.1
22+import Unity.Test 0.1
2223
2324/*
2425 Two blue fullscreen Showables (a vertical and a horizontal one) with red handles you
@@ -33,6 +34,12 @@ Rectangle {
3334 width: units.gu(70)
3435 height: units.gu(70)
3536
37+ Binding {
38+ target: MouseTouchAdaptor
39+ property: "enabled"
40+ value: true
41+ }
42+
3643 property var dragHandle
3744
3845 property bool stretch: false
tests/qmltests/Components/tst_DragHandle/BidirectionalShowable.qml+2−2
@@ -60,8 +60,8 @@ Item {
6060 maxTotalDragDistance: root.parent.width
6161 autoCompleteDragThreshold: parent.width / 3
6262
63− onStatusChanged: {
64− if (status === DirectionalDragArea.Recognized) {
63+ onDraggingChanged: {
64+ if (dragging) {
6565 dragHandleRecognizedGesture(dragHandle);
6666 }
6767 }
tests/qmltests/Components/tst_DragHandle/HorizontalShowable.qml+4−4
@@ -65,8 +65,8 @@ Showable {
6565 maxTotalDragDistance: showable.parent.width
6666 hintDisplacement: showable.hintDisplacement
6767
68− onStatusChanged: {
69− if (status === DirectionalDragArea.Recognized) {
68+ onDraggingChanged: {
69+ if (dragging) {
7070 dragHandleRecognizedGesture(leftwardsDragHandle);
7171 }
7272 }
@@ -88,8 +88,8 @@ Showable {
8888 maxTotalDragDistance: showable.parent.width
8989 hintDisplacement: showable.hintDisplacement
9090
91− onStatusChanged: {
92− if (status === DirectionalDragArea.Recognized) {
91+ onDraggingChanged: {
92+ if (dragging) {
9393 dragHandleRecognizedGesture(rightwardsDragHandle);
9494 }
9595 }
tests/qmltests/Components/tst_DragHandle/VerticalShowable.qml+4−4
@@ -65,8 +65,8 @@ Showable {
6565 maxTotalDragDistance: showable.parent.height
6666 hintDisplacement: showable.hintDisplacement
6767
68− onStatusChanged: {
69− if (status === DirectionalDragArea.Recognized) {
68+ onDraggingChanged: {
69+ if (dragging) {
7070 dragHandleRecognizedGesture(upwardsDragHandle);
7171 }
7272 }
@@ -88,8 +88,8 @@ Showable {
8888 maxTotalDragDistance: showable.parent.height
8989 hintDisplacement: showable.hintDisplacement
9090
91− onStatusChanged: {
92− if (status === DirectionalDragArea.Recognized) {
91+ onDraggingChanged: {
92+ if (dragging) {
9393 dragHandleRecognizedGesture(downwardsDragHandle);
9494 }
9595 }
tests/qmltests/Stages/tst_PhoneStage.qml+8−8
@@ -164,14 +164,14 @@ Item {
164164
165165 function test_enterSpread_data() {
166166 return [
167− {tag: "<position1 (linear movement)", positionMarker: "positionMarker1", linear: true, offset: -1, endPhase: 0, targetPhase: 0, newFocusedIndex: 1 },
168− {tag: "<position1 (non-linear movement)", positionMarker: "positionMarker1", linear: false, offset: -1, endPhase: 0, targetPhase: 0, newFocusedIndex: 0 },
169− {tag: ">position1", positionMarker: "positionMarker1", linear: true, offset: +1, endPhase: 0, targetPhase: 0, newFocusedIndex: 1 },
170− {tag: "<position2 (linear)", positionMarker: "positionMarker2", linear: true, offset: -1, endPhase: 0, targetPhase: 0, newFocusedIndex: 1 },
171− {tag: "<position2 (non-linear)", positionMarker: "positionMarker2", linear: false, offset: -1, endPhase: 0, targetPhase: 0, newFocusedIndex: 1 },
172− {tag: ">position2", positionMarker: "positionMarker2", linear: true, offset: +1, endPhase: 1, targetPhase: 0, newFocusedIndex: 1 },
173− {tag: "<position3", positionMarker: "positionMarker3", linear: true, offset: -1, endPhase: 1, targetPhase: 0, newFocusedIndex: 1 },
174− {tag: ">position3", positionMarker: "positionMarker3", linear: true, offset: +1, endPhase: 1, targetPhase: 2, newFocusedIndex: 2 },
167+ {tag: "<position1 (linear movement)", positionMarker: "positionMarker1", linear: true, offset: 0, endPhase: 0, targetPhase: 0, newFocusedIndex: 1 },
168+ {tag: "<position1 (non-linear movement)", positionMarker: "positionMarker1", linear: false, offset: 0, endPhase: 0, targetPhase: 0, newFocusedIndex: 0 },
169+ {tag: ">position1", positionMarker: "positionMarker1", linear: true, offset: +5, endPhase: 0, targetPhase: 0, newFocusedIndex: 1 },
170+ {tag: "<position2 (linear)", positionMarker: "positionMarker2", linear: true, offset: 0, endPhase: 0, targetPhase: 0, newFocusedIndex: 1 },
171+ {tag: "<position2 (non-linear)", positionMarker: "positionMarker2", linear: false, offset: 0, endPhase: 0, targetPhase: 0, newFocusedIndex: 1 },
172+ {tag: ">position2", positionMarker: "positionMarker2", linear: true, offset: +5, endPhase: 1, targetPhase: 0, newFocusedIndex: 1 },
173+ {tag: "<position3", positionMarker: "positionMarker3", linear: true, offset: 0, endPhase: 1, targetPhase: 0, newFocusedIndex: 1 },
174+ {tag: ">position3", positionMarker: "positionMarker3", linear: true, offset: +5, endPhase: 1, targetPhase: 2, newFocusedIndex: 2 },
175175 ];
176176 }
177177
tests/qmltests/Tutorial/tst_Tutorial.qml+5−1
@@ -235,7 +235,11 @@ Item {
235235 checkLeftEdge();
236236 checkBottomEdge();
237237 if (name === "tutorialRight") return page;
238− touchFlick(shell, shell.width, halfHeight, halfWidth, halfHeight);
238+ touchFlick(shell,
239+ shell.width, halfHeight,
240+ halfWidth, halfHeight,
241+ true /* beginTouch */, true /* endTouch */,
242+ 20 /* speed */, 50 /* iterations */);
239243 var overlay = findChild(page, "overlay");
240244 tryCompare(overlay, "shown", true);
241245 var tick = findChild(page, "tick");
tests/qmltests/tst_Shell.qml+14−10
@@ -373,11 +373,11 @@ Rectangle {
373373 function test_phoneLeftEdgeDrag_data() {
374374 return [
375375 {tag: "without launcher",
376− revealLauncher: false, swipeLength: units.gu(27), appHides: true, focusedApp: "dialer-app",
376+ revealLauncher: false, swipeLength: units.gu(30), appHides: true, focusedApp: "dialer-app",
377377 launcherHides: true, greeterShown: false},
378378
379379 {tag: "with launcher",
380− revealLauncher: true, swipeLength: units.gu(27), appHides: true, focusedApp: "dialer-app",
380+ revealLauncher: true, swipeLength: units.gu(30), appHides: true, focusedApp: "dialer-app",
381381 launcherHides: true, greeterShown: false},
382382
383383 {tag: "small swipe",
@@ -385,7 +385,7 @@ Rectangle {
385385 launcherHides: false, greeterShown: false},
386386
387387 {tag: "long swipe",
388− revealLauncher: false, swipeLength: units.gu(27), appHides: true, focusedApp: "dialer-app",
388+ revealLauncher: false, swipeLength: units.gu(30), appHides: true, focusedApp: "dialer-app",
389389 launcherHides: true, greeterShown: false},
390390
391391 {tag: "small swipe with greeter",
@@ -393,11 +393,11 @@ Rectangle {
393393 launcherHides: false, greeterShown: true},
394394
395395 {tag: "long swipe with greeter",
396− revealLauncher: false, swipeLength: units.gu(27), appHides: true, focusedApp: "dialer-app",
396+ revealLauncher: false, swipeLength: units.gu(30), appHides: true, focusedApp: "dialer-app",
397397 launcherHides: true, greeterShown: true},
398398
399399 {tag: "swipe over dash",
400− revealLauncher: false, swipeLength: units.gu(27), appHides: true, focusedApp: "unity8-dash",
400+ revealLauncher: false, swipeLength: units.gu(30), appHides: true, focusedApp: "unity8-dash",
401401 launcherHides: false, greeterShown: false},
402402 ];
403403 }
@@ -602,7 +602,7 @@ Rectangle {
602602
603603 dashCommunicatorSpy.clear();
604604 // Minimize the application we just launched
605− swipeFromLeftEdge(units.gu(27));
605+ swipeFromLeftEdge(shell.width * 0.75);
606606
607607 tryCompare(ApplicationManager, "focusedApplicationId", "unity8-dash");
608608
@@ -724,7 +724,8 @@ Rectangle {
724724 }
725725
726726 function dragLauncherIntoView() {
727− var launcherPanel = findChild(shell, "launcherPanel");
727+ var launcher = findChild(shell, "launcher");
728+ var launcherPanel = findChild(launcher, "launcherPanel");
728729 verify(launcherPanel.x = - launcherPanel.width);
729730
730731 var touchStartX = 2;
@@ -732,6 +733,7 @@ Rectangle {
732733 touchFlick(shell, touchStartX, touchStartY, launcherPanel.width + units.gu(1), touchStartY);
733734
734735 tryCompare(launcherPanel, "x", 0);
736+ tryCompare(launcher, "state", "visible");
735737 }
736738
737739 function tapOnAppIconInLauncher() {
@@ -768,7 +770,9 @@ Rectangle {
768770 function swipeFromLeftEdge(swipeLength) {
769771 var touchStartX = 2;
770772 var touchStartY = shell.height / 2;
771− touchFlick(shell, touchStartX, touchStartY, swipeLength, touchStartY);
773+ touchFlick(shell,
774+ touchStartX , touchStartY,
775+ touchStartX + swipeLength, touchStartY);
772776 }
773777
774778 function itemIsOnScreen(item) {
@@ -835,7 +839,7 @@ Rectangle {
835839 tryCompareFunction(function() { return app.session !== null && app.session.surface !== null }, true);
836840
837841 // Minimize the application we just launched
838− swipeFromLeftEdge(units.gu(26) + 1);
842+ swipeFromLeftEdge(shell.width * 0.75);
839843
840844 waitUntilDashIsFocused();
841845
@@ -917,7 +921,7 @@ Rectangle {
917921
918922 compare(panel.fullscreenMode, true);
919923
920− touchFlick(shell, units.gu(2), touchStartY, units.gu(10), touchStartY, false, false);
924+ touchFlick(shell, units.gu(2), touchStartY, shell.width * 0.5, touchStartY, false, false);
921925
922926 tryCompare(panel, "fullscreenMode", false);
923927
tests/qmltests/tst_ShellWithPin.qml+2−2
@@ -612,14 +612,14 @@ Item {
612612 tryCompare(coverPage, "showProgress", 1);
613613
614614 // Swipe cover page away
615− touchFlick(shell, 2, shell.height / 2, units.gu(27), shell.height / 2);
615+ touchFlick(shell, 2, shell.height / 2, units.gu(30), shell.height / 2);
616616 tryCompare(launcher, "x", -launcher.width);
617617 tryCompare(coverPage, "showProgress", 0);
618618 compare(lockscreen.shown, true);
619619 compare(ApplicationManager.focusedApplicationId, "gallery-app");
620620
621621 // Now attempt a swipe on lockscreen
622− touchFlick(shell, 2, shell.height / 2, units.gu(27), shell.height / 2);
622+ touchFlick(shell, 2, shell.height / 2, units.gu(30), shell.height / 2);
623623 tryCompare(launcher, "x", 0);
624624 compare(lockscreen.shown, true);
625625 compare(ApplicationManager.focusedApplicationId, "gallery-app");
tests/utils/modules/Unity/Test/UnityTestCase.qml+3−7
@@ -269,7 +269,7 @@ TestCase {
269269 speed = (speed !== undefined) ? speed : units.gu(10)
270270
271271 // Set a default iterations if not specified
272− var iterations = (iterations !== undefined) ? iterations : 5
272+ var iterations = (iterations !== undefined) ? iterations : 10
273273
274274 var distance = Math.sqrt(Math.pow(rootTo.x - rootFrom.x, 2) + Math.pow(rootTo.Y - rootFrom.y, 2))
275275 var totalTime = (distance / speed) * 1000 /* converting speed to pixels/ms */
@@ -403,12 +403,8 @@ TestCase {
403403 function removeTimeConstraintsFromDirectionalDragAreas(item) {
404404
405405 // use duck-typing to identify a DirectionalDragArea
406− if (item.minSpeed != undefined
407− && item.maxSilenceTime != undefined
408− && item.compositionTime != undefined) {
409− item.minSpeed = 0;
410− item.maxSilenceTime = 60 * 60 * 1000;
411− item.compositionTime = 0;
406+ if (item.removeTimeConstraints != undefined) {
407+ item.removeTimeConstraints();
412408 } else {
413409 for (var i in item.children) {
414410 removeTimeConstraintsFromDirectionalDragAreas(item.children[i]);