Merge lp:~unity-team/unity8/disable-flaky-adt into lp:unity8

Proposed by Michael Zanetti
Status: Superseded
Proposed branch: lp:~unity-team/unity8/disable-flaky-adt
Merge into: lp:unity8
Diff against target: 11356 lines (+6980/-1449)
113 files modified
CMakeLists.txt (+2/-1)
CODING (+2/-0)
debian/control (+6/-5)
libs/UbuntuGestures/Timer.h (+5/-0)
libs/UbuntuGestures/TouchRegistry.cpp (+15/-10)
plugins/AccountsService/AccountsService.cpp (+27/-3)
plugins/AccountsService/AccountsService.h (+9/-3)
plugins/Cursor/Cursor.qmltypes (+78/-0)
plugins/GlobalShortcut/GlobalShortcut.qmltypes (+31/-0)
plugins/Ubuntu/Gestures/CMakeLists.txt (+1/-0)
plugins/Ubuntu/Gestures/DirectionalDragArea.cpp (+3/-0)
plugins/Ubuntu/Gestures/Gestures.qmltypes (+87/-46)
plugins/Ubuntu/Gestures/TouchGestureArea.cpp (+875/-0)
plugins/Ubuntu/Gestures/TouchGestureArea.h (+229/-0)
plugins/Ubuntu/Gestures/plugin.cpp (+3/-0)
plugins/Unity/InputInfo/InputInfo.qmltypes (+71/-0)
plugins/Unity/Platform/Platform.qmltypes (+20/-0)
plugins/Utils/CMakeLists.txt (+1/-0)
plugins/Utils/globalfunctions.cpp (+56/-0)
plugins/Utils/globalfunctions.h (+42/-0)
plugins/Utils/plugin.cpp (+9/-0)
plugins/Utils/timezoneFormatter.cpp (+10/-0)
plugins/Utils/timezoneFormatter.h (+1/-0)
plugins/Utils/windowstatestorage.cpp (+36/-4)
plugins/Utils/windowstatestorage.h (+3/-0)
plugins/Wizard/CMakeLists.txt (+7/-1)
plugins/Wizard/LocalePlugin.cpp (+291/-0)
plugins/Wizard/LocalePlugin.h (+58/-0)
plugins/Wizard/PageList.cpp (+4/-4)
plugins/Wizard/Status.cpp (+143/-0)
plugins/Wizard/Status.h (+60/-0)
plugins/Wizard/System.cpp (+5/-4)
plugins/Wizard/System.h (+3/-2)
plugins/Wizard/plugin.cpp (+7/-8)
plugins/Wizard/plugin.h (+1/-2)
plugins/Wizard/timezonemodel.cpp (+239/-0)
plugins/Wizard/timezonemodel.h (+80/-0)
qml/Components/Showable.qml (+10/-0)
qml/Shell.qml (+2/-7)
qml/Stages/AbstractStage.qml (+1/-1)
qml/Stages/ApplicationWindow.qml (+3/-1)
qml/Stages/SideStage.qml (+118/-0)
qml/Stages/SpreadDelegate.qml (+10/-1)
qml/Stages/SurfaceContainer.qml (+1/-0)
qml/Stages/TabletSideStageTouchGesture.qml (+154/-0)
qml/Stages/TabletStage.qml (+402/-187)
qml/Stages/TransformedTabletSpreadDelegate.qml (+86/-58)
qml/Stages/graphics/sidestage_drag.svg (+207/-0)
qml/Stages/graphics/sidestage_open.svg (+181/-0)
qml/Wizard/CheckableSetting.qml (+11/-3)
qml/Wizard/Page.qml (+275/-58)
qml/Wizard/Pages.qml (+76/-32)
qml/Wizard/Pages/10-welcome.qml (+129/-56)
qml/Wizard/Pages/30-wifi.qml (+119/-128)
qml/Wizard/Pages/40-location.qml (+217/-76)
qml/Wizard/Pages/50-timezone.qml (+275/-0)
qml/Wizard/Pages/60-account.qml (+74/-0)
qml/Wizard/Pages/60-reporting.qml (+0/-64)
qml/Wizard/Pages/70-passwd-type.qml (+105/-79)
qml/Wizard/Pages/75-report-check.qml (+91/-0)
qml/Wizard/Pages/80-finished.qml (+106/-28)
qml/Wizard/Pages/here-terms.qml (+81/-74)
qml/Wizard/Pages/passcode-confirm.qml (+14/-31)
qml/Wizard/Pages/passcode-desktop.qml (+132/-0)
qml/Wizard/Pages/passcode-set.qml (+13/-29)
qml/Wizard/Pages/password-set.qml (+149/-0)
qml/Wizard/Pages/sim.qml (+71/-40)
qml/Wizard/PasswordMeter.qml (+106/-0)
qml/Wizard/StackButton.qml (+7/-12)
qml/Wizard/Wizard.qml (+0/-9)
qml/Wizard/WizardTextField.qml (+25/-0)
src/MouseTouchAdaptor.cpp (+53/-9)
src/MouseTouchAdaptor.h (+5/-3)
tests/libs/UbuntuGestures/tst_TouchRegistry.cpp (+59/-0)
tests/mocks/AccountsService/AccountsService.cpp (+23/-3)
tests/mocks/AccountsService/AccountsService.h (+12/-5)
tests/mocks/MeeGo/QOfono/MockOfonoSimManager.qml (+1/-0)
tests/mocks/Ubuntu/Connectivity/plugin.cpp (+1/-0)
tests/mocks/Ubuntu/SystemSettings/CMakeLists.txt (+2/-0)
tests/mocks/Ubuntu/SystemSettings/Diagnostics/CMakeLists.txt (+1/-0)
tests/mocks/Ubuntu/SystemSettings/Diagnostics/MockDiagnostics.qml (+24/-0)
tests/mocks/Ubuntu/SystemSettings/Diagnostics/qmldir (+2/-0)
tests/mocks/Ubuntu/SystemSettings/LanguagePlugin/MockLanguagePlugin.qml (+2/-2)
tests/mocks/Ubuntu/SystemSettings/SecurityPrivacy/MockSecurityPrivacy.cpp (+1/-1)
tests/mocks/Ubuntu/SystemSettings/TimeDate/CMakeLists.txt (+1/-0)
tests/mocks/Ubuntu/SystemSettings/TimeDate/MockTimeDate.qml (+25/-0)
tests/mocks/Ubuntu/SystemSettings/TimeDate/qmldir (+2/-0)
tests/mocks/Unity/Application/ApplicationManager.cpp (+10/-25)
tests/mocks/Unity/Application/ApplicationManager.h (+0/-8)
tests/mocks/Unity/Application/MirSurfaceItem.cpp (+17/-0)
tests/mocks/Unity/Application/MirSurfaceItem.h (+1/-0)
tests/mocks/Unity/Application/resources/MirSurfaceItem.qml (+59/-0)
tests/mocks/Unity/Indicators/ModelActionRootState.qml (+27/-5)
tests/mocks/Utils/CMakeLists.txt (+2/-1)
tests/mocks/Utils/plugin.cpp (+12/-0)
tests/mocks/Utils/windowstatestorage.cpp (+13/-0)
tests/mocks/Utils/windowstatestorage.h (+4/-0)
tests/mocks/Wizard/CMakeLists.txt (+6/-1)
tests/mocks/Wizard/MockSystem.cpp (+4/-2)
tests/mocks/Wizard/MockSystem.h (+2/-2)
tests/mocks/Wizard/mockplugin.cpp (+7/-8)
tests/plugins/Ubuntu/Gestures/CMakeLists.txt (+7/-1)
tests/plugins/Ubuntu/Gestures/GestureTest.cpp (+44/-0)
tests/plugins/Ubuntu/Gestures/GestureTest.h (+13/-1)
tests/plugins/Ubuntu/Gestures/tst_DirectionalDragArea.cpp (+0/-56)
tests/plugins/Ubuntu/Gestures/tst_TouchGestureArea.cpp (+270/-0)
tests/plugins/Ubuntu/Gestures/tst_TouchGestureArea.qml (+90/-0)
tests/plugins/Unity/Launcher/launchermodeltest.cpp (+1/-0)
tests/qmltests/Dash/Previews/tst_Preview.qml (+1/-0)
tests/qmltests/Stages/tst_TabletStage.qml (+143/-0)
tests/qmltests/Wizard/tst_Wizard.qml (+139/-227)
tests/qmltests/tst_OrientedShell.qml (+56/-11)
tests/utils/modules/Unity/Test/UnityTestCase.qml (+67/-11)
To merge this branch: bzr merge lp:~unity-team/unity8/disable-flaky-adt
Reviewer Review Type Date Requested Status
Unity Team Pending
Review via email: mp+289294@code.launchpad.net

This proposal has been superseded by a proposal from 2016-03-16.

Commit message

disabling some tests that are flaky in adt but can't be made fail on our machines

To post a comment you must log in.
2281. By Michael Zanetti

skip not working for some reason

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 2016-03-03 18:41:20 +0000
3+++ CMakeLists.txt 2016-03-16 20:46:19 +0000
4@@ -57,7 +57,8 @@
5 find_package(Qt5Concurrent 5.4 REQUIRED)
6 find_package(Qt5Sql 5.4 REQUIRED)
7
8-pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=13)
9+pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=14)
10+pkg_check_modules(GEONAMES REQUIRED geonames>=0.2)
11 pkg_check_modules(GIO REQUIRED gio-2.0>=2.32)
12 pkg_check_modules(GLIB REQUIRED glib-2.0>=2.32)
13 pkg_check_modules(QMENUMODEL REQUIRED qmenumodel)
14
15=== modified file 'CODING'
16--- CODING 2015-06-23 07:51:27 +0000
17+++ CODING 2016-03-16 20:46:19 +0000
18@@ -54,6 +54,8 @@
19 Notes
20 -----
21 - to navigate, utilize the mouse left button as you would your finger
22+- a three point drag (for side stage operations) can be performed by using
23+ the Shift+Control+Alt (all together) keyboard modifiers while using the mouse.
24 - to get the translations work, currently you have to do make install to
25 put the *.mo files into correct structure. We'll look for a better solution
26 later.
27
28=== modified file 'debian/control'
29--- debian/control 2016-03-10 22:42:34 +0000
30+++ debian/control 2016-03-16 20:46:19 +0000
31@@ -8,13 +8,14 @@
32 doxyqml,
33 # To allow cross-compiling to work, we also must append :native
34 # to g++ so we don't try to run armhf g++
35-# on an x86 CPU for eaxmple, when cross-compiling.
36+# on an x86 CPU for example, when cross-compiling.
37 g++:native,
38 libandroid-properties-dev,
39 graphviz,
40 gsettings-ubuntu-schemas (>= 0.0.2+14.10.20140815),
41 libconnectivity-qt1-dev (>= 0.7.1),
42 libevdev-dev,
43+ libgeonames-dev (>= 0.2),
44 libgl1-mesa-dev[!armhf] | libgl-dev[!armhf],
45 libgl1-mesa-dri,
46 libgles2-mesa-dev[armhf],
47@@ -29,7 +30,7 @@
48 libqt5xmlpatterns5-dev,
49 libsystemsettings-dev,
50 libudev-dev,
51- libunity-api-dev (>= 7.107),
52+ libunity-api-dev (>= 7.108),
53 libusermetricsoutput1-dev,
54 # Need those X11 libs touch emulation from mouse events in manual QML tests on a X11 desktop
55 libx11-dev[!armhf],
56@@ -101,7 +102,7 @@
57 qml-module-qt-labs-folderlistmodel,
58 qml-module-qtquick-xmllistmodel,
59 qml-module-qtsysteminfo,
60- qtdeclarative5-qtmir-plugin (>= 0.4.5),
61+ qtdeclarative5-qtmir-plugin (>= 0.4.8),
62 qtdeclarative5-ubuntu-telephony0.1,
63 qtdeclarative5-ubuntu-web-plugin,
64 ubuntu-system-settings,
65@@ -130,7 +131,7 @@
66 qtdeclarative5-ubuntu-ui-toolkit-plugin (>= 1.3.1845) | qtdeclarative5-ubuntu-ui-toolkit-plugin-gles (>= 1.3.1845),
67 qtdeclarative5-unity-notifications-plugin (>= 0.1.2) | unity-notifications-impl,
68 ubuntu-thumbnailer-impl-0,
69- unity-application-impl-13,
70+ unity-application-impl-14,
71 unity-notifications-impl-3,
72 unity-plugin-scopes | unity-scopes-impl,
73 unity-scopes-impl-10,
74@@ -176,7 +177,7 @@
75 Depends: ${misc:Depends},
76 ${shlibs:Depends},
77 Provides: unity-application-impl,
78- unity-application-impl-13,
79+ unity-application-impl-14,
80 Replaces: unity8-autopilot (<< 8.02+15.04.20150422-0ubuntu1)
81 Description: Fake environment for running Unity 8 shell
82 Provides fake implementations of some QML modules used by Unity 8 shell
83
84=== modified file 'libs/UbuntuGestures/Timer.h'
85--- libs/UbuntuGestures/Timer.h 2015-04-24 13:19:24 +0000
86+++ libs/UbuntuGestures/Timer.h 2016-03-16 20:46:19 +0000
87@@ -35,6 +35,11 @@
88 virtual int interval() const = 0;
89 virtual void setInterval(int msecs) = 0;
90 virtual void start() { m_isRunning = true; }
91+ virtual void start(int msecs)
92+ {
93+ setInterval(msecs);
94+ start();
95+ }
96 virtual void stop() { m_isRunning = false; }
97 bool isRunning() const { return m_isRunning; }
98 virtual bool isSingleShot() const = 0;
99
100=== modified file 'libs/UbuntuGestures/TouchRegistry.cpp'
101--- libs/UbuntuGestures/TouchRegistry.cpp 2015-09-14 09:11:08 +0000
102+++ libs/UbuntuGestures/TouchRegistry.cpp 2016-03-16 20:46:19 +0000
103@@ -359,10 +359,6 @@
104
105 void TouchRegistry::requestTouchOwnership(int id, QQuickItem *candidate)
106 {
107- #if TOUCHREGISTRY_DEBUG
108- UG_DEBUG << "requestTouchOwnership id " << id << "candidate" << candidate;
109- #endif
110-
111 Pool<TouchInfo>::Iterator touchInfo = findTouchInfo(id);
112 if (!touchInfo) { qFatal("TouchRegistry: Failed to find TouchInfo"); }
113
114@@ -379,6 +375,9 @@
115 break;
116 }
117 }
118+ #if TOUCHREGISTRY_DEBUG
119+ UG_DEBUG << "requestTouchOwnership id " << id << "candidate" << candidate << "index: " << candidateIndex;
120+ #endif
121
122 // add it as a candidate if not present yet
123 if (candidateIndex < 0) {
124@@ -533,16 +532,22 @@
125 << " gained) to candidate" << candidates[0].item;
126 #endif
127
128+ // need to take a copy of the item list in case
129+ // we call back in to remove candidate during the lost ownership event.
130+ QList<QPointer<QQuickItem>> items;
131+ Q_FOREACH(const CandidateInfo& info, candidates) {
132+ items << info.item;
133+ }
134+
135 TouchOwnershipEvent gainedOwnershipEvent(id, true /*gained*/);
136- QCoreApplication::sendEvent(candidates[0].item, &gainedOwnershipEvent);
137-
138+ QCoreApplication::sendEvent(items[0], &gainedOwnershipEvent);
139
140 TouchOwnershipEvent lostOwnershipEvent(id, false /*gained*/);
141- for (int i = 1; i < candidates.count(); ++i) {
142+ for (int i = 1; i < items.count(); ++i) {
143 #if TOUCHREGISTRY_DEBUG
144- UG_DEBUG << "sending TouchWonershipEvent(id =" << id << " lost) to candidate"
145- << candidates[i].item;
146+ UG_DEBUG << "sending TouchOwnershipEvent(id =" << id << " lost) to candidate"
147+ << items[i];
148 #endif
149- QCoreApplication::sendEvent(candidates[i].item, &lostOwnershipEvent);
150+ QCoreApplication::sendEvent(items[i], &lostOwnershipEvent);
151 }
152 }
153
154=== modified file 'plugins/AccountsService/AccountsService.cpp'
155--- plugins/AccountsService/AccountsService.cpp 2016-02-25 10:57:17 +0000
156+++ plugins/AccountsService/AccountsService.cpp 2016-03-16 20:46:19 +0000
157@@ -1,5 +1,5 @@
158 /*
159- * Copyright (C) 2013 Canonical, Ltd.
160+ * Copyright (C) 2013, 2015 Canonical, Ltd.
161 *
162 * This program is free software; you can redistribute it and/or modify
163 * it under the terms of the GNU General Public License as published by
164@@ -12,8 +12,6 @@
165 *
166 * You should have received a copy of the GNU General Public License
167 * along with this program. If not, see <http://www.gnu.org/licenses/>.
168- *
169- * Author: Michael Terry <michael.terry@canonical.com>
170 */
171
172 #include "AccountsService.h"
173@@ -34,6 +32,7 @@
174
175 #define PROP_BACKGROUND_FILE QStringLiteral("BackgroundFile")
176 #define PROP_DEMO_EDGES QStringLiteral("demo-edges")
177+#define PROP_EMAIL QStringLiteral("Email")
178 #define PROP_ENABLE_INDICATORS_WHILE_LOCKED QStringLiteral("EnableIndicatorsWhileLocked")
179 #define PROP_ENABLE_LAUNCHER_WHILE_LOCKED QStringLiteral("EnableLauncherWhileLocked")
180 #define PROP_FAILED_LOGINS QStringLiteral("FailedLogins")
181@@ -44,6 +43,7 @@
182 #define PROP_MOUSE_PRIMARY_BUTTON QStringLiteral("MousePrimaryButton")
183 #define PROP_MOUSE_SCROLL_SPEED QStringLiteral("MouseScrollSpeed")
184 #define PROP_PASSWORD_DISPLAY_HINT QStringLiteral("PasswordDisplayHint")
185+#define PROP_REAL_NAME QStringLiteral("RealName")
186 #define PROP_STATS_WELCOME_SCREEN QStringLiteral("StatsWelcomeScreen")
187 #define PROP_TOUCHPAD_CURSOR_SPEED QStringLiteral("TouchpadCursorSpeed")
188 #define PROP_TOUCHPAD_DISABLE_WHILE_TYPING QStringLiteral("TouchpadDisableWhileTyping")
189@@ -80,6 +80,8 @@
190 connect(m_service, &AccountsServiceDBusAdaptor::maybeChanged, this, &AccountsService::onMaybeChanged);
191
192 registerProperty(IFACE_ACCOUNTS_USER, PROP_BACKGROUND_FILE, QStringLiteral("backgroundFileChanged"));
193+ registerProperty(IFACE_ACCOUNTS_USER, PROP_EMAIL, QStringLiteral("emailChanged"));
194+ registerProperty(IFACE_ACCOUNTS_USER, PROP_REAL_NAME, QStringLiteral("realNameChanged"));
195 registerProperty(IFACE_LOCATION_HERE, PROP_LICENSE_ACCEPTED, QStringLiteral("hereEnabledChanged"));
196 registerProperty(IFACE_LOCATION_HERE, PROP_LICENSE_BASE_PATH, QStringLiteral("hereLicensePathChanged"));
197 registerProperty(IFACE_UBUNTU_SECURITY, PROP_ENABLE_LAUNCHER_WHILE_LOCKED, QStringLiteral("enableLauncherWhileLockedChanged"));
198@@ -206,6 +208,28 @@
199 return !value.toString().isNull();
200 }
201
202+QString AccountsService::realName() const
203+{
204+ auto value = getProperty(IFACE_ACCOUNTS_USER, PROP_REAL_NAME);
205+ return value.toString();
206+}
207+
208+void AccountsService::setRealName(const QString &realName)
209+{
210+ setProperty(IFACE_ACCOUNTS_USER, PROP_REAL_NAME, realName);
211+}
212+
213+QString AccountsService::email() const
214+{
215+ auto value = getProperty(IFACE_ACCOUNTS_USER, PROP_EMAIL);
216+ return value.toString();
217+}
218+
219+void AccountsService::setEmail(const QString &email)
220+{
221+ setProperty(IFACE_ACCOUNTS_USER, PROP_EMAIL, email);
222+}
223+
224 uint AccountsService::failedLogins() const
225 {
226 return getProperty(IFACE_UNITY_PRIVATE, PROP_FAILED_LOGINS).toUInt();
227
228=== modified file 'plugins/AccountsService/AccountsService.h'
229--- plugins/AccountsService/AccountsService.h 2016-02-25 10:57:17 +0000
230+++ plugins/AccountsService/AccountsService.h 2016-03-16 20:46:19 +0000
231@@ -1,5 +1,5 @@
232 /*
233- * Copyright (C) 2013 Canonical, Ltd.
234+ * Copyright (C) 2013, 2015 Canonical, Ltd.
235 *
236 * This program is free software; you can redistribute it and/or modify
237 * it under the terms of the GNU General Public License as published by
238@@ -12,8 +12,6 @@
239 *
240 * You should have received a copy of the GNU General Public License
241 * along with this program. If not, see <http://www.gnu.org/licenses/>.
242- *
243- * Authors: Michael Terry <michael.terry@canonical.com>
244 */
245
246 #ifndef UNITY_ACCOUNTSSERVICE_H
247@@ -68,6 +66,8 @@
248 Q_PROPERTY(bool hereLicensePathValid // qml sees a null string as "", so we use proxy setting for that
249 READ hereLicensePathValid
250 NOTIFY hereLicensePathChanged)
251+ Q_PROPERTY(QString realName READ realName WRITE setRealName NOTIFY realNameChanged)
252+ Q_PROPERTY(QString email READ email WRITE setEmail NOTIFY emailChanged)
253
254 public:
255 enum PasswordDisplayHint {
256@@ -93,6 +93,10 @@
257 void setHereEnabled(bool enabled);
258 QString hereLicensePath() const;
259 bool hereLicensePathValid() const;
260+ QString realName() const;
261+ void setRealName(const QString &realName);
262+ QString email() const;
263+ void setEmail(const QString &email);
264
265 Q_SIGNALS:
266 void userChanged();
267@@ -105,6 +109,8 @@
268 void failedLoginsChanged();
269 void hereEnabledChanged();
270 void hereLicensePathChanged();
271+ void realNameChanged();
272+ void emailChanged();
273
274 private Q_SLOTS:
275 void onPropertiesChanged(const QString &user, const QString &interface, const QStringList &changed);
276
277=== added file 'plugins/Cursor/Cursor.qmltypes'
278--- plugins/Cursor/Cursor.qmltypes 1970-01-01 00:00:00 +0000
279+++ plugins/Cursor/Cursor.qmltypes 2016-03-16 20:46:19 +0000
280@@ -0,0 +1,78 @@
281+import QtQuick.tooling 1.1
282+
283+// This file describes the plugin-supplied types contained in the library.
284+// It is used for QML tooling purposes only.
285+//
286+// This file was auto-generated by:
287+// 'qmlplugindump -notrelocatable Cursor 1.0 plugins'
288+
289+Module {
290+ Component {
291+ name: "MirMousePointerInterface"
292+ defaultProperty: "data"
293+ prototype: "QQuickItem"
294+ Property { name: "cursorName"; type: "string"; isReadonly: true }
295+ Property { name: "themeName"; type: "string"; isReadonly: true }
296+ Property { name: "hotspotX"; type: "double"; isReadonly: true }
297+ Property { name: "hotspotY"; type: "double"; isReadonly: true }
298+ Signal {
299+ name: "cursorNameChanged"
300+ Parameter { name: "name"; type: "string" }
301+ }
302+ Signal {
303+ name: "themeNameChanged"
304+ Parameter { name: "name"; type: "string" }
305+ }
306+ Signal {
307+ name: "hotspotXChanged"
308+ Parameter { name: "value"; type: "double" }
309+ }
310+ Signal {
311+ name: "hotspotYChanged"
312+ Parameter { name: "value"; type: "double" }
313+ }
314+ Method {
315+ name: "handleMouseEvent"
316+ Parameter { name: "timestamp"; type: "ulong" }
317+ Parameter { name: "movement"; type: "QPointF" }
318+ Parameter { name: "buttons"; type: "Qt::MouseButtons" }
319+ Parameter { name: "modifiers"; type: "Qt::KeyboardModifiers" }
320+ }
321+ Method {
322+ name: "handleWheelEvent"
323+ Parameter { name: "timestamp"; type: "ulong" }
324+ Parameter { name: "angleDelta"; type: "QPoint" }
325+ Parameter { name: "modifiers"; type: "Qt::KeyboardModifiers" }
326+ }
327+ }
328+ Component {
329+ name: "MousePointer"
330+ defaultProperty: "data"
331+ prototype: "MirMousePointerInterface"
332+ exports: ["Cursor/MousePointer 1.0"]
333+ exportMetaObjectRevisions: [0]
334+ Signal {
335+ name: "pushedLeftBoundary"
336+ Parameter { name: "amount"; type: "double" }
337+ Parameter { name: "buttons"; type: "Qt::MouseButtons" }
338+ }
339+ Signal {
340+ name: "pushedRightBoundary"
341+ Parameter { name: "amount"; type: "double" }
342+ Parameter { name: "buttons"; type: "Qt::MouseButtons" }
343+ }
344+ Method {
345+ name: "handleMouseEvent"
346+ Parameter { name: "timestamp"; type: "ulong" }
347+ Parameter { name: "movement"; type: "QPointF" }
348+ Parameter { name: "buttons"; type: "Qt::MouseButtons" }
349+ Parameter { name: "modifiers"; type: "Qt::KeyboardModifiers" }
350+ }
351+ Method {
352+ name: "handleWheelEvent"
353+ Parameter { name: "timestamp"; type: "ulong" }
354+ Parameter { name: "angleDelta"; type: "QPoint" }
355+ Parameter { name: "modifiers"; type: "Qt::KeyboardModifiers" }
356+ }
357+ }
358+}
359
360=== added file 'plugins/GlobalShortcut/GlobalShortcut.qmltypes'
361--- plugins/GlobalShortcut/GlobalShortcut.qmltypes 1970-01-01 00:00:00 +0000
362+++ plugins/GlobalShortcut/GlobalShortcut.qmltypes 2016-03-16 20:46:19 +0000
363@@ -0,0 +1,31 @@
364+import QtQuick.tooling 1.1
365+
366+// This file describes the plugin-supplied types contained in the library.
367+// It is used for QML tooling purposes only.
368+//
369+// This file was auto-generated by:
370+// 'qmlplugindump -notrelocatable GlobalShortcut 1.0 plugins'
371+
372+Module {
373+ Component {
374+ name: "GlobalShortcut"
375+ defaultProperty: "data"
376+ prototype: "QQuickItem"
377+ exports: ["GlobalShortcut/GlobalShortcut 1.0"]
378+ exportMetaObjectRevisions: [0]
379+ Property { name: "shortcut"; type: "QVariant" }
380+ Property { name: "active"; type: "bool" }
381+ Signal {
382+ name: "shortcutChanged"
383+ Parameter { name: "shortcut"; type: "QVariant" }
384+ }
385+ Signal {
386+ name: "triggered"
387+ Parameter { name: "shortcut"; type: "string" }
388+ }
389+ Signal {
390+ name: "activeChanged"
391+ Parameter { name: "active"; type: "bool" }
392+ }
393+ }
394+}
395
396=== modified file 'plugins/Ubuntu/Gestures/CMakeLists.txt'
397--- plugins/Ubuntu/Gestures/CMakeLists.txt 2015-05-27 09:37:34 +0000
398+++ plugins/Ubuntu/Gestures/CMakeLists.txt 2016-03-16 20:46:19 +0000
399@@ -11,6 +11,7 @@
400 PressedOutsideNotifier.cpp
401 TouchDispatcher.cpp
402 TouchGate.cpp
403+ TouchGestureArea.cpp
404 )
405
406 add_definitions(-DUBUNTUGESTURESQML_LIBRARY)
407
408=== modified file 'plugins/Ubuntu/Gestures/DirectionalDragArea.cpp'
409--- plugins/Ubuntu/Gestures/DirectionalDragArea.cpp 2015-12-16 18:28:21 +0000
410+++ plugins/Ubuntu/Gestures/DirectionalDragArea.cpp 2016-03-16 20:46:19 +0000
411@@ -606,6 +606,9 @@
412 Q_EMIT q->pressedChanged(true);
413 break;
414 case Recognized:
415+ if (oldStatus == WaitingForTouch) { // for immediate recognition
416+ Q_EMIT q->pressedChanged(true);
417+ }
418 Q_EMIT q->draggingChanged(true);
419 break;
420 default:
421
422=== modified file 'plugins/Ubuntu/Gestures/Gestures.qmltypes'
423--- plugins/Ubuntu/Gestures/Gestures.qmltypes 2015-02-13 09:01:16 +0000
424+++ plugins/Ubuntu/Gestures/Gestures.qmltypes 2016-03-16 20:46:19 +0000
425@@ -34,7 +34,8 @@
426 "Leftwards": 1,
427 "Downwards": 2,
428 "Upwards": 3,
429- "Horizontal": 4
430+ "Horizontal": 4,
431+ "Vertical": 5
432 }
433 }
434 Method {
435@@ -59,14 +60,6 @@
436 prototype: "QQuickItem"
437 exports: ["Ubuntu.Gestures/DirectionalDragArea 0.1"]
438 exportMetaObjectRevisions: [0]
439- Enum {
440- name: "Status"
441- values: {
442- "WaitingForTouch": 0,
443- "Undecided": 1,
444- "Recognized": 2
445- }
446- }
447 Property { name: "direction"; type: "Direction::Type" }
448 Property { name: "distance"; type: "double"; isReadonly: true }
449 Property { name: "sceneDistance"; type: "double"; isReadonly: true }
450@@ -74,27 +67,22 @@
451 Property { name: "touchY"; type: "double"; isReadonly: true }
452 Property { name: "touchSceneX"; type: "double"; isReadonly: true }
453 Property { name: "touchSceneY"; type: "double"; isReadonly: true }
454- Property { name: "status"; type: "Status"; isReadonly: true }
455 Property { name: "dragging"; type: "bool"; isReadonly: true }
456- Property { name: "maxDeviation"; type: "double" }
457- Property { name: "wideningAngle"; type: "double" }
458- Property { name: "distanceThreshold"; type: "double" }
459- Property { name: "minSpeed"; type: "double" }
460- Property { name: "maxSilenceTime"; type: "int" }
461- Property { name: "compositionTime"; type: "int" }
462+ Property { name: "pressed"; type: "bool"; isReadonly: true }
463+ Property { name: "immediateRecognition"; type: "bool" }
464 Signal {
465 name: "directionChanged"
466 Parameter { name: "direction"; type: "Direction::Type" }
467 }
468 Signal {
469- name: "statusChanged"
470- Parameter { name: "value"; type: "Status" }
471- }
472- Signal {
473 name: "draggingChanged"
474 Parameter { name: "value"; type: "bool" }
475 }
476 Signal {
477+ name: "pressedChanged"
478+ Parameter { name: "value"; type: "bool" }
479+ }
480+ Signal {
481 name: "distanceChanged"
482 Parameter { name: "value"; type: "double" }
483 }
484@@ -103,30 +91,6 @@
485 Parameter { name: "value"; type: "double" }
486 }
487 Signal {
488- name: "maxDeviationChanged"
489- Parameter { name: "value"; type: "double" }
490- }
491- Signal {
492- name: "wideningAngleChanged"
493- Parameter { name: "value"; type: "double" }
494- }
495- Signal {
496- name: "distanceThresholdChanged"
497- Parameter { name: "value"; type: "double" }
498- }
499- Signal {
500- name: "minSpeedChanged"
501- Parameter { name: "value"; type: "double" }
502- }
503- Signal {
504- name: "maxSilenceTimeChanged"
505- Parameter { name: "value"; type: "int" }
506- }
507- Signal {
508- name: "compositionTimeChanged"
509- Parameter { name: "value"; type: "int" }
510- }
511- Signal {
512 name: "touchXChanged"
513 Parameter { name: "value"; type: "double" }
514 }
515@@ -142,7 +106,35 @@
516 name: "touchSceneYChanged"
517 Parameter { name: "value"; type: "double" }
518 }
519- Signal { name: "tapped" }
520+ Signal {
521+ name: "immediateRecognitionChanged"
522+ Parameter { name: "value"; type: "bool" }
523+ }
524+ Method { name: "removeTimeConstraints" }
525+ }
526+ Component {
527+ name: "FloatingFlickable"
528+ defaultProperty: "data"
529+ prototype: "QQuickItem"
530+ exports: ["Ubuntu.Gestures/FloatingFlickable 0.1"]
531+ exportMetaObjectRevisions: [0]
532+ Property { name: "contentWidth"; type: "double" }
533+ Property { name: "contentHeight"; type: "double" }
534+ Property { name: "contentX"; type: "double" }
535+ Property { name: "contentY"; type: "double" }
536+ Property { name: "direction"; type: "Direction::Type" }
537+ }
538+ Component {
539+ name: "GestureTouchPoint"
540+ prototype: "QObject"
541+ exports: ["Ubuntu.Gestures/GestureTouchPoint 0.1"]
542+ isCreatable: false
543+ exportMetaObjectRevisions: [0]
544+ Property { name: "pointId"; type: "int"; isReadonly: true }
545+ Property { name: "pressed"; type: "bool"; isReadonly: true }
546+ Property { name: "x"; type: "double"; isReadonly: true }
547+ Property { name: "y"; type: "double"; isReadonly: true }
548+ Property { name: "dragging"; type: "bool"; isReadonly: true }
549 }
550 Component {
551 name: "PressedOutsideNotifier"
552@@ -163,6 +155,55 @@
553 name: "targetItemChanged"
554 Parameter { name: "item"; type: "QQuickItem"; isPointer: true }
555 }
556- Signal { name: "pressed" }
557+ }
558+ Component {
559+ name: "TouchGestureArea"
560+ defaultProperty: "data"
561+ prototype: "QQuickItem"
562+ exports: ["Ubuntu.Gestures/TouchGestureArea 0.1"]
563+ exportMetaObjectRevisions: [0]
564+ Enum {
565+ name: "Status"
566+ values: {
567+ "WaitingForTouch": 0,
568+ "Undecided": 1,
569+ "Recognized": 2,
570+ "Rejected": 3
571+ }
572+ }
573+ Property { name: "touchPoints"; type: "GestureTouchPoint"; isList: true; isReadonly: true }
574+ Property { name: "dragging"; type: "bool"; isReadonly: true }
575+ Property { name: "minimumTouchPoints"; type: "int" }
576+ Property { name: "maximumTouchPoints"; type: "int" }
577+ Signal {
578+ name: "statusChanged"
579+ Parameter { name: "status"; type: "Status" }
580+ }
581+ Signal { name: "touchPointsUpdated" }
582+ Signal {
583+ name: "draggingChanged"
584+ Parameter { name: "dragging"; type: "bool" }
585+ }
586+ Signal {
587+ name: "minimumTouchPointsChanged"
588+ Parameter { name: "value"; type: "bool" }
589+ }
590+ Signal {
591+ name: "maximumTouchPointsChanged"
592+ Parameter { name: "value"; type: "bool" }
593+ }
594+ Signal {
595+ name: "pressed"
596+ Parameter { name: "points"; type: "QList<QObject*>" }
597+ }
598+ Signal {
599+ name: "released"
600+ Parameter { name: "points"; type: "QList<QObject*>" }
601+ }
602+ Signal {
603+ name: "updated"
604+ Parameter { name: "points"; type: "QList<QObject*>" }
605+ }
606+ Signal { name: "clicked" }
607 }
608 }
609
610=== added file 'plugins/Ubuntu/Gestures/TouchGestureArea.cpp'
611--- plugins/Ubuntu/Gestures/TouchGestureArea.cpp 1970-01-01 00:00:00 +0000
612+++ plugins/Ubuntu/Gestures/TouchGestureArea.cpp 2016-03-16 20:46:19 +0000
613@@ -0,0 +1,875 @@
614+/*
615+ * Copyright (C) 2016 Canonical, Ltd.
616+ *
617+ * This program is free software; you can redistribute it and/or modify
618+ * it under the terms of the GNU General Public License as published by
619+ * the Free Software Foundation; version 3.
620+ *
621+ * This program is distributed in the hope that it will be useful,
622+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
623+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
624+ * GNU General Public License for more details.
625+ *
626+ * You should have received a copy of the GNU General Public License
627+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
628+ */
629+
630+#include "TouchGestureArea.h"
631+
632+// local
633+#include "TouchOwnershipEvent.h"
634+#include "TouchRegistry.h"
635+#include "UnownedTouchEvent.h"
636+
637+#include <QGuiApplication>
638+#include <QStyleHints>
639+#include <private/qquickwindow_p.h>
640+
641+#define TOUCHGESTUREAREA_DEBUG 0
642+
643+// TODO - understand more about why we lose touch event releases.
644+// Workaround for now is to monitor all the touch points from first touch till
645+// all have been released; no matter if we've rejected the gesture.
646+
647+namespace {
648+
649+struct InternalStatus {
650+ enum Status {
651+ WaitingForTouch,
652+ WaitingForMoreTouches,
653+ WaitingForOwnership, //Recognizing,
654+ Recognized,
655+ WaitingForRejection,
656+ Rejected
657+ };
658+};
659+
660+TouchGestureArea::Status internalStatusToGestureStatus(int internalStatus) {
661+ switch (internalStatus) {
662+ case InternalStatus::WaitingForTouch: return TouchGestureArea::WaitingForTouch;
663+ case InternalStatus::WaitingForMoreTouches: return TouchGestureArea::Undecided;
664+ case InternalStatus::WaitingForOwnership: return TouchGestureArea::Undecided;
665+ case InternalStatus::Recognized: return TouchGestureArea::Recognized;
666+ case InternalStatus::WaitingForRejection: return TouchGestureArea::Recognized;
667+ case InternalStatus::Rejected: return TouchGestureArea::Rejected;
668+ }
669+ return TouchGestureArea::WaitingForTouch;
670+}
671+
672+}
673+
674+#if TOUCHGESTUREAREA_DEBUG
675+#define tgaDebug(params) qDebug().nospace() << "[TGA(" << qPrintable(objectName()) << ")] " << params
676+#include "DebugHelpers.h"
677+
678+namespace {
679+
680+const char *statusToString(int status)
681+{
682+ if (status == InternalStatus::WaitingForTouch) {
683+ return "WaitingForTouch";
684+ } else if (status == InternalStatus::WaitingForMoreTouches) {
685+ return "WaitingForMoreTouches";
686+ } else if (status == InternalStatus::WaitingForOwnership) {
687+ return "WaitingForOwnership";
688+ } else if (status == InternalStatus::Rejected) {
689+ return "Rejected";
690+ } else if (status == InternalStatus::WaitingForRejection) {
691+ return "WaitingForRejection";
692+ } else {
693+ return "Recognized";
694+ }
695+ return "Unknown";
696+}
697+
698+QString touchState(Qt::TouchPointState state) {
699+ switch (state) {
700+ case Qt::TouchPointPressed: return "pressed";
701+ case Qt::TouchPointMoved: return "moved";
702+ case Qt::TouchPointStationary: return "stationary";
703+ case Qt::TouchPointReleased: return "released";
704+ break;
705+ }
706+ return "unknown";
707+}
708+
709+QString touchesString(const QList<QObject*> touches) {
710+ QString str;
711+ Q_FOREACH(QObject* object, touches) {
712+ GestureTouchPoint* touchPoint = qobject_cast<GestureTouchPoint*>(object);
713+ if (touchPoint) {
714+ str += QStringLiteral("[%1 @ (%2, %3)], ").arg(touchPoint->id())
715+ .arg(touchPoint->x())
716+ .arg(touchPoint->y());
717+ }
718+ }
719+ return str;
720+}
721+
722+QString touchEventString(QTouchEvent* event) {
723+ if (!event) return QString();
724+ QString str;
725+ Q_FOREACH(const auto& touchPoint, event->touchPoints()) {
726+ str += QStringLiteral("[%1:%2 @ (%3, %4)], ").arg(touchPoint.id())
727+ .arg(touchState(touchPoint.state()))
728+ .arg(touchPoint.pos().x())
729+ .arg(touchPoint.pos().y());
730+ }
731+ return str;
732+}
733+
734+
735+} // namespace {
736+#else // TOUCHGESTUREAREA_DEBUG
737+#define tgaDebug(params) ((void)0)
738+#endif // TOUCHGESTUREAREA_DEBUG
739+
740+TouchGestureArea::TouchGestureArea(QQuickItem* parent)
741+ : QQuickItem(parent)
742+ , m_status(WaitingForTouch)
743+ , m_recognitionTimer(nullptr)
744+ , m_dragging(false)
745+ , m_minimumTouchPoints(1)
746+ , m_maximumTouchPoints(INT_MAX)
747+ , m_recognitionPeriod(50)
748+ , m_releaseRejectPeriod(100)
749+{
750+ setRecognitionTimer(new UbuntuGestures::Timer(this));
751+ m_recognitionTimer->setInterval(m_recognitionPeriod);
752+ m_recognitionTimer->setSingleShot(true);
753+}
754+
755+TouchGestureArea::~TouchGestureArea()
756+{
757+ clearTouchLists();
758+ qDeleteAll(m_liveTouchPoints);
759+ m_liveTouchPoints.clear();
760+ qDeleteAll(m_cachedTouchPoints);
761+ m_cachedTouchPoints.clear();
762+}
763+
764+bool TouchGestureArea::event(QEvent *event)
765+{
766+ // Process unowned touch events (handles update/release for incomplete gestures)
767+ if (event->type() == TouchOwnershipEvent::touchOwnershipEventType()) {
768+ touchOwnershipEvent(static_cast<TouchOwnershipEvent *>(event));
769+ return true;
770+ } else if (event->type() == UnownedTouchEvent::unownedTouchEventType()) {
771+ unownedTouchEvent(static_cast<UnownedTouchEvent *>(event)->touchEvent());
772+ return true;
773+ }
774+
775+ return QQuickItem::event(event);
776+}
777+
778+void TouchGestureArea::touchOwnershipEvent(TouchOwnershipEvent *event)
779+{
780+ int touchId = event->touchId();
781+ tgaDebug("touchOwnershipEvent - id:" << touchId << ", gained:" << event->gained());
782+
783+ if (event->gained()) {
784+ grabTouchPoints(QVector<int>() << touchId);
785+ m_candidateTouches.remove(touchId);
786+ TouchRegistry::instance()->addTouchWatcher(touchId, this);
787+ m_watchedTouches.insert(touchId);
788+
789+ if (m_watchedTouches.count() >= m_minimumTouchPoints) {
790+ setInternalStatus(InternalStatus::Recognized);
791+ }
792+ } else {
793+ rejectGesture();
794+ }
795+}
796+
797+void TouchGestureArea::touchEvent(QTouchEvent *event)
798+{
799+ if (!isEnabled() || !isVisible()) {
800+ tgaDebug(QString("NOT ENABLED touchEvent(%1) %2").arg(statusToString(m_status)).arg(touchEventString(event)));
801+ QQuickItem::touchEvent(event);
802+ return;
803+ }
804+
805+ tgaDebug(QString("touchEvent(%1) %2").arg(statusToString(m_status)).arg(touchEventString(event)));
806+
807+ switch (m_status) {
808+ case InternalStatus::WaitingForTouch:
809+ touchEvent_waitingForTouch(event);
810+ break;
811+ case InternalStatus::WaitingForMoreTouches:
812+ touchEvent_waitingForMoreTouches(event);
813+ break;
814+ case InternalStatus::WaitingForOwnership:
815+ touchEvent_waitingForOwnership(event);
816+ break;
817+ case InternalStatus::Recognized:
818+ case InternalStatus::WaitingForRejection:
819+ touchEvent_recognized(event);
820+ break;
821+ case InternalStatus::Rejected:
822+ touchEvent_rejected(event);
823+ break;
824+ default: // Recognized:
825+ break;
826+ }
827+
828+ updateTouchPoints(event);
829+}
830+
831+void TouchGestureArea::touchEvent_waitingForTouch(QTouchEvent *event)
832+{
833+ Q_FOREACH(const QTouchEvent::TouchPoint& touchPoint, event->touchPoints()) {
834+ Qt::TouchPointState touchPointState = touchPoint.state();
835+ int touchId = touchPoint.id();
836+
837+ if (touchPointState == Qt::TouchPointPressed) {
838+ if (!m_candidateTouches.contains(touchId)) {
839+ TouchRegistry::instance()->addCandidateOwnerForTouch(touchId, this);
840+ m_candidateTouches.insert(touchId);
841+ }
842+ }
843+ }
844+ event->ignore();
845+
846+ if (m_candidateTouches.count() > m_maximumTouchPoints) {
847+ rejectGesture();
848+ } else if (m_candidateTouches.count() >= m_minimumTouchPoints) {
849+ setInternalStatus(InternalStatus::WaitingForOwnership);
850+
851+ QSet<int> tmpCandidates(m_candidateTouches);
852+ Q_FOREACH(int candidateTouchId, tmpCandidates) {
853+ TouchRegistry::instance()->requestTouchOwnership(candidateTouchId, this);
854+ }
855+ // We accept the gesture; so don't pass to lower items
856+ event->accept();
857+ } else if (m_candidateTouches.count() > 0) {
858+ setInternalStatus(InternalStatus::WaitingForMoreTouches);
859+ }
860+}
861+
862+void TouchGestureArea::touchEvent_waitingForMoreTouches(QTouchEvent *event)
863+{
864+ Q_FOREACH(const QTouchEvent::TouchPoint& touchPoint, event->touchPoints()) {
865+ Qt::TouchPointState touchPointState = touchPoint.state();
866+ int touchId = touchPoint.id();
867+
868+ if (touchPointState == Qt::TouchPointPressed) {
869+ if (!m_candidateTouches.contains(touchId)) {
870+ TouchRegistry::instance()->addCandidateOwnerForTouch(touchId, this);
871+ m_candidateTouches.insert(touchId);
872+ }
873+ }
874+ }
875+ event->ignore();
876+
877+ if (m_candidateTouches.count() > m_maximumTouchPoints) {
878+ rejectGesture();
879+ } else if (m_candidateTouches.count() >= m_minimumTouchPoints) {
880+ setInternalStatus(InternalStatus::WaitingForOwnership);
881+
882+ QSet<int> tmpCandidates(m_candidateTouches);
883+ Q_FOREACH(int candidateTouchId, tmpCandidates) {
884+ TouchRegistry::instance()->requestTouchOwnership(candidateTouchId, this);
885+ }
886+ // We accept the gesture; so don't pass to lower items
887+ event->accept();
888+ }
889+}
890+
891+void TouchGestureArea::touchEvent_waitingForOwnership(QTouchEvent *event)
892+{
893+ Q_FOREACH(const QTouchEvent::TouchPoint& touchPoint, event->touchPoints()) {
894+ Qt::TouchPointState touchPointState = touchPoint.state();
895+ int touchId = touchPoint.id();
896+
897+ if (touchPointState == Qt::TouchPointPressed) {
898+ if (!m_watchedTouches.contains(touchId)) {
899+ TouchRegistry::instance()->addTouchWatcher(touchId, this);
900+ m_watchedTouches.insert(touchId);
901+ }
902+ }
903+ }
904+}
905+
906+void TouchGestureArea::touchEvent_recognized(QTouchEvent *event)
907+{
908+ Q_FOREACH(const QTouchEvent::TouchPoint& touchPoint, event->touchPoints()) {
909+ Qt::TouchPointState touchPointState = touchPoint.state();
910+ int touchId = touchPoint.id();
911+
912+ if (touchPointState == Qt::TouchPointPressed) {
913+ if (!m_watchedTouches.contains(touchId)) {
914+ TouchRegistry::instance()->addTouchWatcher(touchId, this);
915+ m_watchedTouches.insert(touchId);
916+ }
917+ }
918+ }
919+
920+ if (m_watchedTouches.count() > m_maximumTouchPoints) {
921+ rejectGesture();
922+ } else if (m_watchedTouches.count() >= m_minimumTouchPoints &&
923+ m_status==InternalStatus::WaitingForRejection) {
924+ setInternalStatus(InternalStatus::Recognized);
925+ }
926+}
927+
928+void TouchGestureArea::touchEvent_rejected(QTouchEvent *event)
929+{
930+ Q_FOREACH(const QTouchEvent::TouchPoint& touchPoint, event->touchPoints()) {
931+ Qt::TouchPointState touchPointState = touchPoint.state();
932+ int touchId = touchPoint.id();
933+
934+ if (touchPointState == Qt::TouchPointPressed) {
935+ if (!m_watchedTouches.contains(touchId)) {
936+ TouchRegistry::instance()->addTouchWatcher(touchId, this);
937+ m_watchedTouches.insert(touchId);
938+ }
939+ }
940+ }
941+}
942+
943+void TouchGestureArea::unownedTouchEvent(QTouchEvent *unownedTouchEvent)
944+{
945+ tgaDebug(QString("unownedTouchEvent(%1) %2").arg(statusToString(m_status)).arg(touchEventString(unownedTouchEvent)));
946+
947+ // Only monitor unowned touch events for presses/releases
948+ if ((unownedTouchEvent->touchPointStates() & (Qt::TouchPointPressed|Qt::TouchPointReleased)) == 0) {
949+ return;
950+ }
951+
952+ switch (m_status) {
953+ case InternalStatus::WaitingForTouch:
954+ break;
955+ case InternalStatus::WaitingForMoreTouches:
956+ unownedTouchEvent_waitingForMoreTouches(unownedTouchEvent);
957+ // do nothing
958+ break;
959+ case InternalStatus::WaitingForOwnership:
960+ unownedTouchEvent_waitingForOwnership(unownedTouchEvent);
961+ break;
962+ case InternalStatus::Recognized:
963+ case InternalStatus::WaitingForRejection:
964+ unownedTouchEvent_recognised(unownedTouchEvent);
965+ break;
966+ case InternalStatus::Rejected:
967+ unownedTouchEvent_rejected(unownedTouchEvent);
968+ break;
969+ default:
970+ break;
971+ }
972+
973+ updateTouchPoints(unownedTouchEvent);
974+}
975+
976+void TouchGestureArea::unownedTouchEvent_waitingForMoreTouches(QTouchEvent *event)
977+{
978+ Q_FOREACH(const QTouchEvent::TouchPoint& touchPoint, event->touchPoints()) {
979+ Qt::TouchPointState touchPointState = touchPoint.state();
980+ int touchId = touchPoint.id();
981+
982+ if (touchPointState == Qt::TouchPointReleased) {
983+ if (m_candidateTouches.contains(touchId)) {
984+ TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, this);
985+ m_candidateTouches.remove(touchId);
986+ }
987+ }
988+ }
989+
990+ if (m_candidateTouches.count() == 0) {
991+ setInternalStatus(InternalStatus::WaitingForTouch);
992+ }
993+}
994+
995+void TouchGestureArea::unownedTouchEvent_waitingForOwnership(QTouchEvent *event)
996+{
997+ Q_FOREACH(const QTouchEvent::TouchPoint& touchPoint, event->touchPoints()) {
998+ Qt::TouchPointState touchPointState = touchPoint.state();
999+ int touchId = touchPoint.id();
1000+
1001+ if (touchPointState == Qt::TouchPointReleased) {
1002+ if (m_candidateTouches.contains(touchId)) {
1003+ TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, this);
1004+ m_candidateTouches.remove(touchId);
1005+ }
1006+ if (m_watchedTouches.contains(touchId)) {
1007+ m_watchedTouches.remove(touchId);
1008+ }
1009+ }
1010+ }
1011+
1012+ if (m_candidateTouches.count() + m_watchedTouches.count() == 0) {
1013+ setInternalStatus(InternalStatus::WaitingForTouch);
1014+ }
1015+}
1016+
1017+void TouchGestureArea::unownedTouchEvent_recognised(QTouchEvent *event)
1018+{
1019+ Q_FOREACH(const QTouchEvent::TouchPoint& touchPoint, event->touchPoints()) {
1020+ Qt::TouchPointState touchPointState = touchPoint.state();
1021+ int touchId = touchPoint.id();
1022+
1023+ if (touchPointState == Qt::TouchPointReleased) {
1024+ if (m_watchedTouches.contains(touchId)) {
1025+ m_watchedTouches.remove(touchId);
1026+ }
1027+ }
1028+ }
1029+
1030+ if (m_watchedTouches.count() < m_minimumTouchPoints && m_status==InternalStatus::Recognized) {
1031+ setInternalStatus(InternalStatus::WaitingForRejection);
1032+ }
1033+}
1034+
1035+void TouchGestureArea::unownedTouchEvent_rejected(QTouchEvent *event)
1036+{
1037+ Q_FOREACH(const QTouchEvent::TouchPoint& touchPoint, event->touchPoints()) {
1038+ Qt::TouchPointState touchPointState = touchPoint.state();
1039+ int touchId = touchPoint.id();
1040+
1041+ if (touchPointState == Qt::TouchPointPressed) {
1042+ if (!m_watchedTouches.contains(touchId)) {
1043+ TouchRegistry::instance()->addTouchWatcher(touchId, this);
1044+ m_watchedTouches.insert(touchId);
1045+ }
1046+ }
1047+ if (touchPointState == Qt::TouchPointReleased) {
1048+ if (m_watchedTouches.contains(touchId)) {
1049+ m_watchedTouches.remove(touchId);
1050+ }
1051+ }
1052+ }
1053+
1054+ if (m_watchedTouches.count() == 0) {
1055+ setInternalStatus(InternalStatus::WaitingForTouch);
1056+ }
1057+}
1058+
1059+void TouchGestureArea::updateTouchPoints(QTouchEvent *touchEvent)
1060+{
1061+ bool added = false;
1062+ bool ended = false;
1063+ bool moved = false;
1064+
1065+ const int dragThreshold = qApp->styleHints()->startDragDistance();
1066+ const int dragVelocity = qApp->styleHints()->startDragVelocity();
1067+
1068+ clearTouchLists();
1069+ bool updateable = m_status != InternalStatus::WaitingForRejection;
1070+
1071+ Q_FOREACH(const QTouchEvent::TouchPoint& touchPoint, touchEvent->touchPoints()) {
1072+ Qt::TouchPointState touchPointState = touchPoint.state();
1073+ int touchId = touchPoint.id();
1074+
1075+ if (touchPointState & Qt::TouchPointReleased) {
1076+ GestureTouchPoint* gtp = m_liveTouchPoints.value(touchId);
1077+ if (!gtp) continue;
1078+
1079+ gtp->setPos(touchPoint.pos());
1080+ gtp->setPressed(false);
1081+ m_releasedTouchPoints.append(gtp);
1082+ m_liveTouchPoints.remove(touchId);
1083+
1084+ if (updateable) {
1085+ if (m_cachedTouchPoints.contains(touchId)) {
1086+ GestureTouchPoint* cachedPoint = m_cachedTouchPoints.take(touchId);
1087+ cachedPoint->deleteLater();
1088+ }
1089+ }
1090+ ended = true;
1091+ } else {
1092+ GestureTouchPoint* gtp = m_liveTouchPoints.value(touchPoint.id(), nullptr);
1093+ if (!gtp) {
1094+ gtp = addTouchPoint(&touchPoint);
1095+ m_pressedTouchPoints.append(gtp);
1096+
1097+ if (updateable) {
1098+ if (m_cachedTouchPoints.contains(touchId)) {
1099+ m_cachedTouchPoints[touchId]->setPos(touchPoint.pos());
1100+ } else {
1101+ m_cachedTouchPoints[touchId] = new GestureTouchPoint(*gtp);
1102+ }
1103+ }
1104+ added = true;
1105+ } else if (touchPointState & Qt::TouchPointMoved) {
1106+ gtp->setPos(touchPoint.pos());
1107+ m_movedTouchPoints.append(gtp);
1108+ moved = true;
1109+
1110+ const QPointF &currentPos = touchPoint.scenePos();
1111+ const QPointF &startPos = touchPoint.startScenePos();
1112+
1113+ bool overDragThreshold = false;
1114+ bool supportsVelocity = (touchEvent->device()->capabilities() & QTouchDevice::Velocity) && dragVelocity;
1115+ overDragThreshold |= qAbs(currentPos.x() - startPos.x()) > dragThreshold ||
1116+ qAbs(currentPos.y() - startPos.y()) > dragThreshold;
1117+ if (supportsVelocity) {
1118+ QVector2D velocityVec = touchPoint.velocity();
1119+ overDragThreshold |= qAbs(velocityVec.x()) > dragVelocity;
1120+ overDragThreshold |= qAbs(velocityVec.y()) > dragVelocity;
1121+ }
1122+
1123+ if (overDragThreshold) {
1124+ gtp->setDragging(true);
1125+ }
1126+
1127+ if (updateable) {
1128+ if (m_cachedTouchPoints.contains(touchId)) {
1129+ m_cachedTouchPoints[touchId]->setPos(touchPoint.pos());
1130+ if (overDragThreshold) {
1131+ m_cachedTouchPoints[touchId]->setDragging(true);
1132+ }
1133+ }
1134+ }
1135+ }
1136+ }
1137+ }
1138+
1139+ if (updateable) {
1140+ if (!dragging() && m_status == InternalStatus::Recognized) {
1141+ bool allWantDrag = !m_liveTouchPoints.isEmpty();
1142+ Q_FOREACH(auto point, m_liveTouchPoints) {
1143+ allWantDrag &= point->dragging();
1144+ }
1145+ // only dragging if all points are dragging.
1146+ if (allWantDrag) {
1147+ setDragging(true);
1148+ }
1149+ }
1150+
1151+ if (ended) {
1152+ if (m_liveTouchPoints.isEmpty()) {
1153+ if (!dragging()) Q_EMIT clicked();
1154+ setDragging(false);
1155+ }
1156+ tgaDebug("Released " << touchesString(m_releasedTouchPoints));
1157+ Q_EMIT released(m_releasedTouchPoints);
1158+ }
1159+ if (added) {
1160+ tgaDebug("Pressed " << touchesString(m_pressedTouchPoints));
1161+ Q_EMIT pressed(m_pressedTouchPoints);
1162+ }
1163+ if (moved) {
1164+ tgaDebug("Updated " << touchesString(m_movedTouchPoints));
1165+ Q_EMIT updated(m_movedTouchPoints);
1166+ }
1167+ if (added || ended || moved) {
1168+ Q_EMIT touchPointsUpdated();
1169+ }
1170+ }
1171+}
1172+
1173+void TouchGestureArea::clearTouchLists()
1174+{
1175+ Q_FOREACH (QObject *gtp, m_releasedTouchPoints) {
1176+ delete gtp;
1177+ }
1178+ m_releasedTouchPoints.clear();
1179+ m_pressedTouchPoints.clear();
1180+ m_movedTouchPoints.clear();
1181+}
1182+
1183+void TouchGestureArea::setInternalStatus(uint newStatus)
1184+{
1185+ if (newStatus == m_status)
1186+ return;
1187+
1188+ uint oldStatus = m_status;
1189+
1190+ m_status = newStatus;
1191+ Q_EMIT statusChanged(status());
1192+
1193+ if (oldStatus == InternalStatus::WaitingForMoreTouches || oldStatus == InternalStatus::WaitingForRejection) {
1194+ m_recognitionTimer->stop();
1195+ }
1196+
1197+ tgaDebug(statusToString(oldStatus) << " -> " << statusToString(newStatus));
1198+
1199+ switch (newStatus) {
1200+ case InternalStatus::WaitingForTouch:
1201+ resyncCachedTouchPoints();
1202+ break;
1203+ case InternalStatus::WaitingForMoreTouches:
1204+ m_recognitionTimer->start(m_recognitionPeriod);
1205+ break;
1206+ case InternalStatus::Recognized:
1207+ resyncCachedTouchPoints();
1208+ break;
1209+ case InternalStatus::WaitingForRejection:
1210+ m_recognitionTimer->start(m_releaseRejectPeriod);
1211+ break;
1212+ case InternalStatus::Rejected:
1213+ resyncCachedTouchPoints();
1214+ break;
1215+ default:
1216+ // no-op
1217+ break;
1218+ }
1219+}
1220+
1221+void TouchGestureArea::setRecognitionTimer(UbuntuGestures::AbstractTimer *timer)
1222+{
1223+ int interval = 0;
1224+ bool timerWasRunning = false;
1225+ bool wasSingleShot = false;
1226+
1227+ // can be null when called from the constructor
1228+ if (m_recognitionTimer) {
1229+ interval = m_recognitionTimer->interval();
1230+ timerWasRunning = m_recognitionTimer->isRunning();
1231+ if (m_recognitionTimer->parent() == this) {
1232+ delete m_recognitionTimer;
1233+ }
1234+ }
1235+
1236+ m_recognitionTimer = timer;
1237+ timer->setInterval(interval);
1238+ timer->setSingleShot(wasSingleShot);
1239+ connect(timer, SIGNAL(timeout()),
1240+ this, SLOT(rejectGesture()));
1241+ if (timerWasRunning) {
1242+ m_recognitionTimer->start();
1243+ }
1244+}
1245+
1246+int TouchGestureArea::status() const
1247+{
1248+ return internalStatusToGestureStatus(m_status);
1249+}
1250+
1251+bool TouchGestureArea::dragging() const
1252+{
1253+ return m_dragging;
1254+}
1255+
1256+QQmlListProperty<GestureTouchPoint> TouchGestureArea::touchPoints()
1257+{
1258+ return QQmlListProperty<GestureTouchPoint>(this,
1259+ 0,
1260+ nullptr,
1261+ TouchGestureArea::touchPoint_count,
1262+ TouchGestureArea::touchPoint_at,
1263+ 0);
1264+}
1265+
1266+int TouchGestureArea::minimumTouchPoints() const
1267+{
1268+ return m_minimumTouchPoints;
1269+}
1270+
1271+void TouchGestureArea::setMinimumTouchPoints(int value)
1272+{
1273+ if (m_minimumTouchPoints != value) {
1274+ m_minimumTouchPoints = value;
1275+ Q_EMIT minimumTouchPointsChanged(value);
1276+ }
1277+}
1278+
1279+int TouchGestureArea::maximumTouchPoints() const
1280+{
1281+ return m_maximumTouchPoints;
1282+}
1283+
1284+void TouchGestureArea::setMaximumTouchPoints(int value)
1285+{
1286+ if (m_maximumTouchPoints != value) {
1287+ m_maximumTouchPoints = value;
1288+ Q_EMIT maximumTouchPointsChanged(value);
1289+ }
1290+}
1291+
1292+int TouchGestureArea::recognitionPeriod() const
1293+{
1294+ return m_recognitionPeriod;
1295+}
1296+
1297+void TouchGestureArea::setRecognitionPeriod(int value)
1298+{
1299+ if (value != m_recognitionPeriod) {
1300+ m_recognitionPeriod = value;
1301+ Q_EMIT recognitionPeriodChanged(value);
1302+ }
1303+}
1304+
1305+int TouchGestureArea::releaseRejectPeriod() const
1306+{
1307+ return m_releaseRejectPeriod;
1308+}
1309+
1310+void TouchGestureArea::setReleaseRejectPeriod(int value)
1311+{
1312+ if (value != m_releaseRejectPeriod) {
1313+ m_releaseRejectPeriod = value;
1314+ Q_EMIT releaseRejectPeriodChanged(value);
1315+ }
1316+}
1317+
1318+void TouchGestureArea::rejectGesture()
1319+{
1320+ tgaDebug("rejectGesture");
1321+ ungrabTouchPoints();
1322+
1323+ Q_FOREACH(int touchId, m_candidateTouches) {
1324+ TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, this);
1325+ }
1326+
1327+ // Monitor the candidates
1328+ Q_FOREACH(int touchId, m_candidateTouches) {
1329+ TouchRegistry::instance()->addTouchWatcher(touchId, this);
1330+ m_watchedTouches.insert(touchId);
1331+ }
1332+ m_candidateTouches.clear();
1333+
1334+ if (m_watchedTouches.count() == 0) {
1335+ setInternalStatus(InternalStatus::WaitingForTouch);
1336+ } else {
1337+ setInternalStatus(InternalStatus::Rejected);
1338+ }
1339+}
1340+
1341+void TouchGestureArea::resyncCachedTouchPoints()
1342+{
1343+ clearTouchLists();
1344+
1345+ bool added = false;
1346+ bool ended = false;
1347+ bool moved = false;
1348+ bool wantsDrag = false;
1349+
1350+ // list of deletes
1351+ QMutableHashIterator<int, GestureTouchPoint*> removeIter(m_cachedTouchPoints);
1352+ while(removeIter.hasNext()) {
1353+ removeIter.next();
1354+ if (!m_liveTouchPoints.contains(removeIter.key())) {
1355+ m_releasedTouchPoints.append(removeIter.value());
1356+ removeIter.remove();
1357+ ended = true;
1358+ }
1359+ }
1360+
1361+ // list of adds/moves
1362+ Q_FOREACH(GestureTouchPoint* touchPoint, m_liveTouchPoints) {
1363+ if (m_cachedTouchPoints.contains(touchPoint->id())) {
1364+ GestureTouchPoint* cachedPoint = m_cachedTouchPoints[touchPoint->id()];
1365+
1366+ if (*cachedPoint != *touchPoint) {
1367+ *cachedPoint = *touchPoint;
1368+ m_movedTouchPoints.append(touchPoint);
1369+ moved = true;
1370+ }
1371+ } else {
1372+ m_cachedTouchPoints.insert(touchPoint->id(), new GestureTouchPoint(*touchPoint));
1373+ m_pressedTouchPoints.append(touchPoint);
1374+ added = true;
1375+ }
1376+ }
1377+
1378+ if (wantsDrag && !dragging()) {
1379+ setDragging(true);
1380+ }
1381+
1382+ if (ended) {
1383+ if (m_cachedTouchPoints.isEmpty()) {
1384+ if (!dragging()) Q_EMIT clicked();
1385+ setDragging(false);
1386+ }
1387+ tgaDebug("Cached Release " << touchesString(m_releasedTouchPoints));
1388+ Q_EMIT released(m_releasedTouchPoints);
1389+ }
1390+ if (added) {
1391+ tgaDebug("Cached Press " << touchesString(m_pressedTouchPoints));
1392+ Q_EMIT pressed(m_pressedTouchPoints);
1393+ }
1394+ if (moved) {
1395+ tgaDebug("Cached Update " << touchesString(m_movedTouchPoints));
1396+ Q_EMIT updated(m_movedTouchPoints);
1397+ }
1398+ if (added || ended || moved) Q_EMIT touchPointsUpdated();
1399+}
1400+
1401+int TouchGestureArea::touchPoint_count(QQmlListProperty<GestureTouchPoint> *list)
1402+{
1403+ TouchGestureArea *q = static_cast<TouchGestureArea*>(list->object);
1404+ return q->m_cachedTouchPoints.count();
1405+}
1406+
1407+GestureTouchPoint *TouchGestureArea::touchPoint_at(QQmlListProperty<GestureTouchPoint> *list, int index)
1408+{
1409+ TouchGestureArea *q = static_cast<TouchGestureArea*>(list->object);
1410+ return (q->m_cachedTouchPoints.begin()+index).value();
1411+}
1412+
1413+GestureTouchPoint* TouchGestureArea::addTouchPoint(QTouchEvent::TouchPoint const* tp)
1414+{
1415+ GestureTouchPoint* gtp = new GestureTouchPoint();
1416+ gtp->setId(tp->id());
1417+ gtp->setPressed(true);
1418+ gtp->setPos(tp->pos());
1419+ m_liveTouchPoints.insert(tp->id(), gtp);
1420+ return gtp;
1421+}
1422+
1423+void TouchGestureArea::itemChange(ItemChange change, const ItemChangeData &value)
1424+{
1425+ if (change == QQuickItem::ItemSceneChange) {
1426+ if (value.window != nullptr) {
1427+ value.window->installEventFilter(TouchRegistry::instance());
1428+ }
1429+ }
1430+}
1431+
1432+void TouchGestureArea::setDragging(bool dragging)
1433+{
1434+ if (m_dragging == dragging)
1435+ return;
1436+
1437+ tgaDebug("setDragging " << dragging);
1438+
1439+ m_dragging = dragging;
1440+ Q_EMIT draggingChanged(m_dragging);
1441+}
1442+
1443+void GestureTouchPoint::setId(int id)
1444+{
1445+ if (m_id == id)
1446+ return;
1447+ m_id = id;
1448+ Q_EMIT idChanged();
1449+}
1450+
1451+void GestureTouchPoint::setPressed(bool pressed)
1452+{
1453+ if (m_pressed == pressed)
1454+ return;
1455+ m_pressed = pressed;
1456+ Q_EMIT pressedChanged();
1457+}
1458+
1459+void GestureTouchPoint::setX(qreal x)
1460+{
1461+ if (m_x == x)
1462+ return;
1463+ m_x = x;
1464+ Q_EMIT xChanged();
1465+}
1466+
1467+void GestureTouchPoint::setY(qreal y)
1468+{
1469+ if (m_y == y)
1470+ return;
1471+ m_y = y;
1472+ Q_EMIT yChanged();
1473+}
1474+
1475+void GestureTouchPoint::setDragging(bool dragging)
1476+{
1477+ if (m_dragging == dragging)
1478+ return;
1479+
1480+ m_dragging = dragging;
1481+ Q_EMIT draggingChanged();
1482+}
1483+
1484+void GestureTouchPoint::setPos(const QPointF &pos)
1485+{
1486+ setX(pos.x());
1487+ setY(pos.y());
1488+}
1489
1490=== added file 'plugins/Ubuntu/Gestures/TouchGestureArea.h'
1491--- plugins/Ubuntu/Gestures/TouchGestureArea.h 1970-01-01 00:00:00 +0000
1492+++ plugins/Ubuntu/Gestures/TouchGestureArea.h 2016-03-16 20:46:19 +0000
1493@@ -0,0 +1,229 @@
1494+/*
1495+ * Copyright (C) 2016 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+#ifndef TOUCHGESTUREAREA_H
1511+#define TOUCHGESTUREAREA_H
1512+
1513+#include "UbuntuGesturesQmlGlobal.h"
1514+
1515+#include <QQuickItem>
1516+
1517+// lib UbuntuGestures
1518+#include <Timer.h>
1519+
1520+class TouchOwnershipEvent;
1521+class UnownedTouchEvent;
1522+
1523+class GestureTouchPoint : public QObject
1524+{
1525+ Q_OBJECT
1526+ Q_PROPERTY(int id READ id NOTIFY idChanged)
1527+ Q_PROPERTY(bool pressed READ pressed NOTIFY pressedChanged)
1528+ Q_PROPERTY(qreal x READ x NOTIFY xChanged)
1529+ Q_PROPERTY(qreal y READ y NOTIFY yChanged)
1530+ Q_PROPERTY(bool dragging READ dragging NOTIFY draggingChanged)
1531+public:
1532+ GestureTouchPoint()
1533+ : m_id(-1)
1534+ , m_pressed(false)
1535+ , m_x(0)
1536+ , m_y(0)
1537+ , m_dragging(false)
1538+ {
1539+ }
1540+
1541+ GestureTouchPoint(const GestureTouchPoint& other)
1542+ : QObject(nullptr)
1543+ {
1544+ operator=(other);
1545+ }
1546+
1547+ int id() const { return m_id; }
1548+ void setId(int id);
1549+
1550+ bool pressed() const { return m_pressed; }
1551+ void setPressed(bool pressed);
1552+
1553+ qreal x() const { return m_x; }
1554+ void setX(qreal x);
1555+
1556+ qreal y() const { return m_y; }
1557+ void setY(qreal y);
1558+
1559+ bool dragging() const { return m_dragging; }
1560+ void setDragging(bool dragging);
1561+
1562+ GestureTouchPoint& operator=(const GestureTouchPoint& rhs) {
1563+ if (&rhs == this) return *this;
1564+ m_id = rhs.m_id;
1565+ m_pressed = rhs.m_pressed;
1566+ m_x = rhs.m_x;
1567+ m_y = rhs.m_y;
1568+ m_dragging = rhs.m_dragging;
1569+ return *this;
1570+ }
1571+
1572+ bool operator=(const GestureTouchPoint& rhs) const {
1573+ if (&rhs == this) return true;
1574+ return m_id == rhs.m_id &&
1575+ m_pressed == rhs.m_pressed &&
1576+ m_x == rhs.m_x &&
1577+ m_y == rhs.m_y &&
1578+ m_dragging == rhs.m_dragging;
1579+ }
1580+ bool operator!=(const GestureTouchPoint& rhs) const { return !operator=(rhs); }
1581+
1582+ void setPos(const QPointF &pos);
1583+
1584+Q_SIGNALS:
1585+ void idChanged();
1586+ void pressedChanged();
1587+ void xChanged();
1588+ void yChanged();
1589+ void draggingChanged();
1590+
1591+private:
1592+ int m_id;
1593+ bool m_pressed;
1594+ qreal m_x;
1595+ qreal m_y;
1596+ bool m_dragging;
1597+};
1598+
1599+/*
1600+ An area that detects multi-finger gestures.
1601+
1602+ We can use this to detect gestures contstrained by a minimim and/or maximum number of touch points.
1603+ This components uses the touch registry to apply for ownership of touch points.
1604+ This way we can use the component in conjuntion with the directional drag area to compete for ownwership
1605+ or gestures; unlike the MultiPointTouchArea.
1606+ */
1607+class UBUNTUGESTURESQML_EXPORT TouchGestureArea : public QQuickItem
1608+{
1609+ Q_OBJECT
1610+ Q_ENUMS(Status)
1611+
1612+ Q_PROPERTY(int status READ status NOTIFY statusChanged)
1613+ Q_PROPERTY(bool dragging READ dragging NOTIFY draggingChanged)
1614+ Q_PROPERTY(QQmlListProperty<GestureTouchPoint> touchPoints READ touchPoints NOTIFY touchPointsUpdated)
1615+
1616+ Q_PROPERTY(int minimumTouchPoints READ minimumTouchPoints WRITE setMinimumTouchPoints NOTIFY minimumTouchPointsChanged)
1617+ Q_PROPERTY(int maximumTouchPoints READ maximumTouchPoints WRITE setMaximumTouchPoints NOTIFY maximumTouchPointsChanged)
1618+
1619+ // Time(ms) the component will wait for after receiving an initial touch to recognise a gesutre before rejecting it.
1620+ Q_PROPERTY(int recognitionPeriod READ recognitionPeriod WRITE setRecognitionPeriod NOTIFY recognitionPeriodChanged)
1621+ // Time(ms) the component will allow a recognised gesture to intermitently release a touch point before rejecting the gesture.
1622+ // This is so we will not immediately reject a gesture if there are fleeting touch point releases while dragging.
1623+ Q_PROPERTY(int releaseRejectPeriod READ releaseRejectPeriod WRITE setReleaseRejectPeriod NOTIFY releaseRejectPeriodChanged)
1624+
1625+public:
1626+ // Describes the state of the touch gesture area.
1627+ enum Status {
1628+ WaitingForTouch,
1629+ Undecided,
1630+ Recognized,
1631+ Rejected
1632+ };
1633+ TouchGestureArea(QQuickItem* parent = NULL);
1634+ ~TouchGestureArea();
1635+
1636+ bool event(QEvent *e) override;
1637+
1638+ void setRecognitionTimer(UbuntuGestures::AbstractTimer *timer);
1639+
1640+ int status() const;
1641+ bool dragging() const;
1642+ QQmlListProperty<GestureTouchPoint> touchPoints();
1643+
1644+ int minimumTouchPoints() const;
1645+ void setMinimumTouchPoints(int value);
1646+
1647+ int maximumTouchPoints() const;
1648+ void setMaximumTouchPoints(int value);
1649+
1650+ int recognitionPeriod() const;
1651+ void setRecognitionPeriod(int value);
1652+
1653+ int releaseRejectPeriod() const;
1654+ void setReleaseRejectPeriod(int value);
1655+
1656+Q_SIGNALS:
1657+ void statusChanged(int status);
1658+
1659+ void touchPointsUpdated();
1660+ void draggingChanged(bool dragging);
1661+ void minimumTouchPointsChanged(bool value);
1662+ void maximumTouchPointsChanged(bool value);
1663+ void recognitionPeriodChanged(bool value);
1664+ void releaseRejectPeriodChanged(bool value);
1665+
1666+ void pressed(const QList<QObject*>& points);
1667+ void released(const QList<QObject*>& points);
1668+ void updated(const QList<QObject*>& points);
1669+ void clicked();
1670+
1671+protected:
1672+ void itemChange(ItemChange change, const ItemChangeData &value);
1673+
1674+private Q_SLOTS:
1675+ void rejectGesture();
1676+
1677+private:
1678+ void touchEvent(QTouchEvent *event) override;
1679+ void touchEvent_waitingForTouch(QTouchEvent *event);
1680+ void touchEvent_waitingForMoreTouches(QTouchEvent *event);
1681+ void touchEvent_waitingForOwnership(QTouchEvent *event);
1682+ void touchEvent_recognized(QTouchEvent *event);
1683+ void touchEvent_rejected(QTouchEvent *event);
1684+
1685+ void unownedTouchEvent(QTouchEvent *unownedTouchEvent);
1686+ void unownedTouchEvent_waitingForMoreTouches(QTouchEvent *unownedTouchEvent);
1687+ void unownedTouchEvent_waitingForOwnership(QTouchEvent *unownedTouchEvent);
1688+ void unownedTouchEvent_recognised(QTouchEvent *unownedTouchEvent);
1689+ void unownedTouchEvent_rejected(QTouchEvent *unownedTouchEvent);
1690+
1691+ void touchOwnershipEvent(TouchOwnershipEvent *event);
1692+ void updateTouchPoints(QTouchEvent *event);
1693+
1694+ GestureTouchPoint* addTouchPoint(const QTouchEvent::TouchPoint *tp);
1695+ void clearTouchLists();
1696+ void setDragging(bool dragging);
1697+ void setInternalStatus(uint status);
1698+ void resyncCachedTouchPoints();
1699+
1700+ static int touchPoint_count(QQmlListProperty<GestureTouchPoint> *list);
1701+ static GestureTouchPoint* touchPoint_at(QQmlListProperty<GestureTouchPoint> *list, int index);
1702+
1703+ uint m_status;
1704+ QSet<int> m_candidateTouches;
1705+ QSet<int> m_watchedTouches;
1706+ UbuntuGestures::AbstractTimer *m_recognitionTimer;
1707+
1708+ bool m_dragging;
1709+ QHash<int, GestureTouchPoint*> m_liveTouchPoints;
1710+ QHash<int, GestureTouchPoint*> m_cachedTouchPoints;
1711+ QList<QObject*> m_releasedTouchPoints;
1712+ QList<QObject*> m_pressedTouchPoints;
1713+ QList<QObject*> m_movedTouchPoints;
1714+ int m_minimumTouchPoints;
1715+ int m_maximumTouchPoints;
1716+ int m_recognitionPeriod;
1717+ int m_releaseRejectPeriod;
1718+};
1719+
1720+QML_DECLARE_TYPE(GestureTouchPoint)
1721+
1722+#endif // TOUCHGESTUREAREA_H
1723
1724=== modified file 'plugins/Ubuntu/Gestures/plugin.cpp'
1725--- plugins/Ubuntu/Gestures/plugin.cpp 2015-05-27 09:37:34 +0000
1726+++ plugins/Ubuntu/Gestures/plugin.cpp 2016-03-16 20:46:19 +0000
1727@@ -21,6 +21,7 @@
1728 #include "FloatingFlickable.h"
1729 #include "PressedOutsideNotifier.h"
1730 #include "TouchGate.h"
1731+#include "TouchGestureArea.h"
1732
1733 #include <qqml.h>
1734
1735@@ -38,4 +39,6 @@
1736 qmlRegisterType<FloatingFlickable>(uri, 0, 1, "FloatingFlickable");
1737 qmlRegisterType<PressedOutsideNotifier>(uri, 0, 1, "PressedOutsideNotifier");
1738 qmlRegisterType<TouchGate>(uri, 0, 1, "TouchGate");
1739+ qmlRegisterType<TouchGestureArea>(uri, 0, 1, "TouchGestureArea");
1740+ qmlRegisterUncreatableType<GestureTouchPoint>(uri, 0, 1, "GestureTouchPoint", "Cannot create GestureTouchPoints");
1741 }
1742
1743=== added file 'plugins/Unity/InputInfo/InputInfo.qmltypes'
1744--- plugins/Unity/InputInfo/InputInfo.qmltypes 1970-01-01 00:00:00 +0000
1745+++ plugins/Unity/InputInfo/InputInfo.qmltypes 2016-03-16 20:46:19 +0000
1746@@ -0,0 +1,71 @@
1747+import QtQuick.tooling 1.1
1748+
1749+// This file describes the plugin-supplied types contained in the library.
1750+// It is used for QML tooling purposes only.
1751+//
1752+// This file was auto-generated by:
1753+// 'qmlplugindump -notrelocatable Unity.InputInfo 0.1 plugins'
1754+
1755+Module {
1756+ Component {
1757+ name: "QDeclarativeInputDeviceModel"
1758+ prototype: "QAbstractListModel"
1759+ exports: ["Unity.InputInfo/InputDeviceModel 0.1"]
1760+ exportMetaObjectRevisions: [0]
1761+ Property { name: "deviceFilter"; type: "QInputDevice::InputType" }
1762+ Property { name: "count"; type: "int"; isReadonly: true }
1763+ Signal {
1764+ name: "deviceAdded"
1765+ Parameter { name: "devicePath"; type: "string" }
1766+ }
1767+ Signal {
1768+ name: "deviceRemoved"
1769+ Parameter { name: "devicePath"; type: "string" }
1770+ }
1771+ Signal {
1772+ name: "deviceFilterChanged"
1773+ Parameter { name: "filter"; type: "QInputDevice::InputType" }
1774+ }
1775+ Method { name: "updateDeviceList" }
1776+ Method {
1777+ name: "indexOf"
1778+ type: "int"
1779+ Parameter { name: "devicePath"; type: "string" }
1780+ }
1781+ Method {
1782+ name: "get"
1783+ type: "QInputDevice*"
1784+ Parameter { name: "index"; type: "int" }
1785+ }
1786+ }
1787+ Component {
1788+ name: "QInputDevice"
1789+ prototype: "QObject"
1790+ exports: ["Unity.InputInfo/InputInfo 0.1"]
1791+ exportMetaObjectRevisions: [0]
1792+ Enum {
1793+ name: "InputType"
1794+ values: {
1795+ "Unknown": 0,
1796+ "Button": 1,
1797+ "Mouse": 2,
1798+ "TouchPad": 4,
1799+ "TouchScreen": 8,
1800+ "Keyboard": 16,
1801+ "Switch": 32
1802+ }
1803+ }
1804+ Enum {
1805+ name: "InputTypeFlags"
1806+ values: {
1807+ "Unknown": 0,
1808+ "Button": 1,
1809+ "Mouse": 2,
1810+ "TouchPad": 4,
1811+ "TouchScreen": 8,
1812+ "Keyboard": 16,
1813+ "Switch": 32
1814+ }
1815+ }
1816+ }
1817+}
1818
1819=== added file 'plugins/Unity/Platform/Platform.qmltypes'
1820--- plugins/Unity/Platform/Platform.qmltypes 1970-01-01 00:00:00 +0000
1821+++ plugins/Unity/Platform/Platform.qmltypes 2016-03-16 20:46:19 +0000
1822@@ -0,0 +1,20 @@
1823+import QtQuick.tooling 1.1
1824+
1825+// This file describes the plugin-supplied types contained in the library.
1826+// It is used for QML tooling purposes only.
1827+//
1828+// This file was auto-generated by:
1829+// 'qmlplugindump -notrelocatable Unity.Platform 1.0 plugins'
1830+
1831+Module {
1832+ Component {
1833+ name: "Platform"
1834+ prototype: "QObject"
1835+ exports: ["Unity.Platform/Platform 1.0"]
1836+ isCreatable: false
1837+ isSingleton: true
1838+ exportMetaObjectRevisions: [0]
1839+ Property { name: "chassis"; type: "string"; isReadonly: true }
1840+ Property { name: "isPC"; type: "bool"; isReadonly: true }
1841+ }
1842+}
1843
1844=== modified file 'plugins/Utils/CMakeLists.txt'
1845--- plugins/Utils/CMakeLists.txt 2016-02-11 16:59:40 +0000
1846+++ plugins/Utils/CMakeLists.txt 2016-03-16 20:46:19 +0000
1847@@ -24,6 +24,7 @@
1848 timezoneFormatter.cpp
1849 inputeventgenerator.cpp
1850 deviceconfigparser.cpp
1851+ globalfunctions.cpp
1852 plugin.cpp
1853 )
1854
1855
1856=== added file 'plugins/Utils/globalfunctions.cpp'
1857--- plugins/Utils/globalfunctions.cpp 1970-01-01 00:00:00 +0000
1858+++ plugins/Utils/globalfunctions.cpp 2016-03-16 20:46:19 +0000
1859@@ -0,0 +1,56 @@
1860+/*
1861+ * Copyright 2015 Canonical Ltd.
1862+ *
1863+ * This program is free software; you can redistribute it and/or modify
1864+ * it under the terms of the GNU Lesser General Public License as published by
1865+ * the Free Software Foundation; version 3.
1866+ *
1867+ * This program is distributed in the hope that it will be useful,
1868+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1869+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1870+ * GNU Lesser General Public License for more details.
1871+ *
1872+ * You should have received a copy of the GNU Lesser General Public License
1873+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1874+*/
1875+
1876+#include "globalfunctions.h"
1877+
1878+#pragma GCC diagnostic push
1879+#pragma GCC diagnostic ignored "-pedantic"
1880+#include <private/qquickitem_p.h>
1881+#pragma GCC diagnostic pop
1882+#include <QQmlEngine>
1883+
1884+GlobalFunctions::GlobalFunctions(QObject *parent)
1885+ : QObject(parent)
1886+{
1887+}
1888+
1889+QQuickItem *GlobalFunctions::itemAt(QQuickItem* parent, int x, int y, QJSValue matcher)
1890+{
1891+ if (!parent) return nullptr;
1892+ QList<QQuickItem *> children = QQuickItemPrivate::get(parent)->paintOrderChildItems();
1893+
1894+ for (int i = children.count() - 1; i >= 0; --i) {
1895+ QQuickItem *child = children.at(i);
1896+
1897+ // Map coordinates to the child element's coordinate space
1898+ QPointF point = parent->mapToItem(child, QPointF(x, y));
1899+ if (child->isVisible() && point.x() >= 0
1900+ && child->width() >= point.x()
1901+ && point.y() >= 0
1902+ && child->height() >= point.y()) {
1903+ if (!matcher.isCallable()) return child;
1904+
1905+ QQmlEngine* engine = qmlEngine(child);
1906+ if (!engine) return child;
1907+
1908+ QJSValue newObj = engine->newQObject(child);
1909+ if (matcher.call(QJSValueList() << newObj).toBool()) {
1910+ return child;
1911+ }
1912+ }
1913+ }
1914+ return nullptr;
1915+}
1916
1917=== added file 'plugins/Utils/globalfunctions.h'
1918--- plugins/Utils/globalfunctions.h 1970-01-01 00:00:00 +0000
1919+++ plugins/Utils/globalfunctions.h 2016-03-16 20:46:19 +0000
1920@@ -0,0 +1,42 @@
1921+/*
1922+ * Copyright 2015 Canonical Ltd.
1923+ *
1924+ * This program is free software; you can redistribute it and/or modify
1925+ * it under the terms of the GNU Lesser General Public License as published by
1926+ * the Free Software Foundation; version 3.
1927+ *
1928+ * This program is distributed in the hope that it will be useful,
1929+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1930+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1931+ * GNU Lesser General Public License for more details.
1932+ *
1933+ * You should have received a copy of the GNU Lesser General Public License
1934+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1935+*/
1936+
1937+#ifndef GLOBALFUNCTIONS_H
1938+#define GLOBALFUNCTIONS_H
1939+
1940+#include <QObject>
1941+#include <QJSValue>
1942+class QQuickItem;
1943+
1944+/**
1945+ * @brief The GlobalFunctions class
1946+ *
1947+ * This singleton class exposes utility functions to QML
1948+ *
1949+ */
1950+class GlobalFunctions : public QObject
1951+{
1952+ Q_OBJECT
1953+public:
1954+ explicit GlobalFunctions(QObject *parent = 0);
1955+
1956+ static Q_INVOKABLE QQuickItem* itemAt(QQuickItem* parent,
1957+ int x,
1958+ int y,
1959+ QJSValue matcher);
1960+};
1961+
1962+#endif // GLOBALFUNCTIONS_H
1963
1964=== modified file 'plugins/Utils/plugin.cpp'
1965--- plugins/Utils/plugin.cpp 2016-02-11 16:59:40 +0000
1966+++ plugins/Utils/plugin.cpp 2016-03-16 20:46:19 +0000
1967@@ -38,6 +38,7 @@
1968 #include "applicationsfiltermodel.h"
1969 #include "inputeventgenerator.h"
1970 #include "deviceconfigparser.h"
1971+#include "globalfunctions.h"
1972
1973 static QObject *createWindowStateStorage(QQmlEngine *engine, QJSEngine *scriptEngine)
1974 {
1975@@ -53,6 +54,13 @@
1976 return new Constants();
1977 }
1978
1979+static QObject *createGlobalFunctions(QQmlEngine *engine, QJSEngine *scriptEngine)
1980+{
1981+ Q_UNUSED(engine)
1982+ Q_UNUSED(scriptEngine)
1983+ return new GlobalFunctions();
1984+}
1985+
1986 void UtilsPlugin::registerTypes(const char *uri)
1987 {
1988 Q_ASSERT(uri == QLatin1String("Utils"));
1989@@ -72,6 +80,7 @@
1990 qmlRegisterType<ApplicationsFilterModel>(uri, 0, 1, "ApplicationsFilterModel");
1991 qmlRegisterType<InputEventGenerator>(uri, 0, 1, "InputEventGenerator");
1992 qmlRegisterType<DeviceConfigParser>(uri, 0, 1, "DeviceConfigParser");
1993+ qmlRegisterSingletonType<GlobalFunctions>(uri, 0, 1, "Functions", createGlobalFunctions);
1994 }
1995
1996 void UtilsPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
1997
1998=== modified file 'plugins/Utils/timezoneFormatter.cpp'
1999--- plugins/Utils/timezoneFormatter.cpp 2015-11-20 15:01:39 +0000
2000+++ plugins/Utils/timezoneFormatter.cpp 2016-03-16 20:46:19 +0000
2001@@ -33,3 +33,13 @@
2002 }
2003 return QString();
2004 }
2005+
2006+QString TimezoneFormatter::currentTimeInTimezoneWithAbbrev(const QVariant &tzId) const
2007+{
2008+ QTimeZone tz(tzId.toByteArray());
2009+ if (tz.isValid()) {
2010+ const QDateTime now = QDateTime::currentDateTime().toTimeZone(tz);
2011+ return QStringLiteral("%1 %2").arg(now.time().toString(QStringLiteral("h:mm"))).arg(tz.abbreviation(now));
2012+ }
2013+ return QString();
2014+}
2015
2016=== modified file 'plugins/Utils/timezoneFormatter.h'
2017--- plugins/Utils/timezoneFormatter.h 2015-09-17 13:42:15 +0000
2018+++ plugins/Utils/timezoneFormatter.h 2016-03-16 20:46:19 +0000
2019@@ -27,6 +27,7 @@
2020 ~TimezoneFormatter() = default;
2021
2022 Q_INVOKABLE QString currentTimeInTimezone(const QVariant &tzId) const;
2023+ Q_INVOKABLE QString currentTimeInTimezoneWithAbbrev(const QVariant &tzId) const;
2024 };
2025
2026 #endif
2027
2028=== modified file 'plugins/Utils/windowstatestorage.cpp'
2029--- plugins/Utils/windowstatestorage.cpp 2015-11-20 15:01:39 +0000
2030+++ plugins/Utils/windowstatestorage.cpp 2016-03-16 20:46:19 +0000
2031@@ -23,9 +23,14 @@
2032 #include <QSqlError>
2033 #include <QSqlResult>
2034 #include <QRect>
2035+#include <unity/shell/application/ApplicationInfoInterface.h>
2036
2037 QMutex WindowStateStorage::s_mutex;
2038
2039+inline QString sanitiseString(QString string) {
2040+ return string.remove("\"").remove("'").remove("\\");
2041+}
2042+
2043 WindowStateStorage::WindowStateStorage(QObject *parent):
2044 QObject(parent)
2045 {
2046@@ -50,7 +55,7 @@
2047 void WindowStateStorage::saveState(const QString &windowId, WindowStateStorage::WindowState state)
2048 {
2049 const QString queryString = QStringLiteral("INSERT OR REPLACE INTO state (windowId, state) values ('%1', '%2');")
2050- .arg(windowId)
2051+ .arg(sanitiseString(windowId))
2052 .arg((int)state);
2053
2054 saveValue(queryString);
2055@@ -59,7 +64,7 @@
2056 WindowStateStorage::WindowState WindowStateStorage::getState(const QString &windowId, WindowStateStorage::WindowState defaultValue) const
2057 {
2058 const QString queryString = QStringLiteral("SELECT * FROM state WHERE windowId = '%1';")
2059- .arg(windowId);
2060+ .arg(sanitiseString(windowId));
2061
2062 QSqlQuery query = getValue(queryString);
2063
2064@@ -72,7 +77,7 @@
2065 void WindowStateStorage::saveGeometry(const QString &windowId, const QRect rect)
2066 {
2067 const QString queryString = QStringLiteral("INSERT OR REPLACE INTO geometry (windowId, x, y, width, height) values ('%1', '%2', '%3', '%4', '%5');")
2068- .arg(windowId)
2069+ .arg(sanitiseString(windowId))
2070 .arg(rect.x())
2071 .arg(rect.y())
2072 .arg(rect.width())
2073@@ -81,6 +86,28 @@
2074 saveValue(queryString);
2075 }
2076
2077+void WindowStateStorage::saveStage(const QString &appId, int stage)
2078+{
2079+ const QString queryString = QStringLiteral("INSERT OR REPLACE INTO stage (appId, stage) values ('%1', '%2');")
2080+ .arg(sanitiseString(appId))
2081+ .arg((int)stage);
2082+
2083+ saveValue(queryString);
2084+}
2085+
2086+int WindowStateStorage::getStage(const QString &appId) const
2087+{
2088+ const QString queryString = QStringLiteral("SELECT * FROM stage WHERE appId = '%1';")
2089+ .arg(sanitiseString(appId));
2090+
2091+ QSqlQuery query = getValue(queryString);
2092+
2093+ if (!query.first()) {
2094+ return unity::shell::application::ApplicationInfoInterface::MainStage;
2095+ }
2096+ return query.value("stage").toInt();
2097+}
2098+
2099 void WindowStateStorage::executeAsyncQuery(const QString &queryString)
2100 {
2101 QMutexLocker l(&s_mutex);
2102@@ -97,7 +124,7 @@
2103 QRect WindowStateStorage::getGeometry(const QString &windowId, const QRect defaultValue) const
2104 {
2105 QString queryString = QStringLiteral("SELECT * FROM geometry WHERE windowId = '%1';")
2106- .arg(windowId);
2107+ .arg(sanitiseString(windowId));
2108
2109 QSqlQuery query = getValue(queryString);
2110
2111@@ -124,6 +151,11 @@
2112 QSqlQuery query;
2113 query.exec(QStringLiteral("CREATE TABLE state(windowId TEXT UNIQUE, state INTEGER);"));
2114 }
2115+
2116+ if (!m_db.tables().contains(QStringLiteral("stage"))) {
2117+ QSqlQuery query;
2118+ query.exec(QStringLiteral("CREATE TABLE stage(appId TEXT UNIQUE, stage INTEGER);"));
2119+ }
2120 }
2121
2122 void WindowStateStorage::saveValue(const QString &queryString)
2123
2124=== modified file 'plugins/Utils/windowstatestorage.h'
2125--- plugins/Utils/windowstatestorage.h 2015-11-20 15:01:39 +0000
2126+++ plugins/Utils/windowstatestorage.h 2016-03-16 20:46:19 +0000
2127@@ -38,6 +38,9 @@
2128 Q_INVOKABLE void saveGeometry(const QString &windowId, const QRect rect);
2129 Q_INVOKABLE QRect getGeometry(const QString &windowId, const QRect defaultValue) const;
2130
2131+ Q_INVOKABLE void saveStage(const QString &appId, int stage);
2132+ Q_INVOKABLE int getStage(const QString &appId) const;
2133+
2134 private:
2135 void initdb();
2136
2137
2138=== modified file 'plugins/Wizard/CMakeLists.txt'
2139--- plugins/Wizard/CMakeLists.txt 2014-11-14 17:47:00 +0000
2140+++ plugins/Wizard/CMakeLists.txt 2016-03-16 20:46:19 +0000
2141@@ -1,10 +1,16 @@
2142+include_directories(${GLIB_INCLUDE_DIRS} ${GEONAMES_INCLUDE_DIRS})
2143+
2144 add_library(Wizard-qml MODULE
2145 plugin.cpp
2146 PageList.cpp
2147 System.cpp
2148+ LocalePlugin.cpp
2149+ timezonemodel.cpp
2150+ Status.cpp
2151 )
2152
2153-qt5_use_modules(Wizard-qml DBus Qml)
2154+qt5_use_modules(Wizard-qml DBus Qml Concurrent)
2155+target_link_libraries(Wizard-qml ${GLIB_LDFLAGS} ${GEONAMES_LDFLAGS})
2156 add_unity8_plugin(Wizard 0.1 Wizard TARGETS Wizard-qml)
2157
2158 set(POLKIT_LIB_DIR "${CMAKE_INSTALL_LOCALSTATEDIR}/lib/polkit-1")
2159
2160=== added file 'plugins/Wizard/LocalePlugin.cpp'
2161--- plugins/Wizard/LocalePlugin.cpp 1970-01-01 00:00:00 +0000
2162+++ plugins/Wizard/LocalePlugin.cpp 2016-03-16 20:46:19 +0000
2163@@ -0,0 +1,291 @@
2164+/*
2165+ * Copyright (C) 2015 Canonical Ltd.
2166+ *
2167+ * This program is free software: you can redistribute it and/or modify it
2168+ * under the terms of the GNU General Public License version 3, as published
2169+ * by the Free Software Foundation.
2170+ *
2171+ * This program is distributed in the hope that it will be useful, but
2172+ * WITHOUT ANY WARRANTY; without even the implied warranties of
2173+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
2174+ * PURPOSE. See the GNU General Public License for more details.
2175+ *
2176+ * You should have received a copy of the GNU General Public License along
2177+ * with this program. If not, see <http://www.gnu.org/licenses/>.
2178+ */
2179+
2180+#include <QLocale>
2181+#include <QStringList>
2182+
2183+#include <libintl.h>
2184+
2185+#include "LocalePlugin.h"
2186+
2187+class LocalePrivate {
2188+public:
2189+ LocalePrivate() {
2190+ m_mccCodes = QHash<int, QString>
2191+ ({{202,"gr"},
2192+ {204,"nl"},
2193+ {206,"be"},
2194+ {208,"fr"},
2195+ {212,"mc"},
2196+ {213,"ad"},
2197+ {214,"es"},
2198+ {216,"hu"},
2199+ {218,"ba"},
2200+ {219,"hr"},
2201+ {220,"rs"},
2202+ {222,"it"},
2203+ {226,"ro"},
2204+ {228,"ch"},
2205+ {230,"cz"},
2206+ {231,"sk"},
2207+ {232,"at"},
2208+ {234,"gb"},
2209+ {235,"gb"},
2210+ {238,"dk"},
2211+ {240,"se"},
2212+ {242,"no"},
2213+ {244,"fi"},
2214+ {246,"lt"},
2215+ {247,"lv"},
2216+ {248,"ee"},
2217+ {250,"ru"},
2218+ {255,"ua"},
2219+ {257,"by"},
2220+ {259,"md"},
2221+ {260,"pl"},
2222+ {262,"de"},
2223+ {266,"gi"},
2224+ {268,"pt"},
2225+ {270,"lu"},
2226+ {272,"ie"},
2227+ {274,"is"},
2228+ {276,"al"},
2229+ {278,"mt"},
2230+ {280,"cy"},
2231+ {282,"ge"},
2232+ {283,"am"},
2233+ {284,"bg"},
2234+ {286,"tr"},
2235+ {288,"fo"},
2236+ {289,"ge"},
2237+ {290,"gl"},
2238+ {292,"sm"},
2239+ {293,"si"},
2240+ {294,"mk"},
2241+ {295,"li"},
2242+ {297,"me"},
2243+ {302,"ca"},
2244+ {308,"pm"},
2245+ {310,"gu"},
2246+ {310,"us"},
2247+ {311,"gu"},
2248+ {311,"us"},
2249+ {312,"us"},
2250+ {316,"us"},
2251+ {330,"pr"},
2252+ {334,"mx"},
2253+ {338,"jm"},
2254+ {340,"fg"},
2255+ {340,"gp"},
2256+ {340,"mq"},
2257+ {342,"bb"},
2258+ {344,"ag"},
2259+ {346,"ky"},
2260+ {348,"vg"},
2261+ {350,"bm"},
2262+ {352,"gd"},
2263+ {354,"ms"},
2264+ {356,"kn"},
2265+ {358,"lc"},
2266+ {360,"vc"},
2267+ {362,"an"},
2268+ {362,"cw"},
2269+ {363,"aw"},
2270+ {364,"bs"},
2271+ {365,"ai"},
2272+ {366,"dm"},
2273+ {368,"cu"},
2274+ {370,"do"},
2275+ {372,"ht"},
2276+ {374,"tt"},
2277+ {376,"tc"},
2278+ {376,"vi"},
2279+ {400,"az"},
2280+ {401,"kz"},
2281+ {402,"bt"},
2282+ {404,"in"},
2283+ {405,"in"},
2284+ {410,"pk"},
2285+ {412,"af"},
2286+ {413,"lk"},
2287+ {414,"mm"},
2288+ {415,"lb"},
2289+ {416,"jo"},
2290+ {417,"sy"},
2291+ {418,"iq"},
2292+ {419,"kw"},
2293+ {420,"sa"},
2294+ {421,"ye"},
2295+ {422,"om"},
2296+ {424,"ae"},
2297+ {425,"il"},
2298+ {425,"ps"},
2299+ {426,"bh"},
2300+ {427,"qa"},
2301+ {428,"mn"},
2302+ {429,"np"},
2303+ {430,"ae"},
2304+ {431,"ae"},
2305+ {432,"ir"},
2306+ {434,"uz"},
2307+ {436,"tk"},
2308+ {437,"kg"},
2309+ {438,"tm"},
2310+ {440,"jp"},
2311+ {441,"jp"},
2312+ {450,"kr"},
2313+ {452,"vn"},
2314+ {454,"hk"},
2315+ {455,"mo"},
2316+ {456,"kh"},
2317+ {457,"la"},
2318+ {460,"cn"},
2319+ {466,"tw"},
2320+ {467,"kp"},
2321+ {470,"bd"},
2322+ {472,"mv"},
2323+ {502,"my"},
2324+ {505,"au"},
2325+ {510,"id"},
2326+ {514,"tp"},
2327+ {515,"ph"},
2328+ {520,"th"},
2329+ {525,"sg"},
2330+ {528,"bn"},
2331+ {530,"nz"},
2332+ {537,"pg"},
2333+ {539,"to"},
2334+ {540,"sb"},
2335+ {541,"vu"},
2336+ {542,"fj"},
2337+ {544,"as"},
2338+ {545,"ki"},
2339+ {546,"nc"},
2340+ {547,"pf"},
2341+ {548,"ck"},
2342+ {549,"ws"},
2343+ {550,"fm"},
2344+ {552,"pw"},
2345+ {553,"tv"},
2346+ {555,"nu"},
2347+ {602,"eg"},
2348+ {603,"dz"},
2349+ {604,"ma"},
2350+ {605,"tn"},
2351+ {606,"ly"},
2352+ {607,"gm"},
2353+ {608,"sn"},
2354+ {609,"mr"},
2355+ {610,"ml"},
2356+ {611,"gn"},
2357+ {612,"ci"},
2358+ {613,"bf"},
2359+ {614,"ne"},
2360+ {615,"tg"},
2361+ {616,"bj"},
2362+ {617,"mu"},
2363+ {618,"lr"},
2364+ {619,"sl"},
2365+ {620,"gh"},
2366+ {621,"ng"},
2367+ {622,"td"},
2368+ {623,"cf"},
2369+ {624,"cm"},
2370+ {625,"cv"},
2371+ {626,"st"},
2372+ {627,"gq"},
2373+ {628,"ga"},
2374+ {629,"cg"},
2375+ {630,"cd"},
2376+ {631,"ao"},
2377+ {632,"gw"},
2378+ {633,"sc"},
2379+ {634,"sd"},
2380+ {635,"rw"},
2381+ {636,"et"},
2382+ {637,"so"},
2383+ {638,"dj"},
2384+ {639,"ke"},
2385+ {640,"tz"},
2386+ {641,"ug"},
2387+ {642,"bi"},
2388+ {643,"mz"},
2389+ {645,"zm"},
2390+ {646,"mg"},
2391+ {647,"re"},
2392+ {648,"zw"},
2393+ {649,"na"},
2394+ {650,"mw"},
2395+ {651,"ls"},
2396+ {652,"bw"},
2397+ {653,"sz"},
2398+ {654,"km"},
2399+ {655,"za"},
2400+ {657,"er"},
2401+ {659,"ss"},
2402+ {702,"bz"},
2403+ {704,"gt"},
2404+ {706,"sv"},
2405+ {708,"hn"},
2406+ {710,"ni"},
2407+ {712,"cr"},
2408+ {714,"pa"},
2409+ {716,"pe"},
2410+ {722,"ar"},
2411+ {724,"br"},
2412+ {730,"cl"},
2413+ {732,"co"},
2414+ {734,"ve"},
2415+ {736,"bo"},
2416+ {738,"gy"},
2417+ {740,"ec"},
2418+ {744,"py"},
2419+ {746,"sr"},
2420+ {748,"uy"},
2421+ {750,"fk"}}
2422+ );
2423+ }
2424+
2425+ QString mccToCountryCode(int mcc) const {
2426+ return m_mccCodes.value(mcc, "us").toUpper();
2427+ }
2428+
2429+private:
2430+ // MCC = Mobile Country Code, see https://en.wikipedia.org/wiki/Mobile_country_code
2431+ QHash<int,QString> m_mccCodes;
2432+};
2433+
2434+Q_GLOBAL_STATIC(LocalePrivate, d)
2435+
2436+LocaleAttached::LocaleAttached(QObject* parent)
2437+ : QObject(parent)
2438+{
2439+}
2440+
2441+QString LocaleAttached::mccToCountryCode(int mcc) const
2442+{
2443+ return d->mccToCountryCode(mcc);
2444+}
2445+
2446+LocalePlugin::LocalePlugin(QObject* parent)
2447+ : QObject(parent)
2448+{
2449+}
2450+
2451+LocaleAttached* LocalePlugin::qmlAttachedProperties(QObject* parent)
2452+{
2453+ return new LocaleAttached(parent);
2454+}
2455
2456=== added file 'plugins/Wizard/LocalePlugin.h'
2457--- plugins/Wizard/LocalePlugin.h 1970-01-01 00:00:00 +0000
2458+++ plugins/Wizard/LocalePlugin.h 2016-03-16 20:46:19 +0000
2459@@ -0,0 +1,58 @@
2460+/*
2461+ * Copyright (C) 2015 Canonical Ltd.
2462+ *
2463+ * This program is free software: you can redistribute it and/or modify it
2464+ * under the terms of the GNU General Public License version 3, as published
2465+ * by the Free Software Foundation.
2466+ *
2467+ * This program is distributed in the hope that it will be useful, but
2468+ * WITHOUT ANY WARRANTY; without even the implied warranties of
2469+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
2470+ * PURPOSE. See the GNU General Public License for more details.
2471+ *
2472+ * You should have received a copy of the GNU General Public License along
2473+ * with this program. If not, see <http://www.gnu.org/licenses/>.
2474+ */
2475+
2476+#ifndef LOCALE_PLUGIN_H
2477+#define LOCALE_PLUGIN_H
2478+
2479+#include <QObject>
2480+#include <QString>
2481+#include <QJsonObject>
2482+#include <QtQml>
2483+
2484+class LocaleAttached: public QObject
2485+{
2486+ Q_OBJECT
2487+protected:
2488+ explicit LocaleAttached(QObject *parent = 0);
2489+
2490+public:
2491+ Q_INVOKABLE QString mccToCountryCode(int mcc) const;
2492+
2493+ friend class LocalePlugin;
2494+};
2495+
2496+/**
2497+ * A simplified wrapper around QLocale.
2498+ *
2499+ * The wrapper is implemented as an attached property, which makes it possible
2500+ * to use it as if its methods were static:
2501+ *
2502+ * @code
2503+ * var langs = LocalePlugin.languages();
2504+ * @endcode
2505+ */
2506+class LocalePlugin: public QObject
2507+{
2508+ Q_OBJECT
2509+public:
2510+ explicit LocalePlugin(QObject *parent = 0);
2511+
2512+ static LocaleAttached* qmlAttachedProperties(QObject *parent);
2513+};
2514+
2515+QML_DECLARE_TYPEINFO(LocalePlugin, QML_HAS_ATTACHED_PROPERTIES)
2516+
2517+#endif // LOCALE_PLUGIN_H
2518
2519=== modified file 'plugins/Wizard/PageList.cpp'
2520--- plugins/Wizard/PageList.cpp 2015-09-14 09:11:08 +0000
2521+++ plugins/Wizard/PageList.cpp 2016-03-16 20:46:19 +0000
2522@@ -40,20 +40,20 @@
2523 m_index(-1),
2524 m_pages()
2525 {
2526- QString qmlSuffix = QStringLiteral(".qml");
2527- QString disabledSuffix = QStringLiteral(".disabled");
2528+ const QString qmlSuffix = QStringLiteral(".qml");
2529+ const QString disabledSuffix = QStringLiteral(".disabled");
2530 QSet<QString> disabledPages;
2531 QStringList dataDirs;
2532
2533 if (!isRunningInstalled() && getenv("WIZARD_TESTING") == nullptr) {
2534- dataDirs = QStringList() << qmlDirectory();
2535+ dataDirs << qmlDirectory();
2536 } else {
2537 dataDirs = shellDataDirs();
2538 }
2539
2540 Q_FOREACH(const QString &dataDir, dataDirs) {
2541 QDir dir(dataDir + "/Wizard/Pages");
2542- QStringList entries = dir.entryList(QStringList(QStringLiteral("[0-9]*")), QDir::Files | QDir::Readable);
2543+ const QStringList entries = dir.entryList(QStringList(QStringLiteral("[0-9]*")), QDir::Files | QDir::Readable);
2544 Q_FOREACH(const QString &entry, entries) {
2545 if (!m_pages.contains(entry) && entry.endsWith(qmlSuffix))
2546 m_pages.insert(entry, dir.absoluteFilePath(entry));
2547
2548=== added file 'plugins/Wizard/Status.cpp'
2549--- plugins/Wizard/Status.cpp 1970-01-01 00:00:00 +0000
2550+++ plugins/Wizard/Status.cpp 2016-03-16 20:46:19 +0000
2551@@ -0,0 +1,143 @@
2552+/*
2553+ * Copyright (C) 2015 Canonical Ltd.
2554+ *
2555+ * This program is free software: you can redistribute it and/or modify it
2556+ * under the terms of the GNU General Public License version 3, as published
2557+ * by the Free Software Foundation.
2558+ *
2559+ * This program is distributed in the hope that it will be useful, but
2560+ * WITHOUT ANY WARRANTY; without even the implied warranties of
2561+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
2562+ * PURPOSE. See the GNU General Public License for more details.
2563+ *
2564+ * You should have received a copy of the GNU General Public License along
2565+ * with this program. If not, see <http://www.gnu.org/licenses/>.
2566+ */
2567+
2568+#include <QDebug>
2569+#include <QDBusConnection>
2570+
2571+#include "Status.h"
2572+
2573+Status::Status()
2574+{
2575+ initNM();
2576+ initUPower();
2577+}
2578+
2579+void Status::initNM()
2580+{
2581+ m_nmIface = new QDBusInterface("org.freedesktop.NetworkManager", "/org/freedesktop/NetworkManager", "org.freedesktop.NetworkManager",
2582+ QDBusConnection::systemBus(), this);
2583+
2584+ QDBusConnection::systemBus().connect("org.freedesktop.NetworkManager", "/org/freedesktop/NetworkManager", "org.freedesktop.NetworkManager", "PropertiesChanged",
2585+ this, SLOT(onNMPropertiesChanged(QVariantMap)));
2586+}
2587+
2588+void Status::onNMPropertiesChanged(const QVariantMap &changedProps)
2589+{
2590+ if (changedProps.contains("State") || changedProps.contains("Connectivity")) {
2591+ Q_EMIT onlineChanged();
2592+ Q_EMIT networkIconChanged();
2593+ }
2594+
2595+ if (changedProps.contains("PrimaryConnection") || changedProps.contains("SpecificObject") || changedProps.contains("Strength")) {
2596+ Q_EMIT networkIconChanged();
2597+ }
2598+}
2599+
2600+bool Status::online() const
2601+{
2602+ if (!m_nmIface->isValid())
2603+ return false;
2604+
2605+ return m_nmIface->property("State").toUInt() == 70;
2606+}
2607+
2608+QString Status::networkIcon()
2609+{
2610+ QString iconName = QStringLiteral("nm-no-connection");
2611+
2612+ if (!online()) {
2613+ return iconName;
2614+ }
2615+
2616+ const QString primaryConn = m_nmIface->property("PrimaryConnection").value<QDBusObjectPath>().path();
2617+ const QString primaryConnType = m_nmIface->property("PrimaryConnectionType").toString();
2618+
2619+ if (primaryConn.isEmpty()) {
2620+ qWarning() << "Empty primary connection";
2621+ return iconName;
2622+ }
2623+
2624+ if (primaryConnType == "802-11-wireless") {
2625+ QDBusInterface activeConn("org.freedesktop.NetworkManager", primaryConn, "org.freedesktop.NetworkManager.Connection.Active", QDBusConnection::systemBus());
2626+
2627+ if (activeConn.isValid()) {
2628+ const QString apPath = activeConn.property("SpecificObject").value<QDBusObjectPath>().path();
2629+
2630+ if (apPath.isEmpty()) {
2631+ qWarning() << "No AP path";
2632+ return iconName;
2633+ }
2634+
2635+ QDBusConnection::systemBus().connect("org.freedesktop.NetworkManager", primaryConn, "org.freedesktop.NetworkManager.Connection.Active", "PropertiesChanged",
2636+ this, SLOT(onNMPropertiesChanged(QVariantMap)));
2637+
2638+ QDBusInterface ap("org.freedesktop.NetworkManager", apPath, "org.freedesktop.NetworkManager.AccessPoint", QDBusConnection::systemBus());
2639+
2640+ if (!ap.isValid()) {
2641+ qWarning() << "Invalid AP";
2642+ return iconName;
2643+ }
2644+
2645+ QDBusConnection::systemBus().connect("org.freedesktop.NetworkManager", apPath, "org.freedesktop.NetworkManager.AccessPoint", "PropertiesChanged",
2646+ this, SLOT(onNMPropertiesChanged(QVariantMap)));
2647+
2648+ const uint strength = ap.property("Strength").toUInt();
2649+ const uint flags = ap.property("Flags").toUInt();
2650+
2651+ if (strength == 0) {
2652+ iconName = "nm-signal-00";
2653+ } else if (strength <= 25) {
2654+ iconName = "nm-signal-25";
2655+ } else if (strength <= 50) {
2656+ iconName = "nm-signal-50";
2657+ } else if (strength <= 75) {
2658+ iconName = "nm-signal-75";
2659+ } else if (strength <= 100) {
2660+ iconName = "nm-signal-100";
2661+ }
2662+
2663+ if (flags >= 1) {
2664+ iconName += "-secure";
2665+ }
2666+ }
2667+ }
2668+
2669+ return iconName;
2670+}
2671+
2672+void Status::initUPower()
2673+{
2674+ m_upowerIface = new QDBusInterface("org.freedesktop.UPower", "/org/freedesktop/UPower/devices/DisplayDevice", "org.freedesktop.UPower.Device",
2675+ QDBusConnection::systemBus(), this);
2676+ QDBusConnection::systemBus().connect("org.freedesktop.UPower", "/org/freedesktop/UPower/devices/DisplayDevice", "org.freedesktop.DBus.Properties",
2677+ "PropertiesChanged", this, SLOT(onUPowerPropertiesChanged(QString,QVariantMap,QStringList)));
2678+}
2679+
2680+void Status::onUPowerPropertiesChanged(const QString &iface, const QVariantMap &changedProps, const QStringList &invalidatedProps)
2681+{
2682+ Q_UNUSED(iface)
2683+ Q_UNUSED(invalidatedProps)
2684+
2685+ if (changedProps.contains("IconName")) {
2686+ Q_EMIT batteryIconChanged();
2687+ }
2688+}
2689+
2690+QString Status::batteryIcon() const
2691+{
2692+ const QString iconName = m_upowerIface->property("IconName").toString();
2693+ return iconName;
2694+}
2695
2696=== added file 'plugins/Wizard/Status.h'
2697--- plugins/Wizard/Status.h 1970-01-01 00:00:00 +0000
2698+++ plugins/Wizard/Status.h 2016-03-16 20:46:19 +0000
2699@@ -0,0 +1,60 @@
2700+/*
2701+ * Copyright (C) 2015 Canonical Ltd.
2702+ *
2703+ * This program is free software: you can redistribute it and/or modify it
2704+ * under the terms of the GNU General Public License version 3, as published
2705+ * by the Free Software Foundation.
2706+ *
2707+ * This program is distributed in the hope that it will be useful, but
2708+ * WITHOUT ANY WARRANTY; without even the implied warranties of
2709+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
2710+ * PURPOSE. See the GNU General Public License for more details.
2711+ *
2712+ * You should have received a copy of the GNU General Public License along
2713+ * with this program. If not, see <http://www.gnu.org/licenses/>.
2714+ */
2715+
2716+#ifndef WIZARD_STATUS_H
2717+#define WIZARD_STATUS_H
2718+
2719+#include <QObject>
2720+#include <QString>
2721+#include <QDBusInterface>
2722+
2723+class Status: public QObject
2724+{
2725+ Q_OBJECT
2726+ Q_PROPERTY(bool online READ online NOTIFY onlineChanged)
2727+ Q_PROPERTY(QString networkIcon READ networkIcon NOTIFY networkIconChanged)
2728+ Q_PROPERTY(QString batteryIcon READ batteryIcon NOTIFY batteryIconChanged)
2729+public:
2730+ Status();
2731+ ~Status() = default;
2732+
2733+ bool online() const;
2734+ QString networkIcon();
2735+
2736+ QString batteryIcon() const;
2737+
2738+Q_SIGNALS:
2739+ void networkIconChanged();
2740+ void onlineChanged();
2741+ void batteryIconChanged();
2742+
2743+private Q_SLOTS:
2744+ void onNMPropertiesChanged(const QVariantMap &changedProps);
2745+ void onUPowerPropertiesChanged(const QString &iface, const QVariantMap &changedProps, const QStringList &invalidatedProps);
2746+
2747+private:
2748+ Q_DISABLE_COPY(Status)
2749+
2750+ // network status
2751+ void initNM();
2752+ QDBusInterface * m_nmIface = nullptr;
2753+
2754+ // battery status
2755+ void initUPower();
2756+ QDBusInterface * m_upowerIface = nullptr;
2757+};
2758+
2759+#endif
2760
2761=== modified file 'plugins/Wizard/System.cpp'
2762--- plugins/Wizard/System.cpp 2015-09-23 15:14:01 +0000
2763+++ plugins/Wizard/System.cpp 2016-03-16 20:46:19 +0000
2764@@ -1,5 +1,5 @@
2765 /*
2766- * Copyright (C) 2014 Canonical Ltd.
2767+ * Copyright (C) 2014-2015 Canonical Ltd.
2768 *
2769 * This program is free software: you can redistribute it and/or modify it
2770 * under the terms of the GNU General Public License version 3, as published
2771@@ -25,10 +25,10 @@
2772 #include <QLocale>
2773 #include <QMap>
2774 #include <QProcess>
2775+#include <QDebug>
2776
2777 System::System()
2778- : QObject(),
2779- m_fsWatcher()
2780+ : QObject()
2781 {
2782 // Register the argument needed for UpdateActivationEnvironment below
2783 qDBusRegisterMetaType<QMap<QString,QString>>();
2784@@ -88,7 +88,7 @@
2785 QDBusConnection::sessionBus().asyncCall(msg);
2786 }
2787
2788-void System::updateSessionLanguage(const QString &locale)
2789+void System::updateSessionLocale(const QString &locale)
2790 {
2791 const QString language = locale.split(QStringLiteral("."))[0];
2792
2793@@ -106,5 +106,6 @@
2794 initctl stop smart-scopes-proxy; \
2795 initctl emit --no-wait indicator-services-start; \
2796 initctl restart --no-wait maliit-server; \
2797+ initctl restart --no-wait indicator-messages; \
2798 initctl restart --no-wait unity8-dash\""));
2799 }
2800
2801=== modified file 'plugins/Wizard/System.h'
2802--- plugins/Wizard/System.h 2015-05-21 16:05:57 +0000
2803+++ plugins/Wizard/System.h 2016-03-16 20:46:19 +0000
2804@@ -1,5 +1,5 @@
2805 /*
2806- * Copyright (C) 2014 Canonical Ltd.
2807+ * Copyright (C) 2014-2015 Canonical Ltd.
2808 *
2809 * This program is free software: you can redistribute it and/or modify it
2810 * under the terms of the GNU General Public License version 3, as published
2811@@ -28,12 +28,13 @@
2812
2813 public:
2814 System();
2815+ ~System() = default;
2816
2817 bool wizardEnabled() const;
2818 void setWizardEnabled(bool enabled);
2819
2820 public Q_SLOTS:
2821- void updateSessionLanguage(const QString &locale);
2822+ void updateSessionLocale(const QString &locale);
2823
2824 Q_SIGNALS:
2825 void wizardEnabledChanged();
2826
2827=== modified file 'plugins/Wizard/plugin.cpp'
2828--- plugins/Wizard/plugin.cpp 2014-11-21 19:28:16 +0000
2829+++ plugins/Wizard/plugin.cpp 2016-03-16 20:46:19 +0000
2830@@ -17,19 +17,18 @@
2831 #include "plugin.h"
2832 #include "PageList.h"
2833 #include "System.h"
2834+#include "timezonemodel.h"
2835+#include "LocalePlugin.h"
2836+#include "Status.h"
2837
2838 #include <QtQml/qqml.h>
2839
2840-static QObject *system_provider(QQmlEngine *engine, QJSEngine *scriptEngine)
2841-{
2842- Q_UNUSED(engine)
2843- Q_UNUSED(scriptEngine)
2844- return new System();
2845-}
2846-
2847 void WizardPlugin::registerTypes(const char *uri)
2848 {
2849 Q_ASSERT(uri == QLatin1String("Wizard"));
2850 qmlRegisterType<PageList>(uri, 0, 1, "PageList");
2851- qmlRegisterSingletonType<System>(uri, 0, 1, "System", system_provider);
2852+ qmlRegisterSingletonType<System>(uri, 0, 1, "System", [](QQmlEngine*, QJSEngine*) -> QObject* { return new System; });
2853+ qmlRegisterSingletonType<Status>(uri, 0, 1, "Status", [](QQmlEngine*, QJSEngine*) -> QObject* { return new Status; });
2854+ qmlRegisterType<TimeZoneLocationModel>(uri, 0, 1, "TimeZoneModel");
2855+ qmlRegisterType<LocalePlugin>(uri, 0, 1, "LocalePlugin");
2856 }
2857
2858=== modified file 'plugins/Wizard/plugin.h'
2859--- plugins/Wizard/plugin.h 2015-04-30 09:31:51 +0000
2860+++ plugins/Wizard/plugin.h 2016-03-16 20:46:19 +0000
2861@@ -17,13 +17,12 @@
2862 #ifndef WIZARD_PLUGIN_H
2863 #define WIZARD_PLUGIN_H
2864
2865-#include <QtQml/QQmlEngine>
2866 #include <QtQml/QQmlExtensionPlugin>
2867
2868 class WizardPlugin : public QQmlExtensionPlugin
2869 {
2870 Q_OBJECT
2871- Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
2872+ Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid)
2873
2874 public:
2875 void registerTypes(const char *uri) override;
2876
2877=== added file 'plugins/Wizard/timezonemodel.cpp'
2878--- plugins/Wizard/timezonemodel.cpp 1970-01-01 00:00:00 +0000
2879+++ plugins/Wizard/timezonemodel.cpp 2016-03-16 20:46:19 +0000
2880@@ -0,0 +1,239 @@
2881+/*
2882+ * Copyright (C) 2016 Canonical Ltd.
2883+ *
2884+ * This program is free software: you can redistribute it and/or modify it
2885+ * under the terms of the GNU General Public License version 3, as published
2886+ * by the Free Software Foundation.
2887+ *
2888+ * This program is distributed in the hope that it will be useful, but
2889+ * WITHOUT ANY WARRANTY; without even the implied warranties of
2890+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
2891+ * PURPOSE. See the GNU General Public License for more details.
2892+ *
2893+ * You should have received a copy of the GNU General Public License along
2894+ * with this program. If not, see <http://www.gnu.org/licenses/>.
2895+ */
2896+
2897+#include <QDebug>
2898+
2899+#include <glib.h>
2900+#include <glib-object.h>
2901+
2902+#include "LocalePlugin.h"
2903+#include "timezonemodel.h"
2904+
2905+TimeZoneLocationModel::TimeZoneLocationModel(QObject *parent):
2906+ QAbstractListModel(parent),
2907+ m_listUpdating(false),
2908+ m_cancellable(nullptr)
2909+{
2910+ m_roleNames[Qt::DisplayRole] = "displayName";
2911+ m_roleNames[TimeZoneRole] = "timeZone";
2912+ m_roleNames[CityRole] = "city";
2913+ m_roleNames[CountryRole] = "country";
2914+ m_roleNames[OffsetRole] = "offset";
2915+ m_roleNames[LatitudeRole] = "latitude";
2916+ m_roleNames[LongitudeRole] = "longitude";
2917+}
2918+
2919+int TimeZoneLocationModel::rowCount(const QModelIndex &parent) const
2920+{
2921+ if (parent.isValid()) {
2922+ return 0;
2923+ } else if (m_filter.isEmpty()) {
2924+ return m_countryLocations.count();
2925+ } else {
2926+ return m_locations.count();
2927+ }
2928+}
2929+
2930+QVariant TimeZoneLocationModel::data(const QModelIndex &index, int role) const
2931+{
2932+ GeonamesCity *city;
2933+ if (m_filter.isEmpty()) {
2934+ city = m_countryLocations.value(index.row());
2935+ } else {
2936+ city = m_locations.value(index.row());
2937+ }
2938+ if (!city)
2939+ return QVariant();
2940+
2941+ switch (role) {
2942+ case Qt::DisplayRole:
2943+ return QStringLiteral("%1, %2, %3").arg(geonames_city_get_name(city))
2944+ .arg(geonames_city_get_state(city))
2945+ .arg(geonames_city_get_country(city));
2946+ case SimpleRole:
2947+ return QStringLiteral("%1, %2").arg(geonames_city_get_name(city))
2948+ .arg(geonames_city_get_country(city));
2949+ case TimeZoneRole:
2950+ return geonames_city_get_timezone(city);
2951+ case CountryRole:
2952+ return geonames_city_get_country(city);
2953+ case CityRole:
2954+ return geonames_city_get_name(city);
2955+ case OffsetRole: {
2956+ QTimeZone tmp(geonames_city_get_timezone(city));
2957+ return static_cast<double>(tmp.standardTimeOffset(QDateTime::currentDateTime())) / 3600;
2958+ }
2959+ case LatitudeRole:
2960+ return geonames_city_get_latitude(city);
2961+ case LongitudeRole:
2962+ return geonames_city_get_longitude(city);
2963+ default:
2964+ qWarning() << Q_FUNC_INFO << "Unknown role";
2965+ return QVariant();
2966+ }
2967+}
2968+
2969+QHash<int, QByteArray> TimeZoneLocationModel::roleNames() const
2970+{
2971+ return m_roleNames;
2972+}
2973+
2974+void TimeZoneLocationModel::setModel(const QList<GeonamesCity *> &locations)
2975+{
2976+ beginResetModel();
2977+
2978+ Q_FOREACH(GeonamesCity *city, m_locations) {
2979+ geonames_city_free(city);
2980+ }
2981+
2982+ m_locations = locations;
2983+ endResetModel();
2984+}
2985+
2986+void TimeZoneLocationModel::filterFinished(GObject *source_object,
2987+ GAsyncResult *res,
2988+ gpointer user_data)
2989+{
2990+ Q_UNUSED(source_object);
2991+
2992+ g_autofree gint *cities = nullptr;
2993+ guint cities_len = 0;
2994+ g_autoptr(GError) error = nullptr;
2995+
2996+ cities = geonames_query_cities_finish(res, &cities_len, &error);
2997+ if (error) {
2998+ if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
2999+ TimeZoneLocationModel *model = static_cast<TimeZoneLocationModel *>(user_data);
3000+ g_clear_object(&model->m_cancellable);
3001+ model->setListUpdating(false);
3002+ qWarning() << "Could not filter timezones:" << error->message;
3003+ }
3004+ return;
3005+ }
3006+
3007+ QList<GeonamesCity *> locations;
3008+
3009+ for (guint i = 0; i < cities_len; ++i) {
3010+ GeonamesCity *city = geonames_get_city(cities[i]);
3011+ if (city) {
3012+ locations.append(city);
3013+ }
3014+ }
3015+
3016+ TimeZoneLocationModel *model = static_cast<TimeZoneLocationModel *>(user_data);
3017+
3018+ g_clear_object(&model->m_cancellable);
3019+
3020+ model->setModel(locations);
3021+ model->setListUpdating(false);
3022+}
3023+
3024+bool TimeZoneLocationModel::listUpdating() const
3025+{
3026+ return m_listUpdating;
3027+}
3028+
3029+void TimeZoneLocationModel::setListUpdating(bool listUpdating)
3030+{
3031+ if (m_listUpdating != listUpdating) {
3032+ m_listUpdating = listUpdating;
3033+ Q_EMIT listUpdatingChanged();
3034+ }
3035+}
3036+
3037+QString TimeZoneLocationModel::filter() const
3038+{
3039+ return m_filter;
3040+}
3041+
3042+void TimeZoneLocationModel::setFilter(const QString &filter)
3043+{
3044+ if (filter != m_filter) {
3045+ m_filter = filter;
3046+ Q_EMIT filterChanged();
3047+ }
3048+
3049+ setListUpdating(true);
3050+
3051+ if (m_cancellable) {
3052+ g_cancellable_cancel(m_cancellable);
3053+ g_clear_object(&m_cancellable);
3054+ }
3055+
3056+ setModel(QList<GeonamesCity *>());
3057+
3058+ if (filter.isEmpty()) {
3059+ setListUpdating(false);
3060+ return;
3061+ }
3062+
3063+ m_cancellable = g_cancellable_new();
3064+ geonames_query_cities(filter.toUtf8().data(),
3065+ GEONAMES_QUERY_DEFAULT,
3066+ m_cancellable,
3067+ filterFinished,
3068+ this);
3069+}
3070+
3071+QString TimeZoneLocationModel::country() const
3072+{
3073+ return m_country;
3074+}
3075+
3076+static bool citycmp(GeonamesCity *a, GeonamesCity *b)
3077+{
3078+ return geonames_city_get_population(b) < geonames_city_get_population(a);
3079+}
3080+
3081+void TimeZoneLocationModel::setCountry(const QString &country)
3082+{
3083+ if (m_country == country)
3084+ return;
3085+
3086+ m_country = country;
3087+
3088+ Q_FOREACH(GeonamesCity *city, m_countryLocations) {
3089+ geonames_city_free(city);
3090+ }
3091+
3092+ gint num_cities = geonames_get_n_cities();
3093+ for (gint i = 0; i < num_cities; i++) {
3094+ GeonamesCity *city = geonames_get_city(i);
3095+ if (city && m_country == geonames_city_get_country_code(city)) {
3096+ m_countryLocations.append(city);
3097+ }
3098+ }
3099+
3100+ std::sort(m_countryLocations.begin(), m_countryLocations.end(), citycmp);
3101+
3102+ Q_EMIT countryChanged(country);
3103+}
3104+
3105+TimeZoneLocationModel::~TimeZoneLocationModel()
3106+{
3107+ if (m_cancellable) {
3108+ g_cancellable_cancel(m_cancellable);
3109+ g_clear_object(&m_cancellable);
3110+ }
3111+
3112+ Q_FOREACH(GeonamesCity *city, m_countryLocations) {
3113+ geonames_city_free(city);
3114+ }
3115+
3116+ Q_FOREACH(GeonamesCity *city, m_locations) {
3117+ geonames_city_free(city);
3118+ }
3119+}
3120
3121=== added file 'plugins/Wizard/timezonemodel.h'
3122--- plugins/Wizard/timezonemodel.h 1970-01-01 00:00:00 +0000
3123+++ plugins/Wizard/timezonemodel.h 2016-03-16 20:46:19 +0000
3124@@ -0,0 +1,80 @@
3125+/*
3126+ * Copyright (C) 2016 Canonical Ltd.
3127+ *
3128+ * This program is free software: you can redistribute it and/or modify it
3129+ * under the terms of the GNU General Public License version 3, as published
3130+ * by the Free Software Foundation.
3131+ *
3132+ * This program is distributed in the hope that it will be useful, but
3133+ * WITHOUT ANY WARRANTY; without even the implied warranties of
3134+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
3135+ * PURPOSE. See the GNU General Public License for more details.
3136+ *
3137+ * You should have received a copy of the GNU General Public License along
3138+ * with this program. If not, see <http://www.gnu.org/licenses/>.
3139+ */
3140+
3141+#ifndef TIMEZONEMODEL_H
3142+#define TIMEZONEMODEL_H
3143+
3144+#include <geonames.h>
3145+#include <glib.h>
3146+#include <QAbstractListModel>
3147+
3148+class TimeZoneLocationModel: public QAbstractListModel
3149+{
3150+ Q_OBJECT
3151+ Q_PROPERTY(bool listUpdating READ listUpdating NOTIFY listUpdatingChanged)
3152+ Q_PROPERTY(QString filter READ filter WRITE setFilter NOTIFY filterChanged)
3153+ Q_PROPERTY(QString country READ country WRITE setCountry NOTIFY countryChanged)
3154+ Q_ENUMS(Roles)
3155+
3156+public:
3157+ explicit TimeZoneLocationModel(QObject *parent = nullptr);
3158+ ~TimeZoneLocationModel();
3159+
3160+ enum Roles {
3161+ TimeZoneRole = Qt::UserRole + 1,
3162+ CityRole,
3163+ CountryRole,
3164+ SimpleRole,
3165+ OffsetRole,
3166+ LatitudeRole,
3167+ LongitudeRole
3168+ };
3169+
3170+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
3171+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
3172+ QHash<int, QByteArray> roleNames() const override;
3173+
3174+ bool listUpdating() const;
3175+
3176+ QString filter() const;
3177+ void setFilter(const QString &filter);
3178+
3179+ QString country() const;
3180+ void setCountry(const QString &country);
3181+
3182+Q_SIGNALS:
3183+ void listUpdatingChanged();
3184+ void filterChanged();
3185+ void countryChanged(const QString &country);
3186+
3187+private:
3188+ void setModel(const QList<GeonamesCity *> &locations);
3189+ void setListUpdating(bool listUpdating);
3190+ static void filterFinished(GObject *source_object,
3191+ GAsyncResult *res,
3192+ gpointer user_data);
3193+
3194+
3195+ bool m_listUpdating;
3196+ QString m_filter;
3197+ QString m_country;
3198+ GCancellable *m_cancellable;
3199+ QHash<int, QByteArray> m_roleNames;
3200+ QList<GeonamesCity *> m_locations;
3201+ QList<GeonamesCity *> m_countryLocations;
3202+};
3203+
3204+#endif
3205
3206=== modified file 'qml/Components/Showable.qml'
3207--- qml/Components/Showable.qml 2015-07-15 15:07:19 +0000
3208+++ qml/Components/Showable.qml 2016-03-16 20:46:19 +0000
3209@@ -31,6 +31,7 @@
3210 property bool required
3211 property bool __shouldShow: false
3212 property bool __skipShowAnimation: false
3213+ property bool __skipHideAnimation: false
3214
3215 property list<QtObject> hides
3216 property var showAnimation
3217@@ -120,12 +121,21 @@
3218 if (!hideAnimation.running) {
3219 hideAnimation.restart()
3220 }
3221+ if (__skipHideAnimation) {
3222+ hideAnimation.complete();
3223+ }
3224 } else {
3225 visible = false
3226 required = false
3227 }
3228
3229 shown = false
3230+ __skipHideAnimation = false;
3231+ }
3232+
3233+ function hideNow() {
3234+ __skipHideAnimation = true;
3235+ hide();
3236 }
3237
3238 Connections {
3239
3240=== modified file 'qml/Shell.qml'
3241--- qml/Shell.qml 2016-03-10 22:37:44 +0000
3242+++ qml/Shell.qml 2016-03-16 20:46:19 +0000
3243@@ -120,9 +120,7 @@
3244 if (ApplicationManager.findApplication(appId)) {
3245 ApplicationManager.requestFocusApplication(appId);
3246 } else {
3247- var execFlags = shell.usageScenario === "phone" ? ApplicationManager.ForceMainStage
3248- : ApplicationManager.NoFlag;
3249- ApplicationManager.startApplication(appId, execFlags);
3250+ ApplicationManager.startApplication(appId);
3251 }
3252 }
3253
3254@@ -547,9 +545,7 @@
3255 greeterShown: greeter.shown
3256 }
3257
3258- readonly property bool topmostApplicationIsFullscreen:
3259- ApplicationManager.focusedApplicationId &&
3260- ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
3261+ readonly property bool topmostApplicationIsFullscreen: mainApp && mainApp.fullscreen
3262
3263 fullscreenMode: (topmostApplicationIsFullscreen && !lightDM.greeter.active && launcher.progress == 0)
3264 || greeter.hasLockedApp
3265@@ -632,7 +628,6 @@
3266 id: wizard
3267 objectName: "wizard"
3268 anchors.fill: parent
3269- background: wallpaperResolver.background
3270
3271 function unlockWhenDoneWithWizard() {
3272 if (!active) {
3273
3274=== modified file 'qml/Stages/AbstractStage.qml'
3275--- qml/Stages/AbstractStage.qml 2016-03-10 22:41:22 +0000
3276+++ qml/Stages/AbstractStage.qml 2016-03-16 20:46:19 +0000
3277@@ -44,7 +44,7 @@
3278
3279 // To be read from outside
3280 property var mainApp: null
3281- property int mainAppWindowOrientationAngle
3282+ property int mainAppWindowOrientationAngle: 0
3283 property bool orientationChangesEnabled
3284 property int supportedOrientations: Qt.PortraitOrientation
3285 | Qt.LandscapeOrientation
3286
3287=== modified file 'qml/Stages/ApplicationWindow.qml'
3288--- qml/Stages/ApplicationWindow.qml 2016-02-12 00:10:54 +0000
3289+++ qml/Stages/ApplicationWindow.qml 2016-03-16 20:46:19 +0000
3290@@ -24,12 +24,14 @@
3291 implicitHeight: sessionContainer.implicitHeight
3292
3293 // to be read from outside
3294- readonly property bool fullscreen: application ? application.fullscreen : false
3295 property alias interactive: sessionContainer.interactive
3296 property bool orientationChangesEnabled: d.supportsSurfaceResize ? d.surfaceOldEnoughToBeResized : true
3297 readonly property string title: sessionContainer.surface && sessionContainer.surface.name !== "" ?
3298 sessionContainer.surface.name : d.name
3299
3300+ // overridable from outside
3301+ property bool fullscreen: application ? application.fullscreen : false
3302+
3303 // to be set from outside
3304 property QtObject application
3305 property int surfaceOrientationAngle
3306
3307=== added file 'qml/Stages/SideStage.qml'
3308--- qml/Stages/SideStage.qml 1970-01-01 00:00:00 +0000
3309+++ qml/Stages/SideStage.qml 2016-03-16 20:46:19 +0000
3310@@ -0,0 +1,118 @@
3311+/*
3312+ * Copyright (C) 2016 Canonical, Ltd.
3313+ *
3314+ * This program is free software; you can redistribute it and/or modify
3315+ * it under the terms of the GNU General Public License as published by
3316+ * the Free Software Foundation; version 3.
3317+ *
3318+ * This program is distributed in the hope that it will be useful,
3319+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3320+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3321+ * GNU General Public License for more details.
3322+ *
3323+ * You should have received a copy of the GNU General Public License
3324+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3325+ */
3326+
3327+import QtQuick 2.4
3328+import QtQuick.Layouts 1.1
3329+import Ubuntu.Components 1.3
3330+import Ubuntu.Gestures 0.1
3331+import "../Components"
3332+
3333+Showable {
3334+ id: root
3335+ property bool showHint: true
3336+ property int panelWidth: units.gu(40)
3337+ readonly property alias dragging: hideSideStageDragArea.dragging
3338+ readonly property real progress: width / panelWidth
3339+
3340+ width: 0
3341+ shown: false
3342+
3343+ Item {
3344+ id: sideStageDragHandle
3345+ anchors {
3346+ right: root.left
3347+ top: root.top
3348+ bottom: root.bottom
3349+ }
3350+ width: units.gu(2)
3351+
3352+ opacity: root.shown ? 1 : 0
3353+ Behavior on opacity { UbuntuNumberAnimation {} }
3354+
3355+ Image {
3356+ anchors.centerIn: parent
3357+ width: hideSideStageDragArea.pressed ? parent.width * 2 : parent.width
3358+ height: parent.height
3359+ source: "graphics/sidestage_handle@20.png"
3360+ Behavior on width { UbuntuNumberAnimation {} }
3361+ }
3362+ }
3363+
3364+ Rectangle {
3365+ anchors.fill: parent
3366+ color: Qt.rgba(0,0,0,0.95)
3367+ }
3368+
3369+ Column {
3370+ anchors.verticalCenter: parent.verticalCenter
3371+ width: panelWidth - units.gu(6)
3372+ x: panelWidth/2 - width/2
3373+ spacing: units.gu(3)
3374+ opacity: 0.8
3375+ visible: showHint
3376+
3377+ Icon {
3378+ width: units.gu(30)
3379+ anchors.horizontalCenter: parent.horizontalCenter
3380+ source: "graphics/sidestage_drag.svg"
3381+ color: enabled ? Qt.rgba(1,1,1,1) : Qt.rgba(1,0,0,1)
3382+ keyColor: Qt.rgba(1,1,1,1)
3383+ }
3384+
3385+ Label {
3386+ text: i18n.tr("Drag using 3 fingers any application from one window to the other")
3387+ width: parent.width
3388+ wrapMode: Text.WordWrap
3389+ color: enabled ? Qt.rgba(1,1,1,1) : Qt.rgba(1,0,0,1)
3390+ }
3391+ }
3392+
3393+ showAnimation: NumberAnimation {
3394+ property: "width"
3395+ to: panelWidth
3396+ duration: UbuntuAnimation.BriskDuration
3397+ easing.type: Easing.OutCubic
3398+ }
3399+
3400+ hideAnimation: NumberAnimation {
3401+ property: "width"
3402+ to: 0
3403+ duration: UbuntuAnimation.BriskDuration
3404+ easing.type: Easing.OutCubic
3405+ }
3406+
3407+ DragHandle {
3408+ id: hideSideStageDragArea
3409+ objectName: "hideSideStageDragArea"
3410+
3411+ direction: Direction.Leftwards
3412+ rotation: 180
3413+ enabled: root.shown
3414+ anchors.right: root.left
3415+ width: sideStageDragHandle.width
3416+ height: root.height
3417+ stretch: true
3418+
3419+ immediateRecognition: true
3420+ maxTotalDragDistance: panelWidth
3421+ autoCompleteDragThreshold: panelWidth / 2
3422+ }
3423+
3424+ // SideStage mouse event eater
3425+ MouseArea {
3426+ anchors.fill: parent
3427+ }
3428+}
3429
3430=== modified file 'qml/Stages/SpreadDelegate.qml'
3431--- qml/Stages/SpreadDelegate.qml 2016-03-10 09:19:38 +0000
3432+++ qml/Stages/SpreadDelegate.qml 2016-03-16 20:46:19 +0000
3433@@ -32,6 +32,11 @@
3434 readonly property alias appWindowOrientationAngle: appWindowWithShadow.orientationAngle
3435 readonly property alias appWindowRotation: appWindowWithShadow.rotation
3436 readonly property alias orientationChangesEnabled: appWindow.orientationChangesEnabled
3437+ property int supportedOrientations: application ? application.supportedOrientations :
3438+ Qt.PortraitOrientation
3439+ | Qt.LandscapeOrientation
3440+ | Qt.InvertedPortraitOrientation
3441+ | Qt.InvertedLandscapeOrientation
3442
3443 // to be set from outside
3444 property bool interactive: true
3445@@ -45,6 +50,9 @@
3446 property QtObject orientations
3447 property bool highlightShown: false
3448
3449+ // overrideable from outside
3450+ property alias fullscreen: appWindow.fullscreen
3451+
3452 function matchShellOrientation() {
3453 if (!root.application)
3454 return;
3455@@ -154,7 +162,8 @@
3456 if (!root.application || root.application.rotatesWindowContents) {
3457 return 0;
3458 }
3459- var supportedOrientations = root.application.supportedOrientations;
3460+
3461+ var supportedOrientations = root.supportedOrientations;
3462
3463 if (supportedOrientations === Qt.PrimaryOrientation) {
3464 supportedOrientations = root.orientations.primary;
3465
3466=== modified file 'qml/Stages/SurfaceContainer.qml'
3467--- qml/Stages/SurfaceContainer.qml 2015-11-30 12:18:40 +0000
3468+++ qml/Stages/SurfaceContainer.qml 2016-03-16 20:46:19 +0000
3469@@ -118,6 +118,7 @@
3470
3471
3472 TouchGate {
3473+ objectName: "touchGate-"+name
3474 targetItem: surfaceItem
3475 anchors.fill: root
3476 enabled: surfaceItem.enabled
3477
3478=== added file 'qml/Stages/TabletSideStageTouchGesture.qml'
3479--- qml/Stages/TabletSideStageTouchGesture.qml 1970-01-01 00:00:00 +0000
3480+++ qml/Stages/TabletSideStageTouchGesture.qml 2016-03-16 20:46:19 +0000
3481@@ -0,0 +1,154 @@
3482+/*
3483+ * Copyright (C) 2016 Canonical, Ltd.
3484+ *
3485+ * This program is free software; you can redistribute it and/or modify
3486+ * it under the terms of the GNU General Public License as published by
3487+ * the Free Software Foundation; version 3.
3488+ *
3489+ * This program is distributed in the hope that it will be useful,
3490+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3491+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3492+ * GNU General Public License for more details.
3493+ *
3494+ * You should have received a copy of the GNU General Public License
3495+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3496+ */
3497+
3498+import QtQuick 2.4
3499+import Ubuntu.Gestures 0.1
3500+
3501+TouchGestureArea {
3502+ id: root
3503+ minimumTouchPoints: 3
3504+ maximumTouchPoints: 3
3505+
3506+ property bool enableDrag: true
3507+ property Component dragComponent
3508+ property var dragComponentProperties: undefined
3509+
3510+ readonly property bool recognisedPress: status == TouchGestureArea.Recognized &&
3511+ touchPoints.length >= minimumTouchPoints &&
3512+ touchPoints.length <= maximumTouchPoints
3513+ readonly property bool recognisedDrag: priv.wasRecognisedPress && dragging
3514+
3515+ signal pressed(int x, int y)
3516+ signal clicked
3517+ signal dragStarted
3518+ signal dropped
3519+ signal cancelled
3520+
3521+ onEnabledChanged: {
3522+ if (!enabled) {
3523+ if (priv.dragObject) root.cancelled();
3524+ priv.wasRecognisedDrag = false;
3525+ priv.wasRecognisedPress = false;
3526+ }
3527+ }
3528+
3529+ onRecognisedPressChanged: {
3530+ if (recognisedPress) {
3531+ // get the app at the center of the gesture
3532+ var centerX = 0;
3533+ var centerY = 0;
3534+ for (var i = 0; i < touchPoints.length; i++) {
3535+ centerX += touchPoints[i].x;
3536+ centerY += touchPoints[i].y;
3537+ }
3538+ centerX = centerX/touchPoints.length;
3539+ centerY = centerY/touchPoints.length;
3540+
3541+ pressed(centerX, centerY);
3542+ priv.wasRecognisedPress = true;
3543+ }
3544+ }
3545+
3546+ onStatusChanged: {
3547+ if (status != TouchGestureArea.Recognized) {
3548+ if (status == TouchGestureArea.Rejected) {
3549+ root.cancelled();
3550+ } else if (status == TouchGestureArea.WaitingForTouch) {
3551+ if (priv.wasRecognisedPress) {
3552+ if (!priv.wasRecognisedDrag) {
3553+ root.clicked();
3554+ } else {
3555+ root.dropped();
3556+ }
3557+ }
3558+ }
3559+ priv.wasRecognisedDrag = false;
3560+ priv.wasRecognisedPress = false;
3561+ }
3562+ }
3563+
3564+ onRecognisedDragChanged: {
3565+ if (enableDrag && recognisedDrag) {
3566+ priv.wasRecognisedDrag = true;
3567+ root.dragStarted()
3568+ }
3569+ }
3570+
3571+ QtObject {
3572+ id: priv
3573+ property var dragObject: null
3574+
3575+ property bool wasRecognisedPress: false
3576+ property bool wasRecognisedDrag: false
3577+ }
3578+
3579+ onCancelled: {
3580+ if (priv.dragObject) {
3581+ var obj = priv.dragObject;
3582+ priv.dragObject = null;
3583+
3584+ obj.Drag.cancel();
3585+ obj.destroy();
3586+ }
3587+ }
3588+
3589+ onDragStarted: {
3590+ if (dragComponentProperties) {
3591+ priv.dragObject = dragComponent.createObject(root, dragComponentProperties);
3592+ } else {
3593+ priv.dragObject = dragComponent.createObject(root);
3594+ }
3595+ priv.dragObject.Drag.start();
3596+ }
3597+
3598+ onDropped: {
3599+ if (priv.dragObject) {
3600+ var obj = priv.dragObject;
3601+ priv.dragObject = null;
3602+
3603+ obj.Drag.drop();
3604+ obj.destroy();
3605+ }
3606+ }
3607+
3608+ Binding {
3609+ target: priv.dragObject
3610+ when: priv.dragObject && priv.wasRecognisedDrag
3611+ property: "x"
3612+ value: {
3613+ if (!priv.dragObject) return 0;
3614+ var sum = 0;
3615+ for (var i = 0; i < root.touchPoints.length; i++) {
3616+ sum += root.touchPoints[i].x;
3617+ }
3618+ return sum/root.touchPoints.length - priv.dragObject.width/2;
3619+ }
3620+ }
3621+
3622+ Binding {
3623+ target: priv.dragObject
3624+ when: priv.dragObject && priv.wasRecognisedDrag
3625+ property: "y"
3626+ value: {
3627+ if (!priv.dragObject) return 0;
3628+ var sum = 0;
3629+ for (var i = 0; i < root.touchPoints.length; i++) {
3630+ sum += root.touchPoints[i].y;
3631+ }
3632+ return sum/root.touchPoints.length - priv.dragObject.height/2;
3633+ }
3634+ }
3635+}
3636
3637=== modified file 'qml/Stages/TabletStage.qml'
3638--- qml/Stages/TabletStage.qml 2016-03-10 09:19:38 +0000
3639+++ qml/Stages/TabletStage.qml 2016-03-16 20:46:19 +0000
3640@@ -46,7 +46,7 @@
3641 if (delta < 0) { delta += 360; }
3642 delta = delta % 360;
3643
3644- var supportedOrientations = spreadDelegate.application.supportedOrientations;
3645+ var supportedOrientations = spreadDelegate.supportedOrientations;
3646 if (supportedOrientations === Qt.PrimaryOrientation) {
3647 supportedOrientations = spreadDelegate.orientations.primary;
3648 }
3649@@ -78,9 +78,24 @@
3650
3651 orientationChangesEnabled: priv.mainAppOrientationChangesEnabled
3652
3653- supportedOrientations: mainApp ? mainApp.supportedOrientations
3654- : (Qt.PortraitOrientation | Qt.LandscapeOrientation
3655- | Qt.InvertedPortraitOrientation | Qt.InvertedLandscapeOrientation)
3656+ supportedOrientations: {
3657+ if (mainApp) {
3658+ var orientations = mainApp.supportedOrientations;
3659+ orientations |= Qt.LandscapeOrientation | Qt.InvertedLandscapeOrientation;
3660+ if (priv.sideStageAppId && !spreadView.surfaceDragging) {
3661+ // If we have a sidestage app, support Portrait orientation
3662+ // so that it will switch the sidestage app to mainstage on rotate
3663+ orientations |= Qt.PortraitOrientation|Qt.InvertedPortraitOrientation;
3664+ }
3665+ return orientations;
3666+ } else {
3667+ // we just don't care
3668+ return Qt.PortraitOrientation |
3669+ Qt.LandscapeOrientation |
3670+ Qt.InvertedPortraitOrientation |
3671+ Qt.InvertedLandscapeOrientation;
3672+ }
3673+ }
3674
3675 onWidthChanged: {
3676 spreadView.selectedIndex = -1;
3677@@ -88,13 +103,6 @@
3678 spreadView.contentX = -spreadView.shift;
3679 }
3680
3681- onShellOrientationChanged: {
3682- if (shellOrientation == Qt.PortraitOrientation || shellOrientation == Qt.InvertedPortraitOrientation) {
3683- ApplicationManager.focusApplication(priv.mainStageAppId);
3684- priv.sideStageAppId = "";
3685- }
3686- }
3687-
3688 onInverseProgressChanged: {
3689 // This can't be a simple binding because that would be triggered after this handler
3690 // while we need it active before doing the anition left/right
3691@@ -170,20 +178,7 @@
3692
3693 property int highlightIndex: 0
3694
3695- onFocusedAppIdChanged: {
3696- if (priv.focusedAppId.length > 0) {
3697- var focusedApp = ApplicationManager.findApplication(focusedAppId);
3698- if (focusedApp.stage == ApplicationInfoInterface.SideStage) {
3699- priv.sideStageAppId = focusedAppId;
3700- } else {
3701- priv.mainStageAppId = focusedAppId;
3702- root.mainApp = focusedApp;
3703- }
3704- }
3705-
3706- appId0 = ApplicationManager.count >= 1 ? ApplicationManager.get(0).appId : "";
3707- appId1 = ApplicationManager.count > 1 ? ApplicationManager.get(1).appId : "";
3708- }
3709+ onFocusedAppIdChanged: updateStageApps()
3710
3711 onFocusedAppDelegateChanged: {
3712 if (focusedAppDelegate) {
3713@@ -229,6 +224,46 @@
3714 onHighlightIndexChanged: {
3715 spreadView.contentX = highlightIndex * spreadView.contentWidth / (spreadRepeater.count + 2)
3716 }
3717+
3718+ function getTopApp(stage) {
3719+ for (var i = 0; i < ApplicationManager.count; i++) {
3720+ var app = ApplicationManager.get(i)
3721+ if (app.stage === stage) {
3722+ return app;
3723+ }
3724+ }
3725+ return null;
3726+ }
3727+
3728+ function setAppStage(appId, stage, save) {
3729+ var app = ApplicationManager.findApplication(appId);
3730+ if (app) {
3731+ app.stage = stage;
3732+ if (save) {
3733+ WindowStateStorage.saveStage(appId, stage);
3734+ }
3735+ }
3736+ }
3737+
3738+ function updateStageApps() {
3739+ var app = priv.getTopApp(ApplicationInfoInterface.MainStage);
3740+ priv.mainStageAppId = app ? app.appId : ""
3741+ root.mainApp = app;
3742+
3743+ if (sideStage.shown) {
3744+ app = priv.getTopApp(ApplicationInfoInterface.SideStage);
3745+ priv.sideStageAppId = app ? app.appId : ""
3746+ } else {
3747+ priv.sideStageAppId = "";
3748+ }
3749+
3750+ appId0 = ApplicationManager.count >= 1 ? ApplicationManager.get(0).appId : "";
3751+ appId1 = ApplicationManager.count > 1 ? ApplicationManager.get(1).appId : "";
3752+ }
3753+
3754+ readonly property bool sideStageEnabled: root.shellOrientation == Qt.LandscapeOrientation ||
3755+ root.shellOrientation == Qt.InvertedLandscapeOrientation
3756+ Component.onCompleted: updateStageApps();
3757 }
3758
3759 Connections {
3760@@ -256,7 +291,8 @@
3761 ApplicationManager.focusApplication("unity8-dash")
3762 }
3763 if (priv.sideStageAppId == appId) {
3764- priv.sideStageAppId = "";
3765+ var app = priv.getTopApp(ApplicationInfoInterface.SideStage);
3766+ priv.sideStageAppId = app === null ? "" : app.appId;
3767 }
3768
3769 if (ApplicationManager.count == 0) {
3770@@ -267,6 +303,7 @@
3771 // lets make sure the spread doesn't mess up by the changing app list.
3772 spreadView.phase = 0;
3773 spreadView.contentX = -spreadView.shift;
3774+
3775 ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
3776 }
3777 }
3778@@ -322,6 +359,7 @@
3779 property int selectedIndex: -1
3780 property int draggedDelegateCount: 0
3781 property int closingIndex: -1
3782+ property var selectedApplication: selectedIndex !== -1 ? ApplicationManager.get(selectedIndex) : null
3783
3784 // FIXME: Workaround Flickable's not keepping its contentX still when resized
3785 onContentXChanged: { forceItToRemainStillIfBeingResized(); }
3786@@ -343,15 +381,8 @@
3787 }
3788 }
3789
3790- property bool sideStageDragging: sideStageDragHandle.dragging
3791- property real sideStageDragProgress: sideStageDragHandle.progress
3792-
3793- onSideStageDragProgressChanged: {
3794- if (sideStageDragProgress == 1) {
3795- ApplicationManager.focusApplication(priv.mainStageAppId);
3796- priv.sideStageAppId = "";
3797- }
3798- }
3799+ property real sideStageDragProgress: sideStage.progress
3800+ property bool surfaceDragging: triGestureArea.recognisedDrag
3801
3802 // In case the ApplicationManager already holds an app when starting up we're missing animations
3803 // Make sure we end up in the same state
3804@@ -402,7 +433,7 @@
3805 }
3806 ]
3807 state: {
3808- if (priv.mainStageAppId && !priv.sideStageAppId) {
3809+ if ((priv.mainStageAppId && !priv.sideStageAppId) || !priv.sideStageEnabled) {
3810 return "main";
3811 }
3812 if (!priv.mainStageAppId && priv.sideStageAppId) {
3813@@ -419,12 +450,26 @@
3814 // Flickabe.contentX wiggles during resizes. Don't react to it.
3815 return;
3816 }
3817- if (spreadView.phase == 0 && spreadView.shiftedContentX > spreadView.width * spreadView.positionMarker2) {
3818- spreadView.phase = 1;
3819- } else if (spreadView.phase == 1 && spreadView.shiftedContentX > spreadView.width * spreadView.positionMarker4) {
3820- spreadView.phase = 2;
3821- } else if (spreadView.phase == 1 && spreadView.shiftedContentX < spreadView.width * spreadView.positionMarker2) {
3822- spreadView.phase = 0;
3823+
3824+ switch (phase) {
3825+ case 0:
3826+ // the "spreadEnabled" part is because when code does "phase = 0; contentX = -shift" to
3827+ // dismiss the spread because spreadEnabled went to false, for some reason, during tests,
3828+ // Flickable might jump in and change contentX value back, causing the code below to do
3829+ // "phase = 1" which will make the spread stay.
3830+ // It sucks that we have no control whatsoever over whether or when Flickable animates its
3831+ // contentX.
3832+ if (root.spreadEnabled && shiftedContentX > width * positionMarker2) {
3833+ phase = 1;
3834+ }
3835+ break;
3836+ case 1:
3837+ if (shiftedContentX < width * positionMarker2) {
3838+ phase = 0;
3839+ } else if (shiftedContentX >= width * positionMarker4 && !spreadDragArea.dragging) {
3840+ phase = 2;
3841+ }
3842+ break;
3843 }
3844 }
3845
3846@@ -446,6 +491,7 @@
3847 }
3848
3849 function snapTo(index) {
3850+ snapAnimation.stop();
3851 spreadView.selectedIndex = index;
3852 snapAnimation.targetContentX = -shift;
3853 snapAnimation.start();
3854@@ -455,55 +501,41 @@
3855 // We don't want to really reorder them in the model because that allows us to keep track
3856 // of the last focused order.
3857 function indexToZIndex(index) {
3858+ // only shuffle when we've got a main and overlay
3859+ if (state !== "mainAndOverlay") return index;
3860+
3861 var app = ApplicationManager.get(index);
3862 if (!app) {
3863 return index;
3864 }
3865
3866- var active = app.appId == priv.mainStageAppId || app.appId == priv.sideStageAppId;
3867- if (active && app.stage == ApplicationInfoInterface.MainStage) {
3868- // if this app is active, and its the MainStage, always put it to index 0
3869+ // don't shuffle indexes greater than "actives or next"
3870+ if (index > 2) return index;
3871+
3872+ if (app.appId === priv.mainStageAppId) {
3873+ // Active main stage always at 0
3874 return 0;
3875 }
3876- if (active && app.stage == ApplicationInfoInterface.SideStage) {
3877- if (!priv.mainStageAppId) {
3878- // Only have SS apps running. Put the active one at 0
3879- return 0;
3880- }
3881-
3882- // Precondition now: There's an active MS app and this is SS app:
3883- if (spreadView.nextInStack >= 0 && ApplicationManager.get(spreadView.nextInStack).stage == ApplicationInfoInterface.MainStage) {
3884- // If the next app coming from the right is a MS app, we need to elevate this SS ap above it.
3885- // Put it to at least level 2, or higher if there's more apps coming in before this one.
3886- return Math.max(index, 2);
3887- } else {
3888- // if this is no next app to come in from the right, place this one at index 1, just on top the active MS app.
3889- return 1;
3890- }
3891- }
3892- if (index <= 2 && app.stage == ApplicationInfoInterface.MainStage && priv.sideStageAppId) {
3893- // Ok, this is an inactive MS app. If there's an active SS app around, we need to place this one
3894- // in between the active MS app and the active SS app, so that it comes in from there when dragging from the right.
3895- // If there's now active SS app, just leave it where it is.
3896- return priv.indexOf(priv.sideStageAppId) < index ? index - 1 : index;
3897- }
3898- if (index == spreadView.nextInStack && app.stage == ApplicationInfoInterface.SideStage) {
3899- // This is a SS app and the next one to come in from the right:
3900- if (priv.sideStageAppId && priv.mainStageAppId) {
3901- // If there's both, an active MS and an active SS app, put this one right on top of that
3902- return 2;
3903- }
3904- // Or if there's only one other active app, put it on top of that.
3905- // The case that there isn't any other active app is already handled above.
3906- return 1;
3907- }
3908- if (index == 2 && spreadView.nextInStack == 1 && priv.sideStageAppId) {
3909- // If its index 2 but not the next one to come in, it means
3910- // we've pulled another one down to index 2. Move this one up to 2 instead.
3911- return 3;
3912- }
3913- // don't touch all others... (mostly index > 3 + simple cases where the above doesn't shuffle much)
3914- return index;
3915+
3916+ if (spreadView.nextInStack > 0) {
3917+ var nextAppInStack = ApplicationManager.get(spreadView.nextInStack);
3918+
3919+ if (index === spreadView.nextInStack) {
3920+ // this is the next app in stack.
3921+
3922+ if (app.stage === ApplicationInfoInterface.SideStage) {
3923+ // if the next app in stack is a sidestage app, it must order on top of other side stage app
3924+ return Math.min(2, ApplicationManager.count-1);
3925+ }
3926+ return 1;
3927+ }
3928+ if (nextAppInStack.stage === ApplicationInfoInterface.SideStage) {
3929+ // if the next app in stack is a sidestage app, it must order on top of other side stage app
3930+ return 1;
3931+ }
3932+ return Math.min(2, ApplicationManager.count-1);
3933+ }
3934+ return Math.min(index+1, ApplicationManager.count-1);
3935 }
3936
3937 SequentialAnimation {
3938@@ -521,8 +553,12 @@
3939 script: {
3940 if (spreadView.selectedIndex >= 0) {
3941 var newIndex = spreadView.selectedIndex;
3942+ var application = ApplicationManager.get(newIndex);
3943+ if (application.stage === ApplicationInfoInterface.SideStage) {
3944+ sideStage.showNow();
3945+ }
3946 spreadView.selectedIndex = -1;
3947- ApplicationManager.focusApplication(ApplicationManager.get(newIndex).appId);
3948+ ApplicationManager.focusApplication(application.appId);
3949 spreadView.phase = 0;
3950 spreadView.contentX = -spreadView.shift;
3951 }
3952@@ -545,87 +581,82 @@
3953 spreadView.snapTo(0);
3954 }
3955
3956- Rectangle {
3957- id: sideStageBackground
3958- color: "black"
3959- width: spreadView.sideStageWidth * (1 - sideStageDragHandle.progress)
3960+ DropArea {
3961+ objectName: "MainStageDropArea"
3962+ anchors {
3963+ left: parent.left
3964+ top: parent.top
3965+ bottom: parent.bottom
3966+ }
3967+ width: spreadView.width - sideStage.width
3968+ enabled: priv.sideStageEnabled
3969+
3970+ onDropped: {
3971+ priv.setAppStage(drag.source.appId, ApplicationInfoInterface.MainStage, true);
3972+ ApplicationManager.focusApplication(drag.source.appId);
3973+ }
3974+ keys: "SideStage"
3975+ }
3976+
3977+ SideStage {
3978+ id: sideStage
3979+ objectName: "sideStage"
3980 height: priv.landscapeHeight
3981 x: spreadView.width - width
3982- z: spreadView.indexToZIndex(priv.indexOf(priv.sideStageAppId))
3983- opacity: spreadView.phase == 0 ? 1 : 0
3984- Behavior on opacity { UbuntuNumberAnimation {} }
3985- }
3986-
3987- Item {
3988- id: sideStageDragHandle
3989- anchors.right: sideStageBackground.left
3990- anchors.top: sideStageBackground.top
3991- width: units.gu(2)
3992- height: priv.landscapeHeight
3993- z: sideStageBackground.z
3994- opacity: spreadView.phase <= 0 && spreadView.sideStageVisible ? 1 : 0
3995- property real progress: 0
3996- property bool dragging: false
3997-
3998- Behavior on opacity { UbuntuNumberAnimation {} }
3999-
4000- Connections {
4001- target: spreadView
4002- onSideStageVisibleChanged: {
4003- if (spreadView.sideStageVisible) {
4004- sideStageDragHandle.progress = 0;
4005+ z: {
4006+ if (!priv.mainStageAppId) return 0;
4007+
4008+ if (priv.sideStageAppId && spreadView.nextInStack > 0) {
4009+ var nextAppInStack = ApplicationManager.get(spreadView.nextInStack);
4010+
4011+ if (nextAppInStack.stage === ApplicationInfoInterface.MainStage) {
4012+ // if the next app in stack is a main stage app, put the sidestage on top of it.
4013+ return 2;
4014 }
4015- }
4016- }
4017-
4018- Image {
4019- anchors.centerIn: parent
4020- width: sideStageDragHandleMouseArea.pressed ? parent.width * 2 : parent.width
4021- height: parent.height
4022- source: "graphics/sidestage_handle@20.png"
4023- Behavior on width { UbuntuNumberAnimation {} }
4024- }
4025-
4026- MouseArea {
4027- id: sideStageDragHandleMouseArea
4028+ return 1;
4029+ }
4030+
4031+ return 1;
4032+ }
4033+ visible: progress != 0
4034+ enabled: priv.sideStageEnabled && sideStageDropArea.dropAllowed
4035+ opacity: priv.sideStageEnabled && !spreadView.active ? 1 : 0
4036+ Behavior on opacity { UbuntuNumberAnimation {} }
4037+
4038+ onShownChanged: {
4039+ if (!shown && ApplicationManager.focusedApplicationId == priv.sideStageAppId) {
4040+ ApplicationManager.requestFocusApplication(priv.mainStageAppId);
4041+ }
4042+ priv.updateStageApps();
4043+ if (shown && priv.sideStageAppId) {
4044+ ApplicationManager.requestFocusApplication(priv.sideStageAppId);
4045+ }
4046+ }
4047+
4048+ DropArea {
4049+ id: sideStageDropArea
4050+ objectName: "SideStageDropArea"
4051 anchors.fill: parent
4052- enabled: spreadView.shiftedContentX == 0
4053- property int startX
4054- property var gesturePoints: new Array()
4055- property real totalDiff
4056-
4057- onPressed: {
4058- gesturePoints = [];
4059- startX = mouseX;
4060- totalDiff = 0.0;
4061- sideStageDragHandle.progress = 0;
4062- sideStageDragHandle.dragging = true;
4063- }
4064- onMouseXChanged: {
4065- totalDiff += mouseX - startX;
4066- if (priv.mainStageAppId) {
4067- sideStageDragHandle.progress = Math.max(0, totalDiff / spreadView.sideStageWidth);
4068- }
4069- gesturePoints.push(mouseX);
4070- }
4071- onReleased: {
4072- if (priv.mainStageAppId) {
4073- var oneWayFlick = priv.evaluateOneWayFlick(gesturePoints);
4074- sideStageDragSnapAnimation.to = sideStageDragHandle.progress > 0.5 || oneWayFlick ? 1 : 0;
4075- sideStageDragSnapAnimation.start();
4076- } else {
4077- sideStageDragHandle.dragging = false;
4078- }
4079- }
4080- }
4081- UbuntuNumberAnimation {
4082- id: sideStageDragSnapAnimation
4083- target: sideStageDragHandle
4084- property: "progress"
4085-
4086- onRunningChanged: {
4087- if (!running) {
4088- sideStageDragHandle.dragging = false;
4089+
4090+ property bool dropAllowed: true
4091+
4092+ onEntered: {
4093+ dropAllowed = drag.keys != "Disabled";
4094+ }
4095+ onExited: {
4096+ dropAllowed = true;
4097+ }
4098+ onDropped: {
4099+ if (drop.keys == "MainStage") {
4100+ priv.setAppStage(drop.source.appId, ApplicationInfoInterface.SideStage, true);
4101+ ApplicationManager.requestFocusApplication(drop.source.appId);
4102+ }
4103+ }
4104+ drag {
4105+ onSourceChanged: {
4106+ if (!sideStageDropArea.drag.source) {
4107+ dropAllowed = true;
4108+ }
4109 }
4110 }
4111 }
4112@@ -640,37 +671,53 @@
4113 id: spreadTile
4114 objectName: model.appId ? "tabletSpreadDelegate_" + model.appId
4115 : "tabletSpreadDelegate_null";
4116- width: {
4117- if (wantsMainStage) {
4118- return spreadView.width;
4119- } else {
4120- return spreadView.sideStageWidth;
4121- }
4122- }
4123- height: {
4124- if (wantsMainStage) {
4125- return spreadView.height;
4126- } else {
4127- return priv.landscapeHeight;
4128- }
4129- }
4130- active: model.appId == priv.mainStageAppId || model.appId == priv.sideStageAppId
4131- zIndex: spreadView.indexToZIndex(index)
4132+ width: spreadView.width
4133+ height: spreadView.height
4134+ active: appId == priv.mainStageAppId || appId == priv.sideStageAppId
4135+ zIndex: selected && stage == ApplicationInfoInterface.MainStage ? 0 : spreadView.indexToZIndex(index)
4136 selected: spreadView.selectedIndex == index
4137 otherSelected: spreadView.selectedIndex >= 0 && !selected
4138- isInSideStage: priv.sideStageAppId == model.appId
4139+ isInSideStage: priv.sideStageAppId === appId
4140 interactive: !spreadView.interactive && spreadView.phase === 0 && root.interactive
4141 swipeToCloseEnabled: spreadView.interactive && !snapAnimation.running
4142 maximizedAppTopMargin: root.maximizedAppTopMargin
4143- dragOffset: !isDash && model.appId == priv.mainStageAppId && root.inverseProgress > 0 && spreadView.phase === 0 ? root.inverseProgress : 0
4144+ dragOffset: !isDash && appId == priv.mainStageAppId && root.inverseProgress > 0 && spreadView.phase === 0 ? root.inverseProgress : 0
4145 application: ApplicationManager.get(index)
4146 closeable: !isDash
4147 highlightShown: root.altTabPressed && priv.highlightIndex == zIndex
4148
4149 readonly property bool wantsMainStage: model.stage == ApplicationInfoInterface.MainStage
4150
4151+ readonly property string appId: model.appId
4152 readonly property bool isDash: model.appId == "unity8-dash"
4153
4154+ stage: model.stage
4155+ fullscreen: {
4156+ if (mainApp && stage === ApplicationInfoInterface.SideStage) {
4157+ return mainApp.fullscreen;
4158+ }
4159+ return application ? application.fullscreen : false;
4160+ }
4161+
4162+ supportedOrientations: {
4163+ if (application) {
4164+ var orientations = application.supportedOrientations;
4165+ if (stage == ApplicationInfoInterface.MainStage) {
4166+ // When an app is in the mainstage, it always supports Landscape|InvertedLandscape
4167+ // so that we can drag it from the main stage to the side stage
4168+ orientations |= Qt.LandscapeOrientation | Qt.InvertedLandscapeOrientation;
4169+ }
4170+ return orientations;
4171+ } else {
4172+ // we just don't care
4173+ return Qt.PortraitOrientation |
4174+ Qt.LandscapeOrientation |
4175+ Qt.InvertedPortraitOrientation |
4176+ Qt.InvertedLandscapeOrientation;
4177+ }
4178+ }
4179+
4180+
4181 Binding {
4182 target: spreadTile.application
4183 property: "exemptFromLifecycle"
4184@@ -701,6 +748,28 @@
4185 enabled: spreadView.closingIndex >= 0
4186 UbuntuNumberAnimation {}
4187 }
4188+ Connections {
4189+ target: priv
4190+ onSideStageEnabledChanged: refreshStage()
4191+ }
4192+
4193+ Component.onCompleted: {
4194+ refreshStage()
4195+ stageChanged.connect(priv.updateStageApps);
4196+ }
4197+
4198+ function refreshStage() {
4199+ var stage = ApplicationInfoInterface.MainStage;
4200+ if (priv.sideStageEnabled) {
4201+ if (application && application.supportedOrientations & (Qt.PortraitOrientation|Qt.InvertedPortraitOrientation)) {
4202+ stage = WindowStateStorage.getStage(appId);
4203+ }
4204+ }
4205+
4206+ if (model.stage !== stage) {
4207+ priv.setAppStage(appId, stage, false);
4208+ }
4209+ }
4210
4211 // This is required because none of the bindings are triggered in some cases:
4212 // When an app is closed, it might happen that ApplicationManager.get(nextInStack)
4213@@ -725,7 +794,7 @@
4214
4215 // TODO: Hiding tile when progress is such that it will be off screen.
4216 property bool occluded: {
4217- if (spreadView.active) return false;
4218+ if (spreadView.active && !offScreen) return false;
4219 else if (spreadTile.active) return false;
4220 else if (xTranslateAnimating) return false;
4221 else if (z <= 1 && priv.focusedAppDelegateIsDislocated) return false;
4222@@ -749,17 +818,87 @@
4223 return progress;
4224 }
4225
4226- shellOrientationAngle: wantsMainStage ? root.shellOrientationAngle : 0
4227- shellOrientation: wantsMainStage ? root.shellOrientation : Qt.PortraitOrientation
4228- orientations: Orientations {
4229- primary: spreadTile.wantsMainStage ? root.orientations.primary : Qt.PortraitOrientation
4230- native_: spreadTile.wantsMainStage ? root.orientations.native_ : Qt.PortraitOrientation
4231+ shellOrientationAngle: root.shellOrientationAngle
4232+ shellOrientation: root.shellOrientation
4233+ orientations: root.orientations
4234+
4235+ states: [
4236+ State {
4237+ name: "MainStage"
4238+ when: spreadTile.stage == ApplicationInfoInterface.MainStage
4239+ },
4240+ State {
4241+ name: "SideStage"
4242+ when: spreadTile.stage == ApplicationInfoInterface.SideStage
4243+
4244+ PropertyChanges {
4245+ target: spreadTile
4246+ width: spreadView.sideStageWidth
4247+ height: priv.landscapeHeight
4248+
4249+ supportedOrientations: Qt.PortraitOrientation
4250+ shellOrientationAngle: 0
4251+ shellOrientation: Qt.PortraitOrientation
4252+ orientations: sideStageOrientations
4253+ }
4254+ }
4255+ ]
4256+
4257+ Orientations {
4258+ id: sideStageOrientations
4259+ primary: Qt.PortraitOrientation
4260+ native_: Qt.PortraitOrientation
4261 portrait: root.orientations.portrait
4262 invertedPortrait: root.orientations.invertedPortrait
4263 landscape: root.orientations.landscape
4264 invertedLandscape: root.orientations.invertedLandscape
4265 }
4266
4267+ transitions: [
4268+ Transition {
4269+ to: "SideStage"
4270+ SequentialAnimation {
4271+ PropertyAction {
4272+ target: spreadTile
4273+ properties: "width,height,supportedOrientations,shellOrientationAngle,shellOrientation,orientations"
4274+ }
4275+ ScriptAction {
4276+ script: {
4277+ // rotate immediately.
4278+ spreadTile.matchShellOrientation();
4279+ if (ApplicationManager.focusedApplicationId === spreadTile.appId &&
4280+ priv.sideStageEnabled && !sideStage.shown) {
4281+ // Sidestage was focused, so show the side stage.
4282+ sideStage.show();
4283+ // if we've switched to a main app which doesnt support portrait, hide the side stage.
4284+ } else if (mainApp && (mainApp.supportedOrientations & (Qt.PortraitOrientation|Qt.InvertedPortraitOrientation)) == 0) {
4285+ sideStage.hideNow();
4286+ }
4287+ }
4288+ }
4289+ }
4290+ },
4291+ Transition {
4292+ from: "SideStage"
4293+ SequentialAnimation {
4294+ ScriptAction {
4295+ script: {
4296+ if (priv.sideStageAppId === spreadTile.appId &&
4297+ mainApp && (mainApp.supportedOrientations & (Qt.PortraitOrientation|Qt.InvertedPortraitOrientation)) == 0) {
4298+ // The mainstage app did not natively support portrait orientation, so focus the sidestage.
4299+ ApplicationManager.requestFocusApplication(spreadTile.appId);
4300+ }
4301+ }
4302+ }
4303+ PropertyAction {
4304+ target: spreadTile
4305+ properties: "width,height,supportedOrientations,shellOrientationAngle,shellOrientation,orientations"
4306+ }
4307+ ScriptAction { script: { spreadTile.matchShellOrientation(); } }
4308+ }
4309+ }
4310+ ]
4311+
4312 onClicked: {
4313 if (spreadView.phase == 2) {
4314 spreadView.snapTo(index);
4315@@ -779,6 +918,16 @@
4316 ApplicationManager.stopApplication(ApplicationManager.get(index).appId);
4317 }
4318
4319+ onFocusChanged: {
4320+ if (focus && ApplicationManager.focusedApplicationId !== appId) {
4321+ ApplicationManager.focusApplication(appId);
4322+ }
4323+
4324+ if (focus && priv.sideStageEnabled && stage === ApplicationInfoInterface.SideStage) {
4325+ sideStage.show();
4326+ }
4327+ }
4328+
4329 Binding {
4330 target: root
4331 when: model.appId == priv.mainStageAppId
4332@@ -803,6 +952,71 @@
4333 }
4334 }
4335
4336+ TabletSideStageTouchGesture {
4337+ id: triGestureArea
4338+ anchors.fill: parent
4339+ enabled: priv.sideStageEnabled && !spreadView.active
4340+ property var dragObject: null
4341+ property string appId: ""
4342+ dragComponent: dragComponent
4343+ dragComponentProperties: { "appId": appId }
4344+
4345+ onPressed: {
4346+ function matchDelegate(obj) { return String(obj.objectName).indexOf("tabletSpreadDelegate") >= 0; }
4347+
4348+ var delegateAtCenter = Functions.itemAt(spreadRow, x, y, matchDelegate);
4349+ if (!delegateAtCenter) return;
4350+
4351+ appId = delegateAtCenter.appId;
4352+ }
4353+
4354+ onClicked: {
4355+ if (sideStage.shown) {
4356+ sideStage.hide();
4357+ } else {
4358+ sideStage.show();
4359+ }
4360+ }
4361+
4362+ onDragStarted: {
4363+ // If we're dragging to the sidestage.
4364+ if (!sideStage.shown) {
4365+ sideStage.show();
4366+ }
4367+ }
4368+
4369+ Component {
4370+ id: dragComponent
4371+ SessionContainer {
4372+ property string appId: ""
4373+ property var application: ApplicationManager.findApplication(appId)
4374+
4375+ session: application ? application.session : null
4376+ interactive: false
4377+ resizeSurface: false
4378+ focus: false
4379+
4380+ width: units.gu(40)
4381+ height: units.gu(40)
4382+
4383+ Drag.hotSpot.x: width/2
4384+ Drag.hotSpot.y: height/2
4385+ // only accept opposite stage.
4386+ Drag.keys: {
4387+ if (!application) return "Disabled";
4388+
4389+ if (application.stage === ApplicationInfo.MainStage) {
4390+ if (application.supportedOrientations & (Qt.PortraitOrientation|Qt.InvertedPortraitOrientation)) {
4391+ return "MainStage";
4392+ }
4393+ return "Disabled";
4394+ }
4395+ return "SideStage";
4396+ }
4397+ }
4398+ }
4399+ }
4400+
4401 //eat touch events during the right edge gesture
4402 MouseArea {
4403 anchors.fill: parent
4404@@ -812,7 +1026,8 @@
4405 DirectionalDragArea {
4406 id: spreadDragArea
4407 objectName: "spreadDragArea"
4408- anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
4409+ x: parent.width - root.dragAreaWidth
4410+ anchors { top: parent.top; bottom: parent.bottom }
4411 width: root.dragAreaWidth
4412 direction: Direction.Leftwards
4413 enabled: (spreadView.phase != 2 && root.spreadEnabled) || dragging
4414
4415=== modified file 'qml/Stages/TransformedTabletSpreadDelegate.qml'
4416--- qml/Stages/TransformedTabletSpreadDelegate.qml 2015-11-04 14:58:05 +0000
4417+++ qml/Stages/TransformedTabletSpreadDelegate.qml 2016-03-16 20:46:19 +0000
4418@@ -31,6 +31,7 @@
4419 // Set this to true when this tile a currently active on either the MS or the SS.
4420 property bool active: false
4421
4422+ property int stage
4423 property int zIndex
4424 property real progress: 0
4425 property real animatedProgress: 0
4426@@ -49,10 +50,11 @@
4427
4428 property int dragOffset: 0
4429 readonly property alias xTranslateAnimating: xTranslateAnimation.running
4430+ readonly property bool offScreen: priv.xTranslate >= 0
4431
4432 dropShadow: spreadView.active ||
4433 (active
4434- && (model.stage == ApplicationInfoInterface.MainStage || !priv.shellIsLandscape)
4435+ && (stage == ApplicationInfoInterface.MainStage || !priv.shellIsLandscape)
4436 && priv.xTranslate != 0)
4437
4438 onSelectedChanged: {
4439@@ -141,9 +143,9 @@
4440 enabled: !spreadView.active &&
4441 !snapAnimation.running &&
4442 model.appId !== "unity8-dash" &&
4443- !spreadView.sideStageDragging &&
4444 spreadView.animateX &&
4445- !spreadView.beingResized
4446+ !spreadView.beingResized &&
4447+ priv.state !== "sideStage"
4448 UbuntuNumberAnimation {
4449 id: xTranslateAnimation
4450 duration: UbuntuAnimation.FastDuration
4451@@ -153,21 +155,20 @@
4452 property real xTranslate: {
4453 var newTranslate = 0;
4454
4455- if (otherSelected) {
4456- return priv.selectedXTranslate;
4457- }
4458-
4459- if (isSelected) {
4460- if (model.stage == ApplicationInfoInterface.MainStage) {
4461+ // selected app or opposite stage active app.
4462+ if (isSelected || (otherSelected && root.active && spreadView.selectedApplication && spreadView.selectedApplication.stage !== stage)) {
4463+ if (stage == ApplicationInfoInterface.MainStage) {
4464 return linearAnimation(selectedProgress, negativeProgress, selectedXTranslate, -spreadView.width, root.progress);
4465 } else {
4466 return linearAnimation(selectedProgress, negativeProgress, selectedXTranslate, -spreadView.sideStageWidth, root.progress);
4467 }
4468+ } else if (otherSelected) {
4469+ return selectedXTranslate;
4470 }
4471
4472 // The tile should move a bit to the left if a new one comes on top of it, but not for the Side Stage and not
4473 // when we're only dragging the side stage in on top of a main stage app
4474- var shouldMoveAway = spreadView.nextInStack >= 0 && priv.movedActive && model.stage === ApplicationInfoInterface.MainStage &&
4475+ var shouldMoveAway = spreadView.nextInStack >= 0 && priv.movedActive && stage === ApplicationInfoInterface.MainStage &&
4476 ApplicationManager.get(spreadView.nextInStack).stage === ApplicationInfoInterface.MainStage;
4477
4478 if (active) {
4479@@ -177,36 +178,45 @@
4480 newTranslate += linearAnimation(0, spreadView.positionMarker2, 0, -units.gu(4), root.animatedProgress);
4481 }
4482 newTranslate += root.dragOffset;
4483- }
4484- if (!spreadView.active && model.appId == "unity8-dash" && !root.active) {
4485+ } else if (!spreadView.active && model.appId == "unity8-dash") {
4486 newTranslate -= root.width;
4487 }
4488
4489- if (nextInStack && spreadView.phase == 0) {
4490- if (model.stage == ApplicationInfoInterface.MainStage) {
4491- if (spreadView.sideStageVisible && root.progress > 0) {
4492- // Move it so it appears from behind the side stage immediately
4493- newTranslate += -spreadView.sideStageWidth;
4494- }
4495- }
4496-
4497- if (model.stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
4498- // This is when we only drag the side stage in, without rotation or snapping
4499- newTranslate = linearAnimation(0, spreadView.positionMarker2, 0, -spreadView.sideStageWidth, root.progress);
4500- } else {
4501- newTranslate += linearAnimation(0, spreadView.positionMarker2, 0, -spreadView.sideStageWidth * spreadView.snapPosition, root.animatedProgress);
4502+ if (!spreadView.active) {
4503+ return newTranslate;
4504+ }
4505+
4506+ var shadowOffset = units.gu(2);
4507+
4508+ if (spreadView.phase == 0) {
4509+ if (nextInStack) {
4510+ if (stage == ApplicationInfoInterface.MainStage) {
4511+ if (spreadView.sideStageVisible && root.progress > 0) {
4512+ // Move it so it appears from behind the side stage immediately
4513+ newTranslate += -spreadView.sideStageWidth;
4514+ }
4515+ }
4516+
4517+ if (stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
4518+ // This is when we only drag the side stage in, without rotation or snapping
4519+ newTranslate = linearAnimation(0, spreadView.positionMarker2, 0, -spreadView.sideStageWidth, root.animatedProgress);
4520+ } else {
4521+ newTranslate += linearAnimation(0, spreadView.positionMarker2, 0, -spreadView.sideStageWidth * spreadView.snapPosition, root.animatedProgress);
4522+ }
4523+ } else if (!root.active) {
4524+ newTranslate += shadowOffset;
4525 }
4526 }
4527
4528 if (spreadView.phase == 1) {
4529 if (nextInStack) {
4530- if (model.stage == ApplicationInfoInterface.MainStage) {
4531+ if (stage == ApplicationInfoInterface.MainStage) {
4532 var startValue = -spreadView.sideStageWidth * spreadView.snapPosition + (spreadView.sideStageVisible ? -spreadView.sideStageWidth : 0);
4533 newTranslate += linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, startValue, priv.phase2StartTranslate, root.animatedProgress);
4534 } else {
4535 var endValue = -spreadView.width + spreadView.width * root.zIndex / 6;
4536 if (!spreadView.sideStageVisible) {
4537- newTranslate += linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, -spreadView.sideStageWidth, priv.phase2StartTranslate, root.progress);
4538+ newTranslate += linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, -spreadView.sideStageWidth, priv.phase2StartTranslate, root.animatedProgress);
4539 } else {
4540 newTranslate += linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, -spreadView.sideStageWidth * spreadView.snapPosition, priv.phase2StartTranslate, root.animatedProgress);
4541 }
4542@@ -215,16 +225,20 @@
4543 var startProgress = spreadView.positionMarker2 - (zIndex * spreadView.positionMarker2 / 2);
4544 var endProgress = spreadView.positionMarker4 - (zIndex * spreadView.tileDistance / spreadView.width);
4545 var startTranslate = -root.width + (shouldMoveAway ? -units.gu(4) : 0);
4546- newTranslate = linearAnimation(startProgress, endProgress, startTranslate, priv.phase2StartTranslate, root.progress);
4547+ newTranslate = linearAnimation(startProgress, endProgress, startTranslate, priv.phase2StartTranslate, root.animatedProgress);
4548 } else {
4549 var startProgress = spreadView.positionMarker2 - (zIndex * spreadView.positionMarker2 / 2);
4550 var endProgress = spreadView.positionMarker4 - (zIndex * spreadView.tileDistance / spreadView.width);
4551- newTranslate = linearAnimation(startProgress, endProgress, 0, priv.phase2StartTranslate, root.progress);
4552+ newTranslate = linearAnimation(startProgress, endProgress, shadowOffset * Math.max(0, zIndex-2), priv.phase2StartTranslate, root.animatedProgress);
4553 }
4554 }
4555
4556 if (spreadView.phase == 2) {
4557- newTranslate = -easingCurve.value * (spreadView.width - root.zIndex * animatedEndDistance);
4558+ if (easingCurve.value == 0 && !nextInStack) {
4559+ newTranslate = shadowOffset;
4560+ } else {
4561+ newTranslate = -easingCurve.value * (spreadView.width - root.zIndex * animatedEndDistance);
4562+ }
4563 }
4564
4565 return newTranslate;
4566@@ -235,33 +249,30 @@
4567 return 1;
4568 }
4569
4570- if (otherSelected) {
4571+ // selected app or opposite stage active app.
4572+ if (isSelected || (otherSelected && root.active && spreadView.selectedApplication && spreadView.selectedApplication.stage !== stage)) {
4573+ return linearAnimation(selectedProgress, negativeProgress, selectedScale, 1, root.progress);
4574+ } else if (otherSelected) {
4575 return selectedScale;
4576 }
4577
4578- if (isSelected) {
4579- return linearAnimation(selectedProgress, negativeProgress, selectedScale, 1, root.progress);
4580- }
4581-
4582 if (spreadView.phase == 0) {
4583 if (nextInStack) {
4584- if (model.stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
4585+ if (stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
4586 return 1;
4587 } else {
4588 var targetScale = root.dragStartScale - ((root.dragStartScale - 1) * spreadView.snapPosition);
4589 return linearAnimation(0, spreadView.positionMarker2, root.dragStartScale, targetScale, root.animatedProgress);
4590 }
4591- } else if (active) {
4592+ } else {
4593 return 1;
4594- } else {
4595- return linearAnimation(0, spreadView.positionMarker2, root.startScale, root.endScale, root.progress);
4596 }
4597 }
4598
4599 if (spreadView.phase == 1) {
4600 if (nextInStack) {
4601 var startScale = 1;
4602- if (model.stage !== ApplicationInfoInterface.SideStage || spreadView.sideStageVisible) {
4603+ if (stage !== ApplicationInfoInterface.SideStage || spreadView.sideStageVisible) {
4604 startScale = root.dragStartScale - ((root.dragStartScale - 1) * spreadView.snapPosition);
4605 }
4606 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, startScale, priv.phase2StartScale, root.animatedProgress);
4607@@ -283,21 +294,22 @@
4608 return 0;
4609 }
4610
4611- if (otherSelected) {
4612+ // selected app or opposite stage active app.
4613+ if (isSelected || (otherSelected && root.active && spreadView.selectedApplication && spreadView.selectedApplication.stage !== stage)) {
4614+ return linearAnimation(selectedProgress, negativeProgress, selectedAngle, 0, root.progress);
4615+ } else if (otherSelected) {
4616 return selectedAngle;
4617 }
4618- if (isSelected) {
4619- return linearAnimation(selectedProgress, negativeProgress, selectedAngle, 0, root.progress);
4620- }
4621
4622 // The tile should rotate a bit when another one comes on top, but not when only dragging the side stage in
4623- var shouldMoveAway = spreadView.nextInStack >= 0 && movedActive &&
4624+ var shouldMoveAway = spreadView.nextInStack == -1 ||
4625+ spreadView.nextInStack >= 0 && priv.movedActive &&
4626 (ApplicationManager.get(spreadView.nextInStack).stage === ApplicationInfoInterface.MainStage ||
4627- model.stage == ApplicationInfoInterface.SideStage);
4628+ stage == ApplicationInfoInterface.SideStage);
4629
4630 if (spreadView.phase == 0) {
4631 if (nextInStack) {
4632- if (model.stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
4633+ if (stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
4634 return 0;
4635 } else {
4636 return linearAnimation(0, spreadView.positionMarker2, root.startAngle, root.startAngle * (1-spreadView.snapPosition), root.animatedProgress);
4637@@ -309,7 +321,7 @@
4638 }
4639 if (spreadView.phase == 1) {
4640 if (nextInStack) {
4641- if (model.stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
4642+ if (stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
4643 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, 0, priv.phase2StartAngle, root.animatedProgress);
4644 } else {
4645 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, root.startAngle * (1-spreadView.snapPosition), priv.phase2StartAngle, root.animatedProgress);
4646@@ -328,31 +340,47 @@
4647 }
4648
4649 property real opacityTransform: {
4650- if (otherSelected && spreadView.phase == 2) {
4651+ // animate opacity for items not snapping into view.
4652+ if (spreadView.phase !== 2) return 1;
4653+ if (root.isSelected) return 1;
4654+
4655+ if (otherSelected) {
4656+ if (root.active && spreadView.selectedApplication && spreadView.selectedApplication.stage !== stage) {
4657+ return 1;
4658+ }
4659 return linearAnimation(selectedProgress, negativeProgress, selectedOpacity, 0, root.progress);
4660 }
4661-
4662 return 1;
4663 }
4664
4665 property real topMarginProgress: {
4666- if (priv.isSelected) {
4667+ // selected app or opposite stage active app.
4668+ if (isSelected || (otherSelected && root.active && spreadView.selectedApplication && spreadView.selectedApplication.stage !== stage)) {
4669 return linearAnimation(selectedProgress, negativeProgress, selectedTopMarginProgress, 0, root.progress);
4670 }
4671- switch (spreadView.phase) {
4672- case 0:
4673+
4674+ if (spreadView.phase == 0) {
4675 return 0;
4676- case 1:
4677- return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
4678- 0, priv.phase2StartTopMarginProgress, root.progress);
4679+ } else if (spreadView.phase == 1) {
4680+ if (nextInStack) {
4681+ return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
4682+ 0, 1, root.progress);
4683+ }
4684+ var startProgress = spreadView.positionMarker2 - (zIndex * spreadView.positionMarker2 / 2);
4685+ var endProgress = spreadView.positionMarker4 - (zIndex * spreadView.tileDistance / spreadView.width);
4686+ return linearAnimation(startProgress, endProgress, 0, 1, root.progress);
4687 }
4688 return 1;
4689 }
4690
4691 states: [
4692 State {
4693- name: "sideStageDragging"; when: spreadView.sideStageDragging && root.isInSideStage
4694- PropertyChanges { target: priv; xTranslate: -spreadView.sideStageWidth + spreadView.sideStageWidth * spreadView.sideStageDragProgress }
4695+ name: "sideStage";
4696+ when: root.isInSideStage && spreadView.shiftedContentX == 0 && spreadView.phase == 0
4697+ PropertyChanges {
4698+ target: priv;
4699+ xTranslate: -spreadView.sideStageWidth + spreadView.sideStageWidth * (1-spreadView.sideStageDragProgress)
4700+ }
4701 }
4702 ]
4703 }
4704
4705=== added file 'qml/Stages/graphics/sidestage_drag.svg'
4706--- qml/Stages/graphics/sidestage_drag.svg 1970-01-01 00:00:00 +0000
4707+++ qml/Stages/graphics/sidestage_drag.svg 2016-03-16 20:46:19 +0000
4708@@ -0,0 +1,207 @@
4709+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
4710+<!-- Created with Inkscape (http://www.inkscape.org/) -->
4711+
4712+<svg
4713+ xmlns:dc="http://purl.org/dc/elements/1.1/"
4714+ xmlns:cc="http://creativecommons.org/ns#"
4715+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
4716+ xmlns:svg="http://www.w3.org/2000/svg"
4717+ xmlns="http://www.w3.org/2000/svg"
4718+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
4719+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
4720+ width="540"
4721+ height="324"
4722+ id="svg4874"
4723+ version="1.1"
4724+ inkscape:version="0.91+devel r"
4725+ viewBox="0 0 540 324"
4726+ sodipodi:docname="gesture_3f_touch.svg">
4727+ <defs
4728+ id="defs4876" />
4729+ <sodipodi:namedview
4730+ id="base"
4731+ pagecolor="#292929"
4732+ bordercolor="#666666"
4733+ borderopacity="1.0"
4734+ inkscape:pageopacity="1"
4735+ inkscape:pageshadow="2"
4736+ inkscape:zoom="1.1785993"
4737+ inkscape:cx="-19.084613"
4738+ inkscape:cy="11.707485"
4739+ inkscape:document-units="px"
4740+ inkscape:current-layer="g4780"
4741+ showgrid="false"
4742+ showborder="true"
4743+ fit-margin-top="0"
4744+ fit-margin-left="0"
4745+ fit-margin-right="0"
4746+ fit-margin-bottom="0"
4747+ inkscape:snap-bbox="true"
4748+ inkscape:bbox-paths="true"
4749+ inkscape:bbox-nodes="true"
4750+ inkscape:snap-bbox-edge-midpoints="true"
4751+ inkscape:snap-bbox-midpoints="true"
4752+ inkscape:object-paths="true"
4753+ inkscape:snap-intersection-paths="true"
4754+ inkscape:object-nodes="true"
4755+ inkscape:snap-smooth-nodes="true"
4756+ inkscape:snap-midpoints="true"
4757+ inkscape:snap-object-midpoints="true"
4758+ inkscape:snap-center="true"
4759+ showguides="false"
4760+ inkscape:guide-bbox="true"
4761+ inkscape:snap-global="true"
4762+ inkscape:snap-others="false"
4763+ inkscape:snap-page="true">
4764+ <inkscape:grid
4765+ type="xygrid"
4766+ id="grid5451"
4767+ empspacing="8"
4768+ originx="-2.7715014e-06"
4769+ originy="-2.5124622e-06" />
4770+ <sodipodi:guide
4771+ orientation="1,0"
4772+ position="7.9999972,-8.0000025"
4773+ id="guide4063"
4774+ inkscape:locked="false" />
4775+ <sodipodi:guide
4776+ orientation="1,0"
4777+ position="3.9999972,-8.0000025"
4778+ id="guide4065"
4779+ inkscape:locked="false" />
4780+ <sodipodi:guide
4781+ orientation="0,1"
4782+ position="-8.0000027,87.999998"
4783+ id="guide4067"
4784+ inkscape:locked="false" />
4785+ <sodipodi:guide
4786+ orientation="0,1"
4787+ position="-8.0000027,91.999998"
4788+ id="guide4069"
4789+ inkscape:locked="false" />
4790+ <sodipodi:guide
4791+ orientation="0,1"
4792+ position="104,3.9999975"
4793+ id="guide4071"
4794+ inkscape:locked="false" />
4795+ <sodipodi:guide
4796+ orientation="0,1"
4797+ position="-5.0000027,7.9999975"
4798+ id="guide4073"
4799+ inkscape:locked="false" />
4800+ <sodipodi:guide
4801+ orientation="1,0"
4802+ position="91.999996,-8.0000025"
4803+ id="guide4075"
4804+ inkscape:locked="false" />
4805+ <sodipodi:guide
4806+ orientation="1,0"
4807+ position="87.999997,-8.0000025"
4808+ id="guide4077"
4809+ inkscape:locked="false" />
4810+ <sodipodi:guide
4811+ orientation="0,1"
4812+ position="-8.0000027,83.999998"
4813+ id="guide4074"
4814+ inkscape:locked="false" />
4815+ <sodipodi:guide
4816+ orientation="1,0"
4817+ position="11.999997,-8.0000025"
4818+ id="guide4076"
4819+ inkscape:locked="false" />
4820+ <sodipodi:guide
4821+ orientation="0,1"
4822+ position="-5.0000027,11.999997"
4823+ id="guide4078"
4824+ inkscape:locked="false" />
4825+ <sodipodi:guide
4826+ orientation="1,0"
4827+ position="83.999997,-9.0000025"
4828+ id="guide4080"
4829+ inkscape:locked="false" />
4830+ <sodipodi:guide
4831+ position="47.999997,-8.0000025"
4832+ orientation="1,0"
4833+ id="guide4170"
4834+ inkscape:locked="false" />
4835+ <sodipodi:guide
4836+ position="-8.0000027,47.999997"
4837+ orientation="0,1"
4838+ id="guide4172"
4839+ inkscape:locked="false" />
4840+ </sodipodi:namedview>
4841+ <metadata
4842+ id="metadata4879">
4843+ <rdf:RDF>
4844+ <cc:Work
4845+ rdf:about="">
4846+ <dc:format>image/svg+xml</dc:format>
4847+ <dc:type
4848+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
4849+ <dc:title></dc:title>
4850+ </cc:Work>
4851+ </rdf:RDF>
4852+ </metadata>
4853+ <g
4854+ inkscape:label="Layer 1"
4855+ inkscape:groupmode="layer"
4856+ id="layer1"
4857+ transform="translate(67.857143,149.49498)">
4858+ <g
4859+ transform="matrix(0,-1,-1,0,373.50506,516.50504)"
4860+ id="g4845"
4861+ style="display:inline">
4862+ <g
4863+ inkscape:export-ydpi="90"
4864+ inkscape:export-xdpi="90"
4865+ inkscape:export-filename="next01.png"
4866+ transform="matrix(-0.9996045,0,0,1,575.94296,-611.00001)"
4867+ id="g4778"
4868+ inkscape:label="Layer 1">
4869+ <g
4870+ transform="matrix(-1,0,0,1,575.99999,611)"
4871+ id="g4780"
4872+ style="display:inline">
4873+ <path
4874+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:53.6204567px;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;text-align:end;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:end;display:inline;fill:none;fill-opacity:1;stroke:#f7f7f7;stroke-width:4.00079107;stroke-miterlimit:4;stroke-dasharray:none"
4875+ d="m 526.90578,343.72628 c 14.3803,3.85163 8.37809,26.10701 -5.96811,22.26448 l -10e-6,10e-6 -50.91437,-13.63709 67.92084,18.21108 c 15.93089,4.26693 9.28148,28.92202 -6.61162,24.66516 l -10e-6,1e-5 -67.93497,-18.15834 58.38757,15.63874 c 15.85637,4.24702 9.23761,28.78845 -6.58115,24.5515 v -2e-5 l 1e-5,-2e-5 -71.03537,-19.00646 -33.09607,-8.85524 -0.18781,-0.0503 v 0 l -6.91408,-1.85189 18.07353,23.47068 c 8.45364,10.48525 11.9521,17.66446 -3.54852,27.89697 l -43.91145,-37.31132 c -28.38683,-22.97811 -35.17793,-41.86133 -27.95127,-70.42985 10.25002,-40.5206 40.5632,-49.89857 76.24862,-40.34047 4.5224,1.21129 36.24839,9.63672 40.29164,10.71968 14.0412,3.76084 12.54552,13.74563 6.76936,26.96549 z"
4876+ id="path4845-6"
4877+ inkscape:connector-curvature="0"
4878+ sodipodi:nodetypes="ccccccccccccccccccccssscc" />
4879+ <path
4880+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.25;fill:#f7f7f7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.51758671;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
4881+ d="m 571.27587,383.27233 a 35.064884,35.05101 0 0 1 -16.02978,29.39649 35.064884,35.05101 0 0 1 -34.38665,28.3418 35.064884,35.05101 0 0 1 -34.85362,-31.56055 l 29.19904,7.8125 c 15.81876,4.23695 22.4371,-20.30571 6.58073,-24.55274 l -28.91769,-7.74609 a 35.064884,35.05101 0 0 1 0.01,-0.0137 l 38.45466,10.2793 c 15.8931,4.25686 22.54288,-20.39714 6.61199,-24.66407 l -48.41758,-12.98242 a 35.064884,35.05101 0 0 1 0,-0.006 l 31.41086,8.41407 c 14.3462,3.84253 20.34945,-18.414 5.96915,-22.26563 l -31.70199,-8.49023 a 35.064884,35.05101 0 0 1 29.21468,-15.70704 35.064884,35.05101 0 0 1 35.06465,35.05274 35.064884,35.05101 0 0 1 -0.0879,2.45312 35.064884,35.05101 0 0 1 11.8797,26.23828 z"
4882+ id="ellipse4239-1"
4883+ inkscape:connector-curvature="0" />
4884+ <g
4885+ style="display:inline"
4886+ id="g5379"
4887+ transform="matrix(-0.99999722,0,0,-0.99999722,645.25394,919.46958)">
4888+ <path
4889+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:#f7f7f7;stroke-width:4.00079107;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
4890+ id="path4116"
4891+ sodipodi:type="arc"
4892+ sodipodi:cx="808.72699"
4893+ sodipodi:cy="244.79436"
4894+ sodipodi:rx="250.36082"
4895+ sodipodi:ry="263.63275"
4896+ sodipodi:start="3.8048178"
4897+ sodipodi:end="5.6286868"
4898+ sodipodi:open="true"
4899+ d="M 611.43997,82.485822 A 250.36082,263.63275 0 0 1 809.81939,-18.835885 250.36082,263.63275 0 0 1 1007.3516,84.304899"
4900+ inkscape:transform-center-x="-17.044876"
4901+ inkscape:transform-center-y="-0.0015034578"
4902+ transform="matrix(0,1,1,0,0,0)" />
4903+ <path
4904+ inkscape:transform-center-y="4.9738279"
4905+ inkscape:transform-center-x="2.0443183"
4906+ inkscape:connector-curvature="0"
4907+ id="path4198"
4908+ d="m 73.559638,1014.0226 14.78221,-18.91287 c 0,0 6.80763,12.50667 9.95238,23.00047 -10.9469,-0.5114 -24.73459,-4.0876 -24.73459,-4.0876 z"
4909+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#f7f7f7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3.00059342;marker:none;enable-background:accumulate" />
4910+ </g>
4911+ </g>
4912+ </g>
4913+ </g>
4914+ </g>
4915+</svg>
4916
4917=== added file 'qml/Stages/graphics/sidestage_open.svg'
4918--- qml/Stages/graphics/sidestage_open.svg 1970-01-01 00:00:00 +0000
4919+++ qml/Stages/graphics/sidestage_open.svg 2016-03-16 20:46:19 +0000
4920@@ -0,0 +1,181 @@
4921+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
4922+<!-- Created with Inkscape (http://www.inkscape.org/) -->
4923+
4924+<svg
4925+ xmlns:dc="http://purl.org/dc/elements/1.1/"
4926+ xmlns:cc="http://creativecommons.org/ns#"
4927+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
4928+ xmlns:svg="http://www.w3.org/2000/svg"
4929+ xmlns="http://www.w3.org/2000/svg"
4930+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
4931+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
4932+ width="162"
4933+ height="230"
4934+ id="svg4874"
4935+ version="1.1"
4936+ inkscape:version="0.91+devel r"
4937+ viewBox="0 0 162 230"
4938+ sodipodi:docname="gesture_3f_tap.svg"
4939+ style="shape-rendering:crispEdges">
4940+ <defs
4941+ id="defs4876" />
4942+ <sodipodi:namedview
4943+ id="base"
4944+ pagecolor="#292929"
4945+ bordercolor="#666666"
4946+ borderopacity="1.0"
4947+ inkscape:pageopacity="1"
4948+ inkscape:pageshadow="2"
4949+ inkscape:zoom="1.8415614"
4950+ inkscape:cx="162.13161"
4951+ inkscape:cy="116.01485"
4952+ inkscape:document-units="px"
4953+ inkscape:current-layer="g4780"
4954+ showgrid="false"
4955+ showborder="true"
4956+ fit-margin-top="0"
4957+ fit-margin-left="0"
4958+ fit-margin-right="0"
4959+ fit-margin-bottom="0"
4960+ inkscape:snap-bbox="true"
4961+ inkscape:bbox-paths="true"
4962+ inkscape:bbox-nodes="true"
4963+ inkscape:snap-bbox-edge-midpoints="true"
4964+ inkscape:snap-bbox-midpoints="true"
4965+ inkscape:object-paths="true"
4966+ inkscape:snap-intersection-paths="true"
4967+ inkscape:object-nodes="true"
4968+ inkscape:snap-smooth-nodes="true"
4969+ inkscape:snap-midpoints="true"
4970+ inkscape:snap-object-midpoints="true"
4971+ inkscape:snap-center="true"
4972+ showguides="false"
4973+ inkscape:guide-bbox="true"
4974+ inkscape:snap-global="false"
4975+ inkscape:snap-others="false"
4976+ inkscape:snap-page="true">
4977+ <inkscape:grid
4978+ type="xygrid"
4979+ id="grid5451"
4980+ empspacing="8"
4981+ originx="-2.7715014e-06"
4982+ originy="-2.5124622e-06" />
4983+ <sodipodi:guide
4984+ orientation="1,0"
4985+ position="7.9999972,-8.0000026"
4986+ id="guide4063"
4987+ inkscape:locked="false" />
4988+ <sodipodi:guide
4989+ orientation="1,0"
4990+ position="3.9999972,-8.0000026"
4991+ id="guide4065"
4992+ inkscape:locked="false" />
4993+ <sodipodi:guide
4994+ orientation="0,1"
4995+ position="-8.0000027,87.999998"
4996+ id="guide4067"
4997+ inkscape:locked="false" />
4998+ <sodipodi:guide
4999+ orientation="0,1"
5000+ position="-8.0000027,91.999998"
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches