on xenial help sign in
Merged ddaImprovements
3628f62Daniel d'Andrada
libs/UbuntuGestures/CMakeLists.txt+1
@@ -5,6 +5,7 @@ set(UbuntuGestures_SOURCES
5 CandidateInactivityTimer.cpp
5 CandidateInactivityTimer.cpp
6 DebugHelpers.cpp
6 DebugHelpers.cpp
7 Timer.cpp
7 Timer.cpp
8+ TimeSource.cpp
8 TouchOwnershipEvent.cpp
9 TouchOwnershipEvent.cpp
9 TouchRegistry.cpp
10 TouchRegistry.cpp
10 UnownedTouchEvent.cpp
11 UnownedTouchEvent.cpp
libs/UbuntuGestures/CandidateInactivityTimer.h+1−1
@@ -32,7 +32,7 @@ public:
32 AbstractTimerFactory &timerFactory,
32 AbstractTimerFactory &timerFactory,
33 QObject *parent = nullptr);
33 QObject *parent = nullptr);
34
34
35− const int durationMs = 350;
35+ const int durationMs = 1000;
36
36
37Q_SIGNALS:
37Q_SIGNALS:
38 void candidateDefaulted(int touchId, QQuickItem *candidate);
38 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 @@
1/*
1/*
2− * Copyright (C) 2013 - Canonical Ltd.
2+ * Copyright (C) 2013,2015 Canonical Ltd.
3 *
3 *
4 * This program is free software: you can redistribute it and/or modify it
4 * This program is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU Lesser General Public License, as
5 * under the terms of the GNU Lesser General Public License, as
@@ -21,14 +21,14 @@
21#ifndef UBUNTUGESTURES_TIMESOURCE_H
21#ifndef UBUNTUGESTURES_TIMESOURCE_H
22#define UBUNTUGESTURES_TIMESOURCE_H
22#define UBUNTUGESTURES_TIMESOURCE_H
23
23
24−#include "UbuntuGesturesQmlGlobal.h"
24+#include "UbuntuGesturesGlobal.h"
25#include <QSharedPointer>
25#include <QSharedPointer>
26
26
27namespace UbuntuGestures {
27namespace UbuntuGestures {
28/*
28/*
29 Interface for a time source.
29 Interface for a time source.
30 */
30 */
31−class UBUNTUGESTURESQML_EXPORT TimeSource {
31+class UBUNTUGESTURES_EXPORT TimeSource {
32public:
32public:
33 virtual ~TimeSource() {}
33 virtual ~TimeSource() {}
34 /* Returns the current time in milliseconds since some reference time in the past. */
34 /* Returns the current time in milliseconds since some reference time in the past. */
@@ -40,7 +40,7 @@ typedef QSharedPointer<TimeSource> SharedTimeSource;
40 Implementation of a time source
40 Implementation of a time source
41 */
41 */
42class RealTimeSourcePrivate;
42class RealTimeSourcePrivate;
43−class RealTimeSource : public TimeSource {
43+class UBUNTUGESTURES_EXPORT RealTimeSource : public TimeSource {
44public:
44public:
45 RealTimeSource();
45 RealTimeSource();
46 virtual ~RealTimeSource();
46 virtual ~RealTimeSource();
@@ -49,6 +49,16 @@ private:
49 RealTimeSourcePrivate *d;
49 RealTimeSourcePrivate *d;
50};
50};
51
51
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+
52} // namespace UbuntuGestures
62} // namespace UbuntuGestures
53
63
54#endif // UBUNTUGESTURES_TIMESOURCE_H
64#endif // UBUNTUGESTURES_TIMESOURCE_H
libs/UbuntuGestures/Timer.cpp+63−10
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2014 Canonical, Ltd.
2+ * Copyright (C) 2014-2015 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -58,13 +58,36 @@ void Timer::setSingleShot(bool value)
58
58
59/////////////////////////////////// FakeTimer //////////////////////////////////
59/////////////////////////////////// FakeTimer //////////////////////////////////
60
60
61−FakeTimer::FakeTimer(QObject *parent)
61+FakeTimer::FakeTimer(const SharedTimeSource &timeSource, QObject *parent)
62 : UbuntuGestures::AbstractTimer(parent)
62 : UbuntuGestures::AbstractTimer(parent)
63 , m_interval(0)
63 , m_interval(0)
64 , m_singleShot(false)
64 , m_singleShot(false)
65+ , m_timeSource(timeSource)
65{
66{
66}
67}
67
68
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+
68int FakeTimer::interval() const
91int FakeTimer::interval() const
69{
92{
70 return m_interval;
93 return m_interval;
@@ -87,23 +110,53 @@ void FakeTimer::setSingleShot(bool value)
87
110
88/////////////////////////////////// FakeTimerFactory //////////////////////////////////
111/////////////////////////////////// FakeTimerFactory //////////////////////////////////
89
112
90−AbstractTimer *FakeTimerFactory::createTimer(QObject *parent)
113+FakeTimerFactory::FakeTimerFactory()
91{
114{
92− FakeTimer *fakeTimer = new FakeTimer(parent);
115+ m_timeSource.reset(new FakeTimeSource);
93−
116+}
94− timers.append(fakeTimer);
95
117
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+ }
97}
126}
98
127
99−void FakeTimerFactory::makeRunningTimersTimeout()
128+void FakeTimerFactory::updateTime(qint64 targetTime)
100{
129{
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+
101 for (int i = 0; i < timers.count(); ++i) {
141 for (int i = 0; i < timers.count(); ++i) {
102 FakeTimer *timer = timers[i].data();
142 FakeTimer *timer = timers[i].data();
103− if (timer && timer->isRunning()) {
143+ if (timer) {
104− timer->emitTimeout();
144+ timer->update();
105 }
145 }
106 }
146 }
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;
107}
160}
108
161
109} // namespace UbuntuGestures
162} // namespace UbuntuGestures
libs/UbuntuGestures/Timer.h+15−3
@@ -18,6 +18,7 @@
18#define UBUNTUGESTURES_TIMER_H
18#define UBUNTUGESTURES_TIMER_H
19
19
20#include "UbuntuGesturesGlobal.h"
20#include "UbuntuGesturesGlobal.h"
21+#include "TimeSource.h"
21
22
22#include <QObject>
23#include <QObject>
23#include <QPointer>
24#include <QPointer>
@@ -66,17 +67,21 @@ class UBUNTUGESTURES_EXPORT FakeTimer : public AbstractTimer
66{
67{
67 Q_OBJECT
68 Q_OBJECT
68public:
69public:
69− FakeTimer(QObject *parent = nullptr);
70+ FakeTimer(const SharedTimeSource &timeSource, QObject *parent = nullptr);
70
71
71− virtual void emitTimeout() { Q_EMIT timeout(); }
72+ void update();
73+ qint64 nextTimeoutTime() const { return m_nextTimeoutTime; }
72
74
73 int interval() const override;
75 int interval() const override;
74 void setInterval(int msecs) override;
76 void setInterval(int msecs) override;
77+ void start() override;
75 bool isSingleShot() const override;
78 bool isSingleShot() const override;
76 void setSingleShot(bool value) override;
79 void setSingleShot(bool value) override;
77private:
80private:
78 int m_interval;
81 int m_interval;
79 bool m_singleShot;
82 bool m_singleShot;
83+ SharedTimeSource m_timeSource;
84+ qint64 m_nextTimeoutTime;
80};
85};
81
86
82class UBUNTUGESTURES_EXPORT AbstractTimerFactory
87class UBUNTUGESTURES_EXPORT AbstractTimerFactory
@@ -95,9 +100,16 @@ public:
95class UBUNTUGESTURES_EXPORT FakeTimerFactory : public AbstractTimerFactory
100class UBUNTUGESTURES_EXPORT FakeTimerFactory : public AbstractTimerFactory
96{
101{
97public:
102public:
103+ FakeTimerFactory();
104+ virtual ~FakeTimerFactory();
105+
106+ void updateTime(qint64 msecsSinceReference);
107+ QSharedPointer<TimeSource> timeSource() { return m_timeSource; }
108+
98 AbstractTimer *createTimer(QObject *parent = nullptr) override;
109 AbstractTimer *createTimer(QObject *parent = nullptr) override;
99− void makeRunningTimersTimeout();
100 QList<QPointer<FakeTimer>> timers;
110 QList<QPointer<FakeTimer>> timers;
111+private:
112+ QSharedPointer<FakeTimeSource> m_timeSource;
101};
113};
102
114
103} // namespace UbuntuGestures
115} // namespace UbuntuGestures
libs/UbuntuGestures/TouchRegistry.cpp+12−14
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2014 Canonical, Ltd.
2+ * Copyright (C) 2014-2015 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -95,10 +95,6 @@ void TouchRegistry::deliverTouchUpdatesToUndecidedCandidatesAndWatchers(const QT
95 // for each point and there should not be many active points at any given moment.
95 // for each point and there should not be many active points at any given moment.
96 // But having three nested for-loops does scare.
96 // But having three nested for-loops does scare.
97
97
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−
102 const QList<QTouchEvent::TouchPoint> &updatedTouchPoints = event->touchPoints();
98 const QList<QTouchEvent::TouchPoint> &updatedTouchPoints = event->touchPoints();
103
99
104 // Maps an item to the touches in this event he should be informed about.
100 // Maps an item to the touches in this event he should be informed about.
@@ -118,7 +114,9 @@ void TouchRegistry::deliverTouchUpdatesToUndecidedCandidatesAndWatchers(const QT
118 for (int i = 0; i < touchInfo->candidates.count(); ++i) {
114 for (int i = 0; i < touchInfo->candidates.count(); ++i) {
119 CandidateInfo &candidate = touchInfo->candidates[i];
115 CandidateInfo &candidate = touchInfo->candidates[i];
120 Q_ASSERT(!candidate.item.isNull());
116 Q_ASSERT(!candidate.item.isNull());
117+ if (candidate.state != CandidateInfo::InterimOwner) {
121− touchIdsForItems[candidate.item.data()].append(touchInfo->id);
118+ touchIdsForItems[candidate.item.data()].append(touchInfo->id);
119+ }
122 }
120 }
123 }
121 }
124
122
@@ -260,7 +258,7 @@ void TouchRegistry::addCandidateOwnerForTouch(int id, QQuickItem *candidate)
260 // TODO: Check if candidate already exists
258 // TODO: Check if candidate already exists
261
259
262 CandidateInfo candidateInfo;
260 CandidateInfo candidateInfo;
263− candidateInfo.undecided = true;
261+ candidateInfo.state = CandidateInfo::Undecided;
264 candidateInfo.item = candidate;
262 candidateInfo.item = candidate;
265 candidateInfo.inactivityTimer = new CandidateInactivityTimer(id, candidate,
263 candidateInfo.inactivityTimer = new CandidateInactivityTimer(id, candidate,
266 *m_timerFactory,
264 *m_timerFactory,
@@ -301,8 +299,8 @@ void TouchRegistry::removeCandidateOwnerForTouch(int id, QQuickItem *candidate)
301 for (int i = 0; i < touchInfo->candidates.count() && indexRemoved == -1; ++i) {
299 for (int i = 0; i < touchInfo->candidates.count() && indexRemoved == -1; ++i) {
302 CandidateInfo &candidateInfo = touchInfo->candidates[i];
300 CandidateInfo &candidateInfo = touchInfo->candidates[i];
303 if (candidateInfo.item == candidate) {
301 if (candidateInfo.item == candidate) {
304− Q_ASSERT(i > 0 || candidateInfo.undecided);
302+ Q_ASSERT(i > 0 || candidateInfo.state == CandidateInfo::Undecided);
305− if (i == 0 && !candidateInfo.undecided) {
303+ if (i == 0 && candidateInfo.state != CandidateInfo::Undecided) {
306 qCritical("TouchRegistry: touch owner is being removed.");
304 qCritical("TouchRegistry: touch owner is being removed.");
307 }
305 }
308 delete candidateInfo.inactivityTimer;
306 delete candidateInfo.inactivityTimer;
@@ -340,7 +338,7 @@ void TouchRegistry::requestTouchOwnership(int id, QQuickItem *candidate)
340 for (int i = 0; i < touchInfo->candidates.count(); ++i) {
338 for (int i = 0; i < touchInfo->candidates.count(); ++i) {
341 CandidateInfo &candidateInfo = touchInfo->candidates[i];
339 CandidateInfo &candidateInfo = touchInfo->candidates[i];
342 if (candidateInfo.item == candidate) {
340 if (candidateInfo.item == candidate) {
343− candidateInfo.undecided = false;
341+ candidateInfo.state = CandidateInfo::Requested;
344 delete candidateInfo.inactivityTimer;
342 delete candidateInfo.inactivityTimer;
345 candidateInfo.inactivityTimer = nullptr;
343 candidateInfo.inactivityTimer = nullptr;
346 candidateIndex = i;
344 candidateIndex = i;
@@ -351,7 +349,7 @@ void TouchRegistry::requestTouchOwnership(int id, QQuickItem *candidate)
351 // add it as a candidate if not present yet
349 // add it as a candidate if not present yet
352 if (candidateIndex < 0) {
350 if (candidateIndex < 0) {
353 CandidateInfo candidateInfo;
351 CandidateInfo candidateInfo;
354− candidateInfo.undecided = false;
352+ candidateInfo.state = CandidateInfo::InterimOwner;
355 candidateInfo.item = candidate;
353 candidateInfo.item = candidate;
356 candidateInfo.inactivityTimer = nullptr;
354 candidateInfo.inactivityTimer = nullptr;
357 touchInfo->candidates.append(candidateInfo);
355 touchInfo->candidates.append(candidateInfo);
@@ -407,8 +405,8 @@ void TouchRegistry::rejectCandidateOwnerForTouch(int id, QQuickItem *candidate)
407 for (int i = 0; i < touchInfo->candidates.count() && rejectedCandidateIndex == -1; ++i) {
405 for (int i = 0; i < touchInfo->candidates.count() && rejectedCandidateIndex == -1; ++i) {
408 CandidateInfo &candidateInfo = touchInfo->candidates[i];
406 CandidateInfo &candidateInfo = touchInfo->candidates[i];
409 if (candidateInfo.item == candidate) {
407 if (candidateInfo.item == candidate) {
410− Q_ASSERT(i > 0 || candidateInfo.undecided);
408+ Q_ASSERT(i > 0 || candidateInfo.state == CandidateInfo::Undecided);
411− if (i == 0 && !candidateInfo.undecided) {
409+ if (i == 0 && candidateInfo.state != CandidateInfo::Undecided) {
412 qCritical() << "TouchRegistry: Can't reject item (" << (void*)candidate
410 qCritical() << "TouchRegistry: Can't reject item (" << (void*)candidate
413 << ") as it already owns touch" << id;
411 << ") as it already owns touch" << id;
414 return;
412 return;
@@ -467,7 +465,7 @@ void TouchRegistry::TouchInfo::init(int id)
467
465
468bool TouchRegistry::TouchInfo::isOwned() const
466bool TouchRegistry::TouchInfo::isOwned() const
469{
467{
470− return !candidates.isEmpty() && !candidates.first().undecided;
468+ return !candidates.isEmpty() && candidates.first().state != CandidateInfo::Undecided;
471}
469}
472
470
473bool TouchRegistry::TouchInfo::ended() const
471bool TouchRegistry::TouchInfo::ended() const
libs/UbuntuGestures/TouchRegistry.h+18−4
@@ -79,9 +79,11 @@ namespace UbuntuGestures {
79 If an item wants ownership over touches as soon as he receives the TouchBegin for them, his step 1
79 If an item wants ownership over touches as soon as he receives the TouchBegin for them, his step 1
80 would be instead:
80 would be instead:
81 TouchRegistry::instance()->requestTouchOwnership(touchId, this);
81 TouchRegistry::instance()->requestTouchOwnership(touchId, this);
82− return true;
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
83− He would then be notified once ownership has been granted to him, from which point onwards he could
85+ been granted to him (from TouchRegistry perspective), from which point onwards he could safely assume
84− safely assume other TouchRegistry users wouldn't snatch this touch away from him.
86+ other TouchRegistry users wouldn't snatch this touch away from him.
85
87
86 Items oblivious to TouchRegistry will lose their touch points without warning, just like in plain Qt.
88 Items oblivious to TouchRegistry will lose their touch points without warning, just like in plain Qt.
87
89
@@ -132,7 +134,19 @@ private Q_SLOTS:
132private:
134private:
133 class CandidateInfo {
135 class CandidateInfo {
134 public:
136 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;
136 // TODO: Prune candidates that become null and resolve ownership accordingly.
150 // TODO: Prune candidates that become null and resolve ownership accordingly.
137 QPointer<QQuickItem> item;
151 QPointer<QQuickItem> item;
138 QPointer<UbuntuGestures::CandidateInactivityTimer> inactivityTimer;
152 QPointer<UbuntuGestures::CandidateInactivityTimer> inactivityTimer;
plugins/Ubuntu/Gestures/CMakeLists.txt+1−1
@@ -4,10 +4,10 @@ remove_definitions(-DQT_NO_KEYWORDS)
4set(UbuntuGesturesQml_SOURCES
4set(UbuntuGesturesQml_SOURCES
5 plugin.cpp
5 plugin.cpp
6 AxisVelocityCalculator.cpp
6 AxisVelocityCalculator.cpp
7+ Damper.cpp
7 Direction.cpp
8 Direction.cpp
8 DirectionalDragArea.cpp
9 DirectionalDragArea.cpp
9 PressedOutsideNotifier.cpp
10 PressedOutsideNotifier.cpp
10− TimeSource.cpp
11 TouchDispatcher.cpp
11 TouchDispatcher.cpp
12 TouchGate.cpp
12 TouchGate.cpp
13)
13)
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:
84 Damper<qreal> m_y;
84 Damper<qreal> m_y;
85};
85};
86
86
87+QDebug operator<<(QDebug dbg, const DampedPointF &p);
88+
87#endif // UBUNTU_GESTURES_DAMPER_H
89#endif // UBUNTU_GESTURES_DAMPER_H
plugins/Ubuntu/Gestures/DirectionalDragArea.cpp+322−323
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2013-2014 Canonical, Ltd.
2+ * Copyright (C) 2013-2015 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -21,6 +21,7 @@
21
21
22#include <QQuickWindow>
22#include <QQuickWindow>
23#include <QtCore/qmath.h>
23#include <QtCore/qmath.h>
24+#include <QScreen>
24#include <QDebug>
25#include <QDebug>
25
26
26#pragma GCC diagnostic push
27#pragma GCC diagnostic push
@@ -33,6 +34,8 @@
33#include "TouchRegistry.h"
34#include "TouchRegistry.h"
34#include "UnownedTouchEvent.h"
35#include "UnownedTouchEvent.h"
35
36
37+#include "DirectionalDragArea_p.h"
38+
36using namespace UbuntuGestures;
39using namespace UbuntuGestures;
37
40
38#if DIRECTIONALDRAGAREA_DEBUG
41#if DIRECTIONALDRAGAREA_DEBUG
@@ -40,11 +43,11 @@ using namespace UbuntuGestures;
40#include "DebugHelpers.h"
43#include "DebugHelpers.h"
41
44
42namespace {
45namespace {
43−const char *statusToString(DirectionalDragArea::Status status)
46+const char *statusToString(DirectionalDragAreaPrivate::Status status)
44{
47{
45− if (status == DirectionalDragArea::WaitingForTouch) {
48+ if (status == DirectionalDragAreaPrivate::WaitingForTouch) {
46 return "WaitingForTouch";
49 return "WaitingForTouch";
47− } else if (status == DirectionalDragArea::Undecided) {
50+ } else if (status == DirectionalDragAreaPrivate::Undecided) {
48 return "Undecided";
51 return "Undecided";
49 } else {
52 } else {
50 return "Recognized";
53 return "Recognized";
@@ -56,205 +59,168 @@ const char *statusToString(DirectionalDragArea::Status status)
56#define ddaDebug(params) ((void)0)
59#define ddaDebug(params) ((void)0)
57#endif // DIRECTIONALDRAGAREA_DEBUG
60#endif // DIRECTIONALDRAGAREA_DEBUG
58
61
59−
60DirectionalDragArea::DirectionalDragArea(QQuickItem *parent)
62DirectionalDragArea::DirectionalDragArea(QQuickItem *parent)
61 : QQuickItem(parent)
63 : QQuickItem(parent)
62− , m_status(WaitingForTouch)
64+ , d(new DirectionalDragAreaPrivate(this))
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
112{
65{
113− return m_wideningAngle;
66+ d->setRecognitionTimer(new Timer(this));
114−}
67+ d->recognitionTimer->setInterval(d->maxTime);
115−
68+ d->recognitionTimer->setSingleShot(true);
116−void DirectionalDragArea::setWideningAngle(qreal angle)
117−{
118− if (angle == m_wideningAngle)
119− return;
120
69
121− m_wideningAngle = angle;
70+ connect(this, &QQuickItem::enabledChanged, d, &DirectionalDragAreaPrivate::giveUpIfDisabledOrInvisible);
122−
71+ connect(this, &QQuickItem::visibleChanged, d, &DirectionalDragAreaPrivate::giveUpIfDisabledOrInvisible);
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);
131}
72}
132
73
133−void DirectionalDragArea::setDistanceThreshold(qreal value)
74+Direction::Type DirectionalDragArea::direction() const
134{
75{
135− if (m_distanceThreshold != value) {
76+ return d->direction;
136− m_distanceThreshold = value;
137− m_distanceThresholdSquared = m_distanceThreshold * m_distanceThreshold;
138− Q_EMIT distanceThresholdChanged(value);
139− }
140}
77}
141
78
142−void DirectionalDragArea::setMinSpeed(qreal value)
79+void DirectionalDragArea::setDirection(Direction::Type direction)
143{
80{
144− if (m_minSpeed != value) {
81+ if (direction != d->direction) {
145− m_minSpeed = value;
82+ d->direction = direction;
146− Q_EMIT minSpeedChanged(value);
83+ Q_EMIT directionChanged(d->direction);
147 }
84 }
148}
85}
149
86
150−void DirectionalDragArea::setMaxSilenceTime(int value)
87+void DirectionalDragAreaPrivate::setDistanceThreshold(qreal value)
151{
88{
152− if (m_maxSilenceTime != value) {
89+ if (distanceThreshold != value) {
153− m_maxSilenceTime = value;
90+ distanceThreshold = value;
154− Q_EMIT maxSilenceTimeChanged(value);
91+ distanceThresholdSquared = distanceThreshold * distanceThreshold;
155 }
92 }
156}
93}
157
94
158−void DirectionalDragArea::setCompositionTime(int value)
95+void DirectionalDragAreaPrivate::setMaxTime(int value)
159{
96{
160− if (m_compositionTime != value) {
97+ if (maxTime != value) {
161− m_compositionTime = value;
98+ maxTime = value;
162− Q_EMIT compositionTimeChanged(value);
99+ recognitionTimer->setInterval(maxTime);
163 }
100 }
164}
101}
165
102
166−void DirectionalDragArea::setRecognitionTimer(UbuntuGestures::AbstractTimer *timer)
103+void DirectionalDragAreaPrivate::setRecognitionTimer(UbuntuGestures::AbstractTimer *timer)
167{
104{
168 int interval = 0;
105 int interval = 0;
169 bool timerWasRunning = false;
106 bool timerWasRunning = false;
170 bool wasSingleShot = false;
107 bool wasSingleShot = false;
171
108
172 // can be null when called from the constructor
109 // can be null when called from the constructor
173− if (m_recognitionTimer) {
110+ if (recognitionTimer) {
174− interval = m_recognitionTimer->interval();
111+ interval = recognitionTimer->interval();
175− timerWasRunning = m_recognitionTimer->isRunning();
112+ timerWasRunning = recognitionTimer->isRunning();
176− if (m_recognitionTimer->parent() == this) {
113+ if (recognitionTimer->parent() == this) {
177− delete m_recognitionTimer;
114+ delete recognitionTimer;
178 }
115 }
179 }
116 }
180
117
181− m_recognitionTimer = timer;
118+ recognitionTimer = timer;
182 timer->setInterval(interval);
119 timer->setInterval(interval);
183 timer->setSingleShot(wasSingleShot);
120 timer->setSingleShot(wasSingleShot);
184 connect(timer, &UbuntuGestures::AbstractTimer::timeout,
121 connect(timer, &UbuntuGestures::AbstractTimer::timeout,
185− this, &DirectionalDragArea::checkSpeed);
122+ this, &DirectionalDragAreaPrivate::rejectGesture);
186 if (timerWasRunning) {
123 if (timerWasRunning) {
187− m_recognitionTimer->start();
124+ recognitionTimer->start();
188 }
125 }
189}
126}
190
127
191−void DirectionalDragArea::setTimeSource(const SharedTimeSource &timeSource)
128+void DirectionalDragAreaPrivate::setTimeSource(const SharedTimeSource &timeSource)
192{
129{
193− m_timeSource = timeSource;
130+ this->timeSource = timeSource;
194− m_velocityCalculator->setTimeSource(timeSource);
195− m_activeTouches.m_timeSource = timeSource;
131+ activeTouches.m_timeSource = timeSource;
196}
132}
197
133
198qreal DirectionalDragArea::distance() const
134qreal DirectionalDragArea::distance() const
199{
135{
200− if (Direction::isHorizontal(m_direction)) {
136+ if (Direction::isHorizontal(d->direction)) {
201− return m_previousPos.x() - m_startPos.x();
137+ return d->publicPos.x() - d->startPos.x();
202 } else {
138 } else {
203− return m_previousPos.y() - m_startPos.y();
139+ return d->publicPos.y() - d->startPos.y();
204 }
140 }
205}
141}
206
142
207−void DirectionalDragArea::updateSceneDistance()
143+void DirectionalDragAreaPrivate::updateSceneDistance()
208{
144{
209− QPointF totalMovement = m_previousScenePos - m_startScenePos;
145+ QPointF totalMovement = publicScenePos - startScenePos;
210− m_sceneDistance = projectOntoDirectionVector(totalMovement);
146+ sceneDistance = projectOntoDirectionVector(totalMovement);
211}
147}
212
148
213qreal DirectionalDragArea::sceneDistance() const
149qreal DirectionalDragArea::sceneDistance() const
214{
150{
215− return m_sceneDistance;
151+ return d->sceneDistance;
216}
152}
217
153
218qreal DirectionalDragArea::touchX() const
154qreal DirectionalDragArea::touchX() const
219{
155{
220− return m_previousPos.x();
156+ return d->publicPos.x();
221}
157}
222
158
223qreal DirectionalDragArea::touchY() const
159qreal DirectionalDragArea::touchY() const
224{
160{
225− return m_previousPos.y();
161+ return d->publicPos.y();
226}
162}
227
163
228qreal DirectionalDragArea::touchSceneX() const
164qreal DirectionalDragArea::touchSceneX() const
229{
165{
230− return m_previousScenePos.x();
166+ return d->publicScenePos.x();
231}
167}
232
168
233qreal DirectionalDragArea::touchSceneY() const
169qreal DirectionalDragArea::touchSceneY() const
234{
170{
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");
236}
202}
237
203
238bool DirectionalDragArea::event(QEvent *event)
204bool DirectionalDragArea::event(QEvent *event)
239{
205{
240 if (event->type() == TouchOwnershipEvent::touchOwnershipEventType()) {
206 if (event->type() == TouchOwnershipEvent::touchOwnershipEventType()) {
241− touchOwnershipEvent(static_cast<TouchOwnershipEvent *>(event));
207+ d->touchOwnershipEvent(static_cast<TouchOwnershipEvent *>(event));
242 return true;
208 return true;
243 } else if (event->type() == UnownedTouchEvent::unownedTouchEventType()) {
209 } else if (event->type() == UnownedTouchEvent::unownedTouchEventType()) {
244− unownedTouchEvent(static_cast<UnownedTouchEvent *>(event));
210+ d->unownedTouchEvent(static_cast<UnownedTouchEvent *>(event));
245 return true;
211 return true;
246 } else {
212 } else {
247 return QQuickItem::event(event);
213 return QQuickItem::event(event);
248 }
214 }
249}
215}
250
216
251−void DirectionalDragArea::touchOwnershipEvent(TouchOwnershipEvent *event)
217+void DirectionalDragAreaPrivate::touchOwnershipEvent(TouchOwnershipEvent *event)
252{
218{
253 if (event->gained()) {
219 if (event->gained()) {
254 QVector<int> ids;
220 QVector<int> ids;
255 ids.append(event->touchId());
221 ids.append(event->touchId());
256 ddaDebug("grabbing touch");
222 ddaDebug("grabbing touch");
257− grabTouchPoints(ids);
223+ q->grabTouchPoints(ids);
258
224
259 // Work around for Qt bug. If we grab a touch that is being used for mouse pointer
225 // Work around for Qt bug. If we grab a touch that is being used for mouse pointer
260 // emulation it will cause the emulation logic to go nuts.
226 // emulation it will cause the emulation logic to go nuts.
@@ -262,35 +228,35 @@ void DirectionalDragArea::touchOwnershipEvent(TouchOwnershipEvent *event)
262 //
228 //
263 // The fix for this bug has landed in Qt 5.4 (https://codereview.qt-project.org/96887)
229 // The fix for this bug has landed in Qt 5.4 (https://codereview.qt-project.org/96887)
264 // TODO: Remove this workaround once we start using Qt 5.4
230 // TODO: Remove this workaround once we start using Qt 5.4
265− if (window()) {
231+ if (q->window()) {
266− QQuickWindowPrivate *windowPrivate = QQuickWindowPrivate::get(window());
232+ QQuickWindowPrivate *windowPrivate = QQuickWindowPrivate::get(q->window());
267− if (windowPrivate->touchMouseId == event->touchId() && window()->mouseGrabberItem()) {
233+ if (windowPrivate->touchMouseId == event->touchId() && q->window()->mouseGrabberItem()) {
268 ddaDebug("removing mouse grabber");
234 ddaDebug("removing mouse grabber");
269− window()->mouseGrabberItem()->ungrabMouse();
235+ q->window()->mouseGrabberItem()->ungrabMouse();
270 }
236 }
271 }
237 }
272 } else {
238 } else {
273 // We still wanna know when it ends for keeping the composition time window up-to-date
239 // 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);
275
241
276 setStatus(WaitingForTouch);
242 setStatus(WaitingForTouch);
277 }
243 }
278}
244}
279
245
280−void DirectionalDragArea::unownedTouchEvent(UnownedTouchEvent *unownedTouchEvent)
246+void DirectionalDragAreaPrivate::unownedTouchEvent(UnownedTouchEvent *unownedTouchEvent)
281{
247{
282 QTouchEvent *event = unownedTouchEvent->touchEvent();
248 QTouchEvent *event = unownedTouchEvent->touchEvent();
283
249
284 Q_ASSERT(!event->touchPointStates().testFlag(Qt::TouchPointPressed));
250 Q_ASSERT(!event->touchPointStates().testFlag(Qt::TouchPointPressed));
285
251
286− ddaDebug("Unowned " << m_timeSource->msecsSinceReference() << " " << qPrintable(touchEventToString(event)));
252+ ddaDebug("Unowned " << timeSource->msecsSinceReference() << " " << qPrintable(touchEventToString(event)));
287
253
288− switch (m_status) {
254+ switch (status) {
289 case WaitingForTouch:
255 case WaitingForTouch:
290 // do nothing
256 // do nothing
291 break;
257 break;
292 case Undecided:
258 case Undecided:
293− Q_ASSERT(isEnabled() && isVisible());
259+ Q_ASSERT(q->isEnabled() && q->isVisible());
294 unownedTouchEvent_undecided(unownedTouchEvent);
260 unownedTouchEvent_undecided(unownedTouchEvent);
295 break;
261 break;
296 default: // Recognized:
262 default: // Recognized:
@@ -298,18 +264,18 @@ void DirectionalDragArea::unownedTouchEvent(UnownedTouchEvent *unownedTouchEvent
298 break;
264 break;
299 }
265 }
300
266
301− m_activeTouches.update(event);
267+ activeTouches.update(event);
302}
268}
303
269
304−void DirectionalDragArea::unownedTouchEvent_undecided(UnownedTouchEvent *unownedTouchEvent)
270+void DirectionalDragAreaPrivate::unownedTouchEvent_undecided(UnownedTouchEvent *unownedTouchEvent)
305{
271{
306 const QTouchEvent::TouchPoint *touchPoint = fetchTargetTouchPoint(unownedTouchEvent->touchEvent());
272 const QTouchEvent::TouchPoint *touchPoint = fetchTargetTouchPoint(unownedTouchEvent->touchEvent());
307 if (!touchPoint) {
273 if (!touchPoint) {
308− qCritical() << "DirectionalDragArea[status=Undecided]: touch " << m_touchId
274+ qCritical() << "DirectionalDragArea[status=Undecided]: touch " << touchId
309 << "missing from UnownedTouchEvent without first reaching state Qt::TouchPointReleased. "
275 << "missing from UnownedTouchEvent without first reaching state Qt::TouchPointReleased. "
310 "Considering it as released.";
276 "Considering it as released.";
311
277
312− TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
278+ TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
313 setStatus(WaitingForTouch);
279 setStatus(WaitingForTouch);
314 return;
280 return;
315 }
281 }
@@ -319,48 +285,42 @@ void DirectionalDragArea::unownedTouchEvent_undecided(UnownedTouchEvent *unowned
319 if (touchPoint->state() == Qt::TouchPointReleased) {
285 if (touchPoint->state() == Qt::TouchPointReleased) {
320 // touch has ended before recognition concluded
286 // touch has ended before recognition concluded
321 ddaDebug("Touch has ended before recognition concluded");
287 ddaDebug("Touch has ended before recognition concluded");
322− TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
288+ TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
323− emitSignalIfTapped();
324 setStatus(WaitingForTouch);
289 setStatus(WaitingForTouch);
325 return;
290 return;
326 }
291 }
327
292
328− m_previousDampedScenePos.setX(m_dampedScenePos.x());
293+ previousDampedScenePos.setX(dampedScenePos.x());
329− m_previousDampedScenePos.setY(m_dampedScenePos.y());
294+ previousDampedScenePos.setY(dampedScenePos.y());
330− m_dampedScenePos.update(touchScenePos);
295+ 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− }
341
296
342 if (!movingInRightDirection()) {
297 if (!movingInRightDirection()) {
343 ddaDebug("Rejecting gesture because touch point is moving in the wrong direction.");
298 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);
345 // We still wanna know when it ends for keeping the composition time window up-to-date
300 // 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);
347 setStatus(WaitingForTouch);
302 setStatus(WaitingForTouch);
348 return;
303 return;
349 }
304 }
350
305
351− setPreviousPos(touchPoint->pos());
352− setPreviousScenePos(touchScenePos);
353−
354 if (isWithinTouchCompositionWindow()) {
306 if (isWithinTouchCompositionWindow()) {
355 // There's still time for some new touch to appear and ruin our party as it would be combined
307 // 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.
357 ddaDebug("Sill within composition window. Let's wait more.");
309 ddaDebug("Sill within composition window. Let's wait more.");
358 return;
310 return;
359 }
311 }
360
312
361− if (movedFarEnough(touchScenePos)) {
313+ if (movedFarEnoughAlongGestureAxis()) {
362− TouchRegistry::instance()->requestTouchOwnership(m_touchId, this);
314+ TouchRegistry::instance()->requestTouchOwnership(touchId, q);
363 setStatus(Recognized);
315 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);
364 } else {
324 } else {
365 ddaDebug("Didn't move far enough yet. Let's wait more.");
325 ddaDebug("Didn't move far enough yet. Let's wait more.");
366 }
326 }
@@ -371,29 +331,29 @@ void DirectionalDragArea::touchEvent(QTouchEvent *event)
371 // TODO: Consider when more than one touch starts in the same event (although it's not possible
331 // TODO: Consider when more than one touch starts in the same event (although it's not possible
372 // with Mir's android-input). Have to track them all. Consider it a plus/bonus.
332 // with Mir's android-input). Have to track them all. Consider it a plus/bonus.
373
333
374− ddaDebug(m_timeSource->msecsSinceReference() << " " << qPrintable(touchEventToString(event)));
334+ ddaDebug(d->timeSource->msecsSinceReference() << " " << qPrintable(touchEventToString(event)));
375
335
376 if (!isEnabled() || !isVisible()) {
336 if (!isEnabled() || !isVisible()) {
377 QQuickItem::touchEvent(event);
337 QQuickItem::touchEvent(event);
378 return;
338 return;
379 }
339 }
380
340
381− switch (m_status) {
341+ switch (d->status) {
382− case WaitingForTouch:
342+ case DirectionalDragAreaPrivate::WaitingForTouch:
383− touchEvent_absent(event);
343+ d->touchEvent_absent(event);
384 break;
344 break;
385− case Undecided:
345+ case DirectionalDragAreaPrivate::Undecided:
386− touchEvent_undecided(event);
346+ d->touchEvent_undecided(event);
387 break;
347 break;
388 default: // Recognized:
348 default: // Recognized:
389− touchEvent_recognized(event);
349+ d->touchEvent_recognized(event);
390 break;
350 break;
391 }
351 }
392
352
393− m_activeTouches.update(event);
353+ d->activeTouches.update(event);
394}
354}
395
355
396−void DirectionalDragArea::touchEvent_absent(QTouchEvent *event)
356+void DirectionalDragAreaPrivate::touchEvent_absent(QTouchEvent *event)
397{
357{
398 // TODO: accept/reject is for the whole event, not per touch id. See how that affects us.
358 // TODO: accept/reject is for the whole event, not per touch id. See how that affects us.
399
359
@@ -423,36 +383,40 @@ void DirectionalDragArea::touchEvent_absent(QTouchEvent *event)
423 allGood = false;
383 allGood = false;
424 } else {
384 } else {
425 // that's our candidate
385 // that's our candidate
426− m_touchId = touchPoint.id();
427 newTouchPoint = &touchPoint;
386 newTouchPoint = &touchPoint;
428 }
387 }
429 }
388 }
430 }
389 }
431
390
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+
432 if (allGood) {
399 if (allGood) {
433 Q_ASSERT(newTouchPoint);
400 Q_ASSERT(newTouchPoint);
434
401
435− m_startPos = newTouchPoint->pos();
402+ startPos = newTouchPoint->pos();
436− m_startScenePos = newTouchPoint->scenePos();
403+ startScenePos = newTouchPoint->scenePos();
437− m_touchId = newTouchPoint->id();
404+ touchId = newTouchPoint->id();
438− m_dampedScenePos.reset(m_startScenePos);
405+ dampedScenePos.reset(startScenePos);
439− m_velocityCalculator->setTrackedPosition(0.);
406+ setPublicPos(startPos);
440− m_velocityCalculator->reset();
407+
441− m_numSamplesOnLastSpeedCheck = 0;
408+ setPublicScenePos(startScenePos);
442− m_silenceTime = 0;
443− setPreviousPos(m_startPos);
444− setPreviousScenePos(m_startScenePos);
445 updateSceneDirectionVector();
409 updateSceneDirectionVector();
446
410
447 if (recognitionIsDisabled()) {
411 if (recognitionIsDisabled()) {
448 // Behave like a dumb TouchArea
412 // Behave like a dumb TouchArea
449 ddaDebug("Gesture recognition is disabled. Requesting touch ownership immediately.");
413 ddaDebug("Gesture recognition is disabled. Requesting touch ownership immediately.");
450− TouchRegistry::instance()->requestTouchOwnership(m_touchId, this);
414+ TouchRegistry::instance()->requestTouchOwnership(touchId, q);
451 setStatus(Recognized);
415 setStatus(Recognized);
452 event->accept();
416 event->accept();
453 } else {
417 } else {
454 // just monitor the touch points for now.
418 // just monitor the touch points for now.
455− TouchRegistry::instance()->addCandidateOwnerForTouch(m_touchId, this);
419+ TouchRegistry::instance()->addCandidateOwnerForTouch(touchId, q);
456
420
457 setStatus(Undecided);
421 setStatus(Undecided);
458 // Let the item below have it. We will monitor it and grab it later if a gesture
422 // 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)
465 }
429 }
466}
430}
467
431
468−void DirectionalDragArea::touchEvent_undecided(QTouchEvent *event)
432+void DirectionalDragAreaPrivate::touchEvent_undecided(QTouchEvent *event)
469{
433{
470− Q_ASSERT(event->type() == QEvent::TouchBegin);
471 Q_ASSERT(fetchTargetTouchPoint(event) == nullptr);
434 Q_ASSERT(fetchTargetTouchPoint(event) == nullptr);
472
435
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).
474 // But we do want to know when those new touches end for keeping the composition time
437 // But we do want to know when those new touches end for keeping the composition time
475 // window up-to-date
438 // window up-to-date
476 event->ignore();
439 event->ignore();
@@ -480,63 +443,60 @@ void DirectionalDragArea::touchEvent_undecided(QTouchEvent *event)
480 // multi-finger drags are not accepted
443 // multi-finger drags are not accepted
481 ddaDebug("Multi-finger drags are not accepted");
444 ddaDebug("Multi-finger drags are not accepted");
482
445
483− TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
446+ TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
484 // We still wanna know when it ends for keeping the composition time window up-to-date
447 // 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);
486
449
487 setStatus(WaitingForTouch);
450 setStatus(WaitingForTouch);
488 }
451 }
489}
452}
490
453
491−void DirectionalDragArea::touchEvent_recognized(QTouchEvent *event)
454+void DirectionalDragAreaPrivate::touchEvent_recognized(QTouchEvent *event)
492{
455{
493 const QTouchEvent::TouchPoint *touchPoint = fetchTargetTouchPoint(event);
456 const QTouchEvent::TouchPoint *touchPoint = fetchTargetTouchPoint(event);
494
457
495 if (!touchPoint) {
458 if (!touchPoint) {
496− qCritical() << "DirectionalDragArea[status=Recognized]: touch " << m_touchId
459+ qCritical() << "DirectionalDragArea[status=Recognized]: touch " << touchId
497 << "missing from QTouchEvent without first reaching state Qt::TouchPointReleased. "
460 << "missing from QTouchEvent without first reaching state Qt::TouchPointReleased. "
498 "Considering it as released.";
461 "Considering it as released.";
499 setStatus(WaitingForTouch);
462 setStatus(WaitingForTouch);
500 } else {
463 } else {
501− setPreviousPos(touchPoint->pos());
464+ setPublicPos(touchPoint->pos());
502− setPreviousScenePos(touchPoint->scenePos());
465+ setPublicScenePos(touchPoint->scenePos());
503
466
504 if (touchPoint->state() == Qt::TouchPointReleased) {
467 if (touchPoint->state() == Qt::TouchPointReleased) {
505− emitSignalIfTapped();
506 setStatus(WaitingForTouch);
468 setStatus(WaitingForTouch);
507 }
469 }
508 }
470 }
509}
471}
510
472
511−void DirectionalDragArea::watchPressedTouchPoints(const QList<QTouchEvent::TouchPoint> &touchPoints)
473+void DirectionalDragAreaPrivate::watchPressedTouchPoints(const QList<QTouchEvent::TouchPoint> &touchPoints)
512{
474{
513 for (int i = 0; i < touchPoints.count(); ++i) {
475 for (int i = 0; i < touchPoints.count(); ++i) {
514 const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i);
476 const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i);
515 if (touchPoint.state() == Qt::TouchPointPressed) {
477 if (touchPoint.state() == Qt::TouchPointPressed) {
516− TouchRegistry::instance()->addTouchWatcher(touchPoint.id(), this);
478+ TouchRegistry::instance()->addTouchWatcher(touchPoint.id(), q);
517 }
479 }
518 }
480 }
519}
481}
520
482
521−bool DirectionalDragArea::recognitionIsDisabled() const
483+bool DirectionalDragAreaPrivate::recognitionIsDisabled() const
522{
484{
523− return distanceThreshold() <= 0 && compositionTime() <= 0;
485+ return immediateRecognition || (distanceThreshold <= 0 && compositionTime <= 0);
524}
486}
525
487
526−void DirectionalDragArea::emitSignalIfTapped()
488+bool DirectionalDragAreaPrivate::sanityCheckRecognitionProperties()
527{
489{
528− qint64 touchDuration = m_timeSource->msecsSinceReference() - m_activeTouches.touchStartTime(m_touchId);
490+ return recognitionIsDisabled()
529− if (touchDuration <= maxTapDuration()) {
491+ || (distanceThreshold < maxDistance && compositionTime < maxTime);
530− Q_EMIT tapped();
531− }
532}
492}
533
493
534−const QTouchEvent::TouchPoint *DirectionalDragArea::fetchTargetTouchPoint(QTouchEvent *event)
494+const QTouchEvent::TouchPoint *DirectionalDragAreaPrivate::fetchTargetTouchPoint(QTouchEvent *event)
535{
495{
536 const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
496 const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
537 const QTouchEvent::TouchPoint *touchPoint = 0;
497 const QTouchEvent::TouchPoint *touchPoint = 0;
538 for (int i = 0; i < touchPoints.size(); ++i) {
498 for (int i = 0; i < touchPoints.size(); ++i) {
539− if (touchPoints.at(i).id() == m_touchId) {
499+ if (touchPoints.at(i).id() == touchId) {
540 touchPoint = &touchPoints.at(i);
500 touchPoint = &touchPoints.at(i);
541 break;
501 break;
542 }
502 }
@@ -544,39 +504,13 @@ const QTouchEvent::TouchPoint *DirectionalDragArea::fetchTargetTouchPoint(QTouch
544 return touchPoint;
504 return touchPoint;
545}
505}
546
506
547−bool DirectionalDragArea::pointInsideAllowedArea() const
507+bool DirectionalDragAreaPrivate::movingInRightDirection() const
548{
508{
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) {
576 return true;
510 return true;
577 } else {
511 } else {
578− QPointF movementVector(m_dampedScenePos.x() - m_previousDampedScenePos.x(),
512+ QPointF movementVector(dampedScenePos.x() - previousDampedScenePos.x(),
579− m_dampedScenePos.y() - m_previousDampedScenePos.y());
513+ dampedScenePos.y() - previousDampedScenePos.y());
580
514
581 qreal scalarProjection = projectOntoDirectionVector(movementVector);
515 qreal scalarProjection = projectOntoDirectionVector(movementVector);
582
516
@@ -584,96 +518,95 @@ bool DirectionalDragArea::movingInRightDirection() const
584 }
518 }
585}
519}
586
520
587−bool DirectionalDragArea::movedFarEnough(const QPointF &point) const
521+bool DirectionalDragAreaPrivate::movedFarEnoughAlongGestureAxis() const
588{
522{
589− if (m_distanceThreshold <= 0.) {
523+ if (distanceThreshold <= 0.) {
590 // distance threshold check is disabled
524 // distance threshold check is disabled
591 return true;
525 return true;
592 } else {
526 } else {
593− QPointF totalMovement(point.x() - m_startScenePos.x(),
527+ QPointF totalMovement(dampedScenePos.x() - startScenePos.x(),
594− point.y() - m_startScenePos.y());
528+ dampedScenePos.y() - startScenePos.y());
529+
530+ qreal scalarProjection = projectOntoDirectionVector(totalMovement);
595
531
596− qreal squaredTotalMovSize = totalMovement.x() * totalMovement.x() +
532+ ddaDebug(" movedFarEnoughAlongGestureAxis: scalarProjection=" << scalarProjection
597− totalMovement.y() * totalMovement.y();
533+ << ", distanceThreshold=" << distanceThreshold);
598
534
599− return squaredTotalMovSize > m_distanceThresholdSquared;
535+ if (direction == Direction::Horizontal) {
536+ return qAbs(scalarProjection) > distanceThreshold;
537+ } else {
538+ return scalarProjection > distanceThreshold;
539+ }
600 }
540 }
601}
541}
602
542
603−void DirectionalDragArea::checkSpeed()
543+bool DirectionalDragAreaPrivate::isPastMaxDistance() const
604{
544{
605− Q_ASSERT(m_status == Undecided);
545+ QPointF totalMovement(dampedScenePos.x() - startScenePos.x(),
546+ dampedScenePos.y() - startScenePos.y());
606
547
607− if (m_velocityCalculator->numSamples() >= AxisVelocityCalculator::MIN_SAMPLES_NEEDED) {
548+ qreal squaredDistance = totalMovement.x()*totalMovement.x() + totalMovement.y()*totalMovement.y();
608− qreal speed = qFabs(m_velocityCalculator->calculate());
549+ return squaredDistance > maxDistance*maxDistance;
609− qreal minSpeedMsecs = m_minSpeed / 1000.0;
550+}
610
551
611− if (speed < minSpeedMsecs) {
552+void DirectionalDragAreaPrivate::giveUpIfDisabledOrInvisible()
612− ddaDebug("Rejecting gesture because it's below minimum speed.");
553+{
554+ if (!q->isEnabled() || !q->isVisible()) {
555+ if (status == Undecided) {
613− TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
556+ TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
557+ // We still wanna know when it ends for keeping the composition time window up-to-date
614− TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
558+ TouchRegistry::instance()->addTouchWatcher(touchId, q);
615− setStatus(WaitingForTouch);
616 }
559 }
617− }
618−
619− if (m_velocityCalculator->numSamples() == m_numSamplesOnLastSpeedCheck) {
620− m_silenceTime += m_recognitionTimer->interval();
621
560
622− if (m_silenceTime > m_maxSilenceTime) {
561+ if (status != WaitingForTouch) {
623− ddaDebug("Rejecting gesture because its silence time has been exceeded.");
562+ ddaDebug("Resetting status because got disabled or made invisible");
624− TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
625− TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
626 setStatus(WaitingForTouch);
563 setStatus(WaitingForTouch);
627 }
564 }
628− } else {
629− m_silenceTime = 0;
630 }
565 }
631− m_numSamplesOnLastSpeedCheck = m_velocityCalculator->numSamples();
632}
566}
633
567
634−void DirectionalDragArea::giveUpIfDisabledOrInvisible()
568+void DirectionalDragAreaPrivate::rejectGesture()
635{
569{
636− if (!isEnabled() || !isVisible()) {
637− if (m_status == Undecided) {
570+ if (status == Undecided) {
638− TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
571+ ddaDebug("Rejecting gesture because it's taking too long to drag beyond the threshold.");
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− }
642
572
643− if (m_status != WaitingForTouch) {
573+ TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
644− ddaDebug("Resetting status because got disabled or made invisible");
574+ // We still wanna know when it ends for keeping the composition time window up-to-date
575+ TouchRegistry::instance()->addTouchWatcher(touchId, q);
576+
645− setStatus(WaitingForTouch);
577+ setStatus(WaitingForTouch);
646− }
647 }
578 }
648}
579}
649
580
650−void DirectionalDragArea::setStatus(DirectionalDragArea::Status newStatus)
581+void DirectionalDragAreaPrivate::setStatus(Status newStatus)
651{
582{
652− if (newStatus == m_status)
583+ if (newStatus == status)
653 return;
584 return;
654
585
655− DirectionalDragArea::Status oldStatus = m_status;
586+ Status oldStatus = status;
656
587
657 if (oldStatus == Undecided) {
588 if (oldStatus == Undecided) {
658− m_recognitionTimer->stop();
589+ recognitionTimer->stop();
659 }
590 }
660
591
661− m_status = newStatus;
592+ status = newStatus;
662− Q_EMIT statusChanged(m_status);
593+ Q_EMIT statusChanged(status);
663
594
664 ddaDebug(statusToString(oldStatus) << " -> " << statusToString(newStatus));
595 ddaDebug(statusToString(oldStatus) << " -> " << statusToString(newStatus));
665
596
666 switch (newStatus) {
597 switch (newStatus) {
667 case WaitingForTouch:
598 case WaitingForTouch:
599+ if (oldStatus == Recognized) {
668− Q_EMIT draggingChanged(false);
600+ Q_EMIT q->draggingChanged(false);
601+ }
602+ Q_EMIT q->pressedChanged(false);
669 break;
603 break;
670 case Undecided:
604 case Undecided:
671− m_recognitionTimer->start();
605+ recognitionTimer->start();
672− Q_EMIT draggingChanged(true);
606+ Q_EMIT q->pressedChanged(true);
673 break;
607 break;
674 case Recognized:
608 case Recognized:
675− if (oldStatus == WaitingForTouch)
676− Q_EMIT draggingChanged(true);
609+ Q_EMIT q->draggingChanged(true);
677 break;
610 break;
678 default:
611 default:
679 // no-op
612 // no-op
@@ -681,77 +614,126 @@ void DirectionalDragArea::setStatus(DirectionalDragArea::Status newStatus)
681 }
614 }
682}
615}
683
616
684−void DirectionalDragArea::setPreviousPos(const QPointF &point)
617+void DirectionalDragAreaPrivate::setPublicPos(const QPointF &point)
685{
618{
686− bool xChanged = m_previousPos.x() != point.x();
619+ bool xChanged = publicPos.x() != point.x();
687− bool yChanged = m_previousPos.y() != point.y();
620+ bool yChanged = publicPos.y() != point.y();
688
621
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+ }
690
642
691 if (xChanged) {
643 if (xChanged) {
692− Q_EMIT touchXChanged(point.x());
644+ Q_EMIT q->touchXChanged(publicPos.x());
693− if (Direction::isHorizontal(m_direction))
645+ if (Direction::isHorizontal(direction))
694− Q_EMIT distanceChanged(distance());
646+ Q_EMIT q->distanceChanged(q->distance());
695 }
647 }
696
648
697 if (yChanged) {
649 if (yChanged) {
698− Q_EMIT touchYChanged(point.y());
650+ Q_EMIT q->touchYChanged(publicPos.y());
699− if (Direction::isVertical(m_direction))
651+ if (Direction::isVertical(direction))
700− Q_EMIT distanceChanged(distance());
652+ Q_EMIT q->distanceChanged(q->distance());
701 }
653 }
702}
654}
703
655
704−void DirectionalDragArea::setPreviousScenePos(const QPointF &point)
656+void DirectionalDragAreaPrivate::setPublicScenePos(const QPointF &point)
705{
657{
706− bool xChanged = m_previousScenePos.x() != point.x();
658+ bool xChanged = publicScenePos.x() != point.x();
707− bool yChanged = m_previousScenePos.y() != point.y();
659+ bool yChanged = publicScenePos.y() != point.y();
708
660
709 if (!xChanged && !yChanged)
661 if (!xChanged && !yChanged)
710 return;
662 return;
711
663
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+
712− qreal oldSceneDistance = sceneDistance();
668+ qreal oldSceneDistance = sceneDistance;
713− m_previousScenePos = point;
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+
714 updateSceneDistance();
687 updateSceneDistance();
715
688
716− if (oldSceneDistance != sceneDistance()) {
689+ if (oldSceneDistance != sceneDistance) {
717− Q_EMIT sceneDistanceChanged(sceneDistance());
690+ Q_EMIT q->sceneDistanceChanged(sceneDistance);
718 }
691 }
719
692
720 if (xChanged) {
693 if (xChanged) {
721− Q_EMIT touchSceneXChanged(point.x());
694+ Q_EMIT q->touchSceneXChanged(publicScenePos.x());
722 }
695 }
723
696
724 if (yChanged) {
697 if (yChanged) {
725− Q_EMIT touchSceneYChanged(point.y());
698+ Q_EMIT q->touchSceneYChanged(publicScenePos.y());
726 }
699 }
727}
700}
728
701
729−void DirectionalDragArea::updateVelocityCalculator(const QPointF &scenePos)
702+bool DirectionalDragAreaPrivate::isWithinTouchCompositionWindow()
730{
703{
731− QPointF totalSceneMovement = scenePos - m_startScenePos;
704+ return
732−
705+ compositionTime > 0 &&
733− qreal scalarProjection = projectOntoDirectionVector(totalSceneMovement);
706+ !activeTouches.isEmpty() &&
707+ timeSource->msecsSinceReference() <=
708+ activeTouches.mostRecentStartTime() + (qint64)compositionTime;
709+}
734
710
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+ }
736}
720}
737
721
738−bool DirectionalDragArea::isWithinTouchCompositionWindow()
722+void DirectionalDragAreaPrivate::setPixelsPerMm(qreal pixelsPerMm)
739{
723{
740− return
724+ dampedScenePos.setMaxDelta(1. * pixelsPerMm);
741− compositionTime() > 0 &&
725+ setDistanceThreshold(4. * pixelsPerMm);
742− !m_activeTouches.isEmpty() &&
726+ maxDistance = 10. * pixelsPerMm;
743− m_timeSource->msecsSinceReference() <=
744− m_activeTouches.mostRecentStartTime() + (qint64)compositionTime();
745}
727}
746
728
747//************************** ActiveTouchesInfo **************************
729//************************** ActiveTouchesInfo **************************
748
730
749−DirectionalDragArea::ActiveTouchesInfo::ActiveTouchesInfo(const SharedTimeSource &timeSource)
731+ActiveTouchesInfo::ActiveTouchesInfo(const SharedTimeSource &timeSource)
750 : m_timeSource(timeSource)
732 : m_timeSource(timeSource)
751{
733{
752}
734}
753
735
754−void DirectionalDragArea::ActiveTouchesInfo::update(QTouchEvent *event)
736+void ActiveTouchesInfo::update(QTouchEvent *event)
755{
737{
756 if (!(event->touchPointStates() & (Qt::TouchPointPressed | Qt::TouchPointReleased))) {
738 if (!(event->touchPointStates() & (Qt::TouchPointPressed | Qt::TouchPointReleased))) {
757 // nothing to update
739 // nothing to update
@@ -773,7 +755,7 @@ void DirectionalDragArea::ActiveTouchesInfo::update(QTouchEvent *event)
773}
755}
774
756
775#if ACTIVETOUCHESINFO_DEBUG
757#if ACTIVETOUCHESINFO_DEBUG
776−QString DirectionalDragArea::ActiveTouchesInfo::toString()
758+QString ActiveTouchesInfo::toString()
777{
759{
778 QString string = "(";
760 QString string = "(";
779
761
@@ -791,7 +773,7 @@ QString DirectionalDragArea::ActiveTouchesInfo::toString()
791}
773}
792#endif // ACTIVETOUCHESINFO_DEBUG
774#endif // ACTIVETOUCHESINFO_DEBUG
793
775
794−void DirectionalDragArea::ActiveTouchesInfo::addTouchPoint(int touchId)
776+void ActiveTouchesInfo::addTouchPoint(int touchId)
795{
777{
796 ActiveTouchInfo &activeTouchInfo = m_touchInfoPool.getEmptySlot();
778 ActiveTouchInfo &activeTouchInfo = m_touchInfoPool.getEmptySlot();
797 activeTouchInfo.id = touchId;
779 activeTouchInfo.id = touchId;
@@ -802,7 +784,7 @@ void DirectionalDragArea::ActiveTouchesInfo::addTouchPoint(int touchId)
802 #endif
784 #endif
803}
785}
804
786
805−qint64 DirectionalDragArea::ActiveTouchesInfo::touchStartTime(int touchId)
787+qint64 ActiveTouchesInfo::touchStartTime(int touchId)
806{
788{
807 qint64 result = -1;
789 qint64 result = -1;
808
790
@@ -819,7 +801,7 @@ qint64 DirectionalDragArea::ActiveTouchesInfo::touchStartTime(int touchId)
819 return result;
801 return result;
820}
802}
821
803
822−void DirectionalDragArea::ActiveTouchesInfo::removeTouchPoint(int touchId)
804+void ActiveTouchesInfo::removeTouchPoint(int touchId)
823{
805{
824 m_touchInfoPool.forEach([&](Pool<ActiveTouchInfo>::Iterator &touchInfo) {
806 m_touchInfoPool.forEach([&](Pool<ActiveTouchInfo>::Iterator &touchInfo) {
825 if (touchId == touchInfo->id) {
807 if (touchId == touchInfo->id) {
@@ -835,7 +817,7 @@ void DirectionalDragArea::ActiveTouchesInfo::removeTouchPoint(int touchId)
835 #endif
817 #endif
836}
818}
837
819
838−qint64 DirectionalDragArea::ActiveTouchesInfo::mostRecentStartTime()
820+qint64 ActiveTouchesInfo::mostRecentStartTime()
839{
821{
840 Q_ASSERT(!m_touchInfoPool.isEmpty());
822 Q_ASSERT(!m_touchInfoPool.isEmpty());
841
823
@@ -851,11 +833,11 @@ qint64 DirectionalDragArea::ActiveTouchesInfo::mostRecentStartTime()
851 return highestStartTime;
833 return highestStartTime;
852}
834}
853
835
854−void DirectionalDragArea::updateSceneDirectionVector()
836+void DirectionalDragAreaPrivate::updateSceneDirectionVector()
855{
837{
856 QPointF localOrigin(0., 0.);
838 QPointF localOrigin(0., 0.);
857 QPointF localDirection;
839 QPointF localDirection;
858− switch (m_direction) {
840+ switch (direction) {
859 case Direction::Upwards:
841 case Direction::Upwards:
860 localDirection.rx() = 0.;
842 localDirection.rx() = 0.;
861 localDirection.ry() = -1.;
843 localDirection.ry() = -1.;
@@ -873,14 +855,31 @@ void DirectionalDragArea::updateSceneDirectionVector()
873 localDirection.ry() = 0.;
855 localDirection.ry() = 0.;
874 break;
856 break;
875 }
857 }
876− QPointF sceneOrigin = mapToScene(localOrigin);
858+ QPointF sceneOrigin = q->mapToScene(localOrigin);
877− QPointF sceneDirection = mapToScene(localDirection);
859+ QPointF sceneDirection = q->mapToScene(localDirection);
878− m_sceneDirectionVector = sceneDirection - sceneOrigin;
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();
879}
868}
880
869
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)
882{
884{
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();
886}
885}
plugins/Ubuntu/Gestures/DirectionalDragArea.h+29−202
@@ -18,7 +18,6 @@
18#define DIRECTIONAL_DRAG_AREA_H
18#define DIRECTIONAL_DRAG_AREA_H
19
19
20#include <QtQuick/QQuickItem>
20#include <QtQuick/QQuickItem>
21−#include "AxisVelocityCalculator.h"
22#include "UbuntuGesturesQmlGlobal.h"
21#include "UbuntuGesturesQmlGlobal.h"
23#include "Damper.h"
22#include "Damper.h"
24#include "Direction.h"
23#include "Direction.h"
@@ -29,6 +28,7 @@
29
28
30class TouchOwnershipEvent;
29class TouchOwnershipEvent;
31class UnownedTouchEvent;
30class UnownedTouchEvent;
31+class DirectionalDragAreaPrivate;
32
32
33/*
33/*
34 An area that detects axis-aligned single-finger drag gestures
34 An area that detects axis-aligned single-finger drag gestures
@@ -61,95 +61,31 @@ class UBUNTUGESTURESQML_EXPORT DirectionalDragArea : public QQuickItem {
61 Q_PROPERTY(qreal touchSceneX READ touchSceneX NOTIFY touchSceneXChanged)
61 Q_PROPERTY(qreal touchSceneX READ touchSceneX NOTIFY touchSceneXChanged)
62 Q_PROPERTY(qreal touchSceneY READ touchSceneY NOTIFY touchSceneYChanged)
62 Q_PROPERTY(qreal touchSceneY READ touchSceneY NOTIFY touchSceneYChanged)
63
63
64− // The current status of the directional drag gesture area.
65− Q_PROPERTY(Status status READ status NOTIFY statusChanged)
66−
67 // Whether a drag gesture is taking place
64 // 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.
70 Q_PROPERTY(bool dragging READ dragging NOTIFY draggingChanged)
65 Q_PROPERTY(bool dragging READ dragging NOTIFY draggingChanged)
71
66
72− /////
67+ // Whether the drag area is pressed.
73− // stuff that will be set in stone at some point
68+ Q_PROPERTY(bool pressed READ pressed NOTIFY pressedChanged)
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)
109
69
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.
110 //
73 //
111− /////
74+ // It's false by default. In most cases you will not want that enabled.
112−
75+ Q_PROPERTY(bool immediateRecognition
113− // Maximum time (in milliseconds) after the start of a given touch point where
76+ READ immediateRecognition
114− // subsequent touch starts are grouped with the first one into an N-touches gesture
77+ WRITE setImmediateRecognition
115− // (e.g. a two-fingers tap or drag).
78+ NOTIFY immediateRecognitionChanged)
116− Q_PROPERTY(int compositionTime READ compositionTime
117− WRITE setCompositionTime
118− NOTIFY compositionTimeChanged)
119
79
120 Q_ENUMS(Direction)
80 Q_ENUMS(Direction)
121− Q_ENUMS(Status)
122public:
81public:
123 DirectionalDragArea(QQuickItem *parent = 0);
82 DirectionalDragArea(QQuickItem *parent = 0);
124
83
125 Direction::Type direction() const;
84 Direction::Type direction() const;
126 void setDirection(Direction::Type);
85 void setDirection(Direction::Type);
127
86
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−
150 qreal distance() const;
87 qreal distance() const;
151 qreal sceneDistance() const;
88 qreal sceneDistance() const;
152− void updateSceneDistance();
153
89
154 qreal touchX() const;
90 qreal touchX() const;
155 qreal touchY() const;
91 qreal touchY() const;
@@ -157,152 +93,43 @@ public:
157 qreal touchSceneX() const;
93 qreal touchSceneX() const;
158 qreal touchSceneY() const;
94 qreal touchSceneY() const;
159
95
160− bool dragging() const { return (m_status == Undecided) || (m_status == Recognized); }
96+ bool dragging() const;
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);
173
97
174− int maxSilenceTime() const { return m_maxSilenceTime; }
98+ bool pressed() const;
175− void setMaxSilenceTime(int value);
176
99
177− int compositionTime() const { return m_compositionTime; }
100+ bool immediateRecognition() const;
178− void setCompositionTime(int value);
101+ void setImmediateRecognition(bool enabled);
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);
187
102
188 bool event(QEvent *e) override;
103 bool event(QEvent *e) override;
189
104
190− // Maximum time, in milliseconds, between a press and a release, for a touch
105+ /*
191− // sequence to be considered a tap.
106+ In qmltests, sequences of touch events are sent all at once, unlike in "real life".
192− int maxTapDuration() const { return 300; }
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();
193
114
194Q_SIGNALS:
115Q_SIGNALS:
195 void directionChanged(Direction::Type direction);
116 void directionChanged(Direction::Type direction);
196− void statusChanged(Status value);
197 void draggingChanged(bool value);
117 void draggingChanged(bool value);
118+ void pressedChanged(bool value);
198 void distanceChanged(qreal value);
119 void distanceChanged(qreal value);
199 void sceneDistanceChanged(qreal value);
120 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);
206 void touchXChanged(qreal value);
121 void touchXChanged(qreal value);
207 void touchYChanged(qreal value);
122 void touchYChanged(qreal value);
208 void touchSceneXChanged(qreal value);
123 void touchSceneXChanged(qreal value);
209 void touchSceneYChanged(qreal value);
124 void touchSceneYChanged(qreal value);
210−
125+ void immediateRecognitionChanged(bool value);
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();
215
126
216protected:
127protected:
217 virtual void touchEvent(QTouchEvent *event);
128 virtual void touchEvent(QTouchEvent *event);
129+ virtual void itemChange(ItemChange change, const ItemChangeData &value);
218
130
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:
131+public: // so tests can access it
289− ActiveTouchesInfo(const UbuntuGestures::SharedTimeSource &timeSource);
132+ DirectionalDragAreaPrivate *d;
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;
306};
133};
307
134
308#endif // DIRECTIONAL_DRAG_AREA_H
135#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 @@
1/*
1/*
2− * Copyright (C) 2014 Canonical, Ltd.
2+ * Copyright (C) 2014-2015 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -28,8 +28,11 @@
28#define TOUCHDISPATCHER_DEBUG 0
28#define TOUCHDISPATCHER_DEBUG 0
29
29
30#if TOUCHDISPATCHER_DEBUG
30#if TOUCHDISPATCHER_DEBUG
31+#define ugDebug(params) qDebug().nospace() << "[TouchDispatcher(" << this << ")] " << params
31#include <DebugHelpers.h>
32#include <DebugHelpers.h>
32−#endif
33+#else // TOUCHDISPATCHER_DEBUG
34+#define ugDebug(params) ((void)0)
35+#endif // TOUCHDISPATCHER_DEBUG
33
36
34TouchDispatcher::TouchDispatcher()
37TouchDispatcher::TouchDispatcher()
35 : m_status(NoActiveTouch)
38 : m_status(NoActiveTouch)
@@ -44,13 +47,12 @@ void TouchDispatcher::setTargetItem(QQuickItem *target)
44 m_targetItem = target;
47 m_targetItem = target;
45 if (m_status != NoActiveTouch) {
48 if (m_status != NoActiveTouch) {
46 qWarning("[TouchDispatcher] Changing target item in the middle of a touch stream");
49 qWarning("[TouchDispatcher] Changing target item in the middle of a touch stream");
47− m_status = TargetRejectedTouches;
50+ setStatus(TargetRejectedTouches);
48 }
51 }
49 }
52 }
50}
53}
51
54
52−void TouchDispatcher::dispatch(QEvent::Type eventType,
55+void TouchDispatcher::dispatch(QTouchDevice *device,
53− QTouchDevice *device,
54 Qt::KeyboardModifiers modifiers,
56 Qt::KeyboardModifiers modifiers,
55 const QList<QTouchEvent::TouchPoint> &touchPoints,
57 const QList<QTouchEvent::TouchPoint> &touchPoints,
56 QWindow *window,
58 QWindow *window,
@@ -61,6 +63,8 @@ void TouchDispatcher::dispatch(QEvent::Type eventType,
61 return;
63 return;
62 }
64 }
63
65
66+ QEvent::Type eventType = resolveEventType(touchPoints);
67+
64 if (eventType == QEvent::TouchBegin) {
68 if (eventType == QEvent::TouchBegin) {
65 dispatchTouchBegin(device, modifiers, touchPoints, window, timestamp);
69 dispatchTouchBegin(device, modifiers, touchPoints, window, timestamp);
66
70
@@ -72,15 +76,13 @@ void TouchDispatcher::dispatch(QEvent::Type eventType,
72 dispatchAsMouse(device, modifiers, touchPoints, timestamp);
76 dispatchAsMouse(device, modifiers, touchPoints, timestamp);
73 } else {
77 } else {
74 Q_ASSERT(m_status == TargetRejectedTouches);
78 Q_ASSERT(m_status == TargetRejectedTouches);
75− #if TOUCHDISPATCHER_DEBUG
76− qDebug() << "[TouchDispatcher] Not dispatching touch event to" << m_targetItem.data()
79+ ugDebug("Not dispatching touch event to " << m_targetItem.data()
77− << "because it already rejected the touch stream.";
80+ << "because it already rejected the touch stream.");
78− #endif
79 // Do nothing
81 // Do nothing
80 }
82 }
81
83
82 if (eventType == QEvent::TouchEnd) {
84 if (eventType == QEvent::TouchEnd) {
83− m_status = NoActiveTouch;
85+ setStatus(NoActiveTouch);
84 m_touchMouseId = -1;
86 m_touchMouseId = -1;
85 }
87 }
86
88
@@ -103,10 +105,7 @@ void TouchDispatcher::dispatchTouchBegin(
103 QQuickItem *targetItem = m_targetItem.data();
105 QQuickItem *targetItem = m_targetItem.data();
104
106
105 if (!targetItem->isEnabled() || !targetItem->isVisible()) {
107 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.";
108+ ugDebug("Cannot dispatch touch event to " << targetItem << " because it's disabled or invisible.");
109− #endif
110 return;
109 return;
111 }
110 }
112
111
@@ -118,62 +117,46 @@ void TouchDispatcher::dispatchTouchBegin(
118 createQTouchEvent(QEvent::TouchBegin, device, modifiers, targetTouchPoints, window, timestamp));
117 createQTouchEvent(QEvent::TouchBegin, device, modifiers, targetTouchPoints, window, timestamp));
119
118
120
119
121− #if TOUCHDISPATCHER_DEBUG
122− qDebug() << "[TouchDispatcher] dispatching" << qPrintable(touchEventToString(touchEvent.data()))
120+ ugDebug("dispatching " << qPrintable(touchEventToString(touchEvent.data()))
123− << "to" << targetItem;
121+ << " to " << targetItem);
124− #endif
125 QCoreApplication::sendEvent(targetItem, touchEvent.data());
122 QCoreApplication::sendEvent(targetItem, touchEvent.data());
126
123
127
124
128 if (touchEvent->isAccepted()) {
125 if (touchEvent->isAccepted()) {
129− #if TOUCHDISPATCHER_DEBUG
130− qDebug() << "[TouchDispatcher] Item accepted the touch event.";
126+ ugDebug("Item accepted the touch event.");
131− #endif
132− m_status = DeliveringTouchEvents;
127+ setStatus(DeliveringTouchEvents);
133 } else if (targetItem->acceptedMouseButtons() & Qt::LeftButton) {
128 } else if (targetItem->acceptedMouseButtons() & Qt::LeftButton) {
134− #if TOUCHDISPATCHER_DEBUG
135− qDebug() << "[TouchDispatcher] Item rejected the touch event. Trying a QMouseEvent";
129+ ugDebug("Item rejected the touch event. Trying a QMouseEvent");
136− #endif
137 // NB: Arbitrarily chose the first touch point to emulate the mouse pointer
130 // NB: Arbitrarily chose the first touch point to emulate the mouse pointer
138 QScopedPointer<QMouseEvent> mouseEvent(
131 QScopedPointer<QMouseEvent> mouseEvent(
139 touchToMouseEvent(QEvent::MouseButtonPress, targetTouchPoints.at(0), timestamp,
132 touchToMouseEvent(QEvent::MouseButtonPress, targetTouchPoints.at(0), timestamp,
140 modifiers, false /* transformNeeded */));
133 modifiers, false /* transformNeeded */));
141 Q_ASSERT(targetTouchPoints.at(0).state() == Qt::TouchPointPressed);
134 Q_ASSERT(targetTouchPoints.at(0).state() == Qt::TouchPointPressed);
142
135
143− #if TOUCHDISPATCHER_DEBUG
144− qDebug() << "[TouchDispatcher] dispatching" << qPrintable(mouseEventToString(mouseEvent.data()))
136+ ugDebug("dispatching " << qPrintable(mouseEventToString(mouseEvent.data()))
145− << "to" << m_targetItem.data();
137+ << " to " << m_targetItem.data());
146− #endif
147 QCoreApplication::sendEvent(targetItem, mouseEvent.data());
138 QCoreApplication::sendEvent(targetItem, mouseEvent.data());
148 if (mouseEvent->isAccepted()) {
139 if (mouseEvent->isAccepted()) {
149− #if TOUCHDISPATCHER_DEBUG
150− qDebug() << "[TouchDispatcher] Item accepted the QMouseEvent.";
140+ ugDebug("Item accepted the QMouseEvent.");
151− #endif
152− m_status = DeliveringMouseEvents;
141+ setStatus(DeliveringMouseEvents);
153 m_touchMouseId = targetTouchPoints.at(0).id();
142 m_touchMouseId = targetTouchPoints.at(0).id();
154
143
155 if (checkIfDoubleClicked(timestamp)) {
144 if (checkIfDoubleClicked(timestamp)) {
156 QScopedPointer<QMouseEvent> doubleClickEvent(
145 QScopedPointer<QMouseEvent> doubleClickEvent(
157 touchToMouseEvent(QEvent::MouseButtonDblClick, targetTouchPoints.at(0), timestamp,
146 touchToMouseEvent(QEvent::MouseButtonDblClick, targetTouchPoints.at(0), timestamp,
158 modifiers, false /* transformNeeded */));
147 modifiers, false /* transformNeeded */));
159− #if TOUCHDISPATCHER_DEBUG
160− qDebug() << "[TouchDispatcher] dispatching" << qPrintable(mouseEventToString(doubleClickEvent.data()))
148+ ugDebug("dispatching " << qPrintable(mouseEventToString(doubleClickEvent.data()))
161− << "to" << m_targetItem.data();
149+ << " to " << m_targetItem.data());
162− #endif
163 QCoreApplication::sendEvent(targetItem, doubleClickEvent.data());
150 QCoreApplication::sendEvent(targetItem, doubleClickEvent.data());
164 }
151 }
165
152
166 } else {
153 } else {
167− #if TOUCHDISPATCHER_DEBUG
168− qDebug() << "[TouchDispatcher] Item rejected the QMouseEvent.";
154+ ugDebug("Item rejected the QMouseEvent.");
169− #endif
170− m_status = TargetRejectedTouches;
155+ setStatus(TargetRejectedTouches);
171 }
156 }
172 } else {
157 } else {
173− #if TOUCHDISPATCHER_DEBUG
174− qDebug() << "[TouchDispatcher] Item rejected the touch event and does not accept mouse buttons.";
158+ ugDebug("Item rejected the touch event and does not accept mouse buttons.");
175− #endif
176− m_status = TargetRejectedTouches;
159+ setStatus(TargetRejectedTouches);
177 }
160 }
178}
161}
179
162
@@ -194,10 +177,8 @@ void TouchDispatcher::dispatchAsTouch(QEvent::Type eventType,
194 createQTouchEvent(eventType, device, modifiers, targetTouchPoints, window, timestamp));
177 createQTouchEvent(eventType, device, modifiers, targetTouchPoints, window, timestamp));
195
178
196
179
197− #if TOUCHDISPATCHER_DEBUG
198− qDebug() << "[TouchDispatcher] dispatching" << qPrintable(touchEventToString(eventForTargetItem.data()))
180+ ugDebug("dispatching " << qPrintable(touchEventToString(eventForTargetItem.data()))
199− << "to" << targetItem;
181+ << " to " << targetItem);
200− #endif
201 QCoreApplication::sendEvent(targetItem, eventForTargetItem.data());
182 QCoreApplication::sendEvent(targetItem, eventForTargetItem.data());
202}
183}
203
184
@@ -254,10 +235,8 @@ void TouchDispatcher::dispatchAsMouse(
254 QScopedPointer<QMouseEvent> mouseEvent(touchToMouseEvent(eventType, *touchMouse, timestamp, modifiers,
235 QScopedPointer<QMouseEvent> mouseEvent(touchToMouseEvent(eventType, *touchMouse, timestamp, modifiers,
255 true /* transformNeeded */));
236 true /* transformNeeded */));
256
237
257− #if TOUCHDISPATCHER_DEBUG
258− qDebug() << "[TouchDispatcher] dispatching" << qPrintable(mouseEventToString(mouseEvent.data()))
238+ ugDebug("dispatching " << qPrintable(mouseEventToString(mouseEvent.data()))
259− << "to" << m_targetItem.data();
239+ << " to " << m_targetItem.data());
260− #endif
261 QCoreApplication::sendEvent(m_targetItem.data(), mouseEvent.data());
240 QCoreApplication::sendEvent(m_targetItem.data(), mouseEvent.data());
262 }
241 }
263}
242}
@@ -365,3 +344,59 @@ bool TouchDispatcher::checkIfDoubleClicked(ulong newPressEventTimestamp)
365
344
366 return doubleClicked;
345 return doubleClicked;
367}
346}
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 @@
1/*
1/*
2− * Copyright (C) 2014 Canonical, Ltd.
2+ * Copyright (C) 2014-2015 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -36,12 +36,20 @@ public:
36 void setTargetItem(QQuickItem *target);
36 void setTargetItem(QQuickItem *target);
37 QQuickItem *targetItem() { return m_targetItem; }
37 QQuickItem *targetItem() { return m_targetItem; }
38
38
39− void dispatch(QEvent::Type eventType,
40− QTouchDevice *device,
39+ void dispatch(QTouchDevice *device,
41 Qt::KeyboardModifiers modifiers,
40 Qt::KeyboardModifiers modifiers,
42 const QList<QTouchEvent::TouchPoint> &touchPoints,
41 const QList<QTouchEvent::TouchPoint> &touchPoints,
43 QWindow *window,
42 QWindow *window,
44 ulong timestamp);
43 ulong timestamp);
44+
45+ void reset();
46+
47+ enum Status {
48+ NoActiveTouch,
49+ DeliveringTouchEvents,
50+ DeliveringMouseEvents,
51+ TargetRejectedTouches
52+ };
45private:
53private:
46 void dispatchTouchBegin(
54 void dispatchTouchBegin(
47 QTouchDevice *device,
55 QTouchDevice *device,
@@ -73,14 +81,13 @@ private:
73
81
74 bool checkIfDoubleClicked(ulong newPressEventTimestamp);
82 bool checkIfDoubleClicked(ulong newPressEventTimestamp);
75
83
84+ void setStatus(Status status);
85+
86+ static QEvent::Type resolveEventType(const QList<QTouchEvent::TouchPoint> &touchPoints);
87+
76 QPointer<QQuickItem> m_targetItem;
88 QPointer<QQuickItem> m_targetItem;
77
89
78− enum {
79− NoActiveTouch,
80− DeliveringTouchEvents,
81− DeliveringMouseEvents,
82− TargetRejectedTouches
83− } m_status;
90+ Status m_status;
84
91
85 int m_touchMouseId;
92 int m_touchMouseId;
86 ulong m_touchMousePressTimestamp;
93 ulong m_touchMousePressTimestamp;
plugins/Ubuntu/Gestures/TouchGate.cpp+84−60
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2014 Canonical, Ltd.
2+ * Copyright (C) 2014-2015 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -23,8 +23,18 @@
23#include <TouchRegistry.h>
23#include <TouchRegistry.h>
24
24
25#if TOUCHGATE_DEBUG
25#if TOUCHGATE_DEBUG
26+#define ugDebug(params) qDebug().nospace() << "[TouchGate(" << (void*)this << ")] " << params
26#include <DebugHelpers.h>
27#include <DebugHelpers.h>
28+#else // TOUCHGATE_DEBUG
29+#define ugDebug(params) ((void)0)
27−#endif
30+#endif // TOUCHGATE_DEBUG
31+
32+TouchGate::TouchGate(QQuickItem *parent)
33+ : QQuickItem(parent)
34+{
35+ connect(this, &QQuickItem::enabledChanged,
36+ this, &TouchGate::onEnabledChanged);
37+}
28
38
29bool TouchGate::event(QEvent *e)
39bool TouchGate::event(QEvent *e)
30{
40{
@@ -38,13 +48,12 @@ bool TouchGate::event(QEvent *e)
38
48
39void TouchGate::touchEvent(QTouchEvent *event)
49void TouchGate::touchEvent(QTouchEvent *event)
40{
50{
41− #if TOUCHGATE_DEBUG
42− qDebug() << "[TouchGate] got touch event" << qPrintable(touchEventToString(event));
51+ ugDebug("got touch event" << qPrintable(touchEventToString(event)));
43− #endif
44 event->accept();
52 event->accept();
45
53
46 const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
54 const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
55+ QList<QTouchEvent::TouchPoint> validTouchPoints;
47− bool goodToGo = true;
56+ bool ownsAllTouches = true;
48 for (int i = 0; i < touchPoints.count(); ++i) {
57 for (int i = 0; i < touchPoints.count(); ++i) {
49 const QTouchEvent::TouchPoint &touchPoint = touchPoints[i];
58 const QTouchEvent::TouchPoint &touchPoint = touchPoints[i];
50
59
@@ -55,31 +64,41 @@ void TouchGate::touchEvent(QTouchEvent *event)
55 TouchRegistry::instance()->requestTouchOwnership(touchPoint.id(), this);
64 TouchRegistry::instance()->requestTouchOwnership(touchPoint.id(), this);
56 }
65 }
57
66
58− goodToGo &= m_touchInfoMap.contains(touchPoint.id())
67+ if (m_touchInfoMap.contains(touchPoint.id())) {
68+ validTouchPoints.append(touchPoint);
69+
59− && m_touchInfoMap[touchPoint.id()].ownership == OwnershipGranted;
70+ ownsAllTouches &= m_touchInfoMap[touchPoint.id()].ownership == OwnershipGranted;
60
71
61− if (touchPoint.state() == Qt::TouchPointReleased && m_touchInfoMap.contains(touchPoint.id())) {
72+ if (touchPoint.state() == Qt::TouchPointReleased) {
62− m_touchInfoMap[touchPoint.id()].ended = true;
73+ m_touchInfoMap[touchPoint.id()].ended = true;
74+ }
63 }
75 }
64
76
65 }
77 }
66
78
67− if (goodToGo) {
79+ if (validTouchPoints.isEmpty()) {
80+ // nothing to do.
81+ return;
82+ }
83+
84+ if (ownsAllTouches) {
68 if (m_storedEvents.isEmpty()) {
85 if (m_storedEvents.isEmpty()) {
69 // let it pass through
86 // let it pass through
70− dispatchTouchEventToTarget(event);
87+ removeTouchInfoForEndedTouches(validTouchPoints);
88+ m_dispatcher.dispatch(event->device(), event->modifiers(), validTouchPoints,
89+ event->window(), event->timestamp());
71 } else {
90 } else {
72 // Retain the event to ensure TouchGate dispatches them in order.
91 // Retain the event to ensure TouchGate dispatches them in order.
73 // Otherwise the current event would come before the stored ones, which are older.
92 // Otherwise the current event would come before the stored ones, which are older.
74− #if TOUCHGATE_DEBUG
93+ ugDebug("Storing event because thouches " << qPrintable(oldestPendingTouchIdsString())
75− qDebug("[TouchGate] Storing event because thouches %s are still pending ownership.",
94+ << " are still pending ownership.");
76− qPrintable(oldestPendingTouchIdsString()));
77− #endif
78− storeTouchEvent(event);
95+ storeTouchEvent(event->device(), event->modifiers(), validTouchPoints,
96+ event->window(), event->timestamp());
79 }
97 }
80 } else {
98 } else {
81 // Retain events that have unowned touches
99 // Retain events that have unowned touches
82− storeTouchEvent(event);
100+ storeTouchEvent(event->device(), event->modifiers(), validTouchPoints,
101+ event->window(), event->timestamp());
83 }
102 }
84}
103}
85
104
@@ -88,24 +107,23 @@ void TouchGate::touchOwnershipEvent(TouchOwnershipEvent *event)
88 // TODO: Optimization: batch those actions as TouchOwnershipEvents
107 // TODO: Optimization: batch those actions as TouchOwnershipEvents
89 // might come one right after the other.
108 // might come one right after the other.
90
109
91− Q_ASSERT(m_touchInfoMap.contains(event->touchId()));
110+ if (m_touchInfoMap.contains(event->touchId())) {
111+ TouchInfo &touchInfo = m_touchInfoMap[event->touchId()];
92
112
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+ }
94
121
95− if (event->gained()) {
122+ dispatchFullyOwnedEvents();
96− #if TOUCHGATE_DEBUG
97− qDebug() << "[TouchGate] Got ownership of touch " << event->touchId();
98− #endif
99− touchInfo.ownership = OwnershipGranted;
100 } else {
123 } else {
101− #if TOUCHGATE_DEBUG
124+ // Ignore it. It probably happened because the TouchGate got disabled
102− qDebug() << "[TouchGate] Lost ownership of touch " << event->touchId();
125+ // between the time it requested ownership and the time it got it.
103− #endif
104− m_touchInfoMap.remove(event->touchId());
105− removeTouchFromStoredEvents(event->touchId());
106 }
126 }
107−
108− dispatchFullyOwnedEvents();
109}
127}
110
128
111bool TouchGate::isTouchPointOwned(int touchId) const
129bool TouchGate::isTouchPointOwned(int touchId) const
@@ -113,14 +131,15 @@ bool TouchGate::isTouchPointOwned(int touchId) const
113 return m_touchInfoMap[touchId].ownership == OwnershipGranted;
131 return m_touchInfoMap[touchId].ownership == OwnershipGranted;
114}
132}
115
133
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)
117{
139{
118− #if TOUCHGATE_DEBUG
140+ ugDebug("Storing" << touchPoints);
119− qDebug() << "[TouchGate] Storing" << qPrintable(touchEventToString(event));
141+ TouchEvent event(device, modifiers, touchPoints, window, timestamp);
120− #endif
121−
122− TouchEvent clonedEvent(event);
123− m_storedEvents.append(std::move(clonedEvent));
142+ m_storedEvents.append(std::move(event));
124}
143}
125
144
126void TouchGate::removeTouchFromStoredEvents(int touchId)
145void TouchGate::removeTouchFromStoredEvents(int touchId)
@@ -193,25 +212,13 @@ void TouchGate::setTargetItem(QQuickItem *item)
193void TouchGate::dispatchTouchEventToTarget(const TouchEvent &event)
212void TouchGate::dispatchTouchEventToTarget(const TouchEvent &event)
194{
213{
195 removeTouchInfoForEndedTouches(event.touchPoints);
214 removeTouchInfoForEndedTouches(event.touchPoints);
196− m_dispatcher.dispatch(event.eventType,
215+ m_dispatcher.dispatch(event.device,
197− event.device,
198 event.modifiers,
216 event.modifiers,
199 event.touchPoints,
217 event.touchPoints,
200 event.window,
218 event.window,
201 event.timestamp);
219 event.timestamp);
202}
220}
203
221
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−
215void TouchGate::removeTouchInfoForEndedTouches(const QList<QTouchEvent::TouchPoint> &touchPoints)
222void TouchGate::removeTouchInfoForEndedTouches(const QList<QTouchEvent::TouchPoint> &touchPoints)
216{
223{
217 for (int i = 0; i < touchPoints.size(); ++i) {\
224 for (int i = 0; i < touchPoints.size(); ++i) {\
@@ -226,14 +233,31 @@ void TouchGate::removeTouchInfoForEndedTouches(const QList<QTouchEvent::TouchPoi
226 }
233 }
227}
234}
228
235
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+
229−TouchGate::TouchEvent::TouchEvent(const QTouchEvent *event)
251+TouchGate::TouchEvent::TouchEvent(QTouchDevice *device,
230− : eventType(event->type())
252+ Qt::KeyboardModifiers modifiers,
253+ const QList<QTouchEvent::TouchPoint> &touchPoints,
254+ QWindow *window,
255+ ulong timestamp)
231− , device(event->device())
256+ : device(device)
232− , modifiers(event->modifiers())
257+ , modifiers(modifiers)
233− , touchPoints(event->touchPoints())
258+ , touchPoints(touchPoints)
234− , target(qobject_cast<QQuickItem*>(event->target()))
235− , window(event->window())
259+ , window(window)
236− , timestamp(event->timestamp())
260+ , timestamp(timestamp)
237{
261{
238}
262}
239
263
plugins/Ubuntu/Gestures/TouchGate.h+18−5
@@ -45,6 +45,8 @@ class UBUNTUGESTURESQML_EXPORT TouchGate : public QQuickItem {
45 Q_PROPERTY(QQuickItem* targetItem READ targetItem WRITE setTargetItem NOTIFY targetItemChanged)
45 Q_PROPERTY(QQuickItem* targetItem READ targetItem WRITE setTargetItem NOTIFY targetItemChanged)
46
46
47public:
47public:
48+ TouchGate(QQuickItem *parent = nullptr);
49+
48 bool event(QEvent *e) override;
50 bool event(QEvent *e) override;
49
51
50 QQuickItem *targetItem() { return m_dispatcher.targetItem(); }
52 QQuickItem *targetItem() { return m_dispatcher.targetItem(); }
@@ -55,31 +57,42 @@ Q_SIGNALS:
55
57
56protected:
58protected:
57 void touchEvent(QTouchEvent *event) override;
59 void touchEvent(QTouchEvent *event) override;
60+
61+private Q_SLOTS:
62+ void onEnabledChanged();
63+
58private:
64private:
65+ void reset();
66+
59 class TouchEvent {
67 class TouchEvent {
60 public:
68 public:
69+ TouchEvent(QTouchDevice *device,
70+ Qt::KeyboardModifiers modifiers,
61− TouchEvent(const QTouchEvent *event);
71+ const QList<QTouchEvent::TouchPoint> &touchPoints,
72+ QWindow *window,
73+ ulong timestamp);
62
74
63 bool removeTouch(int touchId);
75 bool removeTouch(int touchId);
64
76
65− QEvent::Type eventType;
66 QTouchDevice *device;
77 QTouchDevice *device;
67 Qt::KeyboardModifiers modifiers;
78 Qt::KeyboardModifiers modifiers;
68 QList<QTouchEvent::TouchPoint> touchPoints;
79 QList<QTouchEvent::TouchPoint> touchPoints;
69− QQuickItem *target;
70 QWindow *window;
80 QWindow *window;
71 ulong timestamp;
81 ulong timestamp;
72 };
82 };
73
83
74 void touchOwnershipEvent(TouchOwnershipEvent *event);
84 void touchOwnershipEvent(TouchOwnershipEvent *event);
75 bool isTouchPointOwned(int touchId) const;
85 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);
77 void removeTouchFromStoredEvents(int touchId);
91 void removeTouchFromStoredEvents(int touchId);
78 void dispatchFullyOwnedEvents();
92 void dispatchFullyOwnedEvents();
79 bool eventIsFullyOwned(const TouchEvent &event) const;
93 bool eventIsFullyOwned(const TouchEvent &event) const;
80
94
81 void dispatchTouchEventToTarget(const TouchEvent &event);
95 void dispatchTouchEventToTarget(const TouchEvent &event);
82− void dispatchTouchEventToTarget(QTouchEvent* event);
83
96
84 void removeTouchInfoForEndedTouches(const QList<QTouchEvent::TouchPoint> &touchPoints);
97 void removeTouchInfoForEndedTouches(const QList<QTouchEvent::TouchPoint> &touchPoints);
85
98
qml/Components/DragHandle.qml+18−33
@@ -43,16 +43,8 @@ import Ubuntu.Gestures 0.1
43 }
43 }
44
44
45 */
45 */
46−EdgeDragArea {
46+DirectionalDragArea {
47 id: dragArea
47 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
56
48
57 property bool stretch: false
49 property bool stretch: false
58
50
@@ -68,6 +60,9 @@ EdgeDragArea {
68 }
60 }
69
61
70 property real hintDisplacement: 0
62 property real hintDisplacement: 0
63+
64+ immediateRecognition: hintDisplacement > 0
65+
71 property var overrideStartValue: undefined
66 property var overrideStartValue: undefined
72 SmoothedAnimation {
67 SmoothedAnimation {
73 id: hintingAnimation
68 id: hintingAnimation
@@ -98,7 +93,6 @@ EdgeDragArea {
98 // Private stuff
93 // Private stuff
99 QtObject {
94 QtObject {
100 id: d
95 id: d
101− property var previousStatus: DirectionalDragArea.WaitingForTouch
102 property real startValue
96 property real startValue
103 property real minValue: {
97 property real minValue: {
104 if (direction == Direction.Horizontal) {
98 if (direction == Direction.Horizontal) {
@@ -183,7 +177,7 @@ EdgeDragArea {
183 }
177 }
184
178
185 onDistanceChanged: {
179 onDistanceChanged: {
186− if (status === DirectionalDragArea.Recognized) {
180+ if (dragging) {
187 // don't go the whole distance in order to smooth out the movement
181 // don't go the whole distance in order to smooth out the movement
188 var step = distance * 0.3;
182 var step = distance * 0.3;
189
183
@@ -193,31 +187,22 @@ EdgeDragArea {
193 }
187 }
194 }
188 }
195
189
196− onStatusChanged: {
190+ onDraggingChanged: {
197− if (status === DirectionalDragArea.WaitingForTouch) {
191+ if (dragging) {
198− hintingAnimation.stop();
192+ dragEvaluator.reset();
199− if (d.previousStatus === DirectionalDragArea.Recognized) {
193+ if (overrideStartValue !== undefined) {
200− d.onFinishedRecognizedGesture();
194+ d.startValue = overrideStartValue;
201− } else /* d.previousStatus === DirectionalDragArea.Undecided */ {
195+ } else {
202− // Gesture was rejected.
196+ d.startValue = parent[d.targetProp];
203− d.rollbackDrag();
204 }
197 }
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− }
213
198
214− if (hintDisplacement > 0) {
199+ if (hintDisplacement > 0) {
215− hintingAnimation.targetValue = d.startValue;
200+ hintingAnimation.targetValue = d.startValue;
216− hintingAnimation.start();
201+ hintingAnimation.start();
217− }
218 }
202 }
203+ } else {
204+ hintingAnimation.stop();
205+ d.onFinishedRecognizedGesture();
219 }
206 }
220−
221− d.previousStatus = status;
222 }
207 }
223}
208}
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 {
153 // (as expected) but would also cause the dash content flickable to move a bit, because
153 // (as expected) but would also cause the dash content flickable to move a bit, because
154 // that flickable was getting the touch events while overviewDragHandle was still undecided
154 // that flickable was getting the touch events while overviewDragHandle was still undecided
155 // about whether that touch was indeed performing a directional drag gesture.
155 // about whether that touch was indeed performing a directional drag gesture.
156− forceNonInteractive: overviewDragHandle.status != DirectionalDragArea.WaitingForTouch
156+ forceNonInteractive: overviewDragHandle.dragging
157
157
158 enabled: bottomEdgeController.progress == 0
158 enabled: bottomEdgeController.progress == 0
159 }
159 }
@@ -330,7 +330,7 @@ Showable {
330 }
330 }
331 }
331 }
332
332
333− EdgeDragArea {
333+ DirectionalDragArea {
334 id: overviewDragHandle
334 id: overviewDragHandle
335 objectName: "overviewDragHandle"
335 objectName: "overviewDragHandle"
336 z: 1
336 z: 1
@@ -346,21 +346,14 @@ Showable {
346 height: units.gu(2)
346 height: units.gu(2)
347
347
348 onSceneDistanceChanged: {
348 onSceneDistanceChanged: {
349− if (status == DirectionalDragArea.Recognized) {
349+ if (dragging) {
350 bottomEdgeController.enableAnimation = false;
350 bottomEdgeController.enableAnimation = false;
351 bottomEdgeController.progress = Math.max(0, Math.min(1, sceneDistance / fullMovement));
351 bottomEdgeController.progress = Math.max(0, Math.min(1, sceneDistance / fullMovement));
352 }
352 }
353 }
353 }
354
354
355− property int previousStatus: -1
355+ onDraggingChanged: {
356− property int currentStatus: DirectionalDragArea.WaitingForTouch
356+ if (!dragging) {
357−
358− onStatusChanged: {
359− previousStatus = currentStatus;
360− currentStatus = status;
361−
362− if (status == DirectionalDragArea.WaitingForTouch &&
363− previousStatus == DirectionalDragArea.Recognized) {
364 bottomEdgeController.enableAnimation = true;
357 bottomEdgeController.enableAnimation = true;
365 bottomEdgeController.progress = (bottomEdgeController.progress > 0.2) ? 1 : 0;
358 bottomEdgeController.progress = (bottomEdgeController.progress > 0.2) ? 1 : 0;
366 }
359 }
qml/Greeter/CoverPage.qml+3−2
@@ -129,13 +129,14 @@ Showable {
129
129
130 DragHandle {
130 DragHandle {
131 id: dragHandle
131 id: dragHandle
132+ objectName: "coverPageDragHandle"
132 anchors.fill: parent
133 anchors.fill: parent
133 anchors.leftMargin: root.dragHandleLeftMargin
134 anchors.leftMargin: root.dragHandleLeftMargin
134 enabled: root.draggable
135 enabled: root.draggable
135 direction: Direction.Horizontal
136 direction: Direction.Horizontal
136
137
137− onDraggingChanged: {
138+ onPressedChanged: {
138− if (dragging) {
139+ if (pressed) {
139 root.tease();
140 root.tease();
140 showLabelAnimation.start();
141 showLabelAnimation.start();
141 }
142 }
qml/Launcher/Launcher.qml+7−15
@@ -210,7 +210,7 @@ Item {
210 bottom: parent.bottom
210 bottom: parent.bottom
211 }
211 }
212 x: -width
212 x: -width
213− visible: root.x > 0 || x > -width || dragArea.status === DirectionalDragArea.Undecided
213+ visible: root.x > 0 || x > -width || dragArea.pressed
214 model: LauncherModel
214 model: LauncherModel
215
215
216 property bool animate: true
216 property bool animate: true
@@ -272,7 +272,7 @@ Item {
272 }
272 }
273 }
273 }
274
274
275− EdgeDragArea {
275+ DirectionalDragArea {
276 id: dragArea
276 id: dragArea
277 objectName: "launcherDragArea"
277 objectName: "launcherDragArea"
278
278
@@ -283,20 +283,11 @@ Item {
283 width: root.dragAreaWidth
283 width: root.dragAreaWidth
284 height: root.height
284 height: root.height
285
285
286− onTouchXChanged: {
286+ onDistanceChanged: {
287− if (status !== DirectionalDragArea.Recognized || launcher.state == "visible")
287+ if (!dragging || launcher.state == "visible")
288 return;
288 return;
289
289
290− // When the gesture finally gets recognized, the finger will likely be
290+ panel.x = -panel.width + Math.min(Math.max(0, distance), panel.width);
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
300 }
291 }
301
292
302 onDraggingChanged: {
293 onDraggingChanged: {
@@ -306,7 +297,8 @@ Item {
306 if (distance > minimizeDistance) {
297 if (distance > minimizeDistance) {
307 root.dash();
298 root.dash();
308 }
299 }
309− } else {
300+ } else if (root.state === "") {
301+ // didn't drag far enough. rollback
310 root.switchToNextState("")
302 root.switchToNextState("")
311 }
303 }
312 }
304 }
qml/Panel/IndicatorsMenu.qml+12−2
@@ -186,9 +186,18 @@ Showable {
186 enabled: !root.shown && root.available
186 enabled: !root.shown && root.available
187 autoCompleteDragThreshold: maxTotalDragDistance / 2
187 autoCompleteDragThreshold: maxTotalDragDistance / 2
188 stretch: true
188 stretch: true
189− distanceThreshold: enableHint ? 0 : minimizedPanelHeight
190
189
190+ onPressedChanged: {
191+ if (pressed) {
192+ touchPressTime = new Date().getTime();
193+ } else {
194+ var touchReleaseTime = new Date().getTime();
195+ if (touchReleaseTime - touchPressTime <= 300) {
191− onTapped: showTapped(Qt.point(touchSceneX, touchSceneY));
196+ root.showTapped(Qt.point(touchSceneX, touchSceneY));
197+ }
198+ }
199+ }
200+ property var touchPressTime
192
201
193 // using hint regulates minimum to hint displacement, but in fullscreen mode, we need to do it manually.
202 // using hint regulates minimum to hint displacement, but in fullscreen mode, we need to do it manually.
194 overrideStartValue: enableHint ? minimizedPanelHeight : expandedPanelHeight + handle.height
203 overrideStartValue: enableHint ? minimizedPanelHeight : expandedPanelHeight + handle.height
@@ -204,6 +213,7 @@ Showable {
204
213
205 DragHandle {
214 DragHandle {
206 id: __hideDragHandle
215 id: __hideDragHandle
216+ objectName: "hideDragHandle"
207 anchors.fill: handle
217 anchors.fill: handle
208 direction: Direction.Upwards
218 direction: Direction.Upwards
209 enabled: root.shown && root.available
219 enabled: root.shown && root.available
qml/Stages/PhoneStage.qml+31−41
@@ -146,14 +146,13 @@ Rectangle {
146 id: spreadView
146 id: spreadView
147 objectName: "spreadView"
147 objectName: "spreadView"
148 anchors.fill: parent
148 anchors.fill: parent
149− interactive: (spreadDragArea.status == DirectionalDragArea.Recognized || phase > 1)
150− && draggedDelegateCount === 0
149+ interactive: (spreadDragArea.dragging || phase > 1) && draggedDelegateCount === 0
151 contentWidth: spreadRow.width - shift
150 contentWidth: spreadRow.width - shift
152 contentX: -shift
151 contentX: -shift
153
152
154 // This indicates when the spreadView is active. That means, all the animations
153 // This indicates when the spreadView is active. That means, all the animations
155 // are activated and tiles need to line up for the spread.
154 // 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
157
156
158 // The flickable needs to fill the screen in order to get touch events all over.
157 // The flickable needs to fill the screen in order to get touch events all over.
159 // However, we don't want to the user to be able to scroll back all the way. For
158 // However, we don't want to the user to be able to scroll back all the way. For
@@ -450,7 +449,7 @@ Rectangle {
450 }
449 }
451 }
450 }
452
451
453− EdgeDragArea {
452+ DirectionalDragArea {
454 id: spreadDragArea
453 id: spreadDragArea
455 objectName: "spreadDragArea"
454 objectName: "spreadDragArea"
456 direction: Direction.Leftwards
455 direction: Direction.Leftwards
@@ -462,59 +461,50 @@ Rectangle {
462 property var gesturePoints: new Array()
461 property var gesturePoints: new Array()
463
462
464 onTouchXChanged: {
463 onTouchXChanged: {
465− if (!dragging) {
464+ 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) {
471 // Gesture recognized. Let's move the spreadView with the finger
465 // Gesture recognized. Let's move the spreadView with the finger
472 var dragX = Math.min(touchX + width, width); // Prevent dragging rightwards
466 var dragX = Math.min(touchX + width, width); // Prevent dragging rightwards
473 dragX = -dragX + spreadDragArea.width - spreadView.shift;
467 dragX = -dragX + spreadDragArea.width - spreadView.shift;
474 // Don't allow dragging further than the animation crossing with phase2's animation
468 // Don't allow dragging further than the animation crossing with phase2's animation
475 var maxMovement = spreadView.width * spreadView.positionMarker4 - spreadView.shift;
469 var maxMovement = spreadView.width * spreadView.positionMarker4 - spreadView.shift;
470+
476 spreadView.contentX = Math.min(dragX, maxMovement);
471 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;
477 }
476 }
478− gesturePoints.push(touchX);
479− }
480−
481− property int previousStatus: -1
482− property int currentStatus: DirectionalDragArea.WaitingForTouch
483
477
484− onStatusChanged: {
478+ gesturePoints.push(touchX);
485− previousStatus = currentStatus;
486− currentStatus = status;
487 }
479 }
488
480
489 onDraggingChanged: {
481 onDraggingChanged: {
490 if (dragging) {
482 if (dragging) {
491 // A potential edge-drag gesture has started. Start recording it
483 // A potential edge-drag gesture has started. Start recording it
492 gesturePoints = [];
484 gesturePoints = [];
493− return;
485+ } else {
494− }
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 = [];
495
497
498+ if (oneWayFlick && spreadView.shiftedContentX > units.gu(2) &&
499+ spreadView.shiftedContentX < spreadView.positionMarker1 * spreadView.width) {
496− // Ok. The user released. Find out if it was a one-way movement.
500+ // If it was a short one-way movement, do the Alt+Tab switch
497− var oneWayFlick = true;
501+ // no matter if we didn't cross positionMarker1 yet.
498− var smallestX = spreadDragArea.width;
502+ spreadView.snapTo(1);
499− for (var i = 0; i < gesturePoints.length; i++) {
503+ } else if (!dragging) {
500− if (gesturePoints[i] >= smallestX) {
504+ // otherwise snap to the closest snap position we can find
501− oneWayFlick = false;
505+ // (might be back to start, to app 1 or to spread)
502− break;
506+ spreadView.snap();
503 }
507 }
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();
518 }
508 }
519 }
509 }
520 }
510 }
qml/Stages/TabletStage.qml+18−20
@@ -170,8 +170,7 @@ Rectangle {
170 Flickable {
170 Flickable {
171 id: spreadView
171 id: spreadView
172 anchors.fill: parent
172 anchors.fill: parent
173− interactive: (spreadDragArea.status == DirectionalDragArea.Recognized || phase > 1)
174− && draggedDelegateCount === 0
173+ interactive: (spreadDragArea.dragging || phase > 1) && draggedDelegateCount === 0
175 contentWidth: spreadRow.width - shift
174 contentWidth: spreadRow.width - shift
176 contentX: -shift
175 contentX: -shift
177
176
@@ -598,7 +597,7 @@ Rectangle {
598 }
597 }
599 }
598 }
600
599
601− EdgeDragArea {
600+ DirectionalDragArea {
602 id: spreadDragArea
601 id: spreadDragArea
603 anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
602 anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
604 width: root.dragAreaWidth
603 width: root.dragAreaWidth
@@ -624,26 +623,25 @@ Rectangle {
624 if (dragging) {
623 if (dragging) {
625 // Gesture recognized. Start recording this gesture
624 // Gesture recognized. Start recording this gesture
626 gesturePoints = [];
625 gesturePoints = [];
627− return;
626+ } else {
628− }
629−
630− // Ok. The user released. Find out if it was a one-way movement.
627+ // Ok. The user released. Find out if it was a one-way movement.
631− var oneWayFlick = priv.evaluateOneWayFlick(gesturePoints);
628+ var oneWayFlick = priv.evaluateOneWayFlick(gesturePoints);
632− gesturePoints = [];
629+ gesturePoints = [];
633
630
634− if (oneWayFlick && spreadView.shiftedContentX < spreadView.positionMarker1 * spreadView.width) {
631+ if (oneWayFlick && spreadView.shiftedContentX < spreadView.positionMarker1 * spreadView.width) {
635− // If it was a short one-way movement, do the Alt+Tab switch
632+ // If it was a short one-way movement, do the Alt+Tab switch
636− // no matter if we didn't cross positionMarker1 yet.
633+ // 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) {
642 spreadView.snapTo(spreadView.nextInStack);
634 spreadView.snapTo(spreadView.nextInStack);
643 } else {
635 } else {
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 {
644− // otherwise snap to the closest snap position we can find
641+ // otherwise snap to the closest snap position we can find
645− // (might be back to start, to app 1 or to spread)
642+ // (might be back to start, to app 1 or to spread)
646− spreadView.snap();
643+ spreadView.snap();
644+ }
647 }
645 }
648 }
646 }
649 }
647 }
qml/Tutorial/TutorialBottom.qml+1−1
@@ -73,7 +73,7 @@ TutorialPage {
73 ]
73 ]
74 }
74 }
75
75
76− EdgeDragArea {
76+ DirectionalDragArea {
77 id: dragArea
77 id: dragArea
78 direction: Direction.Upwards
78 direction: Direction.Upwards
79 anchors {
79 anchors {
tests/libs/UbuntuGestures/tst_TouchRegistry.cpp+68−2
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2014 Canonical, Ltd.
2+ * Copyright (C) 2014-2015 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -66,6 +66,7 @@ private Q_SLOTS:
66 void candidatesAndWatchers_2();
66 void candidatesAndWatchers_2();
67 void rejectingTouchfterItsEnd();
67 void rejectingTouchfterItsEnd();
68 void removeOldUndecidedCandidates();
68 void removeOldUndecidedCandidates();
69+ void interimOwnerWontGetUnownedTouchEvents();
69};
70};
70
71
71void tst_TouchRegistry::requestWithNoCandidates()
72void tst_TouchRegistry::requestWithNoCandidates()
@@ -742,7 +743,7 @@ void tst_TouchRegistry::removeOldUndecidedCandidates()
742
743
743 // Simulate that enough time has passed to cause the CandidateInactivityTimer to timeout,
744 // Simulate that enough time has passed to cause the CandidateInactivityTimer to timeout,
744 // making TouchRegistry consider that undecidedCantidate defaulted.
745 // making TouchRegistry consider that undecidedCantidate defaulted.
745− fakeTimerFactory->makeRunningTimersTimeout();
746+ fakeTimerFactory->updateTime(10000);
746
747
747 QVERIFY(undecidedCandidate.ownedTouches.isEmpty());
748 QVERIFY(undecidedCandidate.ownedTouches.isEmpty());
748 QVERIFY(undecidedCandidate.lostTouches.contains(0));
749 QVERIFY(undecidedCandidate.lostTouches.contains(0));
@@ -752,6 +753,71 @@ void tst_TouchRegistry::removeOldUndecidedCandidates()
752 delete touchRegistry;
753 delete touchRegistry;
753}
754}
754
755
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+
755////////////// TouchMemento //////////
821////////////// TouchMemento //////////
756
822
757TouchMemento::TouchMemento(const QTouchEvent *touchEvent)
823TouchMemento::TouchMemento(const QTouchEvent *touchEvent)
tests/plugins/Ubuntu/Gestures/CMakeLists.txt+3−1
@@ -19,6 +19,7 @@ add_custom_target(UbuntuGesturesTestQmlFiles ALL
19)
19)
20
20
21add_definitions(-DUBUNTU_GESTURES_PLUGIN_DIR="${CMAKE_BINARY_DIR}/plugins")
21add_definitions(-DUBUNTU_GESTURES_PLUGIN_DIR="${CMAKE_BINARY_DIR}/plugins")
22+add_definitions(-DTESTS_UTILS_MODULES_DIR="${CMAKE_BINARY_DIR}/tests/utils/modules")
22
23
23macro(add_gesture_ui_test CLASSNAME)
24macro(add_gesture_ui_test CLASSNAME)
24 add_executable(${CLASSNAME}TestExec tst_${CLASSNAME}.cpp GestureTest.cpp)
25 add_executable(${CLASSNAME}TestExec tst_${CLASSNAME}.cpp GestureTest.cpp)
@@ -26,7 +27,8 @@ macro(add_gesture_ui_test CLASSNAME)
26 target_link_libraries(${CLASSNAME}TestExec UbuntuGesturesQml UbuntuGestures)
27 target_link_libraries(${CLASSNAME}TestExec UbuntuGesturesQml UbuntuGestures)
27
28
28 add_binary_qml_test(${CLASSNAME} ${CMAKE_BINARY_DIR}/plugins/Ubuntu/Gestures UbuntuGesturesTestQmlFiles "")
29 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)
30endmacro(add_gesture_ui_test)
32endmacro(add_gesture_ui_test)
31
33
32macro(add_gesture_test CLASSNAME)
34macro(add_gesture_test CLASSNAME)
tests/plugins/Ubuntu/Gestures/DownwardsLauncher.qml+5−19
@@ -37,8 +37,8 @@ Item {
37
37
38 Rectangle {
38 Rectangle {
39 id: dragAreaRect
39 id: dragAreaRect
40− color: "yellow"
41− opacity: 0.0
40+ opacity: dragArea.dragging ? 0.5 : 0.0
41+ color: "green"
42 anchors.fill: dragArea
42 anchors.fill: dragArea
43 }
43 }
44
44
@@ -49,24 +49,10 @@ Item {
49 height: units.gu(5)
49 height: units.gu(5)
50
50
51 direction: Direction.Downwards
51 direction: Direction.Downwards
52− maxDeviation: units.gu(2)
53− wideningAngle: 10
54− distanceThreshold: units.gu(4)
55
52
56− onStatusChanged: {
53+ onDraggingChanged: {
57− switch (status) {
54+ if (dragging) {
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)
55+ launcher.y = Qt.binding(launcher.followDragArea)
65− break;
66− default: // DirectionalDragArea.Recognized:
67− dragAreaRect.color = "green"
68− dragAreaRect.opacity = 0.5
69− break;
70 }
56 }
71 }
57 }
72
58
tests/plugins/Ubuntu/Gestures/GestureTest.cpp+13−3
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013,2015 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -21,8 +21,11 @@
21#include <QQuickView>
21#include <QQuickView>
22#include <QtTest>
22#include <QtTest>
23
23
24+#include <Timer.h>
24#include <TouchRegistry.h>
25#include <TouchRegistry.h>
25
26
27+using namespace UbuntuGestures;
28+
26GestureTest::GestureTest(const QString &qmlFilename)
29GestureTest::GestureTest(const QString &qmlFilename)
27 : QObject(), m_device(nullptr), m_view(nullptr), m_qmlFilename(qmlFilename)
30 : QObject(), m_device(nullptr), m_view(nullptr), m_qmlFilename(qmlFilename)
28{
31{
@@ -40,14 +43,17 @@ void GestureTest::initTestCase()
40void GestureTest::init()
43void GestureTest::init()
41{
44{
42 m_view = new QQuickView;
45 m_view = new QQuickView;
43− m_view->setResizeMode(QQuickView::SizeViewToRootObject);
46+ m_view->setResizeMode(QQuickView::SizeRootObjectToView);
44 m_view->engine()->addImportPath(QStringLiteral(UBUNTU_GESTURES_PLUGIN_DIR));
47 m_view->engine()->addImportPath(QStringLiteral(UBUNTU_GESTURES_PLUGIN_DIR));
48+ m_view->engine()->addImportPath(QStringLiteral(TESTS_UTILS_MODULES_DIR));
45 m_view->setSource(QUrl::fromLocalFile(m_qmlFilename));
49 m_view->setSource(QUrl::fromLocalFile(m_qmlFilename));
46 m_view->show();
50 m_view->show();
47 QVERIFY(QTest::qWaitForWindowExposed(m_view));
51 QVERIFY(QTest::qWaitForWindowExposed(m_view));
48 QVERIFY(m_view->rootObject() != 0);
52 QVERIFY(m_view->rootObject() != 0);
49
53
54+ m_fakeTimerFactory = new FakeTimerFactory;
55+
50− m_touchRegistry = new TouchRegistry;
56+ m_touchRegistry = new TouchRegistry(nullptr, m_fakeTimerFactory);
51 m_view->installEventFilter(m_touchRegistry);
57 m_view->installEventFilter(m_touchRegistry);
52
58
53 qApp->processEvents();
59 qApp->processEvents();
@@ -59,6 +65,10 @@ void GestureTest::cleanup()
59 delete m_touchRegistry;
65 delete m_touchRegistry;
60 m_touchRegistry = nullptr;
66 m_touchRegistry = nullptr;
61
67
68+ // TouchRegistry will take down the timer factory along with him
69+ // delete m_fakeTimerFactory;
70+ m_fakeTimerFactory = nullptr;
71+
62 delete m_view;
72 delete m_view;
63 m_view = nullptr;
73 m_view = nullptr;
64}
74}
tests/plugins/Ubuntu/Gestures/GestureTest.h+4
@@ -23,6 +23,9 @@
23class QQuickView;
23class QQuickView;
24class QTouchDevice;
24class QTouchDevice;
25
25
26+namespace UbuntuGestures {
27+ class FakeTimerFactory;
28+}
26class TouchRegistry;
29class TouchRegistry;
27
30
28// C++ std lib
31// C++ std lib
@@ -81,6 +84,7 @@ protected:
81 QTouchDevice *m_device;
84 QTouchDevice *m_device;
82 QQuickView *m_view;
85 QQuickView *m_view;
83 TouchRegistry *m_touchRegistry;
86 TouchRegistry *m_touchRegistry;
87+ UbuntuGestures::FakeTimerFactory *m_fakeTimerFactory;
84 QString m_qmlFilename;
88 QString m_qmlFilename;
85};
89};
86
90
tests/plugins/Ubuntu/Gestures/LeftwardsLauncher.qml+6−19
@@ -21,7 +21,7 @@ import Ubuntu.Components 0.1
21Item {
21Item {
22 id: root
22 id: root
23
23
24− function reset() { launcher.x = root.width }
24+ function reset() { launcher.x = Qt.binding(function(){return root.width;}); }
25
25
26 Rectangle {
26 Rectangle {
27 id: launcher
27 id: launcher
@@ -41,7 +41,8 @@ Item {
41
41
42 Rectangle {
42 Rectangle {
43 id: dragAreaRect
43 id: dragAreaRect
44− opacity: 0.0
44+ opacity: dragArea.dragging ? 0.5 : 0.0
45+ color: "green"
45 anchors.fill: dragArea
46 anchors.fill: dragArea
46 }
47 }
47
48
@@ -52,24 +53,10 @@ Item {
52 width: units.gu(5)
53 width: units.gu(5)
53
54
54 direction: Direction.Leftwards
55 direction: Direction.Leftwards
55− maxDeviation: units.gu(2)
56− wideningAngle: 10
57− distanceThreshold: units.gu(4)
58
56
59− onStatusChanged: {
57+ onDraggingChanged: {
60− switch (status) {
58+ if (dragging) {
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)
59+ launcher.x = Qt.binding(launcher.followDragArea)
68− break;
69− default: //case DirectionalDragArea.Recognized:
70− dragAreaRect.color = "green"
71− dragAreaRect.opacity = 0.5
72− break;
73 }
60 }
74 }
61 }
75
62
tests/plugins/Ubuntu/Gestures/RightwardsLauncher.qml+5−19
@@ -38,7 +38,8 @@ Item {
38
38
39 Rectangle {
39 Rectangle {
40 id: dragAreaRect
40 id: dragAreaRect
41− opacity: 0.0
41+ opacity: dragArea.dragging ? 0.5 : 0.0
42+ color: "green"
42 anchors.fill: dragArea
43 anchors.fill: dragArea
43 }
44 }
44
45
@@ -52,25 +53,10 @@ Item {
52 width: units.gu(5)
53 width: units.gu(5)
53
54
54 direction: Direction.Rightwards
55 direction: Direction.Rightwards
55− maxDeviation: units.gu(2)
56− wideningAngle: 10
57− distanceThreshold: units.gu(4)
58− minSpeed: 50
59
56
60− onStatusChanged: {
57+ onDraggingChanged: {
61− switch (status) {
58+ if (dragging) {
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)
59+ launcher.x = Qt.binding(launcher.followDragArea)
69− break;
70− default: //case DirectionalDragArea.Recognized:
71− dragAreaRect.color = "green"
72− dragAreaRect.opacity = 0.5
73− break;
74 }
60 }
75 }
61 }
76
62
tests/plugins/Ubuntu/Gestures/UpwardsLauncher.qml+6−19
@@ -21,7 +21,7 @@ import Ubuntu.Components 0.1
21Item {
21Item {
22 id: root
22 id: root
23
23
24− function reset() { launcher.y = root.height }
24+ function reset() { launcher.y = Qt.binding(function(){return root.height;}); }
25
25
26 Rectangle {
26 Rectangle {
27 id: launcher
27 id: launcher
@@ -41,7 +41,8 @@ Item {
41
41
42 Rectangle {
42 Rectangle {
43 id: dragAreaRect
43 id: dragAreaRect
44− opacity: 0.0
44+ opacity: dragArea.dragging ? 0.5 : 0.0
45+ color: "green"
45 anchors.fill: dragArea
46 anchors.fill: dragArea
46 }
47 }
47
48
@@ -52,24 +53,10 @@ Item {
52 height: units.gu(5)
53 height: units.gu(5)
53
54
54 direction: Direction.Upwards
55 direction: Direction.Upwards
55− maxDeviation: units.gu(2)
56− wideningAngle: 10
57− distanceThreshold: units.gu(4)
58
56
59− onStatusChanged: {
57+ onDraggingChanged: {
60− switch (status) {
58+ if (dragging) {
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)
59+ launcher.y = Qt.binding(launcher.followDragArea)
68− break;
69− default: //case DirectionalDragArea.Recognized:
70− dragAreaRect.color = "green"
71− dragAreaRect.opacity = 0.5
72− break;
73 }
60 }
74 }
61 }
75
62
tests/plugins/Ubuntu/Gestures/tst_DirectionalDragArea.cpp+480−445
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2013-2014 Canonical, Ltd.
2+ * Copyright (C) 2013-2015 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -20,51 +20,38 @@
20#include <QtQml/QQmlEngine>
20#include <QtQml/QQmlEngine>
21#include <QPointer>
21#include <QPointer>
22#include <private/qquickmousearea_p.h>
22#include <private/qquickmousearea_p.h>
23−#if (QT_VERSION >= QT_VERSION_CHECK(5, 4, 0))
24− #include <private/qquickwindow_p.h>
23+#include <private/qquickwindow_p.h>
25−#endif
26
24
27
25
28#include <DirectionalDragArea.h>
26#include <DirectionalDragArea.h>
27+#include <DirectionalDragArea_p.h>
29#include <TouchRegistry.h>
28#include <TouchRegistry.h>
30
29
31#include "GestureTest.h"
30#include "GestureTest.h"
32
31
33using namespace UbuntuGestures;
32using namespace UbuntuGestures;
34
33
35−
34+// Because QSignalSpy(directionalDragArea, SIGNAL(DirectionalDragArea::Status)) simply
36−class ComplexFakeTimer : public FakeTimer
35+// doesn't work
37−{
36+class StatusSpy : public QObject {
38 Q_OBJECT
37 Q_OBJECT
39public:
38public:
40− ComplexFakeTimer(const SharedTimeSource &timeSource, QObject *parent = 0)
39+ StatusSpy(DirectionalDragArea *edgeDragArea) {
41− : FakeTimer(parent),
40+ m_recognized = false;
42− m_timeSource(timeSource)
41+ connect(edgeDragArea->d, &DirectionalDragAreaPrivate::statusChanged,
43− {}
42+ this, &StatusSpy::onStatusChanged);
44−
45− void start() override {
46− AbstractTimer::start();
47− m_nextTimeoutTime = m_timeSource->msecsSinceReference() + (qint64)interval();
48 }
43 }
49−
44+ bool recognized() {
50− void emitTimeout() {
45+ return m_recognized;
51− m_nextTimeoutTime += interval();
52− Q_EMIT timeout();
53 }
46 }
54
47
55− qint64 nextTimeoutTime() const { return m_nextTimeoutTime; }
48+private Q_SLOTS:
49+ void onStatusChanged(DirectionalDragAreaPrivate::Status status) {
50+ m_recognized |= status == DirectionalDragAreaPrivate::Recognized;
51+ }
56
52
57private:
53private:
58− SharedTimeSource m_timeSource;
54+ bool m_recognized;
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;
68};
55};
69
56
70/*
57/*
@@ -98,15 +85,9 @@ public:
98 tst_DirectionalDragArea();
85 tst_DirectionalDragArea();
99private Q_SLOTS:
86private Q_SLOTS:
100 void init(); // called right before each and every test function is executed
87 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
102
88
103− void edgeDrag();
104− void edgeDrag_data();
105 void dragWithShortDirectionChange();
89 void dragWithShortDirectionChange();
106− void minSpeed();
107− void minSpeed_data();
108 void recognitionTimerUsage();
90 void recognitionTimerUsage();
109− void maxSilenceTime();
110 void sceneXAndX();
91 void sceneXAndX();
111 void sceneYAndY();
92 void sceneYAndY();
112 void twoFingerTap();
93 void twoFingerTap();
@@ -122,14 +103,24 @@ private Q_SLOTS:
122 void immediateRecognitionWhenConstraintsDisabled();
103 void immediateRecognitionWhenConstraintsDisabled();
123 void withdrawTouchOwnershipCandidacyIfDisabledDuringRecognition();
104 void withdrawTouchOwnershipCandidacyIfDisabledDuringRecognition();
124 void withdrawTouchOwnershipCandidacyIfDisabledDuringRecognition_data();
105 void withdrawTouchOwnershipCandidacyIfDisabledDuringRecognition_data();
125− void tappedSignal();
126− void tappedSignal_data();
127 void gettingTouchOwnershipMakesMouseAreaBehindGetCanceled();
106 void gettingTouchOwnershipMakesMouseAreaBehindGetCanceled();
107+ void interleavedTouches();
108+ void makoRightEdgeDrag();
109+ void makoRightEdgeDrag_verticalDownwards();
110+ void makoLeftEdgeDrag_slowStart();
111+ void makoLeftEdgeDrag_movesSlightlyBackwardsOnStart();
128
112
129private:
113private:
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+
130 void passTime(qint64 timeSpanMs);
123 void passTime(qint64 timeSpanMs);
131− ComplexFakeTimer *fakeTimer;
132− QSharedPointer<FakeTimeSource> fakeTimeSource;
133};
124};
134
125
135tst_DirectionalDragArea::tst_DirectionalDragArea()
126tst_DirectionalDragArea::tst_DirectionalDragArea()
@@ -148,185 +139,57 @@ void tst_DirectionalDragArea::init()
148 m_view->resize(m_view->rootObject()->width(), m_view->rootObject()->height());
139 m_view->resize(m_view->rootObject()->width(), m_view->rootObject()->height());
149 QTRY_COMPARE(m_view->width(), (int)m_view->rootObject()->width());
140 QTRY_COMPARE(m_view->width(), (int)m_view->rootObject()->width());
150 QTRY_COMPARE(m_view->height(), (int)m_view->rootObject()->height());
141 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− }
181}
142}
182
143
183−namespace {
144+void tst_DirectionalDragArea::sendTouchPress(qint64 timestamp, int id, QPointF pos)
184−QPointF calculateInitialTouchPos(DirectionalDragArea *edgeDragArea)
185{
145{
186− QPointF localCenter(edgeDragArea->width() / 2., edgeDragArea->height() / 2.);
146+ sendTouch(timestamp, id, pos, Qt::TouchPointPressed, QEvent::TouchBegin);
187− return edgeDragArea->mapToScene(localCenter);
188}
147}
189
148
190−QPointF calculateDirectionVector(DirectionalDragArea *edgeDragArea,
149+void tst_DirectionalDragArea::sendTouchUpdate(qint64 timestamp, int id, QPointF pos)
191− qreal wideningAngleMultiplier)
192{
150{
193− qreal angleRadians = edgeDragArea->wideningAngle() * wideningAngleMultiplier
151+ sendTouch(timestamp, id, pos, Qt::TouchPointMoved, QEvent::TouchUpdate);
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− }
209}
152}
210
153
211−QPointF createTouchDeviation(DirectionalDragArea *edgeDragArea)
154+void tst_DirectionalDragArea::sendTouchRelease(qint64 timestamp, int id, QPointF pos)
212{
155{
213− qreal deviation = edgeDragArea->maxDeviation() * 0.8;
156+ sendTouch(timestamp, id, pos, Qt::TouchPointReleased, QEvent::TouchEnd);
214−
215− if (Direction::isHorizontal(edgeDragArea->direction())) {
216− return QPointF(0, deviation);
217− } else {
218− return QPointF(deviation, 0);
219− }
220−}
221}
157}
222
158
223−void tst_DirectionalDragArea::edgeDrag()
159+void tst_DirectionalDragArea::sendTouch(qint64 timestamp, int id, QPointF pos,
160+ Qt::TouchPointState pointState, QEvent::Type eventType)
224{
161{
225− QFETCH(QString, dragAreaObjectName);
162+ m_fakeTimerFactory->updateTime(timestamp);
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− }
261
163
262− for (int i = 0; i < totalMovementSteps; ++i) {
164+ QTouchEvent::TouchPoint point;
263− touchPoint += touchMovement;
264− passTime(movementTimeStepMs);
265− QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
266− }
267
165
268− if (expectGestureRecognition)
166+ point.setState(pointState);
269− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
167+ point.setId(id);
168+ point.setScenePos(pos);
169+ point.setPos(pos);
270
170
271− if (edgeDragArea->status() == DirectionalDragArea::WaitingForTouch) {
171+ QList<QTouchEvent::TouchPoint> points;
272− QCOMPARE(edgeDragArea->dragging(), false);
172+ points << point;
273− QCOMPARE(draggingSpy.count(), 2);
274− }
275
173
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);
277
176
278− QCOMPARE(draggingSpy.count(), 2);
177+ QQuickWindowPrivate *windowPrivate = QQuickWindowPrivate::get(m_view);
279− QCOMPARE(edgeDragArea->dragging(), false);
178+ windowPrivate->flushDelayedTouchEvent();
280}
179}
281
180
282−void tst_DirectionalDragArea::edgeDrag_data()
181+void tst_DirectionalDragArea::passTime(qint64 timeSpanMs)
283{
182{
284− QTest::addColumn<QString>("dragAreaObjectName");
183+ qint64 finalTime = m_fakeTimerFactory->timeSource()->msecsSinceReference() + timeSpanMs;
285− QTest::addColumn<qreal>("wideningAngleMultiplier");
184+ m_fakeTimerFactory->updateTime(finalTime);
286− QTest::addColumn<qreal>("dragDistanceFactor");
185+}
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;
327
186
328− QTest::newRow("upwards, overly diagonal drag")
187+namespace {
329− << "vnDragArea" << 2.0 << 3.0 << false;
188+QPointF calculateInitialTouchPos(DirectionalDragArea *edgeDragArea)
189+{
190+ QPointF localCenter(edgeDragArea->width() / 2., edgeDragArea->height() / 2.);
191+ return edgeDragArea->mapToScene(localCenter);
192+}
330}
193}
331
194
332/*
195/*
@@ -339,17 +202,17 @@ void tst_DirectionalDragArea::dragWithShortDirectionChange()
339 DirectionalDragArea *edgeDragArea =
202 DirectionalDragArea *edgeDragArea =
340 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
203 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
341 QVERIFY(edgeDragArea != 0);
204 QVERIFY(edgeDragArea != 0);
342− edgeDragArea->setRecognitionTimer(fakeTimer);
205+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
343− edgeDragArea->setTimeSource(fakeTimeSource);
206+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
344
207
345 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
208 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
346 QPointF touchPoint = initialTouchPos;
209 QPointF touchPoint = initialTouchPos;
347
210
348− qreal desiredDragDistance = edgeDragArea->distanceThreshold()*2.0;
211+ qreal desiredDragDistance = edgeDragArea->d->distanceThreshold * 2.0;
349 QPointF dragDirectionVector(1.0, 0.0);
212 QPointF dragDirectionVector(1.0, 0.0);
350− qreal touchStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
213+ qreal touchStepDistance = edgeDragArea->d->distanceThreshold * 0.1f;
351− // make sure we are above minimum speed
214+ // make sure we are above maximum time
352− int touchStepTimeMs = (touchStepDistance / (edgeDragArea->minSpeed() * 5.0f)) * 1000.0f;
215+ int touchStepTimeMs = edgeDragArea->d->maxTime / 20. ;
353 QPointF touchMovement = dragDirectionVector * touchStepDistance;
216 QPointF touchMovement = dragDirectionVector * touchStepDistance;
354
217
355 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
218 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
@@ -373,65 +236,13 @@ void tst_DirectionalDragArea::dragWithShortDirectionChange()
373 passTime(touchStepTimeMs);
236 passTime(touchStepTimeMs);
374 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
237 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
375 } while ((touchPoint - initialTouchPos).manhattanLength() < desiredDragDistance
238 } while ((touchPoint - initialTouchPos).manhattanLength() < desiredDragDistance
376− || fakeTimeSource->m_msecsSinceReference < (edgeDragArea->compositionTime() * 1.5f));
239+ || m_fakeTimerFactory->timeSource()->msecsSinceReference() < (edgeDragArea->d->compositionTime * 1.5f));
377
240
378− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
241+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Recognized);
379
242
380 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
243 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
381}
244}
382
245
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−
435/*
246/*
436 Checks that the recognition timer is started and stopped appropriately.
247 Checks that the recognition timer is started and stopped appropriately.
437 I.e., check that it's running only while gesture recognition is taking place
248 I.e., check that it's running only while gesture recognition is taking place
@@ -442,11 +253,9 @@ void tst_DirectionalDragArea::recognitionTimerUsage()
442 DirectionalDragArea *edgeDragArea =
253 DirectionalDragArea *edgeDragArea =
443 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
254 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
444 QVERIFY(edgeDragArea != 0);
255 QVERIFY(edgeDragArea != 0);
256+ AbstractTimer *fakeTimer = m_fakeTimerFactory->createTimer();
445− edgeDragArea->setRecognitionTimer(fakeTimer);
257+ edgeDragArea->d->setRecognitionTimer(fakeTimer);
446− edgeDragArea->setTimeSource(fakeTimeSource);
258+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
447−
448− // don't let it interfere with our test
449− edgeDragArea->setMinSpeed(0.0);
450
259
451 int timeStepMs = 5; // some arbitrary small value.
260 int timeStepMs = 5; // some arbitrary small value.
452
261
@@ -454,62 +263,31 @@ void tst_DirectionalDragArea::recognitionTimerUsage()
454 QPointF touchPoint = initialTouchPos;
263 QPointF touchPoint = initialTouchPos;
455
264
456 QPointF dragDirectionVector(1.0, 0.0);
265 QPointF dragDirectionVector(1.0, 0.0);
457− QPointF touchMovement = dragDirectionVector * (edgeDragArea->distanceThreshold() * 0.2f);
266+ QPointF touchMovement = dragDirectionVector * (edgeDragArea->d->distanceThreshold * 0.2f);
458
267
459− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
268+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
460 QVERIFY(!fakeTimer->isRunning());
269 QVERIFY(!fakeTimer->isRunning());
461
270
462 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
271 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
463
272
464− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
273+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Undecided);
465 QVERIFY(fakeTimer->isRunning());
274 QVERIFY(fakeTimer->isRunning());
466
275
467 // Move beyond distance threshold and composition time to ensure recognition
276 // Move beyond distance threshold and composition time to ensure recognition
468− while (fakeTimeSource->m_msecsSinceReference <= edgeDragArea->compositionTime() ||
277+ while (m_fakeTimerFactory->timeSource()->msecsSinceReference() <= edgeDragArea->d->compositionTime ||
469− (touchPoint - initialTouchPos).manhattanLength() <= edgeDragArea->distanceThreshold()) {
278+ (touchPoint - initialTouchPos).manhattanLength() <= edgeDragArea->d->distanceThreshold) {
470
279
471− QCOMPARE(edgeDragArea->status() == DirectionalDragArea::Undecided, fakeTimer->isRunning());
280+ QCOMPARE(edgeDragArea->d->status == DirectionalDragAreaPrivate::Undecided, fakeTimer->isRunning());
472
281
473 touchPoint += touchMovement;
282 touchPoint += touchMovement;
474 passTime(timeStepMs);
283 passTime(timeStepMs);
475 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
284 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
476 }
285 }
477
286
478− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
287+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Recognized);
479 QVERIFY(!fakeTimer->isRunning());
288 QVERIFY(!fakeTimer->isRunning());
480}
289}
481
290
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−
513/*
291/*
514 Checks that it informs the X coordinate of the touch point in local and scene coordinates
292 Checks that it informs the X coordinate of the touch point in local and scene coordinates
515 correctly.
293 correctly.
@@ -519,18 +297,19 @@ void tst_DirectionalDragArea::sceneXAndX()
519 DirectionalDragArea *edgeDragArea =
297 DirectionalDragArea *edgeDragArea =
520 m_view->rootObject()->findChild<DirectionalDragArea*>("hnDragArea");
298 m_view->rootObject()->findChild<DirectionalDragArea*>("hnDragArea");
521 QVERIFY(edgeDragArea != 0);
299 QVERIFY(edgeDragArea != 0);
522− edgeDragArea->setRecognitionTimer(fakeTimer);
300+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
523− edgeDragArea->setTimeSource(fakeTimeSource);
301+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
302+ edgeDragArea->setImmediateRecognition(true);
524
303
525 QPointF touchScenePos(m_view->width() - (edgeDragArea->width()/2.0f), m_view->height()/2.0f);
304 QPointF touchScenePos(m_view->width() - (edgeDragArea->width()/2.0f), m_view->height()/2.0f);
526
305
527− QTest::touchEvent(m_view, m_device).press(0, touchScenePos.toPoint());
306+ sendTouchPress(0 /* timestamp */, 0 /* id */, touchScenePos);
528
307
529 QSignalSpy touchXSpy(edgeDragArea, SIGNAL(touchXChanged(qreal)));
308 QSignalSpy touchXSpy(edgeDragArea, SIGNAL(touchXChanged(qreal)));
530 QSignalSpy touchSceneXSpy(edgeDragArea, SIGNAL(touchSceneXChanged(qreal)));
309 QSignalSpy touchSceneXSpy(edgeDragArea, SIGNAL(touchSceneXChanged(qreal)));
531
310
532 touchScenePos.rx() = m_view->width() / 2;
311 touchScenePos.rx() = m_view->width() / 2;
533− QTest::touchEvent(m_view, m_device).move(0, touchScenePos.toPoint());
312+ sendTouchUpdate(50 /* timestamp */, 0 /* id */, touchScenePos);
534
313
535 QCOMPARE(touchXSpy.count(), 1);
314 QCOMPARE(touchXSpy.count(), 1);
536 QCOMPARE(touchSceneXSpy.count(), 1);
315 QCOMPARE(touchSceneXSpy.count(), 1);
@@ -547,18 +326,19 @@ void tst_DirectionalDragArea::sceneYAndY()
547 DirectionalDragArea *edgeDragArea =
326 DirectionalDragArea *edgeDragArea =
548 m_view->rootObject()->findChild<DirectionalDragArea*>("vnDragArea");
327 m_view->rootObject()->findChild<DirectionalDragArea*>("vnDragArea");
549 QVERIFY(edgeDragArea != 0);
328 QVERIFY(edgeDragArea != 0);
550− edgeDragArea->setRecognitionTimer(fakeTimer);
329+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
551− edgeDragArea->setTimeSource(fakeTimeSource);
330+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
331+ edgeDragArea->setImmediateRecognition(true);
552
332
553 QPointF touchScenePos(m_view->width()/2.0f, m_view->height() - (edgeDragArea->height()/2.0f));
333 QPointF touchScenePos(m_view->width()/2.0f, m_view->height() - (edgeDragArea->height()/2.0f));
554
334
555− QTest::touchEvent(m_view, m_device).press(0, touchScenePos.toPoint());
335+ sendTouchPress(0 /* timestamp */, 0 /* id */, touchScenePos);
556
336
557 QSignalSpy touchYSpy(edgeDragArea, SIGNAL(touchYChanged(qreal)));
337 QSignalSpy touchYSpy(edgeDragArea, SIGNAL(touchYChanged(qreal)));
558 QSignalSpy touchSceneYSpy(edgeDragArea, SIGNAL(touchSceneYChanged(qreal)));
338 QSignalSpy touchSceneYSpy(edgeDragArea, SIGNAL(touchSceneYChanged(qreal)));
559
339
560 touchScenePos.ry() = m_view->height() / 2;
340 touchScenePos.ry() = m_view->height() / 2;
561− QTest::touchEvent(m_view, m_device).move(0, touchScenePos.toPoint());
341+ sendTouchUpdate(50 /* timestamp */, 0 /* id */, touchScenePos);
562
342
563 QCOMPARE(touchYSpy.count(), 1);
343 QCOMPARE(touchYSpy.count(), 1);
564 QCOMPARE(touchSceneYSpy.count(), 1);
344 QCOMPARE(touchSceneYSpy.count(), 1);
@@ -577,8 +357,8 @@ void tst_DirectionalDragArea::twoFingerTap()
577 DirectionalDragArea *edgeDragArea =
357 DirectionalDragArea *edgeDragArea =
578 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
358 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
579 QVERIFY(edgeDragArea != 0);
359 QVERIFY(edgeDragArea != 0);
580− edgeDragArea->setRecognitionTimer(fakeTimer);
360+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
581− edgeDragArea->setTimeSource(fakeTimeSource);
361+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
582
362
583 // Make touches evenly spaced along the edgeDragArea
363 // Make touches evenly spaced along the edgeDragArea
584 QPoint touchAPos(edgeDragArea->width()/2.0f, m_view->height()*0.33f);
364 QPoint touchAPos(edgeDragArea->width()/2.0f, m_view->height()*0.33f);
@@ -592,7 +372,7 @@ void tst_DirectionalDragArea::twoFingerTap()
592 QTest::touchEvent(m_view, m_device)
372 QTest::touchEvent(m_view, m_device)
593 .press(0, touchAPos);
373 .press(0, touchAPos);
594
374
595− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
375+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Undecided);
596
376
597 passTime(timeStepMsecs);
377 passTime(timeStepMsecs);
598 QTest::touchEvent(m_view, m_device)
378 QTest::touchEvent(m_view, m_device)
@@ -601,20 +381,20 @@ void tst_DirectionalDragArea::twoFingerTap()
601
381
602 // A second touch point appeared during recognition, reject immediately as this
382 // A second touch point appeared during recognition, reject immediately as this
603 // can't be a single-touch gesture anymore.
383 // 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);
605
385
606 passTime(timeStepMsecs);
386 passTime(timeStepMsecs);
607 QTest::touchEvent(m_view, m_device)
387 QTest::touchEvent(m_view, m_device)
608 .release(0, touchAPos)
388 .release(0, touchAPos)
609 .move(1, touchBPos);
389 .move(1, touchBPos);
610
390
611− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
391+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
612
392
613 passTime(timeStepMsecs);
393 passTime(timeStepMsecs);
614 QTest::touchEvent(m_view, m_device)
394 QTest::touchEvent(m_view, m_device)
615 .release(1, touchBPos);
395 .release(1, touchBPos);
616
396
617− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
397+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
618
398
619 // Perform the second two-finger tap
399 // Perform the second two-finger tap
620
400
@@ -622,27 +402,27 @@ void tst_DirectionalDragArea::twoFingerTap()
622 QTest::touchEvent(m_view, m_device)
402 QTest::touchEvent(m_view, m_device)
623 .press(0, touchAPos);
403 .press(0, touchAPos);
624
404
625− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
405+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Undecided);
626
406
627 passTime(timeStepMsecs);
407 passTime(timeStepMsecs);
628 QTest::touchEvent(m_view, m_device)
408 QTest::touchEvent(m_view, m_device)
629 .move(0, touchAPos)
409 .move(0, touchAPos)
630 .press(1, touchBPos);
410 .press(1, touchBPos);
631
411
632− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
412+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
633
413
634 passTime(timeStepMsecs);
414 passTime(timeStepMsecs);
635 QTest::touchEvent(m_view, m_device)
415 QTest::touchEvent(m_view, m_device)
636 .release(0, touchAPos)
416 .release(0, touchAPos)
637 .move(1, touchBPos);
417 .move(1, touchBPos);
638
418
639− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
419+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
640
420
641 passTime(timeStepMsecs);
421 passTime(timeStepMsecs);
642 QTest::touchEvent(m_view, m_device)
422 QTest::touchEvent(m_view, m_device)
643 .release(1, touchBPos);
423 .release(1, touchBPos);
644
424
645− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
425+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
646}
426}
647
427
648/*
428/*
@@ -659,25 +439,25 @@ void tst_DirectionalDragArea::movingDDA()
659 DirectionalDragArea *edgeDragArea =
439 DirectionalDragArea *edgeDragArea =
660 rightwardsLauncher->findChild<DirectionalDragArea*>("hpDragArea");
440 rightwardsLauncher->findChild<DirectionalDragArea*>("hpDragArea");
661 Q_ASSERT(edgeDragArea != 0);
441 Q_ASSERT(edgeDragArea != 0);
662− edgeDragArea->setRecognitionTimer(fakeTimer);
442+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
663− edgeDragArea->setTimeSource(fakeTimeSource);
443+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
664
444
665 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
445 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
666 QPointF touchPoint = initialTouchPos;
446 QPointF touchPoint = initialTouchPos;
667
447
668− qreal desiredDragDistance = edgeDragArea->distanceThreshold()*2.0f;
448+ qreal desiredDragDistance = edgeDragArea->d->distanceThreshold * 2.0f;
669 QPointF dragDirectionVector(1.0f, 0.0f);
449 QPointF dragDirectionVector(1.0f, 0.0f);
670
450
671− qreal movementStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
451+ qreal movementStepDistance = edgeDragArea->d->distanceThreshold * 0.1f;
672 QPointF touchMovement = dragDirectionVector * movementStepDistance;
452 QPointF touchMovement = dragDirectionVector * movementStepDistance;
673 int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
453 int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
674− int movementTimeStepMs = (edgeDragArea->compositionTime() * 1.5f) / totalMovementSteps;
454+ int movementTimeStepMs = (edgeDragArea->d->compositionTime * 1.5f) / totalMovementSteps;
675
455
676 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
456 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
677
457
678 // Move it far ahead along the direction of the gesture
458 // Move it far ahead along the direction of the gesture
679 // rightwardsLauncher is a parent of our DirectionalDragArea. So moving it will move our DDA
459 // 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);
681
461
682 for (int i = 0; i < totalMovementSteps; ++i) {
462 for (int i = 0; i < totalMovementSteps; ++i) {
683 touchPoint += touchMovement;
463 touchPoint += touchMovement;
@@ -685,7 +465,7 @@ void tst_DirectionalDragArea::movingDDA()
685 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
465 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
686 }
466 }
687
467
688− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
468+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Recognized);
689
469
690 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
470 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
691}
471}
@@ -699,8 +479,8 @@ void tst_DirectionalDragArea::ignoreOldFinger()
699 DirectionalDragArea *edgeDragArea =
479 DirectionalDragArea *edgeDragArea =
700 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
480 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
701 Q_ASSERT(edgeDragArea != 0);
481 Q_ASSERT(edgeDragArea != 0);
702− edgeDragArea->setRecognitionTimer(fakeTimer);
482+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
703− edgeDragArea->setTimeSource(fakeTimeSource);
483+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
704
484
705 // Make touches evenly spaced along the edgeDragArea
485 // Make touches evenly spaced along the edgeDragArea
706 QPoint touch0Pos(edgeDragArea->width()/2.0f, m_view->height()*0.33f);
486 QPoint touch0Pos(edgeDragArea->width()/2.0f, m_view->height()*0.33f);
@@ -708,26 +488,26 @@ void tst_DirectionalDragArea::ignoreOldFinger()
708
488
709 QTest::touchEvent(m_view, m_device).press(0, touch0Pos);
489 QTest::touchEvent(m_view, m_device).press(0, touch0Pos);
710
490
711− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
491+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Undecided);
712
492
713 // leave it lying around for some time
493 // leave it lying around for some time
714− passTime(qMax(edgeDragArea->maxSilenceTime(), edgeDragArea->compositionTime()) * 10);
494+ passTime(edgeDragArea->d->maxTime * 10);
715
495
716− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
496+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
717
497
718− qreal desiredDragDistance = edgeDragArea->distanceThreshold()*2.0f;
498+ qreal desiredDragDistance = edgeDragArea->d->distanceThreshold * 2.0f;
719 QPointF dragDirectionVector(1.0f, 0.0f);
499 QPointF dragDirectionVector(1.0f, 0.0f);
720
500
721− qreal movementStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
501+ qreal movementStepDistance = edgeDragArea->d->distanceThreshold * 0.1f;
722 QPointF touchMovement = dragDirectionVector * movementStepDistance;
502 QPointF touchMovement = dragDirectionVector * movementStepDistance;
723 int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
503 int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
724− int movementTimeStepMs = (edgeDragArea->compositionTime() * 1.5f) / totalMovementSteps;
504+ int movementTimeStepMs = (edgeDragArea->d->compositionTime * 1.5f) / totalMovementSteps;
725
505
726 QTest::touchEvent(m_view, m_device)
506 QTest::touchEvent(m_view, m_device)
727 .move(0, touch0Pos)
507 .move(0, touch0Pos)
728 .press(1, touch1Pos);
508 .press(1, touch1Pos);
729
509
730− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
510+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Undecided);
731
511
732 for (int i = 0; i < totalMovementSteps; ++i) {
512 for (int i = 0; i < totalMovementSteps; ++i) {
733 touch1Pos += touchMovement.toPoint();
513 touch1Pos += touchMovement.toPoint();
@@ -737,13 +517,13 @@ void tst_DirectionalDragArea::ignoreOldFinger()
737 .move(1, touch1Pos);
517 .move(1, touch1Pos);
738 }
518 }
739
519
740− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
520+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Recognized);
741
521
742 QTest::touchEvent(m_view, m_device)
522 QTest::touchEvent(m_view, m_device)
743 .move(0, touch0Pos)
523 .move(0, touch0Pos)
744 .release(1, touch1Pos);
524 .release(1, touch1Pos);
745
525
746− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
526+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
747}
527}
748
528
749/*
529/*
@@ -762,19 +542,19 @@ void tst_DirectionalDragArea::rotated()
762 DirectionalDragArea *edgeDragArea =
542 DirectionalDragArea *edgeDragArea =
763 rightwardsLauncher->findChild<DirectionalDragArea*>("hpDragArea");
543 rightwardsLauncher->findChild<DirectionalDragArea*>("hpDragArea");
764 Q_ASSERT(edgeDragArea != 0);
544 Q_ASSERT(edgeDragArea != 0);
765− edgeDragArea->setRecognitionTimer(fakeTimer);
545+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
766− edgeDragArea->setTimeSource(fakeTimeSource);
546+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
767
547
768 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
548 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
769 QPointF touchPoint = initialTouchPos;
549 QPointF touchPoint = initialTouchPos;
770
550
771− qreal desiredDragDistance = edgeDragArea->distanceThreshold()*2.0f;
551+ qreal desiredDragDistance = edgeDragArea->d->distanceThreshold * 2.0f;
772 QPointF dragDirectionVector(0.0f, 1.0f);
552 QPointF dragDirectionVector(0.0f, 1.0f);
773
553
774− qreal movementStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
554+ qreal movementStepDistance = edgeDragArea->d->distanceThreshold * 0.1f;
775 QPointF touchMovement = dragDirectionVector * movementStepDistance;
555 QPointF touchMovement = dragDirectionVector * movementStepDistance;
776 int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
556 int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
777− int movementTimeStepMs = (edgeDragArea->compositionTime() * 1.5f) / totalMovementSteps;
557+ int movementTimeStepMs = (edgeDragArea->d->compositionTime * 1.5f) / totalMovementSteps;
778
558
779 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
559 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
780
560
@@ -784,7 +564,7 @@ void tst_DirectionalDragArea::rotated()
784 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
564 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
785 }
565 }
786
566
787− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
567+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Recognized);
788
568
789 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
569 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
790}
570}
@@ -802,31 +582,30 @@ void tst_DirectionalDragArea::sceneDistance()
802 DirectionalDragArea *edgeDragArea =
582 DirectionalDragArea *edgeDragArea =
803 rightwardsLauncher->findChild<DirectionalDragArea*>("hpDragArea");
583 rightwardsLauncher->findChild<DirectionalDragArea*>("hpDragArea");
804 Q_ASSERT(edgeDragArea != 0);
584 Q_ASSERT(edgeDragArea != 0);
805− edgeDragArea->setRecognitionTimer(fakeTimer);
585+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
806− edgeDragArea->setTimeSource(fakeTimeSource);
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);
807
590
808 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
591 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
809 QPointF touchPoint = initialTouchPos;
592 QPointF touchPoint = initialTouchPos;
810
593
811− qreal desiredDragDistance = edgeDragArea->distanceThreshold()*2.0f;
594+ qreal desiredDragDistance = edgeDragArea->d->distanceThreshold * 2.0f;
812
595
813− qreal movementStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
596+ qreal movementStepDistance = edgeDragArea->d->distanceThreshold * 0.1f;
814 QPointF touchMovement = dragDirectionVector * movementStepDistance;
597 QPointF touchMovement = dragDirectionVector * movementStepDistance;
815 int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
598 int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
816− int movementTimeStepMs = (edgeDragArea->compositionTime() * 1.5f) / totalMovementSteps;
599+ int movementTimeStepMs = (edgeDragArea->d->compositionTime * 1.5f) / totalMovementSteps;
817
600
818− QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
601+ qint64 timestamp = 0;
602+
603+ sendTouchPress(timestamp, 0, touchPoint);
819
604
820−#if (QT_VERSION >= QT_VERSION_CHECK(5, 4, 0))
821− QQuickWindowPrivate *wp = QQuickWindowPrivate::get(m_view);
822−#endif
823 for (int i = 0; i < totalMovementSteps; ++i) {
605 for (int i = 0; i < totalMovementSteps; ++i) {
824 touchPoint += touchMovement;
606 touchPoint += touchMovement;
825− passTime(movementTimeStepMs);
607+ timestamp += movementTimeStepMs;
826− QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
608+ sendTouchUpdate(timestamp, 0, touchPoint);
827−#if (QT_VERSION >= QT_VERSION_CHECK(5, 4, 0))
828− wp->flushDelayedTouchEvent();
829−#endif
830 }
609 }
831
610
832 qreal actualDragDistance = ((qreal)totalMovementSteps) * movementStepDistance;
611 qreal actualDragDistance = ((qreal)totalMovementSteps) * movementStepDistance;
@@ -836,7 +615,8 @@ void tst_DirectionalDragArea::sceneDistance()
836 // NB: qFuzzyCompare(), used internally by QCOMPARE(), is broken.
615 // NB: qFuzzyCompare(), used internally by QCOMPARE(), is broken.
837 QVERIFY(qAbs(edgeDragArea->sceneDistance() - actualDragDistance) < 0.001);
616 QVERIFY(qAbs(edgeDragArea->sceneDistance() - actualDragDistance) < 0.001);
838
617
839− QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
618+ timestamp += movementTimeStepMs;
619+ sendTouchRelease(timestamp, 0, touchPoint);
840}
620}
841
621
842void tst_DirectionalDragArea::sceneDistance_data()
622void tst_DirectionalDragArea::sceneDistance_data()
@@ -860,18 +640,18 @@ void tst_DirectionalDragArea::disabledWhileDragging()
860 DirectionalDragArea *edgeDragArea =
640 DirectionalDragArea *edgeDragArea =
861 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
641 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
862 Q_ASSERT(edgeDragArea != 0);
642 Q_ASSERT(edgeDragArea != 0);
863− edgeDragArea->setRecognitionTimer(fakeTimer);
643+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
864− edgeDragArea->setTimeSource(fakeTimeSource);
644+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
865
645
866 QPointF touchPoint = calculateInitialTouchPos(edgeDragArea);
646 QPointF touchPoint = calculateInitialTouchPos(edgeDragArea);
867
647
868− qreal desiredDragDistance = edgeDragArea->distanceThreshold()*2.0f;
648+ qreal desiredDragDistance = edgeDragArea->d->distanceThreshold * 2.0f;
869 QPointF dragDirectionVector(1., 0.); // horizontal positive
649 QPointF dragDirectionVector(1., 0.); // horizontal positive
870
650
871− qreal movementStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
651+ qreal movementStepDistance = edgeDragArea->d->distanceThreshold * 0.1f;
872 QPointF touchMovement = dragDirectionVector * movementStepDistance;
652 QPointF touchMovement = dragDirectionVector * movementStepDistance;
873 int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
653 int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
874− int movementTimeStepMs = (edgeDragArea->compositionTime() * 1.5f) / totalMovementSteps;
654+ int movementTimeStepMs = (edgeDragArea->d->compositionTime * 1.5f) / totalMovementSteps;
875
655
876 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
656 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
877
657
@@ -881,13 +661,13 @@ void tst_DirectionalDragArea::disabledWhileDragging()
881 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
661 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
882 }
662 }
883
663
884− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
664+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Recognized);
885 QCOMPARE(edgeDragArea->dragging(), true);
665 QCOMPARE(edgeDragArea->dragging(), true);
886
666
887 // disable the dragArea while it's being dragged.
667 // disable the dragArea while it's being dragged.
888 edgeDragArea->setEnabled(false);
668 edgeDragArea->setEnabled(false);
889
669
890− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
670+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
891 QCOMPARE(edgeDragArea->dragging(), false);
671 QCOMPARE(edgeDragArea->dragging(), false);
892
672
893 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
673 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
@@ -898,15 +678,14 @@ void tst_DirectionalDragArea::oneFingerDownFollowedByLateSecondFingerDown()
898 DirectionalDragArea *edgeDragArea =
678 DirectionalDragArea *edgeDragArea =
899 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
679 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
900 Q_ASSERT(edgeDragArea != 0);
680 Q_ASSERT(edgeDragArea != 0);
901− edgeDragArea->setRecognitionTimer(fakeTimer);
681+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
902− edgeDragArea->setTimeSource(fakeTimeSource);
682+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
903
683
904 // Disable some constraints we're not interested in
684 // Disable some constraints we're not interested in
905− edgeDragArea->setMaxSilenceTime(60 * 1000);
685+ edgeDragArea->d->setMaxTime(60 * 1000);
906− edgeDragArea->setMinSpeed(0);
907
686
908 // And ensure others have the values we want
687 // And ensure others have the values we want
909− edgeDragArea->setCompositionTime(60);
688+ edgeDragArea->d->compositionTime = 60;
910
689
911 // Put an item right behind edgeDragArea to receive the touches ignored by it
690 // Put an item right behind edgeDragArea to receive the touches ignored by it
912 DummyItem *dummyItem = new DummyItem;
691 DummyItem *dummyItem = new DummyItem;
@@ -923,10 +702,10 @@ void tst_DirectionalDragArea::oneFingerDownFollowedByLateSecondFingerDown()
923
702
924 QTest::touchEvent(m_view, m_device).press(0, touch0Pos);
703 QTest::touchEvent(m_view, m_device).press(0, touch0Pos);
925
704
926− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
705+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Undecided);
927
706
928 // We are now going to be way beyond compositionTime
707 // We are now going to be way beyond compositionTime
929− passTime(edgeDragArea->compositionTime()*3);
708+ passTime(edgeDragArea->d->compositionTime*3);
930
709
931 QTest::touchEvent(m_view, m_device)
710 QTest::touchEvent(m_view, m_device)
932 .move(0, touch0Pos)
711 .move(0, touch0Pos)
@@ -948,7 +727,7 @@ void tst_DirectionalDragArea::oneFingerDownFollowedByLateSecondFingerDown()
948 .move(0, touch0Pos)
727 .move(0, touch0Pos)
949 .move(1, touch1Pos);
728 .move(1, touch1Pos);
950
729
951− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
730+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Undecided);
952
731
953 passTime(5);
732 passTime(5);
954
733
@@ -956,7 +735,7 @@ void tst_DirectionalDragArea::oneFingerDownFollowedByLateSecondFingerDown()
956 .release(0, touch0Pos)
735 .release(0, touch0Pos)
957 .move(1, touch1Pos);
736 .move(1, touch1Pos);
958
737
959− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
738+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
960
739
961 passTime(5);
740 passTime(5);
962
741
@@ -964,7 +743,7 @@ void tst_DirectionalDragArea::oneFingerDownFollowedByLateSecondFingerDown()
964 .release(1, touch1Pos);
743 .release(1, touch1Pos);
965
744
966 // Shouldn't be keepping info about touches that no longer exist or interest us
745 // 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());
968
747
969 delete dummyItem;
748 delete dummyItem;
970}
749}
@@ -974,12 +753,11 @@ void tst_DirectionalDragArea::givesUpWhenLosesTouch()
974 DirectionalDragArea *edgeDragArea =
753 DirectionalDragArea *edgeDragArea =
975 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
754 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
976 Q_ASSERT(edgeDragArea != 0);
755 Q_ASSERT(edgeDragArea != 0);
977− edgeDragArea->setRecognitionTimer(fakeTimer);
756+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
978− edgeDragArea->setTimeSource(fakeTimeSource);
757+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
979
758
980 // Disable some constraints we're not interested in
759 // Disable some constraints we're not interested in
981− edgeDragArea->setMaxSilenceTime(60 * 1000);
760+ edgeDragArea->d->setMaxTime(60 * 1000);
982− edgeDragArea->setMinSpeed(0);
983
761
984 // Put an item right in front of edgeDragArea
762 // Put an item right in front of edgeDragArea
985 DummyItem *dummyItem = new DummyItem(edgeDragArea->parentItem());
763 DummyItem *dummyItem = new DummyItem(edgeDragArea->parentItem());
@@ -998,11 +776,11 @@ void tst_DirectionalDragArea::givesUpWhenLosesTouch()
998
776
999 QTest::touchEvent(m_view, m_device).press(0, touchPos);
777 QTest::touchEvent(m_view, m_device).press(0, touchPos);
1000
778
1001− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
779+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Undecided);
1002
780
1003 m_touchRegistry->requestTouchOwnership(0, dummyItem);
781 m_touchRegistry->requestTouchOwnership(0, dummyItem);
1004
782
1005− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
783+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
1006
784
1007 dummyItem->grabTouchPoints({0});
785 dummyItem->grabTouchPoints({0});
1008 dummyItem->touchEventHandler = [&](QTouchEvent *event) { event->accept(); };
786 dummyItem->touchEventHandler = [&](QTouchEvent *event) { event->accept(); };
@@ -1010,9 +788,9 @@ void tst_DirectionalDragArea::givesUpWhenLosesTouch()
1010 passTime(5);
788 passTime(5);
1011 QTest::touchEvent(m_view, m_device).release(0, touchPos);
789 QTest::touchEvent(m_view, m_device).release(0, touchPos);
1012
790
1013− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
791+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
1014
792
1015− QVERIFY(edgeDragArea->m_activeTouches.isEmpty());
793+ QVERIFY(edgeDragArea->d->activeTouches.isEmpty());
1016}
794}
1017
795
1018void tst_DirectionalDragArea::threeFingerDrag()
796void tst_DirectionalDragArea::threeFingerDrag()
@@ -1020,15 +798,14 @@ void tst_DirectionalDragArea::threeFingerDrag()
1020 DirectionalDragArea *edgeDragArea =
798 DirectionalDragArea *edgeDragArea =
1021 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
799 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
1022 Q_ASSERT(edgeDragArea != 0);
800 Q_ASSERT(edgeDragArea != 0);
1023− edgeDragArea->setRecognitionTimer(fakeTimer);
801+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
1024− edgeDragArea->setTimeSource(fakeTimeSource);
802+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
1025
803
1026 // Disable some constraints we're not interested in
804 // Disable some constraints we're not interested in
1027− edgeDragArea->setMaxSilenceTime(60 * 1000);
805+ edgeDragArea->d->setMaxTime(60 * 1000);
1028− edgeDragArea->setMinSpeed(0);
1029
806
1030 // And ensure others have the values we want
807 // And ensure others have the values we want
1031− edgeDragArea->setCompositionTime(60);
808+ edgeDragArea->d->compositionTime = 60;
1032
809
1033 // Make touches evenly spaced along the edgeDragArea
810 // Make touches evenly spaced along the edgeDragArea
1034 QPoint touch0Pos(edgeDragArea->width()/2.0f, m_view->height()*0.25f);
811 QPoint touch0Pos(edgeDragArea->width()/2.0f, m_view->height()*0.25f);
@@ -1038,14 +815,14 @@ void tst_DirectionalDragArea::threeFingerDrag()
1038 QTest::touchEvent(m_view, m_device)
815 QTest::touchEvent(m_view, m_device)
1039 .press(0, touch0Pos);
816 .press(0, touch0Pos);
1040
817
1041− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
818+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Undecided);
1042
819
1043 passTime(5);
820 passTime(5);
1044 QTest::touchEvent(m_view, m_device)
821 QTest::touchEvent(m_view, m_device)
1045 .move(0, touch0Pos)
822 .move(0, touch0Pos)
1046 .press(1, touch1Pos);
823 .press(1, touch1Pos);
1047
824
1048− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
825+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
1049
826
1050 passTime(5);
827 passTime(5);
1051 QTest::touchEvent(m_view, m_device)
828 QTest::touchEvent(m_view, m_device)
@@ -1053,7 +830,7 @@ void tst_DirectionalDragArea::threeFingerDrag()
1053 .move(1, touch1Pos)
830 .move(1, touch1Pos)
1054 .press(2, touch2Pos);
831 .press(2, touch2Pos);
1055
832
1056− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
833+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
1057
834
1058 passTime(10);
835 passTime(10);
1059 QTest::touchEvent(m_view, m_device)
836 QTest::touchEvent(m_view, m_device)
@@ -1077,7 +854,7 @@ void tst_DirectionalDragArea::threeFingerDrag()
1077 .release(0, touch0Pos);
854 .release(0, touch0Pos);
1078
855
1079 // Shouldn't be keepping info about touches that no longer exist or interest us
856 // 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());
1081}
858}
1082
859
1083/*
860/*
@@ -1090,12 +867,12 @@ void tst_DirectionalDragArea::immediateRecognitionWhenConstraintsDisabled()
1090 DirectionalDragArea *edgeDragArea =
867 DirectionalDragArea *edgeDragArea =
1091 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
868 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
1092 Q_ASSERT(edgeDragArea != 0);
869 Q_ASSERT(edgeDragArea != 0);
1093− edgeDragArea->setRecognitionTimer(fakeTimer);
870+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
1094− edgeDragArea->setTimeSource(fakeTimeSource);
871+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
1095
872
1096 // Disable the minimum amount of constraints to ensure immediate recognition
873 // Disable the minimum amount of constraints to ensure immediate recognition
1097− edgeDragArea->setDistanceThreshold(0);
874+ edgeDragArea->d->setDistanceThreshold(0);
1098− edgeDragArea->setCompositionTime(0);
875+ edgeDragArea->d->compositionTime = 0;
1099
876
1100 // Put an item right behind edgeDragArea to receive the touches ignored by it
877 // Put an item right behind edgeDragArea to receive the touches ignored by it
1101 DummyItem *dummyItem = new DummyItem;
878 DummyItem *dummyItem = new DummyItem;
@@ -1111,7 +888,7 @@ void tst_DirectionalDragArea::immediateRecognitionWhenConstraintsDisabled()
1111 QTest::touchEvent(m_view, m_device).press(0, touch0Pos);
888 QTest::touchEvent(m_view, m_device).press(0, touch0Pos);
1112
889
1113 // check for immediate recognition
890 // check for immediate recognition
1114− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
891+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Recognized);
1115
892
1116 // and therefore it should have immediately grabbed the touch point,
893 // and therefore it should have immediately grabbed the touch point,
1117 // not letting it leak to items behind him.
894 // not letting it leak to items behind him.
@@ -1123,19 +900,19 @@ void tst_DirectionalDragArea::withdrawTouchOwnershipCandidacyIfDisabledDuringRec
1123 DirectionalDragArea *edgeDragArea =
900 DirectionalDragArea *edgeDragArea =
1124 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
901 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
1125 Q_ASSERT(edgeDragArea != 0);
902 Q_ASSERT(edgeDragArea != 0);
1126− edgeDragArea->setRecognitionTimer(fakeTimer);
903+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
1127− edgeDragArea->setTimeSource(fakeTimeSource);
904+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
1128
905
1129 QPointF touchPoint = calculateInitialTouchPos(edgeDragArea);
906 QPointF touchPoint = calculateInitialTouchPos(edgeDragArea);
1130
907
1131 // Move less than the minimum needed for the drag gesture recognition
908 // 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;
1133 QPointF dragDirectionVector(1., 0.); // horizontal positive
910 QPointF dragDirectionVector(1., 0.); // horizontal positive
1134
911
1135− qreal movementStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
912+ qreal movementStepDistance = edgeDragArea->d->distanceThreshold * 0.1f;
1136 QPointF touchMovement = dragDirectionVector * movementStepDistance;
913 QPointF touchMovement = dragDirectionVector * movementStepDistance;
1137 int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
914 int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
1138− int movementTimeStepMs = (edgeDragArea->compositionTime() * 0.8f) / totalMovementSteps;
915+ int movementTimeStepMs = (edgeDragArea->d->compositionTime * 0.8f) / totalMovementSteps;
1139
916
1140 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
917 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
1141
918
@@ -1145,14 +922,14 @@ void tst_DirectionalDragArea::withdrawTouchOwnershipCandidacyIfDisabledDuringRec
1145 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
922 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
1146 }
923 }
1147
924
1148− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
925+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Undecided);
1149
926
1150 // edgeDragArea should be an undecided candidate
927 // edgeDragArea should be an undecided candidate
1151 {
928 {
1152 auto touchInfo = m_touchRegistry->findTouchInfo(0);
929 auto touchInfo = m_touchRegistry->findTouchInfo(0);
1153 QCOMPARE(touchInfo->candidates.size(), 1);
930 QCOMPARE(touchInfo->candidates.size(), 1);
1154 QCOMPARE(touchInfo->candidates.at(0).item.data(), edgeDragArea);
931 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);
1156 }
933 }
1157
934
1158 // disable the dragArea while it's still recognizing a possible drag gesture.
935 // disable the dragArea while it's still recognizing a possible drag gesture.
@@ -1169,7 +946,7 @@ void tst_DirectionalDragArea::withdrawTouchOwnershipCandidacyIfDisabledDuringRec
1169 QCOMPARE(touchInfo->candidates.size(), 0);
946 QCOMPARE(touchInfo->candidates.size(), 0);
1170 }
947 }
1171
948
1172− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
949+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
1173
950
1174 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
951 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
1175}
952}
@@ -1182,47 +959,13 @@ void tst_DirectionalDragArea::withdrawTouchOwnershipCandidacyIfDisabledDuringRec
1182 QTest::newRow("invisible") << false;
959 QTest::newRow("invisible") << false;
1183}
960}
1184
961
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−
1219void tst_DirectionalDragArea::gettingTouchOwnershipMakesMouseAreaBehindGetCanceled()
962void tst_DirectionalDragArea::gettingTouchOwnershipMakesMouseAreaBehindGetCanceled()
1220{
963{
1221 DirectionalDragArea *edgeDragArea =
964 DirectionalDragArea *edgeDragArea =
1222 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
965 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
1223 QVERIFY(edgeDragArea != nullptr);
966 QVERIFY(edgeDragArea != nullptr);
1224− edgeDragArea->setRecognitionTimer(fakeTimer);
967+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
1225− edgeDragArea->setTimeSource(fakeTimeSource);
968+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
1226
969
1227 QQuickMouseArea *mouseArea =
970 QQuickMouseArea *mouseArea =
1228 m_view->rootObject()->findChild<QQuickMouseArea*>("mouseArea");
971 m_view->rootObject()->findChild<QQuickMouseArea*>("mouseArea");
@@ -1233,12 +976,12 @@ void tst_DirectionalDragArea::gettingTouchOwnershipMakesMouseAreaBehindGetCancel
1233 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
976 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
1234 QPointF touchPoint = initialTouchPos;
977 QPointF touchPoint = initialTouchPos;
1235
978
1236− qreal desiredDragDistance = edgeDragArea->distanceThreshold()*2;
979+ qreal desiredDragDistance = edgeDragArea->d->distanceThreshold * 2;
1237 QPointF dragDirectionVector(1.0f, 0.0f); // rightwards
980 QPointF dragDirectionVector(1.0f, 0.0f); // rightwards
1238− qreal movementStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
981+ qreal movementStepDistance = edgeDragArea->d->distanceThreshold * 0.1f;
1239 QPointF touchMovement = dragDirectionVector * movementStepDistance;
982 QPointF touchMovement = dragDirectionVector * movementStepDistance;
1240 int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
983 int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
1241− int movementTimeStepMs = (edgeDragArea->compositionTime() * 1.5f) / totalMovementSteps;
984+ int movementTimeStepMs = (edgeDragArea->d->compositionTime * 1.5f) / totalMovementSteps;
1242
985
1243 QCOMPARE(mouseArea->pressed(), false);
986 QCOMPARE(mouseArea->pressed(), false);
1244
987
@@ -1259,13 +1002,305 @@ void tst_DirectionalDragArea::gettingTouchOwnershipMakesMouseAreaBehindGetCancel
1259 // As the DirectionalDragArea recognizes the gesture, it grabs the touch from the MouseArea,
1002 // As the DirectionalDragArea recognizes the gesture, it grabs the touch from the MouseArea,
1260 // which should make the MouseArea get a cancelation event, which will then cause it to
1003 // which should make the MouseArea get a cancelation event, which will then cause it to
1261 // reset its state (going back to "unpressed"/"released").
1004 // 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);
1263 QCOMPARE(mouseArea->pressed(), false);
1006 QCOMPARE(mouseArea->pressed(), false);
1264 QCOMPARE(mouseAreaSpy.canceledCount, 1);
1007 QCOMPARE(mouseAreaSpy.canceledCount, 1);
1265
1008
1266 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
1009 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
1267}
1010}
1268
1011
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+
1269QTEST_MAIN(tst_DirectionalDragArea)
1304QTEST_MAIN(tst_DirectionalDragArea)
1270
1305
1271#include "tst_DirectionalDragArea.moc"
1306#include "tst_DirectionalDragArea.moc"
tests/plugins/Ubuntu/Gestures/tst_DirectionalDragArea.qml+8−1
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013,2015 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -16,12 +16,19 @@
16
16
17import QtQuick 2.0
17import QtQuick 2.0
18import Ubuntu.Components 0.1
18import Ubuntu.Components 0.1
19+import Unity.Test 0.1
19
20
20Rectangle {
21Rectangle {
21 width: units.gu(60)
22 width: units.gu(60)
22 height: units.gu(60)
23 height: units.gu(60)
23 color: "white"
24 color: "white"
24
25
26+ Binding {
27+ target: MouseTouchAdaptor
28+ property: "enabled"
29+ value: true
30+ }
31+
25 MouseArea {
32 MouseArea {
26 id: mouseArea
33 id: mouseArea
27 objectName: "mouseArea"
34 objectName: "mouseArea"
tests/plugins/Ubuntu/Gestures/tst_TouchDispatcher.cpp+5−6
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2014 Canonical, Ltd.
2+ * Copyright (C) 2014-2015 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -81,8 +81,7 @@ void tst_TouchDispatcher::sendMouseEventIfTouchIgnored()
81 event->accept();
81 event->accept();
82 };
82 };
83
83
84− touchDispatcher.dispatch(QEvent::TouchBegin, m_device, Qt::NoModifier, touchPoints, m_view,
84+ touchDispatcher.dispatch(m_device, Qt::NoModifier, touchPoints, m_view, 12345 /* timestamp */);
85− 12345 /* timestamp */);
86
85
87 QVERIFY(gotTouchEvent);
86 QVERIFY(gotTouchEvent);
88 QCOMPARE(gotMousePressEvent, shouldGetMousePress);
87 QCOMPARE(gotMousePressEvent, shouldGetMousePress);
@@ -128,18 +127,18 @@ void tst_TouchDispatcher::mouseDoubleClick()
128 }
127 }
129 ulong timestamp = 12345;
128 ulong timestamp = 12345;
130
129
131− touchDispatcher.dispatch(QEvent::TouchBegin, m_device, Qt::NoModifier, touchPoints, m_view, timestamp);
130+ touchDispatcher.dispatch(m_device, Qt::NoModifier, touchPoints, m_view, timestamp);
132
131
133 touchPoints[0].setState(Qt::TouchPointReleased);
132 touchPoints[0].setState(Qt::TouchPointReleased);
134 timestamp += doubleClickInterval / 10;
133 timestamp += doubleClickInterval / 10;
135
134
136− touchDispatcher.dispatch(QEvent::TouchEnd, m_device, Qt::NoModifier, touchPoints, m_view, timestamp);
135+ touchDispatcher.dispatch(m_device, Qt::NoModifier, touchPoints, m_view, timestamp);
137
136
138 touchPoints[0].setId(1);
137 touchPoints[0].setId(1);
139 touchPoints[0].setState(Qt::TouchPointPressed);
138 touchPoints[0].setState(Qt::TouchPointPressed);
140 timestamp += timeBetweenClicks;
139 timestamp += timeBetweenClicks;
141
140
142− touchDispatcher.dispatch(QEvent::TouchBegin, m_device, Qt::NoModifier, touchPoints, m_view, timestamp);
141+ touchDispatcher.dispatch(m_device, Qt::NoModifier, touchPoints, m_view, timestamp);
143
142
144 QCOMPARE(gotDoubleClickEvent, shouldSendDoubleClick);
143 QCOMPARE(gotDoubleClickEvent, shouldSendDoubleClick);
145}
144}
tests/plugins/Ubuntu/Gestures/tst_TouchGate.cpp+56−1
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2014 Canonical, Ltd.
2+ * Copyright (C) 2014-2015 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -63,6 +63,7 @@ private Q_SLOTS:
63 void init(); // called right before each and every test function is executed
63 void init(); // called right before each and every test function is executed
64 void cleanup(); // called right after each and every test function is executed
64 void cleanup(); // called right after each and every test function is executed
65
65
66+private:
66 void onlyCandidate_passThrough();
67 void onlyCandidate_passThrough();
67 void holdsEventsUntilGainsOwnership();
68 void holdsEventsUntilGainsOwnership();
68 void holdsEventsUntilGainsOwnership_data();
69 void holdsEventsUntilGainsOwnership_data();
@@ -70,6 +71,9 @@ private Q_SLOTS:
70 void dontDispacthToDisabledOrInvisibleTarget();
71 void dontDispacthToDisabledOrInvisibleTarget();
71 void dontDispacthToDisabledOrInvisibleTarget_data();
72 void dontDispacthToDisabledOrInvisibleTarget_data();
72
73
74+private Q_SLOTS:
75+ void disabledWhileHoldingTouch();
76+
73private:
77private:
74 QQuickView *createView();
78 QQuickView *createView();
75 TouchRegistry *touchRegistry;
79 TouchRegistry *touchRegistry;
@@ -326,6 +330,57 @@ void tst_TouchGate::dontDispacthToDisabledOrInvisibleTarget_data()
326 QTest::newRow("disabled invisible") << false << false;
330 QTest::newRow("disabled invisible") << false << false;
327}
331}
328
332
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+
329///////////// TestItem /////////////////////////////////////////////////////////////
384///////////// TestItem /////////////////////////////////////////////////////////////
330
385
331void TestItem::touchEvent(QTouchEvent *event)
386void TestItem::touchEvent(QTouchEvent *event)
tests/qmltests/Components/CMakeLists.txt+3−1
@@ -5,6 +5,7 @@ include_directories(
5 ${CMAKE_SOURCE_DIR}/libs/UbuntuGestures
5 ${CMAKE_SOURCE_DIR}/libs/UbuntuGestures
6 ${CMAKE_CURRENT_BINARY_DIR}
6 ${CMAKE_CURRENT_BINARY_DIR}
7 ${Qt5Gui_PRIVATE_INCLUDE_DIRS}
7 ${Qt5Gui_PRIVATE_INCLUDE_DIRS}
8+ ${Qt5Quick_PRIVATE_INCLUDE_DIRS}
8 )
9 )
9
10
10add_executable(DragHandleTestExec tst_DragHandle.cpp)
11add_executable(DragHandleTestExec tst_DragHandle.cpp)
@@ -12,7 +13,8 @@ qt5_use_modules(DragHandleTestExec Test Core Qml Gui Quick)
12target_link_libraries(DragHandleTestExec UbuntuGesturesQml UbuntuGestures)
13target_link_libraries(DragHandleTestExec UbuntuGesturesQml UbuntuGestures)
13
14
14add_binary_qml_test(DragHandle ${CMAKE_BINARY_DIR}/plugins/Ubuntu/Gestures "" "")
15add_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)
16
17
17add_definitions(-DUBUNTU_GESTURES_PLUGIN_DIR="${CMAKE_BINARY_DIR}/plugins")
18add_definitions(-DUBUNTU_GESTURES_PLUGIN_DIR="${CMAKE_BINARY_DIR}/plugins")
18add_definitions(-DTEST_DIR="${CMAKE_CURRENT_LIST_DIR}")
19add_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 @@
20#include <qpa/qwindowsysteminterface.h>
20#include <qpa/qwindowsysteminterface.h>
21#include <QtQuick/QQuickView>
21#include <QtQuick/QQuickView>
22#include <QtQml/QQmlEngine>
22#include <QtQml/QQmlEngine>
23+#include <private/qquickwindow_p.h>
23
24
25+#include <AxisVelocityCalculator.h>
24#include <DirectionalDragArea.h>
26#include <DirectionalDragArea.h>
27+#include <DirectionalDragArea_p.h>
25#include <Timer.h>
28#include <Timer.h>
26#include <TouchRegistry.h>
29#include <TouchRegistry.h>
27
30
28using namespace UbuntuGestures;
31using namespace UbuntuGestures;
29
32
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−
38class tst_DragHandle: public QObject
33class tst_DragHandle: public QObject
39{
34{
40 Q_OBJECT
35 Q_OBJECT
@@ -56,7 +51,6 @@ private Q_SLOTS:
56 void hintingAnimation();
51 void hintingAnimation();
57 void hintingAnimation_dontRestartAfterFinishedAndStillPressed();
52 void hintingAnimation_dontRestartAfterFinishedAndStillPressed();
58
53
59−
60private:
54private:
61 void flickAndHold(DirectionalDragArea *dragHandle, qreal distance);
55 void flickAndHold(DirectionalDragArea *dragHandle, qreal distance);
62 void drag(QPointF &touchPoint, const QPointF& direction, qreal distance,
56 void drag(QPointF &touchPoint, const QPointF& direction, qreal distance,
@@ -105,8 +99,8 @@ void tst_DragHandle::init()
105 QQuickItem *controls = m_view->rootObject()->findChild<QQuickItem*>("controls");
99 QQuickItem *controls = m_view->rootObject()->findChild<QQuickItem*>("controls");
106 controls->setVisible(false);
100 controls->setVisible(false);
107
101
108− m_fakeTimer = new FakeTimer;
109 m_fakeTimeSource.reset(new FakeTimeSource);
102 m_fakeTimeSource.reset(new FakeTimeSource);
103+ m_fakeTimer = new FakeTimer(m_fakeTimeSource);
110}
104}
111
105
112void tst_DragHandle::cleanup()
106void tst_DragHandle::cleanup()
@@ -129,6 +123,7 @@ QQuickView *tst_DragHandle::createView()
129 window->setResizeMode(QQuickView::SizeRootObjectToView);
123 window->setResizeMode(QQuickView::SizeRootObjectToView);
130 window->engine()->addImportPath(QLatin1String(UBUNTU_GESTURES_PLUGIN_DIR));
124 window->engine()->addImportPath(QLatin1String(UBUNTU_GESTURES_PLUGIN_DIR));
131 window->engine()->addImportPath(QLatin1String(TEST_DIR));
125 window->engine()->addImportPath(QLatin1String(TEST_DIR));
126+ window->engine()->addImportPath(QStringLiteral(TESTS_UTILS_MODULES_DIR));
132
127
133 return window;
128 return window;
134}
129}
@@ -190,17 +185,24 @@ void tst_DragHandle::flickAndHold(DirectionalDragArea *dragHandle,
190 // speed.
185 // speed.
191 m_fakeTimeSource->m_msecsSinceReference += 5000;
186 m_fakeTimeSource->m_msecsSinceReference += 5000;
192 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
187 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
188+
189+ QQuickWindowPrivate *windowPrivate = QQuickWindowPrivate::get(m_view);
190+ windowPrivate->flushDelayedTouchEvent();
193}
191}
194
192
195void tst_DragHandle::drag(QPointF &touchPoint, const QPointF& direction, qreal distance,
193void tst_DragHandle::drag(QPointF &touchPoint, const QPointF& direction, qreal distance,
196 int numSteps, qint64 timeMs)
194 int numSteps, qint64 timeMs)
197{
195{
196+ QQuickWindowPrivate *windowPrivate = QQuickWindowPrivate::get(m_view);
197+
198 qint64 timeStep = timeMs / numSteps;
198 qint64 timeStep = timeMs / numSteps;
199 QPointF touchMovement = direction * (distance / (qreal)numSteps);
199 QPointF touchMovement = direction * (distance / (qreal)numSteps);
200 for (int i = 0; i < numSteps; ++i) {
200 for (int i = 0; i < numSteps; ++i) {
201 touchPoint += touchMovement;
201 touchPoint += touchMovement;
202 m_fakeTimeSource->m_msecsSinceReference += timeStep;
202 m_fakeTimeSource->m_msecsSinceReference += timeStep;
203 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
203 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
204+
205+ windowPrivate->flushDelayedTouchEvent();
204 }
206 }
205}
207}
206
208
@@ -209,8 +211,8 @@ DirectionalDragArea *tst_DragHandle::fetchAndSetupDragHandle(const char *objectN
209 DirectionalDragArea *dragHandle =
211 DirectionalDragArea *dragHandle =
210 m_view->rootObject()->findChild<DirectionalDragArea*>(objectName);
212 m_view->rootObject()->findChild<DirectionalDragArea*>(objectName);
211 Q_ASSERT(dragHandle != 0);
213 Q_ASSERT(dragHandle != 0);
212− dragHandle->setRecognitionTimer(m_fakeTimer);
214+ dragHandle->d->setRecognitionTimer(m_fakeTimer);
213− dragHandle->setTimeSource(m_fakeTimeSource);
215+ dragHandle->d->setTimeSource(m_fakeTimeSource);
214
216
215 AxisVelocityCalculator *edgeDragEvaluator =
217 AxisVelocityCalculator *edgeDragEvaluator =
216 dragHandle->findChild<AxisVelocityCalculator*>("edgeDragEvaluator");
218 dragHandle->findChild<AxisVelocityCalculator*>("edgeDragEvaluator");
@@ -242,6 +244,7 @@ void tst_DragHandle::dragThreshold_horizontal()
242 baseItem->setRotation(rotation);
244 baseItem->setRotation(rotation);
243
245
244 DirectionalDragArea *dragHandle = fetchAndSetupDragHandle("rightwardsDragHandle");
246 DirectionalDragArea *dragHandle = fetchAndSetupDragHandle("rightwardsDragHandle");
247+ QQuickItem *parentItem = dragHandle->parentItem();
245
248
246 qreal dragThreshold = fetchDragThreshold(dragHandle);
249 qreal dragThreshold = fetchDragThreshold(dragHandle);
247
250
@@ -249,7 +252,6 @@ void tst_DragHandle::dragThreshold_horizontal()
249 flickAndHold(dragHandle, dragThreshold * 0.7);
252 flickAndHold(dragHandle, dragThreshold * 0.7);
250
253
251 // should rollback
254 // should rollback
252− QQuickItem *parentItem = dragHandle->parentItem();
253 tryCompare([&](){ return parentItem->x(); }, -parentItem->width());
255 tryCompare([&](){ return parentItem->x(); }, -parentItem->width());
254 QCOMPARE(parentItem->property("shown").toBool(), false);
256 QCOMPARE(parentItem->property("shown").toBool(), false);
255
257
tests/qmltests/Components/tst_DragHandle.qml+7
@@ -19,6 +19,7 @@ import "../../../qml/Components"
19import "tst_DragHandle"
19import "tst_DragHandle"
20import Ubuntu.Components 0.1
20import Ubuntu.Components 0.1
21import Ubuntu.Gestures 0.1
21import Ubuntu.Gestures 0.1
22+import Unity.Test 0.1
22
23
23/*
24/*
24 Two blue fullscreen Showables (a vertical and a horizontal one) with red handles you
25 Two blue fullscreen Showables (a vertical and a horizontal one) with red handles you
@@ -33,6 +34,12 @@ Rectangle {
33 width: units.gu(70)
34 width: units.gu(70)
34 height: units.gu(70)
35 height: units.gu(70)
35
36
37+ Binding {
38+ target: MouseTouchAdaptor
39+ property: "enabled"
40+ value: true
41+ }
42+
36 property var dragHandle
43 property var dragHandle
37
44
38 property bool stretch: false
45 property bool stretch: false
tests/qmltests/Components/tst_DragHandle/BidirectionalShowable.qml+2−2
@@ -60,8 +60,8 @@ Item {
60 maxTotalDragDistance: root.parent.width
60 maxTotalDragDistance: root.parent.width
61 autoCompleteDragThreshold: parent.width / 3
61 autoCompleteDragThreshold: parent.width / 3
62
62
63− onStatusChanged: {
63+ onDraggingChanged: {
64− if (status === DirectionalDragArea.Recognized) {
64+ if (dragging) {
65 dragHandleRecognizedGesture(dragHandle);
65 dragHandleRecognizedGesture(dragHandle);
66 }
66 }
67 }
67 }
tests/qmltests/Components/tst_DragHandle/HorizontalShowable.qml+4−4
@@ -65,8 +65,8 @@ Showable {
65 maxTotalDragDistance: showable.parent.width
65 maxTotalDragDistance: showable.parent.width
66 hintDisplacement: showable.hintDisplacement
66 hintDisplacement: showable.hintDisplacement
67
67
68− onStatusChanged: {
68+ onDraggingChanged: {
69− if (status === DirectionalDragArea.Recognized) {
69+ if (dragging) {
70 dragHandleRecognizedGesture(leftwardsDragHandle);
70 dragHandleRecognizedGesture(leftwardsDragHandle);
71 }
71 }
72 }
72 }
@@ -88,8 +88,8 @@ Showable {
88 maxTotalDragDistance: showable.parent.width
88 maxTotalDragDistance: showable.parent.width
89 hintDisplacement: showable.hintDisplacement
89 hintDisplacement: showable.hintDisplacement
90
90
91− onStatusChanged: {
91+ onDraggingChanged: {
92− if (status === DirectionalDragArea.Recognized) {
92+ if (dragging) {
93 dragHandleRecognizedGesture(rightwardsDragHandle);
93 dragHandleRecognizedGesture(rightwardsDragHandle);
94 }
94 }
95 }
95 }
tests/qmltests/Components/tst_DragHandle/VerticalShowable.qml+4−4
@@ -65,8 +65,8 @@ Showable {
65 maxTotalDragDistance: showable.parent.height
65 maxTotalDragDistance: showable.parent.height
66 hintDisplacement: showable.hintDisplacement
66 hintDisplacement: showable.hintDisplacement
67
67
68− onStatusChanged: {
68+ onDraggingChanged: {
69− if (status === DirectionalDragArea.Recognized) {
69+ if (dragging) {
70 dragHandleRecognizedGesture(upwardsDragHandle);
70 dragHandleRecognizedGesture(upwardsDragHandle);
71 }
71 }
72 }
72 }
@@ -88,8 +88,8 @@ Showable {
88 maxTotalDragDistance: showable.parent.height
88 maxTotalDragDistance: showable.parent.height
89 hintDisplacement: showable.hintDisplacement
89 hintDisplacement: showable.hintDisplacement
90
90
91− onStatusChanged: {
91+ onDraggingChanged: {
92− if (status === DirectionalDragArea.Recognized) {
92+ if (dragging) {
93 dragHandleRecognizedGesture(downwardsDragHandle);
93 dragHandleRecognizedGesture(downwardsDragHandle);
94 }
94 }
95 }
95 }
tests/qmltests/Stages/tst_PhoneStage.qml+8−8
@@ -164,14 +164,14 @@ Item {
164
164
165 function test_enterSpread_data() {
165 function test_enterSpread_data() {
166 return [
166 return [
167− {tag: "<position1 (linear movement)", positionMarker: "positionMarker1", linear: true, offset: -1, endPhase: 0, targetPhase: 0, newFocusedIndex: 1 },
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: -1, endPhase: 0, targetPhase: 0, newFocusedIndex: 0 },
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: +1, endPhase: 0, targetPhase: 0, newFocusedIndex: 1 },
169+ {tag: ">position1", positionMarker: "positionMarker1", linear: true, offset: +5, endPhase: 0, targetPhase: 0, newFocusedIndex: 1 },
170− {tag: "<position2 (linear)", positionMarker: "positionMarker2", linear: true, offset: -1, 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: -1, 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: +1, endPhase: 1, 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: -1, 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: +1, endPhase: 1, targetPhase: 2, newFocusedIndex: 2 },
174+ {tag: ">position3", positionMarker: "positionMarker3", linear: true, offset: +5, endPhase: 1, targetPhase: 2, newFocusedIndex: 2 },
175 ];
175 ];
176 }
176 }
177
177
tests/qmltests/Tutorial/tst_Tutorial.qml+5−1
@@ -235,7 +235,11 @@ Item {
235 checkLeftEdge();
235 checkLeftEdge();
236 checkBottomEdge();
236 checkBottomEdge();
237 if (name === "tutorialRight") return page;
237 if (name === "tutorialRight") return page;
238+ touchFlick(shell,
238− touchFlick(shell, shell.width, halfHeight, halfWidth, halfHeight);
239+ shell.width, halfHeight,
240+ halfWidth, halfHeight,
241+ true /* beginTouch */, true /* endTouch */,
242+ 20 /* speed */, 50 /* iterations */);
239 var overlay = findChild(page, "overlay");
243 var overlay = findChild(page, "overlay");
240 tryCompare(overlay, "shown", true);
244 tryCompare(overlay, "shown", true);
241 var tick = findChild(page, "tick");
245 var tick = findChild(page, "tick");
tests/qmltests/tst_Shell.qml+14−10
@@ -373,11 +373,11 @@ Rectangle {
373 function test_phoneLeftEdgeDrag_data() {
373 function test_phoneLeftEdgeDrag_data() {
374 return [
374 return [
375 {tag: "without launcher",
375 {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",
377 launcherHides: true, greeterShown: false},
377 launcherHides: true, greeterShown: false},
378
378
379 {tag: "with launcher",
379 {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",
381 launcherHides: true, greeterShown: false},
381 launcherHides: true, greeterShown: false},
382
382
383 {tag: "small swipe",
383 {tag: "small swipe",
@@ -385,7 +385,7 @@ Rectangle {
385 launcherHides: false, greeterShown: false},
385 launcherHides: false, greeterShown: false},
386
386
387 {tag: "long swipe",
387 {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",
389 launcherHides: true, greeterShown: false},
389 launcherHides: true, greeterShown: false},
390
390
391 {tag: "small swipe with greeter",
391 {tag: "small swipe with greeter",
@@ -393,11 +393,11 @@ Rectangle {
393 launcherHides: false, greeterShown: true},
393 launcherHides: false, greeterShown: true},
394
394
395 {tag: "long swipe with greeter",
395 {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",
397 launcherHides: true, greeterShown: true},
397 launcherHides: true, greeterShown: true},
398
398
399 {tag: "swipe over dash",
399 {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",
401 launcherHides: false, greeterShown: false},
401 launcherHides: false, greeterShown: false},
402 ];
402 ];
403 }
403 }
@@ -602,7 +602,7 @@ Rectangle {
602
602
603 dashCommunicatorSpy.clear();
603 dashCommunicatorSpy.clear();
604 // Minimize the application we just launched
604 // Minimize the application we just launched
605− swipeFromLeftEdge(units.gu(27));
605+ swipeFromLeftEdge(shell.width * 0.75);
606
606
607 tryCompare(ApplicationManager, "focusedApplicationId", "unity8-dash");
607 tryCompare(ApplicationManager, "focusedApplicationId", "unity8-dash");
608
608
@@ -724,7 +724,8 @@ Rectangle {
724 }
724 }
725
725
726 function dragLauncherIntoView() {
726 function dragLauncherIntoView() {
727− var launcherPanel = findChild(shell, "launcherPanel");
727+ var launcher = findChild(shell, "launcher");
728+ var launcherPanel = findChild(launcher, "launcherPanel");
728 verify(launcherPanel.x = - launcherPanel.width);
729 verify(launcherPanel.x = - launcherPanel.width);
729
730
730 var touchStartX = 2;
731 var touchStartX = 2;
@@ -732,6 +733,7 @@ Rectangle {
732 touchFlick(shell, touchStartX, touchStartY, launcherPanel.width + units.gu(1), touchStartY);
733 touchFlick(shell, touchStartX, touchStartY, launcherPanel.width + units.gu(1), touchStartY);
733
734
734 tryCompare(launcherPanel, "x", 0);
735 tryCompare(launcherPanel, "x", 0);
736+ tryCompare(launcher, "state", "visible");
735 }
737 }
736
738
737 function tapOnAppIconInLauncher() {
739 function tapOnAppIconInLauncher() {
@@ -768,7 +770,9 @@ Rectangle {
768 function swipeFromLeftEdge(swipeLength) {
770 function swipeFromLeftEdge(swipeLength) {
769 var touchStartX = 2;
771 var touchStartX = 2;
770 var touchStartY = shell.height / 2;
772 var touchStartY = shell.height / 2;
773+ touchFlick(shell,
771− touchFlick(shell, touchStartX, touchStartY, swipeLength, touchStartY);
774+ touchStartX , touchStartY,
775+ touchStartX + swipeLength, touchStartY);
772 }
776 }
773
777
774 function itemIsOnScreen(item) {
778 function itemIsOnScreen(item) {
@@ -835,7 +839,7 @@ Rectangle {
835 tryCompareFunction(function() { return app.session !== null && app.session.surface !== null }, true);
839 tryCompareFunction(function() { return app.session !== null && app.session.surface !== null }, true);
836
840
837 // Minimize the application we just launched
841 // Minimize the application we just launched
838− swipeFromLeftEdge(units.gu(26) + 1);
842+ swipeFromLeftEdge(shell.width * 0.75);
839
843
840 waitUntilDashIsFocused();
844 waitUntilDashIsFocused();
841
845
@@ -917,7 +921,7 @@ Rectangle {
917
921
918 compare(panel.fullscreenMode, true);
922 compare(panel.fullscreenMode, true);
919
923
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);
921
925
922 tryCompare(panel, "fullscreenMode", false);
926 tryCompare(panel, "fullscreenMode", false);
923
927
tests/qmltests/tst_ShellWithPin.qml+2−2
@@ -612,14 +612,14 @@ Item {
612 tryCompare(coverPage, "showProgress", 1);
612 tryCompare(coverPage, "showProgress", 1);
613
613
614 // Swipe cover page away
614 // 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);
616 tryCompare(launcher, "x", -launcher.width);
616 tryCompare(launcher, "x", -launcher.width);
617 tryCompare(coverPage, "showProgress", 0);
617 tryCompare(coverPage, "showProgress", 0);
618 compare(lockscreen.shown, true);
618 compare(lockscreen.shown, true);
619 compare(ApplicationManager.focusedApplicationId, "gallery-app");
619 compare(ApplicationManager.focusedApplicationId, "gallery-app");
620
620
621 // Now attempt a swipe on lockscreen
621 // 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);
623 tryCompare(launcher, "x", 0);
623 tryCompare(launcher, "x", 0);
624 compare(lockscreen.shown, true);
624 compare(lockscreen.shown, true);
625 compare(ApplicationManager.focusedApplicationId, "gallery-app");
625 compare(ApplicationManager.focusedApplicationId, "gallery-app");
tests/utils/modules/Unity/Test/UnityTestCase.qml+3−7
@@ -269,7 +269,7 @@ TestCase {
269 speed = (speed !== undefined) ? speed : units.gu(10)
269 speed = (speed !== undefined) ? speed : units.gu(10)
270
270
271 // Set a default iterations if not specified
271 // Set a default iterations if not specified
272− var iterations = (iterations !== undefined) ? iterations : 5
272+ var iterations = (iterations !== undefined) ? iterations : 10
273
273
274 var distance = Math.sqrt(Math.pow(rootTo.x - rootFrom.x, 2) + Math.pow(rootTo.Y - rootFrom.y, 2))
274 var distance = Math.sqrt(Math.pow(rootTo.x - rootFrom.x, 2) + Math.pow(rootTo.Y - rootFrom.y, 2))
275 var totalTime = (distance / speed) * 1000 /* converting speed to pixels/ms */
275 var totalTime = (distance / speed) * 1000 /* converting speed to pixels/ms */
@@ -403,12 +403,8 @@ TestCase {
403 function removeTimeConstraintsFromDirectionalDragAreas(item) {
403 function removeTimeConstraintsFromDirectionalDragAreas(item) {
404
404
405 // use duck-typing to identify a DirectionalDragArea
405 // use duck-typing to identify a DirectionalDragArea
406− if (item.minSpeed != undefined
406+ if (item.removeTimeConstraints != undefined) {
407− && item.maxSilenceTime != undefined
407+ item.removeTimeConstraints();
408− && item.compositionTime != undefined) {
409− item.minSpeed = 0;
410− item.maxSilenceTime = 60 * 60 * 1000;
411− item.compositionTime = 0;
412 } else {
408 } else {
413 for (var i in item.children) {
409 for (var i in item.children) {
414 removeTimeConstraintsFromDirectionalDragAreas(item.children[i]);
410 removeTimeConstraintsFromDirectionalDragAreas(item.children[i]);