Merge lp:~mzanetti/unity8/no-more-pinning into lp:unity8/rtm-14.09

Proposed by Michael Zanetti
Status: Superseded
Proposed branch: lp:~mzanetti/unity8/no-more-pinning
Merge into: lp:unity8/rtm-14.09
Diff against target: 8778 lines (+5080/-826)
128 files modified
CMakeLists.txt (+2/-0)
debian/changelog (+27/-0)
debian/control (+1/-1)
debian/unity8-private.install (+1/-0)
libs/CMakeLists.txt (+1/-0)
libs/UbuntuGestures/CMakeLists.txt (+40/-0)
libs/UbuntuGestures/CandidateInactivityTimer.cpp (+41/-0)
libs/UbuntuGestures/CandidateInactivityTimer.h (+49/-0)
libs/UbuntuGestures/DebugHelpers.cpp (+68/-0)
libs/UbuntuGestures/DebugHelpers.h (+29/-0)
libs/UbuntuGestures/Pool.h (+134/-0)
libs/UbuntuGestures/Timer.cpp (+109/-0)
libs/UbuntuGestures/Timer.h (+105/-0)
libs/UbuntuGestures/TouchOwnershipEvent.cpp (+35/-0)
libs/UbuntuGestures/TouchOwnershipEvent.h (+50/-0)
libs/UbuntuGestures/TouchRegistry.cpp (+500/-0)
libs/UbuntuGestures/TouchRegistry.h (+182/-0)
libs/UbuntuGestures/UbuntuGesturesGlobal.h (+23/-0)
libs/UbuntuGestures/UnownedTouchEvent.cpp (+39/-0)
libs/UbuntuGestures/UnownedTouchEvent.h (+45/-0)
plugins/Dash/listviewwithpageheader.cpp (+1/-1)
plugins/Ubuntu/Gestures/AxisVelocityCalculator.h (+2/-2)
plugins/Ubuntu/Gestures/CMakeLists.txt (+26/-7)
plugins/Ubuntu/Gestures/Direction.h (+2/-2)
plugins/Ubuntu/Gestures/DirectionalDragArea.cpp (+335/-209)
plugins/Ubuntu/Gestures/DirectionalDragArea.h (+37/-29)
plugins/Ubuntu/Gestures/TimeSource.h (+2/-2)
plugins/Ubuntu/Gestures/TouchGate.cpp (+347/-0)
plugins/Ubuntu/Gestures/TouchGate.h (+126/-0)
plugins/Ubuntu/Gestures/UbuntuGesturesQmlGlobal.h (+3/-3)
plugins/Ubuntu/Gestures/plugin.cpp (+3/-1)
plugins/Ubuntu/Gestures/plugin.h (+1/-1)
plugins/Ubuntu/Gestures/qmldir (+1/-1)
po/ast.po (+9/-8)
po/en_AU.po (+16/-22)
po/es.po (+5/-5)
po/fa.po (+4/-10)
po/fr.po (+5/-5)
po/gd.po (+4/-4)
po/hu.po (+3/-9)
po/is.po (+9/-9)
qml/Components/Carousel.qml (+2/-3)
qml/Components/DragHandle.qml (+2/-2)
qml/Components/EdgeDragArea.qml (+2/-0)
qml/Components/Flickables/Flickable.qml (+23/-0)
qml/Components/Flickables/GridView.qml (+25/-0)
qml/Components/Flickables/ListView.qml (+25/-0)
qml/Components/Flickables/ListViewWithPageHeader.qml (+23/-0)
qml/Components/InputMethod.qml (+13/-0)
qml/Components/ListItems/Highlight.qml (+0/-46)
qml/Components/ResponsiveGridView.qml (+2/-1)
qml/Components/ZoomableImage.qml (+2/-2)
qml/Dash/CardHorizontalList.qml (+2/-1)
qml/Dash/Dash.qml (+29/-7)
qml/Dash/DashCategoryBase.qml (+4/-58)
qml/Dash/DashContent.qml (+6/-4)
qml/Dash/DashNavigationButton.qml (+2/-1)
qml/Dash/DashNavigationList.qml (+2/-1)
qml/Dash/GenericScopeView.qml (+4/-3)
qml/Dash/PageHeader.qml (+2/-1)
qml/Dash/PreviewListView.qml (+2/-3)
qml/Dash/Previews/Preview.qml (+2/-1)
qml/Dash/Previews/PreviewImageGallery.qml (+3/-2)
qml/Dash/Previews/PreviewTextSummary.qml (+1/-1)
qml/Dash/ScopeListView.qml (+3/-6)
qml/Dash/ScopeSettingsPage.qml (+2/-1)
qml/Dash/ScopesOverviewAll.qml (+2/-1)
qml/Dash/ScopesOverviewFavorites.qml (+4/-2)
qml/Greeter/LoginList.qml (+2/-2)
qml/Hud/Hud.qml (+2/-1)
qml/Hud/HudParametrizedActionsPage.qml (+2/-1)
qml/Launcher/Launcher.qml (+3/-1)
qml/Launcher/LauncherDelegate.qml (+2/-19)
qml/Launcher/LauncherPanel.qml (+3/-6)
qml/Notifications/Notification.qml (+2/-1)
qml/Notifications/Notifications.qml (+2/-1)
qml/Panel/IndicatorRow.qml (+2/-1)
qml/Panel/Indicators/DefaultIndicatorPage.qml (+2/-1)
qml/Panel/Indicators/client/IndicatorsList.qml (+2/-1)
qml/Panel/Indicators/client/IndicatorsTree.qml (+2/-1)
qml/Panel/MenuContent.qml (+3/-2)
qml/Shell.qml (+55/-21)
qml/Stages/ApplicationWindow.qml (+0/-18)
qml/Stages/PhoneStage.qml (+16/-6)
qml/Stages/SessionContainer.qml (+1/-13)
qml/Stages/SurfaceContainer.qml (+38/-7)
qml/Stages/TabletStage.qml (+2/-1)
src/CMakeLists.txt (+6/-0)
src/Dash/CMakeLists.txt (+5/-0)
src/Dash/main.cpp (+6/-0)
src/main.cpp (+5/-0)
tests/CMakeLists.txt (+1/-0)
tests/autopilot/unity8/shell/emulators/dash.py (+1/-1)
tests/libs/CMakeLists.txt (+1/-0)
tests/libs/UbuntuGestures/CMakeLists.txt (+20/-0)
tests/libs/UbuntuGestures/tst_TouchRegistry.cpp (+803/-0)
tests/mocks/AccountsService/AccountsService.cpp (+5/-3)
tests/mocks/AccountsService/AccountsService.h (+1/-0)
tests/mocks/Unity/Application/CMakeLists.txt (+1/-0)
tests/mocks/Unity/Application/MirSurfaceItem.cpp (+18/-22)
tests/mocks/Unity/Application/MirSurfaceItem.h (+18/-1)
tests/mocks/Unity/Application/MirSurfaceItem.qml (+0/-12)
tests/mocks/Unity/Application/UbuntuKeyboardInfo.cpp (+28/-0)
tests/mocks/Unity/Application/UbuntuKeyboardInfo.h (+60/-0)
tests/mocks/Unity/Application/plugin.cpp (+7/-0)
tests/plugins/Ubuntu/Gestures/CMakeLists.txt (+9/-2)
tests/plugins/Ubuntu/Gestures/RightwardsLauncher.qml (+3/-0)
tests/plugins/Ubuntu/Gestures/touchGateExample.qml (+27/-0)
tests/plugins/Ubuntu/Gestures/tst_DirectionalDragArea.cpp (+494/-17)
tests/plugins/Ubuntu/Gestures/tst_DirectionalDragArea.qml (+1/-0)
tests/plugins/Ubuntu/Gestures/tst_TouchGate.cpp (+345/-0)
tests/qmltests/Components/CMakeLists.txt (+2/-1)
tests/qmltests/Components/tst_DragHandle.cpp (+14/-17)
tests/qmltests/Greeter/tst_SingleGreeter.qml (+1/-1)
tests/qmltests/Launcher/tst_Launcher.qml (+123/-107)
tests/qmltests/Stages/tst_ApplicationWindow.qml (+2/-2)
tests/qmltests/Stages/tst_PhoneStage.qml (+2/-1)
tests/qmltests/Stages/tst_SurfaceContainer.qml (+26/-1)
tests/qmltests/tst_Shell.qml (+118/-3)
tests/qmltests/tst_ShellWithPin.qml (+94/-47)
tests/qmltests/tst_TabletShell.qml (+10/-2)
tests/uqmlscene/CMakeLists.txt (+2/-0)
tests/uqmlscene/README (+3/-1)
tests/uqmlscene/main.cpp (+9/-0)
tests/utils/modules/Unity/Test/CMakeLists.txt (+2/-0)
tests/utils/modules/Unity/Test/UnityTestCase.qml (+11/-0)
tests/utils/modules/Unity/Test/testutil.cpp (+41/-2)
tests/utils/modules/Unity/Test/testutil.h (+3/-1)
To merge this branch: bzr merge lp:~mzanetti/unity8/no-more-pinning
Reviewer Review Type Date Requested Status
Unity Team Pending
Review via email: mp+238292@code.launchpad.net

This proposal has been superseded by a proposal from 2014-10-14.

Description of the change

 * Are there any related MPs required for this MP to build/function as expected? Please list.

no

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

yes

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

yes

 * If you changed the packaging (debian), did you subscribe the ubuntu-unity team to this MP?
 * If you changed the UI, has there been a design review?

both N/A

To post a comment you must log in.

Unmerged revisions

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

Subscribers

People subscribed via source and target branches

to all changes: