Merge lp:~osomon/webbrowser-app/bottom-edge into lp:webbrowser-app

Proposed by Olivier Tilloy
Status: Merged
Approved by: Olivier Tilloy
Approved revision: 955
Merged at revision: 935
Proposed branch: lp:~osomon/webbrowser-app/bottom-edge
Merge into: lp:webbrowser-app
Diff against target: 6448 lines (+5313/-361)
62 files modified
CMakeLists.txt (+3/-0)
debian/control (+2/-0)
src/app/CMakeLists.txt (+6/-0)
src/app/browserapplication.cpp (+17/-0)
src/app/favicon-fetcher.h (+1/-1)
src/app/unity8/CMakeLists.txt (+4/-0)
src/app/unity8/README (+11/-0)
src/app/unity8/libs/CMakeLists.txt (+1/-0)
src/app/unity8/libs/UbuntuGestures/CMakeLists.txt (+36/-0)
src/app/unity8/libs/UbuntuGestures/CandidateInactivityTimer.cpp (+41/-0)
src/app/unity8/libs/UbuntuGestures/CandidateInactivityTimer.h (+49/-0)
src/app/unity8/libs/UbuntuGestures/DebugHelpers.cpp (+95/-0)
src/app/unity8/libs/UbuntuGestures/DebugHelpers.h (+31/-0)
src/app/unity8/libs/UbuntuGestures/Pool.h (+132/-0)
src/app/unity8/libs/UbuntuGestures/Timer.cpp (+109/-0)
src/app/unity8/libs/UbuntuGestures/Timer.h (+105/-0)
src/app/unity8/libs/UbuntuGestures/TouchOwnershipEvent.cpp (+35/-0)
src/app/unity8/libs/UbuntuGestures/TouchOwnershipEvent.h (+50/-0)
src/app/unity8/libs/UbuntuGestures/TouchRegistry.cpp (+500/-0)
src/app/unity8/libs/UbuntuGestures/TouchRegistry.h (+182/-0)
src/app/unity8/libs/UbuntuGestures/UbuntuGesturesGlobal.h (+23/-0)
src/app/unity8/libs/UbuntuGestures/UnownedTouchEvent.cpp (+39/-0)
src/app/unity8/libs/UbuntuGestures/UnownedTouchEvent.h (+45/-0)
src/app/unity8/plugins/CMakeLists.txt (+1/-0)
src/app/unity8/plugins/Ubuntu/CMakeLists.txt (+1/-0)
src/app/unity8/plugins/Ubuntu/Gestures/AxisVelocityCalculator.cpp (+149/-0)
src/app/unity8/plugins/Ubuntu/Gestures/AxisVelocityCalculator.h (+146/-0)
src/app/unity8/plugins/Ubuntu/Gestures/CMakeLists.txt (+40/-0)
src/app/unity8/plugins/Ubuntu/Gestures/Damper.h (+87/-0)
src/app/unity8/plugins/Ubuntu/Gestures/Direction.cpp (+36/-0)
src/app/unity8/plugins/Ubuntu/Gestures/Direction.h (+45/-0)
src/app/unity8/plugins/Ubuntu/Gestures/DirectionalDragArea.cpp (+886/-0)
src/app/unity8/plugins/Ubuntu/Gestures/DirectionalDragArea.h (+308/-0)
src/app/unity8/plugins/Ubuntu/Gestures/Gestures.qmltypes (+168/-0)
src/app/unity8/plugins/Ubuntu/Gestures/PressedOutsideNotifier.cpp (+119/-0)
src/app/unity8/plugins/Ubuntu/Gestures/PressedOutsideNotifier.h (+62/-0)
src/app/unity8/plugins/Ubuntu/Gestures/TimeSource.cpp (+49/-0)
src/app/unity8/plugins/Ubuntu/Gestures/TimeSource.h (+54/-0)
src/app/unity8/plugins/Ubuntu/Gestures/TouchDispatcher.cpp (+367/-0)
src/app/unity8/plugins/Ubuntu/Gestures/TouchDispatcher.h (+89/-0)
src/app/unity8/plugins/Ubuntu/Gestures/TouchGate.cpp (+253/-0)
src/app/unity8/plugins/Ubuntu/Gestures/TouchGate.h (+112/-0)
src/app/unity8/plugins/Ubuntu/Gestures/UbuntuGesturesQmlGlobal.h (+23/-0)
src/app/unity8/plugins/Ubuntu/Gestures/plugin.cpp (+39/-0)
src/app/unity8/plugins/Ubuntu/Gestures/plugin.h (+31/-0)
src/app/unity8/plugins/Ubuntu/Gestures/qmldir (+3/-0)
src/app/webbrowser/BottomEdgeHandle.qml (+36/-0)
src/app/webbrowser/Browser.qml (+190/-26)
src/app/webbrowser/BrowserTab.qml (+13/-4)
src/app/webbrowser/ExpandedHistoryView.qml (+2/-2)
src/app/webbrowser/HistorySectionDelegate.qml (+2/-2)
src/app/webbrowser/HistoryView.qml (+2/-2)
src/app/webbrowser/TabChrome.qml (+144/-0)
src/app/webbrowser/TabPreview.qml (+67/-121)
src/app/webbrowser/TabsList.qml (+130/-0)
src/app/webbrowser/TabsView.qml (+0/-123)
src/app/webbrowser/Toolbar.qml (+36/-1)
src/app/webbrowser/UrlDelegate.qml (+3/-3)
tests/autopilot/webbrowser_app/emulators/browser.py (+44/-22)
tests/autopilot/webbrowser_app/tests/__init__.py (+20/-10)
tests/autopilot/webbrowser_app/tests/test_addressbar_bookmark.py (+9/-13)
tests/autopilot/webbrowser_app/tests/test_tabs.py (+30/-31)
To merge this branch: bzr merge lp:~osomon/webbrowser-app/bottom-edge
Reviewer Review Type Date Requested Status
Florian Boucault (community) Approve
PS Jenkins bot continuous-integration Needs Fixing
Ken VanDine Approve
Bartosz Kosiorek (community) Needs Information
Review via email: mp+248019@code.launchpad.net

Commit message

On mobile, reveal the tabs view with a swipe gesture from the bottom edge.

Note: this re-adds qtbase5-private-dev and qtdeclarative5-private-dev as build dependencies, required to build the code imported from unity8. This code will eventually go away, and the dependencies on private headers with it.

Description of the change

On mobile, reveal the tabs view with a swipe gesture from the bottom edge.

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

FAILED: Continuous integration, rev:911
http://jenkins.qa.ubuntu.com/job/webbrowser-app-ci/1453/
Executed test runs:
    UNSTABLE: http://jenkins.qa.ubuntu.com/job/generic-deb-autopilot-vivid-touch/1218
    FAILURE: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-vivid/566/console
    SUCCESS: http://jenkins.qa.ubuntu.com/job/webbrowser-app-vivid-amd64-ci/211
    SUCCESS: http://jenkins.qa.ubuntu.com/job/webbrowser-app-vivid-armhf-ci/211
        deb: http://jenkins.qa.ubuntu.com/job/webbrowser-app-vivid-armhf-ci/211/artifact/work/output/*zip*/output.zip
    SUCCESS: http://jenkins.qa.ubuntu.com/job/webbrowser-app-vivid-i386-ci/211
    UNSTABLE: http://jenkins.qa.ubuntu.com/job/generic-deb-autopilot-runner-vivid-mako/1075
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-vivid-armhf/1216
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-vivid-armhf/1216/artifact/work/output/*zip*/output.zip
    SUCCESS: http://s-jenkins.ubuntu-ci:8080/job/touch-flash-device/17849
    FAILURE: http://jenkins.qa.ubuntu.com/job/autopilot-testrunner-otto-vivid/462/console
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-vivid-amd64/676
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-vivid-amd64/676/artifact/work/output/*zip*/output.zip

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/webbrowser-app-ci/1453/rebuild

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

Hello Olivier.
Thanks for this MR. It is most wanted feature in WebBrowser.

One small remark:
Could you please update README file, because executable location was changed.
To run you should type:

./src/app/webbrowser/webbrowser-app

Do you think it will be possible to use gestures on Desktop?
I'm unable to show up tabs from bottom edge.

review: Needs Information
Revision history for this message
Olivier Tilloy (osomon) wrote :

> Hello Olivier.
> Thanks for this MR. It is most wanted feature in WebBrowser.
>
> One small remark:
> Could you please update README file, because executable location was changed.
> To run you should type:
>
> ./src/app/webbrowser/webbrowser-app

The README file is already up-to-date in this regard.

> Do you think it will be possible to use gestures on Desktop?
> I'm unable to show up tabs from bottom edge.

No, by design this is meant to work on mobile only. In the future the desktop UX will gain visible tabs, much like other desktop browsers do.

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

Packaging changes look fine, ACK from me.

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

* tests/autopilot/webbrowser_app/tests/test_tabs.py
- Factor duplicated code to swipe up from the bottom in a common function

* src/app/webbrowser/Browser.qml
- #5d5d5d is UbuntuColors.darkGrey (lots of that value in the codebase)

* src/app/webbrowser/TabChrome.qml
- generally better for components to not define their own width/height but instead implicitWidth/implicitHeight
- "AbstractButton { id: closeButton" should use anchors instead of "height: parent.height"; same for unnamed Item below
- closeButton has a Rectangle without color
- closeButton has no visual feedback when pressing
- Images "tabBackgroundLeft", "tabBackgroundCenter" and "tabBackgroundRight", sources are PNGs without @GU suffix
- "MouseArea.width: parent.width / 2" should probably have a comment

review: Needs Fixing
Revision history for this message
Florian Boucault (fboucault) wrote :

Thanks!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'CMakeLists.txt'
2--- CMakeLists.txt 2014-11-12 17:00:12 +0000
3+++ CMakeLists.txt 2015-03-20 16:26:56 +0000
4@@ -32,6 +32,9 @@
5 ENABLE_COVERAGE_REPORT(EXCLUDES tests/*|.*moc_.*.cpp FILTER tests/* moc_*.cpp)
6 endif()
7
8+# for unity8 components
9+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
10+
11 # enable QML debugging
12 if(CMAKE_BUILD_TYPE MATCHES DEBUG OR CMAKE_BUILD_TYPE MATCHES "Debug")
13 add_definitions(-DQT_QML_DEBUG)
14
15=== modified file 'debian/control'
16--- debian/control 2015-02-26 17:54:47 +0000
17+++ debian/control 2015-03-20 16:26:56 +0000
18@@ -17,7 +17,9 @@
19 qt5-qmake,
20 qtbase5-dev (>= 5.4),
21 qtbase5-dev-tools,
22+ qtbase5-private-dev,
23 qtdeclarative5-dev,
24+ qtdeclarative5-private-dev,
25 qtdeclarative5-ubuntu-ui-toolkit-plugin,
26 xvfb,
27 Standards-Version: 3.9.5
28
29=== modified file 'src/app/CMakeLists.txt'
30--- src/app/CMakeLists.txt 2015-02-27 09:28:41 +0000
31+++ src/app/CMakeLists.txt 2015-03-20 16:26:56 +0000
32@@ -1,5 +1,7 @@
33 project(webbrowser-common)
34
35+add_subdirectory(unity8)
36+
37 configure_file(
38 config.h.in
39 ${CMAKE_CURRENT_BINARY_DIR}/config.h
40@@ -18,6 +20,10 @@
41
42 qt5_use_modules(${COMMONLIB} Core Gui Network Qml Quick Widgets)
43
44+include_directories(${unity8_SOURCE_DIR}/libs/UbuntuGestures
45+ ${unity8_SOURCE_DIR}/plugins)
46+target_link_libraries(${COMMONLIB} UbuntuGesturesQml)
47+
48 file(GLOB QML_FILES *.qml)
49 install(FILES ${QML_FILES} DESTINATION ${CMAKE_INSTALL_DATADIR}/webbrowser-app)
50 file(GLOB JS_FILES *.js)
51
52=== modified file 'src/app/browserapplication.cpp'
53--- src/app/browserapplication.cpp 2015-02-27 09:28:41 +0000
54+++ src/app/browserapplication.cpp 2015-03-20 16:26:56 +0000
55@@ -34,6 +34,10 @@
56 #include "session-storage.h"
57 #include "webbrowser-window.h"
58
59+#include "TouchRegistry.h"
60+#include "Ubuntu/Gestures/Direction.h"
61+#include "Ubuntu/Gestures/DirectionalDragArea.h"
62+
63 BrowserApplication::BrowserApplication(int& argc, char** argv)
64 : QApplication(argc, argv)
65 , m_engine(0)
66@@ -95,6 +99,13 @@
67 return QString();
68 }
69
70+static QObject* Direction_singleton_factory(QQmlEngine* engine, QJSEngine* scriptEngine)
71+{
72+ Q_UNUSED(engine);
73+ Q_UNUSED(scriptEngine);
74+ return new Direction();
75+}
76+
77 bool BrowserApplication::initialize(const QString& qmlFileSubPath)
78 {
79 Q_ASSERT(m_window == 0);
80@@ -133,6 +144,10 @@
81 qmlRegisterType<FaviconFetcher>(uri, 0, 1, "FaviconFetcher");
82 qmlRegisterType<SessionStorage>(uri, 0, 1, "SessionStorage");
83
84+ const char* gesturesUri = "Ubuntu.Gestures";
85+ qmlRegisterSingletonType<Direction>(gesturesUri, 0, 1, "Direction", Direction_singleton_factory);
86+ qmlRegisterType<DirectionalDragArea>(gesturesUri, 0, 1, "DirectionalDragArea");
87+
88 m_engine = new QQmlEngine;
89 connect(m_engine, SIGNAL(quit()), SLOT(quit()));
90 if (!isRunningInstalled()) {
91@@ -156,6 +171,8 @@
92 m_window = qobject_cast<QQuickWindow*>(browser);
93 m_webbrowserWindowProxy->setWindow(m_window);
94
95+ m_window->installEventFilter(new TouchRegistry(this));
96+
97 browser->setProperty("developerExtrasEnabled", inspectorEnabled);
98 browser->setProperty("forceFullscreen", m_arguments.contains("--fullscreen"));
99
100
101=== modified file 'src/app/favicon-fetcher.h'
102--- src/app/favicon-fetcher.h 2014-11-26 16:24:53 +0000
103+++ src/app/favicon-fetcher.h 2015-03-20 16:26:56 +0000
104@@ -29,7 +29,7 @@
105 class QNetworkAccessManager;
106 class QNetworkReply;
107
108-class FaviconFetcher Q_DECL_FINAL : public QObject
109+class FaviconFetcher : public QObject
110 {
111 Q_OBJECT
112
113
114=== added directory 'src/app/unity8'
115=== added file 'src/app/unity8/CMakeLists.txt'
116--- src/app/unity8/CMakeLists.txt 1970-01-01 00:00:00 +0000
117+++ src/app/unity8/CMakeLists.txt 2015-03-20 16:26:56 +0000
118@@ -0,0 +1,4 @@
119+project(unity8)
120+
121+add_subdirectory(libs)
122+add_subdirectory(plugins)
123
124=== added file 'src/app/unity8/README'
125--- src/app/unity8/README 1970-01-01 00:00:00 +0000
126+++ src/app/unity8/README 2015-03-20 16:26:56 +0000
127@@ -0,0 +1,11 @@
128+Code in this directory was copied over from unity8.
129+ - first import: 2015-02-04, at revision 1583 of lp:unity8
130+ - last sync: 2015-03-17, at revision 1663 of lp:unity8
131+
132+The structure of the directories has been kept identical, to ease syncing the
133+code in the future. Minor changes were made to the build system to integrate it
134+with the existing webbrowser-app code base.
135+
136+The goal is to eventually have those components moved over to the UITK, thus
137+removing the need for a copy here. See
138+https://docs.google.com/a/canonical.com/document/d/1_ljZunMXWqJfrBGEkCp0peVD8gq6-E4ZMFpuH0PVKRc.
139
140=== added directory 'src/app/unity8/libs'
141=== added file 'src/app/unity8/libs/CMakeLists.txt'
142--- src/app/unity8/libs/CMakeLists.txt 1970-01-01 00:00:00 +0000
143+++ src/app/unity8/libs/CMakeLists.txt 2015-03-20 16:26:56 +0000
144@@ -0,0 +1,1 @@
145+add_subdirectory(UbuntuGestures)
146
147=== added directory 'src/app/unity8/libs/UbuntuGestures'
148=== added file 'src/app/unity8/libs/UbuntuGestures/CMakeLists.txt'
149--- src/app/unity8/libs/UbuntuGestures/CMakeLists.txt 1970-01-01 00:00:00 +0000
150+++ src/app/unity8/libs/UbuntuGestures/CMakeLists.txt 2015-03-20 16:26:56 +0000
151@@ -0,0 +1,36 @@
152+# in order to include Qt's private headers
153+remove_definitions(-DQT_NO_KEYWORDS)
154+
155+set(UbuntuGestures_SOURCES
156+ CandidateInactivityTimer.cpp
157+ DebugHelpers.cpp
158+ Timer.cpp
159+ TouchOwnershipEvent.cpp
160+ TouchRegistry.cpp
161+ UnownedTouchEvent.cpp
162+)
163+
164+add_definitions(-DUBUNTUGESTURES_LIBRARY)
165+
166+add_library(UbuntuGestures STATIC ${UbuntuGestures_SOURCES})
167+
168+qt5_use_modules(UbuntuGestures Core Quick)
169+
170+# So that Foo.cpp can #include "Foo.moc"
171+include_directories(${CMAKE_CURRENT_BINARY_DIR})
172+
173+# There's no cmake var for v8 include path :-/ so create one
174+LIST(GET Qt5Core_INCLUDE_DIRS 0 QtCoreDir0)
175+if(${Qt5Core_VERSION_STRING} VERSION_LESS "5.1.0")
176+ SET(Qt5V8_PRIVATE_INCLUDE_DIR ${QtCoreDir0}/../QtV8/${Qt5Core_VERSION_STRING}/QtV8)
177+else()
178+ SET(Qt5V8_PRIVATE_INCLUDE_DIR ${QtCoreDir0}/QtV8/${Qt5Core_VERSION_STRING}/QtV8)
179+endif()
180+
181+# DANGER! DANGER! Using Qt's private API!
182+include_directories(
183+ ${Qt5Qml_PRIVATE_INCLUDE_DIRS}
184+ ${Qt5Quick_INCLUDE_DIRS}
185+ ${Qt5Quick_PRIVATE_INCLUDE_DIRS}
186+ ${Qt5V8_PRIVATE_INCLUDE_DIR}
187+)
188
189=== added file 'src/app/unity8/libs/UbuntuGestures/CandidateInactivityTimer.cpp'
190--- src/app/unity8/libs/UbuntuGestures/CandidateInactivityTimer.cpp 1970-01-01 00:00:00 +0000
191+++ src/app/unity8/libs/UbuntuGestures/CandidateInactivityTimer.cpp 2015-03-20 16:26:56 +0000
192@@ -0,0 +1,41 @@
193+/*
194+ * Copyright (C) 2014 Canonical, Ltd.
195+ *
196+ * This program is free software; you can redistribute it and/or modify
197+ * it under the terms of the GNU General Public License as published by
198+ * the Free Software Foundation; version 3.
199+ *
200+ * This program is distributed in the hope that it will be useful,
201+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
202+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
203+ * GNU General Public License for more details.
204+ *
205+ * You should have received a copy of the GNU General Public License
206+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
207+ */
208+
209+#include "CandidateInactivityTimer.h"
210+
211+namespace UbuntuGestures {
212+
213+CandidateInactivityTimer::CandidateInactivityTimer(int touchId, QQuickItem *candidate,
214+ AbstractTimerFactory &timerFactory, QObject *parent)
215+ : QObject(parent)
216+ , m_touchId(touchId)
217+ , m_candidate(candidate)
218+{
219+ m_timer = timerFactory.createTimer(this);
220+ connect(m_timer, &AbstractTimer::timeout,
221+ this, &CandidateInactivityTimer::onTimeout);
222+ m_timer->setInterval(durationMs);
223+ m_timer->setSingleShot(true);
224+ m_timer->start();
225+}
226+
227+void CandidateInactivityTimer::onTimeout()
228+{
229+ qWarning("[TouchRegistry] Candidate for touch %d defaulted!", m_touchId);
230+ Q_EMIT candidateDefaulted(m_touchId, m_candidate);
231+}
232+
233+} // namespace UbuntuGestures
234
235=== added file 'src/app/unity8/libs/UbuntuGestures/CandidateInactivityTimer.h'
236--- src/app/unity8/libs/UbuntuGestures/CandidateInactivityTimer.h 1970-01-01 00:00:00 +0000
237+++ src/app/unity8/libs/UbuntuGestures/CandidateInactivityTimer.h 2015-03-20 16:26:56 +0000
238@@ -0,0 +1,49 @@
239+/*
240+ * Copyright (C) 2014 Canonical, Ltd.
241+ *
242+ * This program is free software; you can redistribute it and/or modify
243+ * it under the terms of the GNU General Public License as published by
244+ * the Free Software Foundation; version 3.
245+ *
246+ * This program is distributed in the hope that it will be useful,
247+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
248+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
249+ * GNU General Public License for more details.
250+ *
251+ * You should have received a copy of the GNU General Public License
252+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
253+ */
254+
255+#ifndef UBUNTUGESTURES_CANDIDATE_INACTIVITY_TIMER_H
256+#define UBUNTUGESTURES_CANDIDATE_INACTIVITY_TIMER_H
257+
258+#include <QObject>
259+
260+class QQuickItem;
261+
262+#include "Timer.h"
263+
264+namespace UbuntuGestures {
265+
266+class UBUNTUGESTURES_EXPORT CandidateInactivityTimer : public QObject {
267+ Q_OBJECT
268+public:
269+ CandidateInactivityTimer(int touchId, QQuickItem *candidate,
270+ AbstractTimerFactory &timerFactory,
271+ QObject *parent = nullptr);
272+
273+ const int durationMs = 350;
274+
275+Q_SIGNALS:
276+ void candidateDefaulted(int touchId, QQuickItem *candidate);
277+private Q_SLOTS:
278+ void onTimeout();
279+private:
280+ AbstractTimer *m_timer;
281+ int m_touchId;
282+ QQuickItem *m_candidate;
283+};
284+
285+} // namespace UbuntuGestures
286+
287+#endif // UBUNTUGESTURES_CANDIDATE_INACTIVITY_TIMER_H
288
289=== added file 'src/app/unity8/libs/UbuntuGestures/DebugHelpers.cpp'
290--- src/app/unity8/libs/UbuntuGestures/DebugHelpers.cpp 1970-01-01 00:00:00 +0000
291+++ src/app/unity8/libs/UbuntuGestures/DebugHelpers.cpp 2015-03-20 16:26:56 +0000
292@@ -0,0 +1,95 @@
293+/*
294+ * Copyright (C) 2014 Canonical, Ltd.
295+ *
296+ * This program is free software; you can redistribute it and/or modify
297+ * it under the terms of the GNU General Public License as published by
298+ * the Free Software Foundation; version 3.
299+ *
300+ * This program is distributed in the hope that it will be useful,
301+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
302+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
303+ * GNU General Public License for more details.
304+ *
305+ * You should have received a copy of the GNU General Public License
306+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
307+ */
308+
309+#include "DebugHelpers.h"
310+#include <QTouchEvent>
311+#include <QMouseEvent>
312+
313+QString touchPointStateToString(Qt::TouchPointState state)
314+{
315+ switch (state) {
316+ case Qt::TouchPointPressed:
317+ return QString("pressed");
318+ case Qt::TouchPointMoved:
319+ return QString("moved");
320+ case Qt::TouchPointStationary:
321+ return QString("stationary");
322+ case Qt::TouchPointReleased:
323+ return QString("released");
324+ default:
325+ return QString("INVALID_STATE");
326+ }
327+}
328+
329+QString touchEventToString(const QTouchEvent *ev)
330+{
331+ QString message;
332+
333+ switch (ev->type()) {
334+ case QEvent::TouchBegin:
335+ message.append("TouchBegin ");
336+ break;
337+ case QEvent::TouchUpdate:
338+ message.append("TouchUpdate ");
339+ break;
340+ case QEvent::TouchEnd:
341+ message.append("TouchEnd ");
342+ break;
343+ case QEvent::TouchCancel:
344+ message.append("TouchCancel ");
345+ break;
346+ default:
347+ message.append("INVALID_TOUCH_EVENT_TYPE ");
348+ }
349+
350+ foreach(const QTouchEvent::TouchPoint& touchPoint, ev->touchPoints()) {
351+ message.append(
352+ QString("(id:%1, state:%2, scenePos:(%3,%4)) ")
353+ .arg(touchPoint.id())
354+ .arg(touchPointStateToString(touchPoint.state()))
355+ .arg(touchPoint.scenePos().x())
356+ .arg(touchPoint.scenePos().y())
357+ );
358+ }
359+
360+ return message;
361+}
362+
363+QString mouseEventToString(const QMouseEvent *ev)
364+{
365+ QString message;
366+
367+ switch (ev->type()) {
368+ case QEvent::MouseButtonPress:
369+ message.append("MouseButtonPress ");
370+ break;
371+ case QEvent::MouseButtonRelease:
372+ message.append("MouseButtonRelease ");
373+ break;
374+ case QEvent::MouseButtonDblClick:
375+ message.append("MouseButtonDblClick ");
376+ break;
377+ case QEvent::MouseMove:
378+ message.append("MouseMove ");
379+ break;
380+ default:
381+ message.append("INVALID_MOUSE_EVENT_TYPE ");
382+ }
383+
384+ message.append(QString("pos(%1, %2)").arg(ev->x()).arg(ev->y()));
385+
386+ return message;
387+}
388
389=== added file 'src/app/unity8/libs/UbuntuGestures/DebugHelpers.h'
390--- src/app/unity8/libs/UbuntuGestures/DebugHelpers.h 1970-01-01 00:00:00 +0000
391+++ src/app/unity8/libs/UbuntuGestures/DebugHelpers.h 2015-03-20 16:26:56 +0000
392@@ -0,0 +1,31 @@
393+/*
394+ * Copyright (C) 2014 Canonical, Ltd.
395+ *
396+ * This program is free software; you can redistribute it and/or modify
397+ * it under the terms of the GNU General Public License as published by
398+ * the Free Software Foundation; version 3.
399+ *
400+ * This program is distributed in the hope that it will be useful,
401+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
402+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
403+ * GNU General Public License for more details.
404+ *
405+ * You should have received a copy of the GNU General Public License
406+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
407+ */
408+
409+#ifndef UBUNTUGESTURES_DEBUG_HELPER_H
410+#define UBUNTUGESTURES_DEBUG_HELPER_H
411+
412+#include <QString>
413+
414+#include "UbuntuGesturesGlobal.h"
415+
416+class QMouseEvent;
417+class QTouchEvent;
418+
419+UBUNTUGESTURES_EXPORT QString touchPointStateToString(Qt::TouchPointState state);
420+UBUNTUGESTURES_EXPORT QString touchEventToString(const QTouchEvent *ev);
421+UBUNTUGESTURES_EXPORT QString mouseEventToString(const QMouseEvent *ev);
422+
423+#endif // UBUNTUGESTURES_DEBUG_HELPER_H
424
425=== added file 'src/app/unity8/libs/UbuntuGestures/Pool.h'
426--- src/app/unity8/libs/UbuntuGestures/Pool.h 1970-01-01 00:00:00 +0000
427+++ src/app/unity8/libs/UbuntuGestures/Pool.h 2015-03-20 16:26:56 +0000
428@@ -0,0 +1,132 @@
429+/*
430+ * Copyright (C) 2014 Canonical, Ltd.
431+ *
432+ * This program is free software; you can redistribute it and/or modify
433+ * it under the terms of the GNU General Public License as published by
434+ * the Free Software Foundation; version 3.
435+ *
436+ * This program is distributed in the hope that it will be useful,
437+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
438+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
439+ * GNU General Public License for more details.
440+ *
441+ * You should have received a copy of the GNU General Public License
442+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
443+ */
444+
445+#ifndef UBUNTUGESTURES_POOL_H
446+#define UBUNTUGESTURES_POOL_H
447+
448+#include <QVector>
449+
450+#include "UbuntuGesturesGlobal.h"
451+
452+/*
453+ An object pool.
454+ Avoids unnecessary creations/initializations and deletions/destructions of items. Useful
455+ in a scenario where items are created and destroyed very frequently but the total number
456+ of items at any given time remains small. They're stored in a unordered fashion.
457+
458+ To be used in Pool, ItemType needs to have the following methods:
459+
460+ - ItemType();
461+
462+ A constructor that takes no parameters. An object contructed with it must return false if
463+ isValid() is called.
464+
465+ - bool isValid() const;
466+
467+ Returns wheter the object holds a valid , "filled" state or is empty.
468+ Used by Pool to check if the slot occupied by this object is actually available.
469+
470+ - void reset();
471+
472+ Resets the object to its initial, empty, state. After calling this method, isValid() must
473+ return false.
474+ */
475+template <class ItemType> class Pool
476+{
477+public:
478+ Pool() : m_lastUsedIndex(-1) {
479+ }
480+
481+ class Iterator {
482+ public:
483+ Iterator() : index(-1), item(nullptr) {}
484+ Iterator(int index, ItemType *item)
485+ : index(index), item(item) {}
486+
487+ ItemType *operator->() const { return item; }
488+ ItemType &operator*() const { return *item; }
489+ ItemType &value() const { return *item; }
490+
491+ Iterator &operator= (const Iterator& other) {
492+ index = other.index;
493+ item = other.item;
494+
495+ // by convention, always return *this
496+ return *this;
497+ }
498+
499+ operator bool() const { return item != nullptr; }
500+
501+ int index;
502+ ItemType *item;
503+ };
504+
505+ ItemType &getEmptySlot() {
506+ Q_ASSERT(m_lastUsedIndex < m_slots.size());
507+
508+ // Look for an in-between vacancy first
509+ for (int i = 0; i < m_lastUsedIndex; ++i) {
510+ ItemType &item = m_slots[i];
511+ if (!item.isValid()) {
512+ return item;
513+ }
514+ }
515+
516+ ++m_lastUsedIndex;
517+ if (m_lastUsedIndex >= m_slots.size()) {
518+ m_slots.resize(m_lastUsedIndex + 1);
519+ }
520+
521+ return m_slots[m_lastUsedIndex];
522+ }
523+
524+ void freeSlot(Iterator &iterator) {
525+ m_slots[iterator.index].reset();
526+ if (iterator.index == m_lastUsedIndex) {
527+ do {
528+ --m_lastUsedIndex;
529+ } while (m_lastUsedIndex >= 0 && !m_slots.at(m_lastUsedIndex).isValid());
530+ }
531+ }
532+
533+ // Iterates through all valid items (i.e. the occupied slots)
534+ // calling the given function, with the option of ending the loop early.
535+ //
536+ // bool Func(Iterator& item)
537+ //
538+ // Returning true means it wants to continue the "for" loop, false
539+ // terminates the loop.
540+ template<typename Func> void forEach(Func func) {
541+ Iterator it;
542+ for (it.index = 0; it.index <= m_lastUsedIndex; ++it.index) {
543+ it.item = &m_slots[it.index];
544+ if (!it.item->isValid())
545+ continue;
546+
547+ if (!func(it))
548+ break;
549+ }
550+ }
551+
552+ bool isEmpty() const { return m_lastUsedIndex == -1; }
553+
554+
555+private:
556+ QVector<ItemType> m_slots;
557+ int m_lastUsedIndex;
558+};
559+
560+#endif // UBUNTUGESTURES_POOL_H
561
562=== added file 'src/app/unity8/libs/UbuntuGestures/Timer.cpp'
563--- src/app/unity8/libs/UbuntuGestures/Timer.cpp 1970-01-01 00:00:00 +0000
564+++ src/app/unity8/libs/UbuntuGestures/Timer.cpp 2015-03-20 16:26:56 +0000
565@@ -0,0 +1,109 @@
566+/*
567+ * Copyright (C) 2014 Canonical, Ltd.
568+ *
569+ * This program is free software; you can redistribute it and/or modify
570+ * it under the terms of the GNU General Public License as published by
571+ * the Free Software Foundation; version 3.
572+ *
573+ * This program is distributed in the hope that it will be useful,
574+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
575+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
576+ * GNU General Public License for more details.
577+ *
578+ * You should have received a copy of the GNU General Public License
579+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
580+ */
581+
582+#include "Timer.h"
583+
584+namespace UbuntuGestures {
585+
586+Timer::Timer(QObject *parent) : AbstractTimer(parent)
587+{
588+ m_timer.setSingleShot(false);
589+ connect(&m_timer, &QTimer::timeout, this, &AbstractTimer::timeout);
590+}
591+
592+int Timer::interval() const
593+{
594+ return m_timer.interval();
595+}
596+
597+void Timer::setInterval(int msecs)
598+{
599+ m_timer.setInterval(msecs);
600+}
601+
602+void Timer::start()
603+{
604+ m_timer.start();
605+ AbstractTimer::start();
606+}
607+
608+void Timer::stop()
609+{
610+ m_timer.stop();
611+ AbstractTimer::stop();
612+}
613+
614+bool Timer::isSingleShot() const
615+{
616+ return m_timer.isSingleShot();
617+}
618+
619+void Timer::setSingleShot(bool value)
620+{
621+ m_timer.setSingleShot(value);
622+}
623+
624+/////////////////////////////////// FakeTimer //////////////////////////////////
625+
626+FakeTimer::FakeTimer(QObject *parent)
627+ : UbuntuGestures::AbstractTimer(parent)
628+ , m_interval(0)
629+ , m_singleShot(false)
630+{
631+}
632+
633+int FakeTimer::interval() const
634+{
635+ return m_interval;
636+}
637+
638+void FakeTimer::setInterval(int msecs)
639+{
640+ m_interval = msecs;
641+}
642+
643+bool FakeTimer::isSingleShot() const
644+{
645+ return m_singleShot;
646+}
647+
648+void FakeTimer::setSingleShot(bool value)
649+{
650+ m_singleShot = value;
651+}
652+
653+/////////////////////////////////// FakeTimerFactory //////////////////////////////////
654+
655+AbstractTimer *FakeTimerFactory::createTimer(QObject *parent)
656+{
657+ FakeTimer *fakeTimer = new FakeTimer(parent);
658+
659+ timers.append(fakeTimer);
660+
661+ return fakeTimer;
662+}
663+
664+void FakeTimerFactory::makeRunningTimersTimeout()
665+{
666+ for (int i = 0; i < timers.count(); ++i) {
667+ FakeTimer *timer = timers[i].data();
668+ if (timer && timer->isRunning()) {
669+ timer->emitTimeout();
670+ }
671+ }
672+}
673+
674+} // namespace UbuntuGestures
675
676=== added file 'src/app/unity8/libs/UbuntuGestures/Timer.h'
677--- src/app/unity8/libs/UbuntuGestures/Timer.h 1970-01-01 00:00:00 +0000
678+++ src/app/unity8/libs/UbuntuGestures/Timer.h 2015-03-20 16:26:56 +0000
679@@ -0,0 +1,105 @@
680+/*
681+ * Copyright (C) 2014 Canonical, Ltd.
682+ *
683+ * This program is free software; you can redistribute it and/or modify
684+ * it under the terms of the GNU General Public License as published by
685+ * the Free Software Foundation; version 3.
686+ *
687+ * This program is distributed in the hope that it will be useful,
688+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
689+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
690+ * GNU General Public License for more details.
691+ *
692+ * You should have received a copy of the GNU General Public License
693+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
694+ */
695+
696+#ifndef UBUNTUGESTURES_TIMER_H
697+#define UBUNTUGESTURES_TIMER_H
698+
699+#include "UbuntuGesturesGlobal.h"
700+
701+#include <QObject>
702+#include <QPointer>
703+#include <QTimer>
704+
705+namespace UbuntuGestures {
706+
707+/* Defines an interface for a Timer. Useful for tests. */
708+class UBUNTUGESTURES_EXPORT AbstractTimer : public QObject
709+{
710+ Q_OBJECT
711+public:
712+ AbstractTimer(QObject *parent) : QObject(parent), m_isRunning(false) {}
713+ virtual int interval() const = 0;
714+ virtual void setInterval(int msecs) = 0;
715+ virtual void start() { m_isRunning = true; }
716+ virtual void stop() { m_isRunning = false; }
717+ bool isRunning() const { return m_isRunning; }
718+ virtual bool isSingleShot() const = 0;
719+ virtual void setSingleShot(bool value) = 0;
720+Q_SIGNALS:
721+ void timeout();
722+private:
723+ bool m_isRunning;
724+};
725+
726+/* Essentially a QTimer wrapper */
727+class UBUNTUGESTURES_EXPORT Timer : public AbstractTimer
728+{
729+ Q_OBJECT
730+public:
731+ Timer(QObject *parent = nullptr);
732+
733+ int interval() const override;
734+ void setInterval(int msecs) override;
735+ void start() override;
736+ void stop() override;
737+ bool isSingleShot() const override;
738+ void setSingleShot(bool value) override;
739+private:
740+ QTimer m_timer;
741+};
742+
743+/* For tests */
744+class UBUNTUGESTURES_EXPORT FakeTimer : public AbstractTimer
745+{
746+ Q_OBJECT
747+public:
748+ FakeTimer(QObject *parent = nullptr);
749+
750+ virtual void emitTimeout() { Q_EMIT timeout(); }
751+
752+ int interval() const override;
753+ void setInterval(int msecs) override;
754+ bool isSingleShot() const override;
755+ void setSingleShot(bool value) override;
756+private:
757+ int m_interval;
758+ bool m_singleShot;
759+};
760+
761+class UBUNTUGESTURES_EXPORT AbstractTimerFactory
762+{
763+public:
764+ virtual ~AbstractTimerFactory() {}
765+ virtual AbstractTimer *createTimer(QObject *parent = nullptr) = 0;
766+};
767+
768+class UBUNTUGESTURES_EXPORT TimerFactory : public AbstractTimerFactory
769+{
770+public:
771+ AbstractTimer *createTimer(QObject *parent = nullptr) override { return new Timer(parent); }
772+};
773+
774+class UBUNTUGESTURES_EXPORT FakeTimerFactory : public AbstractTimerFactory
775+{
776+public:
777+ AbstractTimer *createTimer(QObject *parent = nullptr) override;
778+ void makeRunningTimersTimeout();
779+ QList<QPointer<FakeTimer>> timers;
780+};
781+
782+} // namespace UbuntuGestures
783+
784+#endif // UBUNTUGESTURES_TIMER_H
785
786=== added file 'src/app/unity8/libs/UbuntuGestures/TouchOwnershipEvent.cpp'
787--- src/app/unity8/libs/UbuntuGestures/TouchOwnershipEvent.cpp 1970-01-01 00:00:00 +0000
788+++ src/app/unity8/libs/UbuntuGestures/TouchOwnershipEvent.cpp 2015-03-20 16:26:56 +0000
789@@ -0,0 +1,35 @@
790+/*
791+ * Copyright (C) 2014 Canonical, Ltd.
792+ *
793+ * This program is free software; you can redistribute it and/or modify
794+ * it under the terms of the GNU General Public License as published by
795+ * the Free Software Foundation; version 3.
796+ *
797+ * This program is distributed in the hope that it will be useful,
798+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
799+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
800+ * GNU General Public License for more details.
801+ *
802+ * You should have received a copy of the GNU General Public License
803+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
804+ */
805+
806+#include "TouchOwnershipEvent.h"
807+
808+QEvent::Type TouchOwnershipEvent::m_touchOwnershipType = (QEvent::Type)-1;
809+
810+TouchOwnershipEvent::TouchOwnershipEvent(int touchId, bool gained)
811+ : QEvent(touchOwnershipEventType())
812+ , m_touchId(touchId)
813+ , m_gained(gained)
814+{
815+}
816+
817+QEvent::Type TouchOwnershipEvent::touchOwnershipEventType()
818+{
819+ if (m_touchOwnershipType == (QEvent::Type)-1) {
820+ m_touchOwnershipType = (QEvent::Type)registerEventType();
821+ }
822+
823+ return m_touchOwnershipType;
824+}
825
826=== added file 'src/app/unity8/libs/UbuntuGestures/TouchOwnershipEvent.h'
827--- src/app/unity8/libs/UbuntuGestures/TouchOwnershipEvent.h 1970-01-01 00:00:00 +0000
828+++ src/app/unity8/libs/UbuntuGestures/TouchOwnershipEvent.h 2015-03-20 16:26:56 +0000
829@@ -0,0 +1,50 @@
830+/*
831+ * Copyright (C) 2014 Canonical, Ltd.
832+ *
833+ * This program is free software; you can redistribute it and/or modify
834+ * it under the terms of the GNU General Public License as published by
835+ * the Free Software Foundation; version 3.
836+ *
837+ * This program is distributed in the hope that it will be useful,
838+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
839+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
840+ * GNU General Public License for more details.
841+ *
842+ * You should have received a copy of the GNU General Public License
843+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
844+ */
845+
846+#ifndef UBUNTU_TOUCHOWNERSHIPEVENT_H
847+#define UBUNTU_TOUCHOWNERSHIPEVENT_H
848+
849+#include <QEvent>
850+#include "UbuntuGesturesGlobal.h"
851+
852+/*
853+ When an item get an ownership event for a touch it can grab/steal that touch
854+ with a clean conscience.
855+ */
856+class UBUNTUGESTURES_EXPORT TouchOwnershipEvent : public QEvent
857+{
858+public:
859+ TouchOwnershipEvent(int touchId, bool gained);
860+
861+ static Type touchOwnershipEventType();
862+
863+ /*
864+ Whether ownership was gained (true) or lost (false)
865+ */
866+ bool gained() const { return m_gained; }
867+
868+ /*
869+ Id of the touch whose ownership was granted.
870+ */
871+ int touchId() const { return m_touchId; }
872+
873+private:
874+ static Type m_touchOwnershipType;
875+ int m_touchId;
876+ bool m_gained;
877+};
878+
879+#endif // UBUNTU_TOUCHOWNERSHIPEVENT_H
880
881=== added file 'src/app/unity8/libs/UbuntuGestures/TouchRegistry.cpp'
882--- src/app/unity8/libs/UbuntuGestures/TouchRegistry.cpp 1970-01-01 00:00:00 +0000
883+++ src/app/unity8/libs/UbuntuGestures/TouchRegistry.cpp 2015-03-20 16:26:56 +0000
884@@ -0,0 +1,500 @@
885+/*
886+ * Copyright (C) 2014 Canonical, Ltd.
887+ *
888+ * This program is free software; you can redistribute it and/or modify
889+ * it under the terms of the GNU General Public License as published by
890+ * the Free Software Foundation; version 3.
891+ *
892+ * This program is distributed in the hope that it will be useful,
893+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
894+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
895+ * GNU General Public License for more details.
896+ *
897+ * You should have received a copy of the GNU General Public License
898+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
899+ */
900+
901+#include "TouchRegistry.h"
902+
903+#include <QCoreApplication>
904+#include <QDebug>
905+
906+#pragma GCC diagnostic push
907+#pragma GCC diagnostic ignored "-pedantic"
908+#include <private/qquickitem_p.h>
909+#pragma GCC diagnostic pop
910+
911+#include "CandidateInactivityTimer.h"
912+#include "Timer.h"
913+#include "TouchOwnershipEvent.h"
914+#include "UnownedTouchEvent.h"
915+
916+#define TOUCHREGISTRY_DEBUG 0
917+
918+#if TOUCHREGISTRY_DEBUG
919+ #include "DebugHelpers.h"
920+ #define UG_DEBUG qDebug() << "[TouchRegistry]"
921+#endif // TOUCHREGISTRY_DEBUG
922+
923+using namespace UbuntuGestures;
924+
925+TouchRegistry *TouchRegistry::m_instance = nullptr;
926+
927+TouchRegistry::TouchRegistry(QObject *parent)
928+ : TouchRegistry(parent, new TimerFactory)
929+{
930+}
931+
932+TouchRegistry::TouchRegistry(QObject *parent, AbstractTimerFactory *timerFactory)
933+ : QObject(parent)
934+ , m_inDispatchLoop(false)
935+ , m_timerFactory(timerFactory)
936+{
937+ if (m_instance == nullptr) {
938+ m_instance = this;
939+ } else {
940+ qFatal("Cannot have more than one instance of TouchRegistry. It must be a singleton.");
941+ }
942+}
943+
944+TouchRegistry::~TouchRegistry()
945+{
946+ Q_ASSERT(m_instance != nullptr);
947+ m_instance = nullptr;
948+ delete m_timerFactory;
949+}
950+
951+void TouchRegistry::update(const QTouchEvent *event)
952+{
953+ #if TOUCHREGISTRY_DEBUG
954+ UG_DEBUG << "got" << qPrintable(touchEventToString(event));
955+ #endif
956+
957+ const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
958+ for (int i = 0; i < touchPoints.count(); ++i) {
959+ const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i);
960+ if (touchPoint.state() == Qt::TouchPointPressed) {
961+ TouchInfo &touchInfo = m_touchInfoPool.getEmptySlot();
962+ touchInfo.init(touchPoint.id());
963+ } else if (touchPoint.state() == Qt::TouchPointReleased) {
964+ Pool<TouchInfo>::Iterator touchInfo = findTouchInfo(touchPoint.id());
965+
966+ touchInfo->physicallyEnded = true;
967+ }
968+ }
969+
970+ deliverTouchUpdatesToUndecidedCandidatesAndWatchers(event);
971+
972+ freeEndedTouchInfos();
973+}
974+
975+void TouchRegistry::deliverTouchUpdatesToUndecidedCandidatesAndWatchers(const QTouchEvent *event)
976+{
977+ // TODO: Look into how we could optimize this whole thing.
978+ // Although it's not really a problem as we should have at most two candidates
979+ // for each point and there should not be many active points at any given moment.
980+ // But having three nested for-loops does scare.
981+
982+ // TODO: Don't send it to the object that is already receiving the regular event
983+ // because QQuickWindow is sending it to him (i.e., he's the touch owner from Qt's point of view)
984+ // Problem is, we cannnot easily get this information.
985+
986+ const QList<QTouchEvent::TouchPoint> &updatedTouchPoints = event->touchPoints();
987+
988+ // Maps an item to the touches in this event he should be informed about.
989+ // E.g.: a QTouchEvent might have three touches but a given item might be interested in only
990+ // one of them. So he will get a UnownedTouchEvent from this QTouchEvent containing only that
991+ // touch point.
992+ QMap<QQuickItem*, QList<int>> touchIdsForItems;
993+
994+ // Build touchIdsForItems
995+ m_touchInfoPool.forEach([&](Pool<TouchInfo>::Iterator &touchInfo) {
996+ if (touchInfo->isOwned() && touchInfo->watchers.isEmpty())
997+ return true;
998+
999+ for (int j = 0; j < updatedTouchPoints.count(); ++j) {
1000+ if (updatedTouchPoints[j].id() == touchInfo->id) {
1001+ if (!touchInfo->isOwned()) {
1002+ for (int i = 0; i < touchInfo->candidates.count(); ++i) {
1003+ CandidateInfo &candidate = touchInfo->candidates[i];
1004+ Q_ASSERT(!candidate.item.isNull());
1005+ touchIdsForItems[candidate.item.data()].append(touchInfo->id);
1006+ }
1007+ }
1008+
1009+ const QList<QPointer<QQuickItem>> &watchers = touchInfo->watchers;
1010+ for (int i = 0; i < watchers.count(); ++i) {
1011+ if (!watchers[i].isNull()) {
1012+ touchIdsForItems[watchers[i].data()].append(touchInfo->id);
1013+ }
1014+ }
1015+
1016+ return true;
1017+ }
1018+ }
1019+
1020+ return true;
1021+ });
1022+
1023+ // TODO: Consider what happens if an item calls any of TouchRegistry's public methods
1024+ // from the event handler callback.
1025+ m_inDispatchLoop = true;
1026+ auto it = touchIdsForItems.constBegin();
1027+ while (it != touchIdsForItems.constEnd()) {
1028+ QQuickItem *item = it.key();
1029+ const QList<int> &touchIds = it.value();
1030+ dispatchPointsToItem(event, touchIds, item);
1031+ ++it;
1032+ };
1033+ m_inDispatchLoop = false;
1034+}
1035+
1036+void TouchRegistry::freeEndedTouchInfos()
1037+{
1038+ m_touchInfoPool.forEach([&](Pool<TouchInfo>::Iterator &touchInfo) {
1039+ if (touchInfo->ended()) {
1040+ m_touchInfoPool.freeSlot(touchInfo);
1041+ }
1042+ return true;
1043+ });
1044+}
1045+
1046+/*
1047+ Extracts the touches with the given touchIds from event and send them in a
1048+ UnownedTouchEvent to the given item
1049+ */
1050+void TouchRegistry::dispatchPointsToItem(const QTouchEvent *event, const QList<int> &touchIds,
1051+ QQuickItem *item)
1052+{
1053+ Qt::TouchPointStates touchPointStates = 0;
1054+ QList<QTouchEvent::TouchPoint> touchPoints;
1055+
1056+ const QList<QTouchEvent::TouchPoint> &allTouchPoints = event->touchPoints();
1057+
1058+ QTransform windowToCandidateTransform = QQuickItemPrivate::get(item)->windowToItemTransform();
1059+ QMatrix4x4 windowToCandidateMatrix(windowToCandidateTransform);
1060+
1061+ for (int i = 0; i < allTouchPoints.count(); ++i) {
1062+ const QTouchEvent::TouchPoint &originalTouchPoint = allTouchPoints[i];
1063+ if (touchIds.contains(originalTouchPoint.id())) {
1064+ QTouchEvent::TouchPoint touchPoint = originalTouchPoint;
1065+
1066+ translateTouchPointFromScreenToWindowCoords(touchPoint);
1067+
1068+ // Set the point's local coordinates to that of the item
1069+ touchPoint.setRect(windowToCandidateTransform.mapRect(touchPoint.sceneRect()));
1070+ touchPoint.setStartPos(windowToCandidateTransform.map(touchPoint.startScenePos()));
1071+ touchPoint.setLastPos(windowToCandidateTransform.map(touchPoint.lastScenePos()));
1072+ touchPoint.setVelocity(windowToCandidateMatrix.mapVector(touchPoint.velocity()).toVector2D());
1073+
1074+ touchPoints.append(touchPoint);
1075+ touchPointStates |= touchPoint.state();
1076+ }
1077+ }
1078+
1079+ QTouchEvent *eventForItem = new QTouchEvent(event->type(),
1080+ event->device(),
1081+ event->modifiers(),
1082+ touchPointStates,
1083+ touchPoints);
1084+ eventForItem->setWindow(event->window());
1085+ eventForItem->setTimestamp(event->timestamp());
1086+ eventForItem->setTarget(event->target());
1087+
1088+ UnownedTouchEvent unownedTouchEvent(eventForItem);
1089+
1090+ #if TOUCHREGISTRY_DEBUG
1091+ UG_DEBUG << "Sending unowned" << qPrintable(touchEventToString(eventForItem))
1092+ << "to" << item;
1093+ #endif
1094+
1095+ QCoreApplication::sendEvent(item, &unownedTouchEvent);
1096+}
1097+
1098+void TouchRegistry::translateTouchPointFromScreenToWindowCoords(QTouchEvent::TouchPoint &touchPoint)
1099+{
1100+ touchPoint.setScreenRect(touchPoint.sceneRect());
1101+ touchPoint.setStartScreenPos(touchPoint.startScenePos());
1102+ touchPoint.setLastScreenPos(touchPoint.lastScenePos());
1103+
1104+ touchPoint.setSceneRect(touchPoint.rect());
1105+ touchPoint.setStartScenePos(touchPoint.startPos());
1106+ touchPoint.setLastScenePos(touchPoint.lastPos());
1107+}
1108+
1109+bool TouchRegistry::eventFilter(QObject *watched, QEvent *event)
1110+{
1111+ Q_UNUSED(watched);
1112+
1113+ switch (event->type()) {
1114+ case QEvent::TouchBegin:
1115+ case QEvent::TouchUpdate:
1116+ case QEvent::TouchEnd:
1117+ case QEvent::TouchCancel:
1118+ update(static_cast<QTouchEvent*>(event));
1119+ break;
1120+ default:
1121+ // do nothing
1122+ break;
1123+ }
1124+
1125+ // Do not filter out the event. i.e., let it be handled further as
1126+ // we're just monitoring events
1127+ return false;
1128+}
1129+
1130+void TouchRegistry::addCandidateOwnerForTouch(int id, QQuickItem *candidate)
1131+{
1132+ #if TOUCHREGISTRY_DEBUG
1133+ UG_DEBUG << "addCandidateOwnerForTouch id" << id << "candidate" << candidate;
1134+ #endif
1135+
1136+ Pool<TouchInfo>::Iterator touchInfo = findTouchInfo(id);
1137+ if (!touchInfo) { qFatal("TouchRegistry: Failed to find TouchInfo"); }
1138+
1139+ if (touchInfo->isOwned()) {
1140+ qWarning("TouchRegistry: trying to add candidate owner for a touch that's already owned");
1141+ return;
1142+ }
1143+
1144+ // TODO: Check if candidate already exists
1145+
1146+ CandidateInfo candidateInfo;
1147+ candidateInfo.undecided = true;
1148+ candidateInfo.item = candidate;
1149+ candidateInfo.inactivityTimer = new CandidateInactivityTimer(id, candidate,
1150+ *m_timerFactory,
1151+ this);
1152+ connect(candidateInfo.inactivityTimer, &CandidateInactivityTimer::candidateDefaulted,
1153+ this, &TouchRegistry::rejectCandidateOwnerForTouch);
1154+
1155+ touchInfo->candidates.append(candidateInfo);
1156+}
1157+
1158+void TouchRegistry::addTouchWatcher(int touchId, QQuickItem *watcher)
1159+{
1160+ #if TOUCHREGISTRY_DEBUG
1161+ UG_DEBUG << "addTouchWatcher id" << touchId << "watcher" << watcher;
1162+ #endif
1163+
1164+ Pool<TouchInfo>::Iterator touchInfo = findTouchInfo(touchId);
1165+ if (!touchInfo) { qFatal("TouchRegistry: Failed to find TouchInfo"); }
1166+
1167+ // TODO: Check if watcher already exists
1168+
1169+ touchInfo->watchers.append(watcher);
1170+}
1171+
1172+void TouchRegistry::removeCandidateOwnerForTouch(int id, QQuickItem *candidate)
1173+{
1174+ #if TOUCHREGISTRY_DEBUG
1175+ UG_DEBUG << "removeCandidateOwnerForTouch id" << id << "candidate" << candidate;
1176+ #endif
1177+
1178+ Pool<TouchInfo>::Iterator touchInfo = findTouchInfo(id);
1179+ if (!touchInfo) { qFatal("TouchRegistry: Failed to find TouchInfo"); }
1180+
1181+ int indexRemoved = -1;
1182+
1183+ // TODO: check if the candidate is in fact the owner of the touch
1184+
1185+ for (int i = 0; i < touchInfo->candidates.count() && indexRemoved == -1; ++i) {
1186+ CandidateInfo &candidateInfo = touchInfo->candidates[i];
1187+ if (candidateInfo.item == candidate) {
1188+ Q_ASSERT(i > 0 || candidateInfo.undecided);
1189+ if (i == 0 && !candidateInfo.undecided) {
1190+ qCritical("TouchRegistry: touch owner is being removed.");
1191+ }
1192+ delete candidateInfo.inactivityTimer;
1193+ candidateInfo.inactivityTimer = nullptr;
1194+ touchInfo->candidates.removeAt(i);
1195+ indexRemoved = i;
1196+ }
1197+ }
1198+
1199+ if (indexRemoved == 0) {
1200+ // the top candidate has been removed. if the new top candidate
1201+ // wants the touch let him know he's now the owner.
1202+ if (touchInfo->isOwned()) {
1203+ touchInfo->notifyCandidatesOfOwnershipResolution();
1204+ }
1205+ }
1206+
1207+ if (!m_inDispatchLoop && touchInfo->ended()) {
1208+ m_touchInfoPool.freeSlot(touchInfo);
1209+ }
1210+}
1211+
1212+void TouchRegistry::requestTouchOwnership(int id, QQuickItem *candidate)
1213+{
1214+ #if TOUCHREGISTRY_DEBUG
1215+ UG_DEBUG << "requestTouchOwnership id " << id << "candidate" << candidate;
1216+ #endif
1217+
1218+ Pool<TouchInfo>::Iterator touchInfo = findTouchInfo(id);
1219+ if (!touchInfo) { qFatal("TouchRegistry: Failed to find TouchInfo"); }
1220+
1221+ Q_ASSERT(!touchInfo->isOwned());
1222+
1223+ int candidateIndex = -1;
1224+ for (int i = 0; i < touchInfo->candidates.count(); ++i) {
1225+ CandidateInfo &candidateInfo = touchInfo->candidates[i];
1226+ if (candidateInfo.item == candidate) {
1227+ candidateInfo.undecided = false;
1228+ delete candidateInfo.inactivityTimer;
1229+ candidateInfo.inactivityTimer = nullptr;
1230+ candidateIndex = i;
1231+ break;
1232+ }
1233+ }
1234+
1235+ // add it as a candidate if not present yet
1236+ if (candidateIndex < 0) {
1237+ CandidateInfo candidateInfo;
1238+ candidateInfo.undecided = false;
1239+ candidateInfo.item = candidate;
1240+ candidateInfo.inactivityTimer = nullptr;
1241+ touchInfo->candidates.append(candidateInfo);
1242+ // it's the last one
1243+ candidateIndex = touchInfo->candidates.count() - 1;
1244+ }
1245+
1246+ // If it's the top candidate it means it's now the owner. Let
1247+ // it know about it.
1248+ if (candidateIndex == 0) {
1249+ touchInfo->notifyCandidatesOfOwnershipResolution();
1250+ }
1251+}
1252+
1253+Pool<TouchRegistry::TouchInfo>::Iterator TouchRegistry::findTouchInfo(int id)
1254+{
1255+ Pool<TouchInfo>::Iterator touchInfo;
1256+
1257+ m_touchInfoPool.forEach([&](Pool<TouchInfo>::Iterator &someTouchInfo) -> bool {
1258+ if (someTouchInfo->id == id) {
1259+ touchInfo = someTouchInfo;
1260+ return false;
1261+ } else {
1262+ return true;
1263+ }
1264+ });
1265+
1266+ return touchInfo;
1267+}
1268+
1269+
1270+void TouchRegistry::rejectCandidateOwnerForTouch(int id, QQuickItem *candidate)
1271+{
1272+ // NB: It's technically possible that candidate is a dangling pointer at this point.
1273+ // Although that would most likely be due to a bug in our code.
1274+ // In any case, only dereference it after it's confirmed that it indeed exists.
1275+
1276+ #if TOUCHREGISTRY_DEBUG
1277+ UG_DEBUG << "rejectCandidateOwnerForTouch id" << id << "candidate" << (void*)candidate;
1278+ #endif
1279+
1280+ Pool<TouchInfo>::Iterator touchInfo = findTouchInfo(id);
1281+ if (!touchInfo) {
1282+ #if TOUCHREGISTRY_DEBUG
1283+ UG_DEBUG << "Failed to find TouchInfo for id" << id;
1284+ #endif
1285+ return;
1286+ }
1287+
1288+ int rejectedCandidateIndex = -1;
1289+
1290+ // Check if the given candidate is valid and still undecided
1291+ for (int i = 0; i < touchInfo->candidates.count() && rejectedCandidateIndex == -1; ++i) {
1292+ CandidateInfo &candidateInfo = touchInfo->candidates[i];
1293+ if (candidateInfo.item == candidate) {
1294+ Q_ASSERT(i > 0 || candidateInfo.undecided);
1295+ if (i == 0 && !candidateInfo.undecided) {
1296+ qCritical() << "TouchRegistry: Can't reject item (" << (void*)candidate
1297+ << ") as it already owns touch" << id;
1298+ return;
1299+ } else {
1300+ // we found the guy and it's all fine.
1301+ rejectedCandidateIndex = i;
1302+ }
1303+ }
1304+ }
1305+
1306+ // If we reached this point it's because the given candidate exists and is indeed undecided.
1307+
1308+ Q_ASSERT(rejectedCandidateIndex >= 0 && rejectedCandidateIndex < touchInfo->candidates.size());
1309+
1310+ {
1311+ TouchOwnershipEvent lostOwnershipEvent(id, false /*gained*/);
1312+ QCoreApplication::sendEvent(candidate, &lostOwnershipEvent);
1313+ }
1314+
1315+ touchInfo->candidates.removeAt(rejectedCandidateIndex);
1316+
1317+ if (rejectedCandidateIndex == 0) {
1318+ // the top candidate has been removed. if the new top candidate
1319+ // wants the touch let him know he's now the owner.
1320+ if (touchInfo->isOwned()) {
1321+ touchInfo->notifyCandidatesOfOwnershipResolution();
1322+ }
1323+ }
1324+}
1325+
1326+////////////////////////////////////// TouchRegistry::TouchInfo ////////////////////////////////////
1327+
1328+TouchRegistry::TouchInfo::TouchInfo(int id)
1329+{
1330+ init(id);
1331+}
1332+
1333+void TouchRegistry::TouchInfo::reset()
1334+{
1335+ id = -1;
1336+
1337+ for (int i = 0; i < candidates.count(); ++i) {
1338+ CandidateInfo &candidate = candidates[i];
1339+ delete candidate.inactivityTimer;
1340+ candidate.inactivityTimer.clear(); // shoundn't be needed but anyway...
1341+ }
1342+}
1343+
1344+void TouchRegistry::TouchInfo::init(int id)
1345+{
1346+ this->id = id;
1347+ physicallyEnded = false;
1348+ candidates.clear();
1349+ watchers.clear();
1350+}
1351+
1352+bool TouchRegistry::TouchInfo::isOwned() const
1353+{
1354+ return !candidates.isEmpty() && !candidates.first().undecided;
1355+}
1356+
1357+bool TouchRegistry::TouchInfo::ended() const
1358+{
1359+ Q_ASSERT(isValid());
1360+ return physicallyEnded && (isOwned() || candidates.isEmpty());
1361+}
1362+
1363+void TouchRegistry::TouchInfo::notifyCandidatesOfOwnershipResolution()
1364+{
1365+ Q_ASSERT(isOwned());
1366+
1367+ #if TOUCHREGISTRY_DEBUG
1368+ UG_DEBUG << "sending TouchOwnershipEvent(id =" << id
1369+ << " gained) to candidate" << candidates[0].item;
1370+ #endif
1371+
1372+ TouchOwnershipEvent gainedOwnershipEvent(id, true /*gained*/);
1373+ QCoreApplication::sendEvent(candidates[0].item, &gainedOwnershipEvent);
1374+
1375+
1376+ TouchOwnershipEvent lostOwnershipEvent(id, false /*gained*/);
1377+ for (int i = 1; i < candidates.count(); ++i) {
1378+ #if TOUCHREGISTRY_DEBUG
1379+ UG_DEBUG << "sending TouchWonershipEvent(id =" << id << " lost) to candidate"
1380+ << candidates[i].item;
1381+ #endif
1382+ QCoreApplication::sendEvent(candidates[i].item, &lostOwnershipEvent);
1383+ }
1384+}
1385
1386=== added file 'src/app/unity8/libs/UbuntuGestures/TouchRegistry.h'
1387--- src/app/unity8/libs/UbuntuGestures/TouchRegistry.h 1970-01-01 00:00:00 +0000
1388+++ src/app/unity8/libs/UbuntuGestures/TouchRegistry.h 2015-03-20 16:26:56 +0000
1389@@ -0,0 +1,182 @@
1390+/*
1391+ * Copyright (C) 2014 Canonical, Ltd.
1392+ *
1393+ * This program is free software; you can redistribute it and/or modify
1394+ * it under the terms of the GNU General Public License as published by
1395+ * the Free Software Foundation; version 3.
1396+ *
1397+ * This program is distributed in the hope that it will be useful,
1398+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1399+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1400+ * GNU General Public License for more details.
1401+ *
1402+ * You should have received a copy of the GNU General Public License
1403+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1404+ */
1405+
1406+#ifndef UNITY_TOUCHREGISTRY_H
1407+#define UNITY_TOUCHREGISTRY_H
1408+
1409+#include <QQuickItem>
1410+#include <QObject>
1411+#include <QPointer>
1412+#include <QTouchEvent>
1413+#include <QVector>
1414+
1415+#include "UbuntuGesturesGlobal.h"
1416+#include "CandidateInactivityTimer.h"
1417+#include "Pool.h"
1418+
1419+namespace UbuntuGestures {
1420+ class AbstractTimerFactory;
1421+}
1422+
1423+/*
1424+ Where the ownership of touches is registered.
1425+
1426+ Singleton used for adding a touch point ownership model analogous to the one
1427+ described in the XInput 2.2 protocol[1] on top of the existing input dispatch logic in QQuickWindow.
1428+
1429+ It provides a much more flexible and powerful way of dealing with pointer ownership than the existing
1430+ mechanisms in Qt. Namely QQuickItem::grabTouchPoints, QuickItem::keepTouchGrab,
1431+ QQuickItem::setFiltersChildMouseEvents, QQuickItem::ungrabTouchPoints and QQuickItem::touchUngrabEvent.
1432+
1433+ Usage:
1434+
1435+ 1- An item receives a a new touch point. If he's not sure whether he wants it yet, he calls:
1436+ TouchRegistry::instance()->addCandidateOwnerForTouch(touchId, this);
1437+ touchEvent->ignore();
1438+ Ignoring the event is crucial so that it can be seen by other interested parties, which will
1439+ behave similarly.
1440+
1441+ 2- That item will then start receiving UnownedTouchEvents for that touch from step 1. Once he's
1442+ made a decision he calls either:
1443+ TouchRegistry::instance()->requestTouchOwnership(touchId, this);
1444+ If he wants the touch point or:
1445+ TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, this);
1446+  if he does not want it.
1447+
1448+ Candidates are put in a priority queue. The first one to call addCandidateOwnerForTouch() will
1449+ take precedence over the others for receiving ownership over the touch point (from now on called
1450+ simply top-candidate).
1451+
1452+ If the top-candidate calls requestTouchOwnership() he will immediately receive a
1453+ TouchOwnershipEvent(gained=true) for that touch point. He can then safely call
1454+ QQuickItem::grabTouchPoints to actually get the owned touch points. The other candidates
1455+ will receive TouchOwnershipEvent(gained=false) and will no longer receive UnownedTouchEvents
1456+ for that touch point. They will have to undo whatever action they were performing with that
1457+ touch point.
1458+
1459+ But if the top-candidate calls removeCandidateOwnerForTouch() instead, he's popped from the
1460+ candidacy queue and ownership is given to the new top-most candidate if he has already
1461+ made his decision, that is.
1462+
1463+ The TouchRegistry cannot enforce the results of this pointer ownership negotiation (i.e.,
1464+ who gets to grab the touch points) as that would clash with QQuickWindow's input event
1465+ dispatching logic. The candidates have to respect the decision and grab the touch points
1466+ themselves.
1467+
1468+ If an item wants ownership over touches as soon as he receives the TouchBegin for them, his step 1
1469+ would be instead:
1470+ TouchRegistry::instance()->requestTouchOwnership(touchId, this);
1471+ return true;
1472+ He would then be notified once ownership has been granted to him, from which point onwards he could
1473+ safely assume other TouchRegistry users wouldn't snatch this touch away from him.
1474+
1475+ Items oblivious to TouchRegistry will lose their touch points without warning, just like in plain Qt.
1476+
1477+ [1] - http://www.x.org/releases/X11R7.7/doc/inputproto/XI2proto.txt (see multitouch-ownership)
1478+ */
1479+class UBUNTUGESTURES_EXPORT TouchRegistry : public QObject
1480+{
1481+ Q_OBJECT
1482+public:
1483+ TouchRegistry(QObject *parent = nullptr);
1484+ // Useful for tests, where you should feed a fake timer
1485+ TouchRegistry(QObject *parent, UbuntuGestures::AbstractTimerFactory *timerFactory);
1486+
1487+ virtual ~TouchRegistry();
1488+
1489+ // Returns a pointer to the application's TouchRegistry instance.
1490+ // If no instance has been allocated, null is returned.
1491+ static TouchRegistry *instance() { return m_instance; }
1492+
1493+ void update(const QTouchEvent *event);
1494+
1495+ // Calls update() if the given event is a QTouchEvent
1496+ bool eventFilter(QObject *watched, QEvent *event) override;
1497+
1498+ // An item that might later request ownership over the given touch point.
1499+ // He will be kept informed about that touch point through UnownedTouchEvents
1500+ // All candidates must eventually decide whether they want to own the touch point
1501+ // or not. That decision is informed through requestTouchOwnership() or
1502+ // removeCandidateOwnerForTouch()
1503+ void addCandidateOwnerForTouch(int id, QQuickItem *candidate);
1504+
1505+ // The same as rejecting ownership of a touch
1506+ void removeCandidateOwnerForTouch(int id, QQuickItem *candidate);
1507+
1508+ // The candidate object wants to be the owner of the touch with the given id.
1509+ // If he's currently the oldest/top-most candidate, he will get an ownership
1510+ // event immediately. If not, he will get ownership if (or once) he becomes the
1511+ // top-most candidate.
1512+ void requestTouchOwnership(int id, QQuickItem *candidate);
1513+
1514+ // An item that has no interest (effective or potential) in owning a touch point
1515+ // but would nonetheless like to be kept up-to-date on its state.
1516+ void addTouchWatcher(int touchId, QQuickItem *watcherItem);
1517+
1518+private Q_SLOTS:
1519+ void rejectCandidateOwnerForTouch(int id, QQuickItem *candidate);
1520+
1521+private:
1522+ class CandidateInfo {
1523+ public:
1524+ bool undecided;
1525+ // TODO: Prune candidates that become null and resolve ownership accordingly.
1526+ QPointer<QQuickItem> item;
1527+ QPointer<UbuntuGestures::CandidateInactivityTimer> inactivityTimer;
1528+ };
1529+
1530+ class TouchInfo {
1531+ public:
1532+ TouchInfo() : id(-1) {}
1533+ TouchInfo(int id);
1534+ bool isValid() const { return id >= 0; }
1535+ void reset();
1536+ void init(int id);
1537+ int id;
1538+ bool physicallyEnded;
1539+ bool isOwned() const;
1540+ bool ended() const;
1541+ void notifyCandidatesOfOwnershipResolution();
1542+
1543+ // TODO optimize storage (s/QList/Pool)
1544+ QList<CandidateInfo> candidates;
1545+ QList<QPointer<QQuickItem>> watchers;
1546+ };
1547+
1548+ Pool<TouchInfo>::Iterator findTouchInfo(int id);
1549+
1550+ void deliverTouchUpdatesToUndecidedCandidatesAndWatchers(const QTouchEvent *event);
1551+
1552+ static void translateTouchPointFromScreenToWindowCoords(QTouchEvent::TouchPoint &touchPoint);
1553+
1554+ static void dispatchPointsToItem(const QTouchEvent *event, const QList<int> &touchIds,
1555+ QQuickItem *item);
1556+ void freeEndedTouchInfos();
1557+
1558+ Pool<TouchInfo> m_touchInfoPool;
1559+
1560+ // the singleton instance
1561+ static TouchRegistry *m_instance;
1562+
1563+ bool m_inDispatchLoop;
1564+
1565+ UbuntuGestures::AbstractTimerFactory *m_timerFactory;
1566+
1567+ friend class tst_TouchRegistry;
1568+ friend class tst_DirectionalDragArea;
1569+};
1570+
1571+#endif // UNITY_TOUCHREGISTRY_H
1572
1573=== added file 'src/app/unity8/libs/UbuntuGestures/UbuntuGesturesGlobal.h'
1574--- src/app/unity8/libs/UbuntuGestures/UbuntuGesturesGlobal.h 1970-01-01 00:00:00 +0000
1575+++ src/app/unity8/libs/UbuntuGestures/UbuntuGesturesGlobal.h 2015-03-20 16:26:56 +0000
1576@@ -0,0 +1,23 @@
1577+/*
1578+ * Copyright (C) 2014 Canonical, Ltd.
1579+ *
1580+ * This program is free software; you can redistribute it and/or modify
1581+ * it under the terms of the GNU General Public License as published by
1582+ * the Free Software Foundation; version 3.
1583+ *
1584+ * This program is distributed in the hope that it will be useful,
1585+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1586+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1587+ * GNU General Public License for more details.
1588+ *
1589+ * You should have received a copy of the GNU General Public License
1590+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1591+ */
1592+
1593+#include <QtCore/QtGlobal>
1594+
1595+#if defined(UBUNTUGESTURES_LIBRARY)
1596+# define UBUNTUGESTURES_EXPORT Q_DECL_EXPORT
1597+#else
1598+# define UBUNTUGESTURES_EXPORT Q_DECL_IMPORT
1599+#endif
1600
1601=== added file 'src/app/unity8/libs/UbuntuGestures/UnownedTouchEvent.cpp'
1602--- src/app/unity8/libs/UbuntuGestures/UnownedTouchEvent.cpp 1970-01-01 00:00:00 +0000
1603+++ src/app/unity8/libs/UbuntuGestures/UnownedTouchEvent.cpp 2015-03-20 16:26:56 +0000
1604@@ -0,0 +1,39 @@
1605+/*
1606+ * Copyright (C) 2014 Canonical, Ltd.
1607+ *
1608+ * This program is free software; you can redistribute it and/or modify
1609+ * it under the terms of the GNU General Public License as published by
1610+ * the Free Software Foundation; version 3.
1611+ *
1612+ * This program is distributed in the hope that it will be useful,
1613+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1614+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1615+ * GNU General Public License for more details.
1616+ *
1617+ * You should have received a copy of the GNU General Public License
1618+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1619+ */
1620+
1621+#include "UnownedTouchEvent.h"
1622+
1623+QEvent::Type UnownedTouchEvent::m_unownedTouchEventType = (QEvent::Type)-1;
1624+
1625+UnownedTouchEvent::UnownedTouchEvent(QTouchEvent *touchEvent)
1626+ : QEvent(unownedTouchEventType())
1627+ , m_touchEvent(touchEvent)
1628+{
1629+}
1630+
1631+QEvent::Type UnownedTouchEvent::unownedTouchEventType()
1632+{
1633+ if (m_unownedTouchEventType == (QEvent::Type)-1) {
1634+ m_unownedTouchEventType = (QEvent::Type)registerEventType();
1635+ }
1636+
1637+ return m_unownedTouchEventType;
1638+}
1639+
1640+QTouchEvent *UnownedTouchEvent::touchEvent()
1641+{
1642+ return m_touchEvent.data();
1643+}
1644
1645=== added file 'src/app/unity8/libs/UbuntuGestures/UnownedTouchEvent.h'
1646--- src/app/unity8/libs/UbuntuGestures/UnownedTouchEvent.h 1970-01-01 00:00:00 +0000
1647+++ src/app/unity8/libs/UbuntuGestures/UnownedTouchEvent.h 2015-03-20 16:26:56 +0000
1648@@ -0,0 +1,45 @@
1649+/*
1650+ * Copyright (C) 2014 Canonical, Ltd.
1651+ *
1652+ * This program is free software; you can redistribute it and/or modify
1653+ * it under the terms of the GNU General Public License as published by
1654+ * the Free Software Foundation; version 3.
1655+ *
1656+ * This program is distributed in the hope that it will be useful,
1657+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1658+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1659+ * GNU General Public License for more details.
1660+ *
1661+ * You should have received a copy of the GNU General Public License
1662+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1663+ */
1664+
1665+#ifndef UBUNTU_UNOWNEDTOUCHEVENT_H
1666+#define UBUNTU_UNOWNEDTOUCHEVENT_H
1667+
1668+#include <QTouchEvent>
1669+#include <QScopedPointer>
1670+#include "UbuntuGesturesGlobal.h"
1671+
1672+/*
1673+ A touch event with touch points that do not belong the item receiving it.
1674+
1675+ See TouchRegistry::addCandidateOwnerForTouch and TouchRegistry::addTouchWatcher
1676+ */
1677+class UBUNTUGESTURES_EXPORT UnownedTouchEvent : public QEvent
1678+{
1679+public:
1680+ UnownedTouchEvent(QTouchEvent *touchEvent);
1681+ static Type unownedTouchEventType();
1682+
1683+ // TODO: It might be cleaner to store the information directly in UnownedTouchEvent
1684+ // instead of carrying around a synthesized QTouchEvent. But the latter option
1685+ // is very convenient.
1686+ QTouchEvent *touchEvent();
1687+
1688+private:
1689+ static Type m_unownedTouchEventType;
1690+ QScopedPointer<QTouchEvent> m_touchEvent;
1691+};
1692+
1693+#endif // UBUNTU_UNOWNEDTOUCHEVENT_H
1694
1695=== added directory 'src/app/unity8/plugins'
1696=== added file 'src/app/unity8/plugins/CMakeLists.txt'
1697--- src/app/unity8/plugins/CMakeLists.txt 1970-01-01 00:00:00 +0000
1698+++ src/app/unity8/plugins/CMakeLists.txt 2015-03-20 16:26:56 +0000
1699@@ -0,0 +1,1 @@
1700+add_subdirectory(Ubuntu)
1701
1702=== added directory 'src/app/unity8/plugins/Ubuntu'
1703=== added file 'src/app/unity8/plugins/Ubuntu/CMakeLists.txt'
1704--- src/app/unity8/plugins/Ubuntu/CMakeLists.txt 1970-01-01 00:00:00 +0000
1705+++ src/app/unity8/plugins/Ubuntu/CMakeLists.txt 2015-03-20 16:26:56 +0000
1706@@ -0,0 +1,1 @@
1707+add_subdirectory(Gestures)
1708
1709=== added directory 'src/app/unity8/plugins/Ubuntu/Gestures'
1710=== added file 'src/app/unity8/plugins/Ubuntu/Gestures/AxisVelocityCalculator.cpp'
1711--- src/app/unity8/plugins/Ubuntu/Gestures/AxisVelocityCalculator.cpp 1970-01-01 00:00:00 +0000
1712+++ src/app/unity8/plugins/Ubuntu/Gestures/AxisVelocityCalculator.cpp 2015-03-20 16:26:56 +0000
1713@@ -0,0 +1,149 @@
1714+/*
1715+ * Copyright (C) 2013 - Canonical Ltd.
1716+ *
1717+ * This program is free software: you can redistribute it and/or modify it
1718+ * under the terms of the GNU Lesser General Public License, as
1719+ * published by the Free Software Foundation; either version 2.1 or 3.0
1720+ * of the License.
1721+ *
1722+ * This program is distributed in the hope that it will be useful, but
1723+ * WITHOUT ANY WARRANTY; without even the implied warranties of
1724+ * MERCHANTABILITY, SATISFACTORY QUALITY or FITNESS FOR A PARTICULAR
1725+ * PURPOSE. See the applicable version of the GNU Lesser General Public
1726+ * License for more details.
1727+ *
1728+ * You should have received a copy of both the GNU Lesser General Public
1729+ * License along with this program. If not, see <http://www.gnu.org/licenses/>
1730+ *
1731+ * Authored by: Daniel d'Andrada <daniel.dandrada@canonical.com>
1732+ */
1733+
1734+#include "AxisVelocityCalculator.h"
1735+#include <QtCore/QElapsedTimer>
1736+
1737+using namespace UbuntuGestures;
1738+
1739+AxisVelocityCalculator::AxisVelocityCalculator(QObject *parent)
1740+ : AxisVelocityCalculator(SharedTimeSource(new RealTimeSource), parent)
1741+{
1742+}
1743+
1744+AxisVelocityCalculator::AxisVelocityCalculator(const SharedTimeSource &timeSource,
1745+ QObject *parent)
1746+ : QObject(parent)
1747+ , m_timeSource(timeSource)
1748+ , m_trackedPosition(0.0)
1749+{
1750+ reset();
1751+}
1752+
1753+AxisVelocityCalculator::~AxisVelocityCalculator()
1754+{
1755+}
1756+
1757+qreal AxisVelocityCalculator::trackedPosition() const
1758+{
1759+ return m_trackedPosition;
1760+}
1761+
1762+void AxisVelocityCalculator::setTrackedPosition(qreal newPosition)
1763+{
1764+ processMovement(newPosition - m_trackedPosition);
1765+
1766+ if (newPosition != m_trackedPosition) {
1767+ m_trackedPosition = newPosition;
1768+ Q_EMIT trackedPositionChanged(newPosition);
1769+ }
1770+}
1771+
1772+void AxisVelocityCalculator::updateIdleTime()
1773+{
1774+ processMovement(0);
1775+}
1776+
1777+void AxisVelocityCalculator::processMovement(qreal movement)
1778+{
1779+ if (m_samplesRead == -1) {
1780+ m_samplesRead = m_samplesWrite;
1781+ } else if (m_samplesRead == m_samplesWrite) {
1782+ /* the oldest value is going to be overwritten.
1783+ so now the oldest will be the next one. */
1784+ m_samplesRead = (m_samplesRead + 1) % MAX_SAMPLES;
1785+ }
1786+
1787+ m_samples[m_samplesWrite].mov = movement;
1788+ m_samples[m_samplesWrite].time = m_timeSource->msecsSinceReference();
1789+ m_samplesWrite = (m_samplesWrite + 1) % MAX_SAMPLES;
1790+}
1791+
1792+qreal AxisVelocityCalculator::calculate()
1793+{
1794+ if (numSamples() < MIN_SAMPLES_NEEDED) {
1795+ return 0.0;
1796+ }
1797+ updateIdleTime(); // consider the time elapsed since the last update and now
1798+
1799+ int lastIndex;
1800+ if (m_samplesWrite == 0) {
1801+ lastIndex = MAX_SAMPLES - 1;
1802+ } else {
1803+ lastIndex = m_samplesWrite - 1;
1804+ }
1805+
1806+ qint64 currTime = m_samples[lastIndex].time;
1807+
1808+ qreal totalTime = 0;
1809+ qreal totalDistance = 0;
1810+
1811+ int sampleIndex = (m_samplesRead + 1) % MAX_SAMPLES;
1812+ qint64 previousTime = m_samples[m_samplesRead].time;
1813+ while (sampleIndex != m_samplesWrite) {
1814+ // Skip this sample if it's too old
1815+ if (currTime - m_samples[sampleIndex].time <= AGE_OLDEST_SAMPLE) {
1816+ int deltaTime = m_samples[sampleIndex].time - previousTime;
1817+ totalDistance += m_samples[sampleIndex].mov;
1818+ totalTime += deltaTime;
1819+ }
1820+
1821+ previousTime = m_samples[sampleIndex].time;
1822+ sampleIndex = (sampleIndex + 1) % MAX_SAMPLES;
1823+ }
1824+
1825+ return totalDistance / totalTime;
1826+}
1827+
1828+void AxisVelocityCalculator::reset()
1829+{
1830+ m_samplesRead = -1;
1831+ m_samplesWrite = 0;
1832+}
1833+
1834+int AxisVelocityCalculator::numSamples() const
1835+{
1836+ if (m_samplesRead == -1) {
1837+ return 0;
1838+ } else {
1839+ if (m_samplesWrite == 0) {
1840+ /* consider only what's to the right of m_samplesRead (including himself) */
1841+ return MAX_SAMPLES - m_samplesRead;
1842+ } else if (m_samplesWrite == m_samplesRead) {
1843+ return MAX_SAMPLES; /* buffer is full */
1844+ } else if (m_samplesWrite < m_samplesRead) {
1845+ return (MAX_SAMPLES - m_samplesRead) + m_samplesWrite;
1846+ } else {
1847+ return m_samplesWrite - m_samplesRead;
1848+ }
1849+ }
1850+}
1851+
1852+void AxisVelocityCalculator::setTimeSource(const SharedTimeSource &timeSource)
1853+{
1854+ m_timeSource = timeSource;
1855+
1856+ if (numSamples() > 0) {
1857+ qWarning("AxisVelocityCalculator: changing time source while there are samples present.");
1858+ // Any existent samples are based on the old time source and are, therefore, incompatible
1859+ // with this new one.
1860+ reset();
1861+ }
1862+}
1863
1864=== added file 'src/app/unity8/plugins/Ubuntu/Gestures/AxisVelocityCalculator.h'
1865--- src/app/unity8/plugins/Ubuntu/Gestures/AxisVelocityCalculator.h 1970-01-01 00:00:00 +0000
1866+++ src/app/unity8/plugins/Ubuntu/Gestures/AxisVelocityCalculator.h 2015-03-20 16:26:56 +0000
1867@@ -0,0 +1,146 @@
1868+/*
1869+ * Copyright (C) 2013 - Canonical Ltd.
1870+ *
1871+ * This program is free software: you can redistribute it and/or modify it
1872+ * under the terms of the GNU Lesser General Public License, as
1873+ * published by the Free Software Foundation; either version 2.1 or 3.0
1874+ * of the License.
1875+ *
1876+ * This program is distributed in the hope that it will be useful, but
1877+ * WITHOUT ANY WARRANTY; without even the implied warranties of
1878+ * MERCHANTABILITY, SATISFACTORY QUALITY or FITNESS FOR A PARTICULAR
1879+ * PURPOSE. See the applicable version of the GNU Lesser General Public
1880+ * License for more details.
1881+ *
1882+ * You should have received a copy of both the GNU Lesser General Public
1883+ * License along with this program. If not, see <http://www.gnu.org/licenses/>
1884+ *
1885+ * Authored by: Daniel d'Andrada <daniel.dandrada@canonical.com>
1886+ */
1887+
1888+#ifndef VELOCITY_CALCULATOR_H
1889+#define VELOCITY_CALCULATOR_H
1890+
1891+#include "UbuntuGesturesQmlGlobal.h"
1892+#include <stdint.h>
1893+#include <QtCore/QObject>
1894+#include "TimeSource.h"
1895+
1896+/*
1897+ Estimates the current velocity of a finger based on recent movement along an axis
1898+
1899+ Taking an estimate from a reasonable number of samples, instead of only
1900+ from its last movement, removes wild variations in velocity caused
1901+ by the jitter normally present in input from a touchscreen.
1902+
1903+ Usage example:
1904+
1905+ AxisVelocityCalculator {
1906+ id: velocityCalculator
1907+ trackedPosition: myMouseArea.mouseX
1908+ }
1909+
1910+ MouseArea {
1911+ id: myMouseArea
1912+
1913+ onReleased: {
1914+ console.log("Drag velocity along the X axis before release was: "
1915+ + velocityCalculator.calculate())
1916+ }
1917+ }
1918+ */
1919+class UBUNTUGESTURESQML_EXPORT AxisVelocityCalculator : public QObject
1920+{
1921+ Q_OBJECT
1922+
1923+ /*
1924+ Position whose movement will be tracked to calculate its velocity
1925+ */
1926+ Q_PROPERTY(qreal trackedPosition READ trackedPosition WRITE setTrackedPosition
1927+ NOTIFY trackedPositionChanged)
1928+public:
1929+
1930+ /*
1931+ Regular, simple, constructor
1932+ */
1933+ AxisVelocityCalculator(QObject *parent = 0);
1934+
1935+ /*
1936+ Constructor that takes a TimeSource
1937+ */
1938+ AxisVelocityCalculator(const UbuntuGestures::SharedTimeSource &timeSource, QObject *parent = 0);
1939+
1940+ virtual ~AxisVelocityCalculator();
1941+
1942+ qreal trackedPosition() const;
1943+ void setTrackedPosition(qreal value);
1944+
1945+ /*
1946+ Calculates the finger velocity, in axis units/millisecond
1947+ */
1948+ Q_INVOKABLE qreal calculate();
1949+
1950+ /*
1951+ Removes all stored movements from previous calls to setTrackedPosition()
1952+ */
1953+ Q_INVOKABLE void reset();
1954+
1955+ int numSamples() const;
1956+
1957+ /*
1958+ Replaces the TimeSource with the given one. Useful for testing purposes.
1959+ */
1960+ void setTimeSource(const UbuntuGestures::SharedTimeSource &timeSource);
1961+
1962+ /*
1963+ The minimum amount of samples needed for a velocity calculation.
1964+ */
1965+ static const int MIN_SAMPLES_NEEDED = 2;
1966+
1967+ /*
1968+ Maximum number of movement samples stored
1969+ */
1970+ static const int MAX_SAMPLES = 50;
1971+
1972+ /*
1973+ Age of the oldest sample considered in the velocity calculations, in
1974+ milliseconds, compared to the most recent one.
1975+ */
1976+ static const int AGE_OLDEST_SAMPLE = 100;
1977+
1978+Q_SIGNALS:
1979+ void trackedPositionChanged(qreal value);
1980+
1981+private:
1982+
1983+ /*
1984+ Inform that trackedPosition remained motionless since the time it was
1985+ last changed.
1986+
1987+ It's the same as calling setTrackedPosition(trackedPosition())
1988+ */
1989+ void updateIdleTime();
1990+
1991+ /*
1992+ How much the finger has moved since processMovement() was last called.
1993+ */
1994+ void processMovement(qreal movement);
1995+
1996+ class Sample
1997+ {
1998+ public:
1999+ qreal mov; /* movement distance since last sample */
2000+ qint64 time; /* time, in milliseconds */
2001+ };
2002+
2003+ /* a circular buffer of samples */
2004+ Sample m_samples[MAX_SAMPLES];
2005+ int m_samplesRead; /* index of the oldest sample available. -1 if buffer is empty */
2006+ int m_samplesWrite; /* index where the next sample will be written */
2007+
2008+ UbuntuGestures::SharedTimeSource m_timeSource;
2009+
2010+ qreal m_trackedPosition;
2011+};
2012+
2013+#endif // VELOCITY_CALCULATOR_H
2014
2015=== added file 'src/app/unity8/plugins/Ubuntu/Gestures/CMakeLists.txt'
2016--- src/app/unity8/plugins/Ubuntu/Gestures/CMakeLists.txt 1970-01-01 00:00:00 +0000
2017+++ src/app/unity8/plugins/Ubuntu/Gestures/CMakeLists.txt 2015-03-20 16:26:56 +0000
2018@@ -0,0 +1,40 @@
2019+# in order to include Qt's private headers
2020+remove_definitions(-DQT_NO_KEYWORDS)
2021+
2022+set(UbuntuGesturesQml_SOURCES
2023+# plugin.cpp
2024+ AxisVelocityCalculator.cpp
2025+ Direction.cpp
2026+ DirectionalDragArea.cpp
2027+ PressedOutsideNotifier.cpp
2028+ TimeSource.cpp
2029+ TouchDispatcher.cpp
2030+ TouchGate.cpp
2031+)
2032+
2033+add_definitions(-DUBUNTUGESTURESQML_LIBRARY)
2034+
2035+add_library(UbuntuGesturesQml STATIC ${UbuntuGesturesQml_SOURCES})
2036+target_link_libraries(UbuntuGesturesQml UbuntuGestures)
2037+
2038+qt5_use_modules(UbuntuGesturesQml Core Quick)
2039+
2040+# So that Foo.cpp can #include "Foo.moc"
2041+include_directories(${CMAKE_CURRENT_BINARY_DIR})
2042+
2043+include_directories(${unity8_SOURCE_DIR}/libs/UbuntuGestures)
2044+
2045+# There's no cmake var for v8 include path :-/ so create one
2046+LIST(GET Qt5Core_INCLUDE_DIRS 0 QtCoreDir0)
2047+SET(Qt5V8_PRIVATE_INCLUDE_DIR ${QtCoreDir0}/QtV8/${Qt5Core_VERSION_STRING}/QtV8)
2048+
2049+# DANGER! DANGER! Using Qt's private API!
2050+include_directories(
2051+ ${Qt5Qml_PRIVATE_INCLUDE_DIRS}
2052+ ${Qt5Quick_INCLUDE_DIRS}
2053+ ${Qt5Quick_PRIVATE_INCLUDE_DIRS}
2054+ ${Qt5V8_PRIVATE_INCLUDE_DIR}
2055+)
2056+
2057+#add_unity8_plugin(Ubuntu.Gestures 0.1 Ubuntu/Gestures TARGETS UbuntuGesturesQml)
2058+# TODO
2059
2060=== added file 'src/app/unity8/plugins/Ubuntu/Gestures/Damper.h'
2061--- src/app/unity8/plugins/Ubuntu/Gestures/Damper.h 1970-01-01 00:00:00 +0000
2062+++ src/app/unity8/plugins/Ubuntu/Gestures/Damper.h 2015-03-20 16:26:56 +0000
2063@@ -0,0 +1,87 @@
2064+/*
2065+ * Copyright (C) 2013 Canonical, Ltd.
2066+ *
2067+ * This program is free software; you can redistribute it and/or modify
2068+ * it under the terms of the GNU General Public License as published by
2069+ * the Free Software Foundation; version 3.
2070+ *
2071+ * This program is distributed in the hope that it will be useful,
2072+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2073+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2074+ * GNU General Public License for more details.
2075+ *
2076+ * You should have received a copy of the GNU General Public License
2077+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2078+ */
2079+
2080+#ifndef UBUNTU_GESTURES_DAMPER_H
2081+#define UBUNTU_GESTURES_DAMPER_H
2082+
2083+#include <QtCore/QPointF>
2084+
2085+/*
2086+ Decreases the oscillations of a value along an axis.
2087+ */
2088+template <class Type> class Damper {
2089+public:
2090+ Damper() : m_value(0), m_maxDelta(0) { }
2091+
2092+ // Maximum delta between the raw value and its dampened counterpart.
2093+ void setMaxDelta(Type maxDelta) {
2094+ if (maxDelta < 0) qFatal("Damper::maxDelta must be a positive number.");
2095+ m_maxDelta = maxDelta;
2096+ }
2097+ Type maxDelta() const { return m_maxDelta; }
2098+
2099+ void reset(Type value) {
2100+ m_value = value;
2101+ }
2102+
2103+ Type update(Type value) {
2104+ Type delta = value - m_value;
2105+ if (delta > 0 && delta > m_maxDelta) {
2106+ m_value += delta - m_maxDelta;
2107+ } else if (delta < 0 && delta < -m_maxDelta) {
2108+ m_value += delta + m_maxDelta;
2109+ }
2110+
2111+ return m_value;
2112+ }
2113+
2114+ Type value() const { return m_value; }
2115+
2116+private:
2117+ Type m_value;
2118+ Type m_maxDelta;
2119+};
2120+
2121+/*
2122+ A point that has its movement dampened.
2123+ */
2124+class DampedPointF {
2125+public:
2126+ void setMaxDelta(qreal maxDelta) {
2127+ m_x.setMaxDelta(maxDelta);
2128+ m_y.setMaxDelta(maxDelta);
2129+ }
2130+
2131+ qreal maxDelta() const { return m_x.maxDelta(); }
2132+
2133+ void reset(const QPointF &point) {
2134+ m_x.reset(point.x());
2135+ m_y.reset(point.y());
2136+ }
2137+
2138+ void update(const QPointF &point) {
2139+ m_x.update(point.x());
2140+ m_y.update(point.y());
2141+ }
2142+
2143+ qreal x() const { return m_x.value(); }
2144+ qreal y() const { return m_y.value(); }
2145+private:
2146+ Damper<qreal> m_x;
2147+ Damper<qreal> m_y;
2148+};
2149+
2150+#endif // UBUNTU_GESTURES_DAMPER_H
2151
2152=== added file 'src/app/unity8/plugins/Ubuntu/Gestures/Direction.cpp'
2153--- src/app/unity8/plugins/Ubuntu/Gestures/Direction.cpp 1970-01-01 00:00:00 +0000
2154+++ src/app/unity8/plugins/Ubuntu/Gestures/Direction.cpp 2015-03-20 16:26:56 +0000
2155@@ -0,0 +1,36 @@
2156+/*
2157+ * Copyright (C) 2013 Canonical, Ltd.
2158+ *
2159+ * This program is free software; you can redistribute it and/or modify
2160+ * it under the terms of the GNU General Public License as published by
2161+ * the Free Software Foundation; version 3.
2162+ *
2163+ * This program is distributed in the hope that it will be useful,
2164+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2165+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2166+ * GNU General Public License for more details.
2167+ *
2168+ * You should have received a copy of the GNU General Public License
2169+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2170+ */
2171+
2172+#include "Direction.h"
2173+
2174+bool Direction::isHorizontal(Direction::Type type)
2175+{
2176+ return type == Direction::Leftwards
2177+ || type == Direction::Rightwards
2178+ || type == Direction::Horizontal;
2179+}
2180+
2181+bool Direction::isVertical(Direction::Type type)
2182+{
2183+ return type == Direction::Upwards || type == Direction::Downwards;
2184+}
2185+
2186+bool Direction::isPositive(Direction::Type type)
2187+{
2188+ return type == Rightwards
2189+ || type == Downwards
2190+ || type == Horizontal;
2191+}
2192
2193=== added file 'src/app/unity8/plugins/Ubuntu/Gestures/Direction.h'
2194--- src/app/unity8/plugins/Ubuntu/Gestures/Direction.h 1970-01-01 00:00:00 +0000
2195+++ src/app/unity8/plugins/Ubuntu/Gestures/Direction.h 2015-03-20 16:26:56 +0000
2196@@ -0,0 +1,45 @@
2197+/*
2198+ * Copyright (C) 2013 Canonical, Ltd.
2199+ *
2200+ * This program is free software; you can redistribute it and/or modify
2201+ * it under the terms of the GNU General Public License as published by
2202+ * the Free Software Foundation; version 3.
2203+ *
2204+ * This program is distributed in the hope that it will be useful,
2205+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2206+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2207+ * GNU General Public License for more details.
2208+ *
2209+ * You should have received a copy of the GNU General Public License
2210+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2211+ */
2212+
2213+#ifndef DIRECTION_H
2214+#define DIRECTION_H
2215+
2216+#include "UbuntuGesturesQmlGlobal.h"
2217+#include <QObject>
2218+
2219+/*
2220+ A Direction enum wrapper so that we can do things like "direction: Direction.Righwards"
2221+ from QML.
2222+ */
2223+class UBUNTUGESTURESQML_EXPORT Direction : public QObject {
2224+ Q_OBJECT
2225+ Q_ENUMS(Type)
2226+
2227+public:
2228+ enum Type {
2229+ Rightwards, // Along the positive direction of the X axis
2230+ Leftwards, // Along the negative direction of the X axis
2231+ Downwards, // Along the positive direction of the Y axis
2232+ Upwards, // Along the negative direction of the Y axis
2233+ Horizontal // Along the X axis, in any direction
2234+ };
2235+
2236+ Q_INVOKABLE static bool isHorizontal(Direction::Type type);
2237+ Q_INVOKABLE static bool isVertical(Direction::Type type);
2238+ Q_INVOKABLE static bool isPositive(Direction::Type type);
2239+};
2240+
2241+#endif // DIRECTION_H
2242
2243=== added file 'src/app/unity8/plugins/Ubuntu/Gestures/DirectionalDragArea.cpp'
2244--- src/app/unity8/plugins/Ubuntu/Gestures/DirectionalDragArea.cpp 1970-01-01 00:00:00 +0000
2245+++ src/app/unity8/plugins/Ubuntu/Gestures/DirectionalDragArea.cpp 2015-03-20 16:26:56 +0000
2246@@ -0,0 +1,886 @@
2247+/*
2248+ * Copyright (C) 2013-2014 Canonical, Ltd.
2249+ *
2250+ * This program is free software; you can redistribute it and/or modify
2251+ * it under the terms of the GNU General Public License as published by
2252+ * the Free Software Foundation; version 3.
2253+ *
2254+ * This program is distributed in the hope that it will be useful,
2255+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2256+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2257+ * GNU General Public License for more details.
2258+ *
2259+ * You should have received a copy of the GNU General Public License
2260+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2261+ */
2262+
2263+#define ACTIVETOUCHESINFO_DEBUG 0
2264+#define DIRECTIONALDRAGAREA_DEBUG 0
2265+
2266+#include "DirectionalDragArea.h"
2267+
2268+#include <QQuickWindow>
2269+#include <QtCore/qmath.h>
2270+#include <QDebug>
2271+
2272+#pragma GCC diagnostic push
2273+#pragma GCC diagnostic ignored "-pedantic"
2274+#include <private/qquickwindow_p.h>
2275+#pragma GCC diagnostic pop
2276+
2277+// local
2278+#include "TouchOwnershipEvent.h"
2279+#include "TouchRegistry.h"
2280+#include "UnownedTouchEvent.h"
2281+
2282+using namespace UbuntuGestures;
2283+
2284+#if DIRECTIONALDRAGAREA_DEBUG
2285+#define ddaDebug(params) qDebug().nospace() << "[DDA(" << qPrintable(objectName()) << ")] " << params
2286+#include "DebugHelpers.h"
2287+
2288+namespace {
2289+const char *statusToString(DirectionalDragArea::Status status)
2290+{
2291+ if (status == DirectionalDragArea::WaitingForTouch) {
2292+ return "WaitingForTouch";
2293+ } else if (status == DirectionalDragArea::Undecided) {
2294+ return "Undecided";
2295+ } else {
2296+ return "Recognized";
2297+ }
2298+}
2299+
2300+} // namespace {
2301+#else // DIRECTIONALDRAGAREA_DEBUG
2302+#define ddaDebug(params) ((void)0)
2303+#endif // DIRECTIONALDRAGAREA_DEBUG
2304+
2305+
2306+DirectionalDragArea::DirectionalDragArea(QQuickItem *parent)
2307+ : QQuickItem(parent)
2308+ , m_status(WaitingForTouch)
2309+ , m_sceneDistance(0)
2310+ , m_touchId(-1)
2311+ , m_direction(Direction::Rightwards)
2312+ , m_wideningAngle(0)
2313+ , m_wideningFactor(0)
2314+ , m_distanceThreshold(0)
2315+ , m_distanceThresholdSquared(0.)
2316+ , m_minSpeed(0)
2317+ , m_maxSilenceTime(200)
2318+ , m_silenceTime(0)
2319+ , m_compositionTime(60)
2320+ , m_numSamplesOnLastSpeedCheck(0)
2321+ , m_recognitionTimer(0)
2322+ , m_velocityCalculator(0)
2323+ , m_timeSource(new RealTimeSource)
2324+ , m_activeTouches(m_timeSource)
2325+{
2326+ setRecognitionTimer(new Timer(this));
2327+ m_recognitionTimer->setInterval(60);
2328+ m_recognitionTimer->setSingleShot(false);
2329+
2330+ m_velocityCalculator = new AxisVelocityCalculator(this);
2331+
2332+ connect(this, &QQuickItem::enabledChanged, this, &DirectionalDragArea::giveUpIfDisabledOrInvisible);
2333+ connect(this, &QQuickItem::visibleChanged, this, &DirectionalDragArea::giveUpIfDisabledOrInvisible);
2334+}
2335+
2336+Direction::Type DirectionalDragArea::direction() const
2337+{
2338+ return m_direction;
2339+}
2340+
2341+void DirectionalDragArea::setDirection(Direction::Type direction)
2342+{
2343+ if (direction != m_direction) {
2344+ m_direction = direction;
2345+ Q_EMIT directionChanged(m_direction);
2346+ }
2347+}
2348+
2349+void DirectionalDragArea::setMaxDeviation(qreal value)
2350+{
2351+ if (m_dampedScenePos.maxDelta() != value) {
2352+ m_dampedScenePos.setMaxDelta(value);
2353+ Q_EMIT maxDeviationChanged(value);
2354+ }
2355+}
2356+
2357+qreal DirectionalDragArea::wideningAngle() const
2358+{
2359+ return m_wideningAngle;
2360+}
2361+
2362+void DirectionalDragArea::setWideningAngle(qreal angle)
2363+{
2364+ if (angle == m_wideningAngle)
2365+ return;
2366+
2367+ m_wideningAngle = angle;
2368+
2369+ // wideningFactor = pow(cosine(angle), 2)
2370+ {
2371+ qreal angleRadians = angle * M_PI / 180.0;
2372+ m_wideningFactor = qCos(angleRadians);
2373+ m_wideningFactor = m_wideningFactor * m_wideningFactor;
2374+ }
2375+
2376+ Q_EMIT wideningAngleChanged(angle);
2377+}
2378+
2379+void DirectionalDragArea::setDistanceThreshold(qreal value)
2380+{
2381+ if (m_distanceThreshold != value) {
2382+ m_distanceThreshold = value;
2383+ m_distanceThresholdSquared = m_distanceThreshold * m_distanceThreshold;
2384+ Q_EMIT distanceThresholdChanged(value);
2385+ }
2386+}
2387+
2388+void DirectionalDragArea::setMinSpeed(qreal value)
2389+{
2390+ if (m_minSpeed != value) {
2391+ m_minSpeed = value;
2392+ Q_EMIT minSpeedChanged(value);
2393+ }
2394+}
2395+
2396+void DirectionalDragArea::setMaxSilenceTime(int value)
2397+{
2398+ if (m_maxSilenceTime != value) {
2399+ m_maxSilenceTime = value;
2400+ Q_EMIT maxSilenceTimeChanged(value);
2401+ }
2402+}
2403+
2404+void DirectionalDragArea::setCompositionTime(int value)
2405+{
2406+ if (m_compositionTime != value) {
2407+ m_compositionTime = value;
2408+ Q_EMIT compositionTimeChanged(value);
2409+ }
2410+}
2411+
2412+void DirectionalDragArea::setRecognitionTimer(UbuntuGestures::AbstractTimer *timer)
2413+{
2414+ int interval = 0;
2415+ bool timerWasRunning = false;
2416+ bool wasSingleShot = false;
2417+
2418+ // can be null when called from the constructor
2419+ if (m_recognitionTimer) {
2420+ interval = m_recognitionTimer->interval();
2421+ timerWasRunning = m_recognitionTimer->isRunning();
2422+ if (m_recognitionTimer->parent() == this) {
2423+ delete m_recognitionTimer;
2424+ }
2425+ }
2426+
2427+ m_recognitionTimer = timer;
2428+ timer->setInterval(interval);
2429+ timer->setSingleShot(wasSingleShot);
2430+ connect(timer, &UbuntuGestures::AbstractTimer::timeout,
2431+ this, &DirectionalDragArea::checkSpeed);
2432+ if (timerWasRunning) {
2433+ m_recognitionTimer->start();
2434+ }
2435+}
2436+
2437+void DirectionalDragArea::setTimeSource(const SharedTimeSource &timeSource)
2438+{
2439+ m_timeSource = timeSource;
2440+ m_velocityCalculator->setTimeSource(timeSource);
2441+ m_activeTouches.m_timeSource = timeSource;
2442+}
2443+
2444+qreal DirectionalDragArea::distance() const
2445+{
2446+ if (Direction::isHorizontal(m_direction)) {
2447+ return m_previousPos.x() - m_startPos.x();
2448+ } else {
2449+ return m_previousPos.y() - m_startPos.y();
2450+ }
2451+}
2452+
2453+void DirectionalDragArea::updateSceneDistance()
2454+{
2455+ QPointF totalMovement = m_previousScenePos - m_startScenePos;
2456+ m_sceneDistance = projectOntoDirectionVector(totalMovement);
2457+}
2458+
2459+qreal DirectionalDragArea::sceneDistance() const
2460+{
2461+ return m_sceneDistance;
2462+}
2463+
2464+qreal DirectionalDragArea::touchX() const
2465+{
2466+ return m_previousPos.x();
2467+}
2468+
2469+qreal DirectionalDragArea::touchY() const
2470+{
2471+ return m_previousPos.y();
2472+}
2473+
2474+qreal DirectionalDragArea::touchSceneX() const
2475+{
2476+ return m_previousScenePos.x();
2477+}
2478+
2479+qreal DirectionalDragArea::touchSceneY() const
2480+{
2481+ return m_previousScenePos.y();
2482+}
2483+
2484+bool DirectionalDragArea::event(QEvent *event)
2485+{
2486+ if (event->type() == TouchOwnershipEvent::touchOwnershipEventType()) {
2487+ touchOwnershipEvent(static_cast<TouchOwnershipEvent *>(event));
2488+ return true;
2489+ } else if (event->type() == UnownedTouchEvent::unownedTouchEventType()) {
2490+ unownedTouchEvent(static_cast<UnownedTouchEvent *>(event));
2491+ return true;
2492+ } else {
2493+ return QQuickItem::event(event);
2494+ }
2495+}
2496+
2497+void DirectionalDragArea::touchOwnershipEvent(TouchOwnershipEvent *event)
2498+{
2499+ if (event->gained()) {
2500+ QVector<int> ids;
2501+ ids.append(event->touchId());
2502+ ddaDebug("grabbing touch");
2503+ grabTouchPoints(ids);
2504+
2505+ // Work around for Qt bug. If we grab a touch that is being used for mouse pointer
2506+ // emulation it will cause the emulation logic to go nuts.
2507+ // Thus we have to also grab the mouse in this case.
2508+ //
2509+ // The fix for this bug has landed in Qt 5.4 (https://codereview.qt-project.org/96887)
2510+ // TODO: Remove this workaround once we start using Qt 5.4
2511+ if (window()) {
2512+ QQuickWindowPrivate *windowPrivate = QQuickWindowPrivate::get(window());
2513+ if (windowPrivate->touchMouseId == event->touchId() && window()->mouseGrabberItem()) {
2514+ ddaDebug("removing mouse grabber");
2515+ window()->mouseGrabberItem()->ungrabMouse();
2516+ }
2517+ }
2518+ } else {
2519+ // We still wanna know when it ends for keeping the composition time window up-to-date
2520+ TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
2521+
2522+ setStatus(WaitingForTouch);
2523+ }
2524+}
2525+
2526+void DirectionalDragArea::unownedTouchEvent(UnownedTouchEvent *unownedTouchEvent)
2527+{
2528+ QTouchEvent *event = unownedTouchEvent->touchEvent();
2529+
2530+ Q_ASSERT(!event->touchPointStates().testFlag(Qt::TouchPointPressed));
2531+
2532+ ddaDebug("Unowned " << m_timeSource->msecsSinceReference() << " " << qPrintable(touchEventToString(event)));
2533+
2534+ switch (m_status) {
2535+ case WaitingForTouch:
2536+ // do nothing
2537+ break;
2538+ case Undecided:
2539+ Q_ASSERT(isEnabled() && isVisible());
2540+ unownedTouchEvent_undecided(unownedTouchEvent);
2541+ break;
2542+ default: // Recognized:
2543+ // do nothing
2544+ break;
2545+ }
2546+
2547+ m_activeTouches.update(event);
2548+}
2549+
2550+void DirectionalDragArea::unownedTouchEvent_undecided(UnownedTouchEvent *unownedTouchEvent)
2551+{
2552+ const QTouchEvent::TouchPoint *touchPoint = fetchTargetTouchPoint(unownedTouchEvent->touchEvent());
2553+ if (!touchPoint) {
2554+ qCritical() << "DirectionalDragArea[status=Undecided]: touch " << m_touchId
2555+ << "missing from UnownedTouchEvent without first reaching state Qt::TouchPointReleased. "
2556+ "Considering it as released.";
2557+
2558+ TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
2559+ setStatus(WaitingForTouch);
2560+ return;
2561+ }
2562+
2563+ const QPointF &touchScenePos = touchPoint->scenePos();
2564+
2565+ if (touchPoint->state() == Qt::TouchPointReleased) {
2566+ // touch has ended before recognition concluded
2567+ ddaDebug("Touch has ended before recognition concluded");
2568+ TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
2569+ emitSignalIfTapped();
2570+ setStatus(WaitingForTouch);
2571+ return;
2572+ }
2573+
2574+ m_previousDampedScenePos.setX(m_dampedScenePos.x());
2575+ m_previousDampedScenePos.setY(m_dampedScenePos.y());
2576+ m_dampedScenePos.update(touchScenePos);
2577+ updateVelocityCalculator(touchScenePos);
2578+
2579+ if (!pointInsideAllowedArea()) {
2580+ ddaDebug("Rejecting gesture because touch point is outside allowed area.");
2581+ TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
2582+ // We still wanna know when it ends for keeping the composition time window up-to-date
2583+ TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
2584+ setStatus(WaitingForTouch);
2585+ return;
2586+ }
2587+
2588+ if (!movingInRightDirection()) {
2589+ ddaDebug("Rejecting gesture because touch point is moving in the wrong direction.");
2590+ TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
2591+ // We still wanna know when it ends for keeping the composition time window up-to-date
2592+ TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
2593+ setStatus(WaitingForTouch);
2594+ return;
2595+ }
2596+
2597+ setPreviousPos(touchPoint->pos());
2598+ setPreviousScenePos(touchScenePos);
2599+
2600+ if (isWithinTouchCompositionWindow()) {
2601+ // There's still time for some new touch to appear and ruin our party as it would be combined
2602+ // with our m_touchId one and therefore deny the possibility of a single-finger gesture.
2603+ ddaDebug("Sill within composition window. Let's wait more.");
2604+ return;
2605+ }
2606+
2607+ if (movedFarEnough(touchScenePos)) {
2608+ TouchRegistry::instance()->requestTouchOwnership(m_touchId, this);
2609+ setStatus(Recognized);
2610+ } else {
2611+ ddaDebug("Didn't move far enough yet. Let's wait more.");
2612+ }
2613+}
2614+
2615+void DirectionalDragArea::touchEvent(QTouchEvent *event)
2616+{
2617+ // TODO: Consider when more than one touch starts in the same event (although it's not possible
2618+ // with Mir's android-input). Have to track them all. Consider it a plus/bonus.
2619+
2620+ ddaDebug(m_timeSource->msecsSinceReference() << " " << qPrintable(touchEventToString(event)));
2621+
2622+ if (!isEnabled() || !isVisible()) {
2623+ QQuickItem::touchEvent(event);
2624+ return;
2625+ }
2626+
2627+ switch (m_status) {
2628+ case WaitingForTouch:
2629+ touchEvent_absent(event);
2630+ break;
2631+ case Undecided:
2632+ touchEvent_undecided(event);
2633+ break;
2634+ default: // Recognized:
2635+ touchEvent_recognized(event);
2636+ break;
2637+ }
2638+
2639+ m_activeTouches.update(event);
2640+}
2641+
2642+void DirectionalDragArea::touchEvent_absent(QTouchEvent *event)
2643+{
2644+ // TODO: accept/reject is for the whole event, not per touch id. See how that affects us.
2645+
2646+ if (!event->touchPointStates().testFlag(Qt::TouchPointPressed)) {
2647+ // Nothing to see here. No touch starting in this event.
2648+ return;
2649+ }
2650+
2651+ // to be proven wrong, if that's the case
2652+ bool allGood = true;
2653+
2654+ if (isWithinTouchCompositionWindow()) {
2655+ // too close to the last touch start. So we consider them as starting roughly at the same time.
2656+ // Can't be a single-touch gesture.
2657+ ddaDebug("A new touch point came in but we're still within time composition window. Ignoring it.");
2658+ allGood = false;
2659+ }
2660+
2661+ const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
2662+
2663+ const QTouchEvent::TouchPoint *newTouchPoint = nullptr;
2664+ for (int i = 0; i < touchPoints.count() && allGood; ++i) {
2665+ const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i);
2666+ if (touchPoint.state() == Qt::TouchPointPressed) {
2667+ if (newTouchPoint) {
2668+ // more than one touch starting in this QTouchEvent. Can't be a single-touch gesture
2669+ allGood = false;
2670+ } else {
2671+ // that's our candidate
2672+ m_touchId = touchPoint.id();
2673+ newTouchPoint = &touchPoint;
2674+ }
2675+ }
2676+ }
2677+
2678+ if (allGood) {
2679+ Q_ASSERT(newTouchPoint);
2680+
2681+ m_startPos = newTouchPoint->pos();
2682+ m_startScenePos = newTouchPoint->scenePos();
2683+ m_touchId = newTouchPoint->id();
2684+ m_dampedScenePos.reset(m_startScenePos);
2685+ m_velocityCalculator->setTrackedPosition(0.);
2686+ m_velocityCalculator->reset();
2687+ m_numSamplesOnLastSpeedCheck = 0;
2688+ m_silenceTime = 0;
2689+ setPreviousPos(m_startPos);
2690+ setPreviousScenePos(m_startScenePos);
2691+ updateSceneDirectionVector();
2692+
2693+ if (recognitionIsDisabled()) {
2694+ // Behave like a dumb TouchArea
2695+ ddaDebug("Gesture recognition is disabled. Requesting touch ownership immediately.");
2696+ TouchRegistry::instance()->requestTouchOwnership(m_touchId, this);
2697+ setStatus(Recognized);
2698+ event->accept();
2699+ } else {
2700+ // just monitor the touch points for now.
2701+ TouchRegistry::instance()->addCandidateOwnerForTouch(m_touchId, this);
2702+
2703+ setStatus(Undecided);
2704+ // Let the item below have it. We will monitor it and grab it later if a gesture
2705+ // gets recognized.
2706+ event->ignore();
2707+ }
2708+ } else {
2709+ watchPressedTouchPoints(touchPoints);
2710+ event->ignore();
2711+ }
2712+}
2713+
2714+void DirectionalDragArea::touchEvent_undecided(QTouchEvent *event)
2715+{
2716+ Q_ASSERT(event->type() == QEvent::TouchBegin);
2717+ Q_ASSERT(fetchTargetTouchPoint(event) == nullptr);
2718+
2719+ // We're not interested in new touch points. We already have our candidate (m_touchId).
2720+ // But we do want to know when those new touches end for keeping the composition time
2721+ // window up-to-date
2722+ event->ignore();
2723+ watchPressedTouchPoints(event->touchPoints());
2724+
2725+ if (event->touchPointStates().testFlag(Qt::TouchPointPressed) && isWithinTouchCompositionWindow()) {
2726+ // multi-finger drags are not accepted
2727+ ddaDebug("Multi-finger drags are not accepted");
2728+
2729+ TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
2730+ // We still wanna know when it ends for keeping the composition time window up-to-date
2731+ TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
2732+
2733+ setStatus(WaitingForTouch);
2734+ }
2735+}
2736+
2737+void DirectionalDragArea::touchEvent_recognized(QTouchEvent *event)
2738+{
2739+ const QTouchEvent::TouchPoint *touchPoint = fetchTargetTouchPoint(event);
2740+
2741+ if (!touchPoint) {
2742+ qCritical() << "DirectionalDragArea[status=Recognized]: touch " << m_touchId
2743+ << "missing from QTouchEvent without first reaching state Qt::TouchPointReleased. "
2744+ "Considering it as released.";
2745+ setStatus(WaitingForTouch);
2746+ } else {
2747+ setPreviousPos(touchPoint->pos());
2748+ setPreviousScenePos(touchPoint->scenePos());
2749+
2750+ if (touchPoint->state() == Qt::TouchPointReleased) {
2751+ emitSignalIfTapped();
2752+ setStatus(WaitingForTouch);
2753+ }
2754+ }
2755+}
2756+
2757+void DirectionalDragArea::watchPressedTouchPoints(const QList<QTouchEvent::TouchPoint> &touchPoints)
2758+{
2759+ for (int i = 0; i < touchPoints.count(); ++i) {
2760+ const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i);
2761+ if (touchPoint.state() == Qt::TouchPointPressed) {
2762+ TouchRegistry::instance()->addTouchWatcher(touchPoint.id(), this);
2763+ }
2764+ }
2765+}
2766+
2767+bool DirectionalDragArea::recognitionIsDisabled() const
2768+{
2769+ return distanceThreshold() <= 0 && compositionTime() <= 0;
2770+}
2771+
2772+void DirectionalDragArea::emitSignalIfTapped()
2773+{
2774+ qint64 touchDuration = m_timeSource->msecsSinceReference() - m_activeTouches.touchStartTime(m_touchId);
2775+ if (touchDuration <= maxTapDuration()) {
2776+ Q_EMIT tapped();
2777+ }
2778+}
2779+
2780+const QTouchEvent::TouchPoint *DirectionalDragArea::fetchTargetTouchPoint(QTouchEvent *event)
2781+{
2782+ const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
2783+ const QTouchEvent::TouchPoint *touchPoint = 0;
2784+ for (int i = 0; i < touchPoints.size(); ++i) {
2785+ if (touchPoints.at(i).id() == m_touchId) {
2786+ touchPoint = &touchPoints.at(i);
2787+ break;
2788+ }
2789+ }
2790+ return touchPoint;
2791+}
2792+
2793+bool DirectionalDragArea::pointInsideAllowedArea() const
2794+{
2795+ // NB: Using squared values to avoid computing the square root to find
2796+ // the length totalMovement
2797+
2798+ QPointF totalMovement(m_dampedScenePos.x() - m_startScenePos.x(),
2799+ m_dampedScenePos.y() - m_startScenePos.y());
2800+
2801+ qreal squaredTotalMovSize = totalMovement.x() * totalMovement.x() +
2802+ totalMovement.y() * totalMovement.y();
2803+
2804+ if (squaredTotalMovSize == 0.) {
2805+ // didn't move
2806+ return true;
2807+ }
2808+
2809+ qreal projectedMovement = projectOntoDirectionVector(totalMovement);
2810+
2811+
2812+ qreal cosineAngleSquared = (projectedMovement * projectedMovement) / squaredTotalMovSize;
2813+
2814+ // Same as:
2815+ // angle_between_movement_vector_and_gesture_direction_vector <= widening_angle
2816+ return cosineAngleSquared >= m_wideningFactor;
2817+}
2818+
2819+bool DirectionalDragArea::movingInRightDirection() const
2820+{
2821+ if (m_direction == Direction::Horizontal) {
2822+ return true;
2823+ } else {
2824+ QPointF movementVector(m_dampedScenePos.x() - m_previousDampedScenePos.x(),
2825+ m_dampedScenePos.y() - m_previousDampedScenePos.y());
2826+
2827+ qreal scalarProjection = projectOntoDirectionVector(movementVector);
2828+
2829+ return scalarProjection >= 0.;
2830+ }
2831+}
2832+
2833+bool DirectionalDragArea::movedFarEnough(const QPointF &point) const
2834+{
2835+ if (m_distanceThreshold <= 0.) {
2836+ // distance threshold check is disabled
2837+ return true;
2838+ } else {
2839+ QPointF totalMovement(point.x() - m_startScenePos.x(),
2840+ point.y() - m_startScenePos.y());
2841+
2842+ qreal squaredTotalMovSize = totalMovement.x() * totalMovement.x() +
2843+ totalMovement.y() * totalMovement.y();
2844+
2845+ return squaredTotalMovSize > m_distanceThresholdSquared;
2846+ }
2847+}
2848+
2849+void DirectionalDragArea::checkSpeed()
2850+{
2851+ Q_ASSERT(m_status == Undecided);
2852+
2853+ if (m_velocityCalculator->numSamples() >= AxisVelocityCalculator::MIN_SAMPLES_NEEDED) {
2854+ qreal speed = qFabs(m_velocityCalculator->calculate());
2855+ qreal minSpeedMsecs = m_minSpeed / 1000.0;
2856+
2857+ if (speed < minSpeedMsecs) {
2858+ ddaDebug("Rejecting gesture because it's below minimum speed.");
2859+ TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
2860+ TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
2861+ setStatus(WaitingForTouch);
2862+ }
2863+ }
2864+
2865+ if (m_velocityCalculator->numSamples() == m_numSamplesOnLastSpeedCheck) {
2866+ m_silenceTime += m_recognitionTimer->interval();
2867+
2868+ if (m_silenceTime > m_maxSilenceTime) {
2869+ ddaDebug("Rejecting gesture because its silence time has been exceeded.");
2870+ TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
2871+ TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
2872+ setStatus(WaitingForTouch);
2873+ }
2874+ } else {
2875+ m_silenceTime = 0;
2876+ }
2877+ m_numSamplesOnLastSpeedCheck = m_velocityCalculator->numSamples();
2878+}
2879+
2880+void DirectionalDragArea::giveUpIfDisabledOrInvisible()
2881+{
2882+ if (!isEnabled() || !isVisible()) {
2883+ if (m_status == Undecided) {
2884+ TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
2885+ // We still wanna know when it ends for keeping the composition time window up-to-date
2886+ TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
2887+ }
2888+
2889+ if (m_status != WaitingForTouch) {
2890+ ddaDebug("Resetting status because got disabled or made invisible");
2891+ setStatus(WaitingForTouch);
2892+ }
2893+ }
2894+}
2895+
2896+void DirectionalDragArea::setStatus(DirectionalDragArea::Status newStatus)
2897+{
2898+ if (newStatus == m_status)
2899+ return;
2900+
2901+ DirectionalDragArea::Status oldStatus = m_status;
2902+
2903+ if (oldStatus == Undecided) {
2904+ m_recognitionTimer->stop();
2905+ }
2906+
2907+ m_status = newStatus;
2908+ Q_EMIT statusChanged(m_status);
2909+
2910+ ddaDebug(statusToString(oldStatus) << " -> " << statusToString(newStatus));
2911+
2912+ switch (newStatus) {
2913+ case WaitingForTouch:
2914+ Q_EMIT draggingChanged(false);
2915+ break;
2916+ case Undecided:
2917+ m_recognitionTimer->start();
2918+ Q_EMIT draggingChanged(true);
2919+ break;
2920+ case Recognized:
2921+ if (oldStatus == WaitingForTouch)
2922+ Q_EMIT draggingChanged(true);
2923+ break;
2924+ default:
2925+ // no-op
2926+ break;
2927+ }
2928+}
2929+
2930+void DirectionalDragArea::setPreviousPos(const QPointF &point)
2931+{
2932+ bool xChanged = m_previousPos.x() != point.x();
2933+ bool yChanged = m_previousPos.y() != point.y();
2934+
2935+ m_previousPos = point;
2936+
2937+ if (xChanged) {
2938+ Q_EMIT touchXChanged(point.x());
2939+ if (Direction::isHorizontal(m_direction))
2940+ Q_EMIT distanceChanged(distance());
2941+ }
2942+
2943+ if (yChanged) {
2944+ Q_EMIT touchYChanged(point.y());
2945+ if (Direction::isVertical(m_direction))
2946+ Q_EMIT distanceChanged(distance());
2947+ }
2948+}
2949+
2950+void DirectionalDragArea::setPreviousScenePos(const QPointF &point)
2951+{
2952+ bool xChanged = m_previousScenePos.x() != point.x();
2953+ bool yChanged = m_previousScenePos.y() != point.y();
2954+
2955+ if (!xChanged && !yChanged)
2956+ return;
2957+
2958+ qreal oldSceneDistance = sceneDistance();
2959+ m_previousScenePos = point;
2960+ updateSceneDistance();
2961+
2962+ if (oldSceneDistance != sceneDistance()) {
2963+ Q_EMIT sceneDistanceChanged(sceneDistance());
2964+ }
2965+
2966+ if (xChanged) {
2967+ Q_EMIT touchSceneXChanged(point.x());
2968+ }
2969+
2970+ if (yChanged) {
2971+ Q_EMIT touchSceneYChanged(point.y());
2972+ }
2973+}
2974+
2975+void DirectionalDragArea::updateVelocityCalculator(const QPointF &scenePos)
2976+{
2977+ QPointF totalSceneMovement = scenePos - m_startScenePos;
2978+
2979+ qreal scalarProjection = projectOntoDirectionVector(totalSceneMovement);
2980+
2981+ m_velocityCalculator->setTrackedPosition(scalarProjection);
2982+}
2983+
2984+bool DirectionalDragArea::isWithinTouchCompositionWindow()
2985+{
2986+ return
2987+ compositionTime() > 0 &&
2988+ !m_activeTouches.isEmpty() &&
2989+ m_timeSource->msecsSinceReference() <=
2990+ m_activeTouches.mostRecentStartTime() + (qint64)compositionTime();
2991+}
2992+
2993+//************************** ActiveTouchesInfo **************************
2994+
2995+DirectionalDragArea::ActiveTouchesInfo::ActiveTouchesInfo(const SharedTimeSource &timeSource)
2996+ : m_timeSource(timeSource)
2997+{
2998+}
2999+
3000+void DirectionalDragArea::ActiveTouchesInfo::update(QTouchEvent *event)
3001+{
3002+ if (!(event->touchPointStates() & (Qt::TouchPointPressed | Qt::TouchPointReleased))) {
3003+ // nothing to update
3004+ #if ACTIVETOUCHESINFO_DEBUG
3005+ qDebug("[DDA::ActiveTouchesInfo] Nothing to Update");
3006+ #endif
3007+ return;
3008+ }
3009+
3010+ const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
3011+ for (int i = 0; i < touchPoints.count(); ++i) {
3012+ const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i);
3013+ if (touchPoint.state() == Qt::TouchPointPressed) {
3014+ addTouchPoint(touchPoint.id());
3015+ } else if (touchPoint.state() == Qt::TouchPointReleased) {
3016+ removeTouchPoint(touchPoint.id());
3017+ }
3018+ }
3019+}
3020+
3021+#if ACTIVETOUCHESINFO_DEBUG
3022+QString DirectionalDragArea::ActiveTouchesInfo::toString()
3023+{
3024+ QString string = "(";
3025+
3026+ {
3027+ QTextStream stream(&string);
3028+ m_touchInfoPool.forEach([&](Pool<ActiveTouchInfo>::Iterator &touchInfo) {
3029+ stream << "(id=" << touchInfo->id << ",startTime=" << touchInfo->startTime << ")";
3030+ return true;
3031+ });
3032+ }
3033+
3034+ string.append(")");
3035+
3036+ return string;
3037+}
3038+#endif // ACTIVETOUCHESINFO_DEBUG
3039+
3040+void DirectionalDragArea::ActiveTouchesInfo::addTouchPoint(int touchId)
3041+{
3042+ ActiveTouchInfo &activeTouchInfo = m_touchInfoPool.getEmptySlot();
3043+ activeTouchInfo.id = touchId;
3044+ activeTouchInfo.startTime = m_timeSource->msecsSinceReference();
3045+
3046+ #if ACTIVETOUCHESINFO_DEBUG
3047+ qDebug() << "[DDA::ActiveTouchesInfo]" << qPrintable(toString());
3048+ #endif
3049+}
3050+
3051+qint64 DirectionalDragArea::ActiveTouchesInfo::touchStartTime(int touchId)
3052+{
3053+ qint64 result = -1;
3054+
3055+ m_touchInfoPool.forEach([&](Pool<ActiveTouchInfo>::Iterator &touchInfo) {
3056+ if (touchId == touchInfo->id) {
3057+ result = touchInfo->startTime;
3058+ return false;
3059+ } else {
3060+ return true;
3061+ }
3062+ });
3063+
3064+ Q_ASSERT(result != -1);
3065+ return result;
3066+}
3067+
3068+void DirectionalDragArea::ActiveTouchesInfo::removeTouchPoint(int touchId)
3069+{
3070+ m_touchInfoPool.forEach([&](Pool<ActiveTouchInfo>::Iterator &touchInfo) {
3071+ if (touchId == touchInfo->id) {
3072+ m_touchInfoPool.freeSlot(touchInfo);
3073+ return false;
3074+ } else {
3075+ return true;
3076+ }
3077+ });
3078+
3079+ #if ACTIVETOUCHESINFO_DEBUG
3080+ qDebug() << "[DDA::ActiveTouchesInfo]" << qPrintable(toString());
3081+ #endif
3082+}
3083+
3084+qint64 DirectionalDragArea::ActiveTouchesInfo::mostRecentStartTime()
3085+{
3086+ Q_ASSERT(!m_touchInfoPool.isEmpty());
3087+
3088+ qint64 highestStartTime = -1;
3089+
3090+ m_touchInfoPool.forEach([&](Pool<ActiveTouchInfo>::Iterator &activeTouchInfo) {
3091+ if (activeTouchInfo->startTime > highestStartTime) {
3092+ highestStartTime = activeTouchInfo->startTime;
3093+ }
3094+ return true;
3095+ });
3096+
3097+ return highestStartTime;
3098+}
3099+
3100+void DirectionalDragArea::updateSceneDirectionVector()
3101+{
3102+ QPointF localOrigin(0., 0.);
3103+ QPointF localDirection;
3104+ switch (m_direction) {
3105+ case Direction::Upwards:
3106+ localDirection.rx() = 0.;
3107+ localDirection.ry() = -1.;
3108+ break;
3109+ case Direction::Downwards:
3110+ localDirection.rx() = 0.;
3111+ localDirection.ry() = 1;
3112+ break;
3113+ case Direction::Leftwards:
3114+ localDirection.rx() = -1.;
3115+ localDirection.ry() = 0.;
3116+ break;
3117+ default: // Direction::Rightwards || Direction.Horizontal
3118+ localDirection.rx() = 1.;
3119+ localDirection.ry() = 0.;
3120+ break;
3121+ }
3122+ QPointF sceneOrigin = mapToScene(localOrigin);
3123+ QPointF sceneDirection = mapToScene(localDirection);
3124+ m_sceneDirectionVector = sceneDirection - sceneOrigin;
3125+}
3126+
3127+qreal DirectionalDragArea::projectOntoDirectionVector(const QPointF &sceneVector) const
3128+{
3129+ // same as dot product as m_sceneDirectionVector is a unit vector
3130+ return sceneVector.x() * m_sceneDirectionVector.x() +
3131+ sceneVector.y() * m_sceneDirectionVector.y();
3132+}
3133
3134=== added file 'src/app/unity8/plugins/Ubuntu/Gestures/DirectionalDragArea.h'
3135--- src/app/unity8/plugins/Ubuntu/Gestures/DirectionalDragArea.h 1970-01-01 00:00:00 +0000
3136+++ src/app/unity8/plugins/Ubuntu/Gestures/DirectionalDragArea.h 2015-03-20 16:26:56 +0000
3137@@ -0,0 +1,308 @@
3138+/*
3139+ * Copyright (C) 2013,2014 Canonical, Ltd.
3140+ *
3141+ * This program is free software; you can redistribute it and/or modify
3142+ * it under the terms of the GNU General Public License as published by
3143+ * the Free Software Foundation; version 3.
3144+ *
3145+ * This program is distributed in the hope that it will be useful,
3146+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3147+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3148+ * GNU General Public License for more details.
3149+ *
3150+ * You should have received a copy of the GNU General Public License
3151+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3152+ */
3153+
3154+#ifndef DIRECTIONAL_DRAG_AREA_H
3155+#define DIRECTIONAL_DRAG_AREA_H
3156+
3157+#include <QtQuick/QQuickItem>
3158+#include "AxisVelocityCalculator.h"
3159+#include "UbuntuGesturesQmlGlobal.h"
3160+#include "Damper.h"
3161+#include "Direction.h"
3162+
3163+// lib UbuntuGestures
3164+#include <Pool.h>
3165+#include <Timer.h>
3166+
3167+class TouchOwnershipEvent;
3168+class UnownedTouchEvent;
3169+
3170+/*
3171+ An area that detects axis-aligned single-finger drag gestures
3172+
3173+ If a drag deviates too much from the components' direction recognition will
3174+ fail. It will also fail if the drag or flick is too short. E.g. a noisy or
3175+ fidgety click
3176+
3177+ See doc/DirectionalDragArea.svg
3178+ */
3179+class UBUNTUGESTURESQML_EXPORT DirectionalDragArea : public QQuickItem {
3180+ Q_OBJECT
3181+
3182+ // The direction in which the gesture should move in order to be recognized.
3183+ Q_PROPERTY(Direction::Type direction READ direction WRITE setDirection NOTIFY directionChanged)
3184+
3185+ // The distance travelled by the finger along the axis specified by
3186+ // DirectionalDragArea's direction.
3187+ Q_PROPERTY(qreal distance READ distance NOTIFY distanceChanged)
3188+
3189+ // The distance travelled by the finger along the axis specified by
3190+ // DirectionalDragArea's direction in scene coordinates
3191+ Q_PROPERTY(qreal sceneDistance READ sceneDistance NOTIFY sceneDistanceChanged)
3192+
3193+ // Position of the touch point performing the drag relative to this item.
3194+ Q_PROPERTY(qreal touchX READ touchX NOTIFY touchXChanged)
3195+ Q_PROPERTY(qreal touchY READ touchY NOTIFY touchYChanged)
3196+
3197+ // Position of the touch point performing the drag, in scene's coordinate system
3198+ Q_PROPERTY(qreal touchSceneX READ touchSceneX NOTIFY touchSceneXChanged)
3199+ Q_PROPERTY(qreal touchSceneY READ touchSceneY NOTIFY touchSceneYChanged)
3200+
3201+ // The current status of the directional drag gesture area.
3202+ Q_PROPERTY(Status status READ status NOTIFY statusChanged)
3203+
3204+ // Whether a drag gesture is taking place
3205+ // This will be true as long as status is Undecided or Recognized
3206+ // When a gesture gets rejected, dragging turns to false.
3207+ Q_PROPERTY(bool dragging READ dragging NOTIFY draggingChanged)
3208+
3209+ /////
3210+ // stuff that will be set in stone at some point
3211+
3212+ // How far the touch point can move away from its expected position before
3213+ // it causes a rejection in the gesture recognition. This is to compensate
3214+ // for both noise in the touch input signal and for the natural irregularities
3215+ // in the finger movement.
3216+ // Proper value is likely device-specific.
3217+ Q_PROPERTY(qreal maxDeviation READ maxDeviation WRITE setMaxDeviation NOTIFY maxDeviationChanged)
3218+
3219+ // Widening angle, in degrees
3220+ // It's roughly the maximum angle a touch point can make relative to the
3221+ // axis defined by the compoment's direction for it to be recognized as a
3222+ // directional drag.
3223+ Q_PROPERTY(qreal wideningAngle READ wideningAngle WRITE setWideningAngle
3224+ NOTIFY wideningAngleChanged)
3225+
3226+ // How far a touch point has to move from its initial position in order for
3227+ // it to be recognized as a directional drag.
3228+ Q_PROPERTY(qreal distanceThreshold READ distanceThreshold WRITE setDistanceThreshold
3229+ NOTIFY distanceThresholdChanged)
3230+
3231+ // Minimum speed a gesture needs to have in order to be recognized as a
3232+ // directional drag.
3233+ // In pixels per second
3234+ Q_PROPERTY(qreal minSpeed READ minSpeed WRITE setMinSpeed NOTIFY minSpeedChanged)
3235+
3236+ // A gesture will be rejected if more than maxSilenceTime milliseconds has
3237+ // passed since we last got an input event from it (during Undecided state).
3238+ //
3239+ // Silence (i.e., lack of new input events) doesn't necessarily mean that the user's
3240+ // finger is still (zero drag speed). In some cases the finger might be moving but
3241+ // the driver's high noise filtering might cause those silence periods, specially
3242+ // in the moments succeeding a press (talking about Galaxy Nexus here).
3243+ Q_PROPERTY(int maxSilenceTime READ maxSilenceTime
3244+ WRITE setMaxSilenceTime
3245+ NOTIFY maxSilenceTimeChanged)
3246+
3247+ //
3248+ /////
3249+
3250+ // Maximum time (in milliseconds) after the start of a given touch point where
3251+ // subsequent touch starts are grouped with the first one into an N-touches gesture
3252+ // (e.g. a two-fingers tap or drag).
3253+ Q_PROPERTY(int compositionTime READ compositionTime
3254+ WRITE setCompositionTime
3255+ NOTIFY compositionTimeChanged)
3256+
3257+ Q_ENUMS(Direction)
3258+ Q_ENUMS(Status)
3259+public:
3260+ DirectionalDragArea(QQuickItem *parent = 0);
3261+
3262+ Direction::Type direction() const;
3263+ void setDirection(Direction::Type);
3264+
3265+ // Describes the state of the directional drag gesture.
3266+ enum Status {
3267+ // Waiting for a new touch point to land on this area. No gesture is being processed
3268+ // or tracked.
3269+ WaitingForTouch,
3270+
3271+ // A touch point has landed on this area but it's not know yet whether it is
3272+ // performing a drag in the correct direction.
3273+ // If it's decided that the touch point is not performing a directional drag gesture,
3274+ // it will be rejected/ignored and status will return to WaitingForTouch.
3275+ Undecided, //Recognizing,
3276+
3277+ // There's a touch point in this area and it performed a drag in the correct
3278+ // direction.
3279+ //
3280+ // Once recognized, the gesture state will move back to WaitingForTouch only once
3281+ // that touch point ends. The gesture will remain in the Recognized state even if
3282+ // the touch point starts moving in other directions or halts.
3283+ Recognized,
3284+ };
3285+ Status status() const { return m_status; }
3286+
3287+ qreal distance() const;
3288+ qreal sceneDistance() const;
3289+ void updateSceneDistance();
3290+
3291+ qreal touchX() const;
3292+ qreal touchY() const;
3293+
3294+ qreal touchSceneX() const;
3295+ qreal touchSceneY() const;
3296+
3297+ bool dragging() const { return (m_status == Undecided) || (m_status == Recognized); }
3298+
3299+ qreal maxDeviation() const { return m_dampedScenePos.maxDelta(); }
3300+ void setMaxDeviation(qreal value);
3301+
3302+ qreal wideningAngle() const;
3303+ void setWideningAngle(qreal value);
3304+
3305+ qreal distanceThreshold() const { return m_distanceThreshold; }
3306+ void setDistanceThreshold(qreal value);
3307+
3308+ qreal minSpeed() const { return m_minSpeed; }
3309+ void setMinSpeed(qreal value);
3310+
3311+ int maxSilenceTime() const { return m_maxSilenceTime; }
3312+ void setMaxSilenceTime(int value);
3313+
3314+ int compositionTime() const { return m_compositionTime; }
3315+ void setCompositionTime(int value);
3316+
3317+ // Replaces the existing Timer with the given one.
3318+ //
3319+ // Useful for providing a fake timer when testing.
3320+ void setRecognitionTimer(UbuntuGestures::AbstractTimer *timer);
3321+
3322+ // Useful for testing, where a fake time source can be supplied
3323+ void setTimeSource(const UbuntuGestures::SharedTimeSource &timeSource);
3324+
3325+ bool event(QEvent *e) override;
3326+
3327+ // Maximum time, in milliseconds, between a press and a release, for a touch
3328+ // sequence to be considered a tap.
3329+ int maxTapDuration() const { return 300; }
3330+
3331+Q_SIGNALS:
3332+ void directionChanged(Direction::Type direction);
3333+ void statusChanged(Status value);
3334+ void draggingChanged(bool value);
3335+ void distanceChanged(qreal value);
3336+ void sceneDistanceChanged(qreal value);
3337+ void maxDeviationChanged(qreal value);
3338+ void wideningAngleChanged(qreal value);
3339+ void distanceThresholdChanged(qreal value);
3340+ void minSpeedChanged(qreal value);
3341+ void maxSilenceTimeChanged(int value);
3342+ void compositionTimeChanged(int value);
3343+ void touchXChanged(qreal value);
3344+ void touchYChanged(qreal value);
3345+ void touchSceneXChanged(qreal value);
3346+ void touchSceneYChanged(qreal value);
3347+
3348+ // TODO: I would rather not have such signal as it has nothing to do with drag gestures.
3349+ // Remove when no longer used or move its implementation to the QML code that uses it
3350+ // See maxTapDuration()
3351+ void tapped();
3352+
3353+protected:
3354+ virtual void touchEvent(QTouchEvent *event);
3355+
3356+private Q_SLOTS:
3357+ void checkSpeed();
3358+ void giveUpIfDisabledOrInvisible();
3359+
3360+private:
3361+ void touchEvent_absent(QTouchEvent *event);
3362+ void touchEvent_undecided(QTouchEvent *event);
3363+ void touchEvent_recognized(QTouchEvent *event);
3364+ bool pointInsideAllowedArea() const;
3365+ bool movingInRightDirection() const;
3366+ bool movedFarEnough(const QPointF &point) const;
3367+ const QTouchEvent::TouchPoint *fetchTargetTouchPoint(QTouchEvent *event);
3368+ void setStatus(Status newStatus);
3369+ void setPreviousPos(const QPointF &point);
3370+ void setPreviousScenePos(const QPointF &point);
3371+ void updateVelocityCalculator(const QPointF &point);
3372+ bool isWithinTouchCompositionWindow();
3373+ void updateSceneDirectionVector();
3374+ // returns the scalar projection between the given vector (in scene coordinates)
3375+ // and m_sceneDirectionVector
3376+ qreal projectOntoDirectionVector(const QPointF &sceneVector) const;
3377+ void touchOwnershipEvent(TouchOwnershipEvent *event);
3378+ void unownedTouchEvent(UnownedTouchEvent *event);
3379+ void unownedTouchEvent_undecided(UnownedTouchEvent *unownedTouchEvent);
3380+ void watchPressedTouchPoints(const QList<QTouchEvent::TouchPoint> &touchPoints);
3381+ bool recognitionIsDisabled() const;
3382+ void emitSignalIfTapped();
3383+
3384+ Status m_status;
3385+
3386+ QPointF m_startPos;
3387+ QPointF m_startScenePos;
3388+ QPointF m_previousPos;
3389+ QPointF m_previousScenePos;
3390+ qreal m_sceneDistance;
3391+ int m_touchId;
3392+
3393+ // A movement damper is used in some of the gesture recognition calculations
3394+ // to get rid of noise or small oscillations in the touch position.
3395+ DampedPointF m_dampedScenePos;
3396+ QPointF m_previousDampedScenePos;
3397+
3398+ // Unit vector in scene coordinates describing the direction of the gesture recognition
3399+ QPointF m_sceneDirectionVector;
3400+
3401+ Direction::Type m_direction;
3402+ qreal m_wideningAngle; // in degrees
3403+ qreal m_wideningFactor; // it's pow(cosine(m_wideningAngle), 2)
3404+ qreal m_distanceThreshold;
3405+ qreal m_distanceThresholdSquared; // it's pow(m_distanceThreshold, 2)
3406+ qreal m_minSpeed;
3407+ int m_maxSilenceTime; // in milliseconds
3408+ int m_silenceTime; // in milliseconds
3409+ int m_compositionTime; // in milliseconds
3410+ int m_numSamplesOnLastSpeedCheck;
3411+ UbuntuGestures::AbstractTimer *m_recognitionTimer;
3412+ AxisVelocityCalculator *m_velocityCalculator;
3413+
3414+ UbuntuGestures::SharedTimeSource m_timeSource;
3415+
3416+ // Information about an active touch point
3417+ struct ActiveTouchInfo {
3418+ ActiveTouchInfo() : id(-1), startTime(-1) {}
3419+ bool isValid() const { return id != -1; }
3420+ void reset() { id = -1; }
3421+ int id;
3422+ qint64 startTime;
3423+ };
3424+ class ActiveTouchesInfo {
3425+ public:
3426+ ActiveTouchesInfo(const UbuntuGestures::SharedTimeSource &timeSource);
3427+ void update(QTouchEvent *event);
3428+ qint64 touchStartTime(int id);
3429+ bool isEmpty() const { return m_touchInfoPool.isEmpty(); }
3430+ qint64 mostRecentStartTime();
3431+ UbuntuGestures::SharedTimeSource m_timeSource;
3432+ private:
3433+ void addTouchPoint(int touchId);
3434+ void removeTouchPoint(int touchId);
3435+ #if ACTIVETOUCHESINFO_DEBUG
3436+ QString toString();
3437+ #endif
3438+
3439+ Pool<ActiveTouchInfo> m_touchInfoPool;
3440+ } m_activeTouches;
3441+
3442+ friend class tst_DirectionalDragArea;
3443+};
3444+
3445+#endif // DIRECTIONAL_DRAG_AREA_H
3446
3447=== added file 'src/app/unity8/plugins/Ubuntu/Gestures/Gestures.qmltypes'
3448--- src/app/unity8/plugins/Ubuntu/Gestures/Gestures.qmltypes 1970-01-01 00:00:00 +0000
3449+++ src/app/unity8/plugins/Ubuntu/Gestures/Gestures.qmltypes 2015-03-20 16:26:56 +0000
3450@@ -0,0 +1,168 @@
3451+import QtQuick.tooling 1.1
3452+
3453+// This file describes the plugin-supplied types contained in the library.
3454+// It is used for QML tooling purposes only.
3455+//
3456+// This file was auto-generated by:
3457+// 'qmlplugindump -notrelocatable Ubuntu.Gestures 0.1 plugins'
3458+
3459+Module {
3460+ Component {
3461+ name: "AxisVelocityCalculator"
3462+ prototype: "QObject"
3463+ exports: ["Ubuntu.Gestures/AxisVelocityCalculator 0.1"]
3464+ exportMetaObjectRevisions: [0]
3465+ Property { name: "trackedPosition"; type: "double" }
3466+ Signal {
3467+ name: "trackedPositionChanged"
3468+ Parameter { name: "value"; type: "double" }
3469+ }
3470+ Method { name: "calculate"; type: "double" }
3471+ Method { name: "reset" }
3472+ }
3473+ Component {
3474+ name: "Direction"
3475+ prototype: "QObject"
3476+ exports: ["Ubuntu.Gestures/Direction 0.1"]
3477+ isCreatable: false
3478+ isSingleton: true
3479+ exportMetaObjectRevisions: [0]
3480+ Enum {
3481+ name: "Type"
3482+ values: {
3483+ "Rightwards": 0,
3484+ "Leftwards": 1,
3485+ "Downwards": 2,
3486+ "Upwards": 3,
3487+ "Horizontal": 4
3488+ }
3489+ }
3490+ Method {
3491+ name: "isHorizontal"
3492+ type: "bool"
3493+ Parameter { name: "type"; type: "Direction::Type" }
3494+ }
3495+ Method {
3496+ name: "isVertical"
3497+ type: "bool"
3498+ Parameter { name: "type"; type: "Direction::Type" }
3499+ }
3500+ Method {
3501+ name: "isPositive"
3502+ type: "bool"
3503+ Parameter { name: "type"; type: "Direction::Type" }
3504+ }
3505+ }
3506+ Component {
3507+ name: "DirectionalDragArea"
3508+ defaultProperty: "data"
3509+ prototype: "QQuickItem"
3510+ exports: ["Ubuntu.Gestures/DirectionalDragArea 0.1"]
3511+ exportMetaObjectRevisions: [0]
3512+ Enum {
3513+ name: "Status"
3514+ values: {
3515+ "WaitingForTouch": 0,
3516+ "Undecided": 1,
3517+ "Recognized": 2
3518+ }
3519+ }
3520+ Property { name: "direction"; type: "Direction::Type" }
3521+ Property { name: "distance"; type: "double"; isReadonly: true }
3522+ Property { name: "sceneDistance"; type: "double"; isReadonly: true }
3523+ Property { name: "touchX"; type: "double"; isReadonly: true }
3524+ Property { name: "touchY"; type: "double"; isReadonly: true }
3525+ Property { name: "touchSceneX"; type: "double"; isReadonly: true }
3526+ Property { name: "touchSceneY"; type: "double"; isReadonly: true }
3527+ Property { name: "status"; type: "Status"; isReadonly: true }
3528+ Property { name: "dragging"; type: "bool"; isReadonly: true }
3529+ Property { name: "maxDeviation"; type: "double" }
3530+ Property { name: "wideningAngle"; type: "double" }
3531+ Property { name: "distanceThreshold"; type: "double" }
3532+ Property { name: "minSpeed"; type: "double" }
3533+ Property { name: "maxSilenceTime"; type: "int" }
3534+ Property { name: "compositionTime"; type: "int" }
3535+ Signal {
3536+ name: "directionChanged"
3537+ Parameter { name: "direction"; type: "Direction::Type" }
3538+ }
3539+ Signal {
3540+ name: "statusChanged"
3541+ Parameter { name: "value"; type: "Status" }
3542+ }
3543+ Signal {
3544+ name: "draggingChanged"
3545+ Parameter { name: "value"; type: "bool" }
3546+ }
3547+ Signal {
3548+ name: "distanceChanged"
3549+ Parameter { name: "value"; type: "double" }
3550+ }
3551+ Signal {
3552+ name: "sceneDistanceChanged"
3553+ Parameter { name: "value"; type: "double" }
3554+ }
3555+ Signal {
3556+ name: "maxDeviationChanged"
3557+ Parameter { name: "value"; type: "double" }
3558+ }
3559+ Signal {
3560+ name: "wideningAngleChanged"
3561+ Parameter { name: "value"; type: "double" }
3562+ }
3563+ Signal {
3564+ name: "distanceThresholdChanged"
3565+ Parameter { name: "value"; type: "double" }
3566+ }
3567+ Signal {
3568+ name: "minSpeedChanged"
3569+ Parameter { name: "value"; type: "double" }
3570+ }
3571+ Signal {
3572+ name: "maxSilenceTimeChanged"
3573+ Parameter { name: "value"; type: "int" }
3574+ }
3575+ Signal {
3576+ name: "compositionTimeChanged"
3577+ Parameter { name: "value"; type: "int" }
3578+ }
3579+ Signal {
3580+ name: "touchXChanged"
3581+ Parameter { name: "value"; type: "double" }
3582+ }
3583+ Signal {
3584+ name: "touchYChanged"
3585+ Parameter { name: "value"; type: "double" }
3586+ }
3587+ Signal {
3588+ name: "touchSceneXChanged"
3589+ Parameter { name: "value"; type: "double" }
3590+ }
3591+ Signal {
3592+ name: "touchSceneYChanged"
3593+ Parameter { name: "value"; type: "double" }
3594+ }
3595+ Signal { name: "tapped" }
3596+ }
3597+ Component {
3598+ name: "PressedOutsideNotifier"
3599+ defaultProperty: "data"
3600+ prototype: "QQuickItem"
3601+ exports: ["Ubuntu.Gestures/PressedOutsideNotifier 0.1"]
3602+ exportMetaObjectRevisions: [0]
3603+ Signal { name: "pressedOutside" }
3604+ }
3605+ Component {
3606+ name: "TouchGate"
3607+ defaultProperty: "data"
3608+ prototype: "QQuickItem"
3609+ exports: ["Ubuntu.Gestures/TouchGate 0.1"]
3610+ exportMetaObjectRevisions: [0]
3611+ Property { name: "targetItem"; type: "QQuickItem"; isPointer: true }
3612+ Signal {
3613+ name: "targetItemChanged"
3614+ Parameter { name: "item"; type: "QQuickItem"; isPointer: true }
3615+ }
3616+ Signal { name: "pressed" }
3617+ }
3618+}
3619
3620=== added file 'src/app/unity8/plugins/Ubuntu/Gestures/PressedOutsideNotifier.cpp'
3621--- src/app/unity8/plugins/Ubuntu/Gestures/PressedOutsideNotifier.cpp 1970-01-01 00:00:00 +0000
3622+++ src/app/unity8/plugins/Ubuntu/Gestures/PressedOutsideNotifier.cpp 2015-03-20 16:26:56 +0000
3623@@ -0,0 +1,119 @@
3624+/*
3625+ * Copyright (C) 2013 Canonical, Ltd.
3626+ *
3627+ * This program is free software; you can redistribute it and/or modify
3628+ * it under the terms of the GNU General Public License as published by
3629+ * the Free Software Foundation; version 3.
3630+ *
3631+ * This program is distributed in the hope that it will be useful,
3632+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3633+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3634+ * GNU General Public License for more details.
3635+ *
3636+ * You should have received a copy of the GNU General Public License
3637+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3638+ */
3639+
3640+#include "PressedOutsideNotifier.h"
3641+
3642+#include <QMouseEvent>
3643+
3644+PressedOutsideNotifier::PressedOutsideNotifier(QQuickItem *parent)
3645+ : QQuickItem(parent)
3646+{
3647+ connect(this, &QQuickItem::enabledChanged,
3648+ this, &PressedOutsideNotifier::setupOrTearDownEventFiltering);
3649+
3650+ m_signalEmissionTimer.setSingleShot(true);
3651+ m_signalEmissionTimer.setInterval(0); // times out on the next iteration of the event loop
3652+ connect(&m_signalEmissionTimer, &QTimer::timeout,
3653+ this, &PressedOutsideNotifier::pressedOutside);
3654+}
3655+
3656+bool PressedOutsideNotifier::eventFilter(QObject *watched, QEvent *event)
3657+{
3658+ Q_UNUSED(watched);
3659+ Q_ASSERT(watched == m_filteredWindow);
3660+
3661+ // We are already going to emit pressedOutside() anyway, thus no need
3662+ // for new checks.
3663+ // This case takes place when a QTouchEvent comes in and isn't handled by any item,
3664+ // causing QQuickWindow to synthesize a QMouseEvent out of it, which would
3665+ // be filtered by us as well and count as a second press, which is wrong.
3666+ if (m_signalEmissionTimer.isActive()) {
3667+ return false;
3668+ }
3669+
3670+ switch (event->type()) {
3671+ case QEvent::MouseButtonPress: {
3672+ QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
3673+ QPointF p = mapFromScene(mouseEvent->windowPos());
3674+ if (!contains(p)) {
3675+ m_signalEmissionTimer.start();
3676+ }
3677+ break;
3678+ }
3679+ case QEvent::TouchBegin:
3680+ processFilteredTouchBegin(static_cast<QTouchEvent*>(event));
3681+ default:
3682+ break;
3683+ }
3684+
3685+ // let the event be handled further
3686+ return false;
3687+}
3688+
3689+void PressedOutsideNotifier::itemChange(ItemChange change, const ItemChangeData &value)
3690+{
3691+ if (change == QQuickItem::ItemSceneChange) {
3692+ setupOrTearDownEventFiltering();
3693+ }
3694+
3695+ QQuickItem::itemChange(change, value);
3696+}
3697+
3698+void PressedOutsideNotifier::setupOrTearDownEventFiltering()
3699+{
3700+ if (isEnabled() && window()) {
3701+ setupEventFiltering();
3702+ } else if (m_filteredWindow) {
3703+ tearDownEventFiltering();
3704+ }
3705+}
3706+
3707+void PressedOutsideNotifier::setupEventFiltering()
3708+{
3709+ QQuickWindow *currentWindow = window();
3710+ Q_ASSERT(currentWindow != nullptr);
3711+
3712+ if (currentWindow == m_filteredWindow)
3713+ return;
3714+
3715+ if (m_filteredWindow) {
3716+ m_filteredWindow->removeEventFilter(this);
3717+ }
3718+
3719+ currentWindow->installEventFilter(this);
3720+ m_filteredWindow = currentWindow;
3721+}
3722+
3723+void PressedOutsideNotifier::tearDownEventFiltering()
3724+{
3725+ m_filteredWindow->removeEventFilter(this);
3726+ m_filteredWindow.clear();
3727+}
3728+
3729+void PressedOutsideNotifier::processFilteredTouchBegin(QTouchEvent *event)
3730+{
3731+ const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
3732+ for (int i = 0; i < touchPoints.count(); ++i) {
3733+ const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i);
3734+ if (touchPoint.state() == Qt::TouchPointPressed) {
3735+ QPointF p = mapFromScene(touchPoint.pos());
3736+ if (!contains(p)) {
3737+ m_signalEmissionTimer.start();
3738+ return;
3739+ }
3740+ }
3741+ }
3742+}
3743
3744=== added file 'src/app/unity8/plugins/Ubuntu/Gestures/PressedOutsideNotifier.h'
3745--- src/app/unity8/plugins/Ubuntu/Gestures/PressedOutsideNotifier.h 1970-01-01 00:00:00 +0000
3746+++ src/app/unity8/plugins/Ubuntu/Gestures/PressedOutsideNotifier.h 2015-03-20 16:26:56 +0000
3747@@ -0,0 +1,62 @@
3748+/*
3749+ * Copyright (C) 2013 Canonical, Ltd.
3750+ *
3751+ * This program is free software; you can redistribute it and/or modify
3752+ * it under the terms of the GNU General Public License as published by
3753+ * the Free Software Foundation; version 3.
3754+ *
3755+ * This program is distributed in the hope that it will be useful,
3756+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3757+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3758+ * GNU General Public License for more details.
3759+ *
3760+ * You should have received a copy of the GNU General Public License
3761+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3762+ */
3763+
3764+#ifndef PRESSED_OUTSIDE_NOTIFIER_H
3765+#define PRESSED_OUTSIDE_NOTIFIER_H
3766+
3767+#include <QQuickItem>
3768+
3769+#include <QQuickWindow>
3770+#include <QPointer>
3771+#include <QTimer>
3772+
3773+#include "UbuntuGesturesGlobal.h"
3774+
3775+/*
3776+ Notifies when a point, mouse or touch, is pressed outside its area.
3777+
3778+ Only enable it while needed.
3779+ */
3780+class UBUNTUGESTURES_EXPORT PressedOutsideNotifier : public QQuickItem {
3781+ Q_OBJECT
3782+
3783+public:
3784+ PressedOutsideNotifier(QQuickItem * parent = nullptr);
3785+
3786+ // From QObject
3787+ bool eventFilter(QObject *watched, QEvent *event) override;
3788+
3789+Q_SIGNALS:
3790+ void pressedOutside();
3791+
3792+protected:
3793+ void itemChange(ItemChange change, const ItemChangeData &value) override;
3794+
3795+private Q_SLOTS:
3796+ void setupOrTearDownEventFiltering();
3797+
3798+private:
3799+ void setupEventFiltering();
3800+ void tearDownEventFiltering();
3801+ void processFilteredTouchBegin(QTouchEvent *event);
3802+
3803+ QPointer<QQuickWindow> m_filteredWindow;
3804+
3805+ // Emits pressedOutside() signal on timeout
3806+ QTimer m_signalEmissionTimer;
3807+};
3808+
3809+#endif // PRESSED_OUTSIDE_NOTIFIER_H
3810
3811=== added file 'src/app/unity8/plugins/Ubuntu/Gestures/TimeSource.cpp'
3812--- src/app/unity8/plugins/Ubuntu/Gestures/TimeSource.cpp 1970-01-01 00:00:00 +0000
3813+++ src/app/unity8/plugins/Ubuntu/Gestures/TimeSource.cpp 2015-03-20 16:26:56 +0000
3814@@ -0,0 +1,49 @@
3815+/*
3816+ * Copyright (C) 2013 - Canonical Ltd.
3817+ *
3818+ * This program is free software: you can redistribute it and/or modify it
3819+ * under the terms of the GNU Lesser General Public License, as
3820+ * published by the Free Software Foundation; either version 2.1 or 3.0
3821+ * of the License.
3822+ *
3823+ * This program is distributed in the hope that it will be useful, but
3824+ * WITHOUT ANY WARRANTY; without even the implied warranties of
3825+ * MERCHANTABILITY, SATISFACTORY QUALITY or FITNESS FOR A PARTICULAR
3826+ * PURPOSE. See the applicable version of the GNU Lesser General Public
3827+ * License for more details.
3828+ *
3829+ * You should have received a copy of both the GNU Lesser General Public
3830+ * License along with this program. If not, see <http://www.gnu.org/licenses/>
3831+ *
3832+ * Authored by: Daniel d'Andrada <daniel.dandrada@canonical.com>
3833+ */
3834+
3835+#include "TimeSource.h"
3836+
3837+#include <QElapsedTimer>
3838+
3839+namespace UbuntuGestures {
3840+class RealTimeSourcePrivate {
3841+public:
3842+ QElapsedTimer timer;
3843+};
3844+}
3845+
3846+using namespace UbuntuGestures;
3847+
3848+RealTimeSource::RealTimeSource()
3849+ : UbuntuGestures::TimeSource()
3850+ , d(new RealTimeSourcePrivate)
3851+{
3852+ d->timer.start();
3853+}
3854+
3855+RealTimeSource::~RealTimeSource()
3856+{
3857+ delete d;
3858+}
3859+
3860+qint64 RealTimeSource::msecsSinceReference()
3861+{
3862+ return d->timer.elapsed();
3863+}
3864
3865=== added file 'src/app/unity8/plugins/Ubuntu/Gestures/TimeSource.h'
3866--- src/app/unity8/plugins/Ubuntu/Gestures/TimeSource.h 1970-01-01 00:00:00 +0000
3867+++ src/app/unity8/plugins/Ubuntu/Gestures/TimeSource.h 2015-03-20 16:26:56 +0000
3868@@ -0,0 +1,54 @@
3869+/*
3870+ * Copyright (C) 2013 - Canonical Ltd.
3871+ *
3872+ * This program is free software: you can redistribute it and/or modify it
3873+ * under the terms of the GNU Lesser General Public License, as
3874+ * published by the Free Software Foundation; either version 2.1 or 3.0
3875+ * of the License.
3876+ *
3877+ * This program is distributed in the hope that it will be useful, but
3878+ * WITHOUT ANY WARRANTY; without even the implied warranties of
3879+ * MERCHANTABILITY, SATISFACTORY QUALITY or FITNESS FOR A PARTICULAR
3880+ * PURPOSE. See the applicable version of the GNU Lesser General Public
3881+ * License for more details.
3882+ *
3883+ * You should have received a copy of both the GNU Lesser General Public
3884+ * License along with this program. If not, see <http://www.gnu.org/licenses/>
3885+ *
3886+ * Authored by: Daniel d'Andrada <daniel.dandrada@canonical.com>
3887+ */
3888+
3889+#ifndef UBUNTUGESTURES_TIMESOURCE_H
3890+#define UBUNTUGESTURES_TIMESOURCE_H
3891+
3892+#include "UbuntuGesturesQmlGlobal.h"
3893+#include <QSharedPointer>
3894+
3895+namespace UbuntuGestures {
3896+/*
3897+ Interface for a time source.
3898+ */
3899+class UBUNTUGESTURESQML_EXPORT TimeSource {
3900+public:
3901+ virtual ~TimeSource() {}
3902+ /* Returns the current time in milliseconds since some reference time in the past. */
3903+ virtual qint64 msecsSinceReference() = 0;
3904+};
3905+typedef QSharedPointer<TimeSource> SharedTimeSource;
3906+
3907+/*
3908+ Implementation of a time source
3909+ */
3910+class RealTimeSourcePrivate;
3911+class RealTimeSource : public TimeSource {
3912+public:
3913+ RealTimeSource();
3914+ virtual ~RealTimeSource();
3915+ qint64 msecsSinceReference() override;
3916+private:
3917+ RealTimeSourcePrivate *d;
3918+};
3919+
3920+} // namespace UbuntuGestures
3921+
3922+#endif // UBUNTUGESTURES_TIMESOURCE_H
3923
3924=== added file 'src/app/unity8/plugins/Ubuntu/Gestures/TouchDispatcher.cpp'
3925--- src/app/unity8/plugins/Ubuntu/Gestures/TouchDispatcher.cpp 1970-01-01 00:00:00 +0000
3926+++ src/app/unity8/plugins/Ubuntu/Gestures/TouchDispatcher.cpp 2015-03-20 16:26:56 +0000
3927@@ -0,0 +1,367 @@
3928+/*
3929+ * Copyright (C) 2014 Canonical, Ltd.
3930+ *
3931+ * This program is free software; you can redistribute it and/or modify
3932+ * it under the terms of the GNU General Public License as published by
3933+ * the Free Software Foundation; version 3.
3934+ *
3935+ * This program is distributed in the hope that it will be useful,
3936+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3937+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3938+ * GNU General Public License for more details.
3939+ *
3940+ * You should have received a copy of the GNU General Public License
3941+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3942+ */
3943+
3944+#include "TouchDispatcher.h"
3945+
3946+#include <QGuiApplication>
3947+#include <QScopedPointer>
3948+#include <QStyleHints>
3949+
3950+#pragma GCC diagnostic push
3951+#pragma GCC diagnostic ignored "-pedantic"
3952+#include <private/qquickitem_p.h>
3953+#pragma GCC diagnostic pop
3954+
3955+#define TOUCHDISPATCHER_DEBUG 0
3956+
3957+#if TOUCHDISPATCHER_DEBUG
3958+#include <DebugHelpers.h>
3959+#endif
3960+
3961+TouchDispatcher::TouchDispatcher()
3962+ : m_status(NoActiveTouch)
3963+ , m_touchMouseId(-1)
3964+ , m_touchMousePressTimestamp(0)
3965+{
3966+}
3967+
3968+void TouchDispatcher::setTargetItem(QQuickItem *target)
3969+{
3970+ if (target != m_targetItem) {
3971+ m_targetItem = target;
3972+ if (m_status != NoActiveTouch) {
3973+ qWarning("[TouchDispatcher] Changing target item in the middle of a touch stream");
3974+ m_status = TargetRejectedTouches;
3975+ }
3976+ }
3977+}
3978+
3979+void TouchDispatcher::dispatch(QEvent::Type eventType,
3980+ QTouchDevice *device,
3981+ Qt::KeyboardModifiers modifiers,
3982+ const QList<QTouchEvent::TouchPoint> &touchPoints,
3983+ QWindow *window,
3984+ ulong timestamp)
3985+{
3986+ if (m_targetItem.isNull()) {
3987+ qWarning("[TouchDispatcher] Cannot dispatch touch event because target item is null");
3988+ return;
3989+ }
3990+
3991+ if (eventType == QEvent::TouchBegin) {
3992+ dispatchTouchBegin(device, modifiers, touchPoints, window, timestamp);
3993+
3994+ } else if (eventType == QEvent::TouchUpdate || eventType == QEvent::TouchEnd) {
3995+
3996+ if (m_status == DeliveringTouchEvents) {
3997+ dispatchAsTouch(eventType, device, modifiers, touchPoints, window, timestamp);
3998+ } else if (m_status == DeliveringMouseEvents) {
3999+ dispatchAsMouse(device, modifiers, touchPoints, timestamp);
4000+ } else {
4001+ Q_ASSERT(m_status == TargetRejectedTouches);
4002+ #if TOUCHDISPATCHER_DEBUG
4003+ qDebug() << "[TouchDispatcher] Not dispatching touch event to" << m_targetItem.data()
4004+ << "because it already rejected the touch stream.";
4005+ #endif
4006+ // Do nothing
4007+ }
4008+
4009+ if (eventType == QEvent::TouchEnd) {
4010+ m_status = NoActiveTouch;
4011+ m_touchMouseId = -1;
4012+ }
4013+
4014+ } else {
4015+ // Should never happen
4016+ qCritical() << "[TouchDispatcher] Unexpected event type" << eventType;
4017+ Q_ASSERT(false);
4018+ return;
4019+ }
4020+}
4021+
4022+void TouchDispatcher::dispatchTouchBegin(
4023+ QTouchDevice *device,
4024+ Qt::KeyboardModifiers modifiers,
4025+ const QList<QTouchEvent::TouchPoint> &touchPoints,
4026+ QWindow *window,
4027+ ulong timestamp)
4028+{
4029+ Q_ASSERT(m_status == NoActiveTouch);
4030+ QQuickItem *targetItem = m_targetItem.data();
4031+
4032+ if (!targetItem->isEnabled() || !targetItem->isVisible()) {
4033+ #if TOUCHDISPATCHER_DEBUG
4034+ qDebug() << "[TouchDispatcher] Cannot dispatch touch event to" << targetItem
4035+ << "because it's disabled or invisible.";
4036+ #endif
4037+ return;
4038+ }
4039+
4040+ // Map touch points to targetItem coordinates
4041+ QList<QTouchEvent::TouchPoint> targetTouchPoints = touchPoints;
4042+ transformTouchPoints(targetTouchPoints, QQuickItemPrivate::get(targetItem)->windowToItemTransform());
4043+
4044+ QScopedPointer<QTouchEvent> touchEvent(
4045+ createQTouchEvent(QEvent::TouchBegin, device, modifiers, targetTouchPoints, window, timestamp));
4046+
4047+
4048+ #if TOUCHDISPATCHER_DEBUG
4049+ qDebug() << "[TouchDispatcher] dispatching" << qPrintable(touchEventToString(touchEvent.data()))
4050+ << "to" << targetItem;
4051+ #endif
4052+ QCoreApplication::sendEvent(targetItem, touchEvent.data());
4053+
4054+
4055+ if (touchEvent->isAccepted()) {
4056+ #if TOUCHDISPATCHER_DEBUG
4057+ qDebug() << "[TouchDispatcher] Item accepted the touch event.";
4058+ #endif
4059+ m_status = DeliveringTouchEvents;
4060+ } else if (targetItem->acceptedMouseButtons() & Qt::LeftButton) {
4061+ #if TOUCHDISPATCHER_DEBUG
4062+ qDebug() << "[TouchDispatcher] Item rejected the touch event. Trying a QMouseEvent";
4063+ #endif
4064+ // NB: Arbitrarily chose the first touch point to emulate the mouse pointer
4065+ QScopedPointer<QMouseEvent> mouseEvent(
4066+ touchToMouseEvent(QEvent::MouseButtonPress, targetTouchPoints.at(0), timestamp,
4067+ modifiers, false /* transformNeeded */));
4068+ Q_ASSERT(targetTouchPoints.at(0).state() == Qt::TouchPointPressed);
4069+
4070+ #if TOUCHDISPATCHER_DEBUG
4071+ qDebug() << "[TouchDispatcher] dispatching" << qPrintable(mouseEventToString(mouseEvent.data()))
4072+ << "to" << m_targetItem.data();
4073+ #endif
4074+ QCoreApplication::sendEvent(targetItem, mouseEvent.data());
4075+ if (mouseEvent->isAccepted()) {
4076+ #if TOUCHDISPATCHER_DEBUG
4077+ qDebug() << "[TouchDispatcher] Item accepted the QMouseEvent.";
4078+ #endif
4079+ m_status = DeliveringMouseEvents;
4080+ m_touchMouseId = targetTouchPoints.at(0).id();
4081+
4082+ if (checkIfDoubleClicked(timestamp)) {
4083+ QScopedPointer<QMouseEvent> doubleClickEvent(
4084+ touchToMouseEvent(QEvent::MouseButtonDblClick, targetTouchPoints.at(0), timestamp,
4085+ modifiers, false /* transformNeeded */));
4086+ #if TOUCHDISPATCHER_DEBUG
4087+ qDebug() << "[TouchDispatcher] dispatching" << qPrintable(mouseEventToString(doubleClickEvent.data()))
4088+ << "to" << m_targetItem.data();
4089+ #endif
4090+ QCoreApplication::sendEvent(targetItem, doubleClickEvent.data());
4091+ }
4092+
4093+ } else {
4094+ #if TOUCHDISPATCHER_DEBUG
4095+ qDebug() << "[TouchDispatcher] Item rejected the QMouseEvent.";
4096+ #endif
4097+ m_status = TargetRejectedTouches;
4098+ }
4099+ } else {
4100+ #if TOUCHDISPATCHER_DEBUG
4101+ qDebug() << "[TouchDispatcher] Item rejected the touch event and does not accept mouse buttons.";
4102+ #endif
4103+ m_status = TargetRejectedTouches;
4104+ }
4105+}
4106+
4107+void TouchDispatcher::dispatchAsTouch(QEvent::Type eventType,
4108+ QTouchDevice *device,
4109+ Qt::KeyboardModifiers modifiers,
4110+ const QList<QTouchEvent::TouchPoint> &touchPoints,
4111+ QWindow *window,
4112+ ulong timestamp)
4113+{
4114+ QQuickItem *targetItem = m_targetItem.data();
4115+
4116+ // Map touch points to targetItem coordinates
4117+ QList<QTouchEvent::TouchPoint> targetTouchPoints = touchPoints;
4118+ transformTouchPoints(targetTouchPoints, QQuickItemPrivate::get(targetItem)->windowToItemTransform());
4119+
4120+ QScopedPointer<QTouchEvent> eventForTargetItem(
4121+ createQTouchEvent(eventType, device, modifiers, targetTouchPoints, window, timestamp));
4122+
4123+
4124+ #if TOUCHDISPATCHER_DEBUG
4125+ qDebug() << "[TouchDispatcher] dispatching" << qPrintable(touchEventToString(eventForTargetItem.data()))
4126+ << "to" << targetItem;
4127+ #endif
4128+ QCoreApplication::sendEvent(targetItem, eventForTargetItem.data());
4129+}
4130+
4131+void TouchDispatcher::dispatchAsMouse(
4132+ QTouchDevice * /*device*/,
4133+ Qt::KeyboardModifiers modifiers,
4134+ const QList<QTouchEvent::TouchPoint> &touchPoints,
4135+ ulong timestamp)
4136+{
4137+ // TODO: Detect double clicks in order to synthesize QEvent::MouseButtonDblClick events accordingly
4138+
4139+ Q_ASSERT(!touchPoints.isEmpty());
4140+
4141+ const QTouchEvent::TouchPoint *touchMouse = nullptr;
4142+
4143+ if (m_touchMouseId != -1) {
4144+ for (int i = 0; i < touchPoints.count() && !touchMouse; ++i) {
4145+ const auto &touchPoint = touchPoints.at(i);
4146+ if (touchPoint.id() == m_touchMouseId) {
4147+ touchMouse = &touchPoint;
4148+ }
4149+ }
4150+
4151+ Q_ASSERT(touchMouse);
4152+ if (!touchMouse) {
4153+ // should not happen, but deal with it just in case.
4154+ qWarning("[TouchDispatcher] Didn't find touch with id %d, used for mouse pointer emulation.",
4155+ m_touchMouseId);
4156+ m_touchMouseId = touchPoints.at(0).id();
4157+ touchMouse = &touchPoints.at(0);
4158+ }
4159+ } else {
4160+ // Try to find a new touch for mouse emulation
4161+ for (int i = 0; i < touchPoints.count() && !touchMouse; ++i) {
4162+ const auto &touchPoint = touchPoints.at(i);
4163+ if (touchPoint.state() == Qt::TouchPointPressed) {
4164+ touchMouse = &touchPoint;
4165+ m_touchMouseId = touchMouse->id();
4166+ }
4167+ }
4168+ }
4169+
4170+ if (touchMouse) {
4171+ QEvent::Type eventType;
4172+ if (touchMouse->state() == Qt::TouchPointPressed) {
4173+ eventType = QEvent::MouseButtonPress;
4174+ } if (touchMouse->state() == Qt::TouchPointReleased) {
4175+ eventType = QEvent::MouseButtonRelease;
4176+ m_touchMouseId = -1;
4177+ } else {
4178+ eventType = QEvent::MouseMove;
4179+ }
4180+
4181+ QScopedPointer<QMouseEvent> mouseEvent(touchToMouseEvent(eventType, *touchMouse, timestamp, modifiers,
4182+ true /* transformNeeded */));
4183+
4184+ #if TOUCHDISPATCHER_DEBUG
4185+ qDebug() << "[TouchDispatcher] dispatching" << qPrintable(mouseEventToString(mouseEvent.data()))
4186+ << "to" << m_targetItem.data();
4187+ #endif
4188+ QCoreApplication::sendEvent(m_targetItem.data(), mouseEvent.data());
4189+ }
4190+}
4191+
4192+QTouchEvent *TouchDispatcher::createQTouchEvent(QEvent::Type eventType,
4193+ QTouchDevice *device,
4194+ Qt::KeyboardModifiers modifiers,
4195+ const QList<QTouchEvent::TouchPoint> &touchPoints,
4196+ QWindow *window,
4197+ ulong timestamp)
4198+{
4199+ Qt::TouchPointStates eventStates = 0;
4200+ for (int i = 0; i < touchPoints.count(); i++)
4201+ eventStates |= touchPoints[i].state();
4202+ // if all points have the same state, set the event type accordingly
4203+ switch (eventStates) {
4204+ case Qt::TouchPointPressed:
4205+ eventType = QEvent::TouchBegin;
4206+ break;
4207+ case Qt::TouchPointReleased:
4208+ eventType = QEvent::TouchEnd;
4209+ break;
4210+ default:
4211+ eventType = QEvent::TouchUpdate;
4212+ break;
4213+ }
4214+
4215+ QTouchEvent *touchEvent = new QTouchEvent(eventType);
4216+ touchEvent->setWindow(window);
4217+ touchEvent->setTarget(m_targetItem.data());
4218+ touchEvent->setDevice(device);
4219+ touchEvent->setModifiers(modifiers);
4220+ touchEvent->setTouchPoints(touchPoints);
4221+ touchEvent->setTouchPointStates(eventStates);
4222+ touchEvent->setTimestamp(timestamp);
4223+ touchEvent->accept();
4224+ return touchEvent;
4225+}
4226+
4227+// NB: From QQuickWindow
4228+void TouchDispatcher::transformTouchPoints(QList<QTouchEvent::TouchPoint> &touchPoints, const QTransform &transform)
4229+{
4230+ QMatrix4x4 transformMatrix(transform);
4231+ for (int i=0; i<touchPoints.count(); i++) {
4232+ QTouchEvent::TouchPoint &touchPoint = touchPoints[i];
4233+ touchPoint.setRect(transform.mapRect(touchPoint.sceneRect()));
4234+ touchPoint.setStartPos(transform.map(touchPoint.startScenePos()));
4235+ touchPoint.setLastPos(transform.map(touchPoint.lastScenePos()));
4236+ touchPoint.setVelocity(transformMatrix.mapVector(touchPoint.velocity()).toVector2D());
4237+ }
4238+}
4239+
4240+// Copied with minor modifications from qtdeclarative/src/quick/items/qquickwindow.cpp
4241+QMouseEvent *TouchDispatcher::touchToMouseEvent(
4242+ QEvent::Type type, const QTouchEvent::TouchPoint &p,
4243+ ulong timestamp, Qt::KeyboardModifiers modifiers,
4244+ bool transformNeeded)
4245+{
4246+ QQuickItem *item = m_targetItem.data();
4247+
4248+ // The touch point local position and velocity are not yet transformed.
4249+ QMouseEvent *me = new QMouseEvent(type, transformNeeded ? item->mapFromScene(p.scenePos()) : p.pos(),
4250+ p.scenePos(), p.screenPos(), Qt::LeftButton,
4251+ (type == QEvent::MouseButtonRelease ? Qt::NoButton : Qt::LeftButton),
4252+ modifiers);
4253+ me->setAccepted(true);
4254+ me->setTimestamp(timestamp);
4255+ QVector2D transformedVelocity = p.velocity();
4256+ if (transformNeeded) {
4257+ QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item);
4258+ QMatrix4x4 transformMatrix(itemPrivate->windowToItemTransform());
4259+ transformedVelocity = transformMatrix.mapVector(p.velocity()).toVector2D();
4260+ }
4261+
4262+ // Add these later if needed:
4263+ //QGuiApplicationPrivate::setMouseEventCapsAndVelocity(me, event->device()->capabilities(), transformedVelocity);
4264+ //QGuiApplicationPrivate::setMouseEventSource(me, Qt::MouseEventSynthesizedByQt);
4265+ return me;
4266+}
4267+
4268+/*
4269+ Copied from qquickwindow.cpp which has:
4270+ Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies)
4271+ Under GPL 3.0 license.
4272+*/
4273+bool TouchDispatcher::checkIfDoubleClicked(ulong newPressEventTimestamp)
4274+{
4275+ bool doubleClicked;
4276+
4277+ if (m_touchMousePressTimestamp == 0) {
4278+ // just initialize the variable
4279+ m_touchMousePressTimestamp = newPressEventTimestamp;
4280+ doubleClicked = false;
4281+ } else {
4282+ ulong timeBetweenPresses = newPressEventTimestamp - m_touchMousePressTimestamp;
4283+ ulong doubleClickInterval = static_cast<ulong>(qApp->styleHints()->
4284+ mouseDoubleClickInterval());
4285+ doubleClicked = timeBetweenPresses < doubleClickInterval;
4286+ if (doubleClicked) {
4287+ m_touchMousePressTimestamp = 0;
4288+ } else {
4289+ m_touchMousePressTimestamp = newPressEventTimestamp;
4290+ }
4291+ }
4292+
4293+ return doubleClicked;
4294+}
4295
4296=== added file 'src/app/unity8/plugins/Ubuntu/Gestures/TouchDispatcher.h'
4297--- src/app/unity8/plugins/Ubuntu/Gestures/TouchDispatcher.h 1970-01-01 00:00:00 +0000
4298+++ src/app/unity8/plugins/Ubuntu/Gestures/TouchDispatcher.h 2015-03-20 16:26:56 +0000
4299@@ -0,0 +1,89 @@
4300+/*
4301+ * Copyright (C) 2014 Canonical, Ltd.
4302+ *
4303+ * This program is free software; you can redistribute it and/or modify
4304+ * it under the terms of the GNU General Public License as published by
4305+ * the Free Software Foundation; version 3.
4306+ *
4307+ * This program is distributed in the hope that it will be useful,
4308+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
4309+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4310+ * GNU General Public License for more details.
4311+ *
4312+ * You should have received a copy of the GNU General Public License
4313+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
4314+ */
4315+
4316+#ifndef UBUNTU_TOUCH_DISPATCHER_H
4317+#define UBUNTU_TOUCH_DISPATCHER_H
4318+
4319+#include "UbuntuGesturesQmlGlobal.h"
4320+
4321+#include <QPointer>
4322+#include <QQuickItem>
4323+
4324+/*
4325+ Dispatches touches to the given target, converting the touch point
4326+ coordinates accordingly.
4327+
4328+ Also takes care of synthesizing mouse events in case the target
4329+ doesn't work with touch events.
4330+ */
4331+class UBUNTUGESTURESQML_EXPORT TouchDispatcher {
4332+public:
4333+ TouchDispatcher();
4334+
4335+ void setTargetItem(QQuickItem *target);
4336+ QQuickItem *targetItem() { return m_targetItem; }
4337+
4338+ void dispatch(QEvent::Type eventType,
4339+ QTouchDevice *device,
4340+ Qt::KeyboardModifiers modifiers,
4341+ const QList<QTouchEvent::TouchPoint> &touchPoints,
4342+ QWindow *window,
4343+ ulong timestamp);
4344+private:
4345+ void dispatchTouchBegin(
4346+ QTouchDevice *device,
4347+ Qt::KeyboardModifiers modifiers,
4348+ const QList<QTouchEvent::TouchPoint> &touchPoints,
4349+ QWindow *window,
4350+ ulong timestamp);
4351+ void dispatchAsTouch(QEvent::Type eventType,
4352+ QTouchDevice *device,
4353+ Qt::KeyboardModifiers modifiers,
4354+ const QList<QTouchEvent::TouchPoint> &touchPoints,
4355+ QWindow *window,
4356+ ulong timestamp);
4357+ void dispatchAsMouse(
4358+ QTouchDevice *device,
4359+ Qt::KeyboardModifiers modifiers,
4360+ const QList<QTouchEvent::TouchPoint> &touchPoints,
4361+ ulong timestamp);
4362+
4363+ static void transformTouchPoints(QList<QTouchEvent::TouchPoint> &touchPoints, const QTransform &transform);
4364+ QTouchEvent *createQTouchEvent(QEvent::Type eventType,
4365+ QTouchDevice *device,
4366+ Qt::KeyboardModifiers modifiers,
4367+ const QList<QTouchEvent::TouchPoint> &touchPoints,
4368+ QWindow *window,
4369+ ulong timestamp);
4370+ QMouseEvent *touchToMouseEvent(QEvent::Type type, const QTouchEvent::TouchPoint &p,
4371+ ulong timestamp, Qt::KeyboardModifiers modifiers, bool transformNeeded = true);
4372+
4373+ bool checkIfDoubleClicked(ulong newPressEventTimestamp);
4374+
4375+ QPointer<QQuickItem> m_targetItem;
4376+
4377+ enum {
4378+ NoActiveTouch,
4379+ DeliveringTouchEvents,
4380+ DeliveringMouseEvents,
4381+ TargetRejectedTouches
4382+ } m_status;
4383+
4384+ int m_touchMouseId;
4385+ ulong m_touchMousePressTimestamp;
4386+};
4387+
4388+#endif // UBUNTU_TOUCH_DISPATCHER_H
4389
4390=== added file 'src/app/unity8/plugins/Ubuntu/Gestures/TouchGate.cpp'
4391--- src/app/unity8/plugins/Ubuntu/Gestures/TouchGate.cpp 1970-01-01 00:00:00 +0000
4392+++ src/app/unity8/plugins/Ubuntu/Gestures/TouchGate.cpp 2015-03-20 16:26:56 +0000
4393@@ -0,0 +1,253 @@
4394+/*
4395+ * Copyright (C) 2014 Canonical, Ltd.
4396+ *
4397+ * This program is free software; you can redistribute it and/or modify
4398+ * it under the terms of the GNU General Public License as published by
4399+ * the Free Software Foundation; version 3.
4400+ *
4401+ * This program is distributed in the hope that it will be useful,
4402+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
4403+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4404+ * GNU General Public License for more details.
4405+ *
4406+ * You should have received a copy of the GNU General Public License
4407+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
4408+ */
4409+
4410+#include "TouchGate.h"
4411+
4412+#include <QCoreApplication>
4413+#include <QDebug>
4414+
4415+#include <TouchOwnershipEvent.h>
4416+#include <TouchRegistry.h>
4417+
4418+#if TOUCHGATE_DEBUG
4419+#include <DebugHelpers.h>
4420+#endif
4421+
4422+bool TouchGate::event(QEvent *e)
4423+{
4424+ if (e->type() == TouchOwnershipEvent::touchOwnershipEventType()) {
4425+ touchOwnershipEvent(static_cast<TouchOwnershipEvent *>(e));
4426+ return true;
4427+ } else {
4428+ return QQuickItem::event(e);
4429+ }
4430+}
4431+
4432+void TouchGate::touchEvent(QTouchEvent *event)
4433+{
4434+ #if TOUCHGATE_DEBUG
4435+ qDebug() << "[TouchGate] got touch event" << qPrintable(touchEventToString(event));
4436+ #endif
4437+ event->accept();
4438+
4439+ const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
4440+ bool goodToGo = true;
4441+ for (int i = 0; i < touchPoints.count(); ++i) {
4442+ const QTouchEvent::TouchPoint &touchPoint = touchPoints[i];
4443+
4444+ if (touchPoint.state() == Qt::TouchPointPressed) {
4445+ Q_ASSERT(!m_touchInfoMap.contains(touchPoint.id()));
4446+ m_touchInfoMap[touchPoint.id()].ownership = OwnershipRequested;
4447+ m_touchInfoMap[touchPoint.id()].ended = false;
4448+ TouchRegistry::instance()->requestTouchOwnership(touchPoint.id(), this);
4449+
4450+ Q_EMIT pressed();
4451+ }
4452+
4453+ goodToGo &= m_touchInfoMap.contains(touchPoint.id())
4454+ && m_touchInfoMap[touchPoint.id()].ownership == OwnershipGranted;
4455+
4456+ if (touchPoint.state() == Qt::TouchPointReleased && m_touchInfoMap.contains(touchPoint.id())) {
4457+ m_touchInfoMap[touchPoint.id()].ended = true;
4458+ }
4459+
4460+ }
4461+
4462+ if (goodToGo) {
4463+ if (m_storedEvents.isEmpty()) {
4464+ // let it pass through
4465+ dispatchTouchEventToTarget(event);
4466+ } else {
4467+ // Retain the event to ensure TouchGate dispatches them in order.
4468+ // Otherwise the current event would come before the stored ones, which are older.
4469+ #if TOUCHGATE_DEBUG
4470+ qDebug("[TouchGate] Storing event because thouches %s are still pending ownership.",
4471+ qPrintable(oldestPendingTouchIdsString()));
4472+ #endif
4473+ storeTouchEvent(event);
4474+ }
4475+ } else {
4476+ // Retain events that have unowned touches
4477+ storeTouchEvent(event);
4478+ }
4479+}
4480+
4481+void TouchGate::touchOwnershipEvent(TouchOwnershipEvent *event)
4482+{
4483+ // TODO: Optimization: batch those actions as TouchOwnershipEvents
4484+ // might come one right after the other.
4485+
4486+ Q_ASSERT(m_touchInfoMap.contains(event->touchId()));
4487+
4488+ TouchInfo &touchInfo = m_touchInfoMap[event->touchId()];
4489+
4490+ if (event->gained()) {
4491+ #if TOUCHGATE_DEBUG
4492+ qDebug() << "[TouchGate] Got ownership of touch " << event->touchId();
4493+ #endif
4494+ touchInfo.ownership = OwnershipGranted;
4495+ } else {
4496+ #if TOUCHGATE_DEBUG
4497+ qDebug() << "[TouchGate] Lost ownership of touch " << event->touchId();
4498+ #endif
4499+ m_touchInfoMap.remove(event->touchId());
4500+ removeTouchFromStoredEvents(event->touchId());
4501+ }
4502+
4503+ dispatchFullyOwnedEvents();
4504+}
4505+
4506+bool TouchGate::isTouchPointOwned(int touchId) const
4507+{
4508+ return m_touchInfoMap[touchId].ownership == OwnershipGranted;
4509+}
4510+
4511+void TouchGate::storeTouchEvent(const QTouchEvent *event)
4512+{
4513+ #if TOUCHGATE_DEBUG
4514+ qDebug() << "[TouchGate] Storing" << qPrintable(touchEventToString(event));
4515+ #endif
4516+
4517+ TouchEvent clonedEvent(event);
4518+ m_storedEvents.append(std::move(clonedEvent));
4519+}
4520+
4521+void TouchGate::removeTouchFromStoredEvents(int touchId)
4522+{
4523+ int i = 0;
4524+ while (i < m_storedEvents.count()) {
4525+ TouchEvent &event = m_storedEvents[i];
4526+ bool removed = event.removeTouch(touchId);
4527+
4528+ if (removed && event.touchPoints.isEmpty()) {
4529+ m_storedEvents.removeAt(i);
4530+ } else {
4531+ ++i;
4532+ }
4533+ }
4534+}
4535+
4536+void TouchGate::dispatchFullyOwnedEvents()
4537+{
4538+ while (!m_storedEvents.isEmpty() && eventIsFullyOwned(m_storedEvents.first())) {
4539+ TouchEvent event = m_storedEvents.takeFirst();
4540+ dispatchTouchEventToTarget(event);
4541+ }
4542+}
4543+
4544+#if TOUCHGATE_DEBUG
4545+QString TouchGate::oldestPendingTouchIdsString()
4546+{
4547+ Q_ASSERT(!m_storedEvents.isEmpty());
4548+
4549+ QString str;
4550+
4551+ const auto &touchPoints = m_storedEvents.first().touchPoints;
4552+ for (int i = 0; i < touchPoints.count(); ++i) {
4553+ if (!isTouchPointOwned(touchPoints[i].id())) {
4554+ if (!str.isEmpty()) {
4555+ str.append(", ");
4556+ }
4557+ str.append(QString::number(touchPoints[i].id()));
4558+ }
4559+ }
4560+
4561+ return str;
4562+}
4563+#endif
4564+
4565+bool TouchGate::eventIsFullyOwned(const TouchGate::TouchEvent &event) const
4566+{
4567+ for (int i = 0; i < event.touchPoints.count(); ++i) {
4568+ if (!isTouchPointOwned(event.touchPoints[i].id())) {
4569+ return false;
4570+ }
4571+ }
4572+
4573+ return true;
4574+}
4575+
4576+void TouchGate::setTargetItem(QQuickItem *item)
4577+{
4578+ // TODO: changing the target item while dispatch of touch events is taking place will
4579+ // create a mess
4580+
4581+ if (item == m_dispatcher.targetItem())
4582+ return;
4583+
4584+ m_dispatcher.setTargetItem(item);
4585+ Q_EMIT targetItemChanged(item);
4586+}
4587+
4588+void TouchGate::dispatchTouchEventToTarget(const TouchEvent &event)
4589+{
4590+ removeTouchInfoForEndedTouches(event.touchPoints);
4591+ m_dispatcher.dispatch(event.eventType,
4592+ event.device,
4593+ event.modifiers,
4594+ event.touchPoints,
4595+ event.window,
4596+ event.timestamp);
4597+}
4598+
4599+void TouchGate::dispatchTouchEventToTarget(QTouchEvent* event)
4600+{
4601+ removeTouchInfoForEndedTouches(event->touchPoints());
4602+ m_dispatcher.dispatch(event->type(),
4603+ event->device(),
4604+ event->modifiers(),
4605+ event->touchPoints(),
4606+ event->window(),
4607+ event->timestamp());
4608+}
4609+
4610+void TouchGate::removeTouchInfoForEndedTouches(const QList<QTouchEvent::TouchPoint> &touchPoints)
4611+{
4612+ for (int i = 0; i < touchPoints.size(); ++i) {\
4613+ const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i);
4614+
4615+ if (touchPoint.state() == Qt::TouchPointReleased) {
4616+ Q_ASSERT(m_touchInfoMap.contains(touchPoint.id()));
4617+ Q_ASSERT(m_touchInfoMap[touchPoint.id()].ended);
4618+ Q_ASSERT(m_touchInfoMap[touchPoint.id()].ownership == OwnershipGranted);
4619+ m_touchInfoMap.remove(touchPoint.id());
4620+ }
4621+ }
4622+}
4623+
4624+TouchGate::TouchEvent::TouchEvent(const QTouchEvent *event)
4625+ : eventType(event->type())
4626+ , device(event->device())
4627+ , modifiers(event->modifiers())
4628+ , touchPoints(event->touchPoints())
4629+ , target(qobject_cast<QQuickItem*>(event->target()))
4630+ , window(event->window())
4631+ , timestamp(event->timestamp())
4632+{
4633+}
4634+
4635+bool TouchGate::TouchEvent::removeTouch(int touchId)
4636+{
4637+ bool removed = false;
4638+ for (int i = 0; i < touchPoints.count() && !removed; ++i) {
4639+ if (touchPoints[i].id() == touchId) {
4640+ touchPoints.removeAt(i);
4641+ removed = true;
4642+ }
4643+ }
4644+
4645+ return removed;
4646+}
4647
4648=== added file 'src/app/unity8/plugins/Ubuntu/Gestures/TouchGate.h'
4649--- src/app/unity8/plugins/Ubuntu/Gestures/TouchGate.h 1970-01-01 00:00:00 +0000
4650+++ src/app/unity8/plugins/Ubuntu/Gestures/TouchGate.h 2015-03-20 16:26:56 +0000
4651@@ -0,0 +1,112 @@
4652+/*
4653+ * Copyright (C) 2014 Canonical, Ltd.
4654+ *
4655+ * This program is free software; you can redistribute it and/or modify
4656+ * it under the terms of the GNU General Public License as published by
4657+ * the Free Software Foundation; version 3.
4658+ *
4659+ * This program is distributed in the hope that it will be useful,
4660+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
4661+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4662+ * GNU General Public License for more details.
4663+ *
4664+ * You should have received a copy of the GNU General Public License
4665+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
4666+ */
4667+
4668+#ifndef UBUNTU_TOUCH_GATE_H
4669+#define UBUNTU_TOUCH_GATE_H
4670+
4671+#include "UbuntuGesturesQmlGlobal.h"
4672+#include "TouchDispatcher.h"
4673+
4674+#include <QQuickItem>
4675+#include <QList>
4676+#include <QMap>
4677+
4678+#define TOUCHGATE_DEBUG 0
4679+
4680+class TouchOwnershipEvent;
4681+
4682+/*
4683+ Blocks the passage of events until ownership over the related touch points is granted.
4684+
4685+ Blocked touch events won't be discarded. Instead they will be buffered until ownership
4686+ is granted. If ownership is given to another item, the event buffer is cleared.
4687+
4688+ A TouchGate is useful as a mediator for items that do not understand, or gracefully handle,
4689+ touch canceling. By having a TouchGate in front of them you guarantee that only owned touches (i.e.,
4690+ touches that won't be canceled later) reaches them.
4691+ */
4692+class UBUNTUGESTURESQML_EXPORT TouchGate : public QQuickItem {
4693+ Q_OBJECT
4694+
4695+ // Item that's going to receive the touch events that make it through the gate.
4696+ Q_PROPERTY(QQuickItem* targetItem READ targetItem WRITE setTargetItem NOTIFY targetItemChanged)
4697+
4698+public:
4699+ bool event(QEvent *e) override;
4700+
4701+ QQuickItem *targetItem() { return m_dispatcher.targetItem(); }
4702+ void setTargetItem(QQuickItem *item);
4703+
4704+Q_SIGNALS:
4705+ void targetItemChanged(QQuickItem *item);
4706+
4707+ void pressed();
4708+
4709+protected:
4710+ void touchEvent(QTouchEvent *event) override;
4711+private:
4712+ class TouchEvent {
4713+ public:
4714+ TouchEvent(const QTouchEvent *event);
4715+
4716+ bool removeTouch(int touchId);
4717+
4718+ QEvent::Type eventType;
4719+ QTouchDevice *device;
4720+ Qt::KeyboardModifiers modifiers;
4721+ QList<QTouchEvent::TouchPoint> touchPoints;
4722+ QQuickItem *target;
4723+ QWindow *window;
4724+ ulong timestamp;
4725+ };
4726+
4727+ void touchOwnershipEvent(TouchOwnershipEvent *event);
4728+ bool isTouchPointOwned(int touchId) const;
4729+ void storeTouchEvent(const QTouchEvent *event);
4730+ void removeTouchFromStoredEvents(int touchId);
4731+ void dispatchFullyOwnedEvents();
4732+ bool eventIsFullyOwned(const TouchEvent &event) const;
4733+
4734+ void dispatchTouchEventToTarget(const TouchEvent &event);
4735+ void dispatchTouchEventToTarget(QTouchEvent* event);
4736+
4737+ void removeTouchInfoForEndedTouches(const QList<QTouchEvent::TouchPoint> &touchPoints);
4738+
4739+ #if TOUCHGATE_DEBUG
4740+ QString oldestPendingTouchIdsString();
4741+ #endif
4742+
4743+ QList<TouchEvent> m_storedEvents;
4744+
4745+ enum {
4746+ OwnershipUndefined,
4747+ OwnershipRequested,
4748+ OwnershipGranted,
4749+ };
4750+ class TouchInfo {
4751+ public:
4752+ TouchInfo() {ownership = OwnershipUndefined; ended = false;}
4753+ int ownership;
4754+ bool ended;
4755+ };
4756+ QMap<int, TouchInfo> m_touchInfoMap;
4757+
4758+ TouchDispatcher m_dispatcher;
4759+
4760+ friend class tst_TouchGate;
4761+};
4762+
4763+#endif // UBUNTU_TOUCH_GATE_H
4764
4765=== added file 'src/app/unity8/plugins/Ubuntu/Gestures/UbuntuGesturesQmlGlobal.h'
4766--- src/app/unity8/plugins/Ubuntu/Gestures/UbuntuGesturesQmlGlobal.h 1970-01-01 00:00:00 +0000
4767+++ src/app/unity8/plugins/Ubuntu/Gestures/UbuntuGesturesQmlGlobal.h 2015-03-20 16:26:56 +0000
4768@@ -0,0 +1,23 @@
4769+/*
4770+ * Copyright (C) 2013 Canonical, Ltd.
4771+ *
4772+ * This program is free software; you can redistribute it and/or modify
4773+ * it under the terms of the GNU General Public License as published by
4774+ * the Free Software Foundation; version 3.
4775+ *
4776+ * This program is distributed in the hope that it will be useful,
4777+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
4778+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4779+ * GNU General Public License for more details.
4780+ *
4781+ * You should have received a copy of the GNU General Public License
4782+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
4783+ */
4784+
4785+#include <QtCore/QtGlobal>
4786+
4787+#if defined(UBUNTUGESTURESQML_LIBRARY)
4788+# define UBUNTUGESTURESQML_EXPORT Q_DECL_EXPORT
4789+#else
4790+# define UBUNTUGESTURESQML_EXPORT Q_DECL_IMPORT
4791+#endif
4792
4793=== added file 'src/app/unity8/plugins/Ubuntu/Gestures/plugin.cpp'
4794--- src/app/unity8/plugins/Ubuntu/Gestures/plugin.cpp 1970-01-01 00:00:00 +0000
4795+++ src/app/unity8/plugins/Ubuntu/Gestures/plugin.cpp 2015-03-20 16:26:56 +0000
4796@@ -0,0 +1,39 @@
4797+/*
4798+ * Copyright (C) 2013 Canonical, Ltd.
4799+ *
4800+ * This program is free software; you can redistribute it and/or modify
4801+ * it under the terms of the GNU General Public License as published by
4802+ * the Free Software Foundation; version 3.
4803+ *
4804+ * This program is distributed in the hope that it will be useful,
4805+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
4806+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4807+ * GNU General Public License for more details.
4808+ *
4809+ * You should have received a copy of the GNU General Public License
4810+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
4811+ */
4812+
4813+#include "plugin.h"
4814+#include "AxisVelocityCalculator.h"
4815+#include "Direction.h"
4816+#include "DirectionalDragArea.h"
4817+#include "PressedOutsideNotifier.h"
4818+#include "TouchGate.h"
4819+
4820+#include <qqml.h>
4821+
4822+static QObject* directionSingleton(QQmlEngine* engine, QJSEngine* scriptEngine) {
4823+ Q_UNUSED(engine);
4824+ Q_UNUSED(scriptEngine);
4825+ return new Direction;
4826+}
4827+
4828+void UbuntuGesturesQmlPlugin::registerTypes(const char *uri)
4829+{
4830+ qmlRegisterSingletonType<Direction>(uri, 0, 1, "Direction", directionSingleton);
4831+ qmlRegisterType<DirectionalDragArea>(uri, 0, 1, "DirectionalDragArea");
4832+ qmlRegisterType<AxisVelocityCalculator>(uri, 0, 1, "AxisVelocityCalculator");
4833+ qmlRegisterType<PressedOutsideNotifier>(uri, 0, 1, "PressedOutsideNotifier");
4834+ qmlRegisterType<TouchGate>(uri, 0, 1, "TouchGate");
4835+}
4836
4837=== added file 'src/app/unity8/plugins/Ubuntu/Gestures/plugin.h'
4838--- src/app/unity8/plugins/Ubuntu/Gestures/plugin.h 1970-01-01 00:00:00 +0000
4839+++ src/app/unity8/plugins/Ubuntu/Gestures/plugin.h 2015-03-20 16:26:56 +0000
4840@@ -0,0 +1,31 @@
4841+/*
4842+ * Copyright (C) 2013 Canonical, Ltd.
4843+ *
4844+ * This program is free software; you can redistribute it and/or modify
4845+ * it under the terms of the GNU General Public License as published by
4846+ * the Free Software Foundation; version 3.
4847+ *
4848+ * This program is distributed in the hope that it will be useful,
4849+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
4850+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4851+ * GNU General Public License for more details.
4852+ *
4853+ * You should have received a copy of the GNU General Public License
4854+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
4855+ */
4856+
4857+#ifndef PLUGIN_H
4858+#define PLUGIN_H
4859+
4860+#include <QtQml/QQmlExtensionPlugin>
4861+
4862+class UbuntuGesturesQmlPlugin : public QQmlExtensionPlugin
4863+{
4864+ Q_OBJECT
4865+ Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
4866+public:
4867+ void registerTypes(const char *uri);
4868+};
4869+
4870+
4871+#endif
4872
4873=== added file 'src/app/unity8/plugins/Ubuntu/Gestures/qmldir'
4874--- src/app/unity8/plugins/Ubuntu/Gestures/qmldir 1970-01-01 00:00:00 +0000
4875+++ src/app/unity8/plugins/Ubuntu/Gestures/qmldir 2015-03-20 16:26:56 +0000
4876@@ -0,0 +1,3 @@
4877+module Ubuntu.Gestures
4878+plugin UbuntuGesturesQml
4879+typeinfo Gestures.qmltypes
4880
4881=== added file 'src/app/webbrowser/BottomEdgeHandle.qml'
4882--- src/app/webbrowser/BottomEdgeHandle.qml 1970-01-01 00:00:00 +0000
4883+++ src/app/webbrowser/BottomEdgeHandle.qml 2015-03-20 16:26:56 +0000
4884@@ -0,0 +1,36 @@
4885+/*
4886+ * Copyright 2014-2015 Canonical Ltd.
4887+ *
4888+ * This file is part of webbrowser-app.
4889+ *
4890+ * webbrowser-app is free software; you can redistribute it and/or modify
4891+ * it under the terms of the GNU General Public License as published by
4892+ * the Free Software Foundation; version 3.
4893+ *
4894+ * webbrowser-app is distributed in the hope that it will be useful,
4895+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
4896+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4897+ * GNU General Public License for more details.
4898+ *
4899+ * You should have received a copy of the GNU General Public License
4900+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
4901+ */
4902+
4903+import QtQuick 2.0
4904+import Ubuntu.Gestures 0.1
4905+
4906+DirectionalDragArea {
4907+ direction: Direction.Upwards
4908+
4909+ // default values taken from unity8’s EdgeDragArea component
4910+ maxDeviation: units.gu(3)
4911+ wideningAngle: 50
4912+ distanceThreshold: units.gu(1.5)
4913+ minSpeed: 0
4914+ maxSilenceTime: 200
4915+ compositionTime: 60
4916+
4917+ readonly property real dragFraction: dragging ? Math.min(1.0, Math.max(0.0, sceneDistance / parent.height)) : 0.0
4918+ readonly property var thresholds: [0.05, 0.18, 0.36, 0.54, 1.0]
4919+ readonly property int stage: thresholds.map(function(t) { return dragFraction <= t }).indexOf(true)
4920+}
4921
4922=== modified file 'src/app/webbrowser/Browser.qml'
4923--- src/app/webbrowser/Browser.qml 2015-02-05 13:09:42 +0000
4924+++ src/app/webbrowser/Browser.qml 2015-03-20 16:26:56 +0000
4925@@ -1,5 +1,5 @@
4926 /*
4927- * Copyright 2013-2014 Canonical Ltd.
4928+ * Copyright 2013-2015 Canonical Ltd.
4929 *
4930 * This file is part of webbrowser-app.
4931 *
4932@@ -17,6 +17,7 @@
4933 */
4934
4935 import QtQuick 2.0
4936+import QtQuick.Window 2.0
4937 import com.canonical.Oxide 1.4 as Oxide
4938 import Ubuntu.Components 1.1
4939 import webbrowserapp.private 0.1
4940@@ -78,19 +79,33 @@
4941 ]
4942
4943 Item {
4944- id: mainView
4945-
4946 anchors.fill: parent
4947- visible: !historyViewContainer.visible && !tabsViewContainer.visible
4948+
4949+ TabChrome {
4950+ id: invisibleTabChrome
4951+ visible: false
4952+ anchors {
4953+ top: parent.top
4954+ left: parent.left
4955+ right: parent.right
4956+ }
4957+ }
4958+
4959+ Rectangle {
4960+ // Background for the recent view
4961+ anchors.fill: invisibleTabChrome
4962+ visible: recentView.visible
4963+ color: "#312f2c"
4964+ }
4965
4966 FocusScope {
4967 id: tabContainer
4968 anchors {
4969 left: parent.left
4970 right: parent.right
4971- top: chrome.bottom
4972+ top: recentView.visible ? invisibleTabChrome.bottom : chrome.bottom
4973 }
4974- height: parent.height - chrome.visibleHeight - osk.height
4975+ height: parent.height - osk.height - (recentView.visible ? invisibleTabChrome.height : chrome.visibleHeight)
4976 }
4977
4978 Loader {
4979@@ -124,6 +139,8 @@
4980 Chrome {
4981 id: chrome
4982
4983+ visible: !recentView.visible
4984+
4985 webview: browser.currentWebview
4986 searchUrl: browser.searchEngine ? browser.searchEngine.template : ""
4987
4988@@ -181,12 +198,17 @@
4989 objectName: "tabs"
4990 text: i18n.tr("Open tabs")
4991 iconName: "browser-tabs"
4992- onTriggered: tabsViewComponent.createObject(tabsViewContainer)
4993+ enabled: formFactor != "mobile"
4994+ onTriggered: {
4995+ recentView.state = "shown"
4996+ recentToolbar.state = "shown"
4997+ }
4998 },
4999 Action {
5000 objectName: "newtab"
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches

to status/vote changes: