on xenial help sign in
Add touch ownership logic on top of qt input handling
3f3f854Daniel d'Andrada
CMakeLists.txt+2
@@ -58,6 +58,7 @@ find_package(Qt5DBus 5.2 REQUIRED)
58include(GNUInstallDirs)
58include(GNUInstallDirs)
59
59
60set(SHELL_APP_DIR ${CMAKE_INSTALL_DATADIR}/unity8)
60set(SHELL_APP_DIR ${CMAKE_INSTALL_DATADIR}/unity8)
61+set(SHELL_PRIVATE_LIBDIR ${CMAKE_INSTALL_LIBDIR}/unity8)
61
62
62execute_process(COMMAND ${PKG_CONFIG_EXECUTABLE} --variable=plugindir_suffix unity-shell-api OUTPUT_VARIABLE SHELL_INSTALL_QML OUTPUT_STRIP_TRAILING_WHITESPACE)
63execute_process(COMMAND ${PKG_CONFIG_EXECUTABLE} --variable=plugindir_suffix unity-shell-api OUTPUT_VARIABLE SHELL_INSTALL_QML OUTPUT_STRIP_TRAILING_WHITESPACE)
63if(SHELL_INSTALL_QML STREQUAL "")
64if(SHELL_INSTALL_QML STREQUAL "")
@@ -106,6 +107,7 @@ include_directories(
106
107
107# add subdirectories to build
108# add subdirectories to build
108add_subdirectory(include)
109add_subdirectory(include)
110+add_subdirectory(libs)
109add_subdirectory(src)
111add_subdirectory(src)
110add_subdirectory(tools)
112add_subdirectory(tools)
111add_subdirectory(qml)
113add_subdirectory(qml)
debian/unity8-private.install+1
@@ -8,5 +8,6 @@ usr/lib/*/unity8/qml/SessionBroadcast
8usr/lib/*/unity8/qml/Ubuntu
8usr/lib/*/unity8/qml/Ubuntu
9usr/lib/*/unity8/qml/Unity
9usr/lib/*/unity8/qml/Unity
10usr/lib/*/unity8/qml/Utils
10usr/lib/*/unity8/qml/Utils
11+usr/lib/*/unity8/libUbuntuGestures*
11usr/share/accountsservice/interfaces
12usr/share/accountsservice/interfaces
12usr/share/dbus-1/interfaces
13usr/share/dbus-1/interfaces
libs/CMakeLists.txtnew file+1
@@ -0,0 +1 @@
1+add_subdirectory(UbuntuGestures)
libs/UbuntuGestures/CMakeLists.txtnew file+40
@@ -0,0 +1,40 @@
1+# in order to include Qt's private headers
2+remove_definitions(-DQT_NO_KEYWORDS)
3+
4+set(UbuntuGestures_SOURCES
5+ CandidateInactivityTimer.cpp
6+ DebugHelpers.cpp
7+ Timer.cpp
8+ TouchOwnershipEvent.cpp
9+ TouchRegistry.cpp
10+ UnownedTouchEvent.cpp
11+)
12+
13+add_definitions(-DUBUNTUGESTURES_LIBRARY)
14+
15+add_library(UbuntuGestures SHARED ${UbuntuGestures_SOURCES})
16+
17+qt5_use_modules(UbuntuGestures Core Quick)
18+
19+# So that Foo.cpp can #include "Foo.moc"
20+include_directories(${CMAKE_CURRENT_BINARY_DIR})
21+
22+install(TARGETS UbuntuGestures
23+ DESTINATION ${SHELL_PRIVATE_LIBDIR})
24+
25+
26+# There's no cmake var for v8 include path :-/ so create one
27+LIST(GET Qt5Core_INCLUDE_DIRS 0 QtCoreDir0)
28+if(${Qt5Core_VERSION_STRING} VERSION_LESS "5.1.0")
29+ SET(Qt5V8_PRIVATE_INCLUDE_DIR ${QtCoreDir0}/../QtV8/${Qt5Core_VERSION_STRING}/QtV8)
30+else()
31+ SET(Qt5V8_PRIVATE_INCLUDE_DIR ${QtCoreDir0}/QtV8/${Qt5Core_VERSION_STRING}/QtV8)
32+endif()
33+
34+# DANGER! DANGER! Using Qt's private API!
35+include_directories(
36+ ${Qt5Qml_PRIVATE_INCLUDE_DIRS}
37+ ${Qt5Quick_INCLUDE_DIRS}
38+ ${Qt5Quick_PRIVATE_INCLUDE_DIRS}
39+ ${Qt5V8_PRIVATE_INCLUDE_DIR}
40+)
libs/UbuntuGestures/CandidateInactivityTimer.cppnew file+41
@@ -0,0 +1,41 @@
1+/*
2+ * Copyright (C) 2014 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 "CandidateInactivityTimer.h"
18+
19+namespace UbuntuGestures {
20+
21+CandidateInactivityTimer::CandidateInactivityTimer(int touchId, QQuickItem *candidate,
22+ AbstractTimerFactory &timerFactory, QObject *parent)
23+ : QObject(parent)
24+ , m_touchId(touchId)
25+ , m_candidate(candidate)
26+{
27+ m_timer = timerFactory.createTimer(this);
28+ connect(m_timer, &AbstractTimer::timeout,
29+ this, &CandidateInactivityTimer::onTimeout);
30+ m_timer->setInterval(durationMs);
31+ m_timer->setSingleShot(true);
32+ m_timer->start();
33+}
34+
35+void CandidateInactivityTimer::onTimeout()
36+{
37+ qWarning("[TouchRegistry] Candidate for touch %d defaulted!", m_touchId);
38+ Q_EMIT candidateDefaulted(m_touchId, m_candidate);
39+}
40+
41+} // namespace UbuntuGestures
libs/UbuntuGestures/CandidateInactivityTimer.hnew file+49
@@ -0,0 +1,49 @@
1+/*
2+ * Copyright (C) 2014 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 UBUNTUGESTURES_CANDIDATE_INACTIVITY_TIMER_H
18+#define UBUNTUGESTURES_CANDIDATE_INACTIVITY_TIMER_H
19+
20+#include <QObject>
21+
22+class QQuickItem;
23+
24+#include "Timer.h"
25+
26+namespace UbuntuGestures {
27+
28+class UBUNTUGESTURES_EXPORT CandidateInactivityTimer : public QObject {
29+ Q_OBJECT
30+public:
31+ CandidateInactivityTimer(int touchId, QQuickItem *candidate,
32+ AbstractTimerFactory &timerFactory,
33+ QObject *parent = nullptr);
34+
35+ const int durationMs = 350;
36+
37+Q_SIGNALS:
38+ void candidateDefaulted(int touchId, QQuickItem *candidate);
39+private Q_SLOTS:
40+ void onTimeout();
41+private:
42+ AbstractTimer *m_timer;
43+ int m_touchId;
44+ QQuickItem *m_candidate;
45+};
46+
47+} // namespace UbuntuGestures
48+
49+#endif // UBUNTUGESTURES_CANDIDATE_INACTIVITY_TIMER_H
libs/UbuntuGestures/DebugHelpers.cppnew file+68
@@ -0,0 +1,68 @@
1+/*
2+ * Copyright (C) 2014 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 "DebugHelpers.h"
18+#include <QTouchEvent>
19+
20+QString touchPointStateToString(Qt::TouchPointState state)
21+{
22+ switch (state) {
23+ case Qt::TouchPointPressed:
24+ return QString("pressed");
25+ case Qt::TouchPointMoved:
26+ return QString("moved");
27+ case Qt::TouchPointStationary:
28+ return QString("stationary");
29+ case Qt::TouchPointReleased:
30+ return QString("released");
31+ default:
32+ return QString("INVALID_STATE");
33+ }
34+}
35+
36+QString touchEventToString(const QTouchEvent *ev)
37+{
38+ QString message;
39+
40+ switch (ev->type()) {
41+ case QEvent::TouchBegin:
42+ message.append("TouchBegin ");
43+ break;
44+ case QEvent::TouchUpdate:
45+ message.append("TouchUpdate ");
46+ break;
47+ case QEvent::TouchEnd:
48+ message.append("TouchEnd ");
49+ break;
50+ case QEvent::TouchCancel:
51+ message.append("TouchCancel ");
52+ break;
53+ default:
54+ message.append("INVALID_TOUCH_EVENT_TYPE ");
55+ }
56+
57+ foreach(const QTouchEvent::TouchPoint& touchPoint, ev->touchPoints()) {
58+ message.append(
59+ QString("(id:%1, state:%2, scenePos:(%3,%4)) ")
60+ .arg(touchPoint.id())
61+ .arg(touchPointStateToString(touchPoint.state()))
62+ .arg(touchPoint.scenePos().x())
63+ .arg(touchPoint.scenePos().y())
64+ );
65+ }
66+
67+ return message;
68+}
libs/UbuntuGestures/DebugHelpers.hnew file+29
@@ -0,0 +1,29 @@
1+/*
2+ * Copyright (C) 2014 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 UBUNTUGESTURES_DEBUG_HELPER_H
18+#define UBUNTUGESTURES_DEBUG_HELPER_H
19+
20+#include <QString>
21+
22+#include "UbuntuGesturesGlobal.h"
23+
24+class QTouchEvent;
25+
26+UBUNTUGESTURES_EXPORT QString touchPointStateToString(Qt::TouchPointState state);
27+UBUNTUGESTURES_EXPORT QString touchEventToString(const QTouchEvent *ev);
28+
29+#endif // UBUNTUGESTURES_DEBUG_HELPER_H
libs/UbuntuGestures/Pool.hnew file+134
@@ -0,0 +1,134 @@
1+/*
2+ * Copyright (C) 2014 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 UBUNTUGESTURES_POOL_H
18+#define UBUNTUGESTURES_POOL_H
19+
20+#include <QVector>
21+
22+#include "UbuntuGesturesGlobal.h"
23+
24+/*
25+ An object pool.
26+ Avoids unnecessary creations/initializations and deletions/destructions of items. Useful
27+ in a scenario where items are created and destroyed very frequently but the total number
28+ of items at any given time remains small. They're stored in a unordered fashion.
29+
30+ To be used in Pool, ItemType needs to have the following methods:
31+
32+ - ItemType();
33+
34+ A constructor that takes no parameters. An object contructed with it must return false if
35+ isValid() is called.
36+
37+ - bool isValid() const;
38+
39+ Returns wheter the object holds a valid , "filled" state or is empty.
40+ Used by Pool to check if the slot occupied by this object is actually available.
41+
42+ - void reset();
43+
44+ Resets the object to its initial, empty, state. After calling this method, isValid() must
45+ return false.
46+ */
47+template <class ItemType> class Pool
48+{
49+public:
50+ Pool() : m_lastUsedIndex(-1) {
51+ }
52+
53+ class Iterator {
54+ public:
55+ Iterator() : index(-1), item(nullptr) {}
56+ Iterator(int index, ItemType *item)
57+ : index(index), item(item) {}
58+
59+ ItemType *operator->() const { return item; }
60+ ItemType &operator*() const { return *item; }
61+ ItemType &value() const { return *item; }
62+
63+ Iterator &operator= (const Iterator& other) {
64+ index = other.index;
65+ item = other.item;
66+
67+ // by convention, always return *this
68+ return *this;
69+ }
70+
71+ operator bool() const { return item != nullptr; }
72+
73+ int index;
74+ ItemType *item;
75+ };
76+
77+ Iterator &end() const { return Iterator(); }
78+
79+ ItemType &getEmptySlot() {
80+ Q_ASSERT(m_lastUsedIndex < m_slots.size());
81+
82+ // Look for an in-between vacancy first
83+ for (int i = 0; i < m_lastUsedIndex; ++i) {
84+ ItemType &item = m_slots[i];
85+ if (!item.isValid()) {
86+ return item;
87+ }
88+ }
89+
90+ ++m_lastUsedIndex;
91+ if (m_lastUsedIndex >= m_slots.size()) {
92+ m_slots.resize(m_lastUsedIndex + 1);
93+ }
94+
95+ return m_slots[m_lastUsedIndex];
96+ }
97+
98+ void freeSlot(Iterator &iterator) {
99+ m_slots[iterator.index].reset();
100+ if (iterator.index == m_lastUsedIndex) {
101+ do {
102+ --m_lastUsedIndex;
103+ } while (m_lastUsedIndex >= 0 && !m_slots.at(m_lastUsedIndex).isValid());
104+ }
105+ }
106+
107+ // Iterates through all valid items (i.e. the occupied slots)
108+ // calling the given function, with the option of ending the loop early.
109+ //
110+ // bool Func(Iterator& item)
111+ //
112+ // Returning true means it wants to continue the "for" loop, false
113+ // terminates the loop.
114+ template<typename Func> void forEach(Func func) {
115+ Iterator it;
116+ for (it.index = 0; it.index <= m_lastUsedIndex; ++it.index) {
117+ it.item = &m_slots[it.index];
118+ if (!it.item->isValid())
119+ continue;
120+
121+ if (!func(it))
122+ break;
123+ }
124+ }
125+
126+ bool isEmpty() const { return m_lastUsedIndex == -1; }
127+
128+
129+private:
130+ QVector<ItemType> m_slots;
131+ int m_lastUsedIndex;
132+};
133+
134+#endif // UBUNTUGESTURES_POOL_H
libs/UbuntuGestures/Timer.cppnew file+109
@@ -0,0 +1,109 @@
1+/*
2+ * Copyright (C) 2014 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 "Timer.h"
18+
19+namespace UbuntuGestures {
20+
21+Timer::Timer(QObject *parent) : AbstractTimer(parent)
22+{
23+ m_timer.setSingleShot(false);
24+ connect(&m_timer, &QTimer::timeout, this, &AbstractTimer::timeout);
25+}
26+
27+int Timer::interval() const
28+{
29+ return m_timer.interval();
30+}
31+
32+void Timer::setInterval(int msecs)
33+{
34+ m_timer.setInterval(msecs);
35+}
36+
37+void Timer::start()
38+{
39+ m_timer.start();
40+ AbstractTimer::start();
41+}
42+
43+void Timer::stop()
44+{
45+ m_timer.stop();
46+ AbstractTimer::stop();
47+}
48+
49+bool Timer::isSingleShot() const
50+{
51+ return m_timer.isSingleShot();
52+}
53+
54+void Timer::setSingleShot(bool value)
55+{
56+ m_timer.setSingleShot(value);
57+}
58+
59+/////////////////////////////////// FakeTimer //////////////////////////////////
60+
61+FakeTimer::FakeTimer(QObject *parent)
62+ : UbuntuGestures::AbstractTimer(parent)
63+ , m_interval(0)
64+ , m_singleShot(false)
65+{
66+}
67+
68+int FakeTimer::interval() const
69+{
70+ return m_interval;
71+}
72+
73+void FakeTimer::setInterval(int msecs)
74+{
75+ m_interval = msecs;
76+}
77+
78+bool FakeTimer::isSingleShot() const
79+{
80+ return m_singleShot;
81+}
82+
83+void FakeTimer::setSingleShot(bool value)
84+{
85+ m_singleShot = value;
86+}
87+
88+/////////////////////////////////// FakeTimerFactory //////////////////////////////////
89+
90+AbstractTimer *FakeTimerFactory::createTimer(QObject *parent)
91+{
92+ FakeTimer *fakeTimer = new FakeTimer(parent);
93+
94+ timers.append(fakeTimer);
95+
96+ return fakeTimer;
97+}
98+
99+void FakeTimerFactory::makeRunningTimersTimeout()
100+{
101+ for (int i = 0; i < timers.count(); ++i) {
102+ FakeTimer *timer = timers[i].data();
103+ if (timer && timer->isRunning()) {
104+ timer->emitTimeout();
105+ }
106+ }
107+}
108+
109+} // namespace UbuntuGestures
libs/UbuntuGestures/Timer.hnew file+105
@@ -0,0 +1,105 @@
1+/*
2+ * Copyright (C) 2014 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 UBUNTUGESTURES_TIMER_H
18+#define UBUNTUGESTURES_TIMER_H
19+
20+#include "UbuntuGesturesGlobal.h"
21+
22+#include <QObject>
23+#include <QPointer>
24+#include <QTimer>
25+
26+namespace UbuntuGestures {
27+
28+/* Defines an interface for a Timer. Useful for tests. */
29+class UBUNTUGESTURES_EXPORT AbstractTimer : public QObject
30+{
31+ Q_OBJECT
32+public:
33+ AbstractTimer(QObject *parent) : QObject(parent), m_isRunning(false) {}
34+ virtual int interval() const = 0;
35+ virtual void setInterval(int msecs) = 0;
36+ virtual void start() { m_isRunning = true; }
37+ virtual void stop() { m_isRunning = false; }
38+ bool isRunning() const { return m_isRunning; }
39+ virtual bool isSingleShot() const = 0;
40+ virtual void setSingleShot(bool value) = 0;
41+Q_SIGNALS:
42+ void timeout();
43+private:
44+ bool m_isRunning;
45+};
46+
47+/* Essentially a QTimer wrapper */
48+class UBUNTUGESTURES_EXPORT Timer : public AbstractTimer
49+{
50+ Q_OBJECT
51+public:
52+ Timer(QObject *parent = nullptr);
53+
54+ int interval() const override;
55+ void setInterval(int msecs) override;
56+ void start() override;
57+ void stop() override;
58+ bool isSingleShot() const override;
59+ void setSingleShot(bool value) override;
60+private:
61+ QTimer m_timer;
62+};
63+
64+/* For tests */
65+class UBUNTUGESTURES_EXPORT FakeTimer : public AbstractTimer
66+{
67+ Q_OBJECT
68+public:
69+ FakeTimer(QObject *parent = nullptr);
70+
71+ virtual void emitTimeout() { Q_EMIT timeout(); }
72+
73+ int interval() const override;
74+ void setInterval(int msecs) override;
75+ bool isSingleShot() const override;
76+ void setSingleShot(bool value) override;
77+private:
78+ int m_interval;
79+ bool m_singleShot;
80+};
81+
82+class UBUNTUGESTURES_EXPORT AbstractTimerFactory
83+{
84+public:
85+ virtual ~AbstractTimerFactory() {}
86+ virtual AbstractTimer *createTimer(QObject *parent = nullptr) = 0;
87+};
88+
89+class UBUNTUGESTURES_EXPORT TimerFactory : public AbstractTimerFactory
90+{
91+public:
92+ AbstractTimer *createTimer(QObject *parent = nullptr) override { return new Timer(parent); }
93+};
94+
95+class UBUNTUGESTURES_EXPORT FakeTimerFactory : public AbstractTimerFactory
96+{
97+public:
98+ AbstractTimer *createTimer(QObject *parent = nullptr) override;
99+ void makeRunningTimersTimeout();
100+ QList<QPointer<FakeTimer>> timers;
101+};
102+
103+} // namespace UbuntuGestures
104+
105+#endif // UBUNTUGESTURES_TIMER_H
libs/UbuntuGestures/TouchOwnershipEvent.cppnew file+35
@@ -0,0 +1,35 @@
1+/*
2+ * Copyright (C) 2014 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 "TouchOwnershipEvent.h"
18+
19+QEvent::Type TouchOwnershipEvent::m_touchOwnershipType = (QEvent::Type)-1;
20+
21+TouchOwnershipEvent::TouchOwnershipEvent(int touchId, bool gained)
22+ : QEvent(touchOwnershipEventType())
23+ , m_touchId(touchId)
24+ , m_gained(gained)
25+{
26+}
27+
28+QEvent::Type TouchOwnershipEvent::touchOwnershipEventType()
29+{
30+ if (m_touchOwnershipType == (QEvent::Type)-1) {
31+ m_touchOwnershipType = (QEvent::Type)registerEventType();
32+ }
33+
34+ return m_touchOwnershipType;
35+}
libs/UbuntuGestures/TouchOwnershipEvent.hnew file+50
@@ -0,0 +1,50 @@
1+/*
2+ * Copyright (C) 2014 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 UBUNTU_TOUCHOWNERSHIPEVENT_H
18+#define UBUNTU_TOUCHOWNERSHIPEVENT_H
19+
20+#include <QEvent>
21+#include "UbuntuGesturesGlobal.h"
22+
23+/*
24+ When an item get an ownership event for a touch it can grab/steal that touch
25+ with a clean conscience.
26+ */
27+class UBUNTUGESTURES_EXPORT TouchOwnershipEvent : public QEvent
28+{
29+public:
30+ TouchOwnershipEvent(int touchId, bool gained);
31+
32+ static Type touchOwnershipEventType();
33+
34+ /*
35+ Whether ownership was gained (true) or lost (false)
36+ */
37+ bool gained() const { return m_gained; }
38+
39+ /*
40+ Id of the touch whose ownership was granted.
41+ */
42+ int touchId() const { return m_touchId; }
43+
44+private:
45+ static Type m_touchOwnershipType;
46+ int m_touchId;
47+ bool m_gained;
48+};
49+
50+#endif // UBUNTU_TOUCHOWNERSHIPEVENT_H
libs/UbuntuGestures/TouchRegistry.cppnew file+500
@@ -0,0 +1,500 @@
1+/*
2+ * Copyright (C) 2014 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 "TouchRegistry.h"
18+
19+#include <QCoreApplication>
20+#include <QDebug>
21+
22+#pragma GCC diagnostic push
23+#pragma GCC diagnostic ignored "-pedantic"
24+#include <private/qquickitem_p.h>
25+#pragma GCC diagnostic pop
26+
27+#include "CandidateInactivityTimer.h"
28+#include "Timer.h"
29+#include "TouchOwnershipEvent.h"
30+#include "UnownedTouchEvent.h"
31+
32+#define TOUCHREGISTRY_DEBUG 0
33+
34+#if TOUCHREGISTRY_DEBUG
35+ #include "DebugHelpers.h"
36+ #define UG_DEBUG qDebug() << "[TouchRegistry]"
37+#endif // TOUCHREGISTRY_DEBUG
38+
39+using namespace UbuntuGestures;
40+
41+TouchRegistry *TouchRegistry::m_instance = nullptr;
42+
43+TouchRegistry::TouchRegistry(QObject *parent)
44+ : TouchRegistry(parent, new TimerFactory)
45+{
46+}
47+
48+TouchRegistry::TouchRegistry(QObject *parent, AbstractTimerFactory *timerFactory)
49+ : QObject(parent)
50+ , m_inDispatchLoop(false)
51+ , m_timerFactory(timerFactory)
52+{
53+ if (m_instance == nullptr) {
54+ m_instance = this;
55+ } else {
56+ qFatal("Cannot have more than one instance of TouchRegistry. It must be a singleton.");
57+ }
58+}
59+
60+TouchRegistry::~TouchRegistry()
61+{
62+ Q_ASSERT(m_instance != nullptr);
63+ m_instance = nullptr;
64+ delete m_timerFactory;
65+}
66+
67+void TouchRegistry::update(const QTouchEvent *event)
68+{
69+ #if TOUCHREGISTRY_DEBUG
70+ UG_DEBUG << "got" << qPrintable(touchEventToString(event));
71+ #endif
72+
73+ const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
74+ for (int i = 0; i < touchPoints.count(); ++i) {
75+ const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i);
76+ if (touchPoint.state() == Qt::TouchPointPressed) {
77+ TouchInfo &touchInfo = m_touchInfoPool.getEmptySlot();
78+ touchInfo.init(touchPoint.id());
79+ } else if (touchPoint.state() == Qt::TouchPointReleased) {
80+ Pool<TouchInfo>::Iterator touchInfo = findTouchInfo(touchPoint.id());
81+
82+ touchInfo->physicallyEnded = true;
83+ }
84+ }
85+
86+ deliverTouchUpdatesToUndecidedCandidatesAndWatchers(event);
87+
88+ freeEndedTouchInfos();
89+}
90+
91+void TouchRegistry::deliverTouchUpdatesToUndecidedCandidatesAndWatchers(const QTouchEvent *event)
92+{
93+ // TODO: Look into how we could optimize this whole thing.
94+ // Although it's not really a problem as we should have at most two candidates
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.
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();
103+
104+ // Maps an item to the touches in this event he should be informed about.
105+ // E.g.: a QTouchEvent might have three touches but a given item might be interested in only
106+ // one of them. So he will get a UnownedTouchEvent from this QTouchEvent containing only that
107+ // touch point.
108+ QMap<QQuickItem*, QList<int>> touchIdsForItems;
109+
110+ // Build touchIdsForItems
111+ m_touchInfoPool.forEach([&](Pool<TouchInfo>::Iterator &touchInfo) {
112+ if (touchInfo->isOwned() && touchInfo->watchers.isEmpty())
113+ return true;
114+
115+ for (int j = 0; j < updatedTouchPoints.count(); ++j) {
116+ if (updatedTouchPoints[j].id() == touchInfo->id) {
117+ if (!touchInfo->isOwned()) {
118+ for (int i = 0; i < touchInfo->candidates.count(); ++i) {
119+ CandidateInfo &candidate = touchInfo->candidates[i];
120+ Q_ASSERT(!candidate.item.isNull());
121+ touchIdsForItems[candidate.item.data()].append(touchInfo->id);
122+ }
123+ }
124+
125+ const QList<QPointer<QQuickItem>> &watchers = touchInfo->watchers;
126+ for (int i = 0; i < watchers.count(); ++i) {
127+ if (!watchers[i].isNull()) {
128+ touchIdsForItems[watchers[i].data()].append(touchInfo->id);
129+ }
130+ }
131+
132+ return true;
133+ }
134+ }
135+
136+ return true;
137+ });
138+
139+ // TODO: Consider what happens if an item calls any of TouchRegistry's public methods
140+ // from the event handler callback.
141+ m_inDispatchLoop = true;
142+ auto it = touchIdsForItems.constBegin();
143+ while (it != touchIdsForItems.constEnd()) {
144+ QQuickItem *item = it.key();
145+ const QList<int> &touchIds = it.value();
146+ dispatchPointsToItem(event, touchIds, item);
147+ ++it;
148+ };
149+ m_inDispatchLoop = false;
150+}
151+
152+void TouchRegistry::freeEndedTouchInfos()
153+{
154+ m_touchInfoPool.forEach([&](Pool<TouchInfo>::Iterator &touchInfo) {
155+ if (touchInfo->ended()) {
156+ m_touchInfoPool.freeSlot(touchInfo);
157+ }
158+ return true;
159+ });
160+}
161+
162+/*
163+ Extracts the touches with the given touchIds from event and send them in a
164+ UnownedTouchEvent to the given item
165+ */
166+void TouchRegistry::dispatchPointsToItem(const QTouchEvent *event, const QList<int> &touchIds,
167+ QQuickItem *item)
168+{
169+ Qt::TouchPointStates touchPointStates = 0;
170+ QList<QTouchEvent::TouchPoint> touchPoints;
171+
172+ const QList<QTouchEvent::TouchPoint> &allTouchPoints = event->touchPoints();
173+
174+ QTransform windowToCandidateTransform = QQuickItemPrivate::get(item)->windowToItemTransform();
175+ QMatrix4x4 windowToCandidateMatrix(windowToCandidateTransform);
176+
177+ for (int i = 0; i < allTouchPoints.count(); ++i) {
178+ const QTouchEvent::TouchPoint &originalTouchPoint = allTouchPoints[i];
179+ if (touchIds.contains(originalTouchPoint.id())) {
180+ QTouchEvent::TouchPoint touchPoint = originalTouchPoint;
181+
182+ translateTouchPointFromScreenToWindowCoords(touchPoint);
183+
184+ // Set the point's local coordinates to that of the item
185+ touchPoint.setRect(windowToCandidateTransform.mapRect(touchPoint.sceneRect()));
186+ touchPoint.setStartPos(windowToCandidateTransform.map(touchPoint.startScenePos()));
187+ touchPoint.setLastPos(windowToCandidateTransform.map(touchPoint.lastScenePos()));
188+ touchPoint.setVelocity(windowToCandidateMatrix.mapVector(touchPoint.velocity()).toVector2D());
189+
190+ touchPoints.append(touchPoint);
191+ touchPointStates |= touchPoint.state();
192+ }
193+ }
194+
195+ QTouchEvent *eventForItem = new QTouchEvent(event->type(),
196+ event->device(),
197+ event->modifiers(),
198+ touchPointStates,
199+ touchPoints);
200+ eventForItem->setWindow(event->window());
201+ eventForItem->setTimestamp(event->timestamp());
202+ eventForItem->setTarget(event->target());
203+
204+ UnownedTouchEvent unownedTouchEvent(eventForItem);
205+
206+ #if TOUCHREGISTRY_DEBUG
207+ UG_DEBUG << "Sending unowned" << qPrintable(touchEventToString(eventForItem))
208+ << "to" << item;
209+ #endif
210+
211+ QCoreApplication::sendEvent(item, &unownedTouchEvent);
212+}
213+
214+void TouchRegistry::translateTouchPointFromScreenToWindowCoords(QTouchEvent::TouchPoint &touchPoint)
215+{
216+ touchPoint.setScreenRect(touchPoint.sceneRect());
217+ touchPoint.setStartScreenPos(touchPoint.startScenePos());
218+ touchPoint.setLastScreenPos(touchPoint.lastScenePos());
219+
220+ touchPoint.setSceneRect(touchPoint.rect());
221+ touchPoint.setStartScenePos(touchPoint.startPos());
222+ touchPoint.setLastScenePos(touchPoint.lastPos());
223+}
224+
225+bool TouchRegistry::eventFilter(QObject *watched, QEvent *event)
226+{
227+ Q_UNUSED(watched);
228+
229+ switch (event->type()) {
230+ case QEvent::TouchBegin:
231+ case QEvent::TouchUpdate:
232+ case QEvent::TouchEnd:
233+ case QEvent::TouchCancel:
234+ update(static_cast<QTouchEvent*>(event));
235+ break;
236+ default:
237+ // do nothing
238+ break;
239+ }
240+
241+ // Do not filter out the event. i.e., let it be handled further as
242+ // we're just monitoring events
243+ return false;
244+}
245+
246+void TouchRegistry::addCandidateOwnerForTouch(int id, QQuickItem *candidate)
247+{
248+ #if TOUCHREGISTRY_DEBUG
249+ UG_DEBUG << "addCandidateOwnerForTouch id" << id << "candidate" << candidate;
250+ #endif
251+
252+ Pool<TouchInfo>::Iterator touchInfo = findTouchInfo(id);
253+ if (!touchInfo) { qFatal("TouchRegistry: Failed to find TouchInfo"); }
254+
255+ if (touchInfo->isOwned()) {
256+ qWarning("TouchRegistry: trying to add candidate owner for a touch that's already owned");
257+ return;
258+ }
259+
260+ // TODO: Check if candidate already exists
261+
262+ CandidateInfo candidateInfo;
263+ candidateInfo.undecided = true;
264+ candidateInfo.item = candidate;
265+ candidateInfo.inactivityTimer = new CandidateInactivityTimer(id, candidate,
266+ *m_timerFactory,
267+ this);
268+ connect(candidateInfo.inactivityTimer, &CandidateInactivityTimer::candidateDefaulted,
269+ this, &TouchRegistry::rejectCandidateOwnerForTouch);
270+
271+ touchInfo->candidates.append(candidateInfo);
272+}
273+
274+void TouchRegistry::addTouchWatcher(int touchId, QQuickItem *watcher)
275+{
276+ #if TOUCHREGISTRY_DEBUG
277+ UG_DEBUG << "addTouchWatcher id" << touchId << "watcher" << watcher;
278+ #endif
279+
280+ Pool<TouchInfo>::Iterator touchInfo = findTouchInfo(touchId);
281+ if (!touchInfo) { qFatal("TouchRegistry: Failed to find TouchInfo"); }
282+
283+ // TODO: Check if watcher already exists
284+
285+ touchInfo->watchers.append(watcher);
286+}
287+
288+void TouchRegistry::removeCandidateOwnerForTouch(int id, QQuickItem *candidate)
289+{
290+ #if TOUCHREGISTRY_DEBUG
291+ UG_DEBUG << "removeCandidateOwnerForTouch id" << id << "candidate" << candidate;
292+ #endif
293+
294+ Pool<TouchInfo>::Iterator touchInfo = findTouchInfo(id);
295+ if (!touchInfo) { qFatal("TouchRegistry: Failed to find TouchInfo"); }
296+
297+ int indexRemoved = -1;
298+
299+ // TODO: check if the candidate is in fact the owner of the touch
300+
301+ for (int i = 0; i < touchInfo->candidates.count() && indexRemoved == -1; ++i) {
302+ CandidateInfo &candidateInfo = touchInfo->candidates[i];
303+ if (candidateInfo.item == candidate) {
304+ Q_ASSERT(i > 0 || candidateInfo.undecided);
305+ if (i == 0 && !candidateInfo.undecided) {
306+ qCritical("TouchRegistry: touch owner is being removed.");
307+ }
308+ delete candidateInfo.inactivityTimer;
309+ candidateInfo.inactivityTimer = nullptr;
310+ touchInfo->candidates.removeAt(i);
311+ indexRemoved = i;
312+ }
313+ }
314+
315+ if (indexRemoved == 0) {
316+ // the top candidate has been removed. if the new top candidate
317+ // wants the touch let him know he's now the owner.
318+ if (touchInfo->isOwned()) {
319+ touchInfo->notifyCandidatesOfOwnershipResolution();
320+ }
321+ }
322+
323+ if (!m_inDispatchLoop && touchInfo->ended()) {
324+ m_touchInfoPool.freeSlot(touchInfo);
325+ }
326+}
327+
328+void TouchRegistry::requestTouchOwnership(int id, QQuickItem *candidate)
329+{
330+ #if TOUCHREGISTRY_DEBUG
331+ UG_DEBUG << "requestTouchOwnership id " << id << "candidate" << candidate;
332+ #endif
333+
334+ Pool<TouchInfo>::Iterator touchInfo = findTouchInfo(id);
335+ if (!touchInfo) { qFatal("TouchRegistry: Failed to find TouchInfo"); }
336+
337+ Q_ASSERT(!touchInfo->isOwned());
338+
339+ int candidateIndex = -1;
340+ for (int i = 0; i < touchInfo->candidates.count(); ++i) {
341+ CandidateInfo &candidateInfo = touchInfo->candidates[i];
342+ if (candidateInfo.item == candidate) {
343+ candidateInfo.undecided = false;
344+ delete candidateInfo.inactivityTimer;
345+ candidateInfo.inactivityTimer = nullptr;
346+ candidateIndex = i;
347+ break;
348+ }
349+ }
350+
351+ // add it as a candidate if not present yet
352+ if (candidateIndex < 0) {
353+ CandidateInfo candidateInfo;
354+ candidateInfo.undecided = false;
355+ candidateInfo.item = candidate;
356+ candidateInfo.inactivityTimer = nullptr;
357+ touchInfo->candidates.append(candidateInfo);
358+ // it's the last one
359+ candidateIndex = touchInfo->candidates.count() - 1;
360+ }
361+
362+ // If it's the top candidate it means it's now the owner. Let
363+ // it know about it.
364+ if (candidateIndex == 0) {
365+ touchInfo->notifyCandidatesOfOwnershipResolution();
366+ }
367+}
368+
369+Pool<TouchRegistry::TouchInfo>::Iterator TouchRegistry::findTouchInfo(int id)
370+{
371+ Pool<TouchInfo>::Iterator touchInfo;
372+
373+ m_touchInfoPool.forEach([&](Pool<TouchInfo>::Iterator &someTouchInfo) -> bool {
374+ if (someTouchInfo->id == id) {
375+ touchInfo = someTouchInfo;
376+ return false;
377+ } else {
378+ return true;
379+ }
380+ });
381+
382+ return touchInfo;
383+}
384+
385+
386+void TouchRegistry::rejectCandidateOwnerForTouch(int id, QQuickItem *candidate)
387+{
388+ // NB: It's technically possible that candidate is a dangling pointer at this point.
389+ // Although that would most likely be due to a bug in our code.
390+ // In any case, only dereference it after it's confirmed that it indeed exists.
391+
392+ #if TOUCHREGISTRY_DEBUG
393+ UG_DEBUG << "rejectCandidateOwnerForTouch id" << id << "candidate" << (void*)candidate;
394+ #endif
395+
396+ Pool<TouchInfo>::Iterator touchInfo = findTouchInfo(id);
397+ if (!touchInfo) {
398+ #if TOUCHREGISTRY_DEBUG
399+ UG_DEBUG << "Failed to find TouchInfo for id" << id;
400+ #endif
401+ return;
402+ }
403+
404+ int rejectedCandidateIndex = -1;
405+
406+ // Check if the given candidate is valid and still undecided
407+ for (int i = 0; i < touchInfo->candidates.count() && rejectedCandidateIndex == -1; ++i) {
408+ CandidateInfo &candidateInfo = touchInfo->candidates[i];
409+ if (candidateInfo.item == candidate) {
410+ Q_ASSERT(i > 0 || candidateInfo.undecided);
411+ if (i == 0 && !candidateInfo.undecided) {
412+ qCritical() << "TouchRegistry: Can't reject item (" << (void*)candidate
413+ << ") as it already owns touch" << id;
414+ return;
415+ } else {
416+ // we found the guy and it's all fine.
417+ rejectedCandidateIndex = i;
418+ }
419+ }
420+ }
421+
422+ // If we reached this point it's because the given candidate exists and is indeed undecided.
423+
424+ Q_ASSERT(rejectedCandidateIndex >= 0 && rejectedCandidateIndex < touchInfo->candidates.size());
425+
426+ {
427+ TouchOwnershipEvent lostOwnershipEvent(id, false /*gained*/);
428+ QCoreApplication::sendEvent(candidate, &lostOwnershipEvent);
429+ }
430+
431+ touchInfo->candidates.removeAt(rejectedCandidateIndex);
432+
433+ if (rejectedCandidateIndex == 0) {
434+ // the top candidate has been removed. if the new top candidate
435+ // wants the touch let him know he's now the owner.
436+ if (touchInfo->isOwned()) {
437+ touchInfo->notifyCandidatesOfOwnershipResolution();
438+ }
439+ }
440+}
441+
442+////////////////////////////////////// TouchRegistry::TouchInfo ////////////////////////////////////
443+
444+TouchRegistry::TouchInfo::TouchInfo(int id)
445+{
446+ init(id);
447+}
448+
449+void TouchRegistry::TouchInfo::reset()
450+{
451+ id = -1;
452+
453+ for (int i = 0; i < candidates.count(); ++i) {
454+ CandidateInfo &candidate = candidates[i];
455+ delete candidate.inactivityTimer;
456+ candidate.inactivityTimer.clear(); // shoundn't be needed but anyway...
457+ }
458+}
459+
460+void TouchRegistry::TouchInfo::init(int id)
461+{
462+ this->id = id;
463+ physicallyEnded = false;
464+ candidates.clear();
465+ watchers.clear();
466+}
467+
468+bool TouchRegistry::TouchInfo::isOwned() const
469+{
470+ return !candidates.isEmpty() && !candidates.first().undecided;
471+}
472+
473+bool TouchRegistry::TouchInfo::ended() const
474+{
475+ Q_ASSERT(isValid());
476+ return physicallyEnded && (isOwned() || candidates.isEmpty());
477+}
478+
479+void TouchRegistry::TouchInfo::notifyCandidatesOfOwnershipResolution()
480+{
481+ Q_ASSERT(isOwned());
482+
483+ #if TOUCHREGISTRY_DEBUG
484+ UG_DEBUG << "sending TouchOwnershipEvent(id =" << id
485+ << " gained) to candidate" << candidates[0].item;
486+ #endif
487+
488+ TouchOwnershipEvent gainedOwnershipEvent(id, true /*gained*/);
489+ QCoreApplication::sendEvent(candidates[0].item, &gainedOwnershipEvent);
490+
491+
492+ TouchOwnershipEvent lostOwnershipEvent(id, false /*gained*/);
493+ for (int i = 1; i < candidates.count(); ++i) {
494+ #if TOUCHREGISTRY_DEBUG
495+ UG_DEBUG << "sending TouchWonershipEvent(id =" << id << " lost) to candidate"
496+ << candidates[i].item;
497+ #endif
498+ QCoreApplication::sendEvent(candidates[i].item, &lostOwnershipEvent);
499+ }
500+}
libs/UbuntuGestures/TouchRegistry.hnew file+183
@@ -0,0 +1,183 @@
1+/*
2+ * Copyright (C) 2014 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 UNITY_TOUCHREGISTRY_H
18+#define UNITY_TOUCHREGISTRY_H
19+
20+#include <QQuickItem>
21+#include <QObject>
22+#include <QPointer>
23+#include <QTouchEvent>
24+#include <QVector>
25+
26+#include "UbuntuGesturesGlobal.h"
27+#include "CandidateInactivityTimer.h"
28+#include "Pool.h"
29+
30+namespace UbuntuGestures {
31+ class AbstractTimerFactory;
32+}
33+
34+/*
35+ Where the ownership of touches is registered.
36+
37+ Singleton used for adding a touch point ownership model analogous to the one
38+ described in the XInput 2.2 protocol[1] on top of the existing input dispatch logic in QQuickWindow.
39+
40+ It provides a much more flexible and powerful way of dealing with pointer ownership than the existing
41+ mechanisms in Qt. Namely QQuickItem::grabTouchPoints, QuickItem::keepTouchGrab,
42+ QQuickItem::setFiltersChildMouseEvents, QQuickItem::ungrabTouchPoints and QQuickItem::touchUngrabEvent.
43+
44+ Usage:
45+
46+ 1- An item receives a a new touch point. If he's not sure whether he wants it yet, he calls:
47+ TouchRegistry::instance()->addCandidateOwnerForTouch(touchId, this);
48+ touchEvent->ignore();
49+ Ignoring the event is crucial so that it can be seen by other interested parties, which will
50+ behave similarly.
51+
52+ 2- That item will then start receiving UnownedTouchEvents for that touch from step 1. Once he's
53+ made a decision he calls either:
54+ TouchRegistry::instance()->requestTouchOwnership(touchId, this);
55+ If he wants the touch point or:
56+ TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, this);
57+  if he does not want it.
58+
59+ Candidates are put in a priority queue. The first one to call addCandidateOwnerForTouch() will
60+ take precedence over the others for receiving ownership over the touch point (from now on called
61+ simply top-candidate).
62+
63+ If the top-candidate calls requestTouchOwnership() he will immediately receive a
64+ TouchOwnershipEvent(gained=true) for that touch point. He can then safely call
65+ QQuickItem::grabTouchPoints to actually get the owned touch points. The other candidates
66+ will receive TouchOwnershipEvent(gained=false) and will no longer receive UnownedTouchEvents
67+ for that touch point. They will have to undo whatever action they were performing with that
68+ touch point.
69+
70+ But if the top-candidate calls removeCandidateOwnerForTouch() instead, he's popped from the
71+ candidacy queue and ownership is given to the new top-most candidate if he has already
72+ made his decision, that is.
73+
74+ The TouchRegistry cannot enforce the results of this pointer ownership negotiation (i.e.,
75+ who gets to grab the touch points) as that would clash with QQuickWindow's input event
76+ dispatching logic. The candidates have to respect the decision and grab the touch points
77+ themselves.
78+
79+ If an item wants ownership over touches as soon as he receives the TouchBegin for them, his step 1
80+ would be instead:
81+ TouchRegistry::instance()->requestTouchOwnership(touchId, this);
82+ return true;
83+ He would then be notified once ownership has been granted to him, from which point onwards he could
84+ safely assume other TouchRegistry users wouldn't snatch this touch away from him.
85+
86+ Items oblivious to TouchRegistry will lose their touch points without warning, just like in plain Qt.
87+
88+ [1] - http://www.x.org/releases/X11R7.7/doc/inputproto/XI2proto.txt (see multitouch-ownership)
89+ */
90+class UBUNTUGESTURES_EXPORT TouchRegistry : public QObject
91+{
92+ Q_OBJECT
93+public:
94+ TouchRegistry(QObject *parent = nullptr);
95+
96+ virtual ~TouchRegistry();
97+
98+ // Returns a pointer to the application's TouchRegistry instance.
99+ // If no instance has been allocated, null is returned.
100+ static TouchRegistry *instance() { return m_instance; }
101+
102+ void update(const QTouchEvent *event);
103+
104+ // Calls update() if the given event is a QTouchEvent
105+ bool eventFilter(QObject *watched, QEvent *event) override;
106+
107+ // An item that might later request ownership over the given touch point.
108+ // He will be kept informed about that touch point through UnownedTouchEvents
109+ // All candidates must eventually decide whether they want to own the touch point
110+ // or not. That decision is informed through requestTouchOwnership() or
111+ // removeCandidateOwnerForTouch()
112+ void addCandidateOwnerForTouch(int id, QQuickItem *candidate);
113+
114+ // The same as rejecting ownership of a touch
115+ void removeCandidateOwnerForTouch(int id, QQuickItem *candidate);
116+
117+ // The candidate object wants to be the owner of the touch with the given id.
118+ // If he's currently the oldest/top-most candidate, he will get an ownership
119+ // event immediately. If not, he will get ownership if (or once) he becomes the
120+ // top-most candidate.
121+ void requestTouchOwnership(int id, QQuickItem *candidate);
122+
123+ // An item that has no interest (effective or potential) in owning a touch point
124+ // but would nonetheless like to be kept up-to-date on its state.
125+ void addTouchWatcher(int touchId, QQuickItem *watcherItem);
126+
127+private Q_SLOTS:
128+ void rejectCandidateOwnerForTouch(int id, QQuickItem *candidate);
129+
130+private:
131+ // Useful for tests, where you should feed a fake timer
132+ TouchRegistry(QObject *parent, UbuntuGestures::AbstractTimerFactory *timerFactory);
133+
134+ class CandidateInfo {
135+ public:
136+ bool undecided;
137+ // TODO: Prune candidates that become null and resolve ownership accordingly.
138+ QPointer<QQuickItem> item;
139+ QPointer<UbuntuGestures::CandidateInactivityTimer> inactivityTimer;
140+ };
141+
142+ class TouchInfo {
143+ public:
144+ TouchInfo() : id(-1) {}
145+ TouchInfo(int id);
146+ bool isValid() const { return id >= 0; }
147+ void reset();
148+ void init(int id);
149+ int id;
150+ bool physicallyEnded;
151+ bool isOwned() const;
152+ bool ended() const;
153+ void notifyCandidatesOfOwnershipResolution();
154+
155+ // TODO optimize storage (s/QList/Pool)
156+ QList<CandidateInfo> candidates;
157+ QList<QPointer<QQuickItem>> watchers;
158+ };
159+
160+ Pool<TouchInfo>::Iterator findTouchInfo(int id);
161+
162+ void deliverTouchUpdatesToUndecidedCandidatesAndWatchers(const QTouchEvent *event);
163+
164+ static void translateTouchPointFromScreenToWindowCoords(QTouchEvent::TouchPoint &touchPoint);
165+
166+ static void dispatchPointsToItem(const QTouchEvent *event, const QList<int> &touchIds,
167+ QQuickItem *item);
168+ void freeEndedTouchInfos();
169+
170+ Pool<TouchInfo> m_touchInfoPool;
171+
172+ // the singleton instance
173+ static TouchRegistry *m_instance;
174+
175+ bool m_inDispatchLoop;
176+
177+ UbuntuGestures::AbstractTimerFactory *m_timerFactory;
178+
179+ friend class tst_TouchRegistry;
180+ friend class tst_DirectionalDragArea;
181+};
182+
183+#endif // UNITY_TOUCHREGISTRY_H
plugins/Ubuntu/Gestures/UbuntuGesturesGlobal.h → libs/UbuntuGestures/UbuntuGesturesGlobal.hrenamed+1−1
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2014 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
libs/UbuntuGestures/UnownedTouchEvent.cppnew file+39
@@ -0,0 +1,39 @@
1+/*
2+ * Copyright (C) 2014 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 "UnownedTouchEvent.h"
18+
19+QEvent::Type UnownedTouchEvent::m_unownedTouchEventType = (QEvent::Type)-1;
20+
21+UnownedTouchEvent::UnownedTouchEvent(QTouchEvent *touchEvent)
22+ : QEvent(unownedTouchEventType())
23+ , m_touchEvent(touchEvent)
24+{
25+}
26+
27+QEvent::Type UnownedTouchEvent::unownedTouchEventType()
28+{
29+ if (m_unownedTouchEventType == (QEvent::Type)-1) {
30+ m_unownedTouchEventType = (QEvent::Type)registerEventType();
31+ }
32+
33+ return m_unownedTouchEventType;
34+}
35+
36+QTouchEvent *UnownedTouchEvent::touchEvent()
37+{
38+ return m_touchEvent.data();
39+}
libs/UbuntuGestures/UnownedTouchEvent.hnew file+45
@@ -0,0 +1,45 @@
1+/*
2+ * Copyright (C) 2014 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 UBUNTU_UNOWNEDTOUCHEVENT_H
18+#define UBUNTU_UNOWNEDTOUCHEVENT_H
19+
20+#include <QTouchEvent>
21+#include <QScopedPointer>
22+#include "UbuntuGesturesGlobal.h"
23+
24+/*
25+ A touch event with touch points that do not belong the item receiving it.
26+
27+ See TouchRegistry::addCandidateOwnerForTouch and TouchRegistry::addTouchWatcher
28+ */
29+class UBUNTUGESTURES_EXPORT UnownedTouchEvent : public QEvent
30+{
31+public:
32+ UnownedTouchEvent(QTouchEvent *touchEvent);
33+ static Type unownedTouchEventType();
34+
35+ // TODO: It might be cleaner to store the information directly in UnownedTouchEvent
36+ // instead of carrying around a synthesized QTouchEvent. But the latter option
37+ // is very convenient.
38+ QTouchEvent *touchEvent();
39+
40+private:
41+ static Type m_unownedTouchEventType;
42+ QScopedPointer<QTouchEvent> m_touchEvent;
43+};
44+
45+#endif // UBUNTU_UNOWNEDTOUCHEVENT_H
plugins/Ubuntu/Gestures/AxisVelocityCalculator.h+2−2
@@ -21,7 +21,7 @@
21#ifndef VELOCITY_CALCULATOR_H
21#ifndef VELOCITY_CALCULATOR_H
22#define VELOCITY_CALCULATOR_H
22#define VELOCITY_CALCULATOR_H
23
23
24−#include "UbuntuGesturesGlobal.h"
24+#include "UbuntuGesturesQmlGlobal.h"
25#include <stdint.h>
25#include <stdint.h>
26#include <QtCore/QObject>
26#include <QtCore/QObject>
27#include "TimeSource.h"
27#include "TimeSource.h"
@@ -49,7 +49,7 @@
49 }
49 }
50 }
50 }
51 */
51 */
52−class UBUNTUGESTURES_EXPORT AxisVelocityCalculator : public QObject
52+class UBUNTUGESTURESQML_EXPORT AxisVelocityCalculator : public QObject
53{
53{
54 Q_OBJECT
54 Q_OBJECT
55
55
plugins/Ubuntu/Gestures/CMakeLists.txt+24−5
@@ -1,19 +1,38 @@
1−set(UbuntuGestureQml_SOURCES
1+# in order to include Qt's private headers
2+remove_definitions(-DQT_NO_KEYWORDS)
3+
4+set(UbuntuGesturesQml_SOURCES
2 plugin.cpp
5 plugin.cpp
3 AxisVelocityCalculator.cpp
6 AxisVelocityCalculator.cpp
4 Direction.cpp
7 Direction.cpp
5 DirectionalDragArea.cpp
8 DirectionalDragArea.cpp
6 PressedOutsideNotifier.cpp
9 PressedOutsideNotifier.cpp
7 TimeSource.cpp
10 TimeSource.cpp
11+ TouchGate.cpp
8)
12)
9
13
10−add_definitions(-DUBUNTUGESTURES_LIBRARY)
14+add_definitions(-DUBUNTUGESTURESQML_LIBRARY)
11
15
12−add_library(UbuntuGestureQml MODULE ${UbuntuGestureQml_SOURCES})
16+add_library(UbuntuGesturesQml MODULE ${UbuntuGesturesQml_SOURCES})
17+target_link_libraries(UbuntuGesturesQml UbuntuGestures)
13
18
14−qt5_use_modules(UbuntuGestureQml Core Quick)
19+qt5_use_modules(UbuntuGesturesQml Core Quick)
15
20
16# So that Foo.cpp can #include "Foo.moc"
21# So that Foo.cpp can #include "Foo.moc"
17include_directories(${CMAKE_CURRENT_BINARY_DIR})
22include_directories(${CMAKE_CURRENT_BINARY_DIR})
18
23
24+include_directories(${CMAKE_SOURCE_DIR}/libs/UbuntuGestures)
25+
26+# There's no cmake var for v8 include path :-/ so create one
27+LIST(GET Qt5Core_INCLUDE_DIRS 0 QtCoreDir0)
28+SET(Qt5V8_PRIVATE_INCLUDE_DIR ${QtCoreDir0}/QtV8/${Qt5Core_VERSION_STRING}/QtV8)
29+
30+# DANGER! DANGER! Using Qt's private API!
31+include_directories(
32+ ${Qt5Qml_PRIVATE_INCLUDE_DIRS}
33+ ${Qt5Quick_INCLUDE_DIRS}
34+ ${Qt5Quick_PRIVATE_INCLUDE_DIRS}
35+ ${Qt5V8_PRIVATE_INCLUDE_DIR}
36+)
37+
19−add_unity8_plugin(Ubuntu.Gestures 0.1 Ubuntu/Gestures TARGETS UbuntuGestureQml)
38+add_unity8_plugin(Ubuntu.Gestures 0.1 Ubuntu/Gestures TARGETS UbuntuGesturesQml)
plugins/Ubuntu/Gestures/Direction.h+2−2
@@ -17,14 +17,14 @@
17#ifndef DIRECTION_H
17#ifndef DIRECTION_H
18#define DIRECTION_H
18#define DIRECTION_H
19
19
20−#include "UbuntuGesturesGlobal.h"
20+#include "UbuntuGesturesQmlGlobal.h"
21#include <QObject>
21#include <QObject>
22
22
23/*
23/*
24 A Direction enum wrapper so that we can do things like "direction: Direction.Righwards"
24 A Direction enum wrapper so that we can do things like "direction: Direction.Righwards"
25 from QML.
25 from QML.
26 */
26 */
27−class UBUNTUGESTURES_EXPORT Direction : public QObject {
27+class UBUNTUGESTURESQML_EXPORT Direction : public QObject {
28 Q_OBJECT
28 Q_OBJECT
29 Q_ENUMS(Type)
29 Q_ENUMS(Type)
30
30
plugins/Ubuntu/Gestures/DirectionalDragArea.cpp+299−189
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013-2014 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
@@ -14,64 +14,26 @@
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
15 */
16
16
17+#define ACTIVETOUCHESINFO_DEBUG 0
18+#define DIRECTIONALDRAGAREA_DEBUG 0
19+
17#include "DirectionalDragArea.h"
20#include "DirectionalDragArea.h"
18
21
19#include <QtCore/qmath.h>
22#include <QtCore/qmath.h>
20−#include <QtCore/QTimer>
21#include <QDebug>
23#include <QDebug>
22
24
23−using namespace UbuntuGestures;
25+// local
26+#include "TouchOwnershipEvent.h"
27+#include "TouchRegistry.h"
28+#include "UnownedTouchEvent.h"
24
29
25−#define DIRECTIONALDRAGAREA_DEBUG 0
30+using namespace UbuntuGestures;
26
31
27#if DIRECTIONALDRAGAREA_DEBUG
32#if DIRECTIONALDRAGAREA_DEBUG
28#define DDA_DEBUG(msg) qDebug("[DDA] " msg)
33#define DDA_DEBUG(msg) qDebug("[DDA] " msg)
29−namespace {
34+#include "DebugHelpers.h"
30−QString touchPointStateToString(Qt::TouchPointState state) {
31− switch (state) {
32− case Qt::TouchPointPressed:
33− return QString("pressed");
34− case Qt::TouchPointMoved:
35− return QString("moved");
36− case Qt::TouchPointStationary:
37− return QString("stationary");
38− default: // Qt::TouchPointReleased:
39− return QString("released");
40− }
41−}
42−QString touchEventToString(QTouchEvent *ev)
43−{
44− QString message;
45−
46− switch (ev->type()) {
47− case QEvent::TouchBegin:
48− message.append("TouchBegin ");
49− break;
50− case QEvent::TouchUpdate:
51− message.append("TouchUpdate ");
52− break;
53− case QEvent::TouchEnd:
54− message.append("TouchEnd ");
55− break;
56− default: //QEvent::TouchCancel
57− message.append("TouchCancel ");
58− }
59−
60− for (int i=0; i < ev->touchPoints().size(); ++i) {
61−
62− const QTouchEvent::TouchPoint& touchPoint = ev->touchPoints().at(i);
63− message.append(
64− QString("(id:%1, state:%2, scenePos:(%3,%4)) ")
65− .arg(touchPoint.id())
66− .arg(touchPointStateToString(touchPoint.state()))
67− .arg(touchPoint.scenePos().x())
68− .arg(touchPoint.scenePos().y())
69− );
70− }
71−
72− return message;
73−}
74
35
36+namespace {
75const char *statusToString(DirectionalDragArea::Status status)
37const char *statusToString(DirectionalDragArea::Status status)
76{
38{
77 if (status == DirectionalDragArea::WaitingForTouch) {
39 if (status == DirectionalDragArea::WaitingForTouch) {
@@ -88,23 +50,6 @@ const char *statusToString(DirectionalDragArea::Status status)
88#define DDA_DEBUG(msg) do{}while(0)
50#define DDA_DEBUG(msg) do{}while(0)
89#endif // DIRECTIONALDRAGAREA_DEBUG
51#endif // DIRECTIONALDRAGAREA_DEBUG
90
52
91−// Essentially a QTimer wrapper
92−class RecognitionTimer : public UbuntuGestures::AbstractTimer
93−{
94− Q_OBJECT
95−public:
96− RecognitionTimer(QObject *parent) : UbuntuGestures::AbstractTimer(parent) {
97− m_timer.setSingleShot(false);
98− connect(&m_timer, &QTimer::timeout,
99− this, &UbuntuGestures::AbstractTimer::timeout);
100− }
101− virtual int interval() const { return m_timer.interval(); }
102− virtual void setInterval(int msecs) { m_timer.setInterval(msecs); }
103− virtual void start() { m_timer.start(); UbuntuGestures::AbstractTimer::start(); }
104− virtual void stop() { m_timer.stop(); UbuntuGestures::AbstractTimer::stop(); }
105−private:
106− QTimer m_timer;
107−};
108
53
109DirectionalDragArea::DirectionalDragArea(QQuickItem *parent)
54DirectionalDragArea::DirectionalDragArea(QQuickItem *parent)
110 : QQuickItem(parent)
55 : QQuickItem(parent)
@@ -126,12 +71,14 @@ DirectionalDragArea::DirectionalDragArea(QQuickItem *parent)
126 , m_timeSource(new RealTimeSource)
71 , m_timeSource(new RealTimeSource)
127 , m_activeTouches(m_timeSource)
72 , m_activeTouches(m_timeSource)
128{
73{
129− setRecognitionTimer(new RecognitionTimer(this));
74+ setRecognitionTimer(new Timer(this));
130 m_recognitionTimer->setInterval(60);
75 m_recognitionTimer->setInterval(60);
76+ m_recognitionTimer->setSingleShot(false);
131
77
132 m_velocityCalculator = new AxisVelocityCalculator(this);
78 m_velocityCalculator = new AxisVelocityCalculator(this);
133
79
134− connect(this, &QQuickItem::enabledChanged, this, &DirectionalDragArea::onEnabledChanged);
80+ connect(this, &QQuickItem::enabledChanged, this, &DirectionalDragArea::giveUpIfDisabledOrInvisible);
81+ connect(this, &QQuickItem::visibleChanged, this, &DirectionalDragArea::giveUpIfDisabledOrInvisible);
135}
82}
136
83
137Direction::Type DirectionalDragArea::direction() const
84Direction::Type DirectionalDragArea::direction() const
@@ -214,6 +161,7 @@ void DirectionalDragArea::setRecognitionTimer(UbuntuGestures::AbstractTimer *tim
214{
161{
215 int interval = 0;
162 int interval = 0;
216 bool timerWasRunning = false;
163 bool timerWasRunning = false;
164+ bool wasSingleShot = false;
217
165
218 // can be null when called from the constructor
166 // can be null when called from the constructor
219 if (m_recognitionTimer) {
167 if (m_recognitionTimer) {
@@ -226,6 +174,7 @@ void DirectionalDragArea::setRecognitionTimer(UbuntuGestures::AbstractTimer *tim
226
174
227 m_recognitionTimer = timer;
175 m_recognitionTimer = timer;
228 timer->setInterval(interval);
176 timer->setInterval(interval);
177+ timer->setSingleShot(wasSingleShot);
229 connect(timer, &UbuntuGestures::AbstractTimer::timeout,
178 connect(timer, &UbuntuGestures::AbstractTimer::timeout,
230 this, &DirectionalDragArea::checkSpeed);
179 this, &DirectionalDragArea::checkSpeed);
231 if (timerWasRunning) {
180 if (timerWasRunning) {
@@ -280,8 +229,131 @@ qreal DirectionalDragArea::touchSceneY() const
280 return m_previousScenePos.y();
229 return m_previousScenePos.y();
281}
230}
282
231
232+bool DirectionalDragArea::event(QEvent *event)
233+{
234+ if (event->type() == TouchOwnershipEvent::touchOwnershipEventType()) {
235+ touchOwnershipEvent(static_cast<TouchOwnershipEvent *>(event));
236+ return true;
237+ } else if (event->type() == UnownedTouchEvent::unownedTouchEventType()) {
238+ unownedTouchEvent(static_cast<UnownedTouchEvent *>(event));
239+ return true;
240+ } else {
241+ return QQuickItem::event(event);
242+ }
243+}
244+
245+void DirectionalDragArea::touchOwnershipEvent(TouchOwnershipEvent *event)
246+{
247+ if (event->gained()) {
248+ QVector<int> ids;
249+ ids.append(event->touchId());
250+ grabTouchPoints(ids);
251+ } else {
252+ // We still wanna know when it ends for keeping the composition time window up-to-date
253+ TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
254+
255+ setStatus(WaitingForTouch);
256+ }
257+}
258+
259+void DirectionalDragArea::unownedTouchEvent(UnownedTouchEvent *unownedTouchEvent)
260+{
261+ QTouchEvent *event = unownedTouchEvent->touchEvent();
262+
263+ Q_ASSERT(!event->touchPointStates().testFlag(Qt::TouchPointPressed));
264+
265+ #if DIRECTIONALDRAGAREA_DEBUG
266+ // TODO Consider using qCDebug() when available (Qt 5.2)
267+ qDebug() << "[DDA] Unowned" << m_timeSource->msecsSinceReference()
268+ << qPrintable(touchEventToString(event));
269+ #endif
270+
271+ switch (m_status) {
272+ case WaitingForTouch:
273+ // do nothing
274+ break;
275+ case Undecided:
276+ Q_ASSERT(isEnabled() && isVisible());
277+ unownedTouchEvent_undecided(unownedTouchEvent);
278+ break;
279+ default: // Recognized:
280+ // do nothing
281+ break;
282+ }
283+
284+ m_activeTouches.update(event);
285+}
286+
287+void DirectionalDragArea::unownedTouchEvent_undecided(UnownedTouchEvent *unownedTouchEvent)
288+{
289+ const QTouchEvent::TouchPoint *touchPoint = fetchTargetTouchPoint(unownedTouchEvent->touchEvent());
290+ if (!touchPoint) {
291+ qCritical() << "DirectionalDragArea[status=Undecided]: touch " << m_touchId
292+ << "missing from UnownedTouchEvent without first reaching state Qt::TouchPointReleased. "
293+ "Considering it as released.";
294+
295+ TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
296+ setStatus(WaitingForTouch);
297+ return;
298+ }
299+
300+ const QPointF &touchScenePos = touchPoint->scenePos();
301+
302+ if (touchPoint->state() == Qt::TouchPointReleased) {
303+ // touch has ended before recognition concluded
304+ DDA_DEBUG("Touch has ended before recognition concluded");
305+ TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
306+ emitSignalIfTapped();
307+ setStatus(WaitingForTouch);
308+ return;
309+ }
310+
311+ m_previousDampedScenePos.setX(m_dampedScenePos.x());
312+ m_previousDampedScenePos.setY(m_dampedScenePos.y());
313+ m_dampedScenePos.update(touchScenePos);
314+ updateVelocityCalculator(touchScenePos);
315+
316+ if (!pointInsideAllowedArea()) {
317+ DDA_DEBUG("Rejecting gesture because touch point is outside allowed area.");
318+ TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
319+ // We still wanna know when it ends for keeping the composition time window up-to-date
320+ TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
321+ setStatus(WaitingForTouch);
322+ return;
323+ }
324+
325+ if (!movingInRightDirection()) {
326+ DDA_DEBUG("Rejecting gesture because touch point is moving in the wrong direction.");
327+ TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
328+ // We still wanna know when it ends for keeping the composition time window up-to-date
329+ TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
330+ setStatus(WaitingForTouch);
331+ return;
332+ }
333+
334+ setPreviousPos(touchPoint->pos());
335+ setPreviousScenePos(touchScenePos);
336+
337+ if (isWithinTouchCompositionWindow()) {
338+ // There's still time for some new touch to appear and ruin our party as it would be combined
339+ // with our m_touchId one and therefore deny the possibility of a single-finger gesture.
340+ DDA_DEBUG("Sill within composition window. Let's wait more.");
341+ return;
342+ }
343+
344+ if (movedFarEnough(touchScenePos)) {
345+ TouchRegistry::instance()->requestTouchOwnership(m_touchId, this);
346+ setStatus(Recognized);
347+ } else {
348+ DDA_DEBUG("Didn't move far enough yet. Let's wait more.");
349+ }
350+}
351+
283void DirectionalDragArea::touchEvent(QTouchEvent *event)
352void DirectionalDragArea::touchEvent(QTouchEvent *event)
284{
353{
354+ // TODO: Consider when more than one touch starts in the same event (although it's not possible
355+ // with Mir's android-input). Have to track them all. Consider it a plus/bonus.
356+
285 #if DIRECTIONALDRAGAREA_DEBUG
357 #if DIRECTIONALDRAGAREA_DEBUG
286 // TODO Consider using qCDebug() when available (Qt 5.2)
358 // TODO Consider using qCDebug() when available (Qt 5.2)
287 qDebug() << "[DDA]" << m_timeSource->msecsSinceReference()
359 qDebug() << "[DDA]" << m_timeSource->msecsSinceReference()
@@ -310,18 +382,23 @@ void DirectionalDragArea::touchEvent(QTouchEvent *event)
310
382
311void DirectionalDragArea::touchEvent_absent(QTouchEvent *event)
383void DirectionalDragArea::touchEvent_absent(QTouchEvent *event)
312{
384{
385+ // TODO: accept/reject is for the whole event, not per touch id. See how that affects us.
386+
313 if (!event->touchPointStates().testFlag(Qt::TouchPointPressed)) {
387 if (!event->touchPointStates().testFlag(Qt::TouchPointPressed)) {
314 // Nothing to see here. No touch starting in this event.
388 // Nothing to see here. No touch starting in this event.
315 return;
389 return;
316 }
390 }
317
391
392+ // to be proven wrong, if that's the case
393+ bool allGood = true;
394+
318 if (isWithinTouchCompositionWindow()) {
395 if (isWithinTouchCompositionWindow()) {
319 // too close to the last touch start. So we consider them as starting roughly at the same time.
396 // too close to the last touch start. So we consider them as starting roughly at the same time.
320 // Can't be a single-touch gesture.
397 // Can't be a single-touch gesture.
321 #if DIRECTIONALDRAGAREA_DEBUG
398 #if DIRECTIONALDRAGAREA_DEBUG
322 qDebug("[DDA] A new touch point came in but we're still within time composition window. Ignoring it.");
399 qDebug("[DDA] A new touch point came in but we're still within time composition window. Ignoring it.");
323 #endif
400 #endif
324− return;
401+ allGood = false;
325 }
402 }
326
403
327 const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
404 const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
@@ -332,7 +409,7 @@ void DirectionalDragArea::touchEvent_absent(QTouchEvent *event)
332 if (touchPoint.state() == Qt::TouchPointPressed) {
409 if (touchPoint.state() == Qt::TouchPointPressed) {
333 if (newTouchPoint) {
410 if (newTouchPoint) {
334 // more than one touch starting in this QTouchEvent. Can't be a single-touch gesture
411 // more than one touch starting in this QTouchEvent. Can't be a single-touch gesture
335− return;
412+ allGood = false;
336 } else {
413 } else {
337 // that's our candidate
414 // that's our candidate
338 m_touchId = touchPoint.id();
415 m_touchId = touchPoint.id();
@@ -341,86 +418,63 @@ void DirectionalDragArea::touchEvent_absent(QTouchEvent *event)
341 }
418 }
342 }
419 }
343
420
421+ if (allGood) {
344− Q_ASSERT(newTouchPoint);
422+ Q_ASSERT(newTouchPoint);
345
423
346− // If we have made this far, we are good to go to the next status.
347−
348− m_startPos = newTouchPoint->pos();
424+ m_startPos = newTouchPoint->pos();
349− m_startScenePos = newTouchPoint->scenePos();
425+ m_startScenePos = newTouchPoint->scenePos();
350− m_touchId = newTouchPoint->id();
426+ m_touchId = newTouchPoint->id();
351− m_dampedScenePos.reset(m_startScenePos);
427+ m_dampedScenePos.reset(m_startScenePos);
352− m_velocityCalculator->setTrackedPosition(0.);
428+ m_velocityCalculator->setTrackedPosition(0.);
353− m_velocityCalculator->reset();
429+ m_velocityCalculator->reset();
354− m_numSamplesOnLastSpeedCheck = 0;
430+ m_numSamplesOnLastSpeedCheck = 0;
355− m_silenceTime = 0;
431+ m_silenceTime = 0;
356− setPreviousPos(m_startPos);
432+ setPreviousPos(m_startPos);
357− setPreviousScenePos(m_startScenePos);
433+ setPreviousScenePos(m_startScenePos);
358− updateSceneDirectionVector();
434+ updateSceneDirectionVector();
359−
435+
436+ if (recognitionIsDisabled()) {
437+ // Behave like a dumb TouchArea
438+ TouchRegistry::instance()->requestTouchOwnership(m_touchId, this);
439+ setStatus(Recognized);
440+ event->accept();
441+ } else {
442+ // just monitor the touch points for now.
443+ TouchRegistry::instance()->addCandidateOwnerForTouch(m_touchId, this);
444+
360− setStatus(Undecided);
445+ setStatus(Undecided);
446+ // Let the item below have it. We will monitor it and grab it later if a gesture
447+ // gets recognized.
448+ event->ignore();
449+ }
450+ } else {
451+ watchPressedTouchPoints(touchPoints);
452+ event->ignore();
453+ }
361}
454}
362
455
363void DirectionalDragArea::touchEvent_undecided(QTouchEvent *event)
456void DirectionalDragArea::touchEvent_undecided(QTouchEvent *event)
364{
457{
458+ Q_ASSERT(event->type() == QEvent::TouchBegin);
365− const QTouchEvent::TouchPoint *touchPoint = fetchTargetTouchPoint(event);
459+ Q_ASSERT(fetchTargetTouchPoint(event) == nullptr);
366−
367− if (!touchPoint) {
368− qCritical() << "DirectionalDragArea[status=Undecided]: touch " << m_touchId
369− << "missing from QTouchEvent without first reaching state Qt::TouchPointReleased. "
370− "Considering it as released.";
371− setStatus(WaitingForTouch);
372− return;
373− }
374
460
375− const QPointF &touchScenePos = touchPoint->scenePos();
461+ // We're not interested in new touch points. We already have our candidate (m_touchId).
376−
462+ // But we do want to know when those new touches end for keeping the composition time
377− if (touchPoint->state() == Qt::TouchPointReleased) {
463+ // window up-to-date
378− // touch has ended before recognition concluded
464+ event->ignore();
379− DDA_DEBUG("Touch has ended before recognition concluded");
465+ watchPressedTouchPoints(event->touchPoints());
380− setStatus(WaitingForTouch);
381− Q_EMIT tapped();
382− return;
383− }
384
466
385 if (event->touchPointStates().testFlag(Qt::TouchPointPressed) && isWithinTouchCompositionWindow()) {
467 if (event->touchPointStates().testFlag(Qt::TouchPointPressed) && isWithinTouchCompositionWindow()) {
386 // multi-finger drags are not accepted
468 // multi-finger drags are not accepted
387 DDA_DEBUG("Multi-finger drags are not accepted");
469 DDA_DEBUG("Multi-finger drags are not accepted");
388− setStatus(WaitingForTouch);
389− return;
390− }
391
470
392− m_previousDampedScenePos.setX(m_dampedScenePos.x());
471+ TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
393− m_previousDampedScenePos.setY(m_dampedScenePos.y());
472+ // We still wanna know when it ends for keeping the composition time window up-to-date
394− m_dampedScenePos.update(touchScenePos);
473+ TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
395− updateVelocityCalculator(touchScenePos);
396−
397− if (!pointInsideAllowedArea()) {
398− DDA_DEBUG("Rejecting gesture because touch point is outside allowed area.");
399− setStatus(WaitingForTouch);
400− return;
401− }
402
474
403− if (!movingInRightDirection()) {
404− DDA_DEBUG("Rejecting gesture becauuse touch point is moving in the wrong direction.");
405 setStatus(WaitingForTouch);
475 setStatus(WaitingForTouch);
406 return;
476 return;
407 }
477 }
408−
409− setPreviousPos(touchPoint->pos());
410− setPreviousScenePos(touchScenePos);
411−
412− if (isWithinTouchCompositionWindow()) {
413− // There's still time for some new touch to appear and ruin our party as it would be combined
414− // with our m_touchId one and therefore deny the possibility of a single-finger gesture.
415− DDA_DEBUG("Sill within composition window. Let's wait more.");
416− return;
417− }
418−
419− if (movedFarEnough(touchScenePos)) {
420− setStatus(Recognized);
421− } else {
422− DDA_DEBUG("Didn't move far enough yet. Let's wait more.");
423− }
424}
478}
425
479
426void DirectionalDragArea::touchEvent_recognized(QTouchEvent *event)
480void DirectionalDragArea::touchEvent_recognized(QTouchEvent *event)
@@ -437,11 +491,35 @@ void DirectionalDragArea::touchEvent_recognized(QTouchEvent *event)
437 setPreviousScenePos(touchPoint->scenePos());
491 setPreviousScenePos(touchPoint->scenePos());
438
492
439 if (touchPoint->state() == Qt::TouchPointReleased) {
493 if (touchPoint->state() == Qt::TouchPointReleased) {
494+ emitSignalIfTapped();
440 setStatus(WaitingForTouch);
495 setStatus(WaitingForTouch);
441 }
496 }
442 }
497 }
443}
498}
444
499
500+void DirectionalDragArea::watchPressedTouchPoints(const QList<QTouchEvent::TouchPoint> &touchPoints)
501+{
502+ for (int i = 0; i < touchPoints.count(); ++i) {
503+ const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i);
504+ if (touchPoint.state() == Qt::TouchPointPressed) {
505+ TouchRegistry::instance()->addTouchWatcher(touchPoint.id(), this);
506+ }
507+ }
508+}
509+
510+bool DirectionalDragArea::recognitionIsDisabled() const
511+{
512+ return distanceThreshold() <= 0 && compositionTime() <= 0;
513+}
514+
515+void DirectionalDragArea::emitSignalIfTapped()
516+{
517+ qint64 touchDuration = m_timeSource->msecsSinceReference() - m_activeTouches.touchStartTime(m_touchId);
518+ if (touchDuration <= maxTapDuration()) {
519+ Q_EMIT tapped();
520+ }
521+}
522+
445const QTouchEvent::TouchPoint *DirectionalDragArea::fetchTargetTouchPoint(QTouchEvent *event)
523const QTouchEvent::TouchPoint *DirectionalDragArea::fetchTargetTouchPoint(QTouchEvent *event)
446{
524{
447 const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
525 const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
@@ -509,12 +587,16 @@ bool DirectionalDragArea::movedFarEnough(const QPointF &point) const
509
587
510void DirectionalDragArea::checkSpeed()
588void DirectionalDragArea::checkSpeed()
511{
589{
590+ Q_ASSERT(m_status == Undecided);
591+
512 if (m_velocityCalculator->numSamples() >= AxisVelocityCalculator::MIN_SAMPLES_NEEDED) {
592 if (m_velocityCalculator->numSamples() >= AxisVelocityCalculator::MIN_SAMPLES_NEEDED) {
513 qreal speed = qFabs(m_velocityCalculator->calculate());
593 qreal speed = qFabs(m_velocityCalculator->calculate());
514 qreal minSpeedMsecs = m_minSpeed / 1000.0;
594 qreal minSpeedMsecs = m_minSpeed / 1000.0;
515
595
516 if (speed < minSpeedMsecs) {
596 if (speed < minSpeedMsecs) {
517 DDA_DEBUG("Rejecting gesture because it's below minimum speed.");
597 DDA_DEBUG("Rejecting gesture because it's below minimum speed.");
598+ TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
599+ TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
518 setStatus(WaitingForTouch);
600 setStatus(WaitingForTouch);
519 }
601 }
520 }
602 }
@@ -523,7 +605,9 @@ void DirectionalDragArea::checkSpeed()
523 m_silenceTime += m_recognitionTimer->interval();
605 m_silenceTime += m_recognitionTimer->interval();
524
606
525 if (m_silenceTime > m_maxSilenceTime) {
607 if (m_silenceTime > m_maxSilenceTime) {
526− DDA_DEBUG("Rejecting gesture because it's silence time has been exceeded.");
608+ DDA_DEBUG("Rejecting gesture because its silence time has been exceeded.");
609+ TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
610+ TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
527 setStatus(WaitingForTouch);
611 setStatus(WaitingForTouch);
528 }
612 }
529 } else {
613 } else {
@@ -532,10 +616,19 @@ void DirectionalDragArea::checkSpeed()
532 m_numSamplesOnLastSpeedCheck = m_velocityCalculator->numSamples();
616 m_numSamplesOnLastSpeedCheck = m_velocityCalculator->numSamples();
533}
617}
534
618
535−void DirectionalDragArea::onEnabledChanged()
619+void DirectionalDragArea::giveUpIfDisabledOrInvisible()
536{
620{
621+ if (!isEnabled() || !isVisible()) {
622+ if (m_status == Undecided) {
623+ TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
624+ // We still wanna know when it ends for keeping the composition time window up-to-date
625+ TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
626+ }
627+
537− if (!isEnabled() && m_status != WaitingForTouch) {
628+ if (m_status != WaitingForTouch) {
629+ DDA_DEBUG("Resetting status because got disabled or made invisible");
538− setStatus(WaitingForTouch);
630+ setStatus(WaitingForTouch);
631+ }
539 }
632 }
540}
633}
541
634
@@ -560,6 +653,8 @@ void DirectionalDragArea::setStatus(DirectionalDragArea::Status newStatus)
560 switch (newStatus) {
653 switch (newStatus) {
561 case WaitingForTouch:
654 case WaitingForTouch:
562 Q_EMIT draggingChanged(false);
655 Q_EMIT draggingChanged(false);
656+ if (oldStatus == Undecided) {
657+ }
563 break;
658 break;
564 case Undecided:
659 case Undecided:
565 m_recognitionTimer->start();
660 m_recognitionTimer->start();
@@ -631,7 +726,9 @@ void DirectionalDragArea::updateVelocityCalculator(const QPointF &scenePos)
631
726
632bool DirectionalDragArea::isWithinTouchCompositionWindow()
727bool DirectionalDragArea::isWithinTouchCompositionWindow()
633{
728{
729+ return
730+ compositionTime() > 0 &&
634− return !m_activeTouches.isEmpty() &&
731+ !m_activeTouches.isEmpty() &&
635 m_timeSource->msecsSinceReference() <=
732 m_timeSource->msecsSinceReference() <=
636 m_activeTouches.mostRecentStartTime() + (qint64)compositionTime();
733 m_activeTouches.mostRecentStartTime() + (qint64)compositionTime();
637}
734}
@@ -639,18 +736,17 @@ bool DirectionalDragArea::isWithinTouchCompositionWindow()
639//************************** ActiveTouchesInfo **************************
736//************************** ActiveTouchesInfo **************************
640
737
641DirectionalDragArea::ActiveTouchesInfo::ActiveTouchesInfo(const SharedTimeSource &timeSource)
738DirectionalDragArea::ActiveTouchesInfo::ActiveTouchesInfo(const SharedTimeSource &timeSource)
642− : m_timeSource(timeSource), m_lastUsedIndex(-1)
739+ : m_timeSource(timeSource)
643{
740{
644− // Estimate of the maximum number of active touches we might reach.
645− // Not a problem if it ends up being an underestimate as this is just
646− // an optimization.
647− m_vector.resize(3);
648}
741}
649
742
650void DirectionalDragArea::ActiveTouchesInfo::update(QTouchEvent *event)
743void DirectionalDragArea::ActiveTouchesInfo::update(QTouchEvent *event)
651{
744{
652 if (!(event->touchPointStates() & (Qt::TouchPointPressed | Qt::TouchPointReleased))) {
745 if (!(event->touchPointStates() & (Qt::TouchPointPressed | Qt::TouchPointReleased))) {
653 // nothing to update
746 // nothing to update
747+ #if ACTIVETOUCHESINFO_DEBUG
748+ qDebug("[DDA::ActiveTouchesInfo] Nothing to Update");
749+ #endif
654 return;
750 return;
655 }
751 }
656
752
@@ -658,74 +754,88 @@ void DirectionalDragArea::ActiveTouchesInfo::update(QTouchEvent *event)
658 for (int i = 0; i < touchPoints.count(); ++i) {
754 for (int i = 0; i < touchPoints.count(); ++i) {
659 const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i);
755 const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i);
660 if (touchPoint.state() == Qt::TouchPointPressed) {
756 if (touchPoint.state() == Qt::TouchPointPressed) {
661− addTouchPoint(touchPoint);
757+ addTouchPoint(touchPoint.id());
662 } else if (touchPoint.state() == Qt::TouchPointReleased) {
758 } else if (touchPoint.state() == Qt::TouchPointReleased) {
663− removeTouchPoint(touchPoint);
759+ removeTouchPoint(touchPoint.id());
664 }
760 }
665 }
761 }
666}
762}
667
763
764+#if ACTIVETOUCHESINFO_DEBUG
668−void DirectionalDragArea::ActiveTouchesInfo::addTouchPoint(const QTouchEvent::TouchPoint &touchPoint)
765+QString DirectionalDragArea::ActiveTouchesInfo::toString()
669{
766{
670− ActiveTouchInfo &activeTouchInfo = getEmptySlot();
767+ QString string = "(";
671− activeTouchInfo.id = touchPoint.id();
768+
672− activeTouchInfo.startTime = m_timeSource->msecsSinceReference();
769+ {
770+ QTextStream stream(&string);
771+ m_touchInfoPool.forEach([&](Pool<ActiveTouchInfo>::Iterator &touchInfo) {
772+ stream << "(id=" << touchInfo->id << ",startTime=" << touchInfo->startTime << ")";
773+ return true;
774+ });
775+ }
776+
777+ string.append(")");
778+
779+ return string;
673}
780}
781+#endif // ACTIVETOUCHESINFO_DEBUG
674
782
675−void DirectionalDragArea::ActiveTouchesInfo::removeTouchPoint(const QTouchEvent::TouchPoint &touchPoint)
783+void DirectionalDragArea::ActiveTouchesInfo::addTouchPoint(int touchId)
676{
784{
677− for (int i = 0; i <= m_lastUsedIndex; ++i) {
785+ ActiveTouchInfo &activeTouchInfo = m_touchInfoPool.getEmptySlot();
678− if (touchPoint.id() == m_vector.at(i).id) {
786+ activeTouchInfo.id = touchId;
679− freeSlot(i);
787+ activeTouchInfo.startTime = m_timeSource->msecsSinceReference();
680− return;
788+
681− }
789+ #if ACTIVETOUCHESINFO_DEBUG
682− }
790+ qDebug() << "[DDA::ActiveTouchesInfo]" << qPrintable(toString());
683− Q_ASSERT(false); // shouldn't reach this point
791+ #endif
684}
792}
685
793
686−DirectionalDragArea::ActiveTouchInfo &DirectionalDragArea::ActiveTouchesInfo::getEmptySlot()
794+qint64 DirectionalDragArea::ActiveTouchesInfo::touchStartTime(int touchId)
687{
795{
688− Q_ASSERT(m_lastUsedIndex < m_vector.size());
796+ qint64 result = -1;
689
797
690− // Look for an in-between vacancy first
798+ m_touchInfoPool.forEach([&](Pool<ActiveTouchInfo>::Iterator &touchInfo) {
691− for (int i = 0; i < m_lastUsedIndex; ++i) {
799+ if (touchId == touchInfo->id) {
692− ActiveTouchInfo &activeTouchInfo = m_vector[i];
800+ result = touchInfo->startTime;
693− if (!activeTouchInfo.isValid()) {
801+ return false;
694− return activeTouchInfo;
802+ } else {
803+ return true;
695 }
804 }
696− }
805+ });
697−
698− ++m_lastUsedIndex;
699− if (m_lastUsedIndex >= m_vector.size()) {
700− m_vector.resize(m_lastUsedIndex + 1);
701− }
702
806
703− return m_vector[m_lastUsedIndex];
807+ Q_ASSERT(result != -1);
808+ return result;
704}
809}
705
810
706−void DirectionalDragArea::ActiveTouchesInfo::freeSlot(int index)
811+void DirectionalDragArea::ActiveTouchesInfo::removeTouchPoint(int touchId)
707{
812{
708− m_vector[index].reset();
813+ m_touchInfoPool.forEach([&](Pool<ActiveTouchInfo>::Iterator &touchInfo) {
709− if (index == m_lastUsedIndex) {
814+ if (touchId == touchInfo->id) {
710− do {
815+ m_touchInfoPool.freeSlot(touchInfo);
711− --m_lastUsedIndex;
816+ return false;
712− } while (m_lastUsedIndex >= 0 && !m_vector.at(m_lastUsedIndex).isValid());
817+ } else {
713− }
818+ return true;
819+ }
820+ });
821+
822+ #if ACTIVETOUCHESINFO_DEBUG
823+ qDebug() << "[DDA::ActiveTouchesInfo]" << qPrintable(toString());
824+ #endif
714}
825}
715
826
716qint64 DirectionalDragArea::ActiveTouchesInfo::mostRecentStartTime()
827qint64 DirectionalDragArea::ActiveTouchesInfo::mostRecentStartTime()
717{
828{
718− Q_ASSERT(m_lastUsedIndex >= 0);
829+ Q_ASSERT(!m_touchInfoPool.isEmpty());
830+
831+ qint64 highestStartTime = -1;
719
832
720− qint64 highestStartTime = m_vector.at(0).startTime;
721− int i = 1;
722− do {
723− const ActiveTouchInfo &activeTouchInfo = m_vector.at(i);
833+ m_touchInfoPool.forEach([&](Pool<ActiveTouchInfo>::Iterator &activeTouchInfo) {
724− if (activeTouchInfo.isValid() && activeTouchInfo.startTime > highestStartTime) {
834+ if (activeTouchInfo->startTime > highestStartTime) {
725− highestStartTime = activeTouchInfo.startTime;
835+ highestStartTime = activeTouchInfo->startTime;
726 }
836 }
727− ++i;
837+ return true;
728− } while (i < m_lastUsedIndex);
838+ });
729
839
730 return highestStartTime;
840 return highestStartTime;
731}
841}
plugins/Ubuntu/Gestures/DirectionalDragArea.h+37−29
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013,2014 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
@@ -19,27 +19,16 @@
19
19
20#include <QtQuick/QQuickItem>
20#include <QtQuick/QQuickItem>
21#include "AxisVelocityCalculator.h"
21#include "AxisVelocityCalculator.h"
22−#include "UbuntuGesturesGlobal.h"
22+#include "UbuntuGesturesQmlGlobal.h"
23#include "Damper.h"
23#include "Damper.h"
24#include "Direction.h"
24#include "Direction.h"
25
25
26−namespace UbuntuGestures {
26+// lib UbuntuGestures
27−/* Defines an interface for a Timer. */
27+#include <Pool.h>
28−class UBUNTUGESTURES_EXPORT AbstractTimer : public QObject {
28+#include <Timer.h>
29− Q_OBJECT
29+
30−public:
30+class TouchOwnershipEvent;
31− AbstractTimer(QObject *parent) : QObject(parent), m_isRunning(false) {}
31+class UnownedTouchEvent;
32− virtual int interval() const = 0;
33− virtual void setInterval(int msecs) = 0;
34− virtual void start() { m_isRunning = true; };
35− virtual void stop() { m_isRunning = false; }
36− bool isRunning() const { return m_isRunning; }
37−Q_SIGNALS:
38− void timeout();
39−private:
40− bool m_isRunning;
41−};
42−}
43
32
44/*
33/*
45 An area that detects axis-aligned single-finger drag gestures
34 An area that detects axis-aligned single-finger drag gestures
@@ -50,7 +39,7 @@ private:
50
39
51 See doc/DirectionalDragArea.svg
40 See doc/DirectionalDragArea.svg
52 */
41 */
53−class UBUNTUGESTURES_EXPORT DirectionalDragArea : public QQuickItem {
42+class UBUNTUGESTURESQML_EXPORT DirectionalDragArea : public QQuickItem {
54 Q_OBJECT
43 Q_OBJECT
55
44
56 // The direction in which the gesture should move in order to be recognized.
45 // The direction in which the gesture should move in order to be recognized.
@@ -196,6 +185,12 @@ public:
196 // Useful for testing, where a fake time source can be supplied
185 // Useful for testing, where a fake time source can be supplied
197 void setTimeSource(const UbuntuGestures::SharedTimeSource &timeSource);
186 void setTimeSource(const UbuntuGestures::SharedTimeSource &timeSource);
198
187
188+ bool event(QEvent *e) override;
189+
190+ // Maximum time, in milliseconds, between a press and a release, for a touch
191+ // sequence to be considered a tap.
192+ int maxTapDuration() const { return 300; }
193+
199Q_SIGNALS:
194Q_SIGNALS:
200 void directionChanged(Direction::Type direction);
195 void directionChanged(Direction::Type direction);
201 void statusChanged(Status value);
196 void statusChanged(Status value);
@@ -212,6 +207,10 @@ Q_SIGNALS:
212 void touchYChanged(qreal value);
207 void touchYChanged(qreal value);
213 void touchSceneXChanged(qreal value);
208 void touchSceneXChanged(qreal value);
214 void touchSceneYChanged(qreal value);
209 void touchSceneYChanged(qreal value);
210+
211+ // TODO: I would rather not have such signal as it has nothing to do with drag gestures.
212+ // Remove when no longer used or move its implementation to the QML code that uses it
213+ // See maxTapDuration()
215 void tapped();
214 void tapped();
216
215
217protected:
216protected:
@@ -219,7 +218,7 @@ protected:
219
218
220private Q_SLOTS:
219private Q_SLOTS:
221 void checkSpeed();
220 void checkSpeed();
222− void onEnabledChanged();
221+ void giveUpIfDisabledOrInvisible();
223
222
224private:
223private:
225 void touchEvent_absent(QTouchEvent *event);
224 void touchEvent_absent(QTouchEvent *event);
@@ -238,6 +237,12 @@ private:
238 // returns the scalar projection between the given vector (in scene coordinates)
237 // returns the scalar projection between the given vector (in scene coordinates)
239 // and m_sceneDirectionVector
238 // and m_sceneDirectionVector
240 qreal projectOntoDirectionVector(const QPointF &sceneVector) const;
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();
241
246
242 Status m_status;
247 Status m_status;
243
248
@@ -283,18 +288,21 @@ private:
283 public:
288 public:
284 ActiveTouchesInfo(const UbuntuGestures::SharedTimeSource &timeSource);
289 ActiveTouchesInfo(const UbuntuGestures::SharedTimeSource &timeSource);
285 void update(QTouchEvent *event);
290 void update(QTouchEvent *event);
286− ActiveTouchInfo &touchInfo(int id);
291+ qint64 touchStartTime(int id);
292+ bool isEmpty() const { return m_touchInfoPool.isEmpty(); }
287 qint64 mostRecentStartTime();
293 qint64 mostRecentStartTime();
288 UbuntuGestures::SharedTimeSource m_timeSource;
294 UbuntuGestures::SharedTimeSource m_timeSource;
289− bool isEmpty() const { return m_lastUsedIndex == -1; }
290 private:
295 private:
291− void addTouchPoint(const QTouchEvent::TouchPoint &touchPoint);
296+ void addTouchPoint(int touchId);
292− ActiveTouchInfo &getEmptySlot();
293− void freeSlot(int index);
294− void removeTouchPoint(const QTouchEvent::TouchPoint &touchPoint);
297+ void removeTouchPoint(int touchId);
298+ #if ACTIVETOUCHESINFO_DEBUG
299+ QString toString();
300+ #endif
301+
295− QVector<struct ActiveTouchInfo> m_vector;
302+ Pool<ActiveTouchInfo> m_touchInfoPool;
296− int m_lastUsedIndex;
297 } m_activeTouches;
303 } m_activeTouches;
304+
305+ friend class tst_DirectionalDragArea;
298};
306};
299
307
300#endif // DIRECTIONAL_DRAG_AREA_H
308#endif // DIRECTIONAL_DRAG_AREA_H
plugins/Ubuntu/Gestures/TimeSource.h+2−2
@@ -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 "UbuntuGesturesGlobal.h"
24+#include "UbuntuGesturesQmlGlobal.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 UBUNTUGESTURES_EXPORT TimeSource {
31+class UBUNTUGESTURESQML_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. */
plugins/Ubuntu/Gestures/TouchGate.cppnew file+322
@@ -0,0 +1,322 @@
1+/*
2+ * Copyright (C) 2014 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 "TouchGate.h"
18+
19+#include <QCoreApplication>
20+#include <QDebug>
21+
22+#include <TouchOwnershipEvent.h>
23+#include <TouchRegistry.h>
24+
25+#pragma GCC diagnostic push
26+#pragma GCC diagnostic ignored "-pedantic"
27+#include <private/qquickitem_p.h>
28+#pragma GCC diagnostic pop
29+
30+#if TOUCHGATE_DEBUG
31+#include <DebugHelpers.h>
32+#endif
33+
34+
35+bool TouchGate::event(QEvent *e)
36+{
37+ if (e->type() == TouchOwnershipEvent::touchOwnershipEventType()) {
38+ touchOwnershipEvent(static_cast<TouchOwnershipEvent *>(e));
39+ return true;
40+ } else {
41+ return QQuickItem::event(e);
42+ }
43+}
44+
45+void TouchGate::touchEvent(QTouchEvent *event)
46+{
47+ #if TOUCHGATE_DEBUG
48+ qDebug() << "[TouchGate] got touch event" << qPrintable(touchEventToString(event));
49+ #endif
50+ event->accept();
51+
52+ const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
53+ bool goodToGo = true;
54+ for (int i = 0; i < touchPoints.count(); ++i) {
55+ const QTouchEvent::TouchPoint &touchPoint = touchPoints[i];
56+
57+ if (touchPoint.state() == Qt::TouchPointPressed) {
58+ Q_ASSERT(!m_touchInfoMap.contains(touchPoint.id()));
59+ m_touchInfoMap[touchPoint.id()].ownership = OwnershipRequested;
60+ m_touchInfoMap[touchPoint.id()].ended = false;
61+ TouchRegistry::instance()->requestTouchOwnership(touchPoint.id(), this);
62+ }
63+
64+ goodToGo &= m_touchInfoMap.contains(touchPoint.id())
65+ && m_touchInfoMap[touchPoint.id()].ownership == OwnershipGranted;
66+
67+ if (touchPoint.state() == Qt::TouchPointReleased && m_touchInfoMap.contains(touchPoint.id())) {
68+ m_touchInfoMap[touchPoint.id()].ended = true;
69+ }
70+
71+ }
72+
73+ if (goodToGo) {
74+ if (m_storedEvents.isEmpty()) {
75+ // let it pass through
76+ dispatchTouchEventToTarget(event);
77+ } else {
78+ // Retain the event to ensure TouchGate dispatches them in order.
79+ // Otherwise the current event would come before the stored ones, which are older.
80+ #if TOUCHGATE_DEBUG
81+ qDebug("[TouchGate] Storing event because thouches %s are still pending ownership.",
82+ qPrintable(oldestPendingTouchIdsString()));
83+ #endif
84+ storeTouchEvent(event);
85+ }
86+ } else {
87+ // Retain events that have unowned touches
88+ storeTouchEvent(event);
89+ }
90+}
91+
92+void TouchGate::touchOwnershipEvent(TouchOwnershipEvent *event)
93+{
94+ // TODO: Optimization: batch those actions as TouchOwnershipEvents
95+ // might come one right after the other.
96+
97+ Q_ASSERT(m_touchInfoMap.contains(event->touchId()));
98+
99+ TouchInfo &touchInfo = m_touchInfoMap[event->touchId()];
100+
101+ if (event->gained()) {
102+ #if TOUCHGATE_DEBUG
103+ qDebug() << "[TouchGate] Got ownership of touch " << event->touchId();
104+ #endif
105+ touchInfo.ownership = OwnershipGranted;
106+ } else {
107+ #if TOUCHGATE_DEBUG
108+ qDebug() << "[TouchGate] Lost ownership of touch " << event->touchId();
109+ #endif
110+ m_touchInfoMap.remove(event->touchId());
111+ removeTouchFromStoredEvents(event->touchId());
112+ }
113+
114+ dispatchFullyOwnedEvents();
115+}
116+
117+bool TouchGate::isTouchPointOwned(int touchId) const
118+{
119+ return m_touchInfoMap[touchId].ownership == OwnershipGranted;
120+}
121+
122+void TouchGate::storeTouchEvent(const QTouchEvent *event)
123+{
124+ #if TOUCHGATE_DEBUG
125+ qDebug() << "[TouchGate] Storing" << qPrintable(touchEventToString(event));
126+ #endif
127+
128+ QTouchEvent *clonedEvent = new QTouchEvent(event->type(),
129+ event->device(),
130+ event->modifiers(),
131+ event->touchPointStates(),
132+ event->touchPoints());
133+
134+ m_storedEvents.append(clonedEvent);
135+}
136+
137+void TouchGate::removeTouchFromStoredEvents(int touchId)
138+{
139+ int i = 0;
140+ while (i < m_storedEvents.count()) {
141+ QTouchEvent *event = m_storedEvents[i];
142+ bool removed = removeTouchFromEvent(touchId, event);
143+
144+ if (removed && event->touchPoints().isEmpty()) {
145+ m_storedEvents.removeAt(i);
146+ } else {
147+ ++i;
148+ }
149+ }
150+}
151+
152+bool TouchGate::removeTouchFromEvent(int touchId, QTouchEvent *event)
153+{
154+ const QList<QTouchEvent::TouchPoint> &eventTouchPoints = event->touchPoints();
155+
156+ int indexToRemove = -1;
157+ for (int i = 0; i < eventTouchPoints.count() && indexToRemove < 0; ++i) {
158+ if (eventTouchPoints[i].id() == touchId) {
159+ indexToRemove = i;
160+ }
161+ }
162+
163+ if (indexToRemove >= 0) {
164+ QList<QTouchEvent::TouchPoint> newTouchPointsList = eventTouchPoints;
165+ newTouchPointsList.removeAt(indexToRemove);
166+ event->setTouchPoints(newTouchPointsList);
167+ return true;
168+ } else {
169+ return false;
170+ }
171+}
172+
173+void TouchGate::dispatchFullyOwnedEvents()
174+{
175+ while (!m_storedEvents.isEmpty() && eventIsFullyOwned(m_storedEvents.first())) {
176+ QTouchEvent *event = m_storedEvents.first();
177+ m_storedEvents.removeFirst();
178+ dispatchTouchEventToTarget(event);
179+ delete event;
180+ }
181+}
182+
183+#if TOUCHGATE_DEBUG
184+QString TouchGate::oldestPendingTouchIdsString()
185+{
186+ Q_ASSERT(!m_storedEvents.isEmpty());
187+
188+ QString str;
189+
190+ auto touchPoints = m_storedEvents.first()->touchPoints();
191+ for (int i = 0; i < touchPoints.count(); ++i) {
192+ if (!isTouchPointOwned(touchPoints[i].id())) {
193+ if (!str.isEmpty()) {
194+ str.append(", ");
195+ }
196+ str.append(QString::number(touchPoints[i].id()));
197+ }
198+ }
199+
200+ return str;
201+}
202+#endif
203+
204+bool TouchGate::eventIsFullyOwned(const QTouchEvent *event) const
205+{
206+ auto touchPoints = event->touchPoints();
207+ for (int i = 0; i < touchPoints.count(); ++i) {
208+ if (!isTouchPointOwned(touchPoints[i].id())) {
209+ return false;
210+ }
211+ }
212+
213+ return true;
214+}
215+
216+void TouchGate::setTargetItem(QQuickItem *item)
217+{
218+ // TODO: changing the target item while dispatch of touch events is taking place will
219+ // create a mess
220+
221+ if (item == m_targetItem.data())
222+ return;
223+
224+ m_targetItem = item;
225+ Q_EMIT targetItemChanged(item);
226+}
227+
228+void TouchGate::dispatchTouchEventToTarget(QTouchEvent* event)
229+{
230+ removeTouchInfoForEndedTouches(event);
231+
232+ if (m_targetItem.isNull()) {
233+ qWarning("[TouchGate] Cannot dispatch touch event because target item is null");
234+ return;
235+ }
236+
237+ QQuickItem *targetItem = m_targetItem.data();
238+
239+ if (!targetItem->isEnabled() || !targetItem->isVisible()) {
240+ #if TOUCHGATE_DEBUG
241+ qDebug() << "[TouchGate] Cannot dispatch touch event to" << targetItem
242+ << "because it's disabled or invisible.";
243+ #endif
244+ return;
245+ }
246+
247+ // Map touch points to targetItem coordinates
248+ QList<QTouchEvent::TouchPoint> touchPoints = event->touchPoints();
249+ transformTouchPoints(touchPoints, QQuickItemPrivate::get(targetItem)->windowToItemTransform());
250+ QTouchEvent *eventForTargetItem = touchEventWithPoints(*event, touchPoints);
251+
252+ #if TOUCHGATE_DEBUG
253+ qDebug() << "[TouchGate] dispatching" << qPrintable(touchEventToString(eventForTargetItem))
254+ << "to" << targetItem;
255+ #endif
256+
257+ QCoreApplication::sendEvent(targetItem, eventForTargetItem);
258+
259+ delete eventForTargetItem;
260+}
261+
262+// NB: From QQuickWindow
263+void TouchGate::transformTouchPoints(QList<QTouchEvent::TouchPoint> &touchPoints, const QTransform &transform)
264+{
265+ QMatrix4x4 transformMatrix(transform);
266+ for (int i=0; i<touchPoints.count(); i++) {
267+ QTouchEvent::TouchPoint &touchPoint = touchPoints[i];
268+ touchPoint.setRect(transform.mapRect(touchPoint.sceneRect()));
269+ touchPoint.setStartPos(transform.map(touchPoint.startScenePos()));
270+ touchPoint.setLastPos(transform.map(touchPoint.lastScenePos()));
271+ touchPoint.setVelocity(transformMatrix.mapVector(touchPoint.velocity()).toVector2D());
272+ }
273+}
274+
275+// NB: From QQuickWindow
276+QTouchEvent *TouchGate::touchEventWithPoints(const QTouchEvent &event,
277+ const QList<QTouchEvent::TouchPoint> &newPoints)
278+{
279+ Qt::TouchPointStates eventStates = 0;
280+ for (int i=0; i<newPoints.count(); i++)
281+ eventStates |= newPoints[i].state();
282+ // if all points have the same state, set the event type accordingly
283+ QEvent::Type eventType = event.type();
284+ switch (eventStates) {
285+ case Qt::TouchPointPressed:
286+ eventType = QEvent::TouchBegin;
287+ break;
288+ case Qt::TouchPointReleased:
289+ eventType = QEvent::TouchEnd;
290+ break;
291+ default:
292+ eventType = QEvent::TouchUpdate;
293+ break;
294+ }
295+
296+ QTouchEvent *touchEvent = new QTouchEvent(eventType);
297+ touchEvent->setWindow(event.window());
298+ touchEvent->setTarget(event.target());
299+ touchEvent->setDevice(event.device());
300+ touchEvent->setModifiers(event.modifiers());
301+ touchEvent->setTouchPoints(newPoints);
302+ touchEvent->setTouchPointStates(eventStates);
303+ touchEvent->setTimestamp(event.timestamp());
304+ touchEvent->accept();
305+ return touchEvent;
306+}
307+
308+void TouchGate::removeTouchInfoForEndedTouches(QTouchEvent *event)
309+{
310+ const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
311+
312+ for (int i = 0; i < touchPoints.size(); ++i) {\
313+ const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i);
314+
315+ if (touchPoint.state() == Qt::TouchPointReleased) {
316+ Q_ASSERT(m_touchInfoMap.contains(touchPoint.id()));
317+ Q_ASSERT(m_touchInfoMap[touchPoint.id()].ended);
318+ Q_ASSERT(m_touchInfoMap[touchPoint.id()].ownership == OwnershipGranted);
319+ m_touchInfoMap.remove(touchPoint.id());
320+ }
321+ }
322+}
plugins/Ubuntu/Gestures/TouchGate.hnew file+97
@@ -0,0 +1,97 @@
1+/*
2+ * Copyright (C) 2014 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 UBUNTU_TOUCH_GATE_H
18+#define UBUNTU_TOUCH_GATE_H
19+
20+#include "UbuntuGesturesQmlGlobal.h"
21+
22+#include <QQuickItem>
23+#include <QList>
24+#include <QPointer>
25+#include <QMap>
26+
27+#define TOUCHGATE_DEBUG 0
28+
29+class TouchOwnershipEvent;
30+
31+/*
32+ Blocks the passage of events until ownership over the related touch points is granted.
33+
34+ Blocked touch events won't be discarded. Instead they will be buffered until ownership
35+ is granted. If ownership is given to another item, the event buffer is cleared.
36+
37+ A TouchGate is useful as a mediator for items that do not understand, or gracefully handle,
38+ touch canceling. By having a TouchGate in front of them you guarantee that only owned touches (i.e.,
39+ touches that won't be canceled later) reaches them.
40+ */
41+class UBUNTUGESTURESQML_EXPORT TouchGate : public QQuickItem {
42+ Q_OBJECT
43+
44+ // Item that's going to receive the touch events that make it through the gate.
45+ Q_PROPERTY(QQuickItem* targetItem READ targetItem WRITE setTargetItem NOTIFY targetItemChanged)
46+
47+public:
48+ bool event(QEvent *e) override;
49+
50+ QQuickItem *targetItem() { return m_targetItem; }
51+ void setTargetItem(QQuickItem *item);
52+
53+Q_SIGNALS:
54+ void targetItemChanged(QQuickItem *item);
55+
56+protected:
57+ void touchEvent(QTouchEvent *event) override;
58+private:
59+ void touchOwnershipEvent(TouchOwnershipEvent *event);
60+ bool isTouchPointOwned(int touchId) const;
61+ void storeTouchEvent(const QTouchEvent *event);
62+ void removeTouchFromStoredEvents(int touchId);
63+ void dispatchFullyOwnedEvents();
64+ bool removeTouchFromEvent(int touchId, QTouchEvent *event);
65+ bool eventIsFullyOwned(const QTouchEvent *event) const;
66+ void dispatchTouchEventToTarget(QTouchEvent* event);
67+ void transformTouchPoints(QList<QTouchEvent::TouchPoint> &touchPoints, const QTransform &transform);
68+ QTouchEvent *touchEventWithPoints(const QTouchEvent &event,
69+ const QList<QTouchEvent::TouchPoint> &newPoints);
70+ void removeTouchInfoForEndedTouches(QTouchEvent *event);
71+
72+ #if TOUCHGATE_DEBUG
73+ QString oldestPendingTouchIdsString();
74+ #endif
75+
76+ // TODO: Optimize storage of buffered events
77+ QList<QTouchEvent*> m_storedEvents;
78+
79+ enum {
80+ OwnershipUndefined,
81+ OwnershipRequested,
82+ OwnershipGranted,
83+ };
84+ class TouchInfo {
85+ public:
86+ TouchInfo() {ownership = OwnershipUndefined; ended = false;}
87+ int ownership;
88+ bool ended;
89+ };
90+ QMap<int, TouchInfo> m_touchInfoMap;
91+
92+ QPointer<QQuickItem> m_targetItem;
93+
94+ friend class tst_TouchGate;
95+};
96+
97+#endif // UBUNTU_TOUCH_GATE_H
plugins/Ubuntu/Gestures/UbuntuGesturesQmlGlobal.hnew file+23
@@ -0,0 +1,23 @@
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+#include <QtCore/QtGlobal>
18+
19+#if defined(UBUNTUGESTURESQML_LIBRARY)
20+# define UBUNTUGESTURESQML_EXPORT Q_DECL_EXPORT
21+#else
22+# define UBUNTUGESTURESQML_EXPORT Q_DECL_IMPORT
23+#endif
plugins/Ubuntu/Gestures/plugin.cpp+3−1
@@ -19,6 +19,7 @@
19#include "Direction.h"
19#include "Direction.h"
20#include "DirectionalDragArea.h"
20#include "DirectionalDragArea.h"
21#include "PressedOutsideNotifier.h"
21#include "PressedOutsideNotifier.h"
22+#include "TouchGate.h"
22
23
23#include <qqml.h>
24#include <qqml.h>
24
25
@@ -28,10 +29,11 @@ static QObject* directionSingleton(QQmlEngine* engine, QJSEngine* scriptEngine)
28 return new Direction;
29 return new Direction;
29}
30}
30
31
31−void UbuntuGestureQmlPlugin::registerTypes(const char *uri)
32+void UbuntuGesturesQmlPlugin::registerTypes(const char *uri)
32{
33{
33 qmlRegisterSingletonType<Direction>(uri, 0, 1, "Direction", directionSingleton);
34 qmlRegisterSingletonType<Direction>(uri, 0, 1, "Direction", directionSingleton);
34 qmlRegisterType<DirectionalDragArea>(uri, 0, 1, "DirectionalDragArea");
35 qmlRegisterType<DirectionalDragArea>(uri, 0, 1, "DirectionalDragArea");
35 qmlRegisterType<AxisVelocityCalculator>(uri, 0, 1, "AxisVelocityCalculator");
36 qmlRegisterType<AxisVelocityCalculator>(uri, 0, 1, "AxisVelocityCalculator");
36 qmlRegisterType<PressedOutsideNotifier>(uri, 0, 1, "PressedOutsideNotifier");
37 qmlRegisterType<PressedOutsideNotifier>(uri, 0, 1, "PressedOutsideNotifier");
38+ qmlRegisterType<TouchGate>(uri, 0, 1, "TouchGate");
37}
39}
plugins/Ubuntu/Gestures/plugin.h+1−1
@@ -19,7 +19,7 @@
19
19
20#include <QtQml/QQmlExtensionPlugin>
20#include <QtQml/QQmlExtensionPlugin>
21
21
22−class UbuntuGestureQmlPlugin : public QQmlExtensionPlugin
22+class UbuntuGesturesQmlPlugin : public QQmlExtensionPlugin
23{
23{
24 Q_OBJECT
24 Q_OBJECT
25 Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
25 Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
plugins/Ubuntu/Gestures/qmldir+1−1
@@ -1,3 +1,3 @@
1module Ubuntu.Gestures
1module Ubuntu.Gestures
2−plugin UbuntuGestureQml
2+plugin UbuntuGesturesQml
3typeinfo Gestures.qmltypes
3typeinfo Gestures.qmltypes
qml/Components/DragHandle.qml+2−2
@@ -47,12 +47,12 @@ EdgeDragArea {
47 id: dragArea
47 id: dragArea
48 objectName: "dragHandle"
48 objectName: "dragHandle"
49
49
50− // Disable most of the gesture recognition parameters by default when hinting is used as
50+ // Disable gesture recognition by default when hinting is used as
51 // it conflicts with the hinting idea.
51 // it conflicts with the hinting idea.
52− // The only part we keep in this situation is that it must be a single-finger gesture.
53 distanceThreshold: hintDisplacement > 0 ? 0 : defaultDistanceThreshold
52 distanceThreshold: hintDisplacement > 0 ? 0 : defaultDistanceThreshold
54 maxSilenceTime: hintDisplacement > 0 ? 60*60*1000 : defaultMaxSilenceTime
53 maxSilenceTime: hintDisplacement > 0 ? 60*60*1000 : defaultMaxSilenceTime
55 maxDeviation: hintDisplacement > 0 ? 999999 : defaultMaxDeviation
54 maxDeviation: hintDisplacement > 0 ? 999999 : defaultMaxDeviation
55+ compositionTime: hintDisplacement > 0 ? 0 : defaultCompositionTime
56
56
57 property bool stretch: false
57 property bool stretch: false
58
58
qml/Components/EdgeDragArea.qml+2
@@ -34,6 +34,7 @@ DirectionalDragArea {
34 distanceThreshold: defaultDistanceThreshold
34 distanceThreshold: defaultDistanceThreshold
35 minSpeed: defaultMinSpeed
35 minSpeed: defaultMinSpeed
36 maxSilenceTime: defaultMaxSilenceTime
36 maxSilenceTime: defaultMaxSilenceTime
37+ compositionTime: defaultCompositionTime
37
38
38 readonly property real defaultMaxDeviation: units.gu(3)
39 readonly property real defaultMaxDeviation: units.gu(3)
39 readonly property real defaultWideningAngle: 50
40 readonly property real defaultWideningAngle: 50
@@ -41,4 +42,5 @@ DirectionalDragArea {
41 // some people were getting false negatives with it enabled.
42 // some people were getting false negatives with it enabled.
42 readonly property real defaultMinSpeed: units.gu(0)
43 readonly property real defaultMinSpeed: units.gu(0)
43 readonly property int defaultMaxSilenceTime: 200
44 readonly property int defaultMaxSilenceTime: 200
45+ readonly property int defaultCompositionTime: 60
44}
46}
qml/Components/InputMethod.qml+13
@@ -17,6 +17,7 @@
17import QtQuick 2.0
17import QtQuick 2.0
18import Unity.Application 0.1
18import Unity.Application 0.1
19import Ubuntu.Components 0.1
19import Ubuntu.Components 0.1
20+import Ubuntu.Gestures 0.1
20
21
21Item {
22Item {
22 id: root
23 id: root
@@ -25,6 +26,18 @@ Item {
25
26
26 property int transitionDuration: UbuntuAnimation.FastDuration
27 property int transitionDuration: UbuntuAnimation.FastDuration
27
28
29+ Binding { target: surface; property: "z"; value: 0 }
30+
31+ TouchGate {
32+ x: UbuntuKeyboardInfo.x
33+ y: UbuntuKeyboardInfo.y
34+ z: 1
35+ width: UbuntuKeyboardInfo.width
36+ height: UbuntuKeyboardInfo.height
37+
38+ targetItem: root.surface
39+ }
40+
28 state: {
41 state: {
29 if (surface && surface.state != MirSurfaceItem.Minimized) {
42 if (surface && surface.state != MirSurfaceItem.Minimized) {
30 return "shown";
43 return "shown";
qml/Stages/ApplicationWindow.qml−17
@@ -61,14 +61,6 @@ Item {
61 // Remove this when possible
61 // Remove this when possible
62 property bool surfaceInitialized: false
62 property bool surfaceInitialized: false
63
63
64− function forceSurfaceActiveFocusIfReady() {
65− if (sessionContainer.surface !== null &&
66− sessionContainer.surface.focus &&
67− sessionContainer.surface.parent === sessionContainer.surfaceContainer &&
68− sessionContainer.surface.enabled) {
69− sessionContainer.surface.forceActiveFocus();
70− }
71− }
72 }
64 }
73
65
74 Timer {
66 Timer {
@@ -77,15 +69,6 @@ Item {
77 onTriggered: { if (sessionContainer.surface) {d.surfaceInitialized = true;} }
69 onTriggered: { if (sessionContainer.surface) {d.surfaceInitialized = true;} }
78 }
70 }
79
71
80− Connections {
81− target: sessionContainer.surface
82− // FIXME: I would rather not need to do this, but currently it doesn't get
83− // active focus without it and I don't know why.
84− onFocusChanged: d.forceSurfaceActiveFocusIfReady();
85− onParentChanged: d.forceSurfaceActiveFocusIfReady();
86− onEnabledChanged: d.forceSurfaceActiveFocusIfReady();
87− }
88−
89 Image {
72 Image {
90 id: screenshotImage
73 id: screenshotImage
91 objectName: "screenshotImage"
74 objectName: "screenshotImage"
qml/Stages/PhoneStage.qml+1
@@ -410,6 +410,7 @@ Rectangle {
410
410
411 EdgeDragArea {
411 EdgeDragArea {
412 id: spreadDragArea
412 id: spreadDragArea
413+ objectName: "spreadDragArea"
413 direction: Direction.Leftwards
414 direction: Direction.Leftwards
414 enabled: spreadView.phase != 2 && root.spreadEnabled
415 enabled: spreadView.phase != 2 && root.spreadEnabled
415
416
qml/Stages/SessionContainer.qml+1−13
@@ -23,7 +23,7 @@ Item {
23 property QtObject session
23 property QtObject session
24 readonly property var childSessions: session ? session.childSessions : null
24 readonly property var childSessions: session ? session.childSessions : null
25 readonly property alias surface: _surfaceContainer.surface
25 readonly property alias surface: _surfaceContainer.surface
26− property bool interactive: true
26+ property alias interactive: _surfaceContainer.interactive
27 property int orientation
27 property int orientation
28
28
29 readonly property alias surfaceContainer: _surfaceContainer
29 readonly property alias surfaceContainer: _surfaceContainer
@@ -34,18 +34,6 @@ Item {
34 orientation: root.orientation
34 orientation: root.orientation
35 }
35 }
36
36
37− Binding {
38− target: surfaceContainer.surface
39− when: surfaceContainer.surface
40− property: "enabled"
41− value: root.interactive
42− }
43− Binding {
44− target: surfaceContainer.surface
45− when: surfaceContainer.surface
46− property: "focus"
47− value: root.interactive
48− }
49
37
50 Repeater {
38 Repeater {
51 model: root.childSessions
39 model: root.childSessions
qml/Stages/SurfaceContainer.qml+33−6
@@ -16,6 +16,7 @@
16
16
17import QtQuick 2.0
17import QtQuick 2.0
18import Ubuntu.Components 1.1
18import Ubuntu.Components 1.1
19+import Ubuntu.Gestures 0.1 // For TouchGate
19
20
20Item {
21Item {
21 id: root
22 id: root
@@ -23,6 +24,7 @@ Item {
23 property Item surface: null
24 property Item surface: null
24 property bool hadSurface: false
25 property bool hadSurface: false
25 property int orientation
26 property int orientation
27+ property bool interactive
26
28
27 onSurfaceChanged: {
29 onSurfaceChanged: {
28 if (surface) {
30 if (surface) {
@@ -31,13 +33,38 @@ Item {
31 hadSurface = true;
33 hadSurface = true;
32 }
34 }
33 }
35 }
34− Binding {
35− target: surface
36− property: "anchors.fill"; value: root
36+ Binding { target: surface; property: "anchors.fill"; value: root }
37+ Binding { target: surface; property: "orientation"; value: root.orientation }
38+ Binding { target: surface; property: "z"; value: 1 }
39+ Binding { target: surface; property: "enabled"; value: root.interactive; when: surface }
40+ Binding { target: surface; property: "focus"; value: root.interactive; when: surface }
41+
42+ TouchGate {
43+ targetItem: surface
44+ anchors.fill: root
45+ enabled: surface.enabled
46+ z: 2
47+ }
48+
49+ Connections {
50+ target: root.surface
51+ // FIXME: I would rather not need to do this, but currently it doesn't get
52+ // active focus without it and I don't know why.
53+ onFocusChanged: d.forceSurfaceActiveFocusIfReady();
54+ onParentChanged: d.forceSurfaceActiveFocusIfReady();
55+ onEnabledChanged: d.forceSurfaceActiveFocusIfReady();
37 }
56 }
38− Binding {
57+
58+ QtObject {
59+ id: d
60+ function forceSurfaceActiveFocusIfReady() {
61+ if (root.surface !== null &&
62+ root.surface.focus &&
39− target: surface
63+ root.surface.parent === root &&
40− property: "orientation"; value: root.orientation
64+ root.surface.enabled) {
65+ root.surface.forceActiveFocus();
66+ }
67+ }
41 }
68 }
42
69
43 states: [
70 states: [
src/CMakeLists.txt+6
@@ -1,5 +1,6 @@
1include_directories(
1include_directories(
2 ${Qt5Gui_PRIVATE_INCLUDE_DIRS}
2 ${Qt5Gui_PRIVATE_INCLUDE_DIRS}
3+ ${CMAKE_SOURCE_DIR}/libs/UbuntuGestures
3)
4)
4
5
5file(GLOB_RECURSE QML_FILES
6file(GLOB_RECURSE QML_FILES
@@ -29,6 +30,11 @@ if (NOT "${XCB_INCLUDE_DIRS}" STREQUAL "")
29 target_link_libraries(${SHELL_APP} ${XCB_LDFLAGS})
30 target_link_libraries(${SHELL_APP} ${XCB_LDFLAGS})
30endif()
31endif()
31
32
33+target_link_libraries(${SHELL_APP} UbuntuGestures)
34+
35+# For it to find libUbuntuGestures.so
36+set_target_properties(${SHELL_APP} PROPERTIES INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${SHELL_PRIVATE_LIBDIR}")
37+
32# install binaries
38# install binaries
33install(TARGETS ${SHELL_APP}
39install(TARGETS ${SHELL_APP}
34 RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
40 RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
src/Dash/CMakeLists.txt+3
@@ -9,6 +9,9 @@ add_executable(unity8-dash ${DASH_SRCS})
9
9
10qt5_use_modules(unity8-dash Gui Qml Quick Test)
10qt5_use_modules(unity8-dash Gui Qml Quick Test)
11
11
12+# For it to find libUbuntuGestures.so, needed by Ubuntu.Gestures QML module.
13+set_target_properties(unity8-dash PROPERTIES INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${SHELL_PRIVATE_LIBDIR}")
14+
12# install binaries
15# install binaries
13install(TARGETS ${DASH_APP}
16install(TARGETS ${DASH_APP}
14 RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
17 RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
src/main.cpp+5
@@ -34,6 +34,9 @@
34#include "ApplicationArguments.h"
34#include "ApplicationArguments.h"
35#include "CachingNetworkManagerFactory.h"
35#include "CachingNetworkManagerFactory.h"
36
36
37+// Ubuntu Gestures
38+#include <TouchRegistry.h>
39+
37int main(int argc, const char *argv[])
40int main(int argc, const char *argv[])
38{
41{
39 bool isMirServer = false;
42 bool isMirServer = false;
@@ -120,6 +123,8 @@ Load the testability driver");
120 if (parser.isSet(framelessOption)) {
123 if (parser.isSet(framelessOption)) {
121 view->setFlags(Qt::FramelessWindowHint);
124 view->setFlags(Qt::FramelessWindowHint);
122 }
125 }
126+ TouchRegistry touchRegistry;
127+ view->installEventFilter(&touchRegistry);
123
128
124 // You will need this if you want to interact with touch-only components using a mouse
129 // You will need this if you want to interact with touch-only components using a mouse
125 // Needed only when manually testing on a desktop.
130 // Needed only when manually testing on a desktop.
tests/CMakeLists.txt+1
@@ -7,6 +7,7 @@ add_custom_target(qmluitests)
7add_custom_target(qmltests)
7add_custom_target(qmltests)
8add_dependencies(qmltests qmlunittests qmluitests)
8add_dependencies(qmltests qmlunittests qmluitests)
9
9
10+add_subdirectory(libs)
10add_subdirectory(qmltests)
11add_subdirectory(qmltests)
11add_subdirectory(mocks)
12add_subdirectory(mocks)
12add_subdirectory(whitespace)
13add_subdirectory(whitespace)
tests/libs/CMakeLists.txtnew file+1
@@ -0,0 +1 @@
1+add_subdirectory(UbuntuGestures)
tests/libs/UbuntuGestures/CMakeLists.txtnew file+20
@@ -0,0 +1,20 @@
1+include_directories(
2+ ${CMAKE_SOURCE_DIR}/libs/UbuntuGestures
3+ ${CMAKE_CURRENT_BINARY_DIR}
4+ )
5+
6+macro(add_gesture_test CLASSNAME)
7+ set(testCommand
8+ ${CLASSNAME}TestExec -o ${CMAKE_BINARY_DIR}/test${CLASSNAME}.xml,xunitxml -o -,txt)
9+
10+ add_test(NAME test${CLASSNAME} COMMAND ${testCommand})
11+
12+ set_tests_properties(test${CLASSNAME} PROPERTIES
13+ ENVIRONMENT LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/libs/UbuntuGestures)
14+
15+ add_executable(${CLASSNAME}TestExec tst_${CLASSNAME}.cpp)
16+ qt5_use_modules(${CLASSNAME}TestExec Test Core Gui Quick)
17+ target_link_libraries(${CLASSNAME}TestExec UbuntuGestures)
18+endmacro(add_gesture_test)
19+
20+add_gesture_test(TouchRegistry)
tests/libs/UbuntuGestures/tst_TouchRegistry.cppnew file+803
@@ -0,0 +1,803 @@
1+/*
2+ * Copyright (C) 2014 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 <QtTest>
18+#include <QSet>
19+#include <QTouchEvent>
20+
21+#include <Timer.h>
22+#include <TouchOwnershipEvent.h>
23+#include <TouchRegistry.h>
24+#include <UnownedTouchEvent.h>
25+
26+using namespace UbuntuGestures;
27+
28+class TouchMemento {
29+public:
30+ TouchMemento(const QTouchEvent *touchEvent);
31+ Qt::TouchPointStates touchPointStates;
32+ QList<QTouchEvent::TouchPoint> touchPoints;
33+
34+ bool containsTouchWithId(int touchId) const;
35+};
36+
37+class DummyCandidate : public QQuickItem
38+{
39+ Q_OBJECT
40+public:
41+ bool event(QEvent *e) override;
42+ QSet<int> ownedTouches;
43+ QSet<int> lostTouches;
44+ QList<TouchMemento> unownedTouchEvents;
45+};
46+
47+class tst_TouchRegistry : public QObject
48+{
49+ Q_OBJECT
50+public:
51+ tst_TouchRegistry() : QObject(0) { }
52+private Q_SLOTS:
53+ void initTestCase() {} // will be called before the first test function is executed
54+ void cleanupTestCase() {} // will be called after the last test function was executed.
55+
56+ void init() {} // called right before each and every test function is executed
57+ void cleanup() {} // called right after each and every test function is executed
58+
59+ void requestWithNoCandidates();
60+ void lateCandidateRequestGetsNothing();
61+ void lateCandidateGestOwnershipOnceEarlyCandidateQuits();
62+ void dispatchesTouchEventsToCandidates();
63+ void dispatchesTouchEventsToWatchers();
64+ void keepDispatchingToWatchersAfterLastCandidateGivesUp();
65+ void candidatesAndWatchers_1();
66+ void candidatesAndWatchers_2();
67+ void rejectingTouchfterItsEnd();
68+ void removeOldUndecidedCandidates();
69+};
70+
71+void tst_TouchRegistry::requestWithNoCandidates()
72+{
73+ TouchRegistry touchRegistry;
74+ DummyCandidate candidate;
75+
76+ {
77+ QList<QTouchEvent::TouchPoint> touchPoints;
78+ touchPoints.append(QTouchEvent::TouchPoint(0));
79+ touchPoints[0].setState(Qt::TouchPointPressed);
80+ QTouchEvent touchEvent(QEvent::TouchBegin,
81+ 0 /* device */,
82+ Qt::NoModifier,
83+ Qt::TouchPointPressed,
84+ touchPoints);
85+ touchRegistry.update(&touchEvent);
86+ }
87+
88+ touchRegistry.requestTouchOwnership(0, &candidate);
89+
90+ QVERIFY(candidate.ownedTouches.contains(0));
91+}
92+
93+void tst_TouchRegistry::lateCandidateRequestGetsNothing()
94+{
95+ TouchRegistry touchRegistry;
96+ DummyCandidate earlyCandidate;
97+ earlyCandidate.setObjectName("early");
98+ DummyCandidate lateCandidate;
99+ lateCandidate.setObjectName("late");
100+
101+ {
102+ QList<QTouchEvent::TouchPoint> touchPoints;
103+ touchPoints.append(QTouchEvent::TouchPoint(0));
104+ touchPoints[0].setState(Qt::TouchPointPressed);
105+ QTouchEvent touchEvent(QEvent::TouchBegin,
106+ 0 /* device */,
107+ Qt::NoModifier,
108+ Qt::TouchPointPressed,
109+ touchPoints);
110+ touchRegistry.update(&touchEvent);
111+ }
112+
113+ touchRegistry.addCandidateOwnerForTouch(0, &earlyCandidate);
114+ touchRegistry.addCandidateOwnerForTouch(0, &lateCandidate);
115+
116+ QVERIFY(earlyCandidate.ownedTouches.isEmpty());
117+ QVERIFY(lateCandidate.ownedTouches.isEmpty());
118+
119+ touchRegistry.requestTouchOwnership(0, &lateCandidate);
120+
121+ QVERIFY(earlyCandidate.ownedTouches.isEmpty());
122+ QVERIFY(lateCandidate.ownedTouches.isEmpty());
123+
124+ touchRegistry.requestTouchOwnership(0, &earlyCandidate);
125+
126+ QVERIFY(earlyCandidate.ownedTouches.contains(0));
127+ QVERIFY(lateCandidate.ownedTouches.isEmpty());
128+}
129+
130+void tst_TouchRegistry::lateCandidateGestOwnershipOnceEarlyCandidateQuits()
131+{
132+ TouchRegistry touchRegistry;
133+ DummyCandidate earlyCandidate;
134+ DummyCandidate lateCandidate;
135+
136+ {
137+ QList<QTouchEvent::TouchPoint> touchPoints;
138+ touchPoints.append(QTouchEvent::TouchPoint(0));
139+ touchPoints[0].setState(Qt::TouchPointPressed);
140+ QTouchEvent touchEvent(QEvent::TouchBegin,
141+ 0 /* device */,
142+ Qt::NoModifier,
143+ Qt::TouchPointPressed,
144+ touchPoints);
145+ touchRegistry.update(&touchEvent);
146+ }
147+
148+ touchRegistry.addCandidateOwnerForTouch(0, &earlyCandidate);
149+ touchRegistry.addCandidateOwnerForTouch(0, &lateCandidate);
150+
151+ QVERIFY(earlyCandidate.ownedTouches.isEmpty());
152+ QVERIFY(lateCandidate.ownedTouches.isEmpty());
153+
154+ touchRegistry.requestTouchOwnership(0, &lateCandidate);
155+
156+ QVERIFY(earlyCandidate.ownedTouches.isEmpty());
157+ QVERIFY(lateCandidate.ownedTouches.isEmpty());
158+
159+ touchRegistry.removeCandidateOwnerForTouch(0, &earlyCandidate);
160+
161+ QVERIFY(earlyCandidate.ownedTouches.isEmpty());
162+ QVERIFY(lateCandidate.ownedTouches.contains(0));
163+}
164+
165+void tst_TouchRegistry::dispatchesTouchEventsToCandidates()
166+{
167+ TouchRegistry touchRegistry;
168+ QQuickItem rootItem;
169+
170+ DummyCandidate candidate0;
171+ candidate0.setObjectName("0");
172+ candidate0.setParentItem(&rootItem);
173+ candidate0.setX(1);
174+ candidate0.setY(2);
175+
176+ DummyCandidate candidate1;
177+ candidate1.setObjectName("1");
178+ candidate0.setParentItem(&rootItem);
179+ candidate1.setX(3);
180+ candidate1.setY(4);
181+
182+ {
183+ QList<QTouchEvent::TouchPoint> touchPoints;
184+ touchPoints.append(QTouchEvent::TouchPoint(0));
185+ touchPoints[0].setState(Qt::TouchPointPressed);
186+ touchPoints[0].setRect(QRect(10, 10, 0, 0));
187+ QTouchEvent touchEvent(QEvent::TouchBegin,
188+ 0 /* device */,
189+ Qt::NoModifier,
190+ Qt::TouchPointPressed,
191+ touchPoints);
192+ touchRegistry.update(&touchEvent);
193+ }
194+
195+ touchRegistry.addCandidateOwnerForTouch(0, &candidate0);
196+
197+ {
198+ QList<QTouchEvent::TouchPoint> touchPoints;
199+ touchPoints.append(QTouchEvent::TouchPoint(0));
200+ touchPoints[0].setState(Qt::TouchPointMoved);
201+ touchPoints[0].setRect(QRect(11, 11, 0, 0));
202+ touchPoints.append(QTouchEvent::TouchPoint(1));
203+ touchPoints[1].setState(Qt::TouchPointPressed);
204+ touchPoints[1].setRect(QRect(20, 20, 0, 0));
205+ QTouchEvent touchEvent(QEvent::TouchUpdate,
206+ 0 /* device */,
207+ Qt::NoModifier,
208+ Qt::TouchPointPressed | Qt::TouchPointMoved,
209+ touchPoints);
210+ touchRegistry.update(&touchEvent);
211+ }
212+
213+ // candidate0 should have received an update on the touch he's interested on.
214+ QCOMPARE(candidate0.unownedTouchEvents.count(), 1);
215+ // make sure only touch 0 is there (i.e. no mention of touch 1)
216+ QCOMPARE(candidate0.unownedTouchEvents[0].touchPoints.count(), 1);
217+ QCOMPARE(candidate0.unownedTouchEvents[0].touchPoints[0].id(), 0);
218+ // Check that the points local coordinates have been mapped to candidate0's coordinate system.
219+ QCOMPARE(candidate0.unownedTouchEvents[0].touchPoints[0].rect().x(), 11 - candidate0.x());
220+ QCOMPARE(candidate0.unownedTouchEvents[0].touchPoints[0].rect().y(), 11 - candidate0.y());
221+
222+ touchRegistry.addCandidateOwnerForTouch(1, &candidate1);
223+
224+ {
225+ QList<QTouchEvent::TouchPoint> touchPoints;
226+ touchPoints.append(QTouchEvent::TouchPoint(0));
227+ touchPoints[0].setState(Qt::TouchPointMoved);
228+ touchPoints[0].setRect(QRect(12, 12, 0, 0));
229+ touchPoints.append(QTouchEvent::TouchPoint(1));
230+ touchPoints[1].setState(Qt::TouchPointMoved);
231+ touchPoints[1].setRect(QRect(21, 21, 0, 0));
232+ QTouchEvent touchEvent(QEvent::TouchUpdate,
233+ 0 /* device */,
234+ Qt::NoModifier,
235+ Qt::TouchPointPressed | Qt::TouchPointMoved,
236+ touchPoints);
237+ touchRegistry.update(&touchEvent);
238+ }
239+
240+ // candidate0 gets updates only for touch 0 and
241+ // candidate1 gets updates only for touch 1
242+
243+ QCOMPARE(candidate0.unownedTouchEvents.count(), 2);
244+ QCOMPARE(candidate0.unownedTouchEvents[1].touchPoints.count(), 1);
245+ QCOMPARE(candidate0.unownedTouchEvents[1].touchPoints[0].id(), 0);
246+
247+ QCOMPARE(candidate1.unownedTouchEvents.count(), 1);
248+ QCOMPARE(candidate1.unownedTouchEvents[0].touchPoints.count(), 1);
249+ QCOMPARE(candidate1.unownedTouchEvents[0].touchPoints[0].id(), 1);
250+}
251+
252+void tst_TouchRegistry::dispatchesTouchEventsToWatchers()
253+{
254+ TouchRegistry touchRegistry;
255+ QQuickItem rootItem;
256+
257+ DummyCandidate watcher;
258+ watcher.setObjectName("watcher");
259+ watcher.setParentItem(&rootItem);
260+ watcher.setX(1);
261+ watcher.setY(2);
262+
263+ {
264+ QList<QTouchEvent::TouchPoint> touchPoints;
265+ touchPoints.append(QTouchEvent::TouchPoint(0));
266+ touchPoints[0].setState(Qt::TouchPointPressed);
267+ touchPoints[0].setRect(QRect(10, 10, 0, 0));
268+ QTouchEvent touchEvent(QEvent::TouchBegin,
269+ 0 /* device */,
270+ Qt::NoModifier,
271+ Qt::TouchPointPressed,
272+ touchPoints);
273+ touchRegistry.update(&touchEvent);
274+ }
275+
276+ touchRegistry.addTouchWatcher(0, &watcher);
277+
278+ {
279+ QList<QTouchEvent::TouchPoint> touchPoints;
280+ touchPoints.append(QTouchEvent::TouchPoint(0));
281+ touchPoints[0].setState(Qt::TouchPointMoved);
282+ touchPoints[0].setRect(QRect(11, 11, 0, 0));
283+ touchPoints.append(QTouchEvent::TouchPoint(1));
284+ touchPoints[1].setState(Qt::TouchPointPressed);
285+ touchPoints[1].setRect(QRect(20, 20, 0, 0));
286+ QTouchEvent touchEvent(QEvent::TouchUpdate,
287+ 0 /* device */,
288+ Qt::NoModifier,
289+ Qt::TouchPointPressed | Qt::TouchPointMoved,
290+ touchPoints);
291+ touchRegistry.update(&touchEvent);
292+ }
293+
294+ // watcher should have received an update on the touch he's interested on.
295+ QCOMPARE(watcher.unownedTouchEvents.count(), 1);
296+ // make sure only touch 0 is there (i.e. no mention of touch 1)
297+ QCOMPARE(watcher.unownedTouchEvents[0].touchPoints.count(), 1);
298+ QCOMPARE(watcher.unownedTouchEvents[0].touchPoints[0].id(), 0);
299+ // Check that the points local coordinates have been mapped to watcher's coordinate system.
300+ QCOMPARE(watcher.unownedTouchEvents[0].touchPoints[0].rect().x(), 11 - watcher.x());
301+ QCOMPARE(watcher.unownedTouchEvents[0].touchPoints[0].rect().y(), 11 - watcher.y());
302+
303+ {
304+ QList<QTouchEvent::TouchPoint> touchPoints;
305+ touchPoints.append(QTouchEvent::TouchPoint(0));
306+ touchPoints[0].setState(Qt::TouchPointMoved);
307+ touchPoints[0].setRect(QRect(12, 12, 0, 0));
308+ touchPoints.append(QTouchEvent::TouchPoint(1));
309+ touchPoints[1].setState(Qt::TouchPointMoved);
310+ touchPoints[1].setRect(QRect(21, 21, 0, 0));
311+ QTouchEvent touchEvent(QEvent::TouchUpdate,
312+ 0 /* device */,
313+ Qt::NoModifier,
314+ Qt::TouchPointPressed | Qt::TouchPointMoved,
315+ touchPoints);
316+ touchRegistry.update(&touchEvent);
317+ }
318+
319+ // watcher gets updates only for touch 0
320+
321+ QCOMPARE(watcher.unownedTouchEvents.count(), 2);
322+ QCOMPARE(watcher.unownedTouchEvents[1].touchPoints.count(), 1);
323+ QCOMPARE(watcher.unownedTouchEvents[1].touchPoints[0].id(), 0);
324+}
325+
326+void tst_TouchRegistry::keepDispatchingToWatchersAfterLastCandidateGivesUp()
327+{
328+ TouchRegistry touchRegistry;
329+ DummyCandidate item;
330+
331+ {
332+ QList<QTouchEvent::TouchPoint> touchPoints;
333+ touchPoints.append(QTouchEvent::TouchPoint(0));
334+ touchPoints[0].setState(Qt::TouchPointPressed);
335+ QTouchEvent touchEvent(QEvent::TouchBegin,
336+ 0 /* device */,
337+ Qt::NoModifier,
338+ Qt::TouchPointPressed,
339+ touchPoints);
340+ touchRegistry.update(&touchEvent);
341+ }
342+
343+ touchRegistry.addCandidateOwnerForTouch(0, &item);
344+
345+ {
346+ QList<QTouchEvent::TouchPoint> touchPoints;
347+ touchPoints.append(QTouchEvent::TouchPoint(0));
348+ touchPoints[0].setState(Qt::TouchPointMoved);
349+ touchPoints.append(QTouchEvent::TouchPoint(1));
350+ touchPoints[1].setState(Qt::TouchPointPressed);
351+ QTouchEvent touchEvent(QEvent::TouchUpdate,
352+ 0 /* device */,
353+ Qt::NoModifier,
354+ Qt::TouchPointPressed | Qt::TouchPointMoved,
355+ touchPoints);
356+ touchRegistry.update(&touchEvent);
357+ }
358+
359+ QCOMPARE(item.unownedTouchEvents.count(), 1);
360+ QCOMPARE(item.unownedTouchEvents[0].touchPoints.count(), 1);
361+ QCOMPARE(item.unownedTouchEvents[0].touchPoints[0].id(), 0);
362+ item.unownedTouchEvents.clear();
363+
364+ touchRegistry.addTouchWatcher(1, &item);
365+
366+ {
367+ QList<QTouchEvent::TouchPoint> touchPoints;
368+ touchPoints.append(QTouchEvent::TouchPoint(0));
369+ touchPoints[0].setState(Qt::TouchPointMoved);
370+ touchPoints.append(QTouchEvent::TouchPoint(1));
371+ touchPoints[1].setState(Qt::TouchPointMoved);
372+ QTouchEvent touchEvent(QEvent::TouchUpdate,
373+ 0 /* device */,
374+ Qt::NoModifier,
375+ Qt::TouchPointMoved,
376+ touchPoints);
377+ touchRegistry.update(&touchEvent);
378+ }
379+
380+ QCOMPARE(item.unownedTouchEvents.count(), 1);
381+ QCOMPARE(item.unownedTouchEvents[0].touchPoints.count(), 2);
382+ QVERIFY(item.unownedTouchEvents[0].containsTouchWithId(0));
383+ QVERIFY(item.unownedTouchEvents[0].containsTouchWithId(1));
384+ item.unownedTouchEvents.clear();
385+
386+ {
387+ QList<QTouchEvent::TouchPoint> touchPoints;
388+ touchPoints.append(QTouchEvent::TouchPoint(0));
389+ touchPoints[0].setState(Qt::TouchPointReleased);
390+ touchPoints.append(QTouchEvent::TouchPoint(1));
391+ touchPoints[1].setState(Qt::TouchPointMoved);
392+ QTouchEvent touchEvent(QEvent::TouchUpdate,
393+ 0 /* device */,
394+ Qt::NoModifier,
395+ Qt::TouchPointReleased | Qt::TouchPointMoved,
396+ touchPoints);
397+ touchRegistry.update(&touchEvent);
398+ }
399+
400+ QCOMPARE(item.unownedTouchEvents.count(), 1);
401+ QCOMPARE(item.unownedTouchEvents[0].touchPoints.count(), 2);
402+ QVERIFY(item.unownedTouchEvents[0].containsTouchWithId(0));
403+ QVERIFY(item.unownedTouchEvents[0].containsTouchWithId(1));
404+ item.unownedTouchEvents.clear();
405+
406+ touchRegistry.removeCandidateOwnerForTouch(0, &item);
407+
408+ {
409+ QList<QTouchEvent::TouchPoint> touchPoints;
410+ touchPoints.append(QTouchEvent::TouchPoint(1));
411+ touchPoints[0].setState(Qt::TouchPointReleased);
412+ QTouchEvent touchEvent(QEvent::TouchEnd,
413+ 0 /* device */,
414+ Qt::NoModifier,
415+ Qt::TouchPointReleased,
416+ touchPoints);
417+ touchRegistry.update(&touchEvent);
418+ }
419+
420+ QCOMPARE(item.unownedTouchEvents.count(), 1);
421+ QCOMPARE(item.unownedTouchEvents[0].touchPoints.count(), 1);
422+ QVERIFY(item.unownedTouchEvents[0].containsTouchWithId(1));
423+ item.unownedTouchEvents.clear();
424+
425+ QVERIFY(touchRegistry.m_touchInfoPool.isEmpty());
426+}
427+
428+/*
429+ Regression test that reproduces a problematic scenario that came up during manual testing.
430+ It reproduces the interaction between TouchRegistry and a DirectionalDragArea
431+ */
432+void tst_TouchRegistry::candidatesAndWatchers_1()
433+{
434+ TouchRegistry touchRegistry;
435+ DummyCandidate item;
436+
437+ {
438+ QList<QTouchEvent::TouchPoint> touchPoints;
439+ touchPoints.append(QTouchEvent::TouchPoint(0));
440+ touchPoints[0].setState(Qt::TouchPointPressed);
441+ QTouchEvent touchEvent(QEvent::TouchBegin,
442+ 0 /* device */,
443+ Qt::NoModifier,
444+ Qt::TouchPointPressed,
445+ touchPoints);
446+ touchRegistry.update(&touchEvent);
447+ }
448+
449+ touchRegistry.addCandidateOwnerForTouch(0, &item);
450+
451+ {
452+ QList<QTouchEvent::TouchPoint> touchPoints;
453+ touchPoints.append(QTouchEvent::TouchPoint(0));
454+ touchPoints[0].setState(Qt::TouchPointMoved);
455+ touchPoints.append(QTouchEvent::TouchPoint(1));
456+ touchPoints[1].setState(Qt::TouchPointPressed);
457+ QTouchEvent touchEvent(QEvent::TouchUpdate,
458+ 0 /* device */,
459+ Qt::NoModifier,
460+ Qt::TouchPointPressed | Qt::TouchPointMoved,
461+ touchPoints);
462+ touchRegistry.update(&touchEvent);
463+ }
464+
465+ touchRegistry.addTouchWatcher(1, &item);
466+
467+ touchRegistry.removeCandidateOwnerForTouch(0, &item);
468+
469+ touchRegistry.addTouchWatcher(0, &item);
470+
471+ {
472+ QList<QTouchEvent::TouchPoint> touchPoints;
473+ touchPoints.append(QTouchEvent::TouchPoint(0));
474+ touchPoints[0].setState(Qt::TouchPointReleased);
475+ touchPoints.append(QTouchEvent::TouchPoint(1));
476+ touchPoints[1].setState(Qt::TouchPointMoved);
477+ QTouchEvent touchEvent(QEvent::TouchUpdate,
478+ 0 /* device */,
479+ Qt::NoModifier,
480+ Qt::TouchPointReleased | Qt::TouchPointMoved,
481+ touchPoints);
482+ touchRegistry.update(&touchEvent);
483+ }
484+
485+ {
486+ QList<QTouchEvent::TouchPoint> touchPoints;
487+ touchPoints.append(QTouchEvent::TouchPoint(1));
488+ touchPoints[0].setState(Qt::TouchPointReleased);
489+ QTouchEvent touchEvent(QEvent::TouchEnd,
490+ 0 /* device */,
491+ Qt::NoModifier,
492+ Qt::TouchPointReleased,
493+ touchPoints);
494+ touchRegistry.update(&touchEvent);
495+ }
496+
497+ QVERIFY(touchRegistry.m_touchInfoPool.isEmpty());
498+
499+ item.unownedTouchEvents.clear();
500+
501+ {
502+ QList<QTouchEvent::TouchPoint> touchPoints;
503+ touchPoints.append(QTouchEvent::TouchPoint(0));
504+ touchPoints[0].setState(Qt::TouchPointPressed);
505+ QTouchEvent touchEvent(QEvent::TouchBegin,
506+ 0 /* device */,
507+ Qt::NoModifier,
508+ Qt::TouchPointPressed,
509+ touchPoints);
510+ touchRegistry.update(&touchEvent);
511+ }
512+
513+ // Haven't made any subscription for that new touch 0.
514+ QCOMPARE(item.unownedTouchEvents.count(), 0);
515+}
516+
517+/*
518+ Regression test that reproduces a problematic scenario that came up during manual testing.
519+ It reproduces the interaction between TouchRegistry, DirectionalDragArea and a TouchGate.
520+ */
521+void tst_TouchRegistry::candidatesAndWatchers_2()
522+{
523+ TouchRegistry touchRegistry;
524+
525+ DummyCandidate directionalDragArea;
526+ directionalDragArea.setObjectName("DirectionalDragArea");
527+
528+ DummyCandidate touchGate;
529+ touchGate.setObjectName("TouchGate");
530+
531+ // [DDA] 1298 TouchBegin (id:0, state:pressed, scenePos:(147,1023))
532+ {
533+ QList<QTouchEvent::TouchPoint> touchPoints;
534+ touchPoints.append(QTouchEvent::TouchPoint(0));
535+ touchPoints[0].setState(Qt::TouchPointPressed);
536+ QTouchEvent touchEvent(QEvent::TouchBegin,
537+ 0 /* device */,
538+ Qt::NoModifier,
539+ Qt::TouchPointPressed,
540+ touchPoints);
541+ touchRegistry.update(&touchEvent);
542+ }
543+
544+ // [TouchRegistry] addCandidateOwnerForTouch id 0 candidate DirectionalDragArea
545+ touchRegistry.addCandidateOwnerForTouch(0, &directionalDragArea);
546+
547+ // [TouchRegistry] requestTouchOwnership id 0 candidate TouchGate
548+ touchRegistry.requestTouchOwnership(0, &touchGate);
549+
550+ //[TouchRegistry] got TouchUpdate (id:0, state:moved, scenePos:(147,1023)) (id:1, state:pressed, scenePos:(327,792))
551+ {
552+ QList<QTouchEvent::TouchPoint> touchPoints;
553+ touchPoints.append(QTouchEvent::TouchPoint(0));
554+ touchPoints[0].setState(Qt::TouchPointMoved);
555+ touchPoints.append(QTouchEvent::TouchPoint(1));
556+ touchPoints[1].setState(Qt::TouchPointPressed);
557+ QTouchEvent touchEvent(QEvent::TouchUpdate,
558+ 0 /* device */,
559+ Qt::NoModifier,
560+ Qt::TouchPointPressed | Qt::TouchPointMoved,
561+ touchPoints);
562+ touchRegistry.update(&touchEvent);
563+ }
564+
565+ // [TouchRegistry] addTouchWatcher id 1 watcher DirectionalDragArea
566+ touchRegistry.addTouchWatcher(1, &directionalDragArea);
567+
568+ // [TouchRegistry] removeCandidateOwnerForTouch id 0 candidate DirectionalDragArea
569+ touchRegistry.removeCandidateOwnerForTouch(0, &directionalDragArea);
570+
571+ //[TouchRegistry] sending TouchWonershipEvent(id = 0 gained) to candidate TouchGate
572+ QCOMPARE(touchGate.ownedTouches.count(), 1);
573+ QVERIFY(touchGate.ownedTouches.contains(0));
574+
575+ // [TouchRegistry] addTouchWatcher id 0 watcher DirectionalDragArea
576+ touchRegistry.addTouchWatcher(0, &directionalDragArea);
577+
578+ // [TouchRegistry] requestTouchOwnership id 1 candidate TouchGate
579+ touchRegistry.requestTouchOwnership(1, &touchGate);
580+
581+ //[TouchRegistry] sending TouchWonershipEvent(id = 1 gained) to candidate TouchGate
582+ QCOMPARE(touchGate.ownedTouches.count(), 2);
583+ QVERIFY(touchGate.ownedTouches.contains(1));
584+
585+ directionalDragArea.unownedTouchEvents.clear();
586+ touchGate.unownedTouchEvents.clear();
587+
588+ //[TouchRegistry] got TouchUpdate (id:0, state:moved, scenePos:(148,1025)) (id:1, state:moved, scenePos:(329,795))
589+ {
590+ QList<QTouchEvent::TouchPoint> touchPoints;
591+ touchPoints.append(QTouchEvent::TouchPoint(0));
592+ touchPoints[0].setState(Qt::TouchPointMoved);
593+ touchPoints.append(QTouchEvent::TouchPoint(1));
594+ touchPoints[1].setState(Qt::TouchPointMoved);
595+ QTouchEvent touchEvent(QEvent::TouchUpdate,
596+ 0 /* device */,
597+ Qt::NoModifier,
598+ Qt::TouchPointMoved,
599+ touchPoints);
600+ touchRegistry.update(&touchEvent);
601+ }
602+
603+ //vvvvv DDA Watchers are being ignored from now on vvvvvvv
604+
605+ QCOMPARE(directionalDragArea.unownedTouchEvents.count(), 1);
606+ QCOMPARE(directionalDragArea.unownedTouchEvents.first().touchPoints.count(), 2);
607+ QVERIFY(directionalDragArea.unownedTouchEvents.first().containsTouchWithId(0));
608+ QVERIFY(directionalDragArea.unownedTouchEvents.first().containsTouchWithId(1));
609+
610+ QVERIFY(touchGate.unownedTouchEvents.isEmpty());
611+
612+ directionalDragArea.unownedTouchEvents.clear();
613+
614+ //[TouchRegistry] got TouchUpdate (id:0, state:moved, scenePos:(151,1029)) (id:1, state:released, scenePos:(329,795))
615+ {
616+ QList<QTouchEvent::TouchPoint> touchPoints;
617+ touchPoints.append(QTouchEvent::TouchPoint(0));
618+ touchPoints[0].setState(Qt::TouchPointMoved);
619+ touchPoints.append(QTouchEvent::TouchPoint(1));
620+ touchPoints[1].setState(Qt::TouchPointReleased);
621+ QTouchEvent touchEvent(QEvent::TouchUpdate,
622+ 0 /* device */,
623+ Qt::NoModifier,
624+ Qt::TouchPointMoved | Qt::TouchPointReleased,
625+ touchPoints);
626+ touchRegistry.update(&touchEvent);
627+ }
628+
629+ QCOMPARE(directionalDragArea.unownedTouchEvents.count(), 1);
630+ QCOMPARE(directionalDragArea.unownedTouchEvents.first().touchPoints.count(), 2);
631+ QVERIFY(directionalDragArea.unownedTouchEvents.first().containsTouchWithId(0));
632+ QVERIFY(directionalDragArea.unownedTouchEvents.first().containsTouchWithId(1));
633+
634+ QVERIFY(touchGate.unownedTouchEvents.isEmpty());
635+
636+ directionalDragArea.unownedTouchEvents.clear();
637+
638+ //[TouchRegistry] got TouchEnd (id:0, state:released, scenePos:(157,1034))
639+ {
640+ QList<QTouchEvent::TouchPoint> touchPoints;
641+ touchPoints.append(QTouchEvent::TouchPoint(0));
642+ touchPoints[0].setState(Qt::TouchPointReleased);
643+ QTouchEvent touchEvent(QEvent::TouchEnd,
644+ 0 /* device */,
645+ Qt::NoModifier,
646+ Qt::TouchPointReleased,
647+ touchPoints);
648+ touchRegistry.update(&touchEvent);
649+ }
650+
651+ QCOMPARE(directionalDragArea.unownedTouchEvents.count(), 1);
652+ QCOMPARE(directionalDragArea.unownedTouchEvents.first().touchPoints.count(), 1);
653+ QVERIFY(directionalDragArea.unownedTouchEvents.first().containsTouchWithId(0));
654+
655+ QVERIFY(touchGate.unownedTouchEvents.isEmpty());
656+}
657+
658+void tst_TouchRegistry::rejectingTouchfterItsEnd()
659+{
660+ TouchRegistry touchRegistry;
661+ DummyCandidate earlyCandidate;
662+ DummyCandidate lateCandidate;
663+
664+ {
665+ QList<QTouchEvent::TouchPoint> touchPoints;
666+ touchPoints.append(QTouchEvent::TouchPoint(0));
667+ touchPoints[0].setState(Qt::TouchPointPressed);
668+ QTouchEvent touchEvent(QEvent::TouchBegin,
669+ 0 /* device */,
670+ Qt::NoModifier,
671+ Qt::TouchPointPressed,
672+ touchPoints);
673+ touchRegistry.update(&touchEvent);
674+ }
675+
676+ touchRegistry.addCandidateOwnerForTouch(0, &earlyCandidate);
677+ touchRegistry.addCandidateOwnerForTouch(0, &lateCandidate);
678+
679+ QVERIFY(earlyCandidate.ownedTouches.isEmpty());
680+ QVERIFY(lateCandidate.ownedTouches.isEmpty());
681+
682+ touchRegistry.requestTouchOwnership(0, &lateCandidate);
683+
684+ QVERIFY(earlyCandidate.ownedTouches.isEmpty());
685+ QVERIFY(lateCandidate.ownedTouches.isEmpty());
686+
687+ {
688+ QList<QTouchEvent::TouchPoint> touchPoints;
689+ touchPoints.append(QTouchEvent::TouchPoint(0));
690+ touchPoints[0].setState(Qt::TouchPointReleased);
691+ QTouchEvent touchEvent(QEvent::TouchEnd,
692+ 0 /* device */,
693+ Qt::NoModifier,
694+ Qt::TouchPointPressed,
695+ touchPoints);
696+ touchRegistry.update(&touchEvent);
697+ }
698+
699+ QVERIFY(earlyCandidate.ownedTouches.isEmpty());
700+ QVERIFY(lateCandidate.ownedTouches.isEmpty());
701+
702+ touchRegistry.removeCandidateOwnerForTouch(0, &earlyCandidate);
703+
704+ QCOMPARE(lateCandidate.ownedTouches.count(), 1);
705+ QCOMPARE(lateCandidate.ownedTouches.contains(0), 1);
706+
707+ // Check that there's no trace left of touch 0 as we no longer need to keep tabs on it.
708+ QVERIFY(!touchRegistry.findTouchInfo(0));
709+}
710+
711+void tst_TouchRegistry::removeOldUndecidedCandidates()
712+{
713+ FakeTimerFactory *fakeTimerFactory = new FakeTimerFactory;
714+ TouchRegistry *touchRegistry = new TouchRegistry(nullptr, fakeTimerFactory);
715+
716+ DummyCandidate undecidedCandidate;
717+ undecidedCandidate.setObjectName("undecided");
718+
719+ DummyCandidate candidateThatWantsTouch;
720+ candidateThatWantsTouch.setObjectName("wantsTouch");
721+
722+ {
723+ QList<QTouchEvent::TouchPoint> touchPoints;
724+ touchPoints.append(QTouchEvent::TouchPoint(0));
725+ touchPoints[0].setState(Qt::TouchPointPressed);
726+ QTouchEvent touchEvent(QEvent::TouchBegin,
727+ 0 /* device */,
728+ Qt::NoModifier,
729+ Qt::TouchPointPressed,
730+ touchPoints);
731+ touchRegistry->update(&touchEvent);
732+ }
733+
734+ touchRegistry->addCandidateOwnerForTouch(0, &undecidedCandidate);
735+
736+ touchRegistry->requestTouchOwnership(0, &candidateThatWantsTouch);
737+
738+ QVERIFY(undecidedCandidate.ownedTouches.isEmpty());
739+ QVERIFY(undecidedCandidate.lostTouches.isEmpty());
740+ QVERIFY(candidateThatWantsTouch.ownedTouches.isEmpty());
741+ QVERIFY(candidateThatWantsTouch.lostTouches.isEmpty());
742+
743+ // Simulate that enough time has passed to cause the CandidateInactivityTimer to timeout,
744+ // making TouchRegistry consider that undecidedCantidate defaulted.
745+ fakeTimerFactory->makeRunningTimersTimeout();
746+
747+ QVERIFY(undecidedCandidate.ownedTouches.isEmpty());
748+ QVERIFY(undecidedCandidate.lostTouches.contains(0));
749+ QVERIFY(candidateThatWantsTouch.ownedTouches.contains(0));
750+ QVERIFY(candidateThatWantsTouch.lostTouches.isEmpty());
751+
752+ delete touchRegistry;
753+}
754+
755+////////////// TouchMemento //////////
756+
757+TouchMemento::TouchMemento(const QTouchEvent *touchEvent)
758+ : touchPointStates(touchEvent->touchPointStates()), touchPoints(touchEvent->touchPoints())
759+{
760+}
761+
762+bool TouchMemento::containsTouchWithId(int touchId) const
763+{
764+ for (int i = 0; i < touchPoints.count(); ++i) {
765+ if (touchPoints.at(i).id() == touchId) {
766+ return true;
767+ }
768+ }
769+ return false;
770+}
771+
772+////////////// DummyCandidate //////////
773+
774+bool DummyCandidate::event(QEvent *e)
775+{
776+ if (e->type() == TouchOwnershipEvent::touchOwnershipEventType()) {
777+ TouchOwnershipEvent *touchOwnershipEvent = static_cast<TouchOwnershipEvent *>(e);
778+
779+ // NB: Cannot use QVERIFY here because the macro doesn't return a boolean and is
780+ // meant for use only directly in the body of a test function
781+ if (ownedTouches.contains(touchOwnershipEvent->touchId()))
782+ qFatal("Sent ownership event for a touch that is already owned.");
783+ if (lostTouches.contains(touchOwnershipEvent->touchId()))
784+ qFatal("Sent ownership event for a touch that has already been lost.");
785+
786+ if (touchOwnershipEvent->gained()) {
787+ ownedTouches.insert(touchOwnershipEvent->touchId());
788+ } else {
789+ lostTouches.insert(touchOwnershipEvent->touchId());
790+ }
791+ return true;
792+ } else if (e->type() == UnownedTouchEvent::unownedTouchEventType()) {
793+ UnownedTouchEvent *unownedTouchEvent = static_cast<UnownedTouchEvent *>(e);
794+ unownedTouchEvents.append(TouchMemento(unownedTouchEvent->touchEvent()));
795+ return true;
796+ } else {
797+ return QObject::event(e);
798+ }
799+}
800+
801+QTEST_GUILESS_MAIN(tst_TouchRegistry)
802+
803+#include "tst_TouchRegistry.moc"
tests/mocks/Unity/Application/MirSurfaceItem.cpp+18−22
@@ -40,6 +40,8 @@ MirSurfaceItem::MirSurfaceItem(const QString& name,
40 , m_state(state)
40 , m_state(state)
41 , m_live(true)
41 , m_live(true)
42 , m_orientation(Qt::PortraitOrientation)
42 , m_orientation(Qt::PortraitOrientation)
43+ , m_touchPressCount(0)
44+ , m_touchReleaseCount(0)
43 , m_qmlItem(nullptr)
45 , m_qmlItem(nullptr)
44 , m_screenshotUrl(screenshot)
46 , m_screenshotUrl(screenshot)
45{
47{
@@ -144,24 +146,12 @@ void MirSurfaceItem::setLive(bool live)
144 }
146 }
145}
147}
146
148
147−void MirSurfaceItem::onQmlWantInputMethodChanged()
148−{
149− QQmlProperty prop(m_qmlItem, "wantInputMethod");
150− bool wantInputMethod = prop.read().toBool();
151−
152− if (hasFocus() && wantInputMethod) {
153− Q_EMIT inputMethodRequested();
154− }
155−}
156−
157void MirSurfaceItem::onFocusChanged()
149void MirSurfaceItem::onFocusChanged()
158{
150{
159− QQmlProperty prop(m_qmlItem, "wantInputMethod");
160− bool wantInputMethod = prop.read().toBool();
161−
162− if (!hasFocus() && wantInputMethod) {
151+ if (!hasFocus()) {
152+ // Causes a crash in tst_Shell.qml, inside the mock Unity.Application itself.
153+ // Didn't have time to debug yet.
163− Q_EMIT inputMethodDismissed();
154+ //Q_EMIT inputMethodDismissed();
164− prop.write(QVariant::fromValue(false));
165 }
155 }
166}
156}
167
157
@@ -194,12 +184,18 @@ void MirSurfaceItem::createQmlContentItem()
194 QQmlProperty screenshotSource(m_qmlItem, "screenshotSource");
184 QQmlProperty screenshotSource(m_qmlItem, "screenshotSource");
195 screenshotSource.write(QVariant::fromValue(m_screenshotUrl));
185 screenshotSource.write(QVariant::fromValue(m_screenshotUrl));
196 }
186 }
187+}
197
188
198− {
189+void MirSurfaceItem::touchEvent(QTouchEvent * event)
199− QQmlProperty prop(m_qmlItem, "wantInputMethod");
190+{
200− if (prop.type() == QQmlProperty::Property) {
191+ if (event->touchPointStates() & Qt::TouchPointPressed) {
201− bool ok = prop.connectNotifySignal(this, SLOT(onQmlWantInputMethodChanged()));
192+ ++m_touchPressCount;
202− if (!ok) qCritical("MirSurfaceItem: failed to connect to wantInputMethod notify signal");
193+ Q_EMIT touchPressCountChanged(m_touchPressCount);
203− }
194+ // Causes a crash in tst_Shell.qml, inside the mock Unity.Application itself.
195+ // Didn't have time to debug yet.
196+ // Q_EMIT inputMethodRequested();
197+ } else if (event->touchPointStates() & Qt::TouchPointReleased) {
198+ ++m_touchReleaseCount;
199+ Q_EMIT touchReleaseCountChanged(m_touchReleaseCount);
204 }
200 }
205}
201}
tests/mocks/Unity/Application/MirSurfaceItem.h+18−1
@@ -36,6 +36,11 @@ class MirSurfaceItem : public QQuickItem
36 Q_PROPERTY(bool live READ live NOTIFY liveChanged)
36 Q_PROPERTY(bool live READ live NOTIFY liveChanged)
37 Q_PROPERTY(Qt::ScreenOrientation orientation READ orientation WRITE setOrientation NOTIFY orientationChanged DESIGNABLE false)
37 Q_PROPERTY(Qt::ScreenOrientation orientation READ orientation WRITE setOrientation NOTIFY orientationChanged DESIGNABLE false)
38
38
39+ Q_PROPERTY(int touchPressCount READ touchPressCount WRITE setTouchPressCount NOTIFY touchPressCountChanged
40+ DESIGNABLE false)
41+ Q_PROPERTY(int touchReleaseCount READ touchReleaseCount WRITE setTouchReleaseCount NOTIFY touchReleaseCountChanged
42+ DESIGNABLE false)
43+
39public:
44public:
40 enum Type {
45 enum Type {
41 Normal,
46 Normal,
@@ -73,6 +78,12 @@ public:
73 void setScreenshot(const QUrl& screenshot);
78 void setScreenshot(const QUrl& screenshot);
74 void setLive(bool live);
79 void setLive(bool live);
75
80
81+ int touchPressCount() const { return m_touchPressCount; }
82+ void setTouchPressCount(int count) { m_touchPressCount = count; Q_EMIT touchPressCountChanged(count); }
83+
84+ int touchReleaseCount() const { return m_touchReleaseCount; }
85+ void setTouchReleaseCount(int count) { m_touchReleaseCount = count; Q_EMIT touchReleaseCountChanged(count); }
86+
76 Q_INVOKABLE void setState(State newState);
87 Q_INVOKABLE void setState(State newState);
77 Q_INVOKABLE void release();
88 Q_INVOKABLE void release();
78
89
@@ -81,6 +92,8 @@ Q_SIGNALS:
81 void stateChanged(State);
92 void stateChanged(State);
82 void liveChanged(bool live);
93 void liveChanged(bool live);
83 void orientationChanged();
94 void orientationChanged();
95+ void touchPressCountChanged(int count);
96+ void touchReleaseCountChanged(int count);
84
97
85 void inputMethodRequested();
98 void inputMethodRequested();
86 void inputMethodDismissed();
99 void inputMethodDismissed();
@@ -88,10 +101,12 @@ Q_SIGNALS:
88 // internal mock use
101 // internal mock use
89 void deregister();
102 void deregister();
90
103
104+protected:
105+ void touchEvent(QTouchEvent * event) override;
106+
91private Q_SLOTS:
107private Q_SLOTS:
92 void onFocusChanged();
108 void onFocusChanged();
93 void onComponentStatusChanged(QQmlComponent::Status status);
109 void onComponentStatusChanged(QQmlComponent::Status status);
94− void onQmlWantInputMethodChanged();
95
110
96private:
111private:
97 explicit MirSurfaceItem(const QString& name,
112 explicit MirSurfaceItem(const QString& name,
@@ -110,6 +125,8 @@ private:
110 State m_state;
125 State m_state;
111 bool m_live;
126 bool m_live;
112 Qt::ScreenOrientation m_orientation;
127 Qt::ScreenOrientation m_orientation;
128+ int m_touchPressCount;
129+ int m_touchReleaseCount;
113
130
114 QQmlComponent *m_qmlContentComponent;
131 QQmlComponent *m_qmlContentComponent;
115 QQuickItem *m_qmlItem;
132 QQuickItem *m_qmlItem;
tests/mocks/Unity/Application/MirSurfaceItem.qml−12
@@ -17,7 +17,6 @@
17import QtQuick 2.0
17import QtQuick 2.0
18
18
19Rectangle {
19Rectangle {
20− objectName: "fakeSurfaceQML"
21 id: root
20 id: root
22 color: "pink"
21 color: "pink"
23
22
@@ -38,11 +37,6 @@ Rectangle {
38 property alias screenshotSource: screenshotImage.source
37 property alias screenshotSource: screenshotImage.source
39 property int orientation: Qt.PortraitOrientation
38 property int orientation: Qt.PortraitOrientation
40
39
41− property bool wantInputMethod: false
42−
43− property int touchPressCount: 0
44− property int touchReleaseCount: 0
45−
46 Image {
40 Image {
47 id: screenshotImage
41 id: screenshotImage
48 anchors.fill: parent
42 anchors.fill: parent
@@ -58,10 +52,4 @@ Rectangle {
58 minimumPixelSize: 10; font.pixelSize: 200
52 minimumPixelSize: 10; font.pixelSize: 200
59 verticalAlignment: Text.AlignVCenter
53 verticalAlignment: Text.AlignVCenter
60 }
54 }
61−
62− MultiPointTouchArea {
63− anchors.fill: parent
64− onPressed: { root.wantInputMethod = true; root.touchPressCount++; }
65− onReleased: { root.touchReleaseCount++; }
66− }
67}
55}
tests/plugins/Ubuntu/Gestures/CMakeLists.txt+4−2
@@ -2,6 +2,7 @@ include(QmlTest)
2
2
3include_directories(
3include_directories(
4 ${CMAKE_SOURCE_DIR}/plugins/Ubuntu/Gestures
4 ${CMAKE_SOURCE_DIR}/plugins/Ubuntu/Gestures
5+ ${CMAKE_SOURCE_DIR}/libs/UbuntuGestures
5 ${CMAKE_CURRENT_BINARY_DIR}
6 ${CMAKE_CURRENT_BINARY_DIR}
6 ${Qt5Gui_PRIVATE_INCLUDE_DIRS}
7 ${Qt5Gui_PRIVATE_INCLUDE_DIRS}
7 )
8 )
@@ -17,7 +18,7 @@ add_definitions(-DUBUNTU_GESTURES_PLUGIN_DIR="${CMAKE_BINARY_DIR}/plugins")
17macro(add_gesture_ui_test CLASSNAME)
18macro(add_gesture_ui_test CLASSNAME)
18 add_executable(${CLASSNAME}TestExec tst_${CLASSNAME}.cpp GestureTest.cpp)
19 add_executable(${CLASSNAME}TestExec tst_${CLASSNAME}.cpp GestureTest.cpp)
19 qt5_use_modules(${CLASSNAME}TestExec Test Core Qml Gui Quick)
20 qt5_use_modules(${CLASSNAME}TestExec Test Core Qml Gui Quick)
20− target_link_libraries(${CLASSNAME}TestExec UbuntuGestureQml)
21+ target_link_libraries(${CLASSNAME}TestExec UbuntuGesturesQml UbuntuGestures)
21
22
22 add_binary_qml_test(${CLASSNAME} ${CMAKE_BINARY_DIR}/plugins/Ubuntu/Gestures UbuntuGesturesTestQmlFiles)
23 add_binary_qml_test(${CLASSNAME} ${CMAKE_BINARY_DIR}/plugins/Ubuntu/Gestures UbuntuGesturesTestQmlFiles)
23 add_manual_qml_test(. ${CLASSNAME} IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins)
24 add_manual_qml_test(. ${CLASSNAME} IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins)
@@ -34,10 +35,11 @@ macro(add_gesture_test CLASSNAME)
34
35
35 add_executable(${CLASSNAME}TestExec tst_${CLASSNAME}.cpp)
36 add_executable(${CLASSNAME}TestExec tst_${CLASSNAME}.cpp)
36 qt5_use_modules(${CLASSNAME}TestExec Test Core)
37 qt5_use_modules(${CLASSNAME}TestExec Test Core)
37− target_link_libraries(${CLASSNAME}TestExec UbuntuGestureQml)
38+ target_link_libraries(${CLASSNAME}TestExec UbuntuGesturesQml)
38endmacro(add_gesture_test)
39endmacro(add_gesture_test)
39
40
40add_gesture_ui_test(DirectionalDragArea)
41add_gesture_ui_test(DirectionalDragArea)
41add_gesture_ui_test(PressedOutsideNotifier)
42add_gesture_ui_test(PressedOutsideNotifier)
43+add_gesture_ui_test(TouchGate)
42add_gesture_test(Damper)
44add_gesture_test(Damper)
43add_gesture_test(AxisVelocityCalculator)
45add_gesture_test(AxisVelocityCalculator)
tests/plugins/Ubuntu/Gestures/RightwardsLauncher.qml+3
@@ -46,6 +46,9 @@ Item {
46 id: dragArea
46 id: dragArea
47 objectName: "hpDragArea"
47 objectName: "hpDragArea"
48
48
49+ // give some room for items to be dynamically stacked right behind him
50+ z: 10.0
51+
49 width: units.gu(5)
52 width: units.gu(5)
50
53
51 direction: Direction.Rightwards
54 direction: Direction.Rightwards
tests/plugins/Ubuntu/Gestures/touchGateExample.qmlnew file+27
@@ -0,0 +1,27 @@
1+/*
2+ * Copyright (C) 2014 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.Gestures 0.1
19+
20+Rectangle {
21+ width: 720; height: 720
22+
23+ TouchGate {
24+ anchors.fill: parent
25+ objectName: "touchGate"
26+ }
27+}
tests/plugins/Ubuntu/Gestures/tst_DirectionalDragArea.cpp+415−14
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013-2014 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,36 +20,61 @@
20#include <QtQml/QQmlEngine>
20#include <QtQml/QQmlEngine>
21#include <QPointer>
21#include <QPointer>
22
22
23+// C++ std lib
24+#include <functional>
25+
23#include <DirectionalDragArea.h>
26#include <DirectionalDragArea.h>
27+#include <TouchRegistry.h>
24
28
25#include "GestureTest.h"
29#include "GestureTest.h"
26
30
27using namespace UbuntuGestures;
31using namespace UbuntuGestures;
28
32
29−class FakeTimer : public AbstractTimer
33+class TouchMemento {
34+public:
35+ TouchMemento(const QTouchEvent *touchEvent);
36+ Qt::TouchPointStates touchPointStates;
37+ QList<QTouchEvent::TouchPoint> touchPoints;
38+
39+ bool containsTouchWithId(int touchId) const;
40+};
41+
42+class DummyItem : public QQuickItem
43+{
44+ Q_OBJECT
45+public:
46+ DummyItem(QQuickItem *parent = 0);
47+
48+ QList<TouchMemento> touchEvents;
49+ std::function<void(QTouchEvent*)> touchEventHandler;
50+protected:
51+ void touchEvent(QTouchEvent *event) override;
52+private:
53+ static void defaultTouchEventHandler(QTouchEvent *event);
54+};
55+
56+class ComplexFakeTimer : public FakeTimer
30{
57{
31 Q_OBJECT
58 Q_OBJECT
32public:
59public:
33− FakeTimer(const SharedTimeSource &timeSource, QObject *parent = 0)
60+ ComplexFakeTimer(const SharedTimeSource &timeSource, QObject *parent = 0)
34− : UbuntuGestures::AbstractTimer(parent),
61+ : FakeTimer(parent),
35 m_timeSource(timeSource)
62 m_timeSource(timeSource)
36 {}
63 {}
37
64
38− int interval() const override { return m_interval; }
39− void setInterval(int msecs) override { m_interval = msecs; }
40 void start() override {
65 void start() override {
41 AbstractTimer::start();
66 AbstractTimer::start();
42− m_nextTimeoutTime = m_timeSource->msecsSinceReference() + (qint64)m_interval;
67+ m_nextTimeoutTime = m_timeSource->msecsSinceReference() + (qint64)interval();
43 }
68 }
44
69
45 void emitTimeout() {
70 void emitTimeout() {
46− m_nextTimeoutTime += m_interval;
71+ m_nextTimeoutTime += interval();
47 Q_EMIT timeout();
72 Q_EMIT timeout();
48 }
73 }
49
74
50 qint64 nextTimeoutTime() const { return m_nextTimeoutTime; }
75 qint64 nextTimeoutTime() const { return m_nextTimeoutTime; }
76+
51private:
77private:
52− int m_interval;
53 SharedTimeSource m_timeSource;
78 SharedTimeSource m_timeSource;
54 qint64 m_nextTimeoutTime;
79 qint64 m_nextTimeoutTime;
55};
80};
@@ -87,10 +112,19 @@ private Q_SLOTS:
87 void sceneDistance();
112 void sceneDistance();
88 void sceneDistance_data();
113 void sceneDistance_data();
89 void disabledWhileDragging();
114 void disabledWhileDragging();
115+ void oneFingerDownFollowedByLateSecondFingerDown();
116+ void givesUpWhenLosesTouch();
117+ void threeFingerDrag();
118+ void immediateRecognitionWhenConstraintsDisabled();
119+ void withdrawTouchOwnershipCandidacyIfDisabledDuringRecognition();
120+ void withdrawTouchOwnershipCandidacyIfDisabledDuringRecognition_data();
121+ void tappedSignal();
122+ void tappedSignal_data();
90
123
91private:
124private:
92− void passTime(qint64 timeSpan);
125+ void passTime(qint64 timeSpanMs);
126+ TouchRegistry *touchRegistry;
93− FakeTimer *fakeTimer;
127+ ComplexFakeTimer *fakeTimer;
94 QSharedPointer<FakeTimeSource> fakeTimeSource;
128 QSharedPointer<FakeTimeSource> fakeTimeSource;
95};
129};
96
130
@@ -111,12 +145,19 @@ void tst_DirectionalDragArea::init()
111 QTRY_COMPARE(m_view->width(), (int)m_view->rootObject()->width());
145 QTRY_COMPARE(m_view->width(), (int)m_view->rootObject()->width());
112 QTRY_COMPARE(m_view->height(), (int)m_view->rootObject()->height());
146 QTRY_COMPARE(m_view->height(), (int)m_view->rootObject()->height());
113
147
148+ touchRegistry = new TouchRegistry;
149+ m_view->installEventFilter(touchRegistry);
150+
114 fakeTimeSource.reset(new FakeTimeSource);
151 fakeTimeSource.reset(new FakeTimeSource);
115− fakeTimer = new FakeTimer(fakeTimeSource);
152+ fakeTimer = new ComplexFakeTimer(fakeTimeSource);
116}
153}
117
154
118void tst_DirectionalDragArea::cleanup()
155void tst_DirectionalDragArea::cleanup()
119{
156{
157+ m_view->removeEventFilter(touchRegistry);
158+ delete touchRegistry;
159+ touchRegistry = nullptr;
160+
120 delete fakeTimer;
161 delete fakeTimer;
121 fakeTimer = 0;
162 fakeTimer = 0;
122
163
@@ -125,9 +166,9 @@ void tst_DirectionalDragArea::cleanup()
125 GestureTest::cleanup();
166 GestureTest::cleanup();
126}
167}
127
168
128−void tst_DirectionalDragArea::passTime(qint64 timeSpan)
169+void tst_DirectionalDragArea::passTime(qint64 timeSpanMs)
129{
170{
130− qint64 finalTime = fakeTimeSource->m_msecsSinceReference + timeSpan;
171+ qint64 finalTime = fakeTimeSource->m_msecsSinceReference + timeSpanMs;
131
172
132 if (fakeTimer->isRunning() && finalTime >= fakeTimer->nextTimeoutTime()) {
173 if (fakeTimer->isRunning() && finalTime >= fakeTimer->nextTimeoutTime()) {
133 fakeTimeSource->m_msecsSinceReference = fakeTimer->nextTimeoutTime();
174 fakeTimeSource->m_msecsSinceReference = fakeTimer->nextTimeoutTime();
@@ -849,6 +890,366 @@ void tst_DirectionalDragArea::disabledWhileDragging()
849 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
890 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
850}
891}
851
892
893+void tst_DirectionalDragArea::oneFingerDownFollowedByLateSecondFingerDown()
894+{
895+ DirectionalDragArea *edgeDragArea =
896+ m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
897+ Q_ASSERT(edgeDragArea != 0);
898+ edgeDragArea->setRecognitionTimer(fakeTimer);
899+ edgeDragArea->setTimeSource(fakeTimeSource);
900+
901+ // Disable some constraints we're not interested in
902+ edgeDragArea->setMaxSilenceTime(60 * 1000);
903+ edgeDragArea->setMinSpeed(0);
904+
905+ // And ensure others have the values we want
906+ edgeDragArea->setCompositionTime(60);
907+
908+ // Put an item right behind edgeDragArea to receive the touches ignored by it
909+ DummyItem *dummyItem = new DummyItem;
910+ dummyItem->setParentItem(edgeDragArea->parentItem());
911+ dummyItem->setX(edgeDragArea->x());
912+ dummyItem->setY(edgeDragArea->y());
913+ dummyItem->setZ(edgeDragArea->z() - 1.0);
914+ dummyItem->setWidth(edgeDragArea->width());
915+ dummyItem->setHeight(edgeDragArea->height());
916+
917+ // Make touches evenly spaced along the edgeDragArea
918+ QPoint touch0Pos(edgeDragArea->width()/2.0f, m_view->height()*0.33f);
919+ QPoint touch1Pos(edgeDragArea->width()/2.0f, m_view->height()*0.66f);
920+
921+ QTest::touchEvent(m_view, m_device).press(0, touch0Pos);
922+
923+ QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
924+
925+ // We are now going to be way beyond compositionTime
926+ passTime(edgeDragArea->compositionTime()*3);
927+
928+ QTest::touchEvent(m_view, m_device)
929+ .move(0, touch0Pos)
930+ .press(1, touch1Pos);
931+
932+ // A new touch has come, but as it can't be composed with touch 0, it should be
933+ // ignored/rejected by the DirectionalDragArea
934+ // Therefore the last event received by dummyItem must have both touch points (0 and 1)
935+ {
936+ TouchMemento &touchMemento = dummyItem->touchEvents.last();
937+ QCOMPARE(touchMemento.touchPoints.count(), 2);
938+ QVERIFY(touchMemento.containsTouchWithId(0));
939+ QVERIFY(touchMemento.containsTouchWithId(1));
940+ }
941+
942+ passTime(30);
943+
944+ QTest::touchEvent(m_view, m_device)
945+ .move(0, touch0Pos)
946+ .move(1, touch1Pos);
947+
948+ QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
949+
950+ passTime(5);
951+
952+ QTest::touchEvent(m_view, m_device)
953+ .release(0, touch0Pos)
954+ .move(1, touch1Pos);
955+
956+ QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
957+
958+ passTime(5);
959+
960+ QTest::touchEvent(m_view, m_device)
961+ .release(1, touch1Pos);
962+
963+ // Shouldn't be keepping info about touches that no longer exist or interest us
964+ QVERIFY(edgeDragArea->m_activeTouches.isEmpty());
965+
966+ delete dummyItem;
967+}
968+
969+void tst_DirectionalDragArea::givesUpWhenLosesTouch()
970+{
971+ DirectionalDragArea *edgeDragArea =
972+ m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
973+ Q_ASSERT(edgeDragArea != 0);
974+ edgeDragArea->setRecognitionTimer(fakeTimer);
975+ edgeDragArea->setTimeSource(fakeTimeSource);
976+
977+ // Disable some constraints we're not interested in
978+ edgeDragArea->setMaxSilenceTime(60 * 1000);
979+ edgeDragArea->setMinSpeed(0);
980+
981+ // Put an item right in front of edgeDragArea
982+ DummyItem *dummyItem = new DummyItem(edgeDragArea->parentItem());
983+ dummyItem->setX(edgeDragArea->x());
984+ dummyItem->setY(edgeDragArea->y());
985+ dummyItem->setZ(edgeDragArea->z() + 1.0);
986+ dummyItem->setWidth(edgeDragArea->width());
987+ dummyItem->setHeight(edgeDragArea->height());
988+
989+ QPoint touchPos(edgeDragArea->width()/2.0f, m_view->height()/2.0f);
990+
991+ dummyItem->touchEventHandler = [&](QTouchEvent *event) {
992+ touchRegistry->addCandidateOwnerForTouch(0, dummyItem);
993+ event->ignore();
994+ };
995+
996+ QTest::touchEvent(m_view, m_device).press(0, touchPos);
997+
998+ QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
999+
1000+ touchRegistry->requestTouchOwnership(0, dummyItem);
1001+
1002+ QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
1003+
1004+ dummyItem->grabTouchPoints({0});
1005+ dummyItem->touchEventHandler = [&](QTouchEvent *event) { event->accept(); };
1006+
1007+ passTime(5);
1008+ QTest::touchEvent(m_view, m_device).release(0, touchPos);
1009+
1010+ QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
1011+
1012+ QVERIFY(edgeDragArea->m_activeTouches.isEmpty());
1013+}
1014+
1015+void tst_DirectionalDragArea::threeFingerDrag()
1016+{
1017+ DirectionalDragArea *edgeDragArea =
1018+ m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
1019+ Q_ASSERT(edgeDragArea != 0);
1020+ edgeDragArea->setRecognitionTimer(fakeTimer);
1021+ edgeDragArea->setTimeSource(fakeTimeSource);
1022+
1023+ // Disable some constraints we're not interested in
1024+ edgeDragArea->setMaxSilenceTime(60 * 1000);
1025+ edgeDragArea->setMinSpeed(0);
1026+
1027+ // And ensure others have the values we want
1028+ edgeDragArea->setCompositionTime(60);
1029+
1030+ // Make touches evenly spaced along the edgeDragArea
1031+ QPoint touch0Pos(edgeDragArea->width()/2.0f, m_view->height()*0.25f);
1032+ QPoint touch1Pos(edgeDragArea->width()/2.0f, m_view->height()*0.50f);
1033+ QPoint touch2Pos(edgeDragArea->width()/2.0f, m_view->height()*0.75f);
1034+
1035+ QTest::touchEvent(m_view, m_device)
1036+ .press(0, touch0Pos);
1037+
1038+ QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
1039+
1040+ passTime(5);
1041+ QTest::touchEvent(m_view, m_device)
1042+ .move(0, touch0Pos)
1043+ .press(1, touch1Pos);
1044+
1045+ QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
1046+
1047+ passTime(5);
1048+ QTest::touchEvent(m_view, m_device)
1049+ .move(0, touch0Pos)
1050+ .move(1, touch1Pos)
1051+ .press(2, touch2Pos);
1052+
1053+ QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
1054+
1055+ passTime(10);
1056+ QTest::touchEvent(m_view, m_device)
1057+ .move(0, touch0Pos)
1058+ .move(1, touch1Pos)
1059+ .move(2, touch2Pos);
1060+
1061+ passTime(200);
1062+ QTest::touchEvent(m_view, m_device)
1063+ .move(0, touch0Pos)
1064+ .release(1, touch1Pos)
1065+ .move(2, touch2Pos);
1066+
1067+ passTime(10);
1068+ QTest::touchEvent(m_view, m_device)
1069+ .move(0, touch0Pos)
1070+ .release(2, touch2Pos);
1071+
1072+ passTime(5);
1073+ QTest::touchEvent(m_view, m_device)
1074+ .release(0, touch0Pos);
1075+
1076+ // Shouldn't be keepping info about touches that no longer exist or interest us
1077+ QVERIFY(edgeDragArea->m_activeTouches.isEmpty());
1078+}
1079+
1080+/*
1081+ If all the relevant gesture recognition constraints/parameters have been disabled,
1082+ it means that the gesture recognition itself has been disabled and DirectionalDragArea
1083+ will therefore work like a simple touch area, merely reporting touch movement.
1084+ */
1085+void tst_DirectionalDragArea::immediateRecognitionWhenConstraintsDisabled()
1086+{
1087+ DirectionalDragArea *edgeDragArea =
1088+ m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
1089+ Q_ASSERT(edgeDragArea != 0);
1090+ edgeDragArea->setRecognitionTimer(fakeTimer);
1091+ edgeDragArea->setTimeSource(fakeTimeSource);
1092+
1093+ // Disable the minimum amount of constraints to ensure immediate recognition
1094+ edgeDragArea->setDistanceThreshold(0);
1095+ edgeDragArea->setCompositionTime(0);
1096+
1097+ // Put an item right behind edgeDragArea to receive the touches ignored by it
1098+ DummyItem *dummyItem = new DummyItem;
1099+ dummyItem->setParentItem(edgeDragArea->parentItem());
1100+ dummyItem->setX(edgeDragArea->x());
1101+ dummyItem->setY(edgeDragArea->y());
1102+ dummyItem->setZ(edgeDragArea->z() - 1.0);
1103+ dummyItem->setWidth(edgeDragArea->width());
1104+ dummyItem->setHeight(edgeDragArea->height());
1105+
1106+ QPoint touch0Pos(edgeDragArea->width()/2.0f, m_view->height()/2.0f);
1107+
1108+ QTest::touchEvent(m_view, m_device).press(0, touch0Pos);
1109+
1110+ // check for immediate recognition
1111+ QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
1112+
1113+ // and therefore it should have immediately grabbed the touch point,
1114+ // not letting it leak to items behind him.
1115+ QCOMPARE(dummyItem->touchEvents.count(), 0);
1116+}
1117+
1118+void tst_DirectionalDragArea::withdrawTouchOwnershipCandidacyIfDisabledDuringRecognition()
1119+{
1120+ DirectionalDragArea *edgeDragArea =
1121+ m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
1122+ Q_ASSERT(edgeDragArea != 0);
1123+ edgeDragArea->setRecognitionTimer(fakeTimer);
1124+ edgeDragArea->setTimeSource(fakeTimeSource);
1125+
1126+ QPointF touchPoint = calculateInitialTouchPos(edgeDragArea);
1127+
1128+ // Move less than the minimum needed for the drag gesture recognition
1129+ qreal desiredDragDistance = edgeDragArea->distanceThreshold()*0.5f;
1130+ QPointF dragDirectionVector(1., 0.); // horizontal positive
1131+
1132+ qreal movementStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
1133+ QPointF touchMovement = dragDirectionVector * movementStepDistance;
1134+ int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
1135+ int movementTimeStepMs = (edgeDragArea->compositionTime() * 0.8f) / totalMovementSteps;
1136+
1137+ QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
1138+
1139+ for (int i = 0; i < totalMovementSteps; ++i) {
1140+ touchPoint += touchMovement;
1141+ passTime(movementTimeStepMs);
1142+ QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
1143+ }
1144+
1145+ QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
1146+
1147+ // edgeDragArea should be an undecided candidate
1148+ {
1149+ auto touchInfo = touchRegistry->findTouchInfo(0);
1150+ QCOMPARE(touchInfo->candidates.size(), 1);
1151+ QCOMPARE(touchInfo->candidates.at(0).item.data(), edgeDragArea);
1152+ QCOMPARE(touchInfo->candidates.at(0).undecided, true);
1153+ }
1154+
1155+ // disable the dragArea while it's still recognizing a possible drag gesture.
1156+ QFETCH(bool, disable);
1157+ if (disable) {
1158+ edgeDragArea->setEnabled(false);
1159+ } else {
1160+ edgeDragArea->setVisible(false);
1161+ }
1162+
1163+ // edgeDragArea should no longer be a candidate
1164+ {
1165+ auto touchInfo = touchRegistry->findTouchInfo(0);
1166+ QCOMPARE(touchInfo->candidates.size(), 0);
1167+ }
1168+
1169+ QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
1170+
1171+ QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
1172+}
1173+
1174+void tst_DirectionalDragArea::withdrawTouchOwnershipCandidacyIfDisabledDuringRecognition_data()
1175+{
1176+ QTest::addColumn<bool>("disable");
1177+
1178+ QTest::newRow("disabled") << true;
1179+ QTest::newRow("invisible") << false;
1180+}
1181+
1182+void tst_DirectionalDragArea::tappedSignal()
1183+{
1184+ DirectionalDragArea *edgeDragArea =
1185+ m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
1186+ QVERIFY(edgeDragArea != 0);
1187+ edgeDragArea->setRecognitionTimer(fakeTimer);
1188+ edgeDragArea->setTimeSource(fakeTimeSource);
1189+
1190+ QFETCH(bool, immediateGestureRecognition);
1191+ if (immediateGestureRecognition) {
1192+ // Disable the minimum amount of constraints to ensure immediate recognition
1193+ edgeDragArea->setDistanceThreshold(0);
1194+ edgeDragArea->setCompositionTime(0);
1195+ }
1196+
1197+ QPoint touch0Pos(edgeDragArea->width()/2.0f, m_view->height()/2.0f);
1198+
1199+ QSignalSpy tappedSpy(edgeDragArea, SIGNAL(tapped()));
1200+
1201+ QTest::touchEvent(m_view, m_device).press(0, touch0Pos);
1202+ passTime(edgeDragArea->maxTapDuration() / 2);
1203+ QTest::touchEvent(m_view, m_device).release(0, touch0Pos);
1204+
1205+ QCOMPARE(tappedSpy.count(), 1);
1206+}
1207+
1208+void tst_DirectionalDragArea::tappedSignal_data()
1209+{
1210+ QTest::addColumn<bool>("immediateGestureRecognition");
1211+
1212+ QTest::newRow("immediate gesture recognition") << true;
1213+ QTest::newRow("default gesture recognition") << false;
1214+}
1215+
1216+////////////////////////// TouchMemento /////////////////////////////
1217+
1218+TouchMemento::TouchMemento(const QTouchEvent *touchEvent)
1219+ : touchPointStates(touchEvent->touchPointStates()), touchPoints(touchEvent->touchPoints())
1220+{
1221+
1222+}
1223+
1224+bool TouchMemento::containsTouchWithId(int touchId) const
1225+{
1226+ for (int i = 0; i < touchPoints.count(); ++i) {
1227+ if (touchPoints.at(i).id() == touchId) {
1228+ return true;
1229+ }
1230+ }
1231+ return false;
1232+}
1233+
1234+////////////////////////// DummyItem /////////////////////////////
1235+
1236+DummyItem::DummyItem(QQuickItem *parent)
1237+ : QQuickItem(parent)
1238+{
1239+ touchEventHandler = defaultTouchEventHandler;
1240+}
1241+
1242+void DummyItem::touchEvent(QTouchEvent *event)
1243+{
1244+ touchEvents.append(TouchMemento(event));
1245+ touchEventHandler(event);
1246+}
1247+
1248+void DummyItem::defaultTouchEventHandler(QTouchEvent *event)
1249+{
1250+ event->accept();
1251+}
1252+
852QTEST_MAIN(tst_DirectionalDragArea)
1253QTEST_MAIN(tst_DirectionalDragArea)
853
1254
854#include "tst_DirectionalDragArea.moc"
1255#include "tst_DirectionalDragArea.moc"
tests/plugins/Ubuntu/Gestures/tst_TouchGate.cppnew file+345
@@ -0,0 +1,345 @@
1+/*
2+ * Copyright (C) 2014 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 <qpa/qwindowsysteminterface.h>
18+#include <QtTest>
19+#include <QQmlEngine>
20+#include <QQuickView>
21+#include <QSharedPointer>
22+
23+// C++ std lib
24+#include <functional>
25+
26+#include <TouchGate.h>
27+#include <TouchRegistry.h>
28+
29+class TestItem : public QQuickItem
30+{
31+ Q_OBJECT
32+
33+public:
34+ QList<QSharedPointer<QTouchEvent>> touchEventsReceived;
35+
36+protected:
37+ void touchEvent(QTouchEvent *event) override;
38+};
39+
40+class CandidateItem : public QQuickItem
41+{
42+ Q_OBJECT
43+
44+public:
45+ std::function<void(QTouchEvent*)> touchEventHandler;
46+
47+protected:
48+ void touchEvent(QTouchEvent *event) override;
49+};
50+
51+class tst_TouchGate : public QObject
52+{
53+ Q_OBJECT
54+public:
55+ tst_TouchGate(QObject *parent = nullptr);
56+private Q_SLOTS:
57+ void initTestCase(); // will be called before the first test function is executed
58+ void cleanupTestCase(); // will be called after the last test function was executed.
59+
60+ void init(); // called right before each and every test function is executed
61+ void cleanup(); // called right after each and every test function is executed
62+
63+ void onlyCandidate_passThrough();
64+ void holdsEventsUntilGainsOwnership();
65+ void holdsEventsUntilGainsOwnership_data();
66+ void holdsEventsUntilGainsOwnership_repeatingTouchId();
67+ void dontDispacthToDisabledOrInvisibleTarget();
68+ void dontDispacthToDisabledOrInvisibleTarget_data();
69+
70+private:
71+ QQuickView *createView();
72+ TouchRegistry *touchRegistry;
73+ QQuickView *view;
74+ QTouchDevice *device;
75+};
76+
77+tst_TouchGate::tst_TouchGate(QObject *parent)
78+ : QObject(parent)
79+ , touchRegistry(nullptr)
80+ , view(nullptr)
81+ , device(nullptr)
82+{
83+}
84+
85+void tst_TouchGate::initTestCase()
86+{
87+ if (!device) {
88+ device = new QTouchDevice;
89+ device->setType(QTouchDevice::TouchScreen);
90+ QWindowSystemInterface::registerTouchDevice(device);
91+ }
92+}
93+
94+void tst_TouchGate::cleanupTestCase()
95+{
96+}
97+
98+void tst_TouchGate::init()
99+{
100+ touchRegistry = new TouchRegistry;
101+
102+ view = createView();
103+ view->installEventFilter(touchRegistry);
104+ view->setSource(QUrl::fromLocalFile("touchGateExample.qml"));
105+ view->show();
106+ QVERIFY(QTest::qWaitForWindowExposed(view));
107+ QVERIFY(view->rootObject() != 0);
108+ qApp->processEvents();
109+}
110+
111+void tst_TouchGate::cleanup()
112+{
113+ delete view;
114+ view = 0;
115+
116+ delete touchRegistry;
117+ touchRegistry = nullptr;
118+}
119+
120+QQuickView *tst_TouchGate::createView()
121+{
122+ QQuickView *window = new QQuickView(0);
123+ window->setResizeMode(QQuickView::SizeRootObjectToView);
124+ window->resize(720, 720);
125+ window->engine()->addImportPath(QLatin1String(UBUNTU_GESTURES_PLUGIN_DIR));
126+
127+ return window;
128+}
129+
130+/*
131+ Simplest case. TouchGate is the only candidate for the touch point, thus it
132+ will instantly gain ownership causing the event to pass through it directly to
133+ its child.
134+ */
135+void tst_TouchGate::onlyCandidate_passThrough()
136+{
137+ TouchGate *touchGate = view->rootObject()->findChild<TouchGate*>("touchGate");
138+ Q_ASSERT(touchGate);
139+
140+ TestItem *testItem = new TestItem;
141+ testItem->setWidth(touchGate->width());
142+ testItem->setHeight(touchGate->height());
143+ testItem->setParentItem(view->rootObject());
144+ testItem->setZ(0.0);
145+
146+ touchGate->setZ(1.0);
147+ touchGate->setTargetItem(testItem);
148+
149+ QTest::touchEvent(view, device)
150+ .press(0, QPoint(100,100));
151+
152+ QCOMPARE(testItem->touchEventsReceived.count(), 1);
153+ QVERIFY(touchGate->m_storedEvents.isEmpty());
154+}
155+
156+void tst_TouchGate::holdsEventsUntilGainsOwnership()
157+{
158+ TouchGate *touchGate = view->rootObject()->findChild<TouchGate*>("touchGate");
159+ Q_ASSERT(touchGate);
160+ QFETCH(bool, ownershipAfterTouchEnd);
161+
162+ TestItem *testItem = new TestItem;
163+ testItem->setWidth(touchGate->width());
164+ testItem->setHeight(touchGate->height());
165+ testItem->setParentItem(view->rootObject());
166+ testItem->setZ(0.0);
167+
168+ touchGate->setZ(1.0);
169+ touchGate->setTargetItem(testItem);
170+
171+ // Put it in front of touchGate
172+ CandidateItem *candidateItem = new CandidateItem;
173+ candidateItem->setWidth(touchGate->width());
174+ candidateItem->setHeight(touchGate->height());
175+ candidateItem->setParentItem(view->rootObject());
176+ candidateItem->setZ(2.0);
177+
178+ candidateItem->touchEventHandler = [&](QTouchEvent* event) {
179+ touchRegistry->addCandidateOwnerForTouch(0, candidateItem);
180+ event->ignore();
181+ };
182+
183+ QTest::touchEvent(view, device)
184+ .press(0, QPoint(100,100));
185+
186+ QCOMPARE(testItem->touchEventsReceived.count(), 0);
187+
188+ QTest::touchEvent(view, device)
189+ .move(0, QPoint(101,101));
190+
191+ QCOMPARE(testItem->touchEventsReceived.count(), 0);
192+
193+ if (!ownershipAfterTouchEnd) {
194+ touchRegistry->removeCandidateOwnerForTouch(0, candidateItem);
195+ // TouchGate should now open its flood gates and let testItem get all
196+ // events from touch 0 produced so far
197+ QCOMPARE(testItem->touchEventsReceived.count(), 2);
198+ }
199+
200+ QTest::touchEvent(view, device)
201+ .release(0, QPoint(101,101));
202+
203+ if (ownershipAfterTouchEnd) {
204+ QCOMPARE(testItem->touchEventsReceived.count(), 0);
205+ touchRegistry->removeCandidateOwnerForTouch(0, candidateItem);
206+ // TouchGate should now open its flood gates and let testItem get all
207+ // events from touch 0 produced so far
208+ }
209+
210+ QCOMPARE(testItem->touchEventsReceived.count(), 3);
211+
212+ QVERIFY(touchGate->m_storedEvents.isEmpty());
213+ QVERIFY(touchGate->m_touchInfoMap.isEmpty());
214+}
215+
216+void tst_TouchGate::holdsEventsUntilGainsOwnership_data()
217+{
218+ QTest::addColumn<bool>("ownershipAfterTouchEnd");
219+
220+ QTest::newRow("touch end after touch ownership") << false;
221+ QTest::newRow("touch ownership after touch end") << true;
222+}
223+
224+/*
225+ A touch point of id 0 starts and ends. Then the next touch point is also given
226+ id 0. Check that TouchGate behaves correcly recognizing them as two distinct touch
227+ points.
228+
229+ Note that this won't be a problem with Mir as it assigns unique ids for the touch
230+ points it produces.
231+ */
232+void tst_TouchGate::holdsEventsUntilGainsOwnership_repeatingTouchId()
233+{
234+ TouchGate *touchGate = view->rootObject()->findChild<TouchGate*>("touchGate");
235+ Q_ASSERT(touchGate);
236+
237+ TestItem *testItem = new TestItem;
238+ testItem->setWidth(touchGate->width());
239+ testItem->setHeight(touchGate->height());
240+ testItem->setParentItem(view->rootObject());
241+ testItem->setZ(0.0);
242+
243+ touchGate->setZ(1.0);
244+ touchGate->setTargetItem(testItem);
245+
246+ QTest::touchEvent(view, device)
247+ .press(0, QPoint(100,100));
248+ QTest::touchEvent(view, device)
249+ .move(0, QPoint(101,101));
250+ QTest::touchEvent(view, device)
251+ .release(0, QPoint(101,101));
252+
253+ QCOMPARE(testItem->touchEventsReceived.count(), 3);
254+ testItem->touchEventsReceived.clear();
255+
256+ // Put it in front of touchGate
257+ CandidateItem *candidateItem = new CandidateItem;
258+ candidateItem->setWidth(touchGate->width());
259+ candidateItem->setHeight(touchGate->height());
260+ candidateItem->setParentItem(view->rootObject());
261+ candidateItem->setZ(2.0);
262+
263+ candidateItem->touchEventHandler = [&](QTouchEvent* event) {
264+ touchRegistry->addCandidateOwnerForTouch(0, candidateItem);
265+ event->ignore();
266+ };
267+
268+ // A brand new touch, but with the same id as the previous one
269+ QTest::touchEvent(view, device)
270+ .press(0, QPoint(200,200));
271+ QTest::touchEvent(view, device)
272+ .move(0, QPoint(201,201));
273+ QTest::touchEvent(view, device)
274+ .release(0, QPoint(201,201));
275+
276+ QCOMPARE(testItem->touchEventsReceived.count(), 0);
277+
278+ touchRegistry->requestTouchOwnership(0, candidateItem);
279+
280+ QCOMPARE(testItem->touchEventsReceived.count(), 0);
281+
282+ QVERIFY(touchGate->m_storedEvents.isEmpty());
283+}
284+
285+void tst_TouchGate::dontDispacthToDisabledOrInvisibleTarget()
286+{
287+ TouchGate *touchGate = view->rootObject()->findChild<TouchGate*>("touchGate");
288+ Q_ASSERT(touchGate);
289+
290+ TestItem *testItem = new TestItem;
291+ testItem->setWidth(touchGate->width());
292+ testItem->setHeight(touchGate->height());
293+ testItem->setParentItem(view->rootObject());
294+ testItem->setZ(0.0);
295+
296+ QFETCH(bool, isEnabled);
297+ testItem->setEnabled(isEnabled);
298+
299+ QFETCH(bool, isVisible);
300+ testItem->setVisible(isVisible);
301+
302+ touchGate->setZ(1.0);
303+ touchGate->setTargetItem(testItem);
304+
305+ QTest::touchEvent(view, device)
306+ .press(0, QPoint(100,100));
307+
308+ QCOMPARE(testItem->touchEventsReceived.count(), 0);
309+ QVERIFY(touchGate->m_storedEvents.isEmpty());
310+}
311+
312+void tst_TouchGate::dontDispacthToDisabledOrInvisibleTarget_data()
313+{
314+ QTest::addColumn<bool>("isEnabled");
315+ QTest::addColumn<bool>("isVisible");
316+
317+ QTest::newRow("disabled visible") << false << true;
318+ QTest::newRow("enabled invisible") << true << false;
319+ QTest::newRow("disabled invisible") << false << false;
320+}
321+
322+///////////// TestItem /////////////////////////////////////////////////////////////
323+
324+void TestItem::touchEvent(QTouchEvent *event)
325+{
326+ QTouchEvent *clonedEvent = new QTouchEvent(event->type(),
327+ event->device(),
328+ event->modifiers(),
329+ event->touchPointStates(),
330+ event->touchPoints());
331+
332+ touchEventsReceived.append(QSharedPointer<QTouchEvent>(clonedEvent));
333+}
334+
335+///////////// CandidateItem /////////////////////////////////////////////////////////////
336+
337+void CandidateItem::touchEvent(QTouchEvent *event)
338+{
339+ touchEventHandler(event);
340+}
341+
342+
343+QTEST_MAIN(tst_TouchGate)
344+
345+#include "tst_TouchGate.moc"
tests/qmltests/Components/CMakeLists.txt+2−1
@@ -2,13 +2,14 @@
2
2
3include_directories(
3include_directories(
4 ${CMAKE_SOURCE_DIR}/plugins/Ubuntu/Gestures
4 ${CMAKE_SOURCE_DIR}/plugins/Ubuntu/Gestures
5+ ${CMAKE_SOURCE_DIR}/libs/UbuntuGestures
5 ${CMAKE_CURRENT_BINARY_DIR}
6 ${CMAKE_CURRENT_BINARY_DIR}
6 ${Qt5Gui_PRIVATE_INCLUDE_DIRS}
7 ${Qt5Gui_PRIVATE_INCLUDE_DIRS}
7 )
8 )
8
9
9add_executable(DragHandleTestExec tst_DragHandle.cpp)
10add_executable(DragHandleTestExec tst_DragHandle.cpp)
10qt5_use_modules(DragHandleTestExec Test Core Qml Gui Quick)
11qt5_use_modules(DragHandleTestExec Test Core Qml Gui Quick)
11−target_link_libraries(DragHandleTestExec UbuntuGestureQml)
12+target_link_libraries(DragHandleTestExec UbuntuGesturesQml UbuntuGestures)
12
13
13add_binary_qml_test(DragHandle ${CMAKE_BINARY_DIR}/plugins/Ubuntu/Gestures "")
14add_binary_qml_test(DragHandle ${CMAKE_BINARY_DIR}/plugins/Ubuntu/Gestures "")
14add_manual_qml_test(. DragHandle IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins)
15add_manual_qml_test(. DragHandle IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins)
tests/qmltests/Components/tst_DragHandle.cpp+12−15
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013-2014 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
@@ -22,22 +22,12 @@
22#include <QtQml/QQmlEngine>
22#include <QtQml/QQmlEngine>
23
23
24#include <DirectionalDragArea.h>
24#include <DirectionalDragArea.h>
25+#include <Timer.h>
26+#include <TouchRegistry.h>
25
27
26−class FakeTimer : public UbuntuGestures::AbstractTimer
27−{
28− Q_OBJECT
29−public:
30− FakeTimer(QObject *parent = 0)
31− : UbuntuGestures::AbstractTimer(parent)
28+using namespace UbuntuGestures;
32− {}
33−
34− virtual int interval() const { return m_duration; }
35− virtual void setInterval(int msecs) { m_duration = msecs; }
36−private:
37− int m_duration;
38−};
39
29
40−class FakeTimeSource : public UbuntuGestures::TimeSource
30+class FakeTimeSource : public TimeSource
41{
31{
42public:
32public:
43 FakeTimeSource() { m_msecsSinceReference = 0; }
33 FakeTimeSource() { m_msecsSinceReference = 0; }
@@ -77,6 +67,7 @@ private:
77
67
78 QQuickView *createView();
68 QQuickView *createView();
79 QQuickView *m_view;
69 QQuickView *m_view;
70+ TouchRegistry *m_touchRegistry;
80 QTouchDevice *m_device;
71 QTouchDevice *m_device;
81 FakeTimer *m_fakeTimer;
72 FakeTimer *m_fakeTimer;
82 QSharedPointer<FakeTimeSource> m_fakeTimeSource;
73 QSharedPointer<FakeTimeSource> m_fakeTimeSource;
@@ -100,7 +91,10 @@ void tst_DragHandle::cleanupTestCase()
100
91
101void tst_DragHandle::init()
92void tst_DragHandle::init()
102{
93{
94+ m_touchRegistry = new TouchRegistry;
95+
103 m_view = createView();
96 m_view = createView();
97+ m_view->installEventFilter(m_touchRegistry);
104 m_view->setSource(QUrl::fromLocalFile(TEST_DIR"/tst_DragHandle.qml"));
98 m_view->setSource(QUrl::fromLocalFile(TEST_DIR"/tst_DragHandle.qml"));
105 m_view->show();
99 m_view->show();
106 QVERIFY(QTest::qWaitForWindowExposed(m_view));
100 QVERIFY(QTest::qWaitForWindowExposed(m_view));
@@ -116,6 +110,9 @@ void tst_DragHandle::cleanup()
116 delete m_view;
110 delete m_view;
117 m_view = 0;
111 m_view = 0;
118
112
113+ delete m_touchRegistry;
114+ m_touchRegistry = 0;
115+
119 delete m_fakeTimer;
116 delete m_fakeTimer;
120 m_fakeTimer = 0;
117 m_fakeTimer = 0;
121
118
tests/qmltests/Greeter/tst_SingleGreeter.qml+1−1
@@ -87,7 +87,7 @@ Item {
87
87
88 function test_teasingArea(data) {
88 function test_teasingArea(data) {
89 teaseSpy.clear()
89 teaseSpy.clear()
90− mouseClick(greeter, data.posX, greeter.height - units.gu(1))
90+ tap(greeter, data.posX, greeter.height - units.gu(1))
91 teaseSpy.wait()
91 teaseSpy.wait()
92 tryCompare(teaseSpy, "count", 1)
92 tryCompare(teaseSpy, "count", 1)
93 }
93 }
tests/qmltests/Stages/tst_ApplicationWindow.qml+1−2
@@ -375,8 +375,7 @@ Rectangle {
375
375
376 waitUntilTransitionsEnd();
376 waitUntilTransitionsEnd();
377
377
378− // Because doing stuff in C++ is a PITA we keep the counter in the interal qml impl.
379− var fakeSurface = findChild(fakeSession.surface, "fakeSurfaceQML");
378+ var fakeSurface = fakeSession.surface;
380 fakeSurface.touchPressCount = 0;
379 fakeSurface.touchPressCount = 0;
381 fakeSurface.touchReleaseCount = 0;
380 fakeSurface.touchReleaseCount = 0;
382
381
tests/qmltests/Stages/tst_SurfaceContainer.qml+26−1
@@ -99,6 +99,9 @@ Rectangle {
99 name: "SurfaceContainer"
99 name: "SurfaceContainer"
100 when: windowShown
100 when: windowShown
101
101
102+ property var surfaceContainer:
103+ surfaceContainerLoader.status === Loader.Ready ? surfaceContainerLoader.item : null
104+
102 function cleanup() {
105 function cleanup() {
103 // reload our test subject to get it in a fresh state once again
106 // reload our test subject to get it in a fresh state once again
104 surfaceContainerLoader.active = false;
107 surfaceContainerLoader.active = false;
@@ -126,7 +129,6 @@ Rectangle {
126
129
127 function test_animateRemoval() {
130 function test_animateRemoval() {
128 surfaceCheckbox.checked = true;
131 surfaceCheckbox.checked = true;
129− var surfaceContainer = surfaceContainerLoader.item;
130
132
131 verify(surfaceContainer.surface !== null);
133 verify(surfaceContainer.surface !== null);
132
134
@@ -135,5 +137,28 @@ Rectangle {
135 compare(surfaceContainer.state, "zombie");
137 compare(surfaceContainer.state, "zombie");
136 tryCompare(surfaceContainer, "surface", null);
138 tryCompare(surfaceContainer, "surface", null);
137 }
139 }
140+
141+ function test_surfaceGetsNoTouchesWhenContainerNotInteractive() {
142+ surfaceCheckbox.checked = true;
143+ verify(surfaceContainer.surface !== null);
144+
145+ surfaceContainer.surface.touchPressCount = 0;
146+ surfaceContainer.surface.touchReleaseCount = 0;
147+
148+ surfaceContainer.interactive = true;
149+ tap(surfaceContainer, surfaceContainer.width / 2, surfaceContainer.height / 2);
150+
151+ // surface got touches as the surfaceContainer is interactive
152+ compare(surfaceContainer.surface.touchPressCount, 1)
153+ compare(surfaceContainer.surface.touchReleaseCount, 1);
154+
155+ surfaceContainer.interactive = false;
156+ tap(surfaceContainer, surfaceContainer.width / 2, surfaceContainer.height / 2);
157+
158+ // surface shouldn't get the touches from the second tap as the surfaceContainer
159+ // was *not* interactive when it happened.
160+ compare(surfaceContainer.surface.touchPressCount, 1)
161+ compare(surfaceContainer.surface.touchReleaseCount, 1);
162+ }
138 }
163 }
139}
164}
tests/qmltests/tst_Shell.qml+42−1
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013-2014 Canonical, Ltd.
3 *
3 *
4 * Authors:
4 * Authors:
5 * Daniel d'Andrada <daniel.dandrada@canonical.com>
5 * Daniel d'Andrada <daniel.dandrada@canonical.com>
@@ -567,5 +567,46 @@ Row {
567
567
568 tryCompare(greeter, "showProgress", data.unlocked ? 0 : 1);
568 tryCompare(greeter, "showProgress", data.unlocked ? 0 : 1);
569 }
569 }
570+
571+ function test_tapOnRightEdgeReachesApplicationSurface() {
572+ var topmostSpreadDelegate = findChild(shell, "appDelegate0");
573+ var topmostSurface = findChild(topmostSpreadDelegate, "surfaceContainer").surface;
574+ var rightEdgeDragArea = findChild(shell, "spreadDragArea");
575+
576+ topmostSurface.touchPressCount = 0;
577+ topmostSurface.touchReleaseCount = 0;
578+
579+ var tapPoint = rightEdgeDragArea.mapToItem(shell, rightEdgeDragArea.width / 2,
580+ rightEdgeDragArea.height / 2);
581+
582+ tap(shell, tapPoint.x, tapPoint.y);
583+
584+ tryCompare(topmostSurface, "touchPressCount", 1);
585+ tryCompare(topmostSurface, "touchReleaseCount", 1);
586+ }
587+
588+ /*
589+ Perform a right edge drag over an application surface and check
590+ that no touch event was sent to it (ie, they were all consumed
591+ by the right-edge drag area)
592+ */
593+ function test_rightEdgeDragDoesNotReachApplicationSurface() {
594+ var topmostSpreadDelegate = findChild(shell, "appDelegate0");
595+ var topmostSurface = findChild(topmostSpreadDelegate, "surfaceContainer").surface;
596+ var rightEdgeDragArea = findChild(shell, "spreadDragArea");
597+
598+ topmostSurface.touchPressCount = 0;
599+ topmostSurface.touchReleaseCount = 0;
600+
601+ var gestureStartPoint = rightEdgeDragArea.mapToItem(shell, rightEdgeDragArea.width / 2,
602+ rightEdgeDragArea.height / 2);
603+
604+ touchFlick(shell,
605+ gestureStartPoint.x /* fromX */, gestureStartPoint.y /* fromY */,
606+ units.gu(1) /* toX */, gestureStartPoint.y /* toY */);
607+
608+ tryCompare(topmostSurface, "touchPressCount", 0);
609+ tryCompare(topmostSurface, "touchReleaseCount", 0);
610+ }
570 }
611 }
571}
612}
tests/uqmlscene/CMakeLists.txt+2
@@ -10,6 +10,7 @@ pkg_check_modules(XCB REQUIRED xcb)
10include_directories(
10include_directories(
11 ${CMAKE_SOURCE_DIR}/src
11 ${CMAKE_SOURCE_DIR}/src
12 ${Qt5Gui_PRIVATE_INCLUDE_DIRS}
12 ${Qt5Gui_PRIVATE_INCLUDE_DIRS}
13+ ${CMAKE_SOURCE_DIR}/libs/UbuntuGestures
13)
14)
14
15
15if (NOT "${XCB_INCLUDE_DIRS}" STREQUAL "")
16if (NOT "${XCB_INCLUDE_DIRS}" STREQUAL "")
@@ -20,4 +21,5 @@ target_link_libraries(uqmlscene
20 ${Qt5Qml_LIBRARIES}
21 ${Qt5Qml_LIBRARIES}
21 ${Qt5Quick_LIBRARIES}
22 ${Qt5Quick_LIBRARIES}
22 ${XCB_LDFLAGS}
23 ${XCB_LDFLAGS}
24+ UbuntuGestures
23 )
25 )
tests/uqmlscene/README+3−1
@@ -1 +1,3 @@
1+Qt's qmlscene tool with the following modifications:
1−Qt's qmlscene tool modified to use MouseTouchAdaptor. That way we can interact with touch-only components
2+ - Added MouseTouchAdaptor. That way we can interact with touch-only components
3+ - Added TouchRegistry. So that DirectionalDragArea, TouchGate and others can negotiate touch ownership etc.
tests/uqmlscene/main.cpp+9
@@ -65,6 +65,9 @@
65
65
66#include <MouseTouchAdaptor.h>
66#include <MouseTouchAdaptor.h>
67
67
68+// UbuntuGestures lib
69+#include <TouchRegistry.h>
70+
68#ifdef QML_RUNTIME_TESTING
71#ifdef QML_RUNTIME_TESTING
69class RenderStatistics
72class RenderStatistics
70{
73{
@@ -496,6 +499,12 @@ int main(int argc, char ** argv)
496 QQuickItem *contentItem = qobject_cast<QQuickItem *>(topLevel);
499 QQuickItem *contentItem = qobject_cast<QQuickItem *>(topLevel);
497 if (contentItem) {
500 if (contentItem) {
498 qxView = new QQuickView(&engine, nullptr);
501 qxView = new QQuickView(&engine, nullptr);
502+ if (TouchRegistry::instance() == nullptr) {
503+ new TouchRegistry(qxView);
504+ } else {
505+ TouchRegistry::instance()->setParent(qxView);
506+ }
507+ qxView->installEventFilter(TouchRegistry::instance());
499 window = qxView;
508 window = qxView;
500 // Set window default properties; the qml can still override them
509 // Set window default properties; the qml can still override them
501 QString oname = contentItem->objectName();
510 QString oname = contentItem->objectName();
tests/utils/modules/Unity/Test/CMakeLists.txt+2
@@ -4,6 +4,7 @@ add_definitions(-DQT_NO_KEYWORDS)
4
4
5include_directories(
5include_directories(
6 ${Qt5Gui_PRIVATE_INCLUDE_DIRS}
6 ${Qt5Gui_PRIVATE_INCLUDE_DIRS}
7+ ${CMAKE_SOURCE_DIR}/libs/UbuntuGestures
7)
8)
8
9
9set(UnityTestQML_SOURCES
10set(UnityTestQML_SOURCES
@@ -13,6 +14,7 @@ set(UnityTestQML_SOURCES
13)
14)
14
15
15add_library(UnityTestQml MODULE ${UnityTestQML_SOURCES})
16add_library(UnityTestQml MODULE ${UnityTestQML_SOURCES})
17+target_link_libraries(UnityTestQml UbuntuGestures)
16
18
17qt5_use_modules(UnityTestQml Core Quick Test)
19qt5_use_modules(UnityTestQml Core Quick Test)
18
20
tests/utils/modules/Unity/Test/UnityTestCase.qml+2
@@ -325,6 +325,8 @@ TestCase {
325 }
325 }
326
326
327 Component.onCompleted: {
327 Component.onCompleted: {
328+ UT.Util.ensureTouchRegistryInstalled();
329+
328 var rootItem = parent;
330 var rootItem = parent;
329 while (rootItem.parent != undefined) {
331 while (rootItem.parent != undefined) {
330 rootItem = rootItem.parent;
332 rootItem = rootItem.parent;
tests/utils/modules/Unity/Test/testutil.cpp+37−2
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2012, 2013 Canonical, Ltd.
2+ * Copyright (C) 2012, 2013, 2014 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
@@ -19,11 +19,16 @@
19
19
20#include <qpa/qwindowsysteminterface.h>
20#include <qpa/qwindowsysteminterface.h>
21#include <QtGui/QGuiApplication>
21#include <QtGui/QGuiApplication>
22+#include <QQuickView>
23+
24+// UbuntuGestures lib
25+#include <TouchRegistry.h>
22
26
23TestUtil::TestUtil(QObject *parent)
27TestUtil::TestUtil(QObject *parent)
24 : QObject(parent)
28 : QObject(parent)
25 , m_targetWindow(0)
29 , m_targetWindow(0)
26 , m_touchDevice(0)
30 , m_touchDevice(0)
31+ , m_installedTouchRegistry(false)
27{
32{
28}
33}
29
34
@@ -52,6 +57,7 @@ TouchEventSequenceWrapper *TestUtil::touchEvent()
52{
57{
53 ensureTargetWindow();
58 ensureTargetWindow();
54 ensureTouchDevice();
59 ensureTouchDevice();
60+ ensureTouchRegistryInstalled();
55
61
56 return new TouchEventSequenceWrapper(
62 return new TouchEventSequenceWrapper(
57 QTest::touchEvent(m_targetWindow, m_touchDevice, /* autoCommit */ false));
63 QTest::touchEvent(m_targetWindow, m_touchDevice, /* autoCommit */ false));
@@ -59,7 +65,7 @@ TouchEventSequenceWrapper *TestUtil::touchEvent()
59
65
60void TestUtil::ensureTargetWindow()
66void TestUtil::ensureTargetWindow()
61{
67{
62− if (!m_targetWindow)
68+ if (!m_targetWindow && !QGuiApplication::topLevelWindows().isEmpty())
63 m_targetWindow = QGuiApplication::topLevelWindows()[0];
69 m_targetWindow = QGuiApplication::topLevelWindows()[0];
64}
70}
65
71
@@ -71,3 +77,32 @@ void TestUtil::ensureTouchDevice()
71 QWindowSystemInterface::registerTouchDevice(m_touchDevice);
77 QWindowSystemInterface::registerTouchDevice(m_touchDevice);
72 }
78 }
73}
79}
80+
81+void TestUtil::ensureTouchRegistryInstalled()
82+{
83+ if (m_installedTouchRegistry)
84+ return;
85+
86+ TouchRegistry *touchRegistry;
87+ if (TouchRegistry::instance() == nullptr) {
88+ touchRegistry = new TouchRegistry;
89+ touchRegistry->setParent(this);
90+ } else {
91+ touchRegistry = TouchRegistry::instance();
92+ if (touchRegistry->parent() != this) {
93+ // someone else created it. leave it alone
94+ m_installedTouchRegistry = true;
95+ }
96+ }
97+
98+ ensureTargetWindow();
99+
100+ if (m_targetWindow) {
101+ QQuickView *view = qobject_cast<QQuickView*>(m_targetWindow);
102+ if (view) {
103+ view->installEventFilter(touchRegistry);
104+ touchRegistry->setParent(view);
105+ m_installedTouchRegistry = true;
106+ }
107+ }
108+}
tests/utils/modules/Unity/Test/testutil.h+3−1
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2012, 2013 Canonical, Ltd.
2+ * Copyright (C) 2012, 2013, 2014 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
@@ -34,6 +34,7 @@ public:
34
34
35 Q_INVOKABLE bool isInstanceOf(QObject*, QString);
35 Q_INVOKABLE bool isInstanceOf(QObject*, QString);
36 Q_INVOKABLE TouchEventSequenceWrapper *touchEvent();
36 Q_INVOKABLE TouchEventSequenceWrapper *touchEvent();
37+ Q_INVOKABLE void ensureTouchRegistryInstalled();
37
38
38private:
39private:
39 void ensureTargetWindow();
40 void ensureTargetWindow();
@@ -41,6 +42,7 @@ private:
41
42
42 QWindow *m_targetWindow;
43 QWindow *m_targetWindow;
43 QTouchDevice *m_touchDevice;
44 QTouchDevice *m_touchDevice;
45+ bool m_installedTouchRegistry;
44};
46};
45
47
46QML_DECLARE_TYPE(TestUtil)
48QML_DECLARE_TYPE(TestUtil)