on xenial help sign in
Merge lp:~unity-team/unity8/shell_chrome
commit79fde1bfe71e3b913a055cfaafc59e5c0879d9ee authorMichał Sawicz <michal.sawicz@canonical.com> dateFri 11 Mar 2016, 23:02 (11 years ago) parent296d98ab7a86e7
173 files changed+7474−1584
CMakeLists.txt+1 CODING+2 debian/changelog+6 debian/control+3−2 libs/UbuntuGestures/Timer.h+5 libs/UbuntuGestures/TouchRegistry.cpp+15−10 plugins/AccountsService/AccountsService.cpp+27−3 plugins/AccountsService/AccountsService.h+9−3 plugins/Cursor/Cursor.qmltypes+78 plugins/GlobalShortcut/GlobalShortcut.qmltypes+31 plugins/Ubuntu/Gestures/CMakeLists.txt+1 plugins/Ubuntu/Gestures/DirectionalDragArea.cpp+3 plugins/Ubuntu/Gestures/Gestures.qmltypes+86−45 plugins/Ubuntu/Gestures/TouchGestureArea.cpp+875 plugins/Ubuntu/Gestures/TouchGestureArea.h+229 plugins/Ubuntu/Gestures/plugin.cpp+3 plugins/Unity/InputInfo/InputInfo.qmltypes+71 plugins/Unity/Platform/Platform.qmltypes+20 plugins/Utils/CMakeLists.txt+1 plugins/Utils/globalfunctions.cpp+56 plugins/Utils/globalfunctions.h+42 plugins/Utils/plugin.cpp+9 plugins/Utils/timezoneFormatter.cpp+10 plugins/Utils/timezoneFormatter.h+1 plugins/Utils/windowstatestorage.cpp+36−4 plugins/Utils/windowstatestorage.h+3 plugins/Wizard/CMakeLists.txt+7−1 plugins/Wizard/LocalePlugin.cpp+291 plugins/Wizard/LocalePlugin.h+58 plugins/Wizard/PageList.cpp+4−4 plugins/Wizard/Status.cpp+143 plugins/Wizard/Status.h+60 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 plugins/Wizard/timezonemodel.h+80 qml/Components/InputMethod.qml+4−1 qml/Components/Showable.qml+10 qml/Shell.qml+7−8 qml/Stages/AbstractStage.qml+3−1 qml/Stages/ApplicationWindow.qml+3−1 qml/Stages/DesktopFullscreenPolicy.qml+40 qml/Stages/DesktopStage.qml+61−9 qml/Stages/PhoneFullscreenPolicy.qml+53 qml/Stages/PhoneStage.qml+10 qml/Stages/SideStage.qml+118 qml/Stages/SpreadDelegate.qml+10−1 qml/Stages/SurfaceContainer.qml+1 qml/Stages/TabletSideStageTouchGesture.qml+154 qml/Stages/TabletStage.qml+397−171 qml/Stages/TransformedTabletSpreadDelegate.qml+84−56 qml/Stages/WindowResizeArea.qml+1−1 qml/Stages/graphics/sidestage_drag.svg+207 qml/Stages/graphics/sidestage_open.svg+181 qml/Wizard/CheckableSetting.qml+10−2 qml/Wizard/Page.qml+260−44 qml/Wizard/Pages.qml+75−31 qml/Wizard/Pages/10-welcome.qml+121−48 qml/Wizard/Pages/20-sim.qml−77 qml/Wizard/Pages/30-passwd-type.qml−140 qml/Wizard/Pages/30-wifi.qml+210 qml/Wizard/Pages/40-location.qml+291 qml/Wizard/Pages/40-wifi.qml−219 qml/Wizard/Pages/50-location.qml−150 qml/Wizard/Pages/50-timezone.qml+275 qml/Wizard/Pages/60-reporting.qml → qml/Wizard/Pages/60-account.qml+30−20 qml/Wizard/Pages/70-passwd-type.qml+166 qml/Wizard/Pages/75-report-check.qml+91 qml/Wizard/Pages/80-finished.qml+94−16 qml/Wizard/Pages/data/Desktop_header_bkg.png qml/Wizard/Pages/data/Desktop_splash_screen_bkg.png qml/Wizard/Pages/data/Phone_header_bkg.png qml/Wizard/Pages/data/Phone_splash_screen_bkg.png qml/Wizard/Pages/data/Tick@30.png qml/Wizard/Pages/data/meet_ubuntu_simcard@30.png qml/Wizard/Pages/data/timezonemap/map.png qml/Wizard/Pages/data/timezonemap/pin.png qml/Wizard/Pages/data/timezonemap/timezone_-1.png qml/Wizard/Pages/data/timezonemap/timezone_-10.png qml/Wizard/Pages/data/timezonemap/timezone_-11.png qml/Wizard/Pages/data/timezonemap/timezone_-2.png qml/Wizard/Pages/data/timezonemap/timezone_-3.5.png qml/Wizard/Pages/data/timezonemap/timezone_-3.png qml/Wizard/Pages/data/timezonemap/timezone_-4.5.png qml/Wizard/Pages/data/timezonemap/timezone_-4.png qml/Wizard/Pages/data/timezonemap/timezone_-5.png qml/Wizard/Pages/data/timezonemap/timezone_-6.png qml/Wizard/Pages/data/timezonemap/timezone_-7.png qml/Wizard/Pages/data/timezonemap/timezone_-8.png qml/Wizard/Pages/data/timezonemap/timezone_-9.5.png qml/Wizard/Pages/data/timezonemap/timezone_-9.png qml/Wizard/Pages/data/timezonemap/timezone_0.png qml/Wizard/Pages/data/timezonemap/timezone_1.png qml/Wizard/Pages/data/timezonemap/timezone_10.5.png qml/Wizard/Pages/data/timezonemap/timezone_10.png qml/Wizard/Pages/data/timezonemap/timezone_11.png qml/Wizard/Pages/data/timezonemap/timezone_12.75.png qml/Wizard/Pages/data/timezonemap/timezone_12.png qml/Wizard/Pages/data/timezonemap/timezone_13.png qml/Wizard/Pages/data/timezonemap/timezone_2.png qml/Wizard/Pages/data/timezonemap/timezone_3.5.png qml/Wizard/Pages/data/timezonemap/timezone_3.png qml/Wizard/Pages/data/timezonemap/timezone_4.5.png qml/Wizard/Pages/data/timezonemap/timezone_4.png qml/Wizard/Pages/data/timezonemap/timezone_5.5.png qml/Wizard/Pages/data/timezonemap/timezone_5.75.png qml/Wizard/Pages/data/timezonemap/timezone_5.png qml/Wizard/Pages/data/timezonemap/timezone_6.5.png qml/Wizard/Pages/data/timezonemap/timezone_6.png qml/Wizard/Pages/data/timezonemap/timezone_7.png qml/Wizard/Pages/data/timezonemap/timezone_8.5.png qml/Wizard/Pages/data/timezonemap/timezone_8.png qml/Wizard/Pages/data/timezonemap/timezone_9.5.png qml/Wizard/Pages/data/timezonemap/timezone_9.png qml/Wizard/Pages/here-terms.qml+72−65 qml/Wizard/Pages/passwd-confirm.qml → qml/Wizard/Pages/passcode-confirm.qml+12−29 qml/Wizard/Pages/passcode-desktop.qml+132 qml/Wizard/Pages/passwd-set.qml → qml/Wizard/Pages/passcode-set.qml+12−28 qml/Wizard/Pages/password-set.qml+149 qml/Wizard/Pages/sim.qml+108 qml/Wizard/PasswordMeter.qml+106 qml/Wizard/StackButton.qml+6−11 qml/Wizard/Wizard.qml−9 qml/Wizard/WizardTextField.qml+25 src/MouseTouchAdaptor.cpp+53−9 src/MouseTouchAdaptor.h+5−3 tests/libs/UbuntuGestures/tst_TouchRegistry.cpp+59 tests/mocks/AccountsService/AccountsService.cpp+23−3 tests/mocks/AccountsService/AccountsService.h+12−5 tests/mocks/MeeGo/QOfono/MockOfonoSimManager.qml+1 tests/mocks/Ubuntu/Connectivity/plugin.cpp+1 tests/mocks/Ubuntu/SystemSettings/CMakeLists.txt+2 tests/mocks/Ubuntu/SystemSettings/Diagnostics/CMakeLists.txt+1 tests/mocks/Ubuntu/SystemSettings/Diagnostics/MockDiagnostics.qml+24 tests/mocks/Ubuntu/SystemSettings/Diagnostics/qmldir+2 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 tests/mocks/Ubuntu/SystemSettings/TimeDate/MockTimeDate.qml+25 tests/mocks/Ubuntu/SystemSettings/TimeDate/qmldir+2 tests/mocks/Unity/Application/ApplicationInfo.cpp+21−3 tests/mocks/Unity/Application/ApplicationInfo.h+5−1 tests/mocks/Unity/Application/ApplicationManager.cpp+13−26 tests/mocks/Unity/Application/ApplicationManager.h−8 tests/mocks/Unity/Application/MirSurface.cpp+15 tests/mocks/Unity/Application/MirSurface.h+4−3 tests/mocks/Unity/Application/MirSurfaceItem.cpp+26 tests/mocks/Unity/Application/MirSurfaceItem.h+2 tests/mocks/Unity/Application/Session.cpp+26−1 tests/mocks/Unity/Application/Session.h+7 tests/mocks/Unity/Application/resources/MirSurfaceItem.qml+59 tests/mocks/Utils/CMakeLists.txt+2−1 tests/mocks/Utils/plugin.cpp+12 tests/mocks/Utils/windowstatestorage.cpp+13 tests/mocks/Utils/windowstatestorage.h+4 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 tests/plugins/Ubuntu/Gestures/GestureTest.h+13−1 tests/plugins/Ubuntu/Gestures/tst_DirectionalDragArea.cpp−56 tests/plugins/Ubuntu/Gestures/tst_TouchGestureArea.cpp+270 tests/plugins/Ubuntu/Gestures/tst_TouchGestureArea.qml+90 tests/plugins/Unity/Launcher/launchermodeltest.cpp+1 tests/qmltests/Stages/tst_TabletStage.qml+143 tests/qmltests/Wizard/tst_Wizard.qml+170−199 tests/qmltests/tst_OrientedShell.qml+55−11 tests/qmltests/tst_Shell.qml+62 tests/utils/modules/Unity/Test/UnityTestCase.qml+67−11
CMakeLists.txt+1
@@ -58,6 +58,7 @@ find_package(Qt5Concurrent 5.4 REQUIRED)
5858find_package(Qt5Sql 5.4 REQUIRED)
5959
6060pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=14)
61+pkg_check_modules(GEONAMES REQUIRED geonames)
6162pkg_check_modules(GIO REQUIRED gio-2.0>=2.32)
6263pkg_check_modules(GLIB REQUIRED glib-2.0>=2.32)
6364pkg_check_modules(QMENUMODEL REQUIRED qmenumodel)
CODING+2
@@ -54,6 +54,8 @@ $ ./run.sh -p OR -k
5454Notes
5555-----
5656- to navigate, utilize the mouse left button as you would your finger
57+- a three point drag (for side stage operations) can be performed by using
58+ the Shift+Control+Alt (all together) keyboard modifiers while using the mouse.
5759- to get the translations work, currently you have to do make install to
5860 put the *.mo files into correct structure. We'll look for a better solution
5961 later.
debian/changelog+6
@@ -1,3 +1,9 @@
1+unity8 (8.13+15.04.20150914-0ubuntu1) UNRELEASED; urgency=medium
2+
3+ * bump version to fix OOBE build
4+
5+ -- Lukas Tinkl <ltinkl@canonical.com> Mon, 14 Sep 2015 19:19:23 +0200
6+
17unity8 (8.11+16.04.20160310.4-0ubuntu1) xenial; urgency=medium
28
39 [ Albert Astals Cid ]
debian/control+3−2
@@ -8,13 +8,14 @@ Build-Depends: cmake,
88 doxyqml,
99# To allow cross-compiling to work, we also must append :native
1010# to g++ so we don't try to run armhf g++
11−# on an x86 CPU for eaxmple, when cross-compiling.
11+# on an x86 CPU for example, when cross-compiling.
1212 g++:native,
1313 libandroid-properties-dev,
1414 graphviz,
1515 gsettings-ubuntu-schemas (>= 0.0.2+14.10.20140815),
1616 libconnectivity-qt1-dev (>= 0.7.1),
1717 libevdev-dev,
18+ libgeonames-dev (>= 0.2),
1819 libgl1-mesa-dev[!armhf] | libgl-dev[!armhf],
1920 libgl1-mesa-dri,
2021 libgles2-mesa-dev[armhf],
@@ -101,7 +102,7 @@ Depends: dmz-cursor-theme,
101102 qml-module-qt-labs-folderlistmodel,
102103 qml-module-qtquick-xmllistmodel,
103104 qml-module-qtsysteminfo,
104− qtdeclarative5-qtmir-plugin (>= 0.4.5),
105+ qtdeclarative5-qtmir-plugin (>= 0.4.8),
105106 qtdeclarative5-ubuntu-telephony0.1,
106107 qtdeclarative5-ubuntu-web-plugin,
107108 ubuntu-system-settings,
libs/UbuntuGestures/Timer.h+5
@@ -35,6 +35,11 @@ public:
3535 virtual int interval() const = 0;
3636 virtual void setInterval(int msecs) = 0;
3737 virtual void start() { m_isRunning = true; }
38+ virtual void start(int msecs)
39+ {
40+ setInterval(msecs);
41+ start();
42+ }
3843 virtual void stop() { m_isRunning = false; }
3944 bool isRunning() const { return m_isRunning; }
4045 virtual bool isSingleShot() const = 0;
libs/UbuntuGestures/TouchRegistry.cpp+15−10
@@ -359,10 +359,6 @@ void TouchRegistry::removeCandidateOwnerForTouchByIndex(Pool<TouchRegistry::Touc
359359
360360void TouchRegistry::requestTouchOwnership(int id, QQuickItem *candidate)
361361{
362− #if TOUCHREGISTRY_DEBUG
363− UG_DEBUG << "requestTouchOwnership id " << id << "candidate" << candidate;
364− #endif
365−
366362 Pool<TouchInfo>::Iterator touchInfo = findTouchInfo(id);
367363 if (!touchInfo) { qFatal("TouchRegistry: Failed to find TouchInfo"); }
368364
@@ -379,6 +375,9 @@ void TouchRegistry::requestTouchOwnership(int id, QQuickItem *candidate)
379375 break;
380376 }
381377 }
378+ #if TOUCHREGISTRY_DEBUG
379+ UG_DEBUG << "requestTouchOwnership id " << id << "candidate" << candidate << "index: " << candidateIndex;
380+ #endif
382381
383382 // add it as a candidate if not present yet
384383 if (candidateIndex < 0) {
@@ -533,16 +532,22 @@ void TouchRegistry::TouchInfo::notifyCandidatesOfOwnershipResolution()
533532 << " gained) to candidate" << candidates[0].item;
534533 #endif
535534
536− TouchOwnershipEvent gainedOwnershipEvent(id, true /*gained*/);
537− QCoreApplication::sendEvent(candidates[0].item, &gainedOwnershipEvent);
535+ // need to take a copy of the item list in case
536+ // we call back in to remove candidate during the lost ownership event.
537+ QList<QPointer<QQuickItem>> items;
538+ Q_FOREACH(const CandidateInfo& info, candidates) {
539+ items << info.item;
540+ }
538541
542+ TouchOwnershipEvent gainedOwnershipEvent(id, true /*gained*/);
543+ QCoreApplication::sendEvent(items[0], &gainedOwnershipEvent);
539544
540545 TouchOwnershipEvent lostOwnershipEvent(id, false /*gained*/);
541− for (int i = 1; i < candidates.count(); ++i) {
546+ for (int i = 1; i < items.count(); ++i) {
542547 #if TOUCHREGISTRY_DEBUG
543− UG_DEBUG << "sending TouchWonershipEvent(id =" << id << " lost) to candidate"
544− << candidates[i].item;
548+ UG_DEBUG << "sending TouchOwnershipEvent(id =" << id << " lost) to candidate"
549+ << items[i];
545550 #endif
546− QCoreApplication::sendEvent(candidates[i].item, &lostOwnershipEvent);
551+ QCoreApplication::sendEvent(items[i], &lostOwnershipEvent);
547552 }
548553}
plugins/AccountsService/AccountsService.cpp+27−3
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013, 2015 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -12,8 +12,6 @@
1212 *
1313 * You should have received a copy of the GNU General Public License
1414 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− *
16− * Author: Michael Terry <michael.terry@canonical.com>
1715 */
1816
1917#include "AccountsService.h"
@@ -34,6 +32,7 @@
3432
3533#define PROP_BACKGROUND_FILE QStringLiteral("BackgroundFile")
3634#define PROP_DEMO_EDGES QStringLiteral("demo-edges")
35+#define PROP_EMAIL QStringLiteral("Email")
3736#define PROP_ENABLE_INDICATORS_WHILE_LOCKED QStringLiteral("EnableIndicatorsWhileLocked")
3837#define PROP_ENABLE_LAUNCHER_WHILE_LOCKED QStringLiteral("EnableLauncherWhileLocked")
3938#define PROP_FAILED_LOGINS QStringLiteral("FailedLogins")
@@ -45,6 +44,7 @@
4544#define PROP_MOUSE_PRIMARY_BUTTON QStringLiteral("MousePrimaryButton")
4645#define PROP_MOUSE_SCROLL_SPEED QStringLiteral("MouseScrollSpeed")
4746#define PROP_PASSWORD_DISPLAY_HINT QStringLiteral("PasswordDisplayHint")
47+#define PROP_REAL_NAME QStringLiteral("RealName")
4848#define PROP_STATS_WELCOME_SCREEN QStringLiteral("StatsWelcomeScreen")
4949#define PROP_TOUCHPAD_CURSOR_SPEED QStringLiteral("TouchpadCursorSpeed")
5050#define PROP_TOUCHPAD_DISABLE_WHILE_TYPING QStringLiteral("TouchpadDisableWhileTyping")
@@ -85,6 +85,8 @@ AccountsService::AccountsService(QObject* parent, const QString &user)
8585 connect(m_service, &AccountsServiceDBusAdaptor::maybeChanged, this, &AccountsService::onMaybeChanged);
8686
8787 registerProperty(IFACE_ACCOUNTS_USER, PROP_BACKGROUND_FILE, QStringLiteral("backgroundFileChanged"));
88+ registerProperty(IFACE_ACCOUNTS_USER, PROP_EMAIL, QStringLiteral("emailChanged"));
89+ registerProperty(IFACE_ACCOUNTS_USER, PROP_REAL_NAME, QStringLiteral("realNameChanged"));
8890 registerProperty(IFACE_ACCOUNTS_USER, PROP_INPUT_SOURCES, QStringLiteral("keymapsChanged"));
8991 registerProperty(IFACE_LOCATION_HERE, PROP_LICENSE_ACCEPTED, QStringLiteral("hereEnabledChanged"));
9092 registerProperty(IFACE_LOCATION_HERE, PROP_LICENSE_BASE_PATH, QStringLiteral("hereLicensePathChanged"));
@@ -212,6 +214,28 @@ bool AccountsService::hereLicensePathValid() const
212214 return !value.toString().isNull();
213215}
214216
217+QString AccountsService::realName() const
218+{
219+ auto value = getProperty(IFACE_ACCOUNTS_USER, PROP_REAL_NAME);
220+ return value.toString();
221+}
222+
223+void AccountsService::setRealName(const QString &realName)
224+{
225+ setProperty(IFACE_ACCOUNTS_USER, PROP_REAL_NAME, realName);
226+}
227+
228+QString AccountsService::email() const
229+{
230+ auto value = getProperty(IFACE_ACCOUNTS_USER, PROP_EMAIL);
231+ return value.toString();
232+}
233+
234+void AccountsService::setEmail(const QString &email)
235+{
236+ setProperty(IFACE_ACCOUNTS_USER, PROP_EMAIL, email);
237+}
238+
215239QStringList AccountsService::keymaps() const
216240{
217241 auto value = getProperty(IFACE_ACCOUNTS_USER, PROP_INPUT_SOURCES);
plugins/AccountsService/AccountsService.h+9−3
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013, 2015 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -12,8 +12,6 @@
1212 *
1313 * You should have received a copy of the GNU General Public License
1414 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− *
16− * Authors: Michael Terry <michael.terry@canonical.com>
1715 */
1816
1917#ifndef UNITY_ACCOUNTSSERVICE_H
@@ -69,6 +67,8 @@ class AccountsService: public QObject
6967 Q_PROPERTY(bool hereLicensePathValid // qml sees a null string as "", so we use proxy setting for that
7068 READ hereLicensePathValid
7169 NOTIFY hereLicensePathChanged)
70+ Q_PROPERTY(QString realName READ realName WRITE setRealName NOTIFY realNameChanged)
71+ Q_PROPERTY(QString email READ email WRITE setEmail NOTIFY emailChanged)
7272 Q_PROPERTY(QStringList keymaps
7373 READ keymaps
7474 NOTIFY keymapsChanged)
@@ -97,6 +97,10 @@ public:
9797 void setHereEnabled(bool enabled);
9898 QString hereLicensePath() const;
9999 bool hereLicensePathValid() const;
100+ QString realName() const;
101+ void setRealName(const QString &realName);
102+ QString email() const;
103+ void setEmail(const QString &email);
100104 QStringList keymaps() const;
101105
102106Q_SIGNALS:
@@ -110,6 +114,8 @@ Q_SIGNALS:
110114 void failedLoginsChanged();
111115 void hereEnabledChanged();
112116 void hereLicensePathChanged();
117+ void realNameChanged();
118+ void emailChanged();
113119 void keymapsChanged();
114120
115121private Q_SLOTS:
plugins/Cursor/Cursor.qmltypesnew file+78
@@ -0,0 +1,78 @@
1+import QtQuick.tooling 1.1
2+
3+// This file describes the plugin-supplied types contained in the library.
4+// It is used for QML tooling purposes only.
5+//
6+// This file was auto-generated by:
7+// 'qmlplugindump -notrelocatable Cursor 1.0 plugins'
8+
9+Module {
10+ Component {
11+ name: "MirMousePointerInterface"
12+ defaultProperty: "data"
13+ prototype: "QQuickItem"
14+ Property { name: "cursorName"; type: "string"; isReadonly: true }
15+ Property { name: "themeName"; type: "string"; isReadonly: true }
16+ Property { name: "hotspotX"; type: "double"; isReadonly: true }
17+ Property { name: "hotspotY"; type: "double"; isReadonly: true }
18+ Signal {
19+ name: "cursorNameChanged"
20+ Parameter { name: "name"; type: "string" }
21+ }
22+ Signal {
23+ name: "themeNameChanged"
24+ Parameter { name: "name"; type: "string" }
25+ }
26+ Signal {
27+ name: "hotspotXChanged"
28+ Parameter { name: "value"; type: "double" }
29+ }
30+ Signal {
31+ name: "hotspotYChanged"
32+ Parameter { name: "value"; type: "double" }
33+ }
34+ Method {
35+ name: "handleMouseEvent"
36+ Parameter { name: "timestamp"; type: "ulong" }
37+ Parameter { name: "movement"; type: "QPointF" }
38+ Parameter { name: "buttons"; type: "Qt::MouseButtons" }
39+ Parameter { name: "modifiers"; type: "Qt::KeyboardModifiers" }
40+ }
41+ Method {
42+ name: "handleWheelEvent"
43+ Parameter { name: "timestamp"; type: "ulong" }
44+ Parameter { name: "angleDelta"; type: "QPoint" }
45+ Parameter { name: "modifiers"; type: "Qt::KeyboardModifiers" }
46+ }
47+ }
48+ Component {
49+ name: "MousePointer"
50+ defaultProperty: "data"
51+ prototype: "MirMousePointerInterface"
52+ exports: ["Cursor/MousePointer 1.0"]
53+ exportMetaObjectRevisions: [0]
54+ Signal {
55+ name: "pushedLeftBoundary"
56+ Parameter { name: "amount"; type: "double" }
57+ Parameter { name: "buttons"; type: "Qt::MouseButtons" }
58+ }
59+ Signal {
60+ name: "pushedRightBoundary"
61+ Parameter { name: "amount"; type: "double" }
62+ Parameter { name: "buttons"; type: "Qt::MouseButtons" }
63+ }
64+ Method {
65+ name: "handleMouseEvent"
66+ Parameter { name: "timestamp"; type: "ulong" }
67+ Parameter { name: "movement"; type: "QPointF" }
68+ Parameter { name: "buttons"; type: "Qt::MouseButtons" }
69+ Parameter { name: "modifiers"; type: "Qt::KeyboardModifiers" }
70+ }
71+ Method {
72+ name: "handleWheelEvent"
73+ Parameter { name: "timestamp"; type: "ulong" }
74+ Parameter { name: "angleDelta"; type: "QPoint" }
75+ Parameter { name: "modifiers"; type: "Qt::KeyboardModifiers" }
76+ }
77+ }
78+}
plugins/GlobalShortcut/GlobalShortcut.qmltypesnew file+31
@@ -0,0 +1,31 @@
1+import QtQuick.tooling 1.1
2+
3+// This file describes the plugin-supplied types contained in the library.
4+// It is used for QML tooling purposes only.
5+//
6+// This file was auto-generated by:
7+// 'qmlplugindump -notrelocatable GlobalShortcut 1.0 plugins'
8+
9+Module {
10+ Component {
11+ name: "GlobalShortcut"
12+ defaultProperty: "data"
13+ prototype: "QQuickItem"
14+ exports: ["GlobalShortcut/GlobalShortcut 1.0"]
15+ exportMetaObjectRevisions: [0]
16+ Property { name: "shortcut"; type: "QVariant" }
17+ Property { name: "active"; type: "bool" }
18+ Signal {
19+ name: "shortcutChanged"
20+ Parameter { name: "shortcut"; type: "QVariant" }
21+ }
22+ Signal {
23+ name: "triggered"
24+ Parameter { name: "shortcut"; type: "string" }
25+ }
26+ Signal {
27+ name: "activeChanged"
28+ Parameter { name: "active"; type: "bool" }
29+ }
30+ }
31+}
plugins/Ubuntu/Gestures/CMakeLists.txt+1
@@ -11,6 +11,7 @@ set(UbuntuGesturesQml_SOURCES
1111 PressedOutsideNotifier.cpp
1212 TouchDispatcher.cpp
1313 TouchGate.cpp
14+ TouchGestureArea.cpp
1415)
1516
1617add_definitions(-DUBUNTUGESTURESQML_LIBRARY)
plugins/Ubuntu/Gestures/DirectionalDragArea.cpp+3
@@ -606,6 +606,9 @@ void DirectionalDragAreaPrivate::setStatus(Status newStatus)
606606 Q_EMIT q->pressedChanged(true);
607607 break;
608608 case Recognized:
609+ if (oldStatus == WaitingForTouch) { // for immediate recognition
610+ Q_EMIT q->pressedChanged(true);
611+ }
609612 Q_EMIT q->draggingChanged(true);
610613 break;
611614 default:
plugins/Ubuntu/Gestures/Gestures.qmltypes+86−45
@@ -34,7 +34,8 @@ Module {
3434 "Leftwards": 1,
3535 "Downwards": 2,
3636 "Upwards": 3,
37− "Horizontal": 4
37+ "Horizontal": 4,
38+ "Vertical": 5
3839 }
3940 }
4041 Method {
@@ -59,14 +60,6 @@ Module {
5960 prototype: "QQuickItem"
6061 exports: ["Ubuntu.Gestures/DirectionalDragArea 0.1"]
6162 exportMetaObjectRevisions: [0]
62− Enum {
63− name: "Status"
64− values: {
65− "WaitingForTouch": 0,
66− "Undecided": 1,
67− "Recognized": 2
68− }
69− }
7063 Property { name: "direction"; type: "Direction::Type" }
7164 Property { name: "distance"; type: "double"; isReadonly: true }
7265 Property { name: "sceneDistance"; type: "double"; isReadonly: true }
@@ -74,24 +67,19 @@ Module {
7467 Property { name: "touchY"; type: "double"; isReadonly: true }
7568 Property { name: "touchSceneX"; type: "double"; isReadonly: true }
7669 Property { name: "touchSceneY"; type: "double"; isReadonly: true }
77− Property { name: "status"; type: "Status"; isReadonly: true }
7870 Property { name: "dragging"; type: "bool"; isReadonly: true }
79− Property { name: "maxDeviation"; type: "double" }
80− Property { name: "wideningAngle"; type: "double" }
81− Property { name: "distanceThreshold"; type: "double" }
82− Property { name: "minSpeed"; type: "double" }
83− Property { name: "maxSilenceTime"; type: "int" }
84− Property { name: "compositionTime"; type: "int" }
71+ Property { name: "pressed"; type: "bool"; isReadonly: true }
72+ Property { name: "immediateRecognition"; type: "bool" }
8573 Signal {
8674 name: "directionChanged"
8775 Parameter { name: "direction"; type: "Direction::Type" }
8876 }
8977 Signal {
90− name: "statusChanged"
91− Parameter { name: "value"; type: "Status" }
78+ name: "draggingChanged"
79+ Parameter { name: "value"; type: "bool" }
9280 }
9381 Signal {
94− name: "draggingChanged"
82+ name: "pressedChanged"
9583 Parameter { name: "value"; type: "bool" }
9684 }
9785 Signal {
@@ -102,30 +90,6 @@ Module {
10290 name: "sceneDistanceChanged"
10391 Parameter { name: "value"; type: "double" }
10492 }
105− Signal {
106− name: "maxDeviationChanged"
107− Parameter { name: "value"; type: "double" }
108− }
109− Signal {
110− name: "wideningAngleChanged"
111− Parameter { name: "value"; type: "double" }
112− }
113− Signal {
114− name: "distanceThresholdChanged"
115− Parameter { name: "value"; type: "double" }
116− }
117− Signal {
118− name: "minSpeedChanged"
119− Parameter { name: "value"; type: "double" }
120− }
121− Signal {
122− name: "maxSilenceTimeChanged"
123− Parameter { name: "value"; type: "int" }
124− }
125− Signal {
126− name: "compositionTimeChanged"
127− Parameter { name: "value"; type: "int" }
128− }
12993 Signal {
13094 name: "touchXChanged"
13195 Parameter { name: "value"; type: "double" }
@@ -142,7 +106,35 @@ Module {
142106 name: "touchSceneYChanged"
143107 Parameter { name: "value"; type: "double" }
144108 }
145− Signal { name: "tapped" }
109+ Signal {
110+ name: "immediateRecognitionChanged"
111+ Parameter { name: "value"; type: "bool" }
112+ }
113+ Method { name: "removeTimeConstraints" }
114+ }
115+ Component {
116+ name: "FloatingFlickable"
117+ defaultProperty: "data"
118+ prototype: "QQuickItem"
119+ exports: ["Ubuntu.Gestures/FloatingFlickable 0.1"]
120+ exportMetaObjectRevisions: [0]
121+ Property { name: "contentWidth"; type: "double" }
122+ Property { name: "contentHeight"; type: "double" }
123+ Property { name: "contentX"; type: "double" }
124+ Property { name: "contentY"; type: "double" }
125+ Property { name: "direction"; type: "Direction::Type" }
126+ }
127+ Component {
128+ name: "GestureTouchPoint"
129+ prototype: "QObject"
130+ exports: ["Ubuntu.Gestures/GestureTouchPoint 0.1"]
131+ isCreatable: false
132+ exportMetaObjectRevisions: [0]
133+ Property { name: "pointId"; type: "int"; isReadonly: true }
134+ Property { name: "pressed"; type: "bool"; isReadonly: true }
135+ Property { name: "x"; type: "double"; isReadonly: true }
136+ Property { name: "y"; type: "double"; isReadonly: true }
137+ Property { name: "dragging"; type: "bool"; isReadonly: true }
146138 }
147139 Component {
148140 name: "PressedOutsideNotifier"
@@ -163,6 +155,55 @@ Module {
163155 name: "targetItemChanged"
164156 Parameter { name: "item"; type: "QQuickItem"; isPointer: true }
165157 }
166− Signal { name: "pressed" }
158+ }
159+ Component {
160+ name: "TouchGestureArea"
161+ defaultProperty: "data"
162+ prototype: "QQuickItem"
163+ exports: ["Ubuntu.Gestures/TouchGestureArea 0.1"]
164+ exportMetaObjectRevisions: [0]
165+ Enum {
166+ name: "Status"
167+ values: {
168+ "WaitingForTouch": 0,
169+ "Undecided": 1,
170+ "Recognized": 2,
171+ "Rejected": 3
172+ }
173+ }
174+ Property { name: "touchPoints"; type: "GestureTouchPoint"; isList: true; isReadonly: true }
175+ Property { name: "dragging"; type: "bool"; isReadonly: true }
176+ Property { name: "minimumTouchPoints"; type: "int" }
177+ Property { name: "maximumTouchPoints"; type: "int" }
178+ Signal {
179+ name: "statusChanged"
180+ Parameter { name: "status"; type: "Status" }
181+ }
182+ Signal { name: "touchPointsUpdated" }
183+ Signal {
184+ name: "draggingChanged"
185+ Parameter { name: "dragging"; type: "bool" }
186+ }
187+ Signal {
188+ name: "minimumTouchPointsChanged"
189+ Parameter { name: "value"; type: "bool" }
190+ }
191+ Signal {
192+ name: "maximumTouchPointsChanged"
193+ Parameter { name: "value"; type: "bool" }
194+ }
195+ Signal {
196+ name: "pressed"
197+ Parameter { name: "points"; type: "QList<QObject*>" }
198+ }
199+ Signal {
200+ name: "released"
201+ Parameter { name: "points"; type: "QList<QObject*>" }
202+ }
203+ Signal {
204+ name: "updated"
205+ Parameter { name: "points"; type: "QList<QObject*>" }
206+ }
207+ Signal { name: "clicked" }
167208 }
168209}
plugins/Ubuntu/Gestures/TouchGestureArea.cppnew file+875
@@ -0,0 +1,875 @@
1+/*
2+ * Copyright (C) 2016 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include "TouchGestureArea.h"
18+
19+// local
20+#include "TouchOwnershipEvent.h"
21+#include "TouchRegistry.h"
22+#include "UnownedTouchEvent.h"
23+
24+#include <QGuiApplication>
25+#include <QStyleHints>
26+#include <private/qquickwindow_p.h>
27+
28+#define TOUCHGESTUREAREA_DEBUG 0
29+
30+// TODO - understand more about why we lose touch event releases.
31+// Workaround for now is to monitor all the touch points from first touch till
32+// all have been released; no matter if we've rejected the gesture.
33+
34+namespace {
35+
36+struct InternalStatus {
37+ enum Status {
38+ WaitingForTouch,
39+ WaitingForMoreTouches,
40+ WaitingForOwnership, //Recognizing,
41+ Recognized,
42+ WaitingForRejection,
43+ Rejected
44+ };
45+};
46+
47+TouchGestureArea::Status internalStatusToGestureStatus(int internalStatus) {
48+ switch (internalStatus) {
49+ case InternalStatus::WaitingForTouch: return TouchGestureArea::WaitingForTouch;
50+ case InternalStatus::WaitingForMoreTouches: return TouchGestureArea::Undecided;
51+ case InternalStatus::WaitingForOwnership: return TouchGestureArea::Undecided;
52+ case InternalStatus::Recognized: return TouchGestureArea::Recognized;
53+ case InternalStatus::WaitingForRejection: return TouchGestureArea::Recognized;
54+ case InternalStatus::Rejected: return TouchGestureArea::Rejected;
55+ }
56+ return TouchGestureArea::WaitingForTouch;
57+}
58+
59+}
60+
61+#if TOUCHGESTUREAREA_DEBUG
62+#define tgaDebug(params) qDebug().nospace() << "[TGA(" << qPrintable(objectName()) << ")] " << params
63+#include "DebugHelpers.h"
64+
65+namespace {
66+
67+const char *statusToString(int status)
68+{
69+ if (status == InternalStatus::WaitingForTouch) {
70+ return "WaitingForTouch";
71+ } else if (status == InternalStatus::WaitingForMoreTouches) {
72+ return "WaitingForMoreTouches";
73+ } else if (status == InternalStatus::WaitingForOwnership) {
74+ return "WaitingForOwnership";
75+ } else if (status == InternalStatus::Rejected) {
76+ return "Rejected";
77+ } else if (status == InternalStatus::WaitingForRejection) {
78+ return "WaitingForRejection";
79+ } else {
80+ return "Recognized";
81+ }
82+ return "Unknown";
83+}
84+
85+QString touchState(Qt::TouchPointState state) {
86+ switch (state) {
87+ case Qt::TouchPointPressed: return "pressed";
88+ case Qt::TouchPointMoved: return "moved";
89+ case Qt::TouchPointStationary: return "stationary";
90+ case Qt::TouchPointReleased: return "released";
91+ break;
92+ }
93+ return "unknown";
94+}
95+
96+QString touchesString(const QList<QObject*> touches) {
97+ QString str;
98+ Q_FOREACH(QObject* object, touches) {
99+ GestureTouchPoint* touchPoint = qobject_cast<GestureTouchPoint*>(object);
100+ if (touchPoint) {
101+ str += QStringLiteral("[%1 @ (%2, %3)], ").arg(touchPoint->id())
102+ .arg(touchPoint->x())
103+ .arg(touchPoint->y());
104+ }
105+ }
106+ return str;
107+}
108+
109+QString touchEventString(QTouchEvent* event) {
110+ if (!event) return QString();
111+ QString str;
112+ Q_FOREACH(const auto& touchPoint, event->touchPoints()) {
113+ str += QStringLiteral("[%1:%2 @ (%3, %4)], ").arg(touchPoint.id())
114+ .arg(touchState(touchPoint.state()))
115+ .arg(touchPoint.pos().x())
116+ .arg(touchPoint.pos().y());
117+ }
118+ return str;
119+}
120+
121+
122+} // namespace {
123+#else // TOUCHGESTUREAREA_DEBUG
124+#define tgaDebug(params) ((void)0)
125+#endif // TOUCHGESTUREAREA_DEBUG
126+
127+TouchGestureArea::TouchGestureArea(QQuickItem* parent)
128+ : QQuickItem(parent)
129+ , m_status(WaitingForTouch)
130+ , m_recognitionTimer(nullptr)
131+ , m_dragging(false)
132+ , m_minimumTouchPoints(1)
133+ , m_maximumTouchPoints(INT_MAX)
134+ , m_recognitionPeriod(50)
135+ , m_releaseRejectPeriod(100)
136+{
137+ setRecognitionTimer(new UbuntuGestures::Timer(this));
138+ m_recognitionTimer->setInterval(m_recognitionPeriod);
139+ m_recognitionTimer->setSingleShot(true);
140+}
141+
142+TouchGestureArea::~TouchGestureArea()
143+{
144+ clearTouchLists();
145+ qDeleteAll(m_liveTouchPoints);
146+ m_liveTouchPoints.clear();
147+ qDeleteAll(m_cachedTouchPoints);
148+ m_cachedTouchPoints.clear();
149+}
150+
151+bool TouchGestureArea::event(QEvent *event)
152+{
153+ // Process unowned touch events (handles update/release for incomplete gestures)
154+ if (event->type() == TouchOwnershipEvent::touchOwnershipEventType()) {
155+ touchOwnershipEvent(static_cast<TouchOwnershipEvent *>(event));
156+ return true;
157+ } else if (event->type() == UnownedTouchEvent::unownedTouchEventType()) {
158+ unownedTouchEvent(static_cast<UnownedTouchEvent *>(event)->touchEvent());
159+ return true;
160+ }
161+
162+ return QQuickItem::event(event);
163+}
164+
165+void TouchGestureArea::touchOwnershipEvent(TouchOwnershipEvent *event)
166+{
167+ int touchId = event->touchId();
168+ tgaDebug("touchOwnershipEvent - id:" << touchId << ", gained:" << event->gained());
169+
170+ if (event->gained()) {
171+ grabTouchPoints(QVector<int>() << touchId);
172+ m_candidateTouches.remove(touchId);
173+ TouchRegistry::instance()->addTouchWatcher(touchId, this);
174+ m_watchedTouches.insert(touchId);
175+
176+ if (m_watchedTouches.count() >= m_minimumTouchPoints) {
177+ setInternalStatus(InternalStatus::Recognized);
178+ }
179+ } else {
180+ rejectGesture();
181+ }
182+}
183+
184+void TouchGestureArea::touchEvent(QTouchEvent *event)
185+{
186+ if (!isEnabled() || !isVisible()) {
187+ tgaDebug(QString("NOT ENABLED touchEvent(%1) %2").arg(statusToString(m_status)).arg(touchEventString(event)));
188+ QQuickItem::touchEvent(event);
189+ return;
190+ }
191+
192+ tgaDebug(QString("touchEvent(%1) %2").arg(statusToString(m_status)).arg(touchEventString(event)));
193+
194+ switch (m_status) {
195+ case InternalStatus::WaitingForTouch:
196+ touchEvent_waitingForTouch(event);
197+ break;
198+ case InternalStatus::WaitingForMoreTouches:
199+ touchEvent_waitingForMoreTouches(event);
200+ break;
201+ case InternalStatus::WaitingForOwnership:
202+ touchEvent_waitingForOwnership(event);
203+ break;
204+ case InternalStatus::Recognized:
205+ case InternalStatus::WaitingForRejection:
206+ touchEvent_recognized(event);
207+ break;
208+ case InternalStatus::Rejected:
209+ touchEvent_rejected(event);
210+ break;
211+ default: // Recognized:
212+ break;
213+ }
214+
215+ updateTouchPoints(event);
216+}
217+
218+void TouchGestureArea::touchEvent_waitingForTouch(QTouchEvent *event)
219+{
220+ Q_FOREACH(const QTouchEvent::TouchPoint& touchPoint, event->touchPoints()) {
221+ Qt::TouchPointState touchPointState = touchPoint.state();
222+ int touchId = touchPoint.id();
223+
224+ if (touchPointState == Qt::TouchPointPressed) {
225+ if (!m_candidateTouches.contains(touchId)) {
226+ TouchRegistry::instance()->addCandidateOwnerForTouch(touchId, this);
227+ m_candidateTouches.insert(touchId);
228+ }
229+ }
230+ }
231+ event->ignore();
232+
233+ if (m_candidateTouches.count() > m_maximumTouchPoints) {
234+ rejectGesture();
235+ } else if (m_candidateTouches.count() >= m_minimumTouchPoints) {
236+ setInternalStatus(InternalStatus::WaitingForOwnership);
237+
238+ QSet<int> tmpCandidates(m_candidateTouches);
239+ Q_FOREACH(int candidateTouchId, tmpCandidates) {
240+ TouchRegistry::instance()->requestTouchOwnership(candidateTouchId, this);
241+ }
242+ // We accept the gesture; so don't pass to lower items
243+ event->accept();
244+ } else if (m_candidateTouches.count() > 0) {
245+ setInternalStatus(InternalStatus::WaitingForMoreTouches);
246+ }
247+}
248+
249+void TouchGestureArea::touchEvent_waitingForMoreTouches(QTouchEvent *event)
250+{
251+ Q_FOREACH(const QTouchEvent::TouchPoint& touchPoint, event->touchPoints()) {
252+ Qt::TouchPointState touchPointState = touchPoint.state();
253+ int touchId = touchPoint.id();
254+
255+ if (touchPointState == Qt::TouchPointPressed) {
256+ if (!m_candidateTouches.contains(touchId)) {
257+ TouchRegistry::instance()->addCandidateOwnerForTouch(touchId, this);
258+ m_candidateTouches.insert(touchId);
259+ }
260+ }
261+ }
262+ event->ignore();
263+
264+ if (m_candidateTouches.count() > m_maximumTouchPoints) {
265+ rejectGesture();
266+ } else if (m_candidateTouches.count() >= m_minimumTouchPoints) {
267+ setInternalStatus(InternalStatus::WaitingForOwnership);
268+
269+ QSet<int> tmpCandidates(m_candidateTouches);
270+ Q_FOREACH(int candidateTouchId, tmpCandidates) {
271+ TouchRegistry::instance()->requestTouchOwnership(candidateTouchId, this);
272+ }
273+ // We accept the gesture; so don't pass to lower items
274+ event->accept();
275+ }
276+}
277+
278+void TouchGestureArea::touchEvent_waitingForOwnership(QTouchEvent *event)
279+{
280+ Q_FOREACH(const QTouchEvent::TouchPoint& touchPoint, event->touchPoints()) {
281+ Qt::TouchPointState touchPointState = touchPoint.state();
282+ int touchId = touchPoint.id();
283+
284+ if (touchPointState == Qt::TouchPointPressed) {
285+ if (!m_watchedTouches.contains(touchId)) {
286+ TouchRegistry::instance()->addTouchWatcher(touchId, this);
287+ m_watchedTouches.insert(touchId);
288+ }
289+ }
290+ }
291+}
292+
293+void TouchGestureArea::touchEvent_recognized(QTouchEvent *event)
294+{
295+ Q_FOREACH(const QTouchEvent::TouchPoint& touchPoint, event->touchPoints()) {
296+ Qt::TouchPointState touchPointState = touchPoint.state();
297+ int touchId = touchPoint.id();
298+
299+ if (touchPointState == Qt::TouchPointPressed) {
300+ if (!m_watchedTouches.contains(touchId)) {
301+ TouchRegistry::instance()->addTouchWatcher(touchId, this);
302+ m_watchedTouches.insert(touchId);
303+ }
304+ }
305+ }
306+
307+ if (m_watchedTouches.count() > m_maximumTouchPoints) {
308+ rejectGesture();
309+ } else if (m_watchedTouches.count() >= m_minimumTouchPoints &&
310+ m_status==InternalStatus::WaitingForRejection) {
311+ setInternalStatus(InternalStatus::Recognized);
312+ }
313+}
314+
315+void TouchGestureArea::touchEvent_rejected(QTouchEvent *event)
316+{
317+ Q_FOREACH(const QTouchEvent::TouchPoint& touchPoint, event->touchPoints()) {
318+ Qt::TouchPointState touchPointState = touchPoint.state();
319+ int touchId = touchPoint.id();
320+
321+ if (touchPointState == Qt::TouchPointPressed) {
322+ if (!m_watchedTouches.contains(touchId)) {
323+ TouchRegistry::instance()->addTouchWatcher(touchId, this);
324+ m_watchedTouches.insert(touchId);
325+ }
326+ }
327+ }
328+}
329+
330+void TouchGestureArea::unownedTouchEvent(QTouchEvent *unownedTouchEvent)
331+{
332+ tgaDebug(QString("unownedTouchEvent(%1) %2").arg(statusToString(m_status)).arg(touchEventString(unownedTouchEvent)));
333+
334+ // Only monitor unowned touch events for presses/releases
335+ if ((unownedTouchEvent->touchPointStates() & (Qt::TouchPointPressed|Qt::TouchPointReleased)) == 0) {
336+ return;
337+ }
338+
339+ switch (m_status) {
340+ case InternalStatus::WaitingForTouch:
341+ break;
342+ case InternalStatus::WaitingForMoreTouches:
343+ unownedTouchEvent_waitingForMoreTouches(unownedTouchEvent);
344+ // do nothing
345+ break;
346+ case InternalStatus::WaitingForOwnership:
347+ unownedTouchEvent_waitingForOwnership(unownedTouchEvent);
348+ break;
349+ case InternalStatus::Recognized:
350+ case InternalStatus::WaitingForRejection:
351+ unownedTouchEvent_recognised(unownedTouchEvent);
352+ break;
353+ case InternalStatus::Rejected:
354+ unownedTouchEvent_rejected(unownedTouchEvent);
355+ break;
356+ default:
357+ break;
358+ }
359+
360+ updateTouchPoints(unownedTouchEvent);
361+}
362+
363+void TouchGestureArea::unownedTouchEvent_waitingForMoreTouches(QTouchEvent *event)
364+{
365+ Q_FOREACH(const QTouchEvent::TouchPoint& touchPoint, event->touchPoints()) {
366+ Qt::TouchPointState touchPointState = touchPoint.state();
367+ int touchId = touchPoint.id();
368+
369+ if (touchPointState == Qt::TouchPointReleased) {
370+ if (m_candidateTouches.contains(touchId)) {
371+ TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, this);
372+ m_candidateTouches.remove(touchId);
373+ }
374+ }
375+ }
376+
377+ if (m_candidateTouches.count() == 0) {
378+ setInternalStatus(InternalStatus::WaitingForTouch);
379+ }
380+}
381+
382+void TouchGestureArea::unownedTouchEvent_waitingForOwnership(QTouchEvent *event)
383+{
384+ Q_FOREACH(const QTouchEvent::TouchPoint& touchPoint, event->touchPoints()) {
385+ Qt::TouchPointState touchPointState = touchPoint.state();
386+ int touchId = touchPoint.id();
387+
388+ if (touchPointState == Qt::TouchPointReleased) {
389+ if (m_candidateTouches.contains(touchId)) {
390+ TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, this);
391+ m_candidateTouches.remove(touchId);
392+ }
393+ if (m_watchedTouches.contains(touchId)) {
394+ m_watchedTouches.remove(touchId);
395+ }
396+ }
397+ }
398+
399+ if (m_candidateTouches.count() + m_watchedTouches.count() == 0) {
400+ setInternalStatus(InternalStatus::WaitingForTouch);
401+ }
402+}
403+
404+void TouchGestureArea::unownedTouchEvent_recognised(QTouchEvent *event)
405+{
406+ Q_FOREACH(const QTouchEvent::TouchPoint& touchPoint, event->touchPoints()) {
407+ Qt::TouchPointState touchPointState = touchPoint.state();
408+ int touchId = touchPoint.id();
409+
410+ if (touchPointState == Qt::TouchPointReleased) {
411+ if (m_watchedTouches.contains(touchId)) {
412+ m_watchedTouches.remove(touchId);
413+ }
414+ }
415+ }
416+
417+ if (m_watchedTouches.count() < m_minimumTouchPoints && m_status==InternalStatus::Recognized) {
418+ setInternalStatus(InternalStatus::WaitingForRejection);
419+ }
420+}
421+
422+void TouchGestureArea::unownedTouchEvent_rejected(QTouchEvent *event)
423+{
424+ Q_FOREACH(const QTouchEvent::TouchPoint& touchPoint, event->touchPoints()) {
425+ Qt::TouchPointState touchPointState = touchPoint.state();
426+ int touchId = touchPoint.id();
427+
428+ if (touchPointState == Qt::TouchPointPressed) {
429+ if (!m_watchedTouches.contains(touchId)) {
430+ TouchRegistry::instance()->addTouchWatcher(touchId, this);
431+ m_watchedTouches.insert(touchId);
432+ }
433+ }
434+ if (touchPointState == Qt::TouchPointReleased) {
435+ if (m_watchedTouches.contains(touchId)) {
436+ m_watchedTouches.remove(touchId);
437+ }
438+ }
439+ }
440+
441+ if (m_watchedTouches.count() == 0) {
442+ setInternalStatus(InternalStatus::WaitingForTouch);
443+ }
444+}
445+
446+void TouchGestureArea::updateTouchPoints(QTouchEvent *touchEvent)
447+{
448+ bool added = false;
449+ bool ended = false;
450+ bool moved = false;
451+
452+ const int dragThreshold = qApp->styleHints()->startDragDistance();
453+ const int dragVelocity = qApp->styleHints()->startDragVelocity();
454+
455+ clearTouchLists();
456+ bool updateable = m_status != InternalStatus::WaitingForRejection;
457+
458+ Q_FOREACH(const QTouchEvent::TouchPoint& touchPoint, touchEvent->touchPoints()) {
459+ Qt::TouchPointState touchPointState = touchPoint.state();
460+ int touchId = touchPoint.id();
461+
462+ if (touchPointState & Qt::TouchPointReleased) {
463+ GestureTouchPoint* gtp = m_liveTouchPoints.value(touchId);
464+ if (!gtp) continue;
465+
466+ gtp->setPos(touchPoint.pos());
467+ gtp->setPressed(false);
468+ m_releasedTouchPoints.append(gtp);
469+ m_liveTouchPoints.remove(touchId);
470+
471+ if (updateable) {
472+ if (m_cachedTouchPoints.contains(touchId)) {
473+ GestureTouchPoint* cachedPoint = m_cachedTouchPoints.take(touchId);
474+ cachedPoint->deleteLater();
475+ }
476+ }
477+ ended = true;
478+ } else {
479+ GestureTouchPoint* gtp = m_liveTouchPoints.value(touchPoint.id(), nullptr);
480+ if (!gtp) {
481+ gtp = addTouchPoint(&touchPoint);
482+ m_pressedTouchPoints.append(gtp);
483+
484+ if (updateable) {
485+ if (m_cachedTouchPoints.contains(touchId)) {
486+ m_cachedTouchPoints[touchId]->setPos(touchPoint.pos());
487+ } else {
488+ m_cachedTouchPoints[touchId] = new GestureTouchPoint(*gtp);
489+ }
490+ }
491+ added = true;
492+ } else if (touchPointState & Qt::TouchPointMoved) {
493+ gtp->setPos(touchPoint.pos());
494+ m_movedTouchPoints.append(gtp);
495+ moved = true;
496+
497+ const QPointF &currentPos = touchPoint.scenePos();
498+ const QPointF &startPos = touchPoint.startScenePos();
499+
500+ bool overDragThreshold = false;
501+ bool supportsVelocity = (touchEvent->device()->capabilities() & QTouchDevice::Velocity) && dragVelocity;
502+ overDragThreshold |= qAbs(currentPos.x() - startPos.x()) > dragThreshold ||
503+ qAbs(currentPos.y() - startPos.y()) > dragThreshold;
504+ if (supportsVelocity) {
505+ QVector2D velocityVec = touchPoint.velocity();
506+ overDragThreshold |= qAbs(velocityVec.x()) > dragVelocity;
507+ overDragThreshold |= qAbs(velocityVec.y()) > dragVelocity;
508+ }
509+
510+ if (overDragThreshold) {
511+ gtp->setDragging(true);
512+ }
513+
514+ if (updateable) {
515+ if (m_cachedTouchPoints.contains(touchId)) {
516+ m_cachedTouchPoints[touchId]->setPos(touchPoint.pos());
517+ if (overDragThreshold) {
518+ m_cachedTouchPoints[touchId]->setDragging(true);
519+ }
520+ }
521+ }
522+ }
523+ }
524+ }
525+
526+ if (updateable) {
527+ if (!dragging() && m_status == InternalStatus::Recognized) {
528+ bool allWantDrag = !m_liveTouchPoints.isEmpty();
529+ Q_FOREACH(auto point, m_liveTouchPoints) {
530+ allWantDrag &= point->dragging();
531+ }
532+ // only dragging if all points are dragging.
533+ if (allWantDrag) {
534+ setDragging(true);
535+ }
536+ }
537+
538+ if (ended) {
539+ if (m_liveTouchPoints.isEmpty()) {
540+ if (!dragging()) Q_EMIT clicked();
541+ setDragging(false);
542+ }
543+ tgaDebug("Released " << touchesString(m_releasedTouchPoints));
544+ Q_EMIT released(m_releasedTouchPoints);
545+ }
546+ if (added) {
547+ tgaDebug("Pressed " << touchesString(m_pressedTouchPoints));
548+ Q_EMIT pressed(m_pressedTouchPoints);
549+ }
550+ if (moved) {
551+ tgaDebug("Updated " << touchesString(m_movedTouchPoints));
552+ Q_EMIT updated(m_movedTouchPoints);
553+ }
554+ if (added || ended || moved) {
555+ Q_EMIT touchPointsUpdated();
556+ }
557+ }
558+}
559+
560+void TouchGestureArea::clearTouchLists()
561+{
562+ Q_FOREACH (QObject *gtp, m_releasedTouchPoints) {
563+ delete gtp;
564+ }
565+ m_releasedTouchPoints.clear();
566+ m_pressedTouchPoints.clear();
567+ m_movedTouchPoints.clear();
568+}
569+
570+void TouchGestureArea::setInternalStatus(uint newStatus)
571+{
572+ if (newStatus == m_status)
573+ return;
574+
575+ uint oldStatus = m_status;
576+
577+ m_status = newStatus;
578+ Q_EMIT statusChanged(status());
579+
580+ if (oldStatus == InternalStatus::WaitingForMoreTouches || oldStatus == InternalStatus::WaitingForRejection) {
581+ m_recognitionTimer->stop();
582+ }
583+
584+ tgaDebug(statusToString(oldStatus) << " -> " << statusToString(newStatus));
585+
586+ switch (newStatus) {
587+ case InternalStatus::WaitingForTouch:
588+ resyncCachedTouchPoints();
589+ break;
590+ case InternalStatus::WaitingForMoreTouches:
591+ m_recognitionTimer->start(m_recognitionPeriod);
592+ break;
593+ case InternalStatus::Recognized:
594+ resyncCachedTouchPoints();
595+ break;
596+ case InternalStatus::WaitingForRejection:
597+ m_recognitionTimer->start(m_releaseRejectPeriod);
598+ break;
599+ case InternalStatus::Rejected:
600+ resyncCachedTouchPoints();
601+ break;
602+ default:
603+ // no-op
604+ break;
605+ }
606+}
607+
608+void TouchGestureArea::setRecognitionTimer(UbuntuGestures::AbstractTimer *timer)
609+{
610+ int interval = 0;
611+ bool timerWasRunning = false;
612+ bool wasSingleShot = false;
613+
614+ // can be null when called from the constructor
615+ if (m_recognitionTimer) {
616+ interval = m_recognitionTimer->interval();
617+ timerWasRunning = m_recognitionTimer->isRunning();
618+ if (m_recognitionTimer->parent() == this) {
619+ delete m_recognitionTimer;
620+ }
621+ }
622+
623+ m_recognitionTimer = timer;
624+ timer->setInterval(interval);
625+ timer->setSingleShot(wasSingleShot);
626+ connect(timer, SIGNAL(timeout()),
627+ this, SLOT(rejectGesture()));
628+ if (timerWasRunning) {
629+ m_recognitionTimer->start();
630+ }
631+}
632+
633+int TouchGestureArea::status() const
634+{
635+ return internalStatusToGestureStatus(m_status);
636+}
637+
638+bool TouchGestureArea::dragging() const
639+{
640+ return m_dragging;
641+}
642+
643+QQmlListProperty<GestureTouchPoint> TouchGestureArea::touchPoints()
644+{
645+ return QQmlListProperty<GestureTouchPoint>(this,
646+ 0,
647+ nullptr,
648+ TouchGestureArea::touchPoint_count,
649+ TouchGestureArea::touchPoint_at,
650+ 0);
651+}
652+
653+int TouchGestureArea::minimumTouchPoints() const
654+{
655+ return m_minimumTouchPoints;
656+}
657+
658+void TouchGestureArea::setMinimumTouchPoints(int value)
659+{
660+ if (m_minimumTouchPoints != value) {
661+ m_minimumTouchPoints = value;
662+ Q_EMIT minimumTouchPointsChanged(value);
663+ }
664+}
665+
666+int TouchGestureArea::maximumTouchPoints() const
667+{
668+ return m_maximumTouchPoints;
669+}
670+
671+void TouchGestureArea::setMaximumTouchPoints(int value)
672+{
673+ if (m_maximumTouchPoints != value) {
674+ m_maximumTouchPoints = value;
675+ Q_EMIT maximumTouchPointsChanged(value);
676+ }
677+}
678+
679+int TouchGestureArea::recognitionPeriod() const
680+{
681+ return m_recognitionPeriod;
682+}
683+
684+void TouchGestureArea::setRecognitionPeriod(int value)
685+{
686+ if (value != m_recognitionPeriod) {
687+ m_recognitionPeriod = value;
688+ Q_EMIT recognitionPeriodChanged(value);
689+ }
690+}
691+
692+int TouchGestureArea::releaseRejectPeriod() const
693+{
694+ return m_releaseRejectPeriod;
695+}
696+
697+void TouchGestureArea::setReleaseRejectPeriod(int value)
698+{
699+ if (value != m_releaseRejectPeriod) {
700+ m_releaseRejectPeriod = value;
701+ Q_EMIT releaseRejectPeriodChanged(value);
702+ }
703+}
704+
705+void TouchGestureArea::rejectGesture()
706+{
707+ tgaDebug("rejectGesture");
708+ ungrabTouchPoints();
709+
710+ Q_FOREACH(int touchId, m_candidateTouches) {
711+ TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, this);
712+ }
713+
714+ // Monitor the candidates
715+ Q_FOREACH(int touchId, m_candidateTouches) {
716+ TouchRegistry::instance()->addTouchWatcher(touchId, this);
717+ m_watchedTouches.insert(touchId);
718+ }
719+ m_candidateTouches.clear();
720+
721+ if (m_watchedTouches.count() == 0) {
722+ setInternalStatus(InternalStatus::WaitingForTouch);
723+ } else {
724+ setInternalStatus(InternalStatus::Rejected);
725+ }
726+}
727+
728+void TouchGestureArea::resyncCachedTouchPoints()
729+{
730+ clearTouchLists();
731+
732+ bool added = false;
733+ bool ended = false;
734+ bool moved = false;
735+ bool wantsDrag = false;
736+
737+ // list of deletes
738+ QMutableHashIterator<int, GestureTouchPoint*> removeIter(m_cachedTouchPoints);
739+ while(removeIter.hasNext()) {
740+ removeIter.next();
741+ if (!m_liveTouchPoints.contains(removeIter.key())) {
742+ m_releasedTouchPoints.append(removeIter.value());
743+ removeIter.remove();
744+ ended = true;
745+ }
746+ }
747+
748+ // list of adds/moves
749+ Q_FOREACH(GestureTouchPoint* touchPoint, m_liveTouchPoints) {
750+ if (m_cachedTouchPoints.contains(touchPoint->id())) {
751+ GestureTouchPoint* cachedPoint = m_cachedTouchPoints[touchPoint->id()];
752+
753+ if (*cachedPoint != *touchPoint) {
754+ *cachedPoint = *touchPoint;
755+ m_movedTouchPoints.append(touchPoint);
756+ moved = true;
757+ }
758+ } else {
759+ m_cachedTouchPoints.insert(touchPoint->id(), new GestureTouchPoint(*touchPoint));
760+ m_pressedTouchPoints.append(touchPoint);
761+ added = true;
762+ }
763+ }
764+
765+ if (wantsDrag && !dragging()) {
766+ setDragging(true);
767+ }
768+
769+ if (ended) {
770+ if (m_cachedTouchPoints.isEmpty()) {
771+ if (!dragging()) Q_EMIT clicked();
772+ setDragging(false);
773+ }
774+ tgaDebug("Cached Release " << touchesString(m_releasedTouchPoints));
775+ Q_EMIT released(m_releasedTouchPoints);
776+ }
777+ if (added) {
778+ tgaDebug("Cached Press " << touchesString(m_pressedTouchPoints));
779+ Q_EMIT pressed(m_pressedTouchPoints);
780+ }
781+ if (moved) {
782+ tgaDebug("Cached Update " << touchesString(m_movedTouchPoints));
783+ Q_EMIT updated(m_movedTouchPoints);
784+ }
785+ if (added || ended || moved) Q_EMIT touchPointsUpdated();
786+}
787+
788+int TouchGestureArea::touchPoint_count(QQmlListProperty<GestureTouchPoint> *list)
789+{
790+ TouchGestureArea *q = static_cast<TouchGestureArea*>(list->object);
791+ return q->m_cachedTouchPoints.count();
792+}
793+
794+GestureTouchPoint *TouchGestureArea::touchPoint_at(QQmlListProperty<GestureTouchPoint> *list, int index)
795+{
796+ TouchGestureArea *q = static_cast<TouchGestureArea*>(list->object);
797+ return (q->m_cachedTouchPoints.begin()+index).value();
798+}
799+
800+GestureTouchPoint* TouchGestureArea::addTouchPoint(QTouchEvent::TouchPoint const* tp)
801+{
802+ GestureTouchPoint* gtp = new GestureTouchPoint();
803+ gtp->setId(tp->id());
804+ gtp->setPressed(true);
805+ gtp->setPos(tp->pos());
806+ m_liveTouchPoints.insert(tp->id(), gtp);
807+ return gtp;
808+}
809+
810+void TouchGestureArea::itemChange(ItemChange change, const ItemChangeData &value)
811+{
812+ if (change == QQuickItem::ItemSceneChange) {
813+ if (value.window != nullptr) {
814+ value.window->installEventFilter(TouchRegistry::instance());
815+ }
816+ }
817+}
818+
819+void TouchGestureArea::setDragging(bool dragging)
820+{
821+ if (m_dragging == dragging)
822+ return;
823+
824+ tgaDebug("setDragging " << dragging);
825+
826+ m_dragging = dragging;
827+ Q_EMIT draggingChanged(m_dragging);
828+}
829+
830+void GestureTouchPoint::setId(int id)
831+{
832+ if (m_id == id)
833+ return;
834+ m_id = id;
835+ Q_EMIT idChanged();
836+}
837+
838+void GestureTouchPoint::setPressed(bool pressed)
839+{
840+ if (m_pressed == pressed)
841+ return;
842+ m_pressed = pressed;
843+ Q_EMIT pressedChanged();
844+}
845+
846+void GestureTouchPoint::setX(qreal x)
847+{
848+ if (m_x == x)
849+ return;
850+ m_x = x;
851+ Q_EMIT xChanged();
852+}
853+
854+void GestureTouchPoint::setY(qreal y)
855+{
856+ if (m_y == y)
857+ return;
858+ m_y = y;
859+ Q_EMIT yChanged();
860+}
861+
862+void GestureTouchPoint::setDragging(bool dragging)
863+{
864+ if (m_dragging == dragging)
865+ return;
866+
867+ m_dragging = dragging;
868+ Q_EMIT draggingChanged();
869+}
870+
871+void GestureTouchPoint::setPos(const QPointF &pos)
872+{
873+ setX(pos.x());
874+ setY(pos.y());
875+}
plugins/Ubuntu/Gestures/TouchGestureArea.hnew file+229
@@ -0,0 +1,229 @@
1+/*
2+ * Copyright (C) 2016 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef TOUCHGESTUREAREA_H
18+#define TOUCHGESTUREAREA_H
19+
20+#include "UbuntuGesturesQmlGlobal.h"
21+
22+#include <QQuickItem>
23+
24+// lib UbuntuGestures
25+#include <Timer.h>
26+
27+class TouchOwnershipEvent;
28+class UnownedTouchEvent;
29+
30+class GestureTouchPoint : public QObject
31+{
32+ Q_OBJECT
33+ Q_PROPERTY(int id READ id NOTIFY idChanged)
34+ Q_PROPERTY(bool pressed READ pressed NOTIFY pressedChanged)
35+ Q_PROPERTY(qreal x READ x NOTIFY xChanged)
36+ Q_PROPERTY(qreal y READ y NOTIFY yChanged)
37+ Q_PROPERTY(bool dragging READ dragging NOTIFY draggingChanged)
38+public:
39+ GestureTouchPoint()
40+ : m_id(-1)
41+ , m_pressed(false)
42+ , m_x(0)
43+ , m_y(0)
44+ , m_dragging(false)
45+ {
46+ }
47+
48+ GestureTouchPoint(const GestureTouchPoint& other)
49+ : QObject(nullptr)
50+ {
51+ operator=(other);
52+ }
53+
54+ int id() const { return m_id; }
55+ void setId(int id);
56+
57+ bool pressed() const { return m_pressed; }
58+ void setPressed(bool pressed);
59+
60+ qreal x() const { return m_x; }
61+ void setX(qreal x);
62+
63+ qreal y() const { return m_y; }
64+ void setY(qreal y);
65+
66+ bool dragging() const { return m_dragging; }
67+ void setDragging(bool dragging);
68+
69+ GestureTouchPoint& operator=(const GestureTouchPoint& rhs) {
70+ if (&rhs == this) return *this;
71+ m_id = rhs.m_id;
72+ m_pressed = rhs.m_pressed;
73+ m_x = rhs.m_x;
74+ m_y = rhs.m_y;
75+ m_dragging = rhs.m_dragging;
76+ return *this;
77+ }
78+
79+ bool operator=(const GestureTouchPoint& rhs) const {
80+ if (&rhs == this) return true;
81+ return m_id == rhs.m_id &&
82+ m_pressed == rhs.m_pressed &&
83+ m_x == rhs.m_x &&
84+ m_y == rhs.m_y &&
85+ m_dragging == rhs.m_dragging;
86+ }
87+ bool operator!=(const GestureTouchPoint& rhs) const { return !operator=(rhs); }
88+
89+ void setPos(const QPointF &pos);
90+
91+Q_SIGNALS:
92+ void idChanged();
93+ void pressedChanged();
94+ void xChanged();
95+ void yChanged();
96+ void draggingChanged();
97+
98+private:
99+ int m_id;
100+ bool m_pressed;
101+ qreal m_x;
102+ qreal m_y;
103+ bool m_dragging;
104+};
105+
106+/*
107+ An area that detects multi-finger gestures.
108+
109+ We can use this to detect gestures contstrained by a minimim and/or maximum number of touch points.
110+ This components uses the touch registry to apply for ownership of touch points.
111+ This way we can use the component in conjuntion with the directional drag area to compete for ownwership
112+ or gestures; unlike the MultiPointTouchArea.
113+ */
114+class UBUNTUGESTURESQML_EXPORT TouchGestureArea : public QQuickItem
115+{
116+ Q_OBJECT
117+ Q_ENUMS(Status)
118+
119+ Q_PROPERTY(int status READ status NOTIFY statusChanged)
120+ Q_PROPERTY(bool dragging READ dragging NOTIFY draggingChanged)
121+ Q_PROPERTY(QQmlListProperty<GestureTouchPoint> touchPoints READ touchPoints NOTIFY touchPointsUpdated)
122+
123+ Q_PROPERTY(int minimumTouchPoints READ minimumTouchPoints WRITE setMinimumTouchPoints NOTIFY minimumTouchPointsChanged)
124+ Q_PROPERTY(int maximumTouchPoints READ maximumTouchPoints WRITE setMaximumTouchPoints NOTIFY maximumTouchPointsChanged)
125+
126+ // Time(ms) the component will wait for after receiving an initial touch to recognise a gesutre before rejecting it.
127+ Q_PROPERTY(int recognitionPeriod READ recognitionPeriod WRITE setRecognitionPeriod NOTIFY recognitionPeriodChanged)
128+ // Time(ms) the component will allow a recognised gesture to intermitently release a touch point before rejecting the gesture.
129+ // This is so we will not immediately reject a gesture if there are fleeting touch point releases while dragging.
130+ Q_PROPERTY(int releaseRejectPeriod READ releaseRejectPeriod WRITE setReleaseRejectPeriod NOTIFY releaseRejectPeriodChanged)
131+
132+public:
133+ // Describes the state of the touch gesture area.
134+ enum Status {
135+ WaitingForTouch,
136+ Undecided,
137+ Recognized,
138+ Rejected
139+ };
140+ TouchGestureArea(QQuickItem* parent = NULL);
141+ ~TouchGestureArea();
142+
143+ bool event(QEvent *e) override;
144+
145+ void setRecognitionTimer(UbuntuGestures::AbstractTimer *timer);
146+
147+ int status() const;
148+ bool dragging() const;
149+ QQmlListProperty<GestureTouchPoint> touchPoints();
150+
151+ int minimumTouchPoints() const;
152+ void setMinimumTouchPoints(int value);
153+
154+ int maximumTouchPoints() const;
155+ void setMaximumTouchPoints(int value);
156+
157+ int recognitionPeriod() const;
158+ void setRecognitionPeriod(int value);
159+
160+ int releaseRejectPeriod() const;
161+ void setReleaseRejectPeriod(int value);
162+
163+Q_SIGNALS:
164+ void statusChanged(int status);
165+
166+ void touchPointsUpdated();
167+ void draggingChanged(bool dragging);
168+ void minimumTouchPointsChanged(bool value);
169+ void maximumTouchPointsChanged(bool value);
170+ void recognitionPeriodChanged(bool value);
171+ void releaseRejectPeriodChanged(bool value);
172+
173+ void pressed(const QList<QObject*>& points);
174+ void released(const QList<QObject*>& points);
175+ void updated(const QList<QObject*>& points);
176+ void clicked();
177+
178+protected:
179+ void itemChange(ItemChange change, const ItemChangeData &value);
180+
181+private Q_SLOTS:
182+ void rejectGesture();
183+
184+private:
185+ void touchEvent(QTouchEvent *event) override;
186+ void touchEvent_waitingForTouch(QTouchEvent *event);
187+ void touchEvent_waitingForMoreTouches(QTouchEvent *event);
188+ void touchEvent_waitingForOwnership(QTouchEvent *event);
189+ void touchEvent_recognized(QTouchEvent *event);
190+ void touchEvent_rejected(QTouchEvent *event);
191+
192+ void unownedTouchEvent(QTouchEvent *unownedTouchEvent);
193+ void unownedTouchEvent_waitingForMoreTouches(QTouchEvent *unownedTouchEvent);
194+ void unownedTouchEvent_waitingForOwnership(QTouchEvent *unownedTouchEvent);
195+ void unownedTouchEvent_recognised(QTouchEvent *unownedTouchEvent);
196+ void unownedTouchEvent_rejected(QTouchEvent *unownedTouchEvent);
197+
198+ void touchOwnershipEvent(TouchOwnershipEvent *event);
199+ void updateTouchPoints(QTouchEvent *event);
200+
201+ GestureTouchPoint* addTouchPoint(const QTouchEvent::TouchPoint *tp);
202+ void clearTouchLists();
203+ void setDragging(bool dragging);
204+ void setInternalStatus(uint status);
205+ void resyncCachedTouchPoints();
206+
207+ static int touchPoint_count(QQmlListProperty<GestureTouchPoint> *list);
208+ static GestureTouchPoint* touchPoint_at(QQmlListProperty<GestureTouchPoint> *list, int index);
209+
210+ uint m_status;
211+ QSet<int> m_candidateTouches;
212+ QSet<int> m_watchedTouches;
213+ UbuntuGestures::AbstractTimer *m_recognitionTimer;
214+
215+ bool m_dragging;
216+ QHash<int, GestureTouchPoint*> m_liveTouchPoints;
217+ QHash<int, GestureTouchPoint*> m_cachedTouchPoints;
218+ QList<QObject*> m_releasedTouchPoints;
219+ QList<QObject*> m_pressedTouchPoints;
220+ QList<QObject*> m_movedTouchPoints;
221+ int m_minimumTouchPoints;
222+ int m_maximumTouchPoints;
223+ int m_recognitionPeriod;
224+ int m_releaseRejectPeriod;
225+};
226+
227+QML_DECLARE_TYPE(GestureTouchPoint)
228+
229+#endif // TOUCHGESTUREAREA_H
plugins/Ubuntu/Gestures/plugin.cpp+3
@@ -21,6 +21,7 @@
2121#include "FloatingFlickable.h"
2222#include "PressedOutsideNotifier.h"
2323#include "TouchGate.h"
24+#include "TouchGestureArea.h"
2425
2526#include <qqml.h>
2627
@@ -38,4 +39,6 @@ void UbuntuGesturesQmlPlugin::registerTypes(const char *uri)
3839 qmlRegisterType<FloatingFlickable>(uri, 0, 1, "FloatingFlickable");
3940 qmlRegisterType<PressedOutsideNotifier>(uri, 0, 1, "PressedOutsideNotifier");
4041 qmlRegisterType<TouchGate>(uri, 0, 1, "TouchGate");
42+ qmlRegisterType<TouchGestureArea>(uri, 0, 1, "TouchGestureArea");
43+ qmlRegisterUncreatableType<GestureTouchPoint>(uri, 0, 1, "GestureTouchPoint", "Cannot create GestureTouchPoints");
4144}
plugins/Unity/InputInfo/InputInfo.qmltypesnew file+71
@@ -0,0 +1,71 @@
1+import QtQuick.tooling 1.1
2+
3+// This file describes the plugin-supplied types contained in the library.
4+// It is used for QML tooling purposes only.
5+//
6+// This file was auto-generated by:
7+// 'qmlplugindump -notrelocatable Unity.InputInfo 0.1 plugins'
8+
9+Module {
10+ Component {
11+ name: "QDeclarativeInputDeviceModel"
12+ prototype: "QAbstractListModel"
13+ exports: ["Unity.InputInfo/InputDeviceModel 0.1"]
14+ exportMetaObjectRevisions: [0]
15+ Property { name: "deviceFilter"; type: "QInputDevice::InputType" }
16+ Property { name: "count"; type: "int"; isReadonly: true }
17+ Signal {
18+ name: "deviceAdded"
19+ Parameter { name: "devicePath"; type: "string" }
20+ }
21+ Signal {
22+ name: "deviceRemoved"
23+ Parameter { name: "devicePath"; type: "string" }
24+ }
25+ Signal {
26+ name: "deviceFilterChanged"
27+ Parameter { name: "filter"; type: "QInputDevice::InputType" }
28+ }
29+ Method { name: "updateDeviceList" }
30+ Method {
31+ name: "indexOf"
32+ type: "int"
33+ Parameter { name: "devicePath"; type: "string" }
34+ }
35+ Method {
36+ name: "get"
37+ type: "QInputDevice*"
38+ Parameter { name: "index"; type: "int" }
39+ }
40+ }
41+ Component {
42+ name: "QInputDevice"
43+ prototype: "QObject"
44+ exports: ["Unity.InputInfo/InputInfo 0.1"]
45+ exportMetaObjectRevisions: [0]
46+ Enum {
47+ name: "InputType"
48+ values: {
49+ "Unknown": 0,
50+ "Button": 1,
51+ "Mouse": 2,
52+ "TouchPad": 4,
53+ "TouchScreen": 8,
54+ "Keyboard": 16,
55+ "Switch": 32
56+ }
57+ }
58+ Enum {
59+ name: "InputTypeFlags"
60+ values: {
61+ "Unknown": 0,
62+ "Button": 1,
63+ "Mouse": 2,
64+ "TouchPad": 4,
65+ "TouchScreen": 8,
66+ "Keyboard": 16,
67+ "Switch": 32
68+ }
69+ }
70+ }
71+}
plugins/Unity/Platform/Platform.qmltypesnew file+20
@@ -0,0 +1,20 @@
1+import QtQuick.tooling 1.1
2+
3+// This file describes the plugin-supplied types contained in the library.
4+// It is used for QML tooling purposes only.
5+//
6+// This file was auto-generated by:
7+// 'qmlplugindump -notrelocatable Unity.Platform 1.0 plugins'
8+
9+Module {
10+ Component {
11+ name: "Platform"
12+ prototype: "QObject"
13+ exports: ["Unity.Platform/Platform 1.0"]
14+ isCreatable: false
15+ isSingleton: true
16+ exportMetaObjectRevisions: [0]
17+ Property { name: "chassis"; type: "string"; isReadonly: true }
18+ Property { name: "isPC"; type: "bool"; isReadonly: true }
19+ }
20+}
plugins/Utils/CMakeLists.txt+1
@@ -24,6 +24,7 @@ set(QMLPLUGIN_SRC
2424 timezoneFormatter.cpp
2525 inputeventgenerator.cpp
2626 deviceconfigparser.cpp
27+ globalfunctions.cpp
2728 plugin.cpp
2829 )
2930
plugins/Utils/globalfunctions.cppnew file+56
@@ -0,0 +1,56 @@
1+/*
2+ * Copyright 2015 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU Lesser General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+*/
16+
17+#include "globalfunctions.h"
18+
19+#pragma GCC diagnostic push
20+#pragma GCC diagnostic ignored "-pedantic"
21+#include <private/qquickitem_p.h>
22+#pragma GCC diagnostic pop
23+#include <QQmlEngine>
24+
25+GlobalFunctions::GlobalFunctions(QObject *parent)
26+ : QObject(parent)
27+{
28+}
29+
30+QQuickItem *GlobalFunctions::itemAt(QQuickItem* parent, int x, int y, QJSValue matcher)
31+{
32+ if (!parent) return nullptr;
33+ QList<QQuickItem *> children = QQuickItemPrivate::get(parent)->paintOrderChildItems();
34+
35+ for (int i = children.count() - 1; i >= 0; --i) {
36+ QQuickItem *child = children.at(i);
37+
38+ // Map coordinates to the child element's coordinate space
39+ QPointF point = parent->mapToItem(child, QPointF(x, y));
40+ if (child->isVisible() && point.x() >= 0
41+ && child->width() >= point.x()
42+ && point.y() >= 0
43+ && child->height() >= point.y()) {
44+ if (!matcher.isCallable()) return child;
45+
46+ QQmlEngine* engine = qmlEngine(child);
47+ if (!engine) return child;
48+
49+ QJSValue newObj = engine->newQObject(child);
50+ if (matcher.call(QJSValueList() << newObj).toBool()) {
51+ return child;
52+ }
53+ }
54+ }
55+ return nullptr;
56+}
plugins/Utils/globalfunctions.hnew file+42
@@ -0,0 +1,42 @@
1+/*
2+ * Copyright 2015 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU Lesser General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+*/
16+
17+#ifndef GLOBALFUNCTIONS_H
18+#define GLOBALFUNCTIONS_H
19+
20+#include <QObject>
21+#include <QJSValue>
22+class QQuickItem;
23+
24+/**
25+ * @brief The GlobalFunctions class
26+ *
27+ * This singleton class exposes utility functions to QML
28+ *
29+ */
30+class GlobalFunctions : public QObject
31+{
32+ Q_OBJECT
33+public:
34+ explicit GlobalFunctions(QObject *parent = 0);
35+
36+ static Q_INVOKABLE QQuickItem* itemAt(QQuickItem* parent,
37+ int x,
38+ int y,
39+ QJSValue matcher);
40+};
41+
42+#endif // GLOBALFUNCTIONS_H
plugins/Utils/plugin.cpp+9
@@ -38,6 +38,7 @@
3838#include "applicationsfiltermodel.h"
3939#include "inputeventgenerator.h"
4040#include "deviceconfigparser.h"
41+#include "globalfunctions.h"
4142
4243static QObject *createWindowStateStorage(QQmlEngine *engine, QJSEngine *scriptEngine)
4344{
@@ -53,6 +54,13 @@ static QObject *createConstants(QQmlEngine *engine, QJSEngine *scriptEngine)
5354 return new Constants();
5455}
5556
57+static QObject *createGlobalFunctions(QQmlEngine *engine, QJSEngine *scriptEngine)
58+{
59+ Q_UNUSED(engine)
60+ Q_UNUSED(scriptEngine)
61+ return new GlobalFunctions();
62+}
63+
5664void UtilsPlugin::registerTypes(const char *uri)
5765{
5866 Q_ASSERT(uri == QLatin1String("Utils"));
@@ -72,6 +80,7 @@ void UtilsPlugin::registerTypes(const char *uri)
7280 qmlRegisterType<ApplicationsFilterModel>(uri, 0, 1, "ApplicationsFilterModel");
7381 qmlRegisterType<InputEventGenerator>(uri, 0, 1, "InputEventGenerator");
7482 qmlRegisterType<DeviceConfigParser>(uri, 0, 1, "DeviceConfigParser");
83+ qmlRegisterSingletonType<GlobalFunctions>(uri, 0, 1, "Functions", createGlobalFunctions);
7584}
7685
7786void UtilsPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
plugins/Utils/timezoneFormatter.cpp+10
@@ -33,3 +33,13 @@ QString TimezoneFormatter::currentTimeInTimezone(const QVariant &tzId) const
3333 }
3434 return QString();
3535}
36+
37+QString TimezoneFormatter::currentTimeInTimezoneWithAbbrev(const QVariant &tzId) const
38+{
39+ QTimeZone tz(tzId.toByteArray());
40+ if (tz.isValid()) {
41+ const QDateTime now = QDateTime::currentDateTime().toTimeZone(tz);
42+ return QStringLiteral("%1 %2").arg(now.time().toString(QStringLiteral("h:mm"))).arg(tz.abbreviation(now));
43+ }
44+ return QString();
45+}
plugins/Utils/timezoneFormatter.h+1
@@ -27,6 +27,7 @@ public:
2727 ~TimezoneFormatter() = default;
2828
2929 Q_INVOKABLE QString currentTimeInTimezone(const QVariant &tzId) const;
30+ Q_INVOKABLE QString currentTimeInTimezoneWithAbbrev(const QVariant &tzId) const;
3031};
3132
3233#endif
plugins/Utils/windowstatestorage.cpp+36−4
@@ -23,9 +23,14 @@
2323#include <QSqlError>
2424#include <QSqlResult>
2525#include <QRect>
26+#include <unity/shell/application/ApplicationInfoInterface.h>
2627
2728QMutex WindowStateStorage::s_mutex;
2829
30+inline QString sanitiseString(QString string) {
31+ return string.remove("\"").remove("'").remove("\\");
32+}
33+
2934WindowStateStorage::WindowStateStorage(QObject *parent):
3035 QObject(parent)
3136{
@@ -50,7 +55,7 @@ WindowStateStorage::~WindowStateStorage()
5055void WindowStateStorage::saveState(const QString &windowId, WindowStateStorage::WindowState state)
5156{
5257 const QString queryString = QStringLiteral("INSERT OR REPLACE INTO state (windowId, state) values ('%1', '%2');")
53− .arg(windowId)
58+ .arg(sanitiseString(windowId))
5459 .arg((int)state);
5560
5661 saveValue(queryString);
@@ -59,7 +64,7 @@ void WindowStateStorage::saveState(const QString &windowId, WindowStateStorage::
5964WindowStateStorage::WindowState WindowStateStorage::getState(const QString &windowId, WindowStateStorage::WindowState defaultValue) const
6065{
6166 const QString queryString = QStringLiteral("SELECT * FROM state WHERE windowId = '%1';")
62− .arg(windowId);
67+ .arg(sanitiseString(windowId));
6368
6469 QSqlQuery query = getValue(queryString);
6570
@@ -72,7 +77,7 @@ WindowStateStorage::WindowState WindowStateStorage::getState(const QString &wind
7277void WindowStateStorage::saveGeometry(const QString &windowId, const QRect rect)
7378{
7479 const QString queryString = QStringLiteral("INSERT OR REPLACE INTO geometry (windowId, x, y, width, height) values ('%1', '%2', '%3', '%4', '%5');")
75− .arg(windowId)
80+ .arg(sanitiseString(windowId))
7681 .arg(rect.x())
7782 .arg(rect.y())
7883 .arg(rect.width())
@@ -81,6 +86,28 @@ void WindowStateStorage::saveGeometry(const QString &windowId, const QRect rect)
8186 saveValue(queryString);
8287}
8388
89+void WindowStateStorage::saveStage(const QString &appId, int stage)
90+{
91+ const QString queryString = QStringLiteral("INSERT OR REPLACE INTO stage (appId, stage) values ('%1', '%2');")
92+ .arg(sanitiseString(appId))
93+ .arg((int)stage);
94+
95+ saveValue(queryString);
96+}
97+
98+int WindowStateStorage::getStage(const QString &appId) const
99+{
100+ const QString queryString = QStringLiteral("SELECT * FROM stage WHERE appId = '%1';")
101+ .arg(sanitiseString(appId));
102+
103+ QSqlQuery query = getValue(queryString);
104+
105+ if (!query.first()) {
106+ return unity::shell::application::ApplicationInfoInterface::MainStage;
107+ }
108+ return query.value("stage").toInt();
109+}
110+
84111void WindowStateStorage::executeAsyncQuery(const QString &queryString)
85112{
86113 QMutexLocker l(&s_mutex);
@@ -97,7 +124,7 @@ void WindowStateStorage::executeAsyncQuery(const QString &queryString)
97124QRect WindowStateStorage::getGeometry(const QString &windowId, const QRect defaultValue) const
98125{
99126 QString queryString = QStringLiteral("SELECT * FROM geometry WHERE windowId = '%1';")
100− .arg(windowId);
127+ .arg(sanitiseString(windowId));
101128
102129 QSqlQuery query = getValue(queryString);
103130
@@ -124,6 +151,11 @@ void WindowStateStorage::initdb()
124151 QSqlQuery query;
125152 query.exec(QStringLiteral("CREATE TABLE state(windowId TEXT UNIQUE, state INTEGER);"));
126153 }
154+
155+ if (!m_db.tables().contains(QStringLiteral("stage"))) {
156+ QSqlQuery query;
157+ query.exec(QStringLiteral("CREATE TABLE stage(appId TEXT UNIQUE, stage INTEGER);"));
158+ }
127159}
128160
129161void WindowStateStorage::saveValue(const QString &queryString)
plugins/Utils/windowstatestorage.h+3
@@ -38,6 +38,9 @@ public:
3838 Q_INVOKABLE void saveGeometry(const QString &windowId, const QRect rect);
3939 Q_INVOKABLE QRect getGeometry(const QString &windowId, const QRect defaultValue) const;
4040
41+ Q_INVOKABLE void saveStage(const QString &appId, int stage);
42+ Q_INVOKABLE int getStage(const QString &appId) const;
43+
4144private:
4245 void initdb();
4346
plugins/Wizard/CMakeLists.txt+7−1
@@ -1,10 +1,16 @@
1+include_directories(${GLIB_INCLUDE_DIRS} ${GEONAMES_INCLUDE_DIRS})
2+
13add_library(Wizard-qml MODULE
24 plugin.cpp
35 PageList.cpp
46 System.cpp
7+ LocalePlugin.cpp
8+ timezonemodel.cpp
9+ Status.cpp
510)
611
7−qt5_use_modules(Wizard-qml DBus Qml)
12+qt5_use_modules(Wizard-qml DBus Qml Concurrent)
13+target_link_libraries(Wizard-qml ${GLIB_LDFLAGS} ${GEONAMES_LDFLAGS})
814add_unity8_plugin(Wizard 0.1 Wizard TARGETS Wizard-qml)
915
1016set(POLKIT_LIB_DIR "${CMAKE_INSTALL_LOCALSTATEDIR}/lib/polkit-1")
plugins/Wizard/LocalePlugin.cppnew file+291
@@ -0,0 +1,291 @@
1+/*
2+ * Copyright (C) 2015 Canonical Ltd.
3+ *
4+ * This program is free software: you can redistribute it and/or modify it
5+ * under the terms of the GNU General Public License version 3, as published
6+ * by the Free Software Foundation.
7+ *
8+ * This program is distributed in the hope that it will be useful, but
9+ * WITHOUT ANY WARRANTY; without even the implied warranties of
10+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11+ * PURPOSE. See the GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License along
14+ * with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include <QLocale>
18+#include <QStringList>
19+
20+#include <libintl.h>
21+
22+#include "LocalePlugin.h"
23+
24+class LocalePrivate {
25+public:
26+ LocalePrivate() {
27+ m_mccCodes = QHash<int, QString>
28+ ({{202,"gr"},
29+ {204,"nl"},
30+ {206,"be"},
31+ {208,"fr"},
32+ {212,"mc"},
33+ {213,"ad"},
34+ {214,"es"},
35+ {216,"hu"},
36+ {218,"ba"},
37+ {219,"hr"},
38+ {220,"rs"},
39+ {222,"it"},
40+ {226,"ro"},
41+ {228,"ch"},
42+ {230,"cz"},
43+ {231,"sk"},
44+ {232,"at"},
45+ {234,"gb"},
46+ {235,"gb"},
47+ {238,"dk"},
48+ {240,"se"},
49+ {242,"no"},
50+ {244,"fi"},
51+ {246,"lt"},
52+ {247,"lv"},
53+ {248,"ee"},
54+ {250,"ru"},
55+ {255,"ua"},
56+ {257,"by"},
57+ {259,"md"},
58+ {260,"pl"},
59+ {262,"de"},
60+ {266,"gi"},
61+ {268,"pt"},
62+ {270,"lu"},
63+ {272,"ie"},
64+ {274,"is"},
65+ {276,"al"},
66+ {278,"mt"},
67+ {280,"cy"},
68+ {282,"ge"},
69+ {283,"am"},
70+ {284,"bg"},
71+ {286,"tr"},
72+ {288,"fo"},
73+ {289,"ge"},
74+ {290,"gl"},
75+ {292,"sm"},
76+ {293,"si"},
77+ {294,"mk"},
78+ {295,"li"},
79+ {297,"me"},
80+ {302,"ca"},
81+ {308,"pm"},
82+ {310,"gu"},
83+ {310,"us"},
84+ {311,"gu"},
85+ {311,"us"},
86+ {312,"us"},
87+ {316,"us"},
88+ {330,"pr"},
89+ {334,"mx"},
90+ {338,"jm"},
91+ {340,"fg"},
92+ {340,"gp"},
93+ {340,"mq"},
94+ {342,"bb"},
95+ {344,"ag"},
96+ {346,"ky"},
97+ {348,"vg"},
98+ {350,"bm"},
99+ {352,"gd"},
100+ {354,"ms"},
101+ {356,"kn"},
102+ {358,"lc"},
103+ {360,"vc"},
104+ {362,"an"},
105+ {362,"cw"},
106+ {363,"aw"},
107+ {364,"bs"},
108+ {365,"ai"},
109+ {366,"dm"},
110+ {368,"cu"},
111+ {370,"do"},
112+ {372,"ht"},
113+ {374,"tt"},
114+ {376,"tc"},
115+ {376,"vi"},
116+ {400,"az"},
117+ {401,"kz"},
118+ {402,"bt"},
119+ {404,"in"},
120+ {405,"in"},
121+ {410,"pk"},
122+ {412,"af"},
123+ {413,"lk"},
124+ {414,"mm"},
125+ {415,"lb"},
126+ {416,"jo"},
127+ {417,"sy"},
128+ {418,"iq"},
129+ {419,"kw"},
130+ {420,"sa"},
131+ {421,"ye"},
132+ {422,"om"},
133+ {424,"ae"},
134+ {425,"il"},
135+ {425,"ps"},
136+ {426,"bh"},
137+ {427,"qa"},
138+ {428,"mn"},
139+ {429,"np"},
140+ {430,"ae"},
141+ {431,"ae"},
142+ {432,"ir"},
143+ {434,"uz"},
144+ {436,"tk"},
145+ {437,"kg"},
146+ {438,"tm"},
147+ {440,"jp"},
148+ {441,"jp"},
149+ {450,"kr"},
150+ {452,"vn"},
151+ {454,"hk"},
152+ {455,"mo"},
153+ {456,"kh"},
154+ {457,"la"},
155+ {460,"cn"},
156+ {466,"tw"},
157+ {467,"kp"},
158+ {470,"bd"},
159+ {472,"mv"},
160+ {502,"my"},
161+ {505,"au"},
162+ {510,"id"},
163+ {514,"tp"},
164+ {515,"ph"},
165+ {520,"th"},
166+ {525,"sg"},
167+ {528,"bn"},
168+ {530,"nz"},
169+ {537,"pg"},
170+ {539,"to"},
171+ {540,"sb"},
172+ {541,"vu"},
173+ {542,"fj"},
174+ {544,"as"},
175+ {545,"ki"},
176+ {546,"nc"},
177+ {547,"pf"},
178+ {548,"ck"},
179+ {549,"ws"},
180+ {550,"fm"},
181+ {552,"pw"},
182+ {553,"tv"},
183+ {555,"nu"},
184+ {602,"eg"},
185+ {603,"dz"},
186+ {604,"ma"},
187+ {605,"tn"},
188+ {606,"ly"},
189+ {607,"gm"},
190+ {608,"sn"},
191+ {609,"mr"},
192+ {610,"ml"},
193+ {611,"gn"},
194+ {612,"ci"},
195+ {613,"bf"},
196+ {614,"ne"},
197+ {615,"tg"},
198+ {616,"bj"},
199+ {617,"mu"},
200+ {618,"lr"},
201+ {619,"sl"},
202+ {620,"gh"},
203+ {621,"ng"},
204+ {622,"td"},
205+ {623,"cf"},
206+ {624,"cm"},
207+ {625,"cv"},
208+ {626,"st"},
209+ {627,"gq"},
210+ {628,"ga"},
211+ {629,"cg"},
212+ {630,"cd"},
213+ {631,"ao"},
214+ {632,"gw"},
215+ {633,"sc"},
216+ {634,"sd"},
217+ {635,"rw"},
218+ {636,"et"},
219+ {637,"so"},
220+ {638,"dj"},
221+ {639,"ke"},
222+ {640,"tz"},
223+ {641,"ug"},
224+ {642,"bi"},
225+ {643,"mz"},
226+ {645,"zm"},
227+ {646,"mg"},
228+ {647,"re"},
229+ {648,"zw"},
230+ {649,"na"},
231+ {650,"mw"},
232+ {651,"ls"},
233+ {652,"bw"},
234+ {653,"sz"},
235+ {654,"km"},
236+ {655,"za"},
237+ {657,"er"},
238+ {659,"ss"},
239+ {702,"bz"},
240+ {704,"gt"},
241+ {706,"sv"},
242+ {708,"hn"},
243+ {710,"ni"},
244+ {712,"cr"},
245+ {714,"pa"},
246+ {716,"pe"},
247+ {722,"ar"},
248+ {724,"br"},
249+ {730,"cl"},
250+ {732,"co"},
251+ {734,"ve"},
252+ {736,"bo"},
253+ {738,"gy"},
254+ {740,"ec"},
255+ {744,"py"},
256+ {746,"sr"},
257+ {748,"uy"},
258+ {750,"fk"}}
259+ );
260+ }
261+
262+ QString mccToCountryCode(int mcc) const {
263+ return m_mccCodes.value(mcc, "us").toUpper();
264+ }
265+
266+private:
267+ // MCC = Mobile Country Code, see https://en.wikipedia.org/wiki/Mobile_country_code
268+ QHash<int,QString> m_mccCodes;
269+};
270+
271+Q_GLOBAL_STATIC(LocalePrivate, d)
272+
273+LocaleAttached::LocaleAttached(QObject* parent)
274+ : QObject(parent)
275+{
276+}
277+
278+QString LocaleAttached::mccToCountryCode(int mcc) const
279+{
280+ return d->mccToCountryCode(mcc);
281+}
282+
283+LocalePlugin::LocalePlugin(QObject* parent)
284+ : QObject(parent)
285+{
286+}
287+
288+LocaleAttached* LocalePlugin::qmlAttachedProperties(QObject* parent)
289+{
290+ return new LocaleAttached(parent);
291+}
plugins/Wizard/LocalePlugin.hnew file+58
@@ -0,0 +1,58 @@
1+/*
2+ * Copyright (C) 2015 Canonical Ltd.
3+ *
4+ * This program is free software: you can redistribute it and/or modify it
5+ * under the terms of the GNU General Public License version 3, as published
6+ * by the Free Software Foundation.
7+ *
8+ * This program is distributed in the hope that it will be useful, but
9+ * WITHOUT ANY WARRANTY; without even the implied warranties of
10+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11+ * PURPOSE. See the GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License along
14+ * with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef LOCALE_PLUGIN_H
18+#define LOCALE_PLUGIN_H
19+
20+#include <QObject>
21+#include <QString>
22+#include <QJsonObject>
23+#include <QtQml>
24+
25+class LocaleAttached: public QObject
26+{
27+ Q_OBJECT
28+protected:
29+ explicit LocaleAttached(QObject *parent = 0);
30+
31+public:
32+ Q_INVOKABLE QString mccToCountryCode(int mcc) const;
33+
34+ friend class LocalePlugin;
35+};
36+
37+/**
38+ * A simplified wrapper around QLocale.
39+ *
40+ * The wrapper is implemented as an attached property, which makes it possible
41+ * to use it as if its methods were static:
42+ *
43+ * @code
44+ * var langs = LocalePlugin.languages();
45+ * @endcode
46+ */
47+class LocalePlugin: public QObject
48+{
49+ Q_OBJECT
50+public:
51+ explicit LocalePlugin(QObject *parent = 0);
52+
53+ static LocaleAttached* qmlAttachedProperties(QObject *parent);
54+};
55+
56+QML_DECLARE_TYPEINFO(LocalePlugin, QML_HAS_ATTACHED_PROPERTIES)
57+
58+#endif // LOCALE_PLUGIN_H
plugins/Wizard/PageList.cpp+4−4
@@ -40,20 +40,20 @@ PageList::PageList(QObject *parent)
4040 m_index(-1),
4141 m_pages()
4242{
43− QString qmlSuffix = QStringLiteral(".qml");
44− QString disabledSuffix = QStringLiteral(".disabled");
43+ const QString qmlSuffix = QStringLiteral(".qml");
44+ const QString disabledSuffix = QStringLiteral(".disabled");
4545 QSet<QString> disabledPages;
4646 QStringList dataDirs;
4747
4848 if (!isRunningInstalled() && getenv("WIZARD_TESTING") == nullptr) {
49− dataDirs = QStringList() << qmlDirectory();
49+ dataDirs << qmlDirectory();
5050 } else {
5151 dataDirs = shellDataDirs();
5252 }
5353
5454 Q_FOREACH(const QString &dataDir, dataDirs) {
5555 QDir dir(dataDir + "/Wizard/Pages");
56− QStringList entries = dir.entryList(QStringList(QStringLiteral("[0-9]*")), QDir::Files | QDir::Readable);
56+ const QStringList entries = dir.entryList(QStringList(QStringLiteral("[0-9]*")), QDir::Files | QDir::Readable);
5757 Q_FOREACH(const QString &entry, entries) {
5858 if (!m_pages.contains(entry) && entry.endsWith(qmlSuffix))
5959 m_pages.insert(entry, dir.absoluteFilePath(entry));
plugins/Wizard/Status.cppnew file+143
@@ -0,0 +1,143 @@
1+/*
2+ * Copyright (C) 2015 Canonical Ltd.
3+ *
4+ * This program is free software: you can redistribute it and/or modify it
5+ * under the terms of the GNU General Public License version 3, as published
6+ * by the Free Software Foundation.
7+ *
8+ * This program is distributed in the hope that it will be useful, but
9+ * WITHOUT ANY WARRANTY; without even the implied warranties of
10+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11+ * PURPOSE. See the GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License along
14+ * with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include <QDebug>
18+#include <QDBusConnection>
19+
20+#include "Status.h"
21+
22+Status::Status()
23+{
24+ initNM();
25+ initUPower();
26+}
27+
28+void Status::initNM()
29+{
30+ m_nmIface = new QDBusInterface("org.freedesktop.NetworkManager", "/org/freedesktop/NetworkManager", "org.freedesktop.NetworkManager",
31+ QDBusConnection::systemBus(), this);
32+
33+ QDBusConnection::systemBus().connect("org.freedesktop.NetworkManager", "/org/freedesktop/NetworkManager", "org.freedesktop.NetworkManager", "PropertiesChanged",
34+ this, SLOT(onNMPropertiesChanged(QVariantMap)));
35+}
36+
37+void Status::onNMPropertiesChanged(const QVariantMap &changedProps)
38+{
39+ if (changedProps.contains("State") || changedProps.contains("Connectivity")) {
40+ Q_EMIT onlineChanged();
41+ Q_EMIT networkIconChanged();
42+ }
43+
44+ if (changedProps.contains("PrimaryConnection") || changedProps.contains("SpecificObject") || changedProps.contains("Strength")) {
45+ Q_EMIT networkIconChanged();
46+ }
47+}
48+
49+bool Status::online() const
50+{
51+ if (!m_nmIface->isValid())
52+ return false;
53+
54+ return m_nmIface->property("State").toUInt() == 70;
55+}
56+
57+QString Status::networkIcon()
58+{
59+ QString iconName = QStringLiteral("nm-no-connection");
60+
61+ if (!online()) {
62+ return iconName;
63+ }
64+
65+ const QString primaryConn = m_nmIface->property("PrimaryConnection").value<QDBusObjectPath>().path();
66+ const QString primaryConnType = m_nmIface->property("PrimaryConnectionType").toString();
67+
68+ if (primaryConn.isEmpty()) {
69+ qWarning() << "Empty primary connection";
70+ return iconName;
71+ }
72+
73+ if (primaryConnType == "802-11-wireless") {
74+ QDBusInterface activeConn("org.freedesktop.NetworkManager", primaryConn, "org.freedesktop.NetworkManager.Connection.Active", QDBusConnection::systemBus());
75+
76+ if (activeConn.isValid()) {
77+ const QString apPath = activeConn.property("SpecificObject").value<QDBusObjectPath>().path();
78+
79+ if (apPath.isEmpty()) {
80+ qWarning() << "No AP path";
81+ return iconName;
82+ }
83+
84+ QDBusConnection::systemBus().connect("org.freedesktop.NetworkManager", primaryConn, "org.freedesktop.NetworkManager.Connection.Active", "PropertiesChanged",
85+ this, SLOT(onNMPropertiesChanged(QVariantMap)));
86+
87+ QDBusInterface ap("org.freedesktop.NetworkManager", apPath, "org.freedesktop.NetworkManager.AccessPoint", QDBusConnection::systemBus());
88+
89+ if (!ap.isValid()) {
90+ qWarning() << "Invalid AP";
91+ return iconName;
92+ }
93+
94+ QDBusConnection::systemBus().connect("org.freedesktop.NetworkManager", apPath, "org.freedesktop.NetworkManager.AccessPoint", "PropertiesChanged",
95+ this, SLOT(onNMPropertiesChanged(QVariantMap)));
96+
97+ const uint strength = ap.property("Strength").toUInt();
98+ const uint flags = ap.property("Flags").toUInt();
99+
100+ if (strength == 0) {
101+ iconName = "nm-signal-00";
102+ } else if (strength <= 25) {
103+ iconName = "nm-signal-25";
104+ } else if (strength <= 50) {
105+ iconName = "nm-signal-50";
106+ } else if (strength <= 75) {
107+ iconName = "nm-signal-75";
108+ } else if (strength <= 100) {
109+ iconName = "nm-signal-100";
110+ }
111+
112+ if (flags >= 1) {
113+ iconName += "-secure";
114+ }
115+ }
116+ }
117+
118+ return iconName;
119+}
120+
121+void Status::initUPower()
122+{
123+ m_upowerIface = new QDBusInterface("org.freedesktop.UPower", "/org/freedesktop/UPower/devices/DisplayDevice", "org.freedesktop.UPower.Device",
124+ QDBusConnection::systemBus(), this);
125+ QDBusConnection::systemBus().connect("org.freedesktop.UPower", "/org/freedesktop/UPower/devices/DisplayDevice", "org.freedesktop.DBus.Properties",
126+ "PropertiesChanged", this, SLOT(onUPowerPropertiesChanged(QString,QVariantMap,QStringList)));
127+}
128+
129+void Status::onUPowerPropertiesChanged(const QString &iface, const QVariantMap &changedProps, const QStringList &invalidatedProps)
130+{
131+ Q_UNUSED(iface)
132+ Q_UNUSED(invalidatedProps)
133+
134+ if (changedProps.contains("IconName")) {
135+ Q_EMIT batteryIconChanged();
136+ }
137+}
138+
139+QString Status::batteryIcon() const
140+{
141+ const QString iconName = m_upowerIface->property("IconName").toString();
142+ return iconName;
143+}
plugins/Wizard/Status.hnew file+60
@@ -0,0 +1,60 @@
1+/*
2+ * Copyright (C) 2015 Canonical Ltd.
3+ *
4+ * This program is free software: you can redistribute it and/or modify it
5+ * under the terms of the GNU General Public License version 3, as published
6+ * by the Free Software Foundation.
7+ *
8+ * This program is distributed in the hope that it will be useful, but
9+ * WITHOUT ANY WARRANTY; without even the implied warranties of
10+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11+ * PURPOSE. See the GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License along
14+ * with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef WIZARD_STATUS_H
18+#define WIZARD_STATUS_H
19+
20+#include <QObject>
21+#include <QString>
22+#include <QDBusInterface>
23+
24+class Status: public QObject
25+{
26+ Q_OBJECT
27+ Q_PROPERTY(bool online READ online NOTIFY onlineChanged)
28+ Q_PROPERTY(QString networkIcon READ networkIcon NOTIFY networkIconChanged)
29+ Q_PROPERTY(QString batteryIcon READ batteryIcon NOTIFY batteryIconChanged)
30+public:
31+ Status();
32+ ~Status() = default;
33+
34+ bool online() const;
35+ QString networkIcon();
36+
37+ QString batteryIcon() const;
38+
39+Q_SIGNALS:
40+ void networkIconChanged();
41+ void onlineChanged();
42+ void batteryIconChanged();
43+
44+private Q_SLOTS:
45+ void onNMPropertiesChanged(const QVariantMap &changedProps);
46+ void onUPowerPropertiesChanged(const QString &iface, const QVariantMap &changedProps, const QStringList &invalidatedProps);
47+
48+private:
49+ Q_DISABLE_COPY(Status)
50+
51+ // network status
52+ void initNM();
53+ QDBusInterface * m_nmIface = nullptr;
54+
55+ // battery status
56+ void initUPower();
57+ QDBusInterface * m_upowerIface = nullptr;
58+};
59+
60+#endif
plugins/Wizard/System.cpp+5−4
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2014 Canonical Ltd.
2+ * Copyright (C) 2014-2015 Canonical Ltd.
33 *
44 * This program is free software: you can redistribute it and/or modify it
55 * under the terms of the GNU General Public License version 3, as published
@@ -25,10 +25,10 @@
2525#include <QLocale>
2626#include <QMap>
2727#include <QProcess>
28+#include <QDebug>
2829
2930System::System()
30− : QObject(),
31− m_fsWatcher()
31+ : QObject()
3232{
3333 // Register the argument needed for UpdateActivationEnvironment below
3434 qDBusRegisterMetaType<QMap<QString,QString>>();
@@ -88,7 +88,7 @@ void System::setSessionVariable(const QString &variable, const QString &value)
8888 QDBusConnection::sessionBus().asyncCall(msg);
8989}
9090
91−void System::updateSessionLanguage(const QString &locale)
91+void System::updateSessionLocale(const QString &locale)
9292{
9393 const QString language = locale.split(QStringLiteral("."))[0];
9494
@@ -106,5 +106,6 @@ void System::updateSessionLanguage(const QString &locale)
106106 initctl stop smart-scopes-proxy; \
107107 initctl emit --no-wait indicator-services-start; \
108108 initctl restart --no-wait maliit-server; \
109+ initctl restart --no-wait indicator-messages; \
109110 initctl restart --no-wait unity8-dash\""));
110111}
plugins/Wizard/System.h+3−2
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2014 Canonical Ltd.
2+ * Copyright (C) 2014-2015 Canonical Ltd.
33 *
44 * This program is free software: you can redistribute it and/or modify it
55 * under the terms of the GNU General Public License version 3, as published
@@ -28,12 +28,13 @@ class System : public QObject
2828
2929public:
3030 System();
31+ ~System() = default;
3132
3233 bool wizardEnabled() const;
3334 void setWizardEnabled(bool enabled);
3435
3536public Q_SLOTS:
36− void updateSessionLanguage(const QString &locale);
37+ void updateSessionLocale(const QString &locale);
3738
3839Q_SIGNALS:
3940 void wizardEnabledChanged();
plugins/Wizard/plugin.cpp+7−8
@@ -17,19 +17,18 @@
1717#include "plugin.h"
1818#include "PageList.h"
1919#include "System.h"
20+#include "timezonemodel.h"
21+#include "LocalePlugin.h"
22+#include "Status.h"
2023
2124#include <QtQml/qqml.h>
2225
23−static QObject *system_provider(QQmlEngine *engine, QJSEngine *scriptEngine)
24−{
25− Q_UNUSED(engine)
26− Q_UNUSED(scriptEngine)
27− return new System();
28−}
29−
3026void WizardPlugin::registerTypes(const char *uri)
3127{
3228 Q_ASSERT(uri == QLatin1String("Wizard"));
3329 qmlRegisterType<PageList>(uri, 0, 1, "PageList");
34− qmlRegisterSingletonType<System>(uri, 0, 1, "System", system_provider);
30+ qmlRegisterSingletonType<System>(uri, 0, 1, "System", [](QQmlEngine*, QJSEngine*) -> QObject* { return new System; });
31+ qmlRegisterSingletonType<Status>(uri, 0, 1, "Status", [](QQmlEngine*, QJSEngine*) -> QObject* { return new Status; });
32+ qmlRegisterType<TimeZoneLocationModel>(uri, 0, 1, "TimeZoneModel");
33+ qmlRegisterType<LocalePlugin>(uri, 0, 1, "LocalePlugin");
3534}
plugins/Wizard/plugin.h+1−2
@@ -17,13 +17,12 @@
1717#ifndef WIZARD_PLUGIN_H
1818#define WIZARD_PLUGIN_H
1919
20−#include <QtQml/QQmlEngine>
2120#include <QtQml/QQmlExtensionPlugin>
2221
2322class WizardPlugin : public QQmlExtensionPlugin
2423{
2524 Q_OBJECT
26− Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
25+ Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid)
2726
2827public:
2928 void registerTypes(const char *uri) override;
plugins/Wizard/timezonemodel.cppnew file+239
@@ -0,0 +1,239 @@
1+/*
2+ * Copyright (C) 2016 Canonical Ltd.
3+ *
4+ * This program is free software: you can redistribute it and/or modify it
5+ * under the terms of the GNU General Public License version 3, as published
6+ * by the Free Software Foundation.
7+ *
8+ * This program is distributed in the hope that it will be useful, but
9+ * WITHOUT ANY WARRANTY; without even the implied warranties of
10+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11+ * PURPOSE. See the GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License along
14+ * with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include <QDebug>
18+
19+#include <glib.h>
20+#include <glib-object.h>
21+
22+#include "LocalePlugin.h"
23+#include "timezonemodel.h"
24+
25+TimeZoneLocationModel::TimeZoneLocationModel(QObject *parent):
26+ QAbstractListModel(parent),
27+ m_listUpdating(false),
28+ m_cancellable(nullptr)
29+{
30+ m_roleNames[Qt::DisplayRole] = "displayName";
31+ m_roleNames[TimeZoneRole] = "timeZone";
32+ m_roleNames[CityRole] = "city";
33+ m_roleNames[CountryRole] = "country";
34+ m_roleNames[OffsetRole] = "offset";
35+ m_roleNames[LatitudeRole] = "latitude";
36+ m_roleNames[LongitudeRole] = "longitude";
37+}
38+
39+int TimeZoneLocationModel::rowCount(const QModelIndex &parent) const
40+{
41+ if (parent.isValid()) {
42+ return 0;
43+ } else if (m_filter.isEmpty()) {
44+ return m_countryLocations.count();
45+ } else {
46+ return m_locations.count();
47+ }
48+}
49+
50+QVariant TimeZoneLocationModel::data(const QModelIndex &index, int role) const
51+{
52+ GeonamesCity *city;
53+ if (m_filter.isEmpty()) {
54+ city = m_countryLocations.value(index.row());
55+ } else {
56+ city = m_locations.value(index.row());
57+ }
58+ if (!city)
59+ return QVariant();
60+
61+ switch (role) {
62+ case Qt::DisplayRole:
63+ return QStringLiteral("%1, %2, %3").arg(geonames_city_get_name(city))
64+ .arg(geonames_city_get_state(city))
65+ .arg(geonames_city_get_country(city));
66+ case SimpleRole:
67+ return QStringLiteral("%1, %2").arg(geonames_city_get_name(city))
68+ .arg(geonames_city_get_country(city));
69+ case TimeZoneRole:
70+ return geonames_city_get_timezone(city);
71+ case CountryRole:
72+ return geonames_city_get_country(city);
73+ case CityRole:
74+ return geonames_city_get_name(city);
75+ case OffsetRole: {
76+ QTimeZone tmp(geonames_city_get_timezone(city));
77+ return static_cast<double>(tmp.standardTimeOffset(QDateTime::currentDateTime())) / 3600;
78+ }
79+ case LatitudeRole:
80+ return geonames_city_get_latitude(city);
81+ case LongitudeRole:
82+ return geonames_city_get_longitude(city);
83+ default:
84+ qWarning() << Q_FUNC_INFO << "Unknown role";
85+ return QVariant();
86+ }
87+}
88+
89+QHash<int, QByteArray> TimeZoneLocationModel::roleNames() const
90+{
91+ return m_roleNames;
92+}
93+
94+void TimeZoneLocationModel::setModel(const QList<GeonamesCity *> &locations)
95+{
96+ beginResetModel();
97+
98+ Q_FOREACH(GeonamesCity *city, m_locations) {
99+ geonames_city_free(city);
100+ }
101+
102+ m_locations = locations;
103+ endResetModel();
104+}
105+
106+void TimeZoneLocationModel::filterFinished(GObject *source_object,
107+ GAsyncResult *res,
108+ gpointer user_data)
109+{
110+ Q_UNUSED(source_object);
111+
112+ g_autofree gint *cities = nullptr;
113+ guint cities_len = 0;
114+ g_autoptr(GError) error = nullptr;
115+
116+ cities = geonames_query_cities_finish(res, &cities_len, &error);
117+ if (error) {
118+ if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
119+ TimeZoneLocationModel *model = static_cast<TimeZoneLocationModel *>(user_data);
120+ g_clear_object(&model->m_cancellable);
121+ model->setListUpdating(false);
122+ qWarning() << "Could not filter timezones:" << error->message;
123+ }
124+ return;
125+ }
126+
127+ QList<GeonamesCity *> locations;
128+
129+ for (guint i = 0; i < cities_len; ++i) {
130+ GeonamesCity *city = geonames_get_city(cities[i]);
131+ if (city) {
132+ locations.append(city);
133+ }
134+ }
135+
136+ TimeZoneLocationModel *model = static_cast<TimeZoneLocationModel *>(user_data);
137+
138+ g_clear_object(&model->m_cancellable);
139+
140+ model->setModel(locations);
141+ model->setListUpdating(false);
142+}
143+
144+bool TimeZoneLocationModel::listUpdating() const
145+{
146+ return m_listUpdating;
147+}
148+
149+void TimeZoneLocationModel::setListUpdating(bool listUpdating)
150+{
151+ if (m_listUpdating != listUpdating) {
152+ m_listUpdating = listUpdating;
153+ Q_EMIT listUpdatingChanged();
154+ }
155+}
156+
157+QString TimeZoneLocationModel::filter() const
158+{
159+ return m_filter;
160+}
161+
162+void TimeZoneLocationModel::setFilter(const QString &filter)
163+{
164+ if (filter != m_filter) {
165+ m_filter = filter;
166+ Q_EMIT filterChanged();
167+ }
168+
169+ setListUpdating(true);
170+
171+ if (m_cancellable) {
172+ g_cancellable_cancel(m_cancellable);
173+ g_clear_object(&m_cancellable);
174+ }
175+
176+ setModel(QList<GeonamesCity *>());
177+
178+ if (filter.isEmpty()) {
179+ setListUpdating(false);
180+ return;
181+ }
182+
183+ m_cancellable = g_cancellable_new();
184+ geonames_query_cities(filter.toUtf8().data(),
185+ GEONAMES_QUERY_DEFAULT,
186+ m_cancellable,
187+ filterFinished,
188+ this);
189+}
190+
191+QString TimeZoneLocationModel::country() const
192+{
193+ return m_country;
194+}
195+
196+static bool citycmp(GeonamesCity *a, GeonamesCity *b)
197+{
198+ return geonames_city_get_population(b) < geonames_city_get_population(a);
199+}
200+
201+void TimeZoneLocationModel::setCountry(const QString &country)
202+{
203+ if (m_country == country)
204+ return;
205+
206+ m_country = country;
207+
208+ Q_FOREACH(GeonamesCity *city, m_countryLocations) {
209+ geonames_city_free(city);
210+ }
211+
212+ gint num_cities = geonames_get_n_cities();
213+ for (gint i = 0; i < num_cities; i++) {
214+ GeonamesCity *city = geonames_get_city(i);
215+ if (city && m_country == geonames_city_get_country_code(city)) {
216+ m_countryLocations.append(city);
217+ }
218+ }
219+
220+ std::sort(m_countryLocations.begin(), m_countryLocations.end(), citycmp);
221+
222+ Q_EMIT countryChanged(country);
223+}
224+
225+TimeZoneLocationModel::~TimeZoneLocationModel()
226+{
227+ if (m_cancellable) {
228+ g_cancellable_cancel(m_cancellable);
229+ g_clear_object(&m_cancellable);
230+ }
231+
232+ Q_FOREACH(GeonamesCity *city, m_countryLocations) {
233+ geonames_city_free(city);
234+ }
235+
236+ Q_FOREACH(GeonamesCity *city, m_locations) {
237+ geonames_city_free(city);
238+ }
239+}
plugins/Wizard/timezonemodel.hnew file+80
@@ -0,0 +1,80 @@
1+/*
2+ * Copyright (C) 2016 Canonical Ltd.
3+ *
4+ * This program is free software: you can redistribute it and/or modify it
5+ * under the terms of the GNU General Public License version 3, as published
6+ * by the Free Software Foundation.
7+ *
8+ * This program is distributed in the hope that it will be useful, but
9+ * WITHOUT ANY WARRANTY; without even the implied warranties of
10+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11+ * PURPOSE. See the GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License along
14+ * with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef TIMEZONEMODEL_H
18+#define TIMEZONEMODEL_H
19+
20+#include <geonames.h>
21+#include <glib.h>
22+#include <QAbstractListModel>
23+
24+class TimeZoneLocationModel: public QAbstractListModel
25+{
26+ Q_OBJECT
27+ Q_PROPERTY(bool listUpdating READ listUpdating NOTIFY listUpdatingChanged)
28+ Q_PROPERTY(QString filter READ filter WRITE setFilter NOTIFY filterChanged)
29+ Q_PROPERTY(QString country READ country WRITE setCountry NOTIFY countryChanged)
30+ Q_ENUMS(Roles)
31+
32+public:
33+ explicit TimeZoneLocationModel(QObject *parent = nullptr);
34+ ~TimeZoneLocationModel();
35+
36+ enum Roles {
37+ TimeZoneRole = Qt::UserRole + 1,
38+ CityRole,
39+ CountryRole,
40+ SimpleRole,
41+ OffsetRole,
42+ LatitudeRole,
43+ LongitudeRole
44+ };
45+
46+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
47+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
48+ QHash<int, QByteArray> roleNames() const override;
49+
50+ bool listUpdating() const;
51+
52+ QString filter() const;
53+ void setFilter(const QString &filter);
54+
55+ QString country() const;
56+ void setCountry(const QString &country);
57+
58+Q_SIGNALS:
59+ void listUpdatingChanged();
60+ void filterChanged();
61+ void countryChanged(const QString &country);
62+
63+private:
64+ void setModel(const QList<GeonamesCity *> &locations);
65+ void setListUpdating(bool listUpdating);
66+ static void filterFinished(GObject *source_object,
67+ GAsyncResult *res,
68+ gpointer user_data);
69+
70+
71+ bool m_listUpdating;
72+ QString m_filter;
73+ QString m_country;
74+ GCancellable *m_cancellable;
75+ QHash<int, QByteArray> m_roleNames;
76+ QList<GeonamesCity *> m_locations;
77+ QList<GeonamesCity *> m_countryLocations;
78+};
79+
80+#endif
qml/Components/InputMethod.qml+4−1
@@ -51,7 +51,10 @@ Item {
5151 }
5252
5353 state: {
54− if (surfaceItem.surface && surfaceItem.surfaceState != Mir.MinimizedState && root.enabled) {
54+ if (surfaceItem.surface &&
55+ surfaceItem.surfaceState != Mir.HiddenState &&
56+ surfaceItem.surfaceState != Mir.MinimizedState &&
57+ root.enabled) {
5558 return "shown";
5659 } else {
5760 return "hidden";
qml/Components/Showable.qml+10
@@ -31,6 +31,7 @@ Item {
3131 property bool required
3232 property bool __shouldShow: false
3333 property bool __skipShowAnimation: false
34+ property bool __skipHideAnimation: false
3435
3536 property list<QtObject> hides
3637 property var showAnimation
@@ -120,12 +121,21 @@ Item {
120121 if (!hideAnimation.running) {
121122 hideAnimation.restart()
122123 }
124+ if (__skipHideAnimation) {
125+ hideAnimation.complete();
126+ }
123127 } else {
124128 visible = false
125129 required = false
126130 }
127131
128132 shown = false
133+ __skipHideAnimation = false;
134+ }
135+
136+ function hideNow() {
137+ __skipHideAnimation = true;
138+ hide();
129139 }
130140
131141 Connections {
qml/Shell.qml+7−8
@@ -122,9 +122,7 @@ Item {
122122 if (ApplicationManager.findApplication(appId)) {
123123 ApplicationManager.requestFocusApplication(appId);
124124 } else {
125− var execFlags = shell.usageScenario === "phone" ? ApplicationManager.ForceMainStage
126− : ApplicationManager.NoFlag;
127− ApplicationManager.startApplication(appId, execFlags);
125+ ApplicationManager.startApplication(appId);
128126 }
129127 }
130128
@@ -250,7 +248,7 @@ Item {
250248 property string usageScenario: shell.usageScenario === "phone" || greeter.hasLockedApp
251249 ? "phone"
252250 : shell.usageScenario
253− source: {
251+ property string qmlComponent: {
254252 if(shell.mode === "greeter") {
255253 return "Stages/ShimStage.qml"
256254 } else if (applicationsDisplayLoader.usageScenario === "phone") {
@@ -261,6 +259,10 @@ Item {
261259 return "Stages/DesktopStage.qml";
262260 }
263261 }
262+ onQmlComponentChanged: {
263+ if (item) item.stageUnloaded();
264+ source = qmlComponent;
265+ }
264266
265267 property bool interactive: tutorial.spreadEnabled
266268 && (!greeter || !greeter.shown)
@@ -549,9 +551,7 @@ Item {
549551 greeterShown: greeter.shown
550552 }
551553
552− readonly property bool topmostApplicationIsFullscreen:
553− ApplicationManager.focusedApplicationId &&
554− ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
554+ readonly property bool topmostApplicationIsFullscreen: mainApp && mainApp.fullscreen
555555
556556 fullscreenMode: (topmostApplicationIsFullscreen && !lightDM.greeter.active && launcher.progress == 0)
557557 || greeter.hasLockedApp
@@ -634,7 +634,6 @@ Item {
634634 id: wizard
635635 objectName: "wizard"
636636 anchors.fill: parent
637− background: wallpaperResolver.background
638637
639638 function unlockWhenDoneWithWizard() {
640639 if (!active) {
qml/Stages/AbstractStage.qml+3−1
@@ -45,13 +45,15 @@ Rectangle {
4545 // To be read from outside
4646 property var mainApp: null
4747 property var mainAppWindow: null
48− property int mainAppWindowOrientationAngle
48+ property int mainAppWindowOrientationAngle: 0
4949 property bool orientationChangesEnabled
5050 property int supportedOrientations: Qt.PortraitOrientation
5151 | Qt.LandscapeOrientation
5252 | Qt.InvertedPortraitOrientation
5353 | Qt.InvertedLandscapeOrientation
5454
55+ signal stageUnloaded
56+
5557 // Shared code for use in stage implementations
5658 GSettings {
5759 id: lifecycleExceptions
qml/Stages/ApplicationWindow.qml+3−1
@@ -24,13 +24,15 @@ FocusScope {
2424 implicitHeight: sessionContainer.implicitHeight
2525
2626 // to be read from outside
27− readonly property bool fullscreen: application ? application.fullscreen : false
2827 property alias interactive: sessionContainer.interactive
2928 property bool orientationChangesEnabled: d.supportsSurfaceResize ? d.surfaceOldEnoughToBeResized : true
3029 readonly property string title: sessionContainer.surface && sessionContainer.surface.name !== "" ?
3130 sessionContainer.surface.name : d.name
3231 readonly property string activeKeymap: sessionContainer.surfaceContainer ? sessionContainer.surfaceContainer.activeKeymap : "us"
3332
33+ // overridable from outside
34+ property bool fullscreen: application ? application.fullscreen : false
35+
3436 // to be set from outside
3537 property QtObject application
3638 property int surfaceOrientationAngle
qml/Stages/DesktopFullscreenPolicy.qmlnew file+40
@@ -0,0 +1,40 @@
1+/*
2+ * Copyright (C) 2016 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+import Unity.Application 0.1
19+
20+Item {
21+ property bool active: true
22+ property QtObject application: null
23+
24+ QtObject {
25+ id: priv
26+ property bool firstTimeSurface: true
27+ property var lastSurface: application && application.session ?
28+ application.session.lastSurface : null
29+ onLastSurfaceChanged: {
30+ if (!active || !lastSurface) return;
31+ if (!firstTimeSurface) return;
32+ firstTimeSurface = false;
33+
34+ if (lastSurface.state === Mir.FullscreenState &&
35+ lastSurface.shellChrome === Mir.LowChrome) {
36+ lastSurface.state = Mir.RestoredState;
37+ }
38+ }
39+ }
40+}
qml/Stages/DesktopStage.qml+61−9
@@ -257,8 +257,8 @@ AbstractStage {
257257 focus: appId === priv.focusedAppId
258258 width: decoratedWindow.width
259259 height: decoratedWindow.height
260− property alias requestedWidth: decoratedWindow.requestedWidth
261− property alias requestedHeight: decoratedWindow.requestedHeight
260+ property int requestedWidth: -1
261+ property int requestedHeight: -1
262262 property alias minimumWidth: decoratedWindow.minimumWidth
263263 property alias minimumHeight: decoratedWindow.minimumHeight
264264 property alias maximumWidth: decoratedWindow.maximumWidth
@@ -392,20 +392,40 @@ AbstractStage {
392392 PropertyChanges {
393393 target: appDelegate;
394394 x: root.leftMargin; y: 0;
395− requestedWidth: appContainer.width - root.leftMargin; requestedHeight: appContainer.height;
396395 visuallyMinimized: false;
397396 visuallyMaximized: true
398397 }
398+ PropertyChanges {
399+ target: decoratedWindow
400+ requestedWidth: root.width;
401+ requestedHeight: root.height;
402+ }
399403 },
400404 State {
401405 name: "maximizedLeft"; when: appDelegate.maximizedLeft && !appDelegate.minimized
402− PropertyChanges { target: appDelegate; x: root.leftMargin; y: PanelState.panelHeight;
403− requestedWidth: (appContainer.width - root.leftMargin)/2; requestedHeight: appContainer.height - PanelState.panelHeight }
406+ PropertyChanges {
407+ target: appDelegate
408+ x: root.leftMargin
409+ y: PanelState.panelHeight
410+ }
411+ PropertyChanges {
412+ target: decoratedWindow
413+ requestedWidth: (appContainer.width - root.leftMargin)/2
414+ requestedHeight: appContainer.height - PanelState.panelHeight
415+ }
404416 },
405417 State {
406418 name: "maximizedRight"; when: appDelegate.maximizedRight && !appDelegate.minimized
407− PropertyChanges { target: appDelegate; x: (appContainer.width + root.leftMargin)/2; y: PanelState.panelHeight;
408− requestedWidth: (appContainer.width - root.leftMargin)/2; requestedHeight: appContainer.height - PanelState.panelHeight }
419+ PropertyChanges {
420+ target: appDelegate;
421+ x: (appContainer.width + root.leftMargin)/2
422+ y: PanelState.panelHeight
423+ }
424+ PropertyChanges {
425+ target: decoratedWindow
426+ requestedWidth: (appContainer.width - root.leftMargin)/2
427+ requestedHeight: appContainer.height - PanelState.panelHeight
428+ }
409429 },
410430 State {
411431 name: "minimized"; when: appDelegate.minimized
@@ -425,13 +445,17 @@ AbstractStage {
425445 enabled: appDelegate.animationsEnabled
426446 PropertyAction { target: appDelegate; properties: "visuallyMinimized,visuallyMaximized" }
427447 UbuntuNumberAnimation { target: appDelegate; properties: "x,y,opacity,requestedWidth,requestedHeight,scale"; duration: UbuntuAnimation.FastDuration }
448+ UbuntuNumberAnimation { target: decoratedWindow; properties: "requestedWidth,requestedHeight"; duration: UbuntuAnimation.FastDuration }
428449 },
429450 Transition {
430451 to: "minimized"
431452 enabled: appDelegate.animationsEnabled
432453 PropertyAction { target: appDelegate; property: "visuallyMaximized" }
433454 SequentialAnimation {
434− UbuntuNumberAnimation { target: appDelegate; properties: "x,y,opacity,requestedWidth,requestedHeight,scale"; duration: UbuntuAnimation.FastDuration }
455+ ParallelAnimation {
456+ UbuntuNumberAnimation { target: appDelegate; properties: "x,y,opacity,scale"; duration: UbuntuAnimation.FastDuration }
457+ UbuntuNumberAnimation { target: decoratedWindow; properties: "requestedWidth,requestedHeight"; duration: UbuntuAnimation.FastDuration }
458+ }
435459 PropertyAction { target: appDelegate; property: "visuallyMinimized" }
436460 ScriptAction {
437461 script: {
@@ -447,7 +471,10 @@ AbstractStage {
447471 enabled: appDelegate.animationsEnabled
448472 PropertyAction { target: appDelegate; property: "visuallyMinimized" }
449473 SequentialAnimation {
450− UbuntuNumberAnimation { target: appDelegate; properties: "x,y,opacity,requestedWidth,requestedHeight,scale"; duration: UbuntuAnimation.FastDuration }
474+ ParallelAnimation {
475+ UbuntuNumberAnimation { target: appDelegate; properties: "x,y,opacity,scale"; duration: UbuntuAnimation.FastDuration }
476+ UbuntuNumberAnimation { target: decoratedWindow; properties: "requestedWidth,requestedHeight"; duration: UbuntuAnimation.FastDuration }
477+ }
451478 PropertyAction { target: appDelegate; property: "visuallyMaximized" }
452479 }
453480 }
@@ -462,6 +489,7 @@ AbstractStage {
462489 }
463490
464491 WindowResizeArea {
492+ id: resizeArea
465493 objectName: "windowResizeArea"
466494 target: appDelegate
467495 minWidth: units.gu(10)
@@ -473,6 +501,21 @@ AbstractStage {
473501 leftMargin: root.leftMargin
474502
475503 onPressed: { ApplicationManager.focusApplication(model.appId) }
504+
505+ property bool saveStateOnDestruction: true
506+ Connections {
507+ target: root
508+ onStageUnloaded: {
509+ resizeArea.saveWindowState();
510+ resizeArea.saveStateOnDestruction = false;
511+ fullscreenPolicy.active = false;
512+ }
513+ }
514+ Component.onDestruction: {
515+ if (saveStateOnDestruction) {
516+ saveWindowState();
517+ }
518+ }
476519 }
477520
478521 DecoratedWindow {
@@ -484,12 +527,21 @@ AbstractStage {
484527 active: ApplicationManager.focusedApplicationId === model.appId
485528 focus: true
486529
530+ requestedWidth: appDelegate.requestedWidth
531+ requestedHeight: appDelegate.requestedHeight
532+
487533 onClose: ApplicationManager.stopApplication(model.appId)
488534 onMaximize: appDelegate.maximized || appDelegate.maximizedLeft || appDelegate.maximizedRight
489535 ? appDelegate.restoreFromMaximized() : appDelegate.maximize()
490536 onMinimize: appDelegate.minimize()
491537 onDecorationPressed: { ApplicationManager.focusApplication(model.appId) }
492538 }
539+
540+ DesktopFullscreenPolicy {
541+ id: fullscreenPolicy
542+ active: true
543+ application: decoratedWindow.application
544+ }
493545 }
494546 }
495547 }
qml/Stages/PhoneFullscreenPolicy.qmlnew file+53
@@ -0,0 +1,53 @@
1+/*
2+ * Copyright (C) 2016 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+import Unity.Application 0.1
19+
20+Item {
21+ property bool active: true
22+ property QtObject application: null
23+
24+ QtObject {
25+ id: priv
26+ property var lastSurface: application && application.session ?
27+ application.session.lastSurface : null
28+ onLastSurfaceChanged: {
29+ if (!active || !lastSurface) return;
30+ if (lastSurface.shellChrome === Mir.LowChrome) {
31+ lastSurface.state = Mir.FullscreenState;
32+ }
33+ }
34+ }
35+
36+ Connections {
37+ target: priv.lastSurface
38+ onShellChromeChanged: {
39+ if (!active || !priv.lastSurface) return;
40+ if (priv.lastSurface.shellChrome === Mir.LowChrome) {
41+ priv.lastSurface.state = Mir.FullscreenState;
42+ } else {
43+ priv.lastSurface.state = Mir.RestoredState;
44+ }
45+ }
46+ onStateChanged: {
47+ if (!active) return;
48+ if (priv.lastSurface.state === Mir.RestoredState && priv.lastSurface.shellChrome === Mir.LowChrome) {
49+ priv.lastSurface.state = Mir.FullscreenState;
50+ }
51+ }
52+ }
53+}
qml/Stages/PhoneStage.qml+10
@@ -631,6 +631,16 @@ AbstractStage {
631631 property: "focusedAppOrientationChangesEnabled"
632632 value: orientationChangesEnabled
633633 }
634+
635+ PhoneFullscreenPolicy {
636+ id: fullscreenPolicy
637+ application: appDelegate.application
638+
639+ Connections {
640+ target: root
641+ onStageUnloaded: fullscreenPolicy.active = false
642+ }
643+ }
634644 }
635645 }
636646 }
qml/Stages/SideStage.qmlnew file+118
@@ -0,0 +1,118 @@
1+/*
2+ * Copyright (C) 2016 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+import QtQuick.Layouts 1.1
19+import Ubuntu.Components 1.3
20+import Ubuntu.Gestures 0.1
21+import "../Components"
22+
23+Showable {
24+ id: root
25+ property bool showHint: true
26+ property int panelWidth: units.gu(40)
27+ readonly property alias dragging: hideSideStageDragArea.dragging
28+ readonly property real progress: width / panelWidth
29+
30+ width: 0
31+ shown: false
32+
33+ Item {
34+ id: sideStageDragHandle
35+ anchors {
36+ right: root.left
37+ top: root.top
38+ bottom: root.bottom
39+ }
40+ width: units.gu(2)
41+
42+ opacity: root.shown ? 1 : 0
43+ Behavior on opacity { UbuntuNumberAnimation {} }
44+
45+ Image {
46+ anchors.centerIn: parent
47+ width: hideSideStageDragArea.pressed ? parent.width * 2 : parent.width
48+ height: parent.height
49+ source: "graphics/sidestage_handle@20.png"
50+ Behavior on width { UbuntuNumberAnimation {} }
51+ }
52+ }
53+
54+ Rectangle {
55+ anchors.fill: parent
56+ color: Qt.rgba(0,0,0,0.95)
57+ }
58+
59+ Column {
60+ anchors.verticalCenter: parent.verticalCenter
61+ width: panelWidth - units.gu(6)
62+ x: panelWidth/2 - width/2
63+ spacing: units.gu(3)
64+ opacity: 0.8
65+ visible: showHint
66+
67+ Icon {
68+ width: units.gu(30)
69+ anchors.horizontalCenter: parent.horizontalCenter
70+ source: "graphics/sidestage_drag.svg"
71+ color: enabled ? Qt.rgba(1,1,1,1) : Qt.rgba(1,0,0,1)
72+ keyColor: Qt.rgba(1,1,1,1)
73+ }
74+
75+ Label {
76+ text: i18n.tr("Drag using 3 fingers any application from one window to the other")
77+ width: parent.width
78+ wrapMode: Text.WordWrap
79+ color: enabled ? Qt.rgba(1,1,1,1) : Qt.rgba(1,0,0,1)
80+ }
81+ }
82+
83+ showAnimation: NumberAnimation {
84+ property: "width"
85+ to: panelWidth
86+ duration: UbuntuAnimation.BriskDuration
87+ easing.type: Easing.OutCubic
88+ }
89+
90+ hideAnimation: NumberAnimation {
91+ property: "width"
92+ to: 0
93+ duration: UbuntuAnimation.BriskDuration
94+ easing.type: Easing.OutCubic
95+ }
96+
97+ DragHandle {
98+ id: hideSideStageDragArea
99+ objectName: "hideSideStageDragArea"
100+
101+ direction: Direction.Leftwards
102+ rotation: 180
103+ enabled: root.shown
104+ anchors.right: root.left
105+ width: sideStageDragHandle.width
106+ height: root.height
107+ stretch: true
108+
109+ immediateRecognition: true
110+ maxTotalDragDistance: panelWidth
111+ autoCompleteDragThreshold: panelWidth / 2
112+ }
113+
114+ // SideStage mouse event eater
115+ MouseArea {
116+ anchors.fill: parent
117+ }
118+}
qml/Stages/SpreadDelegate.qml+10−1
@@ -32,6 +32,11 @@ FocusScope {
3232 readonly property alias appWindowOrientationAngle: appWindowWithShadow.orientationAngle
3333 readonly property alias appWindowRotation: appWindowWithShadow.rotation
3434 readonly property alias orientationChangesEnabled: appWindow.orientationChangesEnabled
35+ property int supportedOrientations: application ? application.supportedOrientations :
36+ Qt.PortraitOrientation
37+ | Qt.LandscapeOrientation
38+ | Qt.InvertedPortraitOrientation
39+ | Qt.InvertedLandscapeOrientation
3540 readonly property alias appWindow: appWindow
3641
3742 // to be set from outside
@@ -46,6 +51,9 @@ FocusScope {
4651 property QtObject orientations
4752 property bool highlightShown: false
4853
54+ // overrideable from outside
55+ property alias fullscreen: appWindow.fullscreen
56+
4957 function matchShellOrientation() {
5058 if (!root.application)
5159 return;
@@ -155,7 +163,8 @@ FocusScope {
155163 if (!root.application || root.application.rotatesWindowContents) {
156164 return 0;
157165 }
158− var supportedOrientations = root.application.supportedOrientations;
166+
167+ var supportedOrientations = root.supportedOrientations;
159168
160169 if (supportedOrientations === Qt.PrimaryOrientation) {
161170 supportedOrientations = root.orientations.primary;
qml/Stages/SurfaceContainer.qml+1
@@ -134,6 +134,7 @@ FocusScope {
134134
135135
136136 TouchGate {
137+ objectName: "touchGate-"+name
137138 targetItem: surfaceItem
138139 anchors.fill: root
139140 enabled: surfaceItem.enabled
qml/Stages/TabletSideStageTouchGesture.qmlnew file+154
@@ -0,0 +1,154 @@
1+/*
2+ * Copyright (C) 2016 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+import Ubuntu.Gestures 0.1
19+
20+TouchGestureArea {
21+ id: root
22+ minimumTouchPoints: 3
23+ maximumTouchPoints: 3
24+
25+ property bool enableDrag: true
26+ property Component dragComponent
27+ property var dragComponentProperties: undefined
28+
29+ readonly property bool recognisedPress: status == TouchGestureArea.Recognized &&
30+ touchPoints.length >= minimumTouchPoints &&
31+ touchPoints.length <= maximumTouchPoints
32+ readonly property bool recognisedDrag: priv.wasRecognisedPress && dragging
33+
34+ signal pressed(int x, int y)
35+ signal clicked
36+ signal dragStarted
37+ signal dropped
38+ signal cancelled
39+
40+ onEnabledChanged: {
41+ if (!enabled) {
42+ if (priv.dragObject) root.cancelled();
43+ priv.wasRecognisedDrag = false;
44+ priv.wasRecognisedPress = false;
45+ }
46+ }
47+
48+ onRecognisedPressChanged: {
49+ if (recognisedPress) {
50+ // get the app at the center of the gesture
51+ var centerX = 0;
52+ var centerY = 0;
53+ for (var i = 0; i < touchPoints.length; i++) {
54+ centerX += touchPoints[i].x;
55+ centerY += touchPoints[i].y;
56+ }
57+ centerX = centerX/touchPoints.length;
58+ centerY = centerY/touchPoints.length;
59+
60+ pressed(centerX, centerY);
61+ priv.wasRecognisedPress = true;
62+ }
63+ }
64+
65+ onStatusChanged: {
66+ if (status != TouchGestureArea.Recognized) {
67+ if (status == TouchGestureArea.Rejected) {
68+ root.cancelled();
69+ } else if (status == TouchGestureArea.WaitingForTouch) {
70+ if (priv.wasRecognisedPress) {
71+ if (!priv.wasRecognisedDrag) {
72+ root.clicked();
73+ } else {
74+ root.dropped();
75+ }
76+ }
77+ }
78+ priv.wasRecognisedDrag = false;
79+ priv.wasRecognisedPress = false;
80+ }
81+ }
82+
83+ onRecognisedDragChanged: {
84+ if (enableDrag && recognisedDrag) {
85+ priv.wasRecognisedDrag = true;
86+ root.dragStarted()
87+ }
88+ }
89+
90+ QtObject {
91+ id: priv
92+ property var dragObject: null
93+
94+ property bool wasRecognisedPress: false
95+ property bool wasRecognisedDrag: false
96+ }
97+
98+ onCancelled: {
99+ if (priv.dragObject) {
100+ var obj = priv.dragObject;
101+ priv.dragObject = null;
102+
103+ obj.Drag.cancel();
104+ obj.destroy();
105+ }
106+ }
107+
108+ onDragStarted: {
109+ if (dragComponentProperties) {
110+ priv.dragObject = dragComponent.createObject(root, dragComponentProperties);
111+ } else {
112+ priv.dragObject = dragComponent.createObject(root);
113+ }
114+ priv.dragObject.Drag.start();
115+ }
116+
117+ onDropped: {
118+ if (priv.dragObject) {
119+ var obj = priv.dragObject;
120+ priv.dragObject = null;
121+
122+ obj.Drag.drop();
123+ obj.destroy();
124+ }
125+ }
126+
127+ Binding {
128+ target: priv.dragObject
129+ when: priv.dragObject && priv.wasRecognisedDrag
130+ property: "x"
131+ value: {
132+ if (!priv.dragObject) return 0;
133+ var sum = 0;
134+ for (var i = 0; i < root.touchPoints.length; i++) {
135+ sum += root.touchPoints[i].x;
136+ }
137+ return sum/root.touchPoints.length - priv.dragObject.width/2;
138+ }
139+ }
140+
141+ Binding {
142+ target: priv.dragObject
143+ when: priv.dragObject && priv.wasRecognisedDrag
144+ property: "y"
145+ value: {
146+ if (!priv.dragObject) return 0;
147+ var sum = 0;
148+ for (var i = 0; i < root.touchPoints.length; i++) {
149+ sum += root.touchPoints[i].y;
150+ }
151+ return sum/root.touchPoints.length - priv.dragObject.height/2;
152+ }
153+ }
154+}
qml/Stages/TabletStage.qml+397−171
@@ -46,7 +46,7 @@ AbstractStage {
4646 if (delta < 0) { delta += 360; }
4747 delta = delta % 360;
4848
49− var supportedOrientations = spreadDelegate.application.supportedOrientations;
49+ var supportedOrientations = spreadDelegate.supportedOrientations;
5050 if (supportedOrientations === Qt.PrimaryOrientation) {
5151 supportedOrientations = spreadDelegate.orientations.primary;
5252 }
@@ -80,9 +80,24 @@ AbstractStage {
8080
8181 orientationChangesEnabled: priv.mainAppOrientationChangesEnabled
8282
83− supportedOrientations: mainApp ? mainApp.supportedOrientations
84− : (Qt.PortraitOrientation | Qt.LandscapeOrientation
85− | Qt.InvertedPortraitOrientation | Qt.InvertedLandscapeOrientation)
83+ supportedOrientations: {
84+ if (mainApp) {
85+ var orientations = mainApp.supportedOrientations;
86+ orientations |= Qt.LandscapeOrientation | Qt.InvertedLandscapeOrientation;
87+ if (priv.sideStageAppId && !spreadView.surfaceDragging) {
88+ // If we have a sidestage app, support Portrait orientation
89+ // so that it will switch the sidestage app to mainstage on rotate
90+ orientations |= Qt.PortraitOrientation|Qt.InvertedPortraitOrientation;
91+ }
92+ return orientations;
93+ } else {
94+ // we just don't care
95+ return Qt.PortraitOrientation |
96+ Qt.LandscapeOrientation |
97+ Qt.InvertedPortraitOrientation |
98+ Qt.InvertedLandscapeOrientation;
99+ }
100+ }
86101
87102 onWidthChanged: {
88103 spreadView.selectedIndex = -1;
@@ -90,13 +105,6 @@ AbstractStage {
90105 spreadView.contentX = -spreadView.shift;
91106 }
92107
93− onShellOrientationChanged: {
94− if (shellOrientation == Qt.PortraitOrientation || shellOrientation == Qt.InvertedPortraitOrientation) {
95− ApplicationManager.focusApplication(priv.mainStageAppId);
96− priv.sideStageAppId = "";
97− }
98− }
99−
100108 onInverseProgressChanged: {
101109 // This can't be a simple binding because that would be triggered after this handler
102110 // while we need it active before doing the anition left/right
@@ -172,20 +180,7 @@ AbstractStage {
172180
173181 property int highlightIndex: 0
174182
175− onFocusedAppIdChanged: {
176− if (priv.focusedAppId.length > 0) {
177− var focusedApp = ApplicationManager.findApplication(focusedAppId);
178− if (focusedApp.stage == ApplicationInfoInterface.SideStage) {
179− priv.sideStageAppId = focusedAppId;
180− } else {
181− priv.mainStageAppId = focusedAppId;
182− root.mainApp = focusedApp;
183− }
184− }
185−
186− appId0 = ApplicationManager.count >= 1 ? ApplicationManager.get(0).appId : "";
187− appId1 = ApplicationManager.count > 1 ? ApplicationManager.get(1).appId : "";
188− }
183+ onFocusedAppIdChanged: updateStageApps()
189184
190185 onFocusedAppDelegateChanged: {
191186 if (focusedAppDelegate) {
@@ -231,6 +226,46 @@ AbstractStage {
231226 onHighlightIndexChanged: {
232227 spreadView.contentX = highlightIndex * spreadView.contentWidth / (spreadRepeater.count + 2)
233228 }
229+
230+ function getTopApp(stage) {
231+ for (var i = 0; i < ApplicationManager.count; i++) {
232+ var app = ApplicationManager.get(i)
233+ if (app.stage === stage) {
234+ return app;
235+ }
236+ }
237+ return null;
238+ }
239+
240+ function setAppStage(appId, stage, save) {
241+ var app = ApplicationManager.findApplication(appId);
242+ if (app) {
243+ app.stage = stage;
244+ if (save) {
245+ WindowStateStorage.saveStage(appId, stage);
246+ }
247+ }
248+ }
249+
250+ function updateStageApps() {
251+ var app = priv.getTopApp(ApplicationInfoInterface.MainStage);
252+ priv.mainStageAppId = app ? app.appId : ""
253+ root.mainApp = app;
254+
255+ if (sideStage.shown) {
256+ app = priv.getTopApp(ApplicationInfoInterface.SideStage);
257+ priv.sideStageAppId = app ? app.appId : ""
258+ } else {
259+ priv.sideStageAppId = "";
260+ }
261+
262+ appId0 = ApplicationManager.count >= 1 ? ApplicationManager.get(0).appId : "";
263+ appId1 = ApplicationManager.count > 1 ? ApplicationManager.get(1).appId : "";
264+ }
265+
266+ readonly property bool sideStageEnabled: root.shellOrientation == Qt.LandscapeOrientation ||
267+ root.shellOrientation == Qt.InvertedLandscapeOrientation
268+ Component.onCompleted: updateStageApps();
234269 }
235270
236271 Connections {
@@ -258,7 +293,8 @@ AbstractStage {
258293 ApplicationManager.focusApplication("unity8-dash")
259294 }
260295 if (priv.sideStageAppId == appId) {
261− priv.sideStageAppId = "";
296+ var app = priv.getTopApp(ApplicationInfoInterface.SideStage);
297+ priv.sideStageAppId = app === null ? "" : app.appId;
262298 }
263299
264300 if (ApplicationManager.count == 0) {
@@ -269,6 +305,7 @@ AbstractStage {
269305 // lets make sure the spread doesn't mess up by the changing app list.
270306 spreadView.phase = 0;
271307 spreadView.contentX = -spreadView.shift;
308+
272309 ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
273310 }
274311 }
@@ -324,6 +361,7 @@ AbstractStage {
324361 property int selectedIndex: -1
325362 property int draggedDelegateCount: 0
326363 property int closingIndex: -1
364+ property var selectedApplication: selectedIndex !== -1 ? ApplicationManager.get(selectedIndex) : null
327365
328366 // FIXME: Workaround Flickable's not keepping its contentX still when resized
329367 onContentXChanged: { forceItToRemainStillIfBeingResized(); }
@@ -345,15 +383,8 @@ AbstractStage {
345383 }
346384 }
347385
348− property bool sideStageDragging: sideStageDragHandle.dragging
349− property real sideStageDragProgress: sideStageDragHandle.progress
350−
351− onSideStageDragProgressChanged: {
352− if (sideStageDragProgress == 1) {
353− ApplicationManager.focusApplication(priv.mainStageAppId);
354− priv.sideStageAppId = "";
355− }
356− }
386+ property real sideStageDragProgress: sideStage.progress
387+ property bool surfaceDragging: triGestureArea.recognisedDrag
357388
358389 // In case the ApplicationManager already holds an app when starting up we're missing animations
359390 // Make sure we end up in the same state
@@ -404,7 +435,7 @@ AbstractStage {
404435 }
405436 ]
406437 state: {
407− if (priv.mainStageAppId && !priv.sideStageAppId) {
438+ if ((priv.mainStageAppId && !priv.sideStageAppId) || !priv.sideStageEnabled) {
408439 return "main";
409440 }
410441 if (!priv.mainStageAppId && priv.sideStageAppId) {
@@ -421,12 +452,26 @@ AbstractStage {
421452 // Flickabe.contentX wiggles during resizes. Don't react to it.
422453 return;
423454 }
424− if (spreadView.phase == 0 && spreadView.shiftedContentX > spreadView.width * spreadView.positionMarker2) {
425− spreadView.phase = 1;
426− } else if (spreadView.phase == 1 && spreadView.shiftedContentX > spreadView.width * spreadView.positionMarker4) {
427− spreadView.phase = 2;
428− } else if (spreadView.phase == 1 && spreadView.shiftedContentX < spreadView.width * spreadView.positionMarker2) {
429− spreadView.phase = 0;
455+
456+ switch (phase) {
457+ case 0:
458+ // the "spreadEnabled" part is because when code does "phase = 0; contentX = -shift" to
459+ // dismiss the spread because spreadEnabled went to false, for some reason, during tests,
460+ // Flickable might jump in and change contentX value back, causing the code below to do
461+ // "phase = 1" which will make the spread stay.
462+ // It sucks that we have no control whatsoever over whether or when Flickable animates its
463+ // contentX.
464+ if (root.spreadEnabled && shiftedContentX > width * positionMarker2) {
465+ phase = 1;
466+ }
467+ break;
468+ case 1:
469+ if (shiftedContentX < width * positionMarker2) {
470+ phase = 0;
471+ } else if (shiftedContentX >= width * positionMarker4 && !spreadDragArea.dragging) {
472+ phase = 2;
473+ }
474+ break;
430475 }
431476 }
432477
@@ -448,6 +493,7 @@ AbstractStage {
448493 }
449494
450495 function snapTo(index) {
496+ snapAnimation.stop();
451497 spreadView.selectedIndex = index;
452498 snapAnimation.targetContentX = -shift;
453499 snapAnimation.start();
@@ -457,55 +503,41 @@ AbstractStage {
457503 // We don't want to really reorder them in the model because that allows us to keep track
458504 // of the last focused order.
459505 function indexToZIndex(index) {
506+ // only shuffle when we've got a main and overlay
507+ if (state !== "mainAndOverlay") return index;
508+
460509 var app = ApplicationManager.get(index);
461510 if (!app) {
462511 return index;
463512 }
464513
465− var active = app.appId == priv.mainStageAppId || app.appId == priv.sideStageAppId;
466− if (active && app.stage == ApplicationInfoInterface.MainStage) {
467− // if this app is active, and its the MainStage, always put it to index 0
514+ // don't shuffle indexes greater than "actives or next"
515+ if (index > 2) return index;
516+
517+ if (app.appId === priv.mainStageAppId) {
518+ // Active main stage always at 0
468519 return 0;
469520 }
470− if (active && app.stage == ApplicationInfoInterface.SideStage) {
471− if (!priv.mainStageAppId) {
472− // Only have SS apps running. Put the active one at 0
473− return 0;
474− }
475521
476− // Precondition now: There's an active MS app and this is SS app:
477− if (spreadView.nextInStack >= 0 && ApplicationManager.get(spreadView.nextInStack).stage == ApplicationInfoInterface.MainStage) {
478− // If the next app coming from the right is a MS app, we need to elevate this SS ap above it.
479− // Put it to at least level 2, or higher if there's more apps coming in before this one.
480− return Math.max(index, 2);
481− } else {
482− // 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.
522+ if (spreadView.nextInStack > 0) {
523+ var nextAppInStack = ApplicationManager.get(spreadView.nextInStack);
524+
525+ if (index === spreadView.nextInStack) {
526+ // this is the next app in stack.
527+
528+ if (app.stage === ApplicationInfoInterface.SideStage) {
529+ // if the next app in stack is a sidestage app, it must order on top of other side stage app
530+ return Math.min(2, ApplicationManager.count-1);
531+ }
483532 return 1;
484533 }
485− }
486− if (index <= 2 && app.stage == ApplicationInfoInterface.MainStage && priv.sideStageAppId) {
487− // Ok, this is an inactive MS app. If there's an active SS app around, we need to place this one
488− // in between the active MS app and the active SS app, so that it comes in from there when dragging from the right.
489− // If there's now active SS app, just leave it where it is.
490− return priv.indexOf(priv.sideStageAppId) < index ? index - 1 : index;
491− }
492− if (index == spreadView.nextInStack && app.stage == ApplicationInfoInterface.SideStage) {
493− // This is a SS app and the next one to come in from the right:
494− if (priv.sideStageAppId && priv.mainStageAppId) {
495− // If there's both, an active MS and an active SS app, put this one right on top of that
496− return 2;
534+ if (nextAppInStack.stage === ApplicationInfoInterface.SideStage) {
535+ // if the next app in stack is a sidestage app, it must order on top of other side stage app
536+ return 1;
497537 }
498− // Or if there's only one other active app, put it on top of that.
499− // The case that there isn't any other active app is already handled above.
500− return 1;
501− }
502− if (index == 2 && spreadView.nextInStack == 1 && priv.sideStageAppId) {
503− // If its index 2 but not the next one to come in, it means
504− // we've pulled another one down to index 2. Move this one up to 2 instead.
505− return 3;
538+ return Math.min(2, ApplicationManager.count-1);
506539 }
507− // don't touch all others... (mostly index > 3 + simple cases where the above doesn't shuffle much)
508− return index;
540+ return Math.min(index+1, ApplicationManager.count-1);
509541 }
510542
511543 SequentialAnimation {
@@ -523,8 +555,12 @@ AbstractStage {
523555 script: {
524556 if (spreadView.selectedIndex >= 0) {
525557 var newIndex = spreadView.selectedIndex;
558+ var application = ApplicationManager.get(newIndex);
559+ if (application.stage === ApplicationInfoInterface.SideStage) {
560+ sideStage.showNow();
561+ }
526562 spreadView.selectedIndex = -1;
527− ApplicationManager.focusApplication(ApplicationManager.get(newIndex).appId);
563+ ApplicationManager.focusApplication(application.appId);
528564 spreadView.phase = 0;
529565 spreadView.contentX = -spreadView.shift;
530566 }
@@ -547,87 +583,82 @@ AbstractStage {
547583 spreadView.snapTo(0);
548584 }
549585
550− Rectangle {
551− id: sideStageBackground
552− color: "black"
553− width: spreadView.sideStageWidth * (1 - sideStageDragHandle.progress)
554− height: priv.landscapeHeight
555− x: spreadView.width - width
556− z: spreadView.indexToZIndex(priv.indexOf(priv.sideStageAppId))
557− opacity: spreadView.phase == 0 ? 1 : 0
558− Behavior on opacity { UbuntuNumberAnimation {} }
586+ DropArea {
587+ objectName: "MainStageDropArea"
588+ anchors {
589+ left: parent.left
590+ top: parent.top
591+ bottom: parent.bottom
592+ }
593+ width: spreadView.width - sideStage.width
594+ enabled: priv.sideStageEnabled
595+
596+ onDropped: {
597+ priv.setAppStage(drag.source.appId, ApplicationInfoInterface.MainStage, true);
598+ ApplicationManager.focusApplication(drag.source.appId);
599+ }
600+ keys: "SideStage"
559601 }
560602
561− Item {
562− id: sideStageDragHandle
563− anchors.right: sideStageBackground.left
564− anchors.top: sideStageBackground.top
565− width: units.gu(2)
603+ SideStage {
604+ id: sideStage
605+ objectName: "sideStage"
566606 height: priv.landscapeHeight
567− z: sideStageBackground.z
568− opacity: spreadView.phase <= 0 && spreadView.sideStageVisible ? 1 : 0
569− property real progress: 0
570− property bool dragging: false
607+ x: spreadView.width - width
608+ z: {
609+ if (!priv.mainStageAppId) return 0;
571610
572− Behavior on opacity { UbuntuNumberAnimation {} }
611+ if (priv.sideStageAppId && spreadView.nextInStack > 0) {
612+ var nextAppInStack = ApplicationManager.get(spreadView.nextInStack);
573613
574− Connections {
575− target: spreadView
576− onSideStageVisibleChanged: {
577− if (spreadView.sideStageVisible) {
578− sideStageDragHandle.progress = 0;
614+ if (nextAppInStack.stage === ApplicationInfoInterface.MainStage) {
615+ // if the next app in stack is a main stage app, put the sidestage on top of it.
616+ return 2;
579617 }
618+ return 1;
580619 }
620+
621+ return 1;
581622 }
623+ visible: progress != 0
624+ enabled: priv.sideStageEnabled && sideStageDropArea.dropAllowed
625+ opacity: priv.sideStageEnabled && !spreadView.active ? 1 : 0
626+ Behavior on opacity { UbuntuNumberAnimation {} }
582627
583− Image {
584− anchors.centerIn: parent
585− width: sideStageDragHandleMouseArea.pressed ? parent.width * 2 : parent.width
586− height: parent.height
587− source: "graphics/sidestage_handle@20.png"
588− Behavior on width { UbuntuNumberAnimation {} }
628+ onShownChanged: {
629+ if (!shown && ApplicationManager.focusedApplicationId == priv.sideStageAppId) {
630+ ApplicationManager.requestFocusApplication(priv.mainStageAppId);
631+ }
632+ priv.updateStageApps();
633+ if (shown && priv.sideStageAppId) {
634+ ApplicationManager.requestFocusApplication(priv.sideStageAppId);
635+ }
589636 }
590637
591− MouseArea {
592− id: sideStageDragHandleMouseArea
638+ DropArea {
639+ id: sideStageDropArea
640+ objectName: "SideStageDropArea"
593641 anchors.fill: parent
594− enabled: spreadView.shiftedContentX == 0
595− property int startX
596− property var gesturePoints: new Array()
597− property real totalDiff
598−
599− onPressed: {
600− gesturePoints = [];
601− startX = mouseX;
602− totalDiff = 0.0;
603− sideStageDragHandle.progress = 0;
604− sideStageDragHandle.dragging = true;
605− }
606− onMouseXChanged: {
607− totalDiff += mouseX - startX;
608− if (priv.mainStageAppId) {
609− sideStageDragHandle.progress = Math.max(0, totalDiff / spreadView.sideStageWidth);
610− }
611− gesturePoints.push(mouseX);
642+
643+ property bool dropAllowed: true
644+
645+ onEntered: {
646+ dropAllowed = drag.keys != "Disabled";
612647 }
613− onReleased: {
614− if (priv.mainStageAppId) {
615− var oneWayFlick = priv.evaluateOneWayFlick(gesturePoints);
616− sideStageDragSnapAnimation.to = sideStageDragHandle.progress > 0.5 || oneWayFlick ? 1 : 0;
617− sideStageDragSnapAnimation.start();
618− } else {
619− sideStageDragHandle.dragging = false;
648+ onExited: {
649+ dropAllowed = true;
650+ }
651+ onDropped: {
652+ if (drop.keys == "MainStage") {
653+ priv.setAppStage(drop.source.appId, ApplicationInfoInterface.SideStage, true);
654+ ApplicationManager.requestFocusApplication(drop.source.appId);
620655 }
621656 }
622− }
623− UbuntuNumberAnimation {
624− id: sideStageDragSnapAnimation
625− target: sideStageDragHandle
626− property: "progress"
627−
628− onRunningChanged: {
629− if (!running) {
630− sideStageDragHandle.dragging = false;
657+ drag {
658+ onSourceChanged: {
659+ if (!sideStageDropArea.drag.source) {
660+ dropAllowed = true;
661+ }
631662 }
632663 }
633664 }
@@ -642,29 +673,17 @@ AbstractStage {
642673 id: spreadTile
643674 objectName: model.appId ? "tabletSpreadDelegate_" + model.appId
644675 : "tabletSpreadDelegate_null";
645− width: {
646− if (wantsMainStage) {
647− return spreadView.width;
648− } else {
649− return spreadView.sideStageWidth;
650− }
651− }
652− height: {
653− if (wantsMainStage) {
654− return spreadView.height;
655− } else {
656− return priv.landscapeHeight;
657− }
658− }
659− active: model.appId == priv.mainStageAppId || model.appId == priv.sideStageAppId
660− zIndex: spreadView.indexToZIndex(index)
676+ width: spreadView.width
677+ height: spreadView.height
678+ active: appId == priv.mainStageAppId || appId == priv.sideStageAppId
679+ zIndex: selected && stage == ApplicationInfoInterface.MainStage ? 0 : spreadView.indexToZIndex(index)
661680 selected: spreadView.selectedIndex == index
662681 otherSelected: spreadView.selectedIndex >= 0 && !selected
663− isInSideStage: priv.sideStageAppId == model.appId
682+ isInSideStage: priv.sideStageAppId === appId
664683 interactive: !spreadView.interactive && spreadView.phase === 0 && root.interactive
665684 swipeToCloseEnabled: spreadView.interactive && !snapAnimation.running
666685 maximizedAppTopMargin: root.maximizedAppTopMargin
667− dragOffset: !isDash && model.appId == priv.mainStageAppId && root.inverseProgress > 0 && spreadView.phase === 0 ? root.inverseProgress : 0
686+ dragOffset: !isDash && appId == priv.mainStageAppId && root.inverseProgress > 0 && spreadView.phase === 0 ? root.inverseProgress : 0
668687 application: ApplicationManager.get(index)
669688 closeable: !isDash
670689 highlightShown: root.altTabPressed && priv.highlightIndex == zIndex
@@ -673,6 +692,35 @@ AbstractStage {
673692
674693 readonly property bool isDash: model.appId == "unity8-dash"
675694
695+ stage: model.stage
696+ fullscreen: {
697+ if (mainApp && stage === ApplicationInfoInterface.SideStage) {
698+ return mainApp.fullscreen;
699+ }
700+ return application ? application.fullscreen : false;
701+ }
702+
703+ supportedOrientations: {
704+ if (application) {
705+ var orientations = application.supportedOrientations;
706+ if (stage == ApplicationInfoInterface.MainStage) {
707+ // When an app is in the mainstage, it always supports Landscape|InvertedLandscape
708+ // so that we can drag it from the main stage to the side stage
709+ orientations |= Qt.LandscapeOrientation | Qt.InvertedLandscapeOrientation;
710+ }
711+ return orientations;
712+ } else {
713+ // we just don't care
714+ return Qt.PortraitOrientation |
715+ Qt.LandscapeOrientation |
716+ Qt.InvertedPortraitOrientation |
717+ Qt.InvertedLandscapeOrientation;
718+ }
719+ }
720+
721+ readonly property string appId: model.appId
722+ readonly property bool isDash: appId == "unity8-dash"
723+
676724 Binding {
677725 target: spreadTile.application
678726 property: "exemptFromLifecycle"
@@ -703,6 +751,28 @@ AbstractStage {
703751 enabled: spreadView.closingIndex >= 0
704752 UbuntuNumberAnimation {}
705753 }
754+ Connections {
755+ target: priv
756+ onSideStageEnabledChanged: refreshStage()
757+ }
758+
759+ Component.onCompleted: {
760+ refreshStage()
761+ stageChanged.connect(priv.updateStageApps);
762+ }
763+
764+ function refreshStage() {
765+ var stage = ApplicationInfoInterface.MainStage;
766+ if (priv.sideStageEnabled) {
767+ if (application && application.supportedOrientations & (Qt.PortraitOrientation|Qt.InvertedPortraitOrientation)) {
768+ stage = WindowStateStorage.getStage(appId);
769+ }
770+ }
771+
772+ if (model.stage !== stage) {
773+ priv.setAppStage(appId, stage, false);
774+ }
775+ }
706776
707777 // This is required because none of the bindings are triggered in some cases:
708778 // When an app is closed, it might happen that ApplicationManager.get(nextInStack)
@@ -727,7 +797,7 @@ AbstractStage {
727797
728798 // TODO: Hiding tile when progress is such that it will be off screen.
729799 property bool occluded: {
730− if (spreadView.active) return false;
800+ if (spreadView.active && !offScreen) return false;
731801 else if (spreadTile.active) return false;
732802 else if (xTranslateAnimating) return false;
733803 else if (z <= 1 && priv.focusedAppDelegateIsDislocated) return false;
@@ -751,17 +821,87 @@ AbstractStage {
751821 return progress;
752822 }
753823
754− shellOrientationAngle: wantsMainStage ? root.shellOrientationAngle : 0
755− shellOrientation: wantsMainStage ? root.shellOrientation : Qt.PortraitOrientation
756− orientations: Orientations {
757− primary: spreadTile.wantsMainStage ? root.orientations.primary : Qt.PortraitOrientation
758− native_: spreadTile.wantsMainStage ? root.orientations.native_ : Qt.PortraitOrientation
824+ shellOrientationAngle: root.shellOrientationAngle
825+ shellOrientation: root.shellOrientation
826+ orientations: root.orientations
827+
828+ states: [
829+ State {
830+ name: "MainStage"
831+ when: spreadTile.stage == ApplicationInfoInterface.MainStage
832+ },
833+ State {
834+ name: "SideStage"
835+ when: spreadTile.stage == ApplicationInfoInterface.SideStage
836+
837+ PropertyChanges {
838+ target: spreadTile
839+ width: spreadView.sideStageWidth
840+ height: priv.landscapeHeight
841+
842+ supportedOrientations: Qt.PortraitOrientation
843+ shellOrientationAngle: 0
844+ shellOrientation: Qt.PortraitOrientation
845+ orientations: sideStageOrientations
846+ }
847+ }
848+ ]
849+
850+ Orientations {
851+ id: sideStageOrientations
852+ primary: Qt.PortraitOrientation
853+ native_: Qt.PortraitOrientation
759854 portrait: root.orientations.portrait
760855 invertedPortrait: root.orientations.invertedPortrait
761856 landscape: root.orientations.landscape
762857 invertedLandscape: root.orientations.invertedLandscape
763858 }
764859
860+ transitions: [
861+ Transition {
862+ to: "SideStage"
863+ SequentialAnimation {
864+ PropertyAction {
865+ target: spreadTile
866+ properties: "width,height,supportedOrientations,shellOrientationAngle,shellOrientation,orientations"
867+ }
868+ ScriptAction {
869+ script: {
870+ // rotate immediately.
871+ spreadTile.matchShellOrientation();
872+ if (ApplicationManager.focusedApplicationId === spreadTile.appId &&
873+ priv.sideStageEnabled && !sideStage.shown) {
874+ // Sidestage was focused, so show the side stage.
875+ sideStage.show();
876+ // if we've switched to a main app which doesnt support portrait, hide the side stage.
877+ } else if (mainApp && (mainApp.supportedOrientations & (Qt.PortraitOrientation|Qt.InvertedPortraitOrientation)) == 0) {
878+ sideStage.hideNow();
879+ }
880+ }
881+ }
882+ }
883+ },
884+ Transition {
885+ from: "SideStage"
886+ SequentialAnimation {
887+ ScriptAction {
888+ script: {
889+ if (priv.sideStageAppId === spreadTile.appId &&
890+ mainApp && (mainApp.supportedOrientations & (Qt.PortraitOrientation|Qt.InvertedPortraitOrientation)) == 0) {
891+ // The mainstage app did not natively support portrait orientation, so focus the sidestage.
892+ ApplicationManager.requestFocusApplication(spreadTile.appId);
893+ }
894+ }
895+ }
896+ PropertyAction {
897+ target: spreadTile
898+ properties: "width,height,supportedOrientations,shellOrientationAngle,shellOrientation,orientations"
899+ }
900+ ScriptAction { script: { spreadTile.matchShellOrientation(); } }
901+ }
902+ }
903+ ]
904+
765905 onClicked: {
766906 if (spreadView.phase == 2) {
767907 spreadView.snapTo(index);
@@ -781,6 +921,16 @@ AbstractStage {
781921 ApplicationManager.stopApplication(ApplicationManager.get(index).appId);
782922 }
783923
924+ onFocusChanged: {
925+ if (focus && ApplicationManager.focusedApplicationId !== appId) {
926+ ApplicationManager.focusApplication(appId);
927+ }
928+
929+ if (focus && priv.sideStageEnabled && stage === ApplicationInfoInterface.SideStage) {
930+ sideStage.show();
931+ }
932+ }
933+
784934 Binding {
785935 target: root
786936 when: model.appId == priv.mainStageAppId
@@ -800,6 +950,81 @@ AbstractStage {
800950 period: (spreadView.positionMarker2 - spreadView.positionMarker1) / 3
801951 progress: spreadTile.progress - spreadView.positionMarker1
802952 }
953+
954+ PhoneFullscreenPolicy {
955+ id: fullscreenPolicy
956+ application: spreadTile.application
957+
958+ Connections {
959+ target: root
960+ onStageUnloaded: fullscreenPolicy.active = false
961+ }
962+ }
963+ }
964+ }
965+ }
966+ }
967+
968+ TabletSideStageTouchGesture {
969+ id: triGestureArea
970+ anchors.fill: parent
971+ enabled: priv.sideStageEnabled && !spreadView.active
972+ property var dragObject: null
973+ property string appId: ""
974+ dragComponent: dragComponent
975+ dragComponentProperties: { "appId": appId }
976+
977+ onPressed: {
978+ function matchDelegate(obj) { return String(obj.objectName).indexOf("tabletSpreadDelegate") >= 0; }
979+
980+ var delegateAtCenter = Functions.itemAt(spreadRow, x, y, matchDelegate);
981+ if (!delegateAtCenter) return;
982+
983+ appId = delegateAtCenter.appId;
984+ }
985+
986+ onClicked: {
987+ if (sideStage.shown) {
988+ sideStage.hide();
989+ } else {
990+ sideStage.show();
991+ }
992+ }
993+
994+ onDragStarted: {
995+ // If we're dragging to the sidestage.
996+ if (!sideStage.shown) {
997+ sideStage.show();
998+ }
999+ }
1000+
1001+ Component {
1002+ id: dragComponent
1003+ SessionContainer {
1004+ property string appId: ""
1005+ property var application: ApplicationManager.findApplication(appId)
1006+
1007+ session: application ? application.session : null
1008+ interactive: false
1009+ resizeSurface: false
1010+ focus: false
1011+
1012+ width: units.gu(40)
1013+ height: units.gu(40)
1014+
1015+ Drag.hotSpot.x: width/2
1016+ Drag.hotSpot.y: height/2
1017+ // only accept opposite stage.
1018+ Drag.keys: {
1019+ if (!application) return "Disabled";
1020+
1021+ if (application.stage === ApplicationInfo.MainStage) {
1022+ if (application.supportedOrientations & (Qt.PortraitOrientation|Qt.InvertedPortraitOrientation)) {
1023+ return "MainStage";
1024+ }
1025+ return "Disabled";
1026+ }
1027+ return "SideStage";
8031028 }
8041029 }
8051030 }
@@ -814,7 +1039,8 @@ AbstractStage {
8141039 DirectionalDragArea {
8151040 id: spreadDragArea
8161041 objectName: "spreadDragArea"
817− anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
1042+ x: parent.width - root.dragAreaWidth
1043+ anchors { top: parent.top; bottom: parent.bottom }
8181044 width: root.dragAreaWidth
8191045 direction: Direction.Leftwards
8201046 enabled: (spreadView.phase != 2 && root.spreadEnabled) || dragging
qml/Stages/TransformedTabletSpreadDelegate.qml+84−56
@@ -31,6 +31,7 @@ SpreadDelegate {
3131 // Set this to true when this tile a currently active on either the MS or the SS.
3232 property bool active: false
3333
34+ property int stage
3435 property int zIndex
3536 property real progress: 0
3637 property real animatedProgress: 0
@@ -49,10 +50,11 @@ SpreadDelegate {
4950
5051 property int dragOffset: 0
5152 readonly property alias xTranslateAnimating: xTranslateAnimation.running
53+ readonly property bool offScreen: priv.xTranslate >= 0
5254
5355 dropShadow: spreadView.active ||
5456 (active
55− && (model.stage == ApplicationInfoInterface.MainStage || !priv.shellIsLandscape)
57+ && (stage == ApplicationInfoInterface.MainStage || !priv.shellIsLandscape)
5658 && priv.xTranslate != 0)
5759
5860 onSelectedChanged: {
@@ -141,9 +143,9 @@ SpreadDelegate {
141143 enabled: !spreadView.active &&
142144 !snapAnimation.running &&
143145 model.appId !== "unity8-dash" &&
144− !spreadView.sideStageDragging &&
145146 spreadView.animateX &&
146− !spreadView.beingResized
147+ !spreadView.beingResized &&
148+ priv.state !== "sideStage"
147149 UbuntuNumberAnimation {
148150 id: xTranslateAnimation
149151 duration: UbuntuAnimation.FastDuration
@@ -153,21 +155,20 @@ SpreadDelegate {
153155 property real xTranslate: {
154156 var newTranslate = 0;
155157
156− if (otherSelected) {
157− return priv.selectedXTranslate;
158− }
159−
160− if (isSelected) {
161− if (model.stage == ApplicationInfoInterface.MainStage) {
158+ // selected app or opposite stage active app.
159+ if (isSelected || (otherSelected && root.active && spreadView.selectedApplication && spreadView.selectedApplication.stage !== stage)) {
160+ if (stage == ApplicationInfoInterface.MainStage) {
162161 return linearAnimation(selectedProgress, negativeProgress, selectedXTranslate, -spreadView.width, root.progress);
163162 } else {
164163 return linearAnimation(selectedProgress, negativeProgress, selectedXTranslate, -spreadView.sideStageWidth, root.progress);
165164 }
165+ } else if (otherSelected) {
166+ return selectedXTranslate;
166167 }
167168
168169 // 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
169170 // when we're only dragging the side stage in on top of a main stage app
170− var shouldMoveAway = spreadView.nextInStack >= 0 && priv.movedActive && model.stage === ApplicationInfoInterface.MainStage &&
171+ var shouldMoveAway = spreadView.nextInStack >= 0 && priv.movedActive && stage === ApplicationInfoInterface.MainStage &&
171172 ApplicationManager.get(spreadView.nextInStack).stage === ApplicationInfoInterface.MainStage;
172173
173174 if (active) {
@@ -177,36 +178,45 @@ SpreadDelegate {
177178 newTranslate += linearAnimation(0, spreadView.positionMarker2, 0, -units.gu(4), root.animatedProgress);
178179 }
179180 newTranslate += root.dragOffset;
180− }
181− if (!spreadView.active && model.appId == "unity8-dash" && !root.active) {
181+ } else if (!spreadView.active && model.appId == "unity8-dash") {
182182 newTranslate -= root.width;
183183 }
184184
185− if (nextInStack && spreadView.phase == 0) {
186− if (model.stage == ApplicationInfoInterface.MainStage) {
187− if (spreadView.sideStageVisible && root.progress > 0) {
188− // Move it so it appears from behind the side stage immediately
189− newTranslate += -spreadView.sideStageWidth;
185+ if (!spreadView.active) {
186+ return newTranslate;
187+ }
188+
189+ var shadowOffset = units.gu(2);
190+
191+ if (spreadView.phase == 0) {
192+ if (nextInStack) {
193+ if (stage == ApplicationInfoInterface.MainStage) {
194+ if (spreadView.sideStageVisible && root.progress > 0) {
195+ // Move it so it appears from behind the side stage immediately
196+ newTranslate += -spreadView.sideStageWidth;
197+ }
190198 }
191− }
192199
193− if (model.stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
194− // This is when we only drag the side stage in, without rotation or snapping
195− newTranslate = linearAnimation(0, spreadView.positionMarker2, 0, -spreadView.sideStageWidth, root.progress);
196− } else {
197− newTranslate += linearAnimation(0, spreadView.positionMarker2, 0, -spreadView.sideStageWidth * spreadView.snapPosition, root.animatedProgress);
200+ if (stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
201+ // This is when we only drag the side stage in, without rotation or snapping
202+ newTranslate = linearAnimation(0, spreadView.positionMarker2, 0, -spreadView.sideStageWidth, root.animatedProgress);
203+ } else {
204+ newTranslate += linearAnimation(0, spreadView.positionMarker2, 0, -spreadView.sideStageWidth * spreadView.snapPosition, root.animatedProgress);
205+ }
206+ } else if (!root.active) {
207+ newTranslate += shadowOffset;
198208 }
199209 }
200210
201211 if (spreadView.phase == 1) {
202212 if (nextInStack) {
203− if (model.stage == ApplicationInfoInterface.MainStage) {
213+ if (stage == ApplicationInfoInterface.MainStage) {
204214 var startValue = -spreadView.sideStageWidth * spreadView.snapPosition + (spreadView.sideStageVisible ? -spreadView.sideStageWidth : 0);
205215 newTranslate += linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, startValue, priv.phase2StartTranslate, root.animatedProgress);
206216 } else {
207217 var endValue = -spreadView.width + spreadView.width * root.zIndex / 6;
208218 if (!spreadView.sideStageVisible) {
209− newTranslate += linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, -spreadView.sideStageWidth, priv.phase2StartTranslate, root.progress);
219+ newTranslate += linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, -spreadView.sideStageWidth, priv.phase2StartTranslate, root.animatedProgress);
210220 } else {
211221 newTranslate += linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, -spreadView.sideStageWidth * spreadView.snapPosition, priv.phase2StartTranslate, root.animatedProgress);
212222 }
@@ -215,16 +225,20 @@ SpreadDelegate {
215225 var startProgress = spreadView.positionMarker2 - (zIndex * spreadView.positionMarker2 / 2);
216226 var endProgress = spreadView.positionMarker4 - (zIndex * spreadView.tileDistance / spreadView.width);
217227 var startTranslate = -root.width + (shouldMoveAway ? -units.gu(4) : 0);
218− newTranslate = linearAnimation(startProgress, endProgress, startTranslate, priv.phase2StartTranslate, root.progress);
228+ newTranslate = linearAnimation(startProgress, endProgress, startTranslate, priv.phase2StartTranslate, root.animatedProgress);
219229 } else {
220230 var startProgress = spreadView.positionMarker2 - (zIndex * spreadView.positionMarker2 / 2);
221231 var endProgress = spreadView.positionMarker4 - (zIndex * spreadView.tileDistance / spreadView.width);
222− newTranslate = linearAnimation(startProgress, endProgress, 0, priv.phase2StartTranslate, root.progress);
232+ newTranslate = linearAnimation(startProgress, endProgress, shadowOffset * Math.max(0, zIndex-2), priv.phase2StartTranslate, root.animatedProgress);
223233 }
224234 }
225235
226236 if (spreadView.phase == 2) {
227− newTranslate = -easingCurve.value * (spreadView.width - root.zIndex * animatedEndDistance);
237+ if (easingCurve.value == 0 && !nextInStack) {
238+ newTranslate = shadowOffset;
239+ } else {
240+ newTranslate = -easingCurve.value * (spreadView.width - root.zIndex * animatedEndDistance);
241+ }
228242 }
229243
230244 return newTranslate;
@@ -235,33 +249,30 @@ SpreadDelegate {
235249 return 1;
236250 }
237251
238− if (otherSelected) {
239− return selectedScale;
240− }
241−
242− if (isSelected) {
252+ // selected app or opposite stage active app.
253+ if (isSelected || (otherSelected && root.active && spreadView.selectedApplication && spreadView.selectedApplication.stage !== stage)) {
243254 return linearAnimation(selectedProgress, negativeProgress, selectedScale, 1, root.progress);
255+ } else if (otherSelected) {
256+ return selectedScale;
244257 }
245258
246259 if (spreadView.phase == 0) {
247260 if (nextInStack) {
248− if (model.stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
261+ if (stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
249262 return 1;
250263 } else {
251264 var targetScale = root.dragStartScale - ((root.dragStartScale - 1) * spreadView.snapPosition);
252265 return linearAnimation(0, spreadView.positionMarker2, root.dragStartScale, targetScale, root.animatedProgress);
253266 }
254− } else if (active) {
255− return 1;
256267 } else {
257− return linearAnimation(0, spreadView.positionMarker2, root.startScale, root.endScale, root.progress);
268+ return 1;
258269 }
259270 }
260271
261272 if (spreadView.phase == 1) {
262273 if (nextInStack) {
263274 var startScale = 1;
264− if (model.stage !== ApplicationInfoInterface.SideStage || spreadView.sideStageVisible) {
275+ if (stage !== ApplicationInfoInterface.SideStage || spreadView.sideStageVisible) {
265276 startScale = root.dragStartScale - ((root.dragStartScale - 1) * spreadView.snapPosition);
266277 }
267278 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, startScale, priv.phase2StartScale, root.animatedProgress);
@@ -283,21 +294,22 @@ SpreadDelegate {
283294 return 0;
284295 }
285296
286− if (otherSelected) {
287− return selectedAngle;
288− }
289− if (isSelected) {
297+ // selected app or opposite stage active app.
298+ if (isSelected || (otherSelected && root.active && spreadView.selectedApplication && spreadView.selectedApplication.stage !== stage)) {
290299 return linearAnimation(selectedProgress, negativeProgress, selectedAngle, 0, root.progress);
300+ } else if (otherSelected) {
301+ return selectedAngle;
291302 }
292303
293304 // The tile should rotate a bit when another one comes on top, but not when only dragging the side stage in
294− var shouldMoveAway = spreadView.nextInStack >= 0 && movedActive &&
305+ var shouldMoveAway = spreadView.nextInStack == -1 ||
306+ spreadView.nextInStack >= 0 && priv.movedActive &&
295307 (ApplicationManager.get(spreadView.nextInStack).stage === ApplicationInfoInterface.MainStage ||
296− model.stage == ApplicationInfoInterface.SideStage);
308+ stage == ApplicationInfoInterface.SideStage);
297309
298310 if (spreadView.phase == 0) {
299311 if (nextInStack) {
300− if (model.stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
312+ if (stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
301313 return 0;
302314 } else {
303315 return linearAnimation(0, spreadView.positionMarker2, root.startAngle, root.startAngle * (1-spreadView.snapPosition), root.animatedProgress);
@@ -309,7 +321,7 @@ SpreadDelegate {
309321 }
310322 if (spreadView.phase == 1) {
311323 if (nextInStack) {
312− if (model.stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
324+ if (stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
313325 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, 0, priv.phase2StartAngle, root.animatedProgress);
314326 } else {
315327 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, root.startAngle * (1-spreadView.snapPosition), priv.phase2StartAngle, root.animatedProgress);
@@ -328,31 +340,47 @@ SpreadDelegate {
328340 }
329341
330342 property real opacityTransform: {
331− if (otherSelected && spreadView.phase == 2) {
343+ // animate opacity for items not snapping into view.
344+ if (spreadView.phase !== 2) return 1;
345+ if (root.isSelected) return 1;
346+
347+ if (otherSelected) {
348+ if (root.active && spreadView.selectedApplication && spreadView.selectedApplication.stage !== stage) {
349+ return 1;
350+ }
332351 return linearAnimation(selectedProgress, negativeProgress, selectedOpacity, 0, root.progress);
333352 }
334−
335353 return 1;
336354 }
337355
338356 property real topMarginProgress: {
339− if (priv.isSelected) {
357+ // selected app or opposite stage active app.
358+ if (isSelected || (otherSelected && root.active && spreadView.selectedApplication && spreadView.selectedApplication.stage !== stage)) {
340359 return linearAnimation(selectedProgress, negativeProgress, selectedTopMarginProgress, 0, root.progress);
341360 }
342− switch (spreadView.phase) {
343− case 0:
361+
362+ if (spreadView.phase == 0) {
344363 return 0;
345− case 1:
346− return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
347− 0, priv.phase2StartTopMarginProgress, root.progress);
364+ } else if (spreadView.phase == 1) {
365+ if (nextInStack) {
366+ return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
367+ 0, 1, root.progress);
368+ }
369+ var startProgress = spreadView.positionMarker2 - (zIndex * spreadView.positionMarker2 / 2);
370+ var endProgress = spreadView.positionMarker4 - (zIndex * spreadView.tileDistance / spreadView.width);
371+ return linearAnimation(startProgress, endProgress, 0, 1, root.progress);
348372 }
349373 return 1;
350374 }
351375
352376 states: [
353377 State {
354− name: "sideStageDragging"; when: spreadView.sideStageDragging && root.isInSideStage
355− PropertyChanges { target: priv; xTranslate: -spreadView.sideStageWidth + spreadView.sideStageWidth * spreadView.sideStageDragProgress }
378+ name: "sideStage";
379+ when: root.isInSideStage && spreadView.shiftedContentX == 0 && spreadView.phase == 0
380+ PropertyChanges {
381+ target: priv;
382+ xTranslate: -spreadView.sideStageWidth + spreadView.sideStageWidth * (1-spreadView.sideStageDragProgress)
383+ }
356384 }
357385 ]
358386 }
qml/Stages/WindowResizeArea.qml+1−1
@@ -87,7 +87,7 @@ MouseArea {
8787 priv.updateNormalGeometry();
8888 }
8989
90− Component.onDestruction: {
90+ function saveWindowState() {
9191 windowStateStorage.saveState(root.windowId, target.state == "maximized" ? WindowStateStorage.WindowStateMaximized : WindowStateStorage.WindowStateNormal)
9292 windowStateStorage.saveGeometry(root.windowId, Qt.rect(priv.normalX, priv.normalY, priv.normalWidth, priv.normalHeight))
9393 }
qml/Stages/graphics/sidestage_drag.svgnew file+207
@@ -0,0 +1,207 @@
1+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2+<!-- Created with Inkscape (http://www.inkscape.org/) -->
3+
4+<svg
5+ xmlns:dc="http://purl.org/dc/elements/1.1/"
6+ xmlns:cc="http://creativecommons.org/ns#"
7+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
8+ xmlns:svg="http://www.w3.org/2000/svg"
9+ xmlns="http://www.w3.org/2000/svg"
10+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
11+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
12+ width="540"
13+ height="324"
14+ id="svg4874"
15+ version="1.1"
16+ inkscape:version="0.91+devel r"
17+ viewBox="0 0 540 324"
18+ sodipodi:docname="gesture_3f_touch.svg">
19+ <defs
20+ id="defs4876" />
21+ <sodipodi:namedview
22+ id="base"
23+ pagecolor="#292929"
24+ bordercolor="#666666"
25+ borderopacity="1.0"
26+ inkscape:pageopacity="1"
27+ inkscape:pageshadow="2"
28+ inkscape:zoom="1.1785993"
29+ inkscape:cx="-19.084613"
30+ inkscape:cy="11.707485"
31+ inkscape:document-units="px"
32+ inkscape:current-layer="g4780"
33+ showgrid="false"
34+ showborder="true"
35+ fit-margin-top="0"
36+ fit-margin-left="0"
37+ fit-margin-right="0"
38+ fit-margin-bottom="0"
39+ inkscape:snap-bbox="true"
40+ inkscape:bbox-paths="true"
41+ inkscape:bbox-nodes="true"
42+ inkscape:snap-bbox-edge-midpoints="true"
43+ inkscape:snap-bbox-midpoints="true"
44+ inkscape:object-paths="true"
45+ inkscape:snap-intersection-paths="true"
46+ inkscape:object-nodes="true"
47+ inkscape:snap-smooth-nodes="true"
48+ inkscape:snap-midpoints="true"
49+ inkscape:snap-object-midpoints="true"
50+ inkscape:snap-center="true"
51+ showguides="false"
52+ inkscape:guide-bbox="true"
53+ inkscape:snap-global="true"
54+ inkscape:snap-others="false"
55+ inkscape:snap-page="true">
56+ <inkscape:grid
57+ type="xygrid"
58+ id="grid5451"
59+ empspacing="8"
60+ originx="-2.7715014e-06"
61+ originy="-2.5124622e-06" />
62+ <sodipodi:guide
63+ orientation="1,0"
64+ position="7.9999972,-8.0000025"
65+ id="guide4063"
66+ inkscape:locked="false" />
67+ <sodipodi:guide
68+ orientation="1,0"
69+ position="3.9999972,-8.0000025"
70+ id="guide4065"
71+ inkscape:locked="false" />
72+ <sodipodi:guide
73+ orientation="0,1"
74+ position="-8.0000027,87.999998"
75+ id="guide4067"
76+ inkscape:locked="false" />
77+ <sodipodi:guide
78+ orientation="0,1"
79+ position="-8.0000027,91.999998"
80+ id="guide4069"
81+ inkscape:locked="false" />
82+ <sodipodi:guide
83+ orientation="0,1"
84+ position="104,3.9999975"
85+ id="guide4071"
86+ inkscape:locked="false" />
87+ <sodipodi:guide
88+ orientation="0,1"
89+ position="-5.0000027,7.9999975"
90+ id="guide4073"
91+ inkscape:locked="false" />
92+ <sodipodi:guide
93+ orientation="1,0"
94+ position="91.999996,-8.0000025"
95+ id="guide4075"
96+ inkscape:locked="false" />
97+ <sodipodi:guide
98+ orientation="1,0"
99+ position="87.999997,-8.0000025"
100+ id="guide4077"
101+ inkscape:locked="false" />
102+ <sodipodi:guide
103+ orientation="0,1"
104+ position="-8.0000027,83.999998"
105+ id="guide4074"
106+ inkscape:locked="false" />
107+ <sodipodi:guide
108+ orientation="1,0"
109+ position="11.999997,-8.0000025"
110+ id="guide4076"
111+ inkscape:locked="false" />
112+ <sodipodi:guide
113+ orientation="0,1"
114+ position="-5.0000027,11.999997"
115+ id="guide4078"
116+ inkscape:locked="false" />
117+ <sodipodi:guide
118+ orientation="1,0"
119+ position="83.999997,-9.0000025"
120+ id="guide4080"
121+ inkscape:locked="false" />
122+ <sodipodi:guide
123+ position="47.999997,-8.0000025"
124+ orientation="1,0"
125+ id="guide4170"
126+ inkscape:locked="false" />
127+ <sodipodi:guide
128+ position="-8.0000027,47.999997"
129+ orientation="0,1"
130+ id="guide4172"
131+ inkscape:locked="false" />
132+ </sodipodi:namedview>
133+ <metadata
134+ id="metadata4879">
135+ <rdf:RDF>
136+ <cc:Work
137+ rdf:about="">
138+ <dc:format>image/svg+xml</dc:format>
139+ <dc:type
140+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
141+ <dc:title></dc:title>
142+ </cc:Work>
143+ </rdf:RDF>
144+ </metadata>
145+ <g
146+ inkscape:label="Layer 1"
147+ inkscape:groupmode="layer"
148+ id="layer1"
149+ transform="translate(67.857143,149.49498)">
150+ <g
151+ transform="matrix(0,-1,-1,0,373.50506,516.50504)"
152+ id="g4845"
153+ style="display:inline">
154+ <g
155+ inkscape:export-ydpi="90"
156+ inkscape:export-xdpi="90"
157+ inkscape:export-filename="next01.png"
158+ transform="matrix(-0.9996045,0,0,1,575.94296,-611.00001)"
159+ id="g4778"
160+ inkscape:label="Layer 1">
161+ <g
162+ transform="matrix(-1,0,0,1,575.99999,611)"
163+ id="g4780"
164+ style="display:inline">
165+ <path
166+ 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"
167+ 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"
168+ id="path4845-6"
169+ inkscape:connector-curvature="0"
170+ sodipodi:nodetypes="ccccccccccccccccccccssscc" />
171+ <path
172+ 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"
173+ 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"
174+ id="ellipse4239-1"
175+ inkscape:connector-curvature="0" />
176+ <g
177+ style="display:inline"
178+ id="g5379"
179+ transform="matrix(-0.99999722,0,0,-0.99999722,645.25394,919.46958)">
180+ <path
181+ 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"
182+ id="path4116"
183+ sodipodi:type="arc"
184+ sodipodi:cx="808.72699"
185+ sodipodi:cy="244.79436"
186+ sodipodi:rx="250.36082"
187+ sodipodi:ry="263.63275"
188+ sodipodi:start="3.8048178"
189+ sodipodi:end="5.6286868"
190+ sodipodi:open="true"
191+ 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"
192+ inkscape:transform-center-x="-17.044876"
193+ inkscape:transform-center-y="-0.0015034578"
194+ transform="matrix(0,1,1,0,0,0)" />
195+ <path
196+ inkscape:transform-center-y="4.9738279"
197+ inkscape:transform-center-x="2.0443183"
198+ inkscape:connector-curvature="0"
199+ id="path4198"
200+ 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"
201+ 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" />
202+ </g>
203+ </g>
204+ </g>
205+ </g>
206+ </g>
207+</svg>
qml/Stages/graphics/sidestage_open.svgnew file+181
@@ -0,0 +1,181 @@
1+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2+<!-- Created with Inkscape (http://www.inkscape.org/) -->
3+
4+<svg
5+ xmlns:dc="http://purl.org/dc/elements/1.1/"
6+ xmlns:cc="http://creativecommons.org/ns#"
7+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
8+ xmlns:svg="http://www.w3.org/2000/svg"
9+ xmlns="http://www.w3.org/2000/svg"
10+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
11+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
12+ width="162"
13+ height="230"
14+ id="svg4874"
15+ version="1.1"
16+ inkscape:version="0.91+devel r"
17+ viewBox="0 0 162 230"
18+ sodipodi:docname="gesture_3f_tap.svg"
19+ style="shape-rendering:crispEdges">
20+ <defs
21+ id="defs4876" />
22+ <sodipodi:namedview
23+ id="base"
24+ pagecolor="#292929"
25+ bordercolor="#666666"
26+ borderopacity="1.0"
27+ inkscape:pageopacity="1"
28+ inkscape:pageshadow="2"
29+ inkscape:zoom="1.8415614"
30+ inkscape:cx="162.13161"
31+ inkscape:cy="116.01485"
32+ inkscape:document-units="px"
33+ inkscape:current-layer="g4780"
34+ showgrid="false"
35+ showborder="true"
36+ fit-margin-top="0"
37+ fit-margin-left="0"
38+ fit-margin-right="0"
39+ fit-margin-bottom="0"
40+ inkscape:snap-bbox="true"
41+ inkscape:bbox-paths="true"
42+ inkscape:bbox-nodes="true"
43+ inkscape:snap-bbox-edge-midpoints="true"
44+ inkscape:snap-bbox-midpoints="true"
45+ inkscape:object-paths="true"
46+ inkscape:snap-intersection-paths="true"
47+ inkscape:object-nodes="true"
48+ inkscape:snap-smooth-nodes="true"
49+ inkscape:snap-midpoints="true"
50+ inkscape:snap-object-midpoints="true"
51+ inkscape:snap-center="true"
52+ showguides="false"
53+ inkscape:guide-bbox="true"
54+ inkscape:snap-global="false"
55+ inkscape:snap-others="false"
56+ inkscape:snap-page="true">
57+ <inkscape:grid
58+ type="xygrid"
59+ id="grid5451"
60+ empspacing="8"
61+ originx="-2.7715014e-06"
62+ originy="-2.5124622e-06" />
63+ <sodipodi:guide
64+ orientation="1,0"
65+ position="7.9999972,-8.0000026"
66+ id="guide4063"
67+ inkscape:locked="false" />
68+ <sodipodi:guide
69+ orientation="1,0"
70+ position="3.9999972,-8.0000026"
71+ id="guide4065"
72+ inkscape:locked="false" />
73+ <sodipodi:guide
74+ orientation="0,1"
75+ position="-8.0000027,87.999998"
76+ id="guide4067"
77+ inkscape:locked="false" />
78+ <sodipodi:guide
79+ orientation="0,1"
80+ position="-8.0000027,91.999998"
81+ id="guide4069"
82+ inkscape:locked="false" />
83+ <sodipodi:guide
84+ orientation="0,1"
85+ position="104,3.9999975"
86+ id="guide4071"
87+ inkscape:locked="false" />
88+ <sodipodi:guide
89+ orientation="0,1"
90+ position="-5.0000027,7.9999975"
91+ id="guide4073"
92+ inkscape:locked="false" />
93+ <sodipodi:guide
94+ orientation="1,0"
95+ position="91.999997,-8.0000026"
96+ id="guide4075"
97+ inkscape:locked="false" />
98+ <sodipodi:guide
99+ orientation="1,0"
100+ position="87.999997,-8.0000026"
101+ id="guide4077"
102+ inkscape:locked="false" />
103+ <sodipodi:guide
104+ orientation="0,1"
105+ position="-8.0000027,83.999998"
106+ id="guide4074"
107+ inkscape:locked="false" />
108+ <sodipodi:guide
109+ orientation="1,0"
110+ position="11.999997,-8.0000026"
111+ id="guide4076"
112+ inkscape:locked="false" />
113+ <sodipodi:guide
114+ orientation="0,1"
115+ position="-5.0000027,11.999997"
116+ id="guide4078"
117+ inkscape:locked="false" />
118+ <sodipodi:guide
119+ orientation="1,0"
120+ position="83.999997,-9.0000026"
121+ id="guide4080"
122+ inkscape:locked="false" />
123+ <sodipodi:guide
124+ position="47.999997,-8.0000026"
125+ orientation="1,0"
126+ id="guide4170"
127+ inkscape:locked="false" />
128+ <sodipodi:guide
129+ position="-8.0000027,47.999997"
130+ orientation="0,1"
131+ id="guide4172"
132+ inkscape:locked="false" />
133+ </sodipodi:namedview>
134+ <metadata
135+ id="metadata4879">
136+ <rdf:RDF>
137+ <cc:Work
138+ rdf:about="">
139+ <dc:format>image/svg+xml</dc:format>
140+ <dc:type
141+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
142+ <dc:title></dc:title>
143+ </cc:Work>
144+ </rdf:RDF>
145+ </metadata>
146+ <g
147+ inkscape:label="Layer 1"
148+ inkscape:groupmode="layer"
149+ id="layer1"
150+ transform="translate(67.857143,55.494966)">
151+ <g
152+ transform="matrix(0,-1,-1,0,373.50506,516.50504)"
153+ id="g4845"
154+ style="display:inline">
155+ <g
156+ inkscape:export-ydpi="90"
157+ inkscape:export-xdpi="90"
158+ inkscape:export-filename="next01.png"
159+ transform="matrix(-0.9996045,0,0,1,575.94296,-611.00001)"
160+ id="g4778"
161+ inkscape:label="Layer 1">
162+ <g
163+ transform="matrix(-1,0,0,1,575.99999,611)"
164+ id="g4780"
165+ style="display:inline">
166+ <path
167+ 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"
168+ 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"
169+ id="path4845-6"
170+ inkscape:connector-curvature="0"
171+ sodipodi:nodetypes="ccccccccccccccccccccssscc" />
172+ <path
173+ style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.5;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#f7f7f7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3.99999976;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
174+ d="m 571.45563,383.2743 c -0.0175,12.04505 -6.22338,23.19765 -16.35804,29.64062 -3.3052,16.24493 -17.57029,28.04101 -34.23815,28.04493 -18.13447,4.3e-4 -33.08059,-13.83538 -34.83995,-31.50586 l 4.16766,1.11523 c 2.22233,14.95164 15.08799,26.39099 30.67229,26.39063 15.06147,-0.004 27.92054,-10.79228 30.53552,-25.61915 l 0.15631,-0.89648 0.78156,-0.46875 c 9.37156,-5.64255 15.10396,-15.76543 15.12121,-26.70117 v -0.004 c -0.0108,-9.30873 -4.17362,-18.12357 -11.35605,-24.04883 l -0.8265,-0.68359 0.10942,-1.06641 c 0.10013,-0.96319 0.1562,-1.9301 0.16608,-2.89843 -0.004,-17.20453 -13.91519,-31.10752 -31.12755,-31.10743 -10.37812,-5e-5 -19.55027,5.05891 -25.20333,12.84375 l -4.0856,-1.09375 c 6.29481,-9.48451 17.06827,-15.75006 29.28893,-15.75 19.37764,-1e-4 35.12924,15.74527 35.12913,35.11524 v 0.01 0.01 c -0.008,0.77822 -0.0716,1.55426 -0.13091,2.33008 7.57765,6.66645 12.02745,16.21382 12.03797,26.33984 v 0.002 z m -74.3634,2.80273 c -0.004,0.005 -0.008,0.0106 -0.0117,0.0156 l -4.15595,-1.11328 c 0.003,-0.004 0.006,-0.009 0.01,-0.0137 z m -3.51507,-27.4082 -4.14421,-1.10938 c -1.8e-4,-0.002 1.7e-4,-0.004 0,-0.006 l 4.14226,1.10937 c 2.7e-4,0.002 0.002,0.004 0.002,0.006 z"
175+ id="ellipse4488"
176+ inkscape:connector-curvature="0" />
177+ </g>
178+ </g>
179+ </g>
180+ </g>
181+</svg>
qml/Wizard/CheckableSetting.qml+10−2
@@ -31,9 +31,11 @@ ListItem.Empty {
3131 signal linkActivated(string link)
3232
3333 implicitHeight: Math.max(label.height, checkBox.height)
34+ highlightWhenPressed: false
3435
35− Item {
36+ FocusScope {
3637 anchors.fill: parent
38+ focus: true
3739
3840 CheckBox {
3941 id: checkBox
@@ -77,8 +79,14 @@ ListItem.Empty {
7779 rightMargin: listItem.rightMargin
7880 }
7981 wrapMode: Text.Wrap
80− linkColor: theme.palette.normal.foregroundText
82+ linkColor: UbuntuColors.orange
83+ color: textColor
84+ font.weight: Font.Light
85+ fontSize: "small"
86+ lineHeight: 1.2
8187 onLinkActivated: listItem.linkActivated(link)
8288 }
89+
90+ Keys.onSpacePressed: checkBox.trigger()
8391 }
8492}
qml/Wizard/Page.qml+260−44
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013,2015 Canonical, Ltd.
2+ * Copyright (C) 2013-2016 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -16,14 +16,33 @@
1616
1717import QtQuick 2.4
1818import Ubuntu.Components 1.3
19+import Wizard 0.1
1920
2021Item {
21− readonly property real buttonMargin: units.gu(2)
22+ readonly property real maximumContentWidth: units.gu(50)
23+ readonly property bool wideMode: width > units.gu(90) && width > height
24+ readonly property bool contentAnimationRunning: contentAnimation.running
25+
26+ readonly property real buttonMargin: units.gu(3)
2227 readonly property real buttonWidth: (width - buttonMargin * 2) / 2 -
2328 buttonMargin / 2
24− readonly property real topMargin: units.gu(8)
25− readonly property real leftMargin: units.gu(2)
26− readonly property real rightMargin: units.gu(2)
29+ readonly property real buttonBarHeight: units.gu(5)
30+ readonly property real titleRectHeight: customTitle ? units.gu(10) : units.gu(16)
31+
32+ readonly property real topMargin: units.gu(11)
33+ readonly property real bottomMargin: units.gu(3)
34+ readonly property real leftMargin: Math.max(units.gu(3), (width - maximumContentWidth) / 3)
35+ readonly property real rightMargin: leftMargin
36+ readonly property real customMargin: wideMode ? units.gu(8) : units.gu(4) // margin for the content
37+ readonly property real staticMargin: units.gu(3)
38+
39+ // colors
40+ readonly property color backgroundColor: "#fdfdfd"
41+ readonly property color dividerColor: UbuntuColors.silk
42+ readonly property color textColor: UbuntuColors.slate
43+ readonly property color errorColor: theme.palette.normal.negative
44+ readonly property color okColor: theme.palette.normal.positive
45+ readonly property color whiteColor: "white" // workaround for a UITK bug
2746
2847 // If you want to skip a page, mark skipValid false while you figure out
2948 // whether to skip, then set it to true once you've determined the value
@@ -32,9 +51,13 @@ Item {
3251 property bool skip: false
3352
3453 property bool hasBackButton: true
54+ property string backButtonText: i18n.ctr("Button: Go back one page in the Wizard", "Back")
3555 property bool customBack: false
56+ property bool customTitle: false
3657 property alias forwardButtonSourceComponent: forwardButton.sourceComponent
3758 property alias content: contentHolder
59+ property bool lastPage: false
60+ property bool buttonBarVisible: true
3861
3962 property string title: ""
4063
@@ -43,66 +66,259 @@ Item {
4366 visible: false
4467 anchors.fill: parent
4568
46− // We want larger than even fontSize: "x-large", so we use a Text instead
47− // of a Label.
48− Text {
49− id: titleLabel
69+ Timer {
70+ id: indicatorTimer
71+ interval: 1000
72+ triggeredOnStart: true
73+ repeat: true
74+ running: System.wizardEnabled
75+ onTriggered: {
76+ indicatorTime.text = Qt.formatTime(new Date(), "h:mm")
77+ }
78+ }
79+
80+ // page header
81+ Image {
82+ id: titleRect
83+ visible: !lastPage
5084 anchors {
5185 top: parent.top
5286 left: parent.left
5387 right: parent.right
54− topMargin: topMargin
55− leftMargin: leftMargin
56− rightMargin: rightMargin
5788 }
58− wrapMode: Text.Wrap
59− text: title
60− color: theme.palette.normal.baseText
61− font.pixelSize: units.gu(4)
89+ source: customTitle ? "" : (wideMode ? "Pages/data/Desktop_header_bkg.png" : "Pages/data/Phone_header_bkg.png")
90+ height: titleRectHeight
91+ clip: true
92+
93+ // page title
94+ Label {
95+ id: titleLabel
96+ property real animatedTopMargin: 0
97+ anchors {
98+ left: parent.left
99+ right: parent.right
100+ bottom: parent.bottom
101+ bottomMargin: bottomMargin
102+ leftMargin: staticMargin
103+ rightMargin: rightMargin
104+ topMargin: titleLabel.animatedTopMargin
105+ }
106+ text: title
107+ color: customTitle ? textColor : backgroundColor
108+ fontSize: customTitle ? "large" : "x-large"
109+ font.weight: Font.Light
110+ wrapMode: Text.WordWrap
111+ maximumLineCount: 2
112+ elide: Text.ElideRight
113+ }
114+
115+ // indicators
116+ Row {
117+ id: indicatorRow
118+ visible: !customTitle
119+ anchors {
120+ top: parent.top
121+ right: parent.right
122+ topMargin: units.gu(.5)
123+ rightMargin: units.gu(.5)
124+ }
125+ height: units.gu(2)
126+ spacing: units.gu(1)
127+
128+ Icon {
129+ id: indicatorSim
130+ anchors.verticalCenter: parent.verticalCenter
131+ name: "no-simcard"
132+ height: parent.height
133+ width: height
134+ visible: !(root.simManager0.present || root.simManager1.present) && root.modemManager.modems.length > 0
135+ color: "white"
136+ }
137+
138+ Icon {
139+ id: indicatorNet
140+ anchors.verticalCenter: parent.verticalCenter
141+ name: Status.networkIcon
142+ height: parent.height
143+ width: height
144+ color: "white"
145+ }
146+
147+ Icon {
148+ id: indicatorBattery
149+ anchors.verticalCenter: parent.verticalCenter
150+ name: Status.batteryIcon
151+ height: parent.height
152+ width: height * 1.4 // the battery icon is not rectangular :/
153+ color: "white"
154+ }
155+
156+ Label {
157+ id: indicatorTime
158+ anchors.verticalCenter: parent.verticalCenter
159+ color: whiteColor
160+ fontSize: "small"
161+ }
162+ }
62163 }
63164
165+ // content
64166 Item {
65167 id: contentHolder
168+ property real animatedMargin: 0
169+ property real animatedTopMargin: 0
66170 anchors {
67− top: titleLabel.bottom
171+ top: titleRect.bottom
68172 left: parent.left
69173 right: parent.right
70− bottom: backButton.top
71− topMargin: units.gu(4)
72− leftMargin: leftMargin
73− rightMargin: rightMargin
74− bottomMargin: buttonMargin
174+ bottom: buttonBarVisible ? buttonRect.top : parent.bottom
175+ leftMargin: content.animatedMargin
176+ rightMargin: -content.animatedMargin
177+ topMargin: content.animatedTopMargin
75178 }
179+ visible: opacity > 0
76180 }
77181
78− StackButton {
79− id: backButton
80− objectName: "backButton"
81− width: buttonWidth
182+ // button bar
183+ Rectangle {
184+ id: buttonRect
185+ visible: buttonBarVisible
82186 anchors {
83− left: parent.left
84187 bottom: parent.bottom
85− leftMargin: buttonMargin
86− bottomMargin: buttonMargin
188+ bottomMargin: Qt.inputMethod.visible ? Qt.inputMethod.keyboardRectangle.height : 0
189+ left: parent.left
190+ right: parent.right
87191 }
88− z: 1
89− text: i18n.ctr("Button: Go back one page in the Wizard", "Back")
90− visible: pageStack.depth > 1 && hasBackButton
91− backArrow: true
192+ height: buttonBarHeight
193+ color: "#f5f5f5"
194+ z: content.z + 1
92195
93− onClicked: customBack ? backClicked() : pageStack.prev()
196+ StackButton {
197+ id: backButton
198+ objectName: "backButton"
199+ width: buttonWidth
200+ anchors {
201+ left: parent.left
202+ bottom: parent.bottom
203+ leftMargin: staticMargin
204+ verticalCenter: parent.verticalCenter
205+ }
206+ text: backButtonText
207+ visible: pageStack.depth > 1 && hasBackButton
208+ backArrow: true
209+
210+ onClicked: customBack ? backClicked() : pageStack.prev()
211+ }
212+
213+ Loader {
214+ id: forwardButton
215+ objectName: "forwardButton"
216+ width: buttonWidth
217+ anchors {
218+ right: parent.right
219+ bottom: parent.bottom
220+ rightMargin: staticMargin
221+ verticalCenter: parent.verticalCenter
222+ }
223+ }
94224 }
95225
96− Loader {
97− id: forwardButton
98− objectName: "forwardButton"
99− width: buttonWidth
100− anchors {
101− right: parent.right
102− bottom: parent.bottom
103− rightMargin: buttonMargin
104− bottomMargin: buttonMargin
226+ // transitions
227+ function aboutToShow(duration, direction) {
228+ startContentAnimation(duration, direction)
229+ startControlsAnimation(duration)
230+ }
231+
232+ function aboutToShowSecondary(duration) {
233+ secondaryAnimation.restart()
234+ startControlsAnimation(duration)
235+ }
236+
237+ function startContentAnimation(duration, direction) {
238+ contentAnimation.animationDurationBase = duration
239+ contentAnimation.direction = direction
240+ contentAnimation.restart()
241+ }
242+
243+ function startControlsAnimation(showDuration) {
244+ actionsShowAnimation.showDuration = showDuration
245+ actionsShowAnimation.restart()
246+ }
247+
248+ SequentialAnimation { // animation for the button bar
249+ id: actionsShowAnimation
250+ property int showDuration: 0
251+ PropertyAction {
252+ target: buttonRect
253+ property: 'opacity'
254+ value: 0
255+ }
256+ PauseAnimation { duration: Math.max(0, actionsShowAnimation.showDuration - UbuntuAnimation.SnapDuration) }
257+ NumberAnimation {
258+ target: buttonRect
259+ property: 'opacity'
260+ to: 1
261+ duration: UbuntuAnimation.SnapDuration
262+ }
263+ }
264+
265+ SequentialAnimation { // animations for the content
266+ id: contentAnimation
267+ property int animationDurationBase: UbuntuAnimation.BriskDuration
268+ readonly property int additionalDuration: 200
269+ property int direction: Qt.LeftToRight
270+ ScriptAction { // direction of the effect
271+ script: {
272+ if (contentAnimation.direction === Qt.LeftToRight) {
273+ content.animatedMargin = -content.width;
274+ } else {
275+ content.animatedMargin = content.width;
276+ }
277+ }
278+ }
279+ ParallelAnimation {
280+ NumberAnimation { // the slide-in animation
281+ targets: content
282+ property: 'animatedMargin'
283+ to: 0
284+ duration: contentAnimation.animationDurationBase + contentAnimation.additionalDuration
285+ easing.type: Easing.OutCubic
286+ }
287+ NumberAnimation { // opacity animation
288+ targets: [titleLabel,content]
289+ property: 'opacity'
290+ from: 0
291+ to: 1
292+ duration: contentAnimation.animationDurationBase
293+ }
294+ }
295+ }
296+
297+ ParallelAnimation { // animation for the secondary pages
298+ id: secondaryAnimation
299+
300+ NumberAnimation { // the slide-up animation
301+ target: titleLabel
302+ property: 'animatedTopMargin'
303+ from: content.height
304+ to: customMargin
305+ duration: UbuntuAnimation.BriskDuration
306+ easing: UbuntuAnimation.StandardEasing
307+ }
308+ NumberAnimation {
309+ target: content
310+ property: 'animatedTopMargin'
311+ from: content.height
312+ to: 0
313+ duration: UbuntuAnimation.BriskDuration
314+ easing: UbuntuAnimation.StandardEasing
315+ }
316+ NumberAnimation { // opacity animation
317+ target: content
318+ property: 'opacity'
319+ from: 0
320+ to: 1
321+ duration: UbuntuAnimation.BriskDuration
105322 }
106− z: 1
107323 }
108324}
qml/Wizard/Pages.qml+75−31
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013,2014,2015 Canonical, Ltd.
2+ * Copyright (C) 2013-2016 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -15,40 +15,77 @@
1515 */
1616
1717import QtQuick 2.4
18+import MeeGo.QOfono 0.2
1819import Ubuntu.Components 1.3
1920import Ubuntu.SystemSettings.SecurityPrivacy 1.0
21+import Ubuntu.SystemSettings.Diagnostics 1.0
2022import Wizard 0.1
2123import "../Components"
2224
23−Item {
25+StyledItem {
2426 id: root
2527 objectName: "wizardPages"
2628 focus: true
2729
28− // The background wallpaper to use
29− property string background
30−
3130 signal quit()
3231
3332 // These should be set by a security page and we apply the settings when
3433 // the user exits the wizard.
35− property int passwordMethod: UbuntuSecurityPrivacyPanel.Passcode
34+ property int passwordMethod: UbuntuSecurityPrivacyPanel.Passphrase
3635 property string password: ""
3736
37+ property bool seenSIMPage: false // we want to see the SIM page at most once
38+
39+ property alias modemManager: modemManager
40+ property alias simManager0: simManager0
41+ property alias simManager1: simManager1
42+
43+ theme: ThemeSettings {
44+ name: "Ubuntu.Components.Themes.Ambiance"
45+ }
46+
3847 UbuntuSecurityPrivacyPanel {
3948 id: securityPrivacy
4049 objectName: "securityPrivacy"
4150 }
4251
52+ UbuntuDiagnostics {
53+ id: diagnostics
54+ objectName: "diagnostics"
55+ }
56+
57+ OfonoManager { // need it here for the language and country detection
58+ id: modemManager
59+ readonly property bool gotSimCard: available && ((simManager0.ready && simManager0.present) || (simManager1.ready && simManager1.present))
60+ property bool ready: false
61+ onModemsChanged: {
62+ ready = true;
63+ }
64+ }
65+
66+ // Ideally we would query the system more cleverly than hardcoding two
67+ // sims. But we don't yet have a more clever way. :(
68+ OfonoSimManager {
69+ id: simManager0
70+ modemPath: modemManager.modems.length >= 1 ? modemManager.modems[0] : ""
71+ }
72+
73+ OfonoSimManager {
74+ id: simManager1
75+ modemPath: modemManager.modems.length >= 2 ? modemManager.modems[1] : ""
76+ }
77+
4378 function quitWizard() {
4479 pageStack.currentPage.enabled = false;
4580
46− var errorMsg = securityPrivacy.setSecurity("", password, passwordMethod)
47− if (errorMsg !== "") {
48− // Ignore (but log) any errors, since we're past where the user set
49− // the method. Worst case, we just leave the user with a swipe
50− // security method and they fix it in the system settings.
51− console.log("Error setting security method:", errorMsg)
81+ if (password != "") {
82+ var errorMsg = securityPrivacy.setSecurity("", password, passwordMethod)
83+ if (errorMsg !== "") {
84+ // Ignore (but log) any errors, since we're past where the user set
85+ // the method. Worst case, we just leave the user with a swipe
86+ // security method and they fix it in the system settings.
87+ console.log("Error setting security method:", errorMsg)
88+ }
5289 }
5390
5491 quit();
@@ -58,19 +95,10 @@ Item {
5895 anchors.fill: parent
5996 }
6097
61− Image {
62− id: image
63− // Use x/y/height/width instead of anchors so that we don't adjust
64− // the image when the OSK appears.
65− x: 0
66− y: 0
67− height: root.height
68− width: root.width
69− sourceSize.height: height
70− sourceSize.width: width
71− source: root.background
72− fillMode: Image.PreserveAspectCrop
73− visible: status === Image.Ready
98+ Rectangle {
99+ id: background
100+ anchors.fill: root
101+ color: "#fdfdfd"
74102 }
75103
76104 PageList {
@@ -86,18 +114,26 @@ Item {
86114 // If we've opened any extra (non-main) pages, pop them before
87115 // continuing so back button returns to the previous main page.
88116 while (pageList.index < pageStack.depth - 1)
89− pop()
90− load(pageList.next())
117+ pop();
118+ load(pageList.next());
91119 }
92120
93121 function prev() {
94− if (pageList.index >= pageStack.depth - 1)
95− pageList.prev() // update pageList.index, but not for extra pages
122+ var isPrimaryPage = currentPage && !currentPage.customTitle;
123+ if (pageList.index >= pageStack.depth - 1) {
124+ pageList.prev(); // update pageList.index, but not for extra pages
125+ }
96126 pop()
97127 if (!currentPage || currentPage.opacity === 0) { // undo skipped pages
98− prev()
128+ prev();
99129 } else {
100− currentPage.enabled = true
130+ currentPage.enabled = true;
131+ }
132+
133+ if (isPrimaryPage) {
134+ currentPage.aboutToShow(UbuntuAnimation.BriskDuration, Qt.LeftToRight);
135+ } else {
136+ currentPage.aboutToShowSecondary(UbuntuAnimation.BriskDuration);
101137 }
102138 }
103139
@@ -115,6 +151,13 @@ Item {
115151 // Check for immediate skip or not. We may have to wait for
116152 // skipValid to be assigned (see Connections object below)
117153 checkSkip()
154+
155+ var isPrimaryPage = !currentPage.customTitle;
156+ if (isPrimaryPage) {
157+ currentPage.aboutToShow(UbuntuAnimation.BriskDuration, Qt.RightToLeft);
158+ } else {
159+ currentPage.aboutToShowSecondary(UbuntuAnimation.BriskDuration);
160+ }
118161 }
119162
120163 function checkSkip() {
@@ -136,6 +179,7 @@ Item {
136179 objectName: "timeout"
137180 interval: 2000 // wizard pages shouldn't take long
138181 onTriggered: {
182+ console.warn("Wizard page " + pageStack.currentPage.objectName + " skipped due to taking too long!!!");
139183 pageStack.currentPage.skip = true;
140184 pageStack.currentPage.skipValid = true;
141185 }
qml/Wizard/Pages/10-welcome.qml+121−48
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013-2015 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -24,60 +24,124 @@ import ".." as LocalComponents
2424LocalComponents.Page {
2525 objectName: "languagePage"
2626
27− title: i18n.tr("Hi!")
27+ title: i18n.tr("Language")
2828 forwardButtonSourceComponent: forwardButton
2929
3030 UbuntuLanguagePlugin {
3131 id: plugin
3232 }
3333
34− Column {
35− id: column
36− anchors.fill: content
37− spacing: units.gu(1)
38−
39− Label {
40− id: label1
41− anchors.left: parent.left
42− anchors.right: parent.right
43− wrapMode: Text.Wrap
44− text: i18n.tr("Welcome to your Ubuntu device.")
34+ function init()
35+ {
36+ var detectedLang = "";
37+ // try to detect the language+country from the SIM card
38+ if (root.simManager0.present && root.simManager0.preferredLanguages.length > 0) {
39+ detectedLang = root.simManager0.preferredLanguages[0] + "_" + LocalePlugin.mccToCountryCode(root.simManager0.mobileCountryCode);
40+ } else if (root.simManager1.present && root.simManager1.preferredLanguages.length > 0) {
41+ detectedLang = root.simManager1.preferredLanguages[0] + "_" + LocalePlugin.mccToCountryCode(root.simManager1.mobileCountryCode);
42+ } else if (plugin.currentLanguage != -1) {
43+ detectedLang = plugin.languageCodes[plugin.currentLanguage].split(".")[0]; // remove the encoding part, after dot (en_US.utf8 -> en_US)
44+ } else {
45+ detectedLang = "en_US"; // fallback to default lang
4546 }
4647
47− Label {
48− id: label2
49− anchors.left: parent.left
50− anchors.right: parent.right
51− wrapMode: Text.Wrap
52− text: i18n.tr("Let’s get started.")
48+ // preselect the detected language
49+ for (var i = 0; i < plugin.languageCodes.length; i++) {
50+ var code = plugin.languageCodes[i].split(".")[0]; // remove the encoding part, after dot (en_US.utf8 -> en_US)
51+ if (detectedLang === code) {
52+ languagesListView.currentIndex = i;
53+ languagesListView.positionViewAtIndex(i, ListView.Center);
54+ i18n.language = plugin.languageCodes[i];
55+ break;
56+ }
57+ }
58+ }
59+
60+ // splash screen (this has to be on the first page)
61+ Image {
62+ id: splashImage
63+ anchors.top: parent.top
64+ anchors.left: parent.left
65+ anchors.right: parent.right
66+ height: parent.height
67+ source: wideMode ? "data/Desktop_splash_screen_bkg.png" : "data/Phone_splash_screen_bkg.png"
68+ fillMode: Image.PreserveAspectCrop
69+ z: 2
70+ visible: opacity > 0
71+ Component.onCompleted: splashAnimation.start()
72+ }
73+
74+ SequentialAnimation {
75+ id: splashAnimation
76+ PauseAnimation { duration: UbuntuAnimation.BriskDuration }
77+ SmoothedAnimation {
78+ target: splashImage
79+ property: "height"
80+ to: units.gu(16)
81+ duration: UbuntuAnimation.BriskDuration
5382 }
83+ NumberAnimation {
84+ target: splashImage
85+ property: 'opacity'
86+ from: 1
87+ to: 0
88+ }
89+ onStopped: init();
90+ }
91+
92+ ListView {
93+ id: languagesListView
94+ clip: true
95+ snapMode: ListView.SnapToItem
5496
55− Item { // spacer
56− height: units.gu(2)
57− width: units.gu(1) // needed else it will be ignored
97+ anchors {
98+ fill: content
99+ leftMargin: wideMode ? parent.leftMargin : 0
100+ rightMargin: wideMode ? parent.rightMargin : 0
101+ topMargin: wideMode ? parent.customMargin : 0
58102 }
59103
60− ComboButton {
61− id: combo
62− objectName: "languageCombo"
63− anchors.left: parent.left
64− anchors.right: parent.right
65− text: listview.currentItem.text
66− onClicked: expanded = !expanded
67− expandedHeight: column.height - combo.y
68− UbuntuListView {
69− id: listview
70− model: plugin.languageNames
71− currentIndex: plugin.currentLanguage
72− delegate: Standard {
73− objectName: "languageDelegate" + index
74− text: modelData
75− onClicked: {
76− listview.currentIndex = index
77− combo.expanded = false
78− i18n.language = plugin.languageCodes[index]
79− }
104+ model: plugin.languageNames
105+
106+ delegate: ListItem {
107+ id: itemDelegate
108+ objectName: "languageDelegate" + index
109+ highlightColor: backgroundColor
110+ divider.colorFrom: dividerColor
111+ divider.colorTo: backgroundColor
112+ readonly property bool isCurrent: index === ListView.view.currentIndex
113+
114+ Label {
115+ id: langLabel
116+ text: modelData
117+
118+ anchors {
119+ left: parent.left
120+ verticalCenter: parent.verticalCenter
121+ leftMargin: languagesListView.anchors.leftMargin == 0 ? staticMargin : 0
80122 }
123+
124+ fontSize: "medium"
125+ font.weight: itemDelegate.isCurrent ? Font.Normal : Font.Light
126+ color: textColor
127+ }
128+
129+ Image {
130+ anchors {
131+ right: parent.right;
132+ verticalCenter: parent.verticalCenter;
133+ rightMargin: languagesListView.anchors.rightMargin == 0 ? staticMargin : 0
134+ }
135+ fillMode: Image.PreserveAspectFit
136+ height: units.gu(1.5)
137+
138+ source: "data/Tick@30.png"
139+ visible: itemDelegate.isCurrent
140+ }
141+
142+ onClicked: {
143+ languagesListView.currentIndex = index;
144+ i18n.language = plugin.languageCodes[index];
81145 }
82146 }
83147 }
@@ -85,14 +149,23 @@ LocalComponents.Page {
85149 Component {
86150 id: forwardButton
87151 LocalComponents.StackButton {
88− text: i18n.tr("Continue")
152+ text: i18n.tr("Next")
153+ enabled: languagesListView.currentIndex != -1
89154 onClicked: {
90− if (plugin.currentLanguage !== listview.currentIndex) {
91− plugin.currentLanguage = listview.currentIndex;
92− System.updateSessionLanguage(plugin.languageCodes[listview.currentIndex]);
93− i18n.language = i18n.language; // re-notify of change after above call (for qlocale change)
155+ if (plugin.currentLanguage !== languagesListView.currentIndex) {
156+ plugin.currentLanguage = languagesListView.currentIndex;
157+ System.updateSessionLocale(plugin.languageCodes[plugin.currentLanguage]);
158+ }
159+ i18n.language = plugin.languageCodes[plugin.currentLanguage]; // re-notify of change after above call (for qlocale change)
160+
161+ if (!root.modemManager.available || !root.modemManager.ready || root.modemManager.modems.length === 0 ||
162+ (root.simManager0.present && root.simManager0.ready) || (root.simManager1.present && root.simManager1.ready) ||
163+ root.seenSIMPage) { // go to next page
164+ pageStack.next();
165+ }
166+ else {
167+ pageStack.load(Qt.resolvedUrl("sim.qml")); // show the SIM page
94168 }
95− pageStack.next()
96169 }
97170 }
98171 }
qml/Wizard/Pages/20-sim.qmldeleted−77
@@ -1,77 +0,0 @@
1−/*
2− * Copyright (C) 2013 Canonical, Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import QtQuick 2.4
18−import MeeGo.QOfono 0.2
19−import Ubuntu.Components 1.3
20−import ".." as LocalComponents
21−
22−LocalComponents.Page {
23− objectName: "simPage"
24−
25− title: i18n.tr("Add a SIM card and restart your device")
26− forwardButtonSourceComponent: forwardButton
27−
28− skipValid: !manager.available ||
29− (manager.ready && (manager.modems.length < 1 || simManager0.ready)
30− && (manager.modems.length < 2 || simManager1.ready))
31− skip: !manager.available || manager.modems.length === 0 || simManager0.present || simManager1.present
32−
33− OfonoManager {
34− id: manager
35− property bool ready: false
36− onModemsChanged: ready = true
37− }
38−
39− // Ideally we would query the system more cleverly than hardcoding two
40− // modems. But we don't yet have a more clever way. :(
41− OfonoSimManager {
42− id: simManager0
43− modemPath: manager.modems.length >= 1 ? manager.modems[0] : ""
44− }
45−
46− OfonoSimManager {
47− id: simManager1
48− modemPath: manager.modems.length >= 2 ? manager.modems[1] : ""
49− }
50−
51− Column {
52− anchors.fill: content
53− spacing: units.gu(4)
54−
55− Label {
56− anchors.left: parent.left
57− anchors.right: parent.right
58− wrapMode: Text.Wrap
59− text: i18n.tr("Without it, you won’t be able to make calls or use text messaging.")
60− }
61−
62− Image {
63− id: image
64− source: "data/meet_ubuntu_simcard@30.png"
65− height: units.gu(6.5)
66− width: units.gu(9)
67− }
68− }
69−
70− Component {
71− id: forwardButton
72− LocalComponents.StackButton {
73− text: i18n.tr("Skip")
74− onClicked: pageStack.next()
75− }
76− }
77−}
qml/Wizard/Pages/30-passwd-type.qmldeleted−140
@@ -1,140 +0,0 @@
1−/*
2− * Copyright (C) 2014,2015 Canonical, Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import QtQuick 2.4
18−import Ubuntu.Components 1.3
19−import Ubuntu.Components.ListItems 1.3
20−import Ubuntu.SystemSettings.SecurityPrivacy 1.0
21−import ".." as LocalComponents
22−
23−/**
24− * One quirk with this page: we don't actually set the password. We avoid
25− * doing it here because the user can come back to this page and change their
26− * answer. We don't run as root, so if we did set the password immediately,
27− * we'd need to prompt for their previous password when they came back and
28− * changed their answer. Which is silly UX. So instead, we just keep track
29− * of their choice and set the password at the end (see main.qml).
30− * Setting the password shouldn't fail, since Ubuntu Touch has loose password
31− * requirements, but we'll check what we can here. Ideally we'd be able to ask
32− * the system if a password is legal without actually setting that password.
33− */
34−
35−LocalComponents.Page {
36− id: passwdPage
37− objectName: "passwdPage"
38−
39− title: i18n.tr("Lock security")
40− forwardButtonSourceComponent: forwardButton
41−
42− // If the user has set a password some other way (via ubuntu-device-flash
43− // or this isn't the first time the wizard has been run, etc). We can't
44− // properly set the password again, so let's not pretend we can.
45− skip: securityPrivacy.securityType !== UbuntuSecurityPrivacyPanel.Swipe
46−
47− function indexToMethod(index) {
48− if (index === 0)
49− return UbuntuSecurityPrivacyPanel.Swipe
50− else if (index === 1)
51− return UbuntuSecurityPrivacyPanel.Passcode
52− else
53− return UbuntuSecurityPrivacyPanel.Passphrase
54− }
55−
56− function methodToIndex(method) {
57− if (method === UbuntuSecurityPrivacyPanel.Swipe)
58− return 0
59− else if (method === UbuntuSecurityPrivacyPanel.Passcode)
60− return 1
61− else
62− return 2
63− }
64−
65− Connections {
66− target: root
67− onPasswordMethodChanged: selector.selectedIndex = methodToIndex(root.passwordMethod)
68− }
69−
70− Column {
71− id: column
72− anchors.fill: content
73− spacing: units.gu(4)
74−
75− Label {
76− anchors.left: parent.left
77− anchors.right: parent.right
78− wrapMode: Text.Wrap
79− text: i18n.tr("Please select how you’d like to unlock your device.")
80− }
81−
82− ItemSelector {
83− id: selector
84− expanded: true
85− anchors.left: parent.left
86− anchors.right: parent.right
87− property color originalBackground
88−
89− model: ["", "", ""] // otherwise the delegate will show the text itself and we only want subText
90−
91− selectedIndex: methodToIndex(root.passwordMethod)
92−
93− delegate: OptionSelectorDelegate {
94− objectName: "passwdDelegate" + index
95−
96− // use subText because we want the text to be small, and we have no other way to control it
97− subText: {
98− var method = indexToMethod(index)
99− var name = ""
100− var desc = ""
101− if (method === UbuntuSecurityPrivacyPanel.Swipe) {
102− name = i18n.ctr("Label: Type of security method", "Swipe")
103− desc = i18n.ctr("Label: Description of security method", "No security")
104− } else if (method === UbuntuSecurityPrivacyPanel.Passcode) {
105− name = i18n.ctr("Label: Type of security method", "Passcode")
106− desc = i18n.ctr("Label: Description of security method", "4 digits only")
107− } else {
108− name = i18n.ctr("Label: Type of security method", "Passphrase")
109− desc = i18n.ctr("Label: Description of security method", "Numbers and letters")
110− }
111− return "<b>%1</b> (%2)".arg(name).arg(desc)
112− }
113− }
114−
115− style: Item {}
116−
117− Component.onCompleted: {
118− // A visible selected background looks bad in ListItem widgets with our theme
119− originalBackground = theme.palette.selected.background
120− theme.palette.selected.background = "transparent"
121− }
122−
123− Component.onDestruction: {
124− // Restore original theme background
125− theme.palette.selected.background = originalBackground
126− }
127− }
128− }
129−
130− Component {
131− id: forwardButton
132− LocalComponents.StackButton {
133− text: i18n.tr("Continue")
134− onClicked: {
135− root.passwordMethod = indexToMethod(selector.selectedIndex)
136− pageStack.load(Qt.resolvedUrl("passwd-set.qml"))
137− }
138− }
139− }
140−}
qml/Wizard/Pages/30-wifi.qmlnew file+210
@@ -0,0 +1,210 @@
1+/*
2+ * Copyright (C) 2013, 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+import QtQuick.Layouts 1.1
19+import QMenuModel 0.1 as QMenuModel
20+import Ubuntu.Components 1.3
21+import Wizard 0.1
22+import Ubuntu.Connectivity 1.0
23+import ".." as LocalComponents
24+
25+LocalComponents.Page {
26+ id: wifiPage
27+ objectName: "wifiPage"
28+
29+ title: i18n.tr("Connect to Wi‑Fi")
30+ forwardButtonSourceComponent: forwardButton
31+
32+ readonly property bool connected: Connectivity.online
33+
34+ function getExtendedProperty(object, propertyName, defaultValue) {
35+ if (object && object.hasOwnProperty(propertyName)) {
36+ return object[propertyName];
37+ }
38+ return defaultValue;
39+ }
40+
41+ function getAPIcon(adHoc, signalStrength, secure) {
42+ var imageName = "nm-no-connection";
43+
44+ if (adHoc) {
45+ imageName = "nm-adhoc";
46+ } else if (signalStrength == 0) {
47+ imageName = "nm-signal-00";
48+ } else if (signalStrength <= 25) {
49+ imageName = "nm-signal-25";
50+ } else if (signalStrength <= 50) {
51+ imageName = "nm-signal-50";
52+ } else if (signalStrength <= 75) {
53+ imageName = "nm-signal-75";
54+ } else if (signalStrength <= 100) {
55+ imageName = "nm-signal-100";
56+ }
57+
58+ if (secure) {
59+ imageName += "-secure";
60+ }
61+ return imageName;
62+ }
63+
64+ QMenuModel.UnityMenuModel {
65+ id: menuModel
66+ busName: "com.canonical.indicator.network"
67+ actions: { "indicator": "/com/canonical/indicator/network" }
68+ menuObjectPath: "/com/canonical/indicator/network/phone_wifi_settings"
69+ }
70+
71+ Component {
72+ id: accessPointComponent
73+ ListItem {
74+ id: accessPoint
75+ objectName: "accessPoint"
76+ highlightColor: backgroundColor
77+ enabled: menuData && menuData.sensitive || false
78+ divider.colorFrom: dividerColor
79+ divider.colorTo: backgroundColor
80+
81+ property QtObject menuData: null
82+ property var unityMenuModel: menuModel
83+ property var extendedData: menuData && menuData.ext || undefined
84+ property var strengthAction: QMenuModel.UnityMenuAction {
85+ model: unityMenuModel
86+ index: menuIndex
87+ name: getExtendedProperty(extendedData, "xCanonicalWifiApStrengthAction", "")
88+ }
89+ readonly property bool secure: getExtendedProperty(extendedData, "xCanonicalWifiApIsSecure", false)
90+ readonly property bool adHoc: getExtendedProperty(extendedData, "xCanonicalWifiApIsAdhoc", false)
91+ readonly property bool isConnected: menuData && menuData.actionState
92+ readonly property bool isEnterprise: getExtendedProperty(extendedData, "xCanonicalWifiApIsEnterprise", false)
93+ readonly property int signalStrength: strengthAction.valid ? strengthAction.state : 0
94+ property int menuIndex: -1
95+
96+ function loadAttributes() {
97+ if (!unityMenuModel || menuIndex == -1) return;
98+ unityMenuModel.loadExtendedAttributes(menuIndex, {'x-canonical-wifi-ap-is-adhoc': 'bool',
99+ 'x-canonical-wifi-ap-is-secure': 'bool',
100+ 'x-canonical-wifi-ap-is-enterprise': 'bool',
101+ 'x-canonical-wifi-ap-strength-action': 'string'});
102+ }
103+
104+ Icon {
105+ id: apIcon
106+ anchors {
107+ left: parent.left
108+ verticalCenter: parent.verticalCenter
109+ leftMargin: column.anchors.leftMargin == 0 ? staticMargin : 0
110+ }
111+ height: units.gu(2.5)
112+ width: height
113+ name: getAPIcon(accessPoint.adHoc, accessPoint.signalStrength, accessPoint.secure)
114+ color: textColor
115+ }
116+
117+ Column {
118+ anchors.verticalCenter: parent.verticalCenter
119+ anchors.left: apIcon.right
120+ anchors.leftMargin: units.gu(2)
121+ Label {
122+ id: apName
123+ text: menuData && menuData.label || ""
124+ font.weight: accessPoint.isConnected ? Font.Normal : Font.Light
125+ fontSize: "medium"
126+ color: textColor
127+ }
128+ Label {
129+ id: connectedLabel
130+ text: i18n.tr("Connected")
131+ font.weight: Font.Light
132+ fontSize: "small"
133+ color: okColor
134+ visible: accessPoint.isConnected
135+ }
136+ }
137+
138+ onClicked: {
139+ unityMenuModel.activate(menuIndex);
140+ listview.positionViewAtBeginning();
141+ }
142+ }
143+ }
144+
145+ ColumnLayout {
146+ id: column
147+ spacing: units.gu(2)
148+ anchors {
149+ fill: content
150+ topMargin: customMargin
151+ leftMargin: wideMode ? parent.leftMargin : 0
152+ rightMargin: wideMode ? parent.rightMargin : 0
153+ }
154+
155+ Label {
156+ id: label
157+ anchors.left: parent.left
158+ anchors.right: parent.right
159+ anchors.leftMargin: column.anchors.leftMargin == 0 ? staticMargin : 0
160+ font.weight: Font.Light
161+ color: "#68064d"
162+ wrapMode: Text.Wrap
163+ text: listview.count > 0 ? i18n.tr("Available Wi-Fi networks")
164+ : i18n.tr("No available Wi-Fi networks")
165+ }
166+
167+ ListView {
168+ id: listview
169+ anchors.left: parent.left
170+ anchors.right: parent.right
171+ clip: true
172+ model: menuModel
173+ Layout.fillHeight: true
174+
175+ delegate: Loader {
176+ id: loader
177+
178+ readonly property bool isAccessPoint: model.type === "unity.widgets.systemsettings.tablet.accesspoint"
179+ readonly property bool isConnected: item && item.menuData && item.menuData.actionState
180+ readonly property bool isEnterprise: item && item.isEnterprise
181+
182+ height: !!sourceComponent ? (isConnected ? units.gu(9) : units.gu(7)) : 0
183+ anchors.left: parent.left
184+ anchors.right: parent.right
185+
186+ asynchronous: true
187+ sourceComponent: {
188+ if (isAccessPoint && !isEnterprise) {
189+ return accessPointComponent;
190+ }
191+ return null;
192+ }
193+
194+ onLoaded: {
195+ item.menuData = Qt.binding(function() { return model; });
196+ item.menuIndex = Qt.binding(function() { return index; });
197+ item.loadAttributes();
198+ }
199+ }
200+ }
201+ }
202+
203+ Component {
204+ id: forwardButton
205+ LocalComponents.StackButton {
206+ text: (connected || listview.count === 0) ? i18n.tr("Next") : i18n.tr("Skip")
207+ onClicked: pageStack.next()
208+ }
209+ }
210+}
qml/Wizard/Pages/40-location.qmlnew file+291
@@ -0,0 +1,291 @@
1+/*
2+ * Copyright (C) 2013-2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+import AccountsService 0.1
19+import QMenuModel 0.1 as QMenuModel
20+import Qt.labs.folderlistmodel 2.1
21+import Ubuntu.Components 1.3
22+import ".." as LocalComponents
23+
24+LocalComponents.Page {
25+ objectName: "locationPage"
26+
27+ title: i18n.tr("Location Services")
28+ forwardButtonSourceComponent: forwardButton
29+
30+ readonly property bool pathSet: AccountsService.hereLicensePathValid
31+ skipValid: pathSet && (AccountsService.hereLicensePath === "" || termsModel.count > 0)
32+ skip: skipValid && (AccountsService.hereLicensePath === "" || termsModel.count === 2) // no files but . and ..
33+
34+ FolderListModel {
35+ id: termsModel
36+ folder: AccountsService.hereLicensePath
37+ nameFilters: ["*.html"]
38+ showOnlyReadable: true
39+ showDotAndDotDot: true // so that count == 0 means we're not done scanning yet
40+ }
41+
42+ QMenuModel.QDBusActionGroup {
43+ id: locationActionGroup
44+ objectName: "locationActionGroup"
45+ busType: QMenuModel.DBus.SessionBus
46+ busName: "com.canonical.indicator.location"
47+ objectPath: "/com/canonical/indicator/location"
48+ property variant location: action("location-detection-enabled")
49+ property variant gps: action("gps-detection-enabled")
50+ Component.onCompleted: start()
51+ }
52+
53+ Item {
54+ id: column
55+ anchors.fill: content
56+ anchors.topMargin: customMargin
57+ anchors.leftMargin: wideMode ? parent.leftMargin : 0
58+ anchors.rightMargin: wideMode ? parent.rightMargin : 0
59+
60+ Item {
61+ id: hereCheckGroup
62+ anchors {
63+ left: parent.left
64+ right: parent.right
65+ top: parent.top
66+ }
67+ height: hereCheckLabel.height + hereTermsLabel.height
68+
69+ Label {
70+ id: hereCheckLabel
71+ objectName: "hereCheckLabel"
72+ anchors {
73+ left: parent.left
74+ right: hereTermsCheck.left
75+ top: parent.top
76+ leftMargin: column.anchors.leftMargin == 0 ? staticMargin : 0
77+ rightMargin: units.gu(2)
78+ }
79+ lineHeight: 1.2
80+ wrapMode: Text.WordWrap
81+ color: textColor
82+ font.weight: checked ? Font.Normal : Font.Light
83+ text: i18n.tr("Use GPS, Wi-Fi hotspots and mobile network anonymously to detect location (recommended)")
84+ property bool checked: true
85+ }
86+
87+ Label {
88+ id: hereTermsLabel
89+ objectName: "hereTermsLabel"
90+ anchors {
91+ left: parent.left
92+ right: hereTermsCheck.left
93+ top: hereCheckLabel.bottom
94+ topMargin: units.gu(1)
95+ leftMargin: column.anchors.leftMargin == 0 ? staticMargin : 0
96+ rightMargin: units.gu(2)
97+ }
98+
99+ wrapMode: Text.WordWrap
100+ color: textColor
101+ font.weight: Font.Light
102+ fontSize: "small"
103+ lineHeight: 1.2
104+ linkColor: UbuntuColors.orange
105+ text: i18n.tr("By selecting this option you agree to the Nokia HERE") + " <a href='#'>" +
106+ // TRANSLATORS: "terms and conditions" is part of the sentence above
107+ i18n.tr("terms and conditions") + "</a>."
108+ Mouse.forwardTo: hereCheckArea // clicking on the link also selects this group
109+ Mouse.priority: Mouse.AfterItem
110+ onLinkActivated: {
111+ pageStack.load(Qt.resolvedUrl("here-terms.qml"));
112+ }
113+ }
114+
115+ Image {
116+ id: hereTermsCheck
117+ fillMode: Image.PreserveAspectFit
118+ height: units.gu(1.5)
119+ source: "data/Tick@30.png"
120+ opacity: hereCheckLabel.checked ? 1 : 0
121+ anchors.right: parent.right
122+ anchors.verticalCenter: hereCheckLabel.verticalCenter
123+ anchors.rightMargin: column.anchors.rightMargin == 0 ? staticMargin : 0
124+ }
125+
126+ MouseArea {
127+ id: hereCheckArea
128+ anchors.fill: parent
129+ z: hereCheckGroup.z - 1 // so that the link can be activated
130+ onClicked: {
131+ if (!hereCheckLabel.checked) {
132+ hereCheckLabel.checked = true;
133+ gpsCheckLabel.checked = false;
134+ nopeCheckLabel.checked = false;
135+ }
136+ }
137+ }
138+ }
139+
140+ Rectangle {
141+ id: divider
142+ anchors.left: parent.left
143+ anchors.right: parent.right
144+ anchors.top: hereCheckGroup.bottom
145+ anchors.topMargin: units.gu(3)
146+ height: units.dp(1)
147+ color: dividerColor
148+ }
149+
150+ Label {
151+ id: gpsCheckLabel
152+ objectName: "gpsCheckLabel"
153+ anchors {
154+ left: parent.left
155+ right: parent.right
156+ top: divider.bottom
157+ topMargin: units.gu(3)
158+ leftMargin: column.anchors.leftMargin == 0 ? staticMargin : 0
159+ rightMargin: units.gu(2)
160+ }
161+ text: i18n.tr("GPS only")
162+ wrapMode: Text.WordWrap
163+ color: textColor
164+ font.weight: checked ? Font.Normal : Font.Light
165+ width: content.width
166+ property bool checked: false
167+
168+ MouseArea {
169+ anchors.fill: parent
170+ onClicked: {
171+ if (!gpsCheckLabel.checked) {
172+ hereCheckLabel.checked = false;
173+ gpsCheckLabel.checked = true;
174+ nopeCheckLabel.checked = false;
175+ }
176+ }
177+ }
178+ }
179+
180+ Image {
181+ fillMode: Image.PreserveAspectFit
182+ height: units.gu(1.5)
183+ source: "data/Tick@30.png"
184+ opacity: gpsCheckLabel.checked ? 1 : 0
185+ anchors.right: parent.right
186+ anchors.verticalCenter: gpsCheckLabel.verticalCenter
187+ anchors.rightMargin: column.anchors.rightMargin == 0 ? staticMargin : 0
188+ }
189+
190+ Rectangle {
191+ id: divider2
192+ anchors.left: parent.left
193+ anchors.right: parent.right
194+ anchors.top: gpsCheckLabel.bottom
195+ anchors.topMargin: units.gu(3)
196+ height: units.dp(1)
197+ color: dividerColor
198+ }
199+
200+ Label {
201+ id: nopeCheckLabel
202+ objectName: "nopeCheckLabel"
203+ anchors {
204+ left: parent.left
205+ right: parent.right
206+ top: divider2.bottom
207+ topMargin: units.gu(3)
208+ leftMargin: column.anchors.leftMargin == 0 ? staticMargin : 0
209+ rightMargin: units.gu(2)
210+ }
211+ wrapMode: Text.WordWrap
212+ color: textColor
213+ font.weight: checked ? Font.Normal : Font.Light
214+ width: content.width
215+ text: i18n.tr("Don't use my location")
216+ property bool checked: false
217+
218+ MouseArea {
219+ anchors.fill: parent
220+ onClicked: {
221+ if (!nopeCheckLabel.checked) {
222+ hereCheckLabel.checked = false;
223+ gpsCheckLabel.checked = false;
224+ nopeCheckLabel.checked = true;
225+ }
226+ }
227+ }
228+ }
229+
230+ Image {
231+ fillMode: Image.PreserveAspectFit
232+ height: units.gu(1.5)
233+ source: "data/Tick@30.png"
234+ opacity: nopeCheckLabel.checked ? 1 : 0
235+ anchors.right: parent.right
236+ anchors.verticalCenter: nopeCheckLabel.verticalCenter
237+ anchors.rightMargin: column.anchors.rightMargin == 0 ? staticMargin : 0
238+ }
239+
240+ Rectangle {
241+ id: divider3
242+ anchors.left: parent.left
243+ anchors.right: parent.right
244+ anchors.top: nopeCheckLabel.bottom
245+ anchors.topMargin: units.gu(3)
246+ height: units.dp(1)
247+ color: dividerColor
248+ }
249+
250+ Label {
251+ id: infoLabel
252+ anchors {
253+ left: parent.left
254+ right: parent.right
255+ top: divider3.bottom
256+ topMargin: units.gu(4)
257+ leftMargin: column.anchors.leftMargin == 0 ? staticMargin : 0
258+ rightMargin: column.anchors.rightMargin == 0 ? staticMargin : 0
259+ }
260+ wrapMode: Text.Wrap
261+ text: i18n.tr("You can change it later in System Settings.")
262+ color: textColor
263+ fontSize: "small"
264+ font.weight: Font.Light
265+ width: content.width
266+ }
267+ }
268+
269+ Component {
270+ id: forwardButton
271+ LocalComponents.StackButton {
272+ text: i18n.tr("Next")
273+ onClicked: {
274+ var locationOn = gpsCheckLabel.checked || hereCheckLabel.checked;
275+ var gpsOn = gpsCheckLabel.checked || hereCheckLabel.checked;
276+ var hereOn = hereCheckLabel.checked;
277+
278+ // location service doesn't currently listen to updateState
279+ // requests, so we activate the actions if needed.
280+ if (locationActionGroup.location.state != locationOn) {
281+ locationActionGroup.location.activate();
282+ }
283+ if (locationActionGroup.gps.state != gpsOn) {
284+ locationActionGroup.gps.activate();
285+ }
286+ AccountsService.hereEnabled = hereOn;
287+ pageStack.next();
288+ }
289+ }
290+ }
291+}
qml/Wizard/Pages/40-wifi.qmldeleted−219
@@ -1,219 +0,0 @@
1−/*
2− * Copyright (C) 2013 Canonical, Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import QtQuick 2.4
18−import QMenuModel 0.1 as QMenuModel
19−import QtSystemInfo 5.0
20−import Ubuntu.Components 1.3
21−import Ubuntu.Components.ListItems 1.3 as ListItem
22−import Ubuntu.Settings.Menus 0.1 as Menus
23−import ".." as LocalComponents
24−
25−LocalComponents.Page {
26− id: wifiPage
27− objectName: "wifiPage"
28−
29− title: i18n.tr("Connect to Wi‑Fi")
30− forwardButtonSourceComponent: forwardButton
31−
32− readonly property bool connected: networkInfo.accessPointName
33−
34− function getExtendedProperty(object, propertyName, defaultValue) {
35− if (object && object.hasOwnProperty(propertyName)) {
36− return object[propertyName];
37− }
38− return defaultValue;
39− }
40−
41− QMenuModel.UnityMenuModel {
42− id: menuModel
43− busName: "com.canonical.indicator.network"
44− actions: { "indicator": "/com/canonical/indicator/network" }
45− menuObjectPath: "/com/canonical/indicator/network/phone_wifi_settings"
46− }
47−
48− NetworkInfo {
49− id: networkInfo
50−
51− property string accessPointName
52−
53− monitorCurrentNetworkMode: true
54− monitorNetworkName: true
55− monitorNetworkStatus: true
56−
57− onCurrentNetworkModeChanged: getAccessPointName()
58− onNetworkNameChanged: getAccessPointName()
59− onNetworkStatusChanged: if (status !== NetworkInfo.HomeNetwork) accessPointName = ""
60−
61− Component.onCompleted: getAccessPointName()
62−
63− function getAccessPointName() {
64− // 0 is the interface
65− if (currentNetworkMode === NetworkInfo.WlanMode &&
66− networkStatus(NetworkInfo.WlanMode, 0) === NetworkInfo.HomeNetwork)
67− accessPointName = networkName(NetworkInfo.WlanMode, 0);
68− else
69− accessPointName = "";
70− }
71− }
72−
73− Component {
74− id: accessPointComponent
75− ListItem.Standard {
76− id: accessPoint
77− objectName: "accessPoint"
78−
79− property QtObject menuData: null
80− property var unityMenuModel: menuModel
81− property var extendedData: menuData && menuData.ext || undefined
82− property var strengthAction: QMenuModel.UnityMenuAction {
83− model: unityMenuModel
84− index: menuIndex
85− name: getExtendedProperty(extendedData, "xCanonicalWifiApStrengthAction", "")
86− }
87− property bool checked: menuData && menuData.isToggled || false
88− property bool secure: getExtendedProperty(extendedData, "xCanonicalWifiApIsSecure", false)
89− property bool adHoc: getExtendedProperty(extendedData, "xCanonicalWifiApIsAdhoc", false)
90− property int signalStrength: strengthAction.valid ? strengthAction.state : 0
91− property int menuIndex: -1
92−
93− function loadAttributes() {
94− if (!unityMenuModel || menuIndex == -1) return;
95− unityMenuModel.loadExtendedAttributes(menuIndex, {'x-canonical-wifi-ap-is-adhoc': 'bool',
96− 'x-canonical-wifi-ap-is-secure': 'bool',
97− 'x-canonical-wifi-ap-strength-action': 'string'});
98− }
99−
100− signal activate()
101−
102− text: menuData && menuData.label || ""
103− enabled: menuData && menuData.sensitive || false
104− iconName: {
105− var imageName = "nm-signal-100";
106−
107− if (adHoc) {
108− imageName = "nm-adhoc";
109− } else if (signalStrength == 0) {
110− imageName = "nm-signal-00";
111− } else if (signalStrength <= 25) {
112− imageName = "nm-signal-25";
113− } else if (signalStrength <= 50) {
114− imageName = "nm-signal-50";
115− } else if (signalStrength <= 75) {
116− imageName = "nm-signal-75";
117− }
118−
119− if (secure) {
120− imageName += "-secure";
121− }
122− return imageName;
123− }
124− iconFrame: false
125− control: CheckBox {
126− id: checkBoxActive
127−
128− onClicked: {
129− accessPoint.activate();
130− }
131− }
132− style: Rectangle {
133− color: "#4c000000"
134− }
135−
136− Component.onCompleted: {
137− loadAttributes();
138− }
139− onUnityMenuModelChanged: {
140− loadAttributes();
141− }
142− onMenuIndexChanged: {
143− loadAttributes();
144− }
145− onCheckedChanged: {
146− // Can't rely on binding. Checked is assigned on click.
147− checkBoxActive.checked = checked;
148− }
149− onActivate: unityMenuModel.activate(menuIndex);
150− }
151− }
152−
153− Column {
154− id: column
155− spacing: units.gu(2)
156− anchors.top: content.top
157− anchors.bottom: content.bottom
158− anchors.left: wifiPage.left
159− anchors.right: wifiPage.right
160−
161− Label {
162− id: label
163− anchors.left: parent.left
164− anchors.leftMargin: leftMargin
165− anchors.right: parent.right
166− anchors.rightMargin: rightMargin
167− fontSize: "small"
168− text: mainMenu.count > 0 ? i18n.tr("Available networks…")
169− : i18n.tr("No available networks.")
170− }
171−
172− Flickable {
173− anchors.left: parent.left
174− anchors.right: parent.right
175− height: column.height - label.height - column.spacing
176− contentHeight: contentItem.childrenRect.height
177− clip: true
178− flickDeceleration: 1500 * units.gridUnit / 8
179− maximumFlickVelocity: 2500 * units.gridUnit / 8
180− boundsBehavior: (contentHeight > height) ? Flickable.DragAndOvershootBounds : Flickable.StopAtBounds
181−
182− Column {
183− anchors.left: parent.left
184− anchors.right: parent.right
185−
186− Repeater {
187− id: mainMenu
188−
189− model: menuModel
190−
191− delegate: Loader {
192− id: loader
193−
194− readonly property bool isAccessPoint: model.type === "unity.widgets.systemsettings.tablet.accesspoint"
195−
196− anchors.left: parent.left
197− anchors.right: parent.right
198− height: isAccessPoint ? units.gu(6) : 0
199− asynchronous: true
200− sourceComponent: isAccessPoint ? accessPointComponent : null
201−
202− onLoaded: {
203− item.menuData = Qt.binding(function() { return model; });
204− item.menuIndex = Qt.binding(function() { return index; });
205− }
206− }
207− }
208− }
209− }
210− }
211−
212− Component {
213− id: forwardButton
214− LocalComponents.StackButton {
215− text: (connected || mainMenu.count === 0) ? i18n.tr("Continue") : i18n.tr("Skip")
216− onClicked: pageStack.next()
217− }
218− }
219−}
qml/Wizard/Pages/50-location.qmldeleted−150
@@ -1,150 +0,0 @@
1−/*
2− * Copyright (C) 2013-2015 Canonical, Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import QtQuick 2.4
18−import AccountsService 0.1
19−import QMenuModel 0.1 as QMenuModel
20−import Qt.labs.folderlistmodel 2.1
21−import Ubuntu.Components 1.3
22−import ".." as LocalComponents
23−
24−LocalComponents.Page {
25− objectName: "locationPage"
26−
27− title: i18n.tr("Location")
28− forwardButtonSourceComponent: forwardButton
29−
30− property bool pathSet: AccountsService.hereLicensePathValid
31− skipValid: pathSet && (AccountsService.hereLicensePath === "" || termsModel.count > 0)
32− skip: skipValid && (AccountsService.hereLicensePath === "" || termsModel.count === 2) // no files but . and ..
33−
34− FolderListModel {
35− id: termsModel
36− folder: AccountsService.hereLicensePath
37− nameFilters: ["*.html"]
38− showOnlyReadable: true
39− showDotAndDotDot: true // so that count == 0 means we're not done scanning yet
40− }
41−
42− QMenuModel.QDBusActionGroup {
43− id: locationActionGroup
44− objectName: "locationActionGroup"
45− busType: QMenuModel.DBus.SessionBus
46− busName: "com.canonical.indicator.location"
47− objectPath: "/com/canonical/indicator/location"
48− property variant location: action("location-detection-enabled")
49− property variant gps: action("gps-detection-enabled")
50− Component.onCompleted: start()
51− }
52−
53− Column {
54− id: column
55− anchors.fill: content
56− spacing: units.gu(3)
57−
58− Label {
59− anchors.left: parent.left
60− anchors.right: parent.right
61− wrapMode: Text.Wrap
62− text: i18n.tr("Let the phone detect your location:")
63− }
64−
65− LocalComponents.CheckableSetting {
66− id: gpsCheck
67− objectName: "gpsCheck"
68− showDivider: false
69− text: i18n.tr("Using GPS only (less accurate)")
70− onTriggered: {
71− gpsCheck.checked = true;
72− hereCheck.checked = false;
73− nopeCheck.checked = false;
74− }
75− }
76−
77− Column {
78− anchors.left: parent.left
79− anchors.right: parent.right
80− height: childrenRect.height
81−
82− LocalComponents.CheckableSetting {
83− id: hereCheck
84− objectName: "hereCheck"
85− showDivider: false
86− text: i18n.tr("Using GPS, anonymized Wi-Fi and cellular network info (recommended)")
87− checked: true
88− onTriggered: {
89− gpsCheck.checked = false;
90− hereCheck.checked = true;
91− nopeCheck.checked = false;
92− }
93− }
94−
95− Label {
96− objectName: "hereTermsLink"
97− anchors.left: parent.left
98− anchors.leftMargin: hereCheck.labelOffset
99− anchors.right: parent.right
100− wrapMode: Text.Wrap
101− linkColor: theme.palette.normal.foregroundText
102− // Translators: HERE is a trademark for Nokia's location service, you probably shouldn't translate it
103− text: i18n.tr("By selecting this option you agree to the Nokia HERE <a href='#'>terms and conditions</a>.")
104− onLinkActivated: pageStack.load(Qt.resolvedUrl("here-terms.qml"))
105− }
106− }
107−
108− LocalComponents.CheckableSetting {
109− id: nopeCheck
110− objectName: "nopeCheck"
111− showDivider: false
112− text: i18n.tr("Not at all")
113− onTriggered: {
114− gpsCheck.checked = false;
115− hereCheck.checked = false;
116− nopeCheck.checked = true;
117− }
118− }
119−
120− Label {
121− anchors.left: parent.left
122− anchors.right: parent.right
123− wrapMode: Text.Wrap
124− text: i18n.tr("You can change your mind later in <b>System Settings</b>.")
125− }
126− }
127−
128− Component {
129− id: forwardButton
130− LocalComponents.StackButton {
131− text: i18n.tr("Continue")
132− onClicked: {
133− var locationOn = gpsCheck.checked || hereCheck.checked;
134− var gpsOn = gpsCheck.checked || hereCheck.checked;
135− var hereOn = hereCheck.checked;
136−
137− // location service doesn't currently listen to updateState
138− // requests, so we activate the actions if needed.
139− if (locationActionGroup.location.state != locationOn) {
140− locationActionGroup.location.activate();
141− }
142− if (locationActionGroup.gps.state != gpsOn) {
143− locationActionGroup.gps.activate();
144− }
145− AccountsService.hereEnabled = hereOn;
146− pageStack.next()
147− }
148− }
149− }
150−}
qml/Wizard/Pages/50-timezone.qmlnew file+275
@@ -0,0 +1,275 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+import QtQuick.Layouts 1.1
19+import Ubuntu.Components 1.3
20+import Wizard 0.1
21+import Ubuntu.SystemSettings.TimeDate 1.0
22+import Utils 0.1 as Utils
23+import ".." as LocalComponents
24+
25+LocalComponents.Page {
26+ id: tzPage
27+ objectName: "tzPage"
28+
29+ title: i18n.tr("Time Zone")
30+ forwardButtonSourceComponent: forwardButton
31+
32+ property string selectedTimeZone: ""
33+ readonly property bool showingMap: wideMode && width >= units.gu(110)
34+
35+ // for testing
36+ readonly property alias tdModule: timeDatePanel
37+
38+ function highlightTimezone(offset) {
39+ highlightImage.source = "data/timezonemap/timezone_" + offset + ".png";
40+ }
41+
42+ // geo coords conversion functions (adapted from libtimezonemap)
43+ function radians(degrees) {
44+ return degrees * Math.PI / 180;
45+ }
46+
47+ function longitudeToX(longitude, map_width) {
48+ const xdeg_offset = -6;
49+ const x = (map_width * (180.0 + longitude) / 360.0) + (map_width * xdeg_offset / 180.0);
50+ return x;
51+ }
52+
53+ function latitudeToY(latitude, map_height) {
54+ const bottom_lat = -59;
55+ const top_lat = 81;
56+ const top_per = top_lat / 180.0;
57+
58+ var y = 1.25 * Math.log(Math.tan(0.25*Math.PI + 0.4 * radians(latitude)));
59+ const full_range = 4.6068250867599998;
60+ const top_offset = full_range * top_per;
61+ const map_range = Math.abs(1.25 * Math.log(Math.tan(0.25*Math.PI + 0.4 * radians(bottom_lat))) - top_offset);
62+ y = Math.abs(y - top_offset);
63+ y = y / map_range;
64+ y = y * map_height;
65+ return y;
66+ }
67+
68+ function resetViews() {
69+ selectedTimeZone = ""
70+ tzList.currentIndex = -1
71+ highlightImage.source = ""
72+ pinImage.x = 0;
73+ pinImage.y = 0;
74+ }
75+
76+ UbuntuTimeDatePanel {
77+ id: timeDatePanel
78+ }
79+
80+ onContentAnimationRunningChanged: {
81+ if (!contentAnimationRunning) {
82+ if (tzList.count == 1) { // preselect the first (and only) TZ
83+ var tz = tzList.itemAt(0,0);
84+ if (!!tz) {
85+ tz.clicked();
86+ }
87+ }
88+
89+ resetViews();
90+ searchField.forceActiveFocus();
91+ }
92+ }
93+
94+ Component {
95+ id: tzComponent
96+ ListItem {
97+ id: tz
98+ objectName: "tz" + index
99+ highlightColor: backgroundColor
100+ divider.colorFrom: dividerColor
101+ divider.colorTo: backgroundColor
102+ readonly property bool currentTz: ListView.view.currentIndex === index
103+
104+ Column {
105+ anchors.verticalCenter: parent.verticalCenter
106+ anchors.left: parent.left
107+ anchors.leftMargin: !wideMode ? staticMargin : 0
108+ anchors.right: image.left
109+ anchors.rightMargin: units.gu(2)
110+
111+ Label {
112+ id: cityLabel
113+ text: displayName
114+ font.weight: tz.currentTz ? Font.Normal : Font.Light
115+ fontSize: "medium"
116+ color: textColor
117+ elide: Text.ElideMiddle
118+ maximumLineCount: 1
119+ width: parent.width
120+ }
121+ Label {
122+ id: timeLabel
123+ text: Utils.TimezoneFormatter.currentTimeInTimezoneWithAbbrev(timeZone)
124+ font.weight: tz.currentTz ? Font.Normal : Font.Light
125+ fontSize: "small"
126+ color: textColor
127+ }
128+ }
129+ Image {
130+ id: image
131+ anchors {
132+ right: parent.right
133+ verticalCenter: parent.verticalCenter
134+ rightMargin: !wideMode ? staticMargin : 0
135+ }
136+ fillMode: Image.PreserveAspectFit
137+ height: units.gu(1.5)
138+
139+ source: "data/Tick@30.png"
140+ visible: tz.currentTz
141+ }
142+
143+ onClicked: {
144+ highlightTimezone(offset);
145+ ListView.view.currentIndex = index;
146+ selectedTimeZone = timeZone;
147+ //print("Clicked at city with coords:", longitude, latitude);
148+ //print("Clicked on TZ:", timeZone);
149+ //print("Highlight at (x,y):", longitudeToX(longitude, map.width), latitudeToY(latitude, map.height));
150+ pinImage.x = Qt.binding(function() { return longitudeToX(longitude, map.width) - pinImage.width; });
151+ pinImage.y = Qt.binding(function() { return latitudeToY(latitude, map.height) - pinImage.height; });
152+ }
153+ }
154+ }
155+
156+
157+ ColumnLayout {
158+ id: leftColumn
159+ anchors {
160+ left: content.left
161+ top: content.top
162+ bottom: content.bottom
163+ right: !showingMap ? content.right : undefined
164+ leftMargin: showingMap ? staticMargin : (wideMode ? tzPage.leftMargin : 0)
165+ rightMargin: showingMap ? staticMargin : (wideMode ? tzPage.rightMargin : 0)
166+ topMargin: customMargin
167+ }
168+
169+ width: Math.min(parent.width, units.gu(34))
170+
171+ LocalComponents.WizardTextField {
172+ id: searchField
173+ objectName: "tzFilter"
174+ anchors.left: parent.left
175+ anchors.right: parent.right
176+ anchors.leftMargin: !showingMap && !wideMode ? staticMargin : undefined
177+ anchors.rightMargin: !showingMap && !wideMode ? staticMargin : undefined
178+ placeholderText: i18n.tr("Enter your city")
179+ inputMethodHints: Qt.ImhNoPredictiveText
180+ onTextChanged: resetViews();
181+ }
182+
183+ ListView {
184+ Layout.fillHeight: true
185+ id: tzList
186+ objectName: "tzList"
187+ clip: true
188+ anchors.left: parent.left
189+ anchors.right: parent.right
190+ currentIndex: -1
191+ model: TimeZoneModel {
192+ id: timeZoneModel
193+ filter: searchField.text
194+ country: i18n.language.split('_')[1].split('.')[0]
195+ }
196+ delegate: tzComponent
197+ }
198+
199+ ActivityIndicator {
200+ anchors.centerIn: tzList
201+ running: tzList.count == 0 &&
202+ searchField.length > 0 &&
203+ timeZoneModel.listUpdating
204+ visible: running
205+ }
206+ }
207+
208+ Item {
209+ id: mapContainer
210+ visible: showingMap && !contentAnimationRunning
211+ enabled: visible
212+
213+ anchors {
214+ left: leftColumn.right
215+ leftMargin: units.gu(4)
216+ right: content.right
217+ rightMargin: staticMargin
218+ top: content.top
219+ topMargin: customMargin
220+ bottom: parent.bottom
221+ bottomMargin: buttonBarHeight
222+ }
223+
224+ Item {
225+ id: map
226+ width: Math.min(parent.width, height * 1.95) // keep our aspect ratio
227+ height: parent.height
228+ anchors {
229+ centerIn: parent
230+ }
231+
232+ Image {
233+ id: backgroundImage
234+ source: "data/timezonemap/map.png"
235+ sourceSize: Qt.size(map.width, map.height)
236+ fillMode: Image.PreserveAspectFit
237+ smooth: false
238+ visible: mapContainer.visible
239+ asynchronous: true
240+ anchors.fill: parent
241+ }
242+
243+ Image {
244+ id: highlightImage
245+ sourceSize: Qt.size(map.width, map.height)
246+ fillMode: Image.PreserveAspectFit
247+ smooth: false
248+ visible: selectedTimeZone != ""
249+ asynchronous: true
250+ anchors.fill: backgroundImage
251+ }
252+
253+ Image {
254+ id: pinImage
255+ source: "data/timezonemap/pin.png"
256+ visible: x != 0 && y != 0
257+ width: units.dp(12)
258+ height: units.dp(20)
259+ z: map.z + 1
260+ }
261+ }
262+ }
263+
264+ Component {
265+ id: forwardButton
266+ LocalComponents.StackButton {
267+ text: i18n.tr("Next")
268+ enabled: selectedTimeZone != ""
269+ onClicked: {
270+ timeDatePanel.timeZone = selectedTimeZone;
271+ pageStack.next();
272+ }
273+ }
274+ }
275+}
qml/Wizard/Pages/60-reporting.qml → qml/Wizard/Pages/60-account.qmlrenamed+30−20
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013,2014 Canonical, Ltd.
2+ * Copyright (C) 2015-2016 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -16,49 +16,59 @@
1616
1717import QtQuick 2.4
1818import Ubuntu.Components 1.3
19−import Ubuntu.SystemSettings.Diagnostics 1.0
19+import AccountsService 0.1
2020import ".." as LocalComponents
2121
2222LocalComponents.Page {
23− id: reportingPage
24− objectName: "reportingPage"
23+ objectName: "accountPage"
24+ title: i18n.tr("Personalize Your Device")
2525
26− title: i18n.tr("Improving your experience")
2726 forwardButtonSourceComponent: forwardButton
2827
29− skipValid: false
30− skip: !diagnostics.reportCrashes // skip the page when the system is configured not to report crashes
31−
32− UbuntuDiagnostics {
33− id: diagnostics
34− Component.onCompleted: reportingPage.skipValid = true;
28+ QtObject {
29+ id: d
30+ readonly property string validName: nameInput.text.trim()
3531 }
3632
3733 Column {
3834 id: column
39− anchors.fill: content
40− spacing: units.gu(2)
35+ spacing: units.gu(1)
36+ anchors {
37+ fill: content
38+ leftMargin: parent.leftMargin
39+ rightMargin: parent.rightMargin
40+ topMargin: customMargin
41+ }
4142
43+ // name
4244 Label {
45+ id: nameLabel
4346 anchors.left: parent.left
4447 anchors.right: parent.right
45− wrapMode: Text.Wrap
46− text: i18n.tr("Your phone is set up to automatically report errors to Canonical and its partners, the makers of the operating system.")
48+ text: i18n.tr("Preferred Name")
49+ color: textColor
50+ font.weight: Font.Light
4751 }
4852
49− Label {
53+ LocalComponents.WizardTextField {
54+ id: nameInput
55+ objectName: "nameInput"
5056 anchors.left: parent.left
5157 anchors.right: parent.right
52− wrapMode: Text.Wrap
53− text: i18n.tr("This can be disabled in <b>System Settings</b> under <b>Security &amp; Privacy</b>")
58+ inputMethodHints: Qt.ImhNoPredictiveText
5459 }
5560 }
5661
5762 Component {
5863 id: forwardButton
5964 LocalComponents.StackButton {
60− text: i18n.tr("Continue")
61− onClicked: pageStack.next()
65+ text: d.validName ? i18n.tr("Next") : i18n.tr("Skip")
66+ onClicked: {
67+ if (d.validName) {
68+ AccountsService.realName = d.validName;
69+ }
70+ pageStack.next();
71+ }
6272 }
6373 }
6474}
qml/Wizard/Pages/70-passwd-type.qmlnew file+166
@@ -0,0 +1,166 @@
1+/*
2+ * Copyright (C) 2014-2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+import Ubuntu.Components 1.3
19+import Ubuntu.Components.ListItems 1.3
20+import Ubuntu.SystemSettings.SecurityPrivacy 1.0
21+import ".." as LocalComponents
22+
23+/**
24+ * One quirk with this page: we don't actually set the password. We avoid
25+ * doing it here because the user can come back to this page and change their
26+ * answer. We don't run as root, so if we did set the password immediately,
27+ * we'd need to prompt for their previous password when they came back and
28+ * changed their answer. Which is silly UX. So instead, we just keep track
29+ * of their choice and set the password at the end (see Pages.qml).
30+ * Setting the password shouldn't fail, since Ubuntu Touch has loose password
31+ * requirements, but we'll check what we can here. Ideally we'd be able to ask
32+ * the system if a password is legal without actually setting that password.
33+ */
34+
35+LocalComponents.Page {
36+ id: passwdPage
37+ objectName: "passwdPage"
38+
39+ title: i18n.tr("Lock Screen")
40+ forwardButtonSourceComponent: forwardButton
41+
42+ // If the user has set a password some other way (via ubuntu-device-flash
43+ // or this isn't the first time the wizard has been run, etc). We can't
44+ // properly set the password again, so let's not pretend we can.
45+ skip: securityPrivacy.securityType !== UbuntuSecurityPrivacyPanel.Swipe
46+
47+ function indexToMethod(index) {
48+ if (index === 0/* || index === 1*/)
49+ return UbuntuSecurityPrivacyPanel.Passphrase;
50+ else if (index === 1/*2*/)
51+ return UbuntuSecurityPrivacyPanel.Passcode;
52+ else
53+ return UbuntuSecurityPrivacyPanel.Swipe;
54+ }
55+
56+// Component.onCompleted: {
57+// if (root.password !== "") // the user has set a password as part of the previous page
58+// selector.currentIndex = 0;
59+// else
60+// selector.currentIndex = 1;
61+// }
62+
63+ Item {
64+ id: column
65+ anchors.fill: content
66+ anchors.topMargin: customMargin
67+ anchors.leftMargin: wideMode ? parent.leftMargin : 0
68+ anchors.rightMargin: wideMode ? parent.rightMargin : 0
69+
70+ ListView {
71+ id: selector
72+ anchors.left: parent.left
73+ anchors.right: parent.right
74+ boundsBehavior: Flickable.StopAtBounds
75+ clip: true
76+ height: childrenRect.height
77+
78+ // this is the order we want to display it; cf indexToMethod()
79+ model: [/*UbuntuSecurityPrivacyPanel.Passphrase, */UbuntuSecurityPrivacyPanel.Passphrase,
80+ UbuntuSecurityPrivacyPanel.Passcode, UbuntuSecurityPrivacyPanel.Swipe]
81+
82+ delegate: ListItem {
83+ id: itemDelegate
84+ objectName: "passwdDelegate" + index
85+ readonly property bool isCurrent: index === ListView.view.currentIndex
86+ highlightColor: backgroundColor
87+ divider.colorFrom: dividerColor
88+ divider.colorTo: backgroundColor
89+ Label {
90+ anchors.verticalCenter: parent.verticalCenter;
91+ anchors.left: parent.left
92+ anchors.leftMargin: column.anchors.leftMargin == 0 ? staticMargin : 0
93+ fontSize: "medium"
94+ color: textColor
95+ font.weight: itemDelegate.isCurrent ? Font.Normal : Font.Light
96+ text: {
97+ switch (index) {
98+// case 0:
99+// return i18n.ctr("Label: Type of security method", "Ubuntu administrator password");
100+ case 0:
101+ return i18n.ctr("Label: Type of security method", "Create new password");
102+ case 1:
103+ return i18n.ctr("Label: Type of security method", "Create passcode (numbers only)");
104+ case 2:
105+ return i18n.ctr("Label: Type of security method", "No lock code");
106+ }
107+ }
108+ width: parent.width
109+ wrapMode: Text.WordWrap
110+ }
111+
112+ Image {
113+ anchors {
114+ right: parent.right
115+ verticalCenter: parent.verticalCenter
116+ rightMargin: column.anchors.rightMargin == 0 ? staticMargin : 0
117+ }
118+ fillMode: Image.PreserveAspectFit
119+ height: units.gu(1.5)
120+
121+ source: "data/Tick@30.png"
122+ visible: itemDelegate.isCurrent
123+ }
124+
125+ onClicked: {
126+ selector.currentIndex = index;
127+ }
128+ }
129+ }
130+
131+ Rectangle {
132+ id: divider2
133+ anchors.left: parent.left
134+ anchors.right: parent.right
135+ anchors.top: selector.bottom
136+ height: units.dp(1)
137+ color: dividerColor
138+ }
139+ }
140+
141+ Component {
142+ id: forwardButton
143+ LocalComponents.StackButton {
144+ text: i18n.tr("Next")
145+ onClicked: {
146+ var method = indexToMethod(selector.currentIndex);
147+ root.passwordMethod = method;
148+
149+ if (method === UbuntuSecurityPrivacyPanel.Passphrase) { // any password
150+ if (selector.currentIndex == 0/*1*/)
151+ pageStack.load(Qt.resolvedUrl("password-set.qml")); // let the user choose a new password
152+ else
153+ pageStack.next(); // got the password already, go next page
154+ } else if (method === UbuntuSecurityPrivacyPanel.Passcode) { // passcode
155+ if (wideMode) {
156+ pageStack.load(Qt.resolvedUrl("passcode-desktop.qml"));
157+ } else {
158+ pageStack.load(Qt.resolvedUrl("passcode-set.qml"));
159+ }
160+ } else { //swipe
161+ pageStack.next();
162+ }
163+ }
164+ }
165+ }
166+}
qml/Wizard/Pages/75-report-check.qmlnew file+91
@@ -0,0 +1,91 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+import Ubuntu.Components 1.3
19+import Ubuntu.Web 0.2
20+import ".." as LocalComponents
21+
22+LocalComponents.Page {
23+ id: reportingPage
24+ objectName: "reportingPage"
25+
26+ title: webview.visible ? i18n.tr("Privacy Policy") : i18n.tr("Help Us Improve")
27+ forwardButtonSourceComponent: !webview.visible ? forwardButton : null
28+ customBack: true
29+ customTitle: webview.visible
30+
31+ skip: !diagnostics.reportCrashes // skip the page when the system is configured not to report crashes
32+
33+ onBackClicked: {
34+ if (webview.visible) {
35+ webview.visible = false;
36+ } else {
37+ pageStack.prev();
38+ }
39+ }
40+
41+ Column {
42+ id: column
43+ anchors {
44+ fill: content
45+ leftMargin: leftMargin
46+ rightMargin: rightMargin
47+ topMargin: customMargin
48+ }
49+
50+ LocalComponents.CheckableSetting {
51+ anchors {
52+ left: parent.left
53+ right: parent.right
54+ }
55+
56+ id: reportCheck
57+ objectName: "reportCheck"
58+ showDivider: false
59+ text: i18n.tr("Improve system performance by sending us crashes and error reports.") + "<br><br><a href='#'>" +
60+ i18n.tr("Privacy policy") + "</a>"
61+ checked: diagnostics.reportCrashes
62+ visible: !webview.visible
63+ onLinkActivated: {
64+ webview.url = "http://www.ubuntu.com/legal/terms-and-policies/privacy-policy";
65+ webview.visible = true;
66+ }
67+ }
68+
69+ WebView {
70+ id: webview
71+ objectName: "webview"
72+ anchors.left: parent.left
73+ anchors.right: parent.right
74+ anchors.leftMargin: -leftMargin
75+ anchors.rightMargin: -rightMargin
76+ height: parent.height
77+ visible: false
78+ }
79+ }
80+
81+ Component {
82+ id: forwardButton
83+ LocalComponents.StackButton {
84+ text: i18n.tr("Next")
85+ onClicked: {
86+ diagnostics.setReportCrashes(reportCheck.checked);
87+ pageStack.next();
88+ }
89+ }
90+ }
91+}
qml/Wizard/Pages/80-finished.qml+94−16
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013,2014 Canonical, Ltd.
2+ * Copyright (C) 2013-2015 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -21,37 +21,115 @@ import ".." as LocalComponents
2121LocalComponents.Page {
2222 objectName: "finishedPage"
2323
24− title: i18n.tr("All done")
25− forwardButtonSourceComponent: forwardButton
2624 hasBackButton: false
25+ customTitle: true
26+ lastPage: true
27+ buttonBarVisible: false
2728
28− Column {
29+ Component.onCompleted: {
30+ state = "reanchored";
31+ }
32+
33+ states: State {
34+ name: "reanchored"
35+ AnchorChanges { target: bgImage; anchors.top: parent.top; anchors.bottom: parent.bottom }
36+ AnchorChanges { target: column;
37+ anchors.verticalCenter: parent.verticalCenter;
38+ anchors.top: undefined
39+ }
40+ }
41+
42+ transitions: Transition {
43+ ParallelAnimation {
44+ AnchorAnimation {
45+ targets: [bgImage, column]
46+ duration: UbuntuAnimation.SlowDuration
47+ easing.type: Easing.OutCirc
48+ }
49+ NumberAnimation {
50+ targets: [bgImage,column]
51+ property: "opacity"
52+ from: 0
53+ to: 1
54+ duration: UbuntuAnimation.SlowDuration
55+ easing.type: Easing.OutCirc
56+ }
57+ }
58+ }
59+
60+ Image {
61+ id: bgImage
62+ source: wideMode ? "data/Desktop_splash_screen_bkg.png" : "data/Phone_splash_screen_bkg.png"
63+ scale: Image.PreserveAspectFit
64+ anchors.left: parent.left
65+ anchors.right: parent.right
66+ anchors.bottom: parent.top // outside to let it slide down
67+ visible: opacity > 0
68+ }
69+
70+ Item {
2971 id: column
30− anchors.fill: content
31− spacing: units.gu(1)
72+ anchors.leftMargin: leftMargin
73+ anchors.rightMargin: rightMargin
74+ anchors.left: parent.left
75+ anchors.right: parent.right
76+ anchors.top: parent.bottom // outside to let it slide in
77+ height: childrenRect.height
78+ visible: opacity > 0
3279
3380 Label {
81+ id: welcomeLabel
3482 anchors.left: parent.left
3583 anchors.right: parent.right
84+ horizontalAlignment: Text.AlignHCenter
3685 wrapMode: Text.Wrap
37− fontSize: "large"
38− font.bold: true
39− text: i18n.tr("Nice work!")
86+ fontSize: "x-large"
87+ font.weight: Font.Light
88+ lineHeight: 1.2
89+ text: i18n.tr("Welcome to Ubuntu")
90+ color: whiteColor
4091 }
4192
4293 Label {
94+ id: welcomeText
4395 anchors.left: parent.left
4496 anchors.right: parent.right
97+ anchors.top: welcomeLabel.bottom
98+ anchors.topMargin: units.gu(2)
99+ horizontalAlignment: Text.AlignHCenter
45100 wrapMode: Text.Wrap
46− text: i18n.tr("Your device is now ready to use.")
101+ fontSize: "large"
102+ font.weight: Font.Light
103+ lineHeight: 1.2
104+ text: i18n.tr("You are ready to use your device now")
105+ color: whiteColor
47106 }
48− }
49107
50− Component {
51− id: forwardButton
52− LocalComponents.StackButton {
53− text: i18n.tr("Finish")
54− onClicked: root.quitWizard()
108+ Rectangle {
109+ anchors {
110+ top: welcomeText.bottom
111+ horizontalCenter: parent.horizontalCenter
112+ topMargin: units.gu(4)
113+ }
114+ color: "transparent"
115+ border.width: units.dp(1)
116+ border.color: whiteColor
117+ radius: units.dp(4)
118+ width: buttonLabel.paintedWidth + units.gu(6)
119+ height: buttonLabel.paintedHeight + units.gu(1.8)
120+
121+ Label {
122+ id: buttonLabel
123+ color: whiteColor
124+ text: i18n.tr("Get Started")
125+ fontSize: "medium"
126+ anchors.centerIn: parent
127+ }
128+ AbstractButton {
129+ objectName: "finishButton"
130+ anchors.fill: parent
131+ onClicked: root.quitWizard()
132+ }
55133 }
56134 }
57135}
qml/Wizard/Pages/data/Desktop_header_bkg.pngnew file

Binary file not shown.

qml/Wizard/Pages/data/Desktop_splash_screen_bkg.pngnew file

Binary file not shown.

qml/Wizard/Pages/data/Phone_header_bkg.pngnew file

Binary file not shown.

qml/Wizard/Pages/data/Phone_splash_screen_bkg.pngnew file

Binary file not shown.

qml/Wizard/Pages/data/Tick@30.pngnew file

Binary file not shown.

qml/Wizard/Pages/data/meet_ubuntu_simcard@30.pngdeleted

Binary file not shown.

qml/Wizard/Pages/data/timezonemap/map.pngnew file

Binary file not shown.

qml/Wizard/Pages/data/timezonemap/pin.pngnew file

Binary file not shown.

qml/Wizard/Pages/data/timezonemap/timezone_-1.pngnew file

Binary file not shown.

qml/Wizard/Pages/data/timezonemap/timezone_-10.pngnew file

Binary file not shown.

qml/Wizard/Pages/data/timezonemap/timezone_-11.pngnew file

Binary file not shown.

qml/Wizard/Pages/data/timezonemap/timezone_-2.pngnew file

Binary file not shown.

qml/Wizard/Pages/data/timezonemap/timezone_-3.5.pngnew file

Binary file not shown.

qml/Wizard/Pages/data/timezonemap/timezone_-3.pngnew file

Binary file not shown.

qml/Wizard/Pages/data/timezonemap/timezone_-4.5.pngnew file

Binary file not shown.

qml/Wizard/Pages/data/timezonemap/timezone_-4.pngnew file

Binary file not shown.

qml/Wizard/Pages/data/timezonemap/timezone_-5.pngnew file

Binary file not shown.

qml/Wizard/Pages/data/timezonemap/timezone_-6.pngnew file

Binary file not shown.

qml/Wizard/Pages/data/timezonemap/timezone_-7.pngnew file

Binary file not shown.

qml/Wizard/Pages/data/timezonemap/timezone_-8.pngnew file

Binary file not shown.

qml/Wizard/Pages/data/timezonemap/timezone_-9.5.pngnew file

Binary file not shown.

qml/Wizard/Pages/data/timezonemap/timezone_-9.pngnew file

Binary file not shown.

qml/Wizard/Pages/data/timezonemap/timezone_0.pngnew file

Binary file not shown.

qml/Wizard/Pages/data/timezonemap/timezone_1.pngnew file

Binary file not shown.

qml/Wizard/Pages/data/timezonemap/timezone_10.5.pngnew file

Binary file not shown.

qml/Wizard/Pages/data/timezonemap/timezone_10.pngnew file

Binary file not shown.

qml/Wizard/Pages/data/timezonemap/timezone_11.pngnew file

Binary file not shown.

qml/Wizard/Pages/data/timezonemap/timezone_12.75.pngnew file

Binary file not shown.

qml/Wizard/Pages/data/timezonemap/timezone_12.pngnew file

Binary file not shown.

qml/Wizard/Pages/data/timezonemap/timezone_13.pngnew file

Binary file not shown.

qml/Wizard/Pages/data/timezonemap/timezone_2.pngnew file

Binary file not shown.

qml/Wizard/Pages/data/timezonemap/timezone_3.5.pngnew file

Binary file not shown.

qml/Wizard/Pages/data/timezonemap/timezone_3.pngnew file

Binary file not shown.

qml/Wizard/Pages/data/timezonemap/timezone_4.5.pngnew file

Binary file not shown.

qml/Wizard/Pages/data/timezonemap/timezone_4.pngnew file

Binary file not shown.

qml/Wizard/Pages/data/timezonemap/timezone_5.5.pngnew file

Binary file not shown.

qml/Wizard/Pages/data/timezonemap/timezone_5.75.pngnew file

Binary file not shown.

qml/Wizard/Pages/data/timezonemap/timezone_5.pngnew file

Binary file not shown.

qml/Wizard/Pages/data/timezonemap/timezone_6.5.pngnew file

Binary file not shown.

qml/Wizard/Pages/data/timezonemap/timezone_6.pngnew file

Binary file not shown.

qml/Wizard/Pages/data/timezonemap/timezone_7.pngnew file

Binary file not shown.

qml/Wizard/Pages/data/timezonemap/timezone_8.5.pngnew file

Binary file not shown.

qml/Wizard/Pages/data/timezonemap/timezone_8.pngnew file

Binary file not shown.

qml/Wizard/Pages/data/timezonemap/timezone_9.5.pngnew file

Binary file not shown.

qml/Wizard/Pages/data/timezonemap/timezone_9.pngnew file

Binary file not shown.

qml/Wizard/Pages/here-terms.qml+72−65
@@ -15,8 +15,6 @@
1515 */
1616
1717import QtQuick 2.4
18−import AccountsService 0.1
19−import Qt.labs.folderlistmodel 2.1
2018import Ubuntu.Components 1.3
2119import Ubuntu.Web 0.2
2220import ".." as LocalComponents
@@ -26,86 +24,93 @@ LocalComponents.Page {
2624
2725 title: i18n.tr("Terms & Conditions")
2826 customBack: true
27+ customTitle: true
2928
30− FolderListModel {
31− id: termsModel
32− folder: AccountsService.hereLicensePath
33− nameFilters: ["*.html"]
34− showDirs: false
35− showOnlyReadable: true
36− onCountChanged: loadFileContent()
37− }
38−
39− function makeFileName(lang, country) {
40− return lang + "_" + country + ".html"
41− }
42−
43− function defaultCountryForLanguage(lang) {
44− if (lang === "da") return "DK"
45− if (lang === "en") return "US"
46− if (lang === "ko") return "KR"
47− if (lang === "zh") return "CN"
48− return lang.toUpperCase()
49− }
50−
51− function determineFileName() {
52− var codes = i18n.language.split(".")[0].split("_")
53− var defaultCountry = defaultCountryForLanguage(codes[0])
54− if (codes.count === 1)
55− codes = [codes[0], defaultCountry]
56− var perfectMatch = makeFileName(codes[0], codes[1])
57− var nearMatch = makeFileName(codes[0], defaultCountry)
58− var nearMatchExists = false
59−
60− for (var i = 0; i < termsModel.count; i++) {
61− var fileName = termsModel.get(i, "fileName")
62− if (fileName == perfectMatch) {
63− return perfectMatch
64− } else if (fileName == nearMatch) {
65− nearMatchExists = true
66− }
67− }
68−
69− if (nearMatchExists) {
70− return nearMatch
29+ onBackClicked: {
30+ if (webview.visible) {
31+ showBrowser(false);
7132 } else {
72− return makeFileName("en", "US")
33+ pageStack.prev();
7334 }
7435 }
7536
76− function loadFileContent() {
77− var xhr = new XMLHttpRequest
78− xhr.open("GET", AccountsService.hereLicensePath + "/" + determineFileName())
79− xhr.onreadystatechange = function() {
80− if (xhr.readyState == XMLHttpRequest.DONE) {
81− termsLabel.text = xhr.responseText
82− }
83− }
84− xhr.send()
85− }
86−
87− onBackClicked: {
88− if (webview.visible) {
89− termsLabel.visible = true
37+ function showBrowser(show) {
38+ if (show) {
39+ label1.visible = false;
40+ label2.visible = false;
41+ label3.visible = false;
42+ label4.visible = false;
43+ webview.visible = true;
9044 } else {
91− pageStack.prev()
45+ webview.visible = false;
46+ label1.visible = true;
47+ label2.visible = true;
48+ label3.visible = true;
49+ label4.visible = true;
9250 }
9351 }
9452
9553 Column {
9654 id: column
9755 anchors.fill: content
56+ anchors.leftMargin: staticMargin
57+ anchors.rightMargin: wideMode ? parent.rightMargin : staticMargin
58+ spacing: units.gu(3)
59+
60+ Label {
61+ id: label1
62+ anchors.left: parent.left
63+ anchors.right: parent.right
64+ wrapMode: Text.Wrap
65+ color: textColor
66+ fontSize: "small"
67+ font.weight: Font.Light
68+ lineHeight: 1.2
69+ text: i18n.tr("Your device uses positioning technologies provided by HERE.")
70+ }
71+
72+ Label {
73+ id: label2
74+ anchors.left: parent.left
75+ anchors.right: parent.right
76+ wrapMode: Text.Wrap
77+ color: textColor
78+ fontSize: "small"
79+ font.weight: Font.Light
80+ lineHeight: 1.2
81+ text: i18n.tr("To provide you with positioning services and to improve their quality, HERE collects " +
82+ "information about nearby cell towers and Wi-Fi hotspots around your current location " +
83+ "whenever your position is being found.")
84+ }
85+
86+ Label {
87+ id: label3
88+ anchors.left: parent.left
89+ anchors.right: parent.right
90+ wrapMode: Text.Wrap
91+ color: textColor
92+ fontSize: "small"
93+ font.weight: Font.Light
94+ lineHeight: 1.2
95+ text: i18n.tr("The information collected is used to analyze the service and to " +
96+ "improve the use of service, but not to identify you personally.")
97+ }
9898
9999 Label {
100− id: termsLabel
101− objectName: "termsLabel"
100+ id: label4
102101 anchors.left: parent.left
103102 anchors.right: parent.right
104103 wrapMode: Text.Wrap
105− linkColor: theme.palette.normal.foregroundText
104+ color: textColor
105+ fontSize: "small"
106+ font.weight: Font.Light
107+ lineHeight: 1.2
108+ linkColor: UbuntuColors.orange
109+ text: i18n.tr("By continuing, you agree to the HERE platform <a href=\"http://here.com/terms/service-terms\">Service Terms</a> " +
110+ "and <a href=\"http://here.com/privacy/privacy-policy\">Privacy Policy</a>.");
106111 onLinkActivated: {
107− webview.url = link
108− termsLabel.visible = false
112+ showBrowser(true);
113+ webview.url = link;
109114 }
110115 }
111116
@@ -114,8 +119,10 @@ LocalComponents.Page {
114119 objectName: "webview"
115120 anchors.left: parent.left
116121 anchors.right: parent.right
122+ anchors.leftMargin: -leftMargin
123+ anchors.rightMargin: -rightMargin
117124 height: parent.height
118− visible: !termsLabel.visible
125+ visible: false
119126 }
120127 }
121128}
qml/Wizard/Pages/passwd-confirm.qml → qml/Wizard/Pages/passcode-confirm.qmlrenamed+12−29
@@ -26,11 +26,10 @@ import "../../Components" as UnityComponents
2626 */
2727
2828LocalComponents.Page {
29− id: passwdConfirmPage
30− objectName: "passwdConfirmPage"
31− forwardButtonSourceComponent: forwardButton
32−
33− skip: root.passwordMethod === UbuntuSecurityPrivacyPanel.Swipe
29+ id: passcodeConfirmPage
30+ objectName: "passcodeConfirmPage"
31+ customTitle: true
32+ backButtonText: i18n.tr("Cancel")
3433
3534 // If we are entering this page, clear any saved password and get focus
3635 onEnabledChanged: if (enabled) lockscreen.clear(false)
@@ -38,32 +37,26 @@ LocalComponents.Page {
3837 UnityComponents.Lockscreen {
3938 id: lockscreen
4039 anchors {
41− fill: parent
42− topMargin: topMargin
43− leftMargin: leftMargin
44− rightMargin: rightMargin
45− bottomMargin: buttonMargin
40+ fill: content
4641 }
4742
48− infoText: root.passwordMethod === UbuntuSecurityPrivacyPanel.Passphrase ?
49− i18n.tr("Confirm passphrase") :
50− i18n.tr("Confirm passcode")
43+ infoText: i18n.tr("Confirm passcode")
44+
45+ errorText: i18n.tr("Incorrect passcode.") + "\n" + i18n.ctr("Enter the passcode again", "Please re-enter.")
5146
52− errorText: root.passwordMethod === UbuntuSecurityPrivacyPanel.Passphrase ?
53− i18n.tr("Sorry, incorrect passphrase.") + "\n" + i18n.tr("Please try again.") :
54− i18n.tr("Sorry, incorrect passcode.") + "\n" + i18n.tr("Please try again.")
47+ foregroundColor: textColor
5548
5649 showEmergencyCallButton: false
5750 showCancelButton: false
58− alphaNumeric: root.passwordMethod === UbuntuSecurityPrivacyPanel.Passphrase
51+ alphaNumeric: false
5952 minPinLength: 4
6053 maxPinLength: 4
6154
6255 onEntered: {
6356 if (passphrase === root.password) {
64− confirmTimer.start()
57+ confirmTimer.start();
6558 } else {
66− clear(true)
59+ clear(true);
6760 }
6861 }
6962
@@ -73,14 +66,4 @@ LocalComponents.Page {
7366 onTriggered: pageStack.next()
7467 }
7568 }
76−
77− Component {
78− id: forwardButton
79− LocalComponents.StackButton {
80− visible: root.passwordMethod === UbuntuSecurityPrivacyPanel.Passphrase
81− enabled: root.password === lockscreen.passphrase
82− text: i18n.tr("Continue")
83− onClicked: pageStack.next()
84− }
85− }
8669}
qml/Wizard/Pages/passcode-desktop.qmlnew file+132
@@ -0,0 +1,132 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+import QtQuick.Layouts 1.1
19+import Ubuntu.Components 1.3
20+import ".." as LocalComponents
21+
22+/**
23+ * See the main passwd-type page for an explanation of why we don't actually
24+ * directly set the password here.
25+ */
26+
27+LocalComponents.Page {
28+ id: passwdSetPage
29+ objectName: "passcodeDesktopPage"
30+ title: i18n.tr("Lock Screen Passcode")
31+ forwardButtonSourceComponent: forwardButton
32+
33+ readonly property alias password: passwordField.text
34+ readonly property alias password2: password2Field.text
35+
36+ GridLayout {
37+ id: column
38+ columns: 1
39+ rows: 3
40+ anchors.fill: content
41+ anchors.leftMargin: leftMargin
42+ anchors.rightMargin: rightMargin
43+ anchors.topMargin: customMargin
44+ rowSpacing: units.gu(3)
45+
46+ Label {
47+ id: infoLabel
48+ objectName: "infoLabel"
49+ anchors {
50+ left: parent.left
51+ right: parent.right
52+ }
53+ wrapMode: Text.Wrap
54+ font.weight: Font.Light
55+ color: textColor
56+ text: i18n.tr("Enter 4 numbers to setup your passcode")
57+ }
58+
59+ GridLayout {
60+ columns: 2
61+ columnSpacing: units.gu(2)
62+ rowSpacing: units.gu(2)
63+
64+ Label {
65+ text: i18n.tr("Choose passcode")
66+ color: textColor
67+ }
68+ LocalComponents.WizardTextField {
69+ Layout.fillWidth: true
70+ id: passwordField
71+ objectName: "passwordField"
72+ echoMode: TextInput.Password
73+ inputMethodHints: Qt.ImhDigitsOnly
74+ validator: RegExpValidator { regExp: /^\d{4}$/ }
75+ maximumLength: 4
76+ onAccepted: password2Field.forceActiveFocus()
77+ }
78+
79+ Label {
80+ text: i18n.tr("Confirm passcode")
81+ color: textColor
82+ }
83+ LocalComponents.WizardTextField {
84+ Layout.fillWidth: true
85+ id: password2Field
86+ objectName: "password2Field"
87+ echoMode: TextInput.Password
88+ inputMethodHints: Qt.ImhDigitsOnly
89+ validator: RegExpValidator { regExp: /^\d{4}$/ }
90+ maximumLength: 4
91+ }
92+
93+ Label {
94+ Layout.row: 2
95+ Layout.column: 1
96+ id: errorLabel
97+ property bool hasError: password && password != password2
98+ wrapMode: Text.Wrap
99+ color: hasError ? errorColor : UbuntuColors.ash
100+ visible: password && password2
101+ fontSize: "small"
102+ text: {
103+ if (password) {
104+ if (password2.length < password2Field.maximumLength)
105+ return i18n.tr("Passcode too short");
106+ else if (password == password2)
107+ return i18n.tr("Passcodes match");
108+ else if (password2)
109+ return i18n.tr("Passcodes do not match");
110+ }
111+ return "";
112+ }
113+ }
114+ }
115+
116+ Item { // spacer
117+ Layout.fillHeight: true
118+ }
119+ }
120+
121+ Component {
122+ id: forwardButton
123+ LocalComponents.StackButton {
124+ text: i18n.tr("Next")
125+ enabled: password != "" && password == password2
126+ onClicked: {
127+ root.password = password;
128+ pageStack.next();
129+ }
130+ }
131+ }
132+}
qml/Wizard/Pages/passwd-set.qml → qml/Wizard/Pages/passcode-set.qmlrenamed+12−28
@@ -26,11 +26,10 @@ import "../../Components" as UnityComponents
2626 */
2727
2828LocalComponents.Page {
29− id: passwdSetPage
30− objectName: "passwdSetPage"
31− forwardButtonSourceComponent: forwardButton
32−
33− skip: root.passwordMethod === UbuntuSecurityPrivacyPanel.Swipe
29+ id: passcodeSetPage
30+ objectName: "passcodeSetPage"
31+ customTitle: true
32+ backButtonText: i18n.tr("Cancel")
3433
3534 // If we are entering this page, clear any saved password and get focus
3635 onEnabledChanged: if (enabled) lockscreen.clear(false)
@@ -43,52 +42,37 @@ LocalComponents.Page {
4342 Timer {
4443 id: confirmTimer
4544 interval: UbuntuAnimation.SnapDuration
46− onTriggered: pageStack.load(Qt.resolvedUrl("passwd-confirm.qml"));
45+ onTriggered: pageStack.load(Qt.resolvedUrl("passcode-confirm.qml"));
4746 }
4847
4948 UnityComponents.Lockscreen {
5049 id: lockscreen
5150 anchors {
52− fill: parent
53− topMargin: topMargin
54− leftMargin: leftMargin
55− rightMargin: rightMargin
56− bottomMargin: buttonMargin
51+ fill: content
5752 }
5853
59− infoText: root.passwordMethod === UbuntuSecurityPrivacyPanel.Passphrase ?
60− i18n.tr("Enter passphrase") :
61− i18n.tr("Choose your passcode")
54+ infoText: i18n.tr("Choose passcode")
55+ foregroundColor: textColor
6256
6357 // Note that the number four comes from PAM settings,
6458 // which we don't have a good way to interrogate. We
6559 // only do this matching instead of PAM because we want
6660 // to set the password via PAM in a different place
6761 // than this page. See comments at top of passwd-type file.
68− errorText: i18n.tr("Passphrase must be 4 characters long")
62+ errorText: i18n.tr("Passcode must be 4 characters long")
6963
7064 showEmergencyCallButton: false
7165 showCancelButton: false
72− alphaNumeric: root.passwordMethod === UbuntuSecurityPrivacyPanel.Passphrase
66+ alphaNumeric: false
7367 minPinLength: 4
7468 maxPinLength: 4
7569
7670 onEntered: {
7771 if (passphrase.length >= 4) {
78− passwdSetPage.confirm();
72+ passcodeSetPage.confirm();
7973 } else {
80− lockscreen.clear(true)
74+ lockscreen.clear(true);
8175 }
8276 }
8377 }
84−
85− Component {
86− id: forwardButton
87− LocalComponents.StackButton {
88− visible: root.passwordMethod === UbuntuSecurityPrivacyPanel.Passphrase
89− enabled: lockscreen.passphrase.length >= 4
90− text: i18n.tr("Continue")
91− onClicked: passwdSetPage.confirm()
92− }
93− }
9478}
qml/Wizard/Pages/password-set.qmlnew file+149
@@ -0,0 +1,149 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+import QtQuick.Layouts 1.1
19+import Ubuntu.Components 1.3
20+import ".." as LocalComponents
21+
22+/**
23+ * See the main passwd-type page for an explanation of why we don't actually
24+ * directly set the password here.
25+ */
26+
27+LocalComponents.Page {
28+ id: passwdSetPage
29+ objectName: "passwdSetPage"
30+ title: i18n.tr("Lock Screen Password")
31+ forwardButtonSourceComponent: forwardButton
32+
33+ readonly property alias password: passwordField.text
34+ readonly property alias password2: password2Field.text
35+ readonly property bool passwordsMatching: password == password2 && password.trim().length > 7
36+
37+ Flickable {
38+ id: column
39+ clip: true
40+ flickableDirection: Flickable.VerticalFlick
41+ anchors.fill: content
42+ anchors.leftMargin: parent.leftMargin
43+ anchors.rightMargin: parent.rightMargin
44+ anchors.topMargin: customMargin
45+
46+ bottomMargin: Qt.inputMethod.keyboardRectangle.height - height
47+
48+ Behavior on contentY { UbuntuNumberAnimation {} }
49+
50+ // info label
51+ Label {
52+ id: infoLabel
53+ objectName: "infoLabel"
54+ anchors {
55+ left: parent.left
56+ right: parent.right
57+ }
58+ wrapMode: Text.Wrap
59+ font.weight: Font.Light
60+ color: textColor
61+ text: i18n.tr("Enter at least 8 characters")
62+ }
63+
64+ // password
65+ Label {
66+ id: pass1Label
67+ anchors {
68+ left: parent.left
69+ right: parent.right
70+ top: infoLabel.bottom
71+ topMargin: units.gu(3)
72+ }
73+ text: i18n.tr("Choose password")
74+ color: textColor
75+ }
76+ LocalComponents.WizardTextField {
77+ id: passwordField
78+ anchors {
79+ left: parent.left
80+ right: parent.right
81+ top: pass1Label.bottom
82+ topMargin: units.gu(1)
83+ }
84+ objectName: "passwordField"
85+ echoMode: TextInput.Password
86+ onAccepted: password2Field.forceActiveFocus()
87+ onActiveFocusChanged: {
88+ if (activeFocus) {
89+ column.contentY = pass1Label.y
90+ }
91+ }
92+ }
93+
94+ // password 2
95+ Label {
96+ id: pass2Label
97+ anchors {
98+ left: parent.left
99+ right: parent.right
100+ top: passwordField.bottom
101+ topMargin: units.gu(3)
102+ }
103+ text: i18n.tr("Confirm password")
104+ color: textColor
105+ }
106+ LocalComponents.WizardTextField {
107+ anchors {
108+ left: parent.left
109+ right: parent.right
110+ top: pass2Label.bottom
111+ topMargin: units.gu(1)
112+ }
113+ id: password2Field
114+ objectName: "password2Field"
115+ echoMode: TextInput.Password
116+ onActiveFocusChanged: {
117+ if (activeFocus) {
118+ column.contentY = pass2Label.y
119+ }
120+ }
121+ }
122+
123+ // password meter
124+ LocalComponents.PasswordMeter {
125+ id: passMeter
126+ anchors {
127+ left: parent.left
128+ right: parent.right
129+ top: password2Field.bottom
130+ topMargin: units.gu(1)
131+ }
132+
133+ password: passwordField.text
134+ matching: passwordsMatching ? true : (password2.trim().length > 0 ? false : undefined)
135+ }
136+ }
137+
138+ Component {
139+ id: forwardButton
140+ LocalComponents.StackButton {
141+ text: i18n.tr("Next")
142+ enabled: passwordsMatching
143+ onClicked: {
144+ root.password = password;
145+ pageStack.next();
146+ }
147+ }
148+ }
149+}
qml/Wizard/Pages/sim.qmlnew file+108
@@ -0,0 +1,108 @@
1+/*
2+ * Copyright (C) 2013-2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+import MeeGo.QOfono 0.2
19+import Ubuntu.Components 1.3
20+import Ubuntu.Components.Popups 1.3
21+import Unity.Session 0.1
22+import ".." as LocalComponents
23+
24+LocalComponents.Page {
25+ objectName: "simPage"
26+
27+ title: i18n.tr("No SIM card installed")
28+ forwardButtonSourceComponent: forwardButton
29+ customTitle: true
30+ hasBackButton: false
31+
32+ property bool hadSIM: root.simManager0.present || root.simManager1.present
33+
34+ Timer {
35+ id: timer
36+ interval: 300
37+ running: true
38+ onTriggered: {
39+ hadSIM = root.simManager0.present || root.simManager1.present;
40+ }
41+ }
42+
43+ Connections {
44+ target: root.modemManager
45+ onGotSimCardChanged: {
46+ if (!hadSIM && root.modemManager.gotSimCard) { // show the restart dialog in case a SIM gets inserted
47+ restartDialog.visible = true;
48+ }
49+ }
50+ }
51+
52+ Dialog {
53+ id: restartDialog
54+ title: i18n.tr("SIM card added")
55+ text: i18n.tr("You must restart the device to access the mobile network.")
56+
57+ Button {
58+ id: restartButton
59+ text: i18n.tr("Restart")
60+ onClicked: {
61+ DBusUnitySessionService.reboot();
62+ }
63+ }
64+ }
65+
66+ Column {
67+ anchors {
68+ fill: content
69+ leftMargin: staticMargin
70+ rightMargin: staticMargin
71+ }
72+ spacing: units.gu(3)
73+
74+ Label {
75+ anchors.left: parent.left
76+ anchors.right: parent.right
77+ wrapMode: Text.Wrap
78+ text: i18n.tr("You won’t be able to make calls or use text messaging without a SIM.")
79+ fontSize: "small"
80+ font.weight: Font.Light
81+ lineHeight: 1.2
82+ color: restartDialog.visible ? theme.palette.normal.backgroundText : textColor
83+ }
84+
85+ Label {
86+ anchors.left: parent.left
87+ anchors.right: parent.right
88+ wrapMode: Text.Wrap
89+ textFormat: Text.RichText
90+ text: i18n.tr("To proceed with no SIM tap Skip.")
91+ fontSize: "small"
92+ font.weight: Font.Light
93+ lineHeight: 1.2
94+ color: restartDialog.visible ? theme.palette.normal.backgroundText : textColor
95+ }
96+ }
97+
98+ Component {
99+ id: forwardButton
100+ LocalComponents.StackButton {
101+ text: i18n.tr("Skip")
102+ onClicked: {
103+ seenSIMPage = true;
104+ pageStack.next();
105+ }
106+ }
107+ }
108+}
qml/Wizard/PasswordMeter.qmlnew file+106
@@ -0,0 +1,106 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+import Ubuntu.Components 1.3
19+
20+Item {
21+ property string password: ""
22+ readonly property int passwordScore: scorePassword(password)
23+ property var matching
24+
25+ function scorePassword(pass) {
26+ var score = 0;
27+ if (!pass)
28+ return score;
29+
30+ // award every unique letter until 5 repetitions
31+ var letters = Object();
32+ for (var i=0; i<pass.length; i++) {
33+ letters[pass[i]] = (letters[pass[i]] || 0) + 1;
34+ score += 5.0 / letters[pass[i]];
35+ }
36+
37+ // bonus points for mixing it up
38+ var variations = {
39+ digits: /\d/.test(pass),
40+ lower: /[a-z]/.test(pass),
41+ upper: /[A-Z]/.test(pass),
42+ nonWords: /\W/.test(pass),
43+ }
44+
45+ var variationCount = 0;
46+ for (var check in variations) {
47+ variationCount += (variations[check] === true) ? 1 : 0;
48+ }
49+ score += (variationCount - 1) * 10;
50+
51+ return parseInt(score);
52+ }
53+
54+ Rectangle {
55+ id: passwordStrengthMeter
56+ anchors {
57+ left: parent.left
58+ right: parent.right
59+ }
60+ width: parent.width
61+ height: units.gu(0.5)
62+ color: {
63+ if (passwordScore > 80)
64+ return okColor;
65+ else if (passwordScore > 60)
66+ return "#f9c00f";
67+ else if (passwordScore >= 30)
68+ return errorColor;
69+
70+ return errorColor;
71+ }
72+ visible: password.length > 0
73+ }
74+
75+ Label {
76+ id: passwordStrengthInfo
77+ anchors {
78+ left: parent.left
79+ right: parent.right
80+ top: passwordStrengthMeter.bottom
81+ topMargin: units.gu(.5)
82+ }
83+ wrapMode: Text.Wrap
84+ text: {
85+ if (matching !== undefined) {
86+ if (matching)
87+ return i18n.tr("Passwords match");
88+ else if (!matching)
89+ return i18n.tr("Passwords do not match");
90+ }
91+
92+ if (passwordScore > 80)
93+ return i18n.tr("Strong password");
94+ else if (passwordScore > 60)
95+ return i18n.tr("Fair password");
96+ else if (passwordScore >= 30)
97+ return i18n.tr("Weak password");
98+
99+ return i18n.tr("Very weak password");
100+ }
101+ color: UbuntuColors.ash
102+ fontSize: "small"
103+ font.weight: Font.Light
104+ visible: password.length > 0
105+ }
106+}
qml/Wizard/StackButton.qml+6−11
@@ -25,23 +25,18 @@ AbstractButton {
2525 property bool backArrow: false
2626
2727 width: label.width
28− height: label.height + units.gu(4)
28+ height: units.gu(5)
2929
3030 Label {
3131 id: label
3232 anchors.verticalCenter: parent.verticalCenter
3333 anchors.left: parent.left
3434 anchors.right: parent.right
35− color: enabled ? theme.palette.normal.backgroundText : Qt.darker(theme.palette.normal.backgroundText, 1.5)
36− text: {
37− if (backArrow) {
38− // Translators: This is the arrow for "Back" buttons
39− return i18n.tr("〈 %1".arg(stackButton.text))
40− } else {
41− // Translators: This is the arrow for "Forward" buttons
42− return i18n.tr("%1 〉".arg(stackButton.text))
43− }
44− }
35+ color: "#333333"
36+ opacity: enabled ? 1.0 : 0.5
37+ fontSize: "medium"
38+ font.weight: backArrow ? Font.Light : Font.DemiBold
39+ text: stackButton.text
4540 horizontalAlignment: backArrow ? Text.AlignLeft : Text.AlignRight
4641 }
4742}
qml/Wizard/Wizard.qml−9
@@ -22,9 +22,6 @@ import "../Components"
2222Showable {
2323 id: root
2424
25− // The background wallpaper to use
26− property string background
27−
2825 // This is a bool instead of an alias because Loader classes like to emit
2926 // changed signals for 'active' during startup even if they aren't actually
3027 // changing values. Having it cached as a proper Qml bool property prevents
@@ -46,12 +43,6 @@ Showable {
4643 active: System.wizardEnabled
4744 source: "Pages.qml"
4845
49− Binding {
50− target: loader.item
51− property: "background"
52− value: root.background
53− }
54−
5546 Connections {
5647 target: loader.item
5748 onQuit: root.hide()
qml/Wizard/WizardTextField.qmlnew file+25
@@ -0,0 +1,25 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+import Ubuntu.Components 1.3
19+import Ubuntu.Components.Themes.Ambiance 1.3
20+
21+TextField {
22+ id: styledItem
23+ style: TextFieldStyle {
24+ }
25+}
src/MouseTouchAdaptor.cpp+53−9
@@ -71,6 +71,8 @@ using QTest::QTouchEventSequence;
7171namespace {
7272MouseTouchAdaptor *g_instance = nullptr;
7373
74+const Qt::KeyboardModifiers TRI_PRESS_MODIFIER = Qt::ShiftModifier|Qt::ControlModifier|Qt::AltModifier;
75+
7476Qt::MouseButton translateMouseButton(xcb_button_t detail)
7577{
7678 switch (detail) {
@@ -81,10 +83,25 @@ Qt::MouseButton translateMouseButton(xcb_button_t detail)
8183 default: return Qt::NoButton;
8284 }
8385}
86+
87+Qt::KeyboardModifiers translateMofidier(uint32_t mod)
88+{
89+ Qt::KeyboardModifiers qtMod = Qt::NoModifier;
90+
91+ if (mod & 0x01) qtMod |= Qt::ShiftModifier;
92+ if (mod & 0x04) qtMod |= Qt::ControlModifier;
93+ if (mod & 0x08) qtMod |= Qt::AltModifier;
94+ if (mod & 0x80) qtMod |= Qt::MetaModifier;
95+
96+ return qtMod;
97+}
8498} // end of anonymous namespace
8599
86100MouseTouchAdaptor::MouseTouchAdaptor()
87− : QObject(nullptr), m_leftButtonIsPressed(false), m_enabled(true)
101+ : QObject(nullptr)
102+ , m_leftButtonIsPressed(false)
103+ , m_triPressModifier(false)
104+ , m_enabled(true)
88105{
89106 QCoreApplication::instance()->installNativeEventFilter(this);
90107
@@ -192,17 +209,20 @@ bool MouseTouchAdaptor::xi2HandleEvent(xcb_ge_event_t *event)
192209 return handleButtonPress(
193210 static_cast<WId>(xiDeviceEvent->event),
194211 xiDeviceEvent->detail,
212+ xiDeviceEvent->mods.base_mods,
195213 fixed1616ToReal(xiDeviceEvent->event_x),
196214 fixed1616ToReal(xiDeviceEvent->event_y));
197215 case XI_ButtonRelease:
198216 return handleButtonRelease(
199217 static_cast<WId>(xiDeviceEvent->event),
200218 xiDeviceEvent->detail,
219+ xiDeviceEvent->mods.base_mods,
201220 fixed1616ToReal(xiDeviceEvent->event_x),
202221 fixed1616ToReal(xiDeviceEvent->event_y));
203222 case XI_Motion:
204223 return handleMotionNotify(
205224 static_cast<WId>(xiDeviceEvent->event),
225+ xiDeviceEvent->mods.base_mods,
206226 fixed1616ToReal(xiDeviceEvent->event_x),
207227 fixed1616ToReal(xiDeviceEvent->event_y));
208228 return true;
@@ -232,17 +252,18 @@ bool MouseTouchAdaptor::nativeEventFilter(const QByteArray & eventType,
232252 switch (xcbEvent->response_type & ~0x80) {
233253 case XCB_BUTTON_PRESS: {
234254 auto pressEvent = reinterpret_cast<xcb_button_press_event_t *>(xcbEvent);
235− return handleButtonPress(static_cast<WId>(pressEvent->event), pressEvent->detail,
255+ return handleButtonPress(static_cast<WId>(pressEvent->event), pressEvent->detail, 0,
236256 pressEvent->event_x, pressEvent->event_y);
237257 }
238258 case XCB_BUTTON_RELEASE: {
239259 auto releaseEvent = reinterpret_cast<xcb_button_release_event_t *>(xcbEvent);
240− return handleButtonRelease(static_cast<WId>(releaseEvent->event), releaseEvent->detail,
260+ return handleButtonRelease(static_cast<WId>(releaseEvent->event), releaseEvent->detail, 0,
241261 releaseEvent->event_x, releaseEvent->event_y);
242262 }
243263 case XCB_MOTION_NOTIFY: {
244264 auto motionEvent = reinterpret_cast<xcb_motion_notify_event_t *>(xcbEvent);
245− return handleMotionNotify(static_cast<WId>(motionEvent->event), motionEvent->event_x, motionEvent->event_y);
265+ return handleMotionNotify(static_cast<WId>(motionEvent->event), 0,
266+ motionEvent->event_x, motionEvent->event_y);
246267 }
247268 case XCB_GE_GENERIC:
248269 if (m_xi2Enabled) {
@@ -255,9 +276,10 @@ bool MouseTouchAdaptor::nativeEventFilter(const QByteArray & eventType,
255276 };
256277}
257278
258−bool MouseTouchAdaptor::handleButtonPress(WId windowId, uint32_t detail, int x, int y)
279+bool MouseTouchAdaptor::handleButtonPress(WId windowId, uint32_t detail, uint32_t modifiers, int x, int y)
259280{
260281 Qt::MouseButton button = translateMouseButton(detail);
282+ Qt::KeyboardModifiers qtMod = translateMofidier(modifiers);
261283
262284 // Just eat the event if it wasn't a left mouse press
263285 if (button != Qt::LeftButton)
@@ -270,19 +292,24 @@ bool MouseTouchAdaptor::handleButtonPress(WId windowId, uint32_t detail, int x,
270292 QTouchEventSequence touchEvent = QTest::touchEvent(targetWindow, m_touchDevice,
271293 false /* autoCommit */);
272294 touchEvent.press(0 /* touchId */, windowPos);
295+ if (qtMod == TRI_PRESS_MODIFIER) {
296+ touchEvent.press(1, windowPos);
297+ touchEvent.press(2, windowPos);
298+ m_triPressModifier = true;
299+ }
273300 touchEvent.commit(false /* processEvents */);
274301
275302 m_leftButtonIsPressed = true;
276303 return true;
277304}
278305
279−bool MouseTouchAdaptor::handleButtonRelease(WId windowId, uint32_t detail, int x, int y)
306+bool MouseTouchAdaptor::handleButtonRelease(WId windowId, uint32_t detail, uint32_t, int x, int y)
280307{
281308 Qt::MouseButton button = translateMouseButton(detail);
282309
283− // Just eat the event if it wasn't a left mouse release
310+ // Don't eat the event if it wasn't a left mouse press
284311 if (button != Qt::LeftButton)
285− return true;
312+ return false;
286313
287314 QWindow *targetWindow = findQWindowWithXWindowID(windowId);
288315
@@ -291,17 +318,23 @@ bool MouseTouchAdaptor::handleButtonRelease(WId windowId, uint32_t detail, int x
291318 QTouchEventSequence touchEvent = QTest::touchEvent(targetWindow, m_touchDevice,
292319 false /* autoCommit */);
293320 touchEvent.release(0 /* touchId */, windowPos);
321+ if (m_triPressModifier) {
322+ touchEvent.release(1, windowPos);
323+ touchEvent.release(2, windowPos);
324+ }
294325 touchEvent.commit(false /* processEvents */);
295326
296327 m_leftButtonIsPressed = false;
328+ m_triPressModifier = false;
297329 return true;
298330}
299331
300−bool MouseTouchAdaptor::handleMotionNotify(WId windowId, int x, int y)
332+bool MouseTouchAdaptor::handleMotionNotify(WId windowId, uint32_t modifiers, int x, int y)
301333{
302334 if (!m_leftButtonIsPressed) {
303335 return true;
304336 }
337+ Qt::KeyboardModifiers qtMod = translateMofidier(modifiers);
305338
306339 QWindow *targetWindow = findQWindowWithXWindowID(windowId);
307340
@@ -310,6 +343,17 @@ bool MouseTouchAdaptor::handleMotionNotify(WId windowId, int x, int y)
310343 QTouchEventSequence touchEvent = QTest::touchEvent(targetWindow, m_touchDevice,
311344 false /* autoCommit */);
312345 touchEvent.move(0 /* touchId */, windowPos);
346+ if (m_triPressModifier) {
347+ if (qtMod == TRI_PRESS_MODIFIER) {
348+ touchEvent.move(1, windowPos);
349+ touchEvent.move(2, windowPos);
350+ } else {
351+ // released modifiers
352+ touchEvent.release(1, windowPos);
353+ touchEvent.release(2, windowPos);
354+ m_triPressModifier = false;
355+ }
356+ }
313357 touchEvent.commit(false /* processEvents */);
314358
315359 return true;
src/MouseTouchAdaptor.h+5−3
@@ -51,13 +51,15 @@ private:
5151 void fetchXInput2Info();
5252 bool xi2HandleEvent(xcb_ge_event_t *event);
5353
54− bool handleButtonPress(WId windowId, uint32_t detail, int x, int y);
55− bool handleButtonRelease(WId windowId, uint32_t detail, int x, int y);
56− bool handleMotionNotify(WId windowId, int x, int y);
54+ bool handleButtonPress(WId windowId, uint32_t detail, uint32_t modifiers, int x, int y);
55+ bool handleButtonRelease(WId windowId, uint32_t detail, uint32_t modifiers, int x, int y);
56+ bool handleMotionNotify(WId windowId, uint32_t modifiers, int x, int y);
5757 QWindow *findQWindowWithXWindowID(WId windowId);
5858
5959 QTouchDevice *m_touchDevice;
6060 bool m_leftButtonIsPressed;
61+ bool m_triPressModifier;
62+
6163
6264 bool m_enabled;
6365
tests/libs/UbuntuGestures/tst_TouchRegistry.cpp+59
@@ -42,6 +42,10 @@ public:
4242 QSet<int> ownedTouches;
4343 QSet<int> lostTouches;
4444 QList<TouchMemento> unownedTouchEvents;
45+
46+Q_SIGNALS:
47+ void gainedOwnership();
48+ void lostOwnership();
4549};
4650
4751class tst_TouchRegistry : public QObject
@@ -68,6 +72,7 @@ private Q_SLOTS:
6872 void removeOldUndecidedCandidates();
6973 void interimOwnerWontGetUnownedTouchEvents();
7074 void candidateVanishes();
75+ void candicateOwnershipReentrace();
7176
7277private:
7378 TouchRegistry *touchRegistry;
@@ -864,6 +869,58 @@ void tst_TouchRegistry::candidateVanishes()
864869 QVERIFY(mainCandidate.ownedTouches.contains(0));
865870}
866871
872+/*
873+ Regression test for canidate reentrance
874+
875+ Bug caused by candidates getting removed during ownership resolution
876+ */
877+void tst_TouchRegistry::candicateOwnershipReentrace()
878+{
879+ DummyCandidate mainCandidate;
880+ DummyCandidate candicate2;
881+ DummyCandidate candicate3;
882+
883+ {
884+ QList<QTouchEvent::TouchPoint> touchPoints;
885+ touchPoints.append(QTouchEvent::TouchPoint(0));
886+ touchPoints[0].setState(Qt::TouchPointPressed);
887+ QTouchEvent touchEvent(QEvent::TouchBegin,
888+ 0 /* device */,
889+ Qt::NoModifier,
890+ Qt::TouchPointPressed,
891+ touchPoints);
892+ touchRegistry->update(&touchEvent);
893+ }
894+
895+ // Re-entrance!
896+ connect(&candicate2, &DummyCandidate::lostOwnership, this, [&]() {
897+ touchRegistry->removeCandidateOwnerForTouch(0, &candicate2);
898+ });
899+
900+ touchRegistry->addCandidateOwnerForTouch(0, &mainCandidate);
901+ touchRegistry->addCandidateOwnerForTouch(0, &candicate2);
902+ touchRegistry->addCandidateOwnerForTouch(0, &candicate3);
903+
904+ {
905+ QList<QTouchEvent::TouchPoint> touchPoints;
906+ touchPoints.append(QTouchEvent::TouchPoint(0));
907+ touchPoints[0].setState(Qt::TouchPointMoved);
908+ QTouchEvent touchEvent(QEvent::TouchUpdate,
909+ 0 /* device */,
910+ Qt::NoModifier,
911+ Qt::TouchPointMoved,
912+ touchPoints);
913+ touchRegistry->update(&touchEvent);
914+ }
915+
916+ touchRegistry->requestTouchOwnership(0, &mainCandidate);
917+
918+ QCOMPARE(mainCandidate.ownedTouches.count(), 1);
919+ QCOMPARE(candicate2.lostTouches.count(), 1);
920+ QCOMPARE(candicate3.lostTouches.count(), 1);
921+}
922+
923+
867924////////////// TouchMemento //////////
868925
869926TouchMemento::TouchMemento(const QTouchEvent *touchEvent)
@@ -897,8 +954,10 @@ bool DummyCandidate::event(QEvent *e)
897954
898955 if (touchOwnershipEvent->gained()) {
899956 ownedTouches.insert(touchOwnershipEvent->touchId());
957+ Q_EMIT gainedOwnership();
900958 } else {
901959 lostTouches.insert(touchOwnershipEvent->touchId());
960+ Q_EMIT lostOwnership();
902961 }
903962 return true;
904963 } else if (e->type() == UnownedTouchEvent::unownedTouchEventType()) {
tests/mocks/AccountsService/AccountsService.cpp+23−3
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013, 2015 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -12,8 +12,6 @@
1212 *
1313 * You should have received a copy of the GNU General Public License
1414 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− *
16− * Author: Michael Terry <michael.terry@canonical.com>
1715 */
1816
1917#include "AccountsService.h"
@@ -153,6 +151,28 @@ bool AccountsService::hereLicensePathValid() const
153151 return !m_hereLicensePath.isNull();
154152}
155153
154+QString AccountsService::realName() const
155+{
156+ return m_realName;
157+}
158+
159+void AccountsService::setRealName(const QString &realName)
160+{
161+ m_realName = realName;
162+ Q_EMIT realNameChanged();
163+}
164+
165+QString AccountsService::email() const
166+{
167+ return m_email;
168+}
169+
170+void AccountsService::setEmail(const QString &email)
171+{
172+ m_email = email;
173+ Q_EMIT emailChanged();
174+}
175+
156176QStringList AccountsService::keymaps() const
157177{
158178 if (!m_kbdMap.isEmpty()) {
tests/mocks/AccountsService/AccountsService.h+12−5
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013, 2015 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -12,9 +12,6 @@
1212 *
1313 * You should have received a copy of the GNU General Public License
1414 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− *
16− * Authors: Gerry Boland <gerry.boland@canonical.com>
17− * Michael Terry <michael.terry@canonical.com>
1815 */
1916
2017#ifndef UNITY_MOCK_ACCOUNTSSERVICE_H
@@ -70,6 +67,8 @@ class AccountsService: public QObject
7067 Q_PROPERTY(bool hereLicensePathValid // qml sees a null string as "", so we use proxy setting for that
7168 READ hereLicensePathValid
7269 NOTIFY hereLicensePathChanged)
70+ Q_PROPERTY(QString realName READ realName WRITE setRealName NOTIFY realNameChanged)
71+ Q_PROPERTY(QString email READ email WRITE setEmail NOTIFY emailChanged)
7372 Q_PROPERTY(QStringList keymaps
7473 READ keymaps
7574 WRITE setKeymaps // only in mock
@@ -78,7 +77,7 @@ class AccountsService: public QObject
7877public:
7978 enum PasswordDisplayHint {
8079 Keyboard,
81− Numeric,
80+ Numeric
8281 };
8382
8483 explicit AccountsService(QObject *parent = 0);
@@ -103,6 +102,10 @@ public:
103102 QString hereLicensePath() const;
104103 void setHereLicensePath(const QString &path);
105104 bool hereLicensePathValid() const;
105+ QString realName() const;
106+ void setRealName(const QString &realName);
107+ QString email() const;
108+ void setEmail(const QString &email);
106109 QStringList keymaps() const;
107110 void setKeymaps(const QStringList &keymaps);
108111
@@ -117,6 +120,8 @@ Q_SIGNALS:
117120 void failedLoginsChanged();
118121 void hereEnabledChanged();
119122 void hereLicensePathChanged();
123+ void realNameChanged();
124+ void emailChanged();
120125 void keymapsChanged();
121126
122127private:
@@ -129,7 +134,9 @@ private:
129134 bool m_demoEdges;
130135 bool m_hereEnabled;
131136 QString m_hereLicensePath;
137+ QString m_realName;
132138 QStringList m_kbdMap;
139+ QString m_email;
133140};
134141
135142#endif
tests/mocks/MeeGo/QOfono/MockOfonoSimManager.qml+1
@@ -23,6 +23,7 @@ Item {
2323 property string modemPath
2424 readonly property alias present: d.present
2525 readonly property alias ready: d.ready
26+ readonly property var preferredLanguages: ["de", "fr", "it", "en"]
2627
2728 QtObject {
2829 id: d
tests/mocks/Ubuntu/Connectivity/plugin.cpp+1
@@ -36,6 +36,7 @@ QmlConnectivityNetworkingPlugin::registerTypes(const char *uri)
3636{
3737 // @uri Ubuntu.Connectivity
3838 qmlRegisterSingletonType<NetworkingStatus>(uri, 1, 0, "NetworkingStatus", networkingStatusSingletonProvider);
39+ qmlRegisterSingletonType<NetworkingStatus>(uri, 1, 0, "Connectivity", networkingStatusSingletonProvider);
3940}
4041
4142void
tests/mocks/Ubuntu/SystemSettings/CMakeLists.txt+2
@@ -1,2 +1,4 @@
11add_subdirectory(LanguagePlugin)
22add_subdirectory(SecurityPrivacy)
3+add_subdirectory(TimeDate)
4+add_subdirectory(Diagnostics)
tests/mocks/Ubuntu/SystemSettings/Diagnostics/CMakeLists.txtnew file+1
@@ -0,0 +1 @@
1+add_unity8_mock(Ubuntu.SystemSettings.Diagnostics 1.0 Ubuntu/SystemSettings/Diagnostics)
tests/mocks/Ubuntu/SystemSettings/Diagnostics/MockDiagnostics.qmlnew file+24
@@ -0,0 +1,24 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+
19+Item {
20+ property bool reportCrashes: true
21+ function setReportCrashes(report) {
22+ reportCrashes = report;
23+ }
24+}
tests/mocks/Ubuntu/SystemSettings/Diagnostics/qmldirnew file+2
@@ -0,0 +1,2 @@
1+module Ubuntu.SystemSettings.Diagnostics
2+UbuntuDiagnostics 1.0 MockDiagnostics.qml
tests/mocks/Ubuntu/SystemSettings/LanguagePlugin/MockLanguagePlugin.qml+2−2
@@ -17,7 +17,7 @@
1717import QtQuick 2.4
1818
1919Item {
20− property var languageNames: ["English", "French", "Spanish"]
21− property var languageCodes: ["en", "fr", "es"]
20+ property var languageNames: ["English (United States)", "French (France)", "Spanish (Spain)"]
21+ property var languageCodes: ["en_US", "fr_FR", "es_ES"]
2222 property int currentLanguage: 0
2323}
tests/mocks/Ubuntu/SystemSettings/SecurityPrivacy/MockSecurityPrivacy.cpp+1−1
@@ -31,5 +31,5 @@ QString MockSecurityPrivacy::setSecurity(const QString &oldPasswd, const QString
3131{
3232 m_type = newType;
3333 Q_EMIT setSecurityCalled(oldPasswd, newPasswd, newType);
34− return "";
34+ return QString();
3535}
tests/mocks/Ubuntu/SystemSettings/TimeDate/CMakeLists.txtnew file+1
@@ -0,0 +1 @@
1+add_unity8_mock(Ubuntu.SystemSettings.TimeDate 1.0 Ubuntu/SystemSettings/TimeDate)
tests/mocks/Ubuntu/SystemSettings/TimeDate/MockTimeDate.qmlnew file+25
@@ -0,0 +1,25 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+
19+Item {
20+ id: root
21+ property string timeZone: "Europe/Prague"
22+ onTimeZoneChanged: root.timeZoneChangedCalled(root.timeZone);
23+
24+ signal timeZoneChangedCalled(string tz);
25+}
tests/mocks/Ubuntu/SystemSettings/TimeDate/qmldirnew file+2
@@ -0,0 +1,2 @@
1+module Ubuntu.SystemSettings.TimeDate
2+UbuntuTimeDatePanel 1.0 MockTimeDate.qml
tests/mocks/Unity/Application/ApplicationInfo.cpp+21−3
@@ -44,6 +44,7 @@ ApplicationInfo::ApplicationInfo(const QString &appId, QObject *parent)
4444 , m_isTouchApp(true)
4545 , m_exemptFromLifecycle(false)
4646 , m_manualSurfaceCreation(false)
47+ , m_shellChrome(Mir::NormalChrome)
4748{
4849}
4950
@@ -63,6 +64,7 @@ ApplicationInfo::ApplicationInfo(QObject *parent)
6364 , m_isTouchApp(true)
6465 , m_exemptFromLifecycle(false)
6566 , m_manualSurfaceCreation(false)
67+ , m_shellChrome(Mir::NormalChrome)
6668{
6769}
6870
@@ -102,9 +104,11 @@ void ApplicationInfo::setSession(Session* session)
102104 if (m_session) {
103105 m_session->setApplication(this);
104106 m_session->setParent(this);
107+ m_session->setFullscreen(m_fullscreen);
105108 SessionManager::singleton()->registerSession(m_session);
106109 connect(m_session, &Session::surfaceAdded,
107110 this, &ApplicationInfo::onSessionSurfaceAdded);
111+ connect(m_session, &Session::fullscreenChanged, this, &ApplicationInfo::fullscreenChanged);
108112
109113 if (!m_manualSurfaceCreation) {
110114 QTimer::singleShot(500, m_session, &Session::createSurface);
@@ -191,12 +195,17 @@ void ApplicationInfo::setFocused(bool value)
191195
192196void ApplicationInfo::setFullscreen(bool value)
193197{
194− if (value != m_fullscreen) {
195− m_fullscreen = value;
196− Q_EMIT fullscreenChanged(value);
198+ m_fullscreen = value;
199+ if (m_session) {
200+ m_session->setFullscreen(value);
197201 }
198202}
199203
204+bool ApplicationInfo::fullscreen() const
205+{
206+ return m_session ? m_session->fullscreen() : false;
207+}
208+
200209void ApplicationInfo::setManualSurfaceCreation(bool value)
201210{
202211 if (value != m_manualSurfaceCreation) {
@@ -262,6 +271,7 @@ void ApplicationInfo::onSessionSurfaceAdded(MirSurface* surface)
262271 } else {
263272 setState(Suspended);
264273 }
274+ surface->setShellChrome(m_shellChrome);
265275 }
266276}
267277
@@ -291,3 +301,11 @@ void ApplicationInfo::setInitialSurfaceSize(const QSize &size)
291301 Q_EMIT initialSurfaceSizeChanged(m_initialSurfaceSize);
292302 }
293303}
304+
305+void ApplicationInfo::setShellChrome(Mir::ShellChrome shellChrome)
306+{
307+ m_shellChrome = shellChrome;
308+ if (m_session && m_session->lastSurface()) {
309+ m_session->lastSurface()->setShellChrome(shellChrome);
310+ }
311+}
tests/mocks/Unity/Application/ApplicationInfo.h+5−1
@@ -24,6 +24,7 @@ class Session;
2424
2525// unity-api
2626#include <unity/shell/application/ApplicationInfoInterface.h>
27+#include <unity/shell/application/Mir.h>
2728
2829using namespace unity::shell::application;
2930
@@ -78,7 +79,7 @@ public:
7879 QString screenshot() const { return m_screenshotFileName; }
7980
8081 void setFullscreen(bool value);
81− bool fullscreen() const { return m_fullscreen; }
82+ bool fullscreen() const;
8283
8384 Qt::ScreenOrientations supportedOrientations() const override;
8485 void setSupportedOrientations(Qt::ScreenOrientations orientations);
@@ -97,6 +98,8 @@ public:
9798
9899 QSize initialSurfaceSize() const override;
99100 void setInitialSurfaceSize(const QSize &size) override;
101+
102+ Q_INVOKABLE void setShellChrome(Mir::ShellChrome shellChrome);
100103public:
101104 void setSession(Session* session);
102105 Session* session() const { return m_session; }
@@ -134,6 +137,7 @@ private:
134137 QSize m_initialSurfaceSize;
135138
136139 bool m_manualSurfaceCreation;
140+ Mir::ShellChrome m_shellChrome;
137141};
138142
139143Q_DECLARE_METATYPE(ApplicationInfo*)
tests/mocks/Unity/Application/ApplicationManager.cpp+13−26
@@ -151,10 +151,6 @@ void ApplicationManager::add(ApplicationInfo *application) {
151151
152152 beginInsertRows(QModelIndex(), m_runningApplications.size(), m_runningApplications.size());
153153 m_runningApplications.append(application);
154− endInsertRows();
155− Q_EMIT applicationAdded(application->appId());
156− Q_EMIT countChanged();
157− if (count() == 1) Q_EMIT emptyChanged(isEmpty()); // was empty but not anymore
158154
159155 connect(application, &ApplicationInfo::sessionChanged, this, [application, this]() {
160156 QModelIndex appIndex = findIndex(application);
@@ -171,6 +167,16 @@ void ApplicationManager::add(ApplicationInfo *application) {
171167 if (!appIndex.isValid()) return;
172168 Q_EMIT dataChanged(appIndex, appIndex, QVector<int>() << ApplicationManager::RoleState);
173169 });
170+ connect(application, &ApplicationInfo::stageChanged, this, [application, this]() {
171+ QModelIndex appIndex = findIndex(application);
172+ if (!appIndex.isValid()) return;
173+ Q_EMIT dataChanged(appIndex, appIndex, QVector<int>() << ApplicationManager::RoleStage);
174+ });
175+
176+ endInsertRows();
177+ Q_EMIT applicationAdded(application->appId());
178+ Q_EMIT countChanged();
179+ if (count() == 1) Q_EMIT emptyChanged(isEmpty()); // was empty but not anymore
174180}
175181
176182void ApplicationManager::remove(ApplicationInfo *application) {
@@ -202,23 +208,11 @@ void ApplicationManager::move(int from, int to) {
202208
203209ApplicationInfo* ApplicationManager::startApplication(const QString &appId,
204210 const QStringList &arguments)
205−{
206− return startApplication(appId, NoFlag, arguments);
207−}
208−
209−ApplicationInfo* ApplicationManager::startApplication(const QString &appId,
210− ExecFlags flags,
211− const QStringList &arguments)
212211{
213212 Q_UNUSED(arguments)
214213 ApplicationInfo *application = add(appId);
215214 if (!application)
216215 return 0;
217−
218− if (flags.testFlag(ApplicationManager::ForceMainStage)
219− && application->stage() == ApplicationInfo::SideStage) {
220− application->setStage(ApplicationInfo::MainStage);
221− }
222216 application->setState(ApplicationInfo::Starting);
223217
224218 return application;
@@ -317,7 +311,6 @@ void ApplicationManager::buildListOfAvailableApplications()
317311 application->setAppId("unity8-dash");
318312 application->setName("Unity 8 Mock Dash");
319313 application->setScreenshotId("unity8-dash");
320− application->setStage(ApplicationInfo::MainStage);
321314 application->setSupportedOrientations(Qt::PrimaryOrientation);
322315 m_availableApplications.append(application);
323316
@@ -326,7 +319,6 @@ void ApplicationManager::buildListOfAvailableApplications()
326319 application->setName("Dialer");
327320 application->setScreenshotId("dialer");
328321 application->setIconId("dialer-app");
329− application->setStage(ApplicationInfo::SideStage);
330322 application->setSupportedOrientations(Qt::PortraitOrientation
331323 | Qt::InvertedPortraitOrientation);
332324 m_availableApplications.append(application);
@@ -336,6 +328,7 @@ void ApplicationManager::buildListOfAvailableApplications()
336328 application->setName("Camera");
337329 application->setScreenshotId("camera");
338330 application->setIconId("camera");
331+ application->setShellChrome(Mir::LowChrome);
339332 application->setFullscreen(true);
340333 application->setSupportedOrientations(Qt::PortraitOrientation
341334 | Qt::LandscapeOrientation
@@ -349,7 +342,7 @@ void ApplicationManager::buildListOfAvailableApplications()
349342 application->setName("Gallery");
350343 application->setScreenshotId("gallery");
351344 application->setIconId("gallery");
352− application->setFullscreen(true);
345+ application->setShellChrome(Mir::LowChrome);
353346 application->setStage(ApplicationInfo::MainStage);
354347 m_availableApplications.append(application);
355348
@@ -358,12 +351,11 @@ void ApplicationManager::buildListOfAvailableApplications()
358351 application->setName("Facebook");
359352 application->setScreenshotId("facebook");
360353 application->setIconId("facebook");
361− application->setStage(ApplicationInfo::SideStage);
362354 m_availableApplications.append(application);
363355
364356 application = new ApplicationInfo(this);
365357 application->setAppId("webbrowser-app");
366− application->setFullscreen(true);
358+ application->setShellChrome(Mir::LowChrome);
367359 application->setName("Browser");
368360 application->setScreenshotId("browser");
369361 application->setIconId("browser");
@@ -374,7 +366,6 @@ void ApplicationManager::buildListOfAvailableApplications()
374366 application->setName("Twitter");
375367 application->setScreenshotId("twitter");
376368 application->setIconId("twitter");
377− application->setStage(ApplicationInfo::SideStage);
378369 m_availableApplications.append(application);
379370
380371 application = new ApplicationInfo(this);
@@ -389,7 +380,6 @@ void ApplicationManager::buildListOfAvailableApplications()
389380 application->setName("GMail");
390381 application->setIconId("gmail");
391382 application->setScreenshotId("gmail-webapp.svg");
392− application->setFullscreen(false);
393383 application->setStage(ApplicationInfo::MainStage);
394384 application->setSupportedOrientations(Qt::PortraitOrientation
395385 | Qt::LandscapeOrientation
@@ -402,7 +392,6 @@ void ApplicationManager::buildListOfAvailableApplications()
402392 application->setName("Music");
403393 application->setIconId("soundcloud");
404394 application->setScreenshotId("music");
405− application->setFullscreen(false);
406395 application->setStage(ApplicationInfo::MainStage);
407396 application->setSupportedOrientations(Qt::PortraitOrientation
408397 | Qt::LandscapeOrientation
@@ -423,14 +412,12 @@ void ApplicationManager::buildListOfAvailableApplications()
423412 application->setAppId("notes-app");
424413 application->setName("Notepad");
425414 application->setIconId("notepad");
426− application->setStage(ApplicationInfo::SideStage);
427415 m_availableApplications.append(application);
428416
429417 application = new ApplicationInfo(this);
430418 application->setAppId("calendar-app");
431419 application->setName("Calendar");
432420 application->setIconId("calendar");
433− application->setStage(ApplicationInfo::SideStage);
434421 m_availableApplications.append(application);
435422
436423 application = new ApplicationInfo(this);
tests/mocks/Unity/Application/ApplicationManager.h−8
@@ -46,11 +46,6 @@ class ApplicationManager : public ApplicationManagerInterface {
4646 RoleSession = RoleExemptFromLifecycle+1,
4747 RoleFullscreen,
4848 };
49− enum Flag {
50− NoFlag = 0x0,
51− ForceMainStage = 0x1,
52− };
53− Q_DECLARE_FLAGS(ExecFlags, Flag)
5449
5550 // QAbstractItemModel methods.
5651 int rowCount(const QModelIndex& parent = QModelIndex()) const override;
@@ -65,7 +60,6 @@ class ApplicationManager : public ApplicationManagerInterface {
6560 Q_INVOKABLE bool focusApplication(const QString &appId) override;
6661 Q_INVOKABLE void unfocusCurrentApplication() override;
6762 Q_INVOKABLE ApplicationInfo *startApplication(const QString &appId, const QStringList &arguments = QStringList()) override;
68− Q_INVOKABLE ApplicationInfo *startApplication(const QString &appId, ExecFlags flags, const QStringList &arguments = QStringList());
6963 Q_INVOKABLE bool stopApplication(const QString &appId) override;
7064
7165 QString focusedApplicationId() const override;
@@ -98,8 +92,6 @@ class ApplicationManager : public ApplicationManagerInterface {
9892 static ApplicationManager *the_application_manager;
9993};
10094
101−Q_DECLARE_OPERATORS_FOR_FLAGS(ApplicationManager::ExecFlags)
102−
10395Q_DECLARE_METATYPE(ApplicationManager*)
10496
10597#endif // APPLICATION_MANAGER_H
tests/mocks/Unity/Application/MirSurface.cpp+15
@@ -38,6 +38,7 @@ MirSurface::MirSurface(const QString& name,
3838 , m_width(-1)
3939 , m_height(-1)
4040 , m_slowToResize(false)
41+ , m_shellChrome(Mir::NormalChrome)
4142{
4243// qDebug() << "MirSurface::MirSurface() " << name;
4344 QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership);
@@ -133,6 +134,20 @@ void MirSurface::setOrientationAngle(Mir::OrientationAngle angle)
133134 Q_EMIT orientationAngleChanged(angle);
134135}
135136
137+Mir::ShellChrome MirSurface::shellChrome() const
138+{
139+ return m_shellChrome;
140+}
141+
142+void MirSurface::setShellChrome(Mir::ShellChrome shellChrome)
143+{
144+ if (shellChrome == m_shellChrome)
145+ return;
146+
147+ m_shellChrome = shellChrome;
148+ Q_EMIT shellChromeChanged(shellChrome);
149+}
150+
136151QString MirSurface::keymapLayout() const
137152{
138153 return m_keyMap.first;
tests/mocks/Unity/Application/MirSurface.h+4−3
@@ -74,6 +74,7 @@ public:
7474 int widthIncrement() const override { return m_widthIncrement; }
7575 int heightIncrement() const override { return m_heightIncrement; }
7676
77+ Mir::ShellChrome shellChrome() const override;
7778 QString keymapLayout() const override;
7879 QString keymapVariant() const override;
7980 Q_INVOKABLE void setKeymap(const QString &layout, const QString &variant) override;
@@ -82,6 +83,7 @@ public:
8283 // API for tests
8384
8485 Q_INVOKABLE void setLive(bool live);
86+ Q_INVOKABLE void setShellChrome(Mir::ShellChrome shellChrome);
8587
8688 void registerView(qintptr viewId);
8789 void unregisterView(qintptr viewId);
@@ -113,9 +115,6 @@ public:
113115 void setActiveFocus(bool);
114116
115117Q_SIGNALS:
116− void stateChanged(Mir::State);
117− void liveChanged(bool live);
118− void orientationAngleChanged(Mir::OrientationAngle angle);
119118 void widthChanged();
120119 void heightChanged();
121120 void slowToResizeChanged();
@@ -156,6 +155,8 @@ private:
156155 QSize m_delayedResize;
157156 QSize m_pendingResize;
158157
158+ Mir::ShellChrome m_shellChrome;
159+
159160 struct View {
160161 bool visible;
161162 };
tests/mocks/Unity/Application/MirSurfaceItem.cpp+26
@@ -100,6 +100,15 @@ bool MirSurfaceItem::live() const
100100 }
101101}
102102
103+Mir::ShellChrome MirSurfaceItem::shellChrome() const
104+{
105+ if (m_qmlSurface) {
106+ return m_qmlSurface->shellChrome();
107+ } else {
108+ return Mir::NormalChrome;
109+ }
110+}
111+
103112Mir::OrientationAngle MirSurfaceItem::orientationAngle() const
104113{
105114 if (m_qmlSurface) {
@@ -156,6 +165,10 @@ void MirSurfaceItem::createQmlContentItem()
156165
157166void MirSurfaceItem::touchEvent(QTouchEvent * event)
158167{
168+ if (event->type() == QEvent::TouchBegin) {
169+ m_touchTrail.clear();
170+ }
171+
159172 if (event->touchPointStates() & Qt::TouchPointPressed) {
160173 ++m_touchPressCount;
161174 Q_EMIT touchPressCountChanged(m_touchPressCount);
@@ -163,6 +176,19 @@ void MirSurfaceItem::touchEvent(QTouchEvent * event)
163176 ++m_touchReleaseCount;
164177 Q_EMIT touchReleaseCountChanged(m_touchReleaseCount);
165178 }
179+
180+ Q_FOREACH(QTouchEvent::TouchPoint touchPoint, event->touchPoints()) {
181+ QString id(touchPoint.id());
182+ QVariantList list = m_touchTrail[id].toList();
183+ list.append(QVariant::fromValue(touchPoint.pos()));
184+ if (list.count() > 100) list.pop_front();
185+ m_touchTrail[id] = list;
186+ }
187+
188+ if (m_qmlItem) {
189+ QQmlProperty touchTrail(m_qmlItem, "touchTrail");
190+ touchTrail.write(m_touchTrail);
191+ }
166192}
167193
168194void MirSurfaceItem::mousePressEvent(QMouseEvent * event)
tests/mocks/Unity/Application/MirSurfaceItem.h+2
@@ -47,6 +47,7 @@ public:
4747 Mir::Type type() const override;
4848 QString name() const override;
4949 bool live() const override;
50+ Mir::ShellChrome shellChrome() const override;
5051
5152 Mir::State surfaceState() const override;
5253 void setSurfaceState(Mir::State) override {}
@@ -123,6 +124,7 @@ private:
123124 int m_touchReleaseCount;
124125 int m_mousePressCount;
125126 int m_mouseReleaseCount;
127+ QVariantMap m_touchTrail;
126128
127129 FillMode m_fillMode{Stretch};
128130
tests/mocks/Unity/Application/Session.cpp+26−1
@@ -34,6 +34,7 @@ Session::Session(const QString &name,
3434 , m_surface(nullptr)
3535 , m_parentSession(nullptr)
3636 , m_children(new SessionModel(this))
37+ , m_fullscreen(false)
3738{
3839// qDebug() << "Session::Session() " << this->name();
3940
@@ -71,6 +72,25 @@ void Session::release()
7172 deleteLater();
7273}
7374
75+void Session::updateFullscreenProperty()
76+{
77+ if (m_surfaces.rowCount() > 0) {
78+ // TODO: Figure out something better
79+ setFullscreen(lastSurface()->state() == Mir::FullscreenState);
80+ } else {
81+ // Keep the current value of the fullscreen property until we get a new
82+ // surface
83+ }
84+}
85+
86+void Session::setFullscreen(bool fullscreen)
87+{
88+ if (m_fullscreen != fullscreen) {
89+ m_fullscreen = fullscreen;
90+ Q_EMIT fullscreenChanged(m_fullscreen);
91+ }
92+}
93+
7494void Session::setApplication(ApplicationInfo* application)
7595{
7696 if (m_application == application)
@@ -82,15 +102,18 @@ void Session::setApplication(ApplicationInfo* application)
82102
83103void Session::appendSurface(MirSurface* surface)
84104{
85− // qDebug() << "Session::appendSurface - session=" << name() << "surface=" << surface;
105+ qDebug() << "Session::appendSurface - session=" << name() << "surface=" << surface;
86106
87107 m_surfaces.insert(m_surfaces.rowCount(), surface);
88108
109+ connect(surface, &MirSurfaceInterface::stateChanged, this, &Session::updateFullscreenProperty);
89110 connect(surface, &QObject::destroyed,
90111 this, [this, surface]() { this->removeSurface(surface); });
91112
92113 Q_EMIT lastSurfaceChanged(surface);
93114 Q_EMIT surfaceAdded(surface);
115+
116+ updateFullscreenProperty();
94117}
95118
96119void Session::removeSurface(MirSurface* surface)
@@ -99,6 +122,8 @@ void Session::removeSurface(MirSurface* surface)
99122 if (m_surfaces.contains(surface)) {
100123 m_surfaces.remove(surface);
101124 }
125+
126+ updateFullscreenProperty();
102127}
103128
104129void Session::setScreenshot(const QUrl& screenshot)
tests/mocks/Unity/Application/Session.h+7
@@ -47,6 +47,7 @@ public:
4747 //getters
4848 QString name() const { return m_name; }
4949 bool live() const { return m_live; }
50+ bool fullscreen() const { return m_fullscreen; }
5051 ApplicationInfo* application() const { return m_application; }
5152 MirSurface *lastSurface() const;
5253 ObjectListModel<MirSurface>* surfaces() const;
@@ -57,6 +58,7 @@ public:
5758 void removeSurface(MirSurface* surface);
5859 void setScreenshot(const QUrl& m_screenshot);
5960 void setLive(bool live);
61+ void setFullscreen(bool fullscreen);
6062
6163 Q_INVOKABLE void addChildSession(Session* session);
6264 void insertChildSession(uint index, Session* session);
@@ -68,6 +70,7 @@ Q_SIGNALS:
6870 void liveChanged(bool live);
6971 void surfaceAdded(MirSurface *surface);
7072 void lastSurfaceChanged(MirSurface *surface);
73+ void fullscreenChanged(bool fullscreen);
7174
7275 // internal mock use
7376 void deregister();
@@ -75,6 +78,9 @@ Q_SIGNALS:
7578public Q_SLOTS:
7679 Q_INVOKABLE void createSurface();
7780
81+private Q_SLOTS:
82+ void updateFullscreenProperty();
83+
7884private:
7985 SessionModel* childSessions() const;
8086 void setParentSession(Session* session);
@@ -87,6 +93,7 @@ private:
8793 Session* m_parentSession;
8894 SessionModel* m_children;
8995 ObjectListModel<MirSurface> m_surfaces;
96+ bool m_fullscreen;
9097
9198 friend class ApplicationTestInterface;
9299};
tests/mocks/Unity/Application/resources/MirSurfaceItem.qml+59
@@ -29,6 +29,9 @@ Rectangle {
2929 property alias screenshotSource: screenshotImage.source
3030 property int orientationAngle
3131
32+ property var touchTrail
33+ onTouchTrailChanged: canvas.requestPaint()
34+
3235 Image {
3336 id: screenshotImage
3437 anchors.fill: parent
@@ -67,4 +70,60 @@ Rectangle {
6770 width: (rotation == 0 || rotation == 180 ? parent.width : parent.height)
6871 height:(rotation == 0 || rotation == 180 ? parent.height : parent.width)
6972 }
73+
74+ Canvas {
75+ id: canvas
76+ anchors.fill: parent
77+ onPaint: {
78+ // get context to draw with
79+ var ctx = getContext("2d")
80+ ctx.reset(0, 0, canvas.width, canvas.height);
81+ ctx.lineWidth = 20;
82+
83+ if (touchTrail === undefined) return;
84+
85+ var i = 0;
86+ for (var prop in touchTrail) {
87+ var trail = touchTrail[prop];
88+
89+ if (trail.length > 0) {
90+ ctx.fillStyle = Qt.rgba(0, 0, 1, 0.3);
91+ ctx.strokeStyle = Qt.rgba(0, 0, 1, 0.3);
92+ ctx.beginPath();
93+ ctx.moveTo(trail[0].x, trail[0].y);
94+ ctx.ellipse(trail[0].x-1, trail[0].y-1, 2, 2);
95+ ctx.stroke();
96+
97+ ctx.beginPath();
98+ if (i == 0) {
99+ ctx.strokeStyle = Qt.rgba(1, 1, 0, 0.3);
100+ } else if (i == 1) {
101+ ctx.strokeStyle = Qt.rgba(0, 1, 1, 0.3);
102+
103+ } else if (i == 2) {
104+ ctx.strokeStyle = Qt.rgba(1, 0, 1, 0.3);
105+
106+ } else if (i == 3) {
107+ ctx.strokeStyle = Qt.rgba(0, 0.5, 0.5, 0.3);
108+
109+ } else {
110+ ctx.strokeStyle = Qt.rgba(0.7, 0.5, 0, 0.3);
111+ }
112+
113+ for (var i = 1; i < trail.length; i++) {
114+ ctx.lineTo(trail[i].x, trail[i].y)
115+ }
116+ ctx.stroke();
117+
118+ ctx.beginPath();
119+ ctx.fillStyle = Qt.rgba(1, 0, 0, 0.3);
120+ ctx.strokeStyle = Qt.rgba(1, 0, 0, 0.3);
121+ ctx.ellipse(trail[i-1].x-1, trail[i-1].y-1, 2, 2);
122+ ctx.stroke();
123+ }
124+ i++;
125+ }
126+
127+ }
128+ }
70129}
tests/mocks/Utils/CMakeLists.txt+2−1
@@ -16,14 +16,15 @@ set(QMLPLUGIN_SRC
1616 ${CMAKE_SOURCE_DIR}/plugins/Utils/windowscreenshotprovider.cpp
1717 ${CMAKE_SOURCE_DIR}/plugins/Utils/easingcurve.cpp
1818 ${CMAKE_SOURCE_DIR}/plugins/Utils/inputwatcher.cpp
19+ ${CMAKE_SOURCE_DIR}/plugins/Utils/timezoneFormatter.cpp
1920 ${CMAKE_SOURCE_DIR}/plugins/Utils/applicationsfiltermodel.cpp
2021 ${CMAKE_SOURCE_DIR}/plugins/Utils/inputeventgenerator.cpp
2122 ${CMAKE_SOURCE_DIR}/plugins/Utils/deviceconfigparser.cpp
23+ ${CMAKE_SOURCE_DIR}/plugins/Utils/globalfunctions.cpp
2224 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationManagerInterface.h
2325 constants.cpp
2426 plugin.cpp
2527 windowstatestorage.cpp
26− plugin.cpp
2728 )
2829
2930add_library(FakeUtils-qml SHARED
tests/mocks/Utils/plugin.cpp+12
@@ -35,9 +35,11 @@
3535#include <windowkeysfilter.h>
3636#include <windowscreenshotprovider.h>
3737#include <easingcurve.h>
38+#include <timezoneFormatter.h>
3839#include <applicationsfiltermodel.h>
3940#include <inputeventgenerator.h>
4041#include <deviceconfigparser.h>
42+#include <globalfunctions.h>
4143
4244static QObject *createWindowStateStorage(QQmlEngine *engine, QJSEngine *scriptEngine)
4345{
@@ -53,6 +55,13 @@ static QObject *createConstants(QQmlEngine *engine, QJSEngine *scriptEngine)
5355 return new Constants();
5456}
5557
58+static QObject *createGlobalFunctions(QQmlEngine *engine, QJSEngine *scriptEngine)
59+{
60+ Q_UNUSED(engine)
61+ Q_UNUSED(scriptEngine)
62+ return new GlobalFunctions();
63+}
64+
5665void FakeUtilsPlugin::registerTypes(const char *uri)
5766{
5867 Q_ASSERT(uri == QLatin1String("Utils"));
@@ -65,10 +74,13 @@ void FakeUtilsPlugin::registerTypes(const char *uri)
6574 qmlRegisterSingletonType<WindowStateStorage>(uri, 0, 1, "WindowStateStorage", createWindowStateStorage);
6675 qmlRegisterType<InputWatcher>(uri, 0, 1, "InputWatcher");
6776 qmlRegisterSingletonType<Constants>(uri, 0, 1, "Constants", createConstants);
77+ qmlRegisterSingletonType<TimezoneFormatter>(uri, 0, 1, "TimezoneFormatter",
78+ [](QQmlEngine*, QJSEngine*) -> QObject* { return new TimezoneFormatter; });
6879 qmlRegisterType<ActiveFocusLogger>(uri, 0, 1, "ActiveFocusLogger");
6980 qmlRegisterType<ApplicationsFilterModel>(uri, 0, 1, "ApplicationsFilterModel");
7081 qmlRegisterType<InputEventGenerator>(uri, 0, 1, "InputEventGenerator");
7182 qmlRegisterType<DeviceConfigParser>(uri, 0, 1, "DeviceConfigParser");
83+ qmlRegisterSingletonType<GlobalFunctions>(uri, 0, 1, "Functions", createGlobalFunctions);
7284}
7385
7486void FakeUtilsPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
tests/mocks/Utils/windowstatestorage.cpp+13
@@ -16,6 +16,8 @@
1616
1717#include "windowstatestorage.h"
1818
19+#include <unity/shell/application/ApplicationInfoInterface.h>
20+
1921WindowStateStorage::WindowStateStorage(QObject *parent):
2022 QObject(parent)
2123{
@@ -45,10 +47,21 @@ QRect WindowStateStorage::getGeometry(const QString &windowId, const QRect &defa
4547 return m_geometry.value(windowId).toRect();
4648}
4749
50+void WindowStateStorage::saveStage(const QString &appId, int stage)
51+{
52+ m_stage[appId] = stage;
53+}
54+
55+int WindowStateStorage::getStage(const QString &appId) const
56+{
57+ return m_stage.value(appId, unity::shell::application::ApplicationInfoInterface::MainStage);
58+}
59+
4860void WindowStateStorage::clear()
4961{
5062 m_state.clear();
5163 m_geometry.clear();
64+ m_stage.clear();
5265}
5366
5467void WindowStateStorage::saveState(const QString &windowId, WindowState state)
tests/mocks/Utils/windowstatestorage.h+4
@@ -36,6 +36,9 @@ public:
3636 Q_INVOKABLE void saveGeometry(const QString &windowId, const QRect &rect);
3737 Q_INVOKABLE QRect getGeometry(const QString &windowId, const QRect &defaultValue);
3838
39+ Q_INVOKABLE void saveStage(const QString &appId, int stage);
40+ Q_INVOKABLE int getStage(const QString &appId) const;
41+
3942 // Only in the mock, to easily restore a fresh state
4043 Q_INVOKABLE void clear();
4144
@@ -47,5 +50,6 @@ private:
4750 QVariantMap geometry() const;
4851
4952 QHash<QString, WindowState> m_state;
53+ QHash<QString, int> m_stage;
5054 QVariantMap m_geometry;
5155};
tests/mocks/Wizard/CMakeLists.txt+6−1
@@ -1,4 +1,5 @@
11include_directories(
2+ ${GLIB_INCLUDE_DIRS} ${GEONAMES_INCLUDE_DIRS}
23 ${CMAKE_SOURCE_DIR}/plugins/Wizard
34)
45
@@ -6,7 +7,11 @@ add_library(MockWizard-qml MODULE
67 mockplugin.cpp
78 MockSystem.cpp
89 ${CMAKE_SOURCE_DIR}/plugins/Wizard/PageList.cpp
10+ ${CMAKE_SOURCE_DIR}/plugins/Wizard/timezonemodel.cpp
11+ ${CMAKE_SOURCE_DIR}/plugins/Wizard/LocalePlugin.cpp
12+ ${CMAKE_SOURCE_DIR}/plugins/Wizard/Status.cpp
913)
1014
11−qt5_use_modules(MockWizard-qml Qml)
15+qt5_use_modules(MockWizard-qml DBus Qml Concurrent)
16+target_link_libraries(MockWizard-qml ${GLIB_LDFLAGS} ${GEONAMES_LDFLAGS})
1217add_unity8_mock(Wizard 0.1 Wizard TARGETS MockWizard-qml)
tests/mocks/Wizard/MockSystem.cpp+4−2
@@ -14,6 +14,8 @@
1414 * with this program. If not, see <http://www.gnu.org/licenses/>.
1515 */
1616
17+#include <QDebug>
18+
1719#include "MockSystem.h"
1820
1921MockSystem::MockSystem()
@@ -33,7 +35,7 @@ void MockSystem::setWizardEnabled(bool enabled)
3335 Q_EMIT wizardEnabledChanged();
3436}
3537
36−void MockSystem::updateSessionLanguage(const QString &locale)
38+void MockSystem::updateSessionLocale(const QString &locale)
3739{
38− Q_EMIT updateSessionLanguageCalled(locale);
40+ Q_EMIT updateSessionLocaleCalled(locale);
3941}
tests/mocks/Wizard/MockSystem.h+2−2
@@ -32,11 +32,11 @@ public:
3232 void setWizardEnabled(bool enabled);
3333
3434public Q_SLOTS:
35− void updateSessionLanguage(const QString &locale);
35+ void updateSessionLocale(const QString &locale);
3636
3737Q_SIGNALS:
3838 void wizardEnabledChanged();
39− void updateSessionLanguageCalled(const QString &locale); // only in mock
39+ void updateSessionLocaleCalled(const QString &locale); // only in mock
4040
4141private:
4242 Q_DISABLE_COPY(MockSystem)
tests/mocks/Wizard/mockplugin.cpp+7−8
@@ -17,19 +17,18 @@
1717#include "mockplugin.h"
1818#include "MockSystem.h"
1919#include "PageList.h"
20+#include "timezonemodel.h"
21+#include "LocalePlugin.h"
22+#include "Status.h"
2023
2124#include <QtQml/qqml.h>
2225
23−static QObject *system_provider(QQmlEngine *engine, QJSEngine *scriptEngine)
24−{
25− Q_UNUSED(engine)
26− Q_UNUSED(scriptEngine)
27− return new MockSystem();
28−}
29−
3026void MockWizardPlugin::registerTypes(const char *uri)
3127{
3228 Q_ASSERT(uri == QLatin1String("Wizard"));
3329 qmlRegisterType<PageList>(uri, 0, 1, "PageList");
34− qmlRegisterSingletonType<MockSystem>(uri, 0, 1, "System", system_provider);
30+ qmlRegisterSingletonType<MockSystem>(uri, 0, 1, "System", [](QQmlEngine*, QJSEngine*) -> QObject* { return new MockSystem; });
31+ qmlRegisterSingletonType<Status>(uri, 0, 1, "Status", [](QQmlEngine*, QJSEngine*) -> QObject* { return new Status; });
32+ qmlRegisterType<TimeZoneLocationModel>(uri, 0, 1, "TimeZoneModel");
33+ qmlRegisterType<LocalePlugin>(uri, 0, 1, "LocalePlugin");
3534}
tests/plugins/Ubuntu/Gestures/CMakeLists.txt+7−1
@@ -20,6 +20,11 @@ macro(build_gesture_test CLASSNAME)
2020 target_link_libraries(${CLASSNAME}TestExec UbuntuGesturesQml UbuntuGestures)
2121endmacro()
2222
23+set(UNITY_IMPORT_PATHS
24+ ${CMAKE_BINARY_DIR}/tests/utils/modules
25+ ${UNITY_PLUGINPATH}
26+)
27+
2328macro(add_gesture_unit_test CLASSNAME)
2429 build_gesture_test(${CLASSNAME})
2530 add_unity8_unittest(${CLASSNAME} ${CLASSNAME}TestExec
@@ -36,7 +41,7 @@ macro(add_gesture_ui_test CLASSNAME)
3641 DEPENDS UbuntuGesturesTest-qmlfiles
3742 ${ARGN}
3843 )
39− add_manual_qml_test(. ${CLASSNAME} IMPORT_PATHS ${UNITY_PLUGINPATH})
44+ add_manual_qml_test(. ${CLASSNAME} IMPORT_PATHS ${UNITY_IMPORT_PATHS})
4045endmacro()
4146
4247add_gesture_ui_test(DirectionalDragArea)
@@ -46,3 +51,4 @@ add_gesture_ui_test(TouchDispatcher)
4651add_gesture_ui_test(TouchGate)
4752add_gesture_unit_test(Damper)
4853add_gesture_unit_test(AxisVelocityCalculator)
54+add_gesture_ui_test(TouchGestureArea)
tests/plugins/Ubuntu/Gestures/GestureTest.cpp+44
@@ -17,6 +17,7 @@
1717#include "GestureTest.h"
1818
1919#include <qpa/qwindowsysteminterface.h>
20+#include <private/qquickwindow_p.h>
2021#include <QQmlEngine>
2122#include <QQuickView>
2223#include <QtTest>
@@ -72,6 +73,49 @@ void GestureTest::cleanup()
7273 m_view = nullptr;
7374}
7475
76+void GestureTest::sendTouchPress(qint64 timestamp, int id, QPointF pos)
77+{
78+ sendTouch(timestamp, id, pos, Qt::TouchPointPressed, QEvent::TouchBegin);
79+}
80+
81+void GestureTest::sendTouchUpdate(qint64 timestamp, int id, QPointF pos)
82+{
83+ sendTouch(timestamp, id, pos, Qt::TouchPointMoved, QEvent::TouchUpdate);
84+}
85+
86+void GestureTest::sendTouchRelease(qint64 timestamp, int id, QPointF pos)
87+{
88+ sendTouch(timestamp, id, pos, Qt::TouchPointReleased, QEvent::TouchEnd);
89+}
90+
91+void GestureTest::sendTouch(qint64 timestamp, int id, QPointF pos,
92+ Qt::TouchPointState pointState, QEvent::Type eventType)
93+{
94+ m_fakeTimerFactory->updateTime(timestamp);
95+
96+ QTouchEvent::TouchPoint point;
97+
98+ point.setState(pointState);
99+ point.setId(id);
100+ point.setScenePos(pos);
101+ point.setPos(pos);
102+
103+ QList<QTouchEvent::TouchPoint> points;
104+ points << point;
105+
106+ QTouchEvent touchEvent(eventType, m_device, Qt::NoModifier, Qt::TouchPointPressed, points);
107+ QCoreApplication::sendEvent(m_view, &touchEvent);
108+
109+ QQuickWindowPrivate *windowPrivate = QQuickWindowPrivate::get(m_view);
110+ windowPrivate->flushDelayedTouchEvent();
111+}
112+
113+void GestureTest::passTime(qint64 timeSpanMs)
114+{
115+ qint64 finalTime = m_fakeTimerFactory->timeSource()->msecsSinceReference() + timeSpanMs;
116+ m_fakeTimerFactory->updateTime(finalTime);
117+}
118+
75119////////////////////////// TouchMemento /////////////////////////////
76120
77121TouchMemento::TouchMemento(const QTouchEvent *touchEvent)
tests/plugins/Ubuntu/Gestures/GestureTest.h+13−1
@@ -72,7 +72,7 @@ class GestureTest : public QObject
7272{
7373 Q_OBJECT
7474public:
75− // \param qmlFilename name of the qml file to be loaded by the QQuickView
75+ // \param qmlFilename name of the qml file to be loaded by the QQuickView
7676 GestureTest(const QString &qmlFilename);
7777
7878protected Q_SLOTS:
@@ -80,6 +80,18 @@ protected Q_SLOTS:
8080 virtual void init(); // called right before each and every test function is executed
8181 virtual void cleanup(); // called right after each and every test function is executed
8282
83+protected:
84+ // QTest::touchEvent takes QPoint instead of QPointF and I don't want to
85+ // lose precision due to rounding.
86+ // Besides, those helper functions lead to more compact code.
87+ void sendTouchPress(qint64 timestamp, int id, QPointF pos);
88+ void sendTouchUpdate(qint64 timestamp, int id, QPointF pos);
89+ void sendTouchRelease(qint64 timestamp, int id, QPointF pos);
90+ void sendTouch(qint64 timestamp, int id, QPointF pos,
91+ Qt::TouchPointState pointState, QEvent::Type eventType);
92+
93+ void passTime(qint64 timeSpanMs);
94+
8395protected:
8496 QTouchDevice *m_device;
8597 QQuickView *m_view;
tests/plugins/Ubuntu/Gestures/tst_DirectionalDragArea.cpp−56
@@ -20,7 +20,6 @@
2020#include <QtQml/QQmlEngine>
2121#include <QPointer>
2222#include <private/qquickmousearea_p.h>
23−#include <private/qquickwindow_p.h>
2423
2524
2625#include <DirectionalDragArea.h>
@@ -109,18 +108,6 @@ private Q_SLOTS:
109108 void makoRightEdgeDrag_verticalDownwards();
110109 void makoLeftEdgeDrag_slowStart();
111110 void makoLeftEdgeDrag_movesSlightlyBackwardsOnStart();
112−
113−private:
114− // QTest::touchEvent takes QPoint instead of QPointF and I don't want to
115− // lose precision due to rounding.
116− // Besides, those helper functions lead to more compact code.
117− void sendTouchPress(qint64 timestamp, int id, QPointF pos);
118− void sendTouchUpdate(qint64 timestamp, int id, QPointF pos);
119− void sendTouchRelease(qint64 timestamp, int id, QPointF pos);
120− void sendTouch(qint64 timestamp, int id, QPointF pos,
121− Qt::TouchPointState pointState, QEvent::Type eventType);
122−
123− void passTime(qint64 timeSpanMs);
124111};
125112
126113tst_DirectionalDragArea::tst_DirectionalDragArea()
@@ -141,49 +128,6 @@ void tst_DirectionalDragArea::init()
141128 QTRY_COMPARE(m_view->height(), (int)m_view->rootObject()->height());
142129}
143130
144−void tst_DirectionalDragArea::sendTouchPress(qint64 timestamp, int id, QPointF pos)
145−{
146− sendTouch(timestamp, id, pos, Qt::TouchPointPressed, QEvent::TouchBegin);
147−}
148−
149−void tst_DirectionalDragArea::sendTouchUpdate(qint64 timestamp, int id, QPointF pos)
150−{
151− sendTouch(timestamp, id, pos, Qt::TouchPointMoved, QEvent::TouchUpdate);
152−}
153−
154−void tst_DirectionalDragArea::sendTouchRelease(qint64 timestamp, int id, QPointF pos)
155−{
156− sendTouch(timestamp, id, pos, Qt::TouchPointReleased, QEvent::TouchEnd);
157−}
158−
159−void tst_DirectionalDragArea::sendTouch(qint64 timestamp, int id, QPointF pos,
160− Qt::TouchPointState pointState, QEvent::Type eventType)
161−{
162− m_fakeTimerFactory->updateTime(timestamp);
163−
164− QTouchEvent::TouchPoint point;
165−
166− point.setState(pointState);
167− point.setId(id);
168− point.setScenePos(pos);
169− point.setPos(pos);
170−
171− QList<QTouchEvent::TouchPoint> points;
172− points << point;
173−
174− QTouchEvent touchEvent(eventType, m_device, Qt::NoModifier, Qt::TouchPointPressed, points);
175− QCoreApplication::sendEvent(m_view, &touchEvent);
176−
177− QQuickWindowPrivate *windowPrivate = QQuickWindowPrivate::get(m_view);
178− windowPrivate->flushDelayedTouchEvent();
179−}
180−
181−void tst_DirectionalDragArea::passTime(qint64 timeSpanMs)
182−{
183− qint64 finalTime = m_fakeTimerFactory->timeSource()->msecsSinceReference() + timeSpanMs;
184− m_fakeTimerFactory->updateTime(finalTime);
185−}
186−
187131namespace {
188132QPointF calculateInitialTouchPos(DirectionalDragArea *edgeDragArea)
189133{
tests/plugins/Ubuntu/Gestures/tst_TouchGestureArea.cppnew file+270
@@ -0,0 +1,270 @@
1+/*
2+ * Copyright (C) 2016 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include "GestureTest.h"
18+
19+#include <TouchGestureArea.h>
20+
21+#include <QQuickItem>
22+#include <QQuickView>
23+#include <QtTest>
24+
25+namespace {
26+QPointF calculateInitialTouchPos(TouchGestureArea *edgeDragArea)
27+{
28+ QPointF localCenter(edgeDragArea->width() / 2., edgeDragArea->height() / 2.);
29+ return edgeDragArea->mapToScene(localCenter);
30+}
31+}
32+
33+class tst_TouchGestureArea: public GestureTest
34+{
35+ Q_OBJECT
36+public:
37+ tst_TouchGestureArea();
38+private Q_SLOTS:
39+ void init() override; // called right before each and every test function is executed
40+
41+ void minimumTouchPoints();
42+ void maximumTouchPoints();
43+ void minimumAndMaximumTouchPoints();
44+ void rejectGestureAfterRecognitionPeriod();
45+ void releaseAndPressRecognisedGestureDoesNotRejectForPeriod();
46+ void topAreaReceivesOwnershipFirstWithEqualPoints();
47+ void topAreaReceivesOwnershipFirstWithMorePoints();
48+
49+private:
50+ void initGestureComponent(TouchGestureArea *area);
51+
52+ QQuickItem *m_blueRect;
53+ TouchGestureArea *m_gestureBottom;
54+ TouchGestureArea *m_gestureMiddle;
55+ TouchGestureArea *m_gestureTop;
56+};
57+
58+tst_TouchGestureArea::tst_TouchGestureArea()
59+ : GestureTest(QStringLiteral("tst_TouchGestureArea.qml"))
60+{
61+}
62+
63+inline void tst_TouchGestureArea::initGestureComponent(TouchGestureArea* area)
64+{
65+ area->setRecognitionTimer(m_fakeTimerFactory->createTimer(area));
66+ area->setMinimumTouchPoints(1);
67+ area->setMaximumTouchPoints(INT_MAX);
68+ area->setRecognitionPeriod(50);
69+ area->setReleaseRejectPeriod(100);
70+ // start tests with area disabled (enable as desired)
71+ area->setEnabled(false);
72+}
73+
74+void tst_TouchGestureArea::init()
75+{
76+ GestureTest::init();
77+
78+ m_blueRect = m_view->rootObject()->findChild<QQuickItem*>("blueRect");
79+ Q_ASSERT(m_blueRect != nullptr);
80+
81+ m_gestureBottom =
82+ m_view->rootObject()->findChild<TouchGestureArea*>("touchGestureAreaBottom");
83+ Q_ASSERT(m_gestureBottom != nullptr);
84+
85+ m_gestureMiddle =
86+ m_view->rootObject()->findChild<TouchGestureArea*>("touchGestureAreaMiddle");
87+ Q_ASSERT(m_gestureMiddle != nullptr);
88+
89+ m_gestureTop =
90+ m_view->rootObject()->findChild<TouchGestureArea*>("touchGestureAreaTop");
91+ Q_ASSERT(m_gestureTop != nullptr);
92+
93+ initGestureComponent(m_gestureBottom);
94+ initGestureComponent(m_gestureMiddle);
95+ initGestureComponent(m_gestureTop);
96+}
97+
98+void tst_TouchGestureArea::minimumTouchPoints()
99+{
100+ m_gestureBottom->setEnabled(true);
101+ m_gestureBottom->setMinimumTouchPoints(4);
102+
103+ QPointF touchPoint = calculateInitialTouchPos(m_gestureBottom);
104+
105+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::WaitingForTouch);
106+ QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
107+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::Undecided);
108+ QTest::touchEvent(m_view, m_device).stationary(0)
109+ .press(1, touchPoint.toPoint());
110+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::Undecided);
111+ QTest::touchEvent(m_view, m_device).stationary(0)
112+ .stationary(1)
113+ .press(2, touchPoint.toPoint());
114+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::Undecided);
115+ QTest::touchEvent(m_view, m_device).stationary(0)
116+ .stationary(1)
117+ .stationary(2)
118+ .press(3, touchPoint.toPoint());
119+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::Recognized);
120+ // test minimum overflow
121+ QTest::touchEvent(m_view, m_device).stationary(0)
122+ .stationary(1)
123+ .stationary(2)
124+ .stationary(3)
125+ .press(4, touchPoint.toPoint());
126+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::Recognized);
127+}
128+
129+void tst_TouchGestureArea::maximumTouchPoints()
130+{
131+ m_gestureBottom->setEnabled(true);
132+ m_gestureBottom->setMaximumTouchPoints(2);
133+
134+ QPointF touchPoint = calculateInitialTouchPos(m_gestureBottom);
135+
136+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::WaitingForTouch);
137+ QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
138+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::Recognized);
139+ QTest::touchEvent(m_view, m_device).stationary(0)
140+ .press(1, touchPoint.toPoint());
141+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::Recognized);
142+
143+ // test maximum overflow
144+ QTest::touchEvent(m_view, m_device).stationary(0)
145+ .stationary(1)
146+ .press(2, touchPoint.toPoint());
147+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::Rejected);
148+ // test still rejected
149+ QTest::touchEvent(m_view, m_device).stationary(0)
150+ .stationary(1)
151+ .press(3, touchPoint.toPoint());
152+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::Rejected);
153+}
154+
155+void tst_TouchGestureArea::minimumAndMaximumTouchPoints()
156+{
157+ m_gestureBottom->setEnabled(true);
158+ m_gestureBottom->setMinimumTouchPoints(2);
159+ m_gestureBottom->setMaximumTouchPoints(2);
160+
161+ QPointF touchPoint = calculateInitialTouchPos(m_gestureBottom);
162+
163+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::WaitingForTouch);
164+ QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
165+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::Undecided);
166+ QTest::touchEvent(m_view, m_device).stationary(0)
167+ .press(1, touchPoint.toPoint());
168+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::Recognized);
169+
170+ // test maximum overflow
171+ QTest::touchEvent(m_view, m_device).stationary(0)
172+ .stationary(1)
173+ .press(2, touchPoint.toPoint());
174+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::Rejected);
175+}
176+
177+void tst_TouchGestureArea::rejectGestureAfterRecognitionPeriod()
178+{
179+ m_gestureBottom->setEnabled(true);
180+ m_gestureBottom->setMinimumTouchPoints(2);
181+ m_gestureBottom->setMaximumTouchPoints(2);
182+
183+ QPointF touchPoint = calculateInitialTouchPos(m_gestureBottom);
184+
185+ QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
186+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::Undecided); // Recognition period is 50.
187+ passTime(40);
188+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::Undecided);
189+ passTime(10);
190+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::Rejected);
191+}
192+
193+void tst_TouchGestureArea::releaseAndPressRecognisedGestureDoesNotRejectForPeriod()
194+{
195+ m_gestureBottom->setEnabled(true);
196+ m_gestureBottom->setMinimumTouchPoints(2);
197+ m_gestureBottom->setMaximumTouchPoints(2);
198+
199+ bool wasRejected = false;
200+ connect(m_gestureBottom, &TouchGestureArea::statusChanged,
201+ this, [&wasRejected](int status) {
202+ if (status == TouchGestureArea::Rejected) wasRejected = true;
203+ });
204+
205+ QPointF touchPoint = calculateInitialTouchPos(m_gestureBottom);
206+
207+ QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint())
208+ .press(1, touchPoint.toPoint());
209+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::Recognized);
210+ passTime(5);
211+ QTest::touchEvent(m_view, m_device).stationary(0)
212+ .release(1, touchPoint.toPoint()); // Release period is 100.
213+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::Recognized);
214+ passTime(70);
215+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::Recognized);
216+ QCOMPARE(wasRejected, false);
217+ passTime(29);
218+ QCOMPARE(wasRejected, false);
219+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::Recognized);
220+ passTime(1);
221+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::Rejected);
222+}
223+
224+void tst_TouchGestureArea::topAreaReceivesOwnershipFirstWithEqualPoints()
225+{
226+ m_gestureBottom->setEnabled(true);
227+ m_gestureMiddle->setEnabled(true);
228+ m_gestureTop->setEnabled(true);
229+
230+ m_gestureBottom->setMinimumTouchPoints(1);
231+ m_gestureMiddle->setMinimumTouchPoints(1);
232+ m_gestureTop->setMinimumTouchPoints(1);
233+
234+ QPointF touchPoint = calculateInitialTouchPos(m_gestureBottom);
235+
236+ QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
237+
238+ QCOMPARE((int)m_gestureTop->status(), (int)TouchGestureArea::Recognized);
239+ // Lower items will not recieve the touch events
240+ QCOMPARE((int)m_gestureMiddle->status(), (int)TouchGestureArea::WaitingForTouch);
241+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::WaitingForTouch);
242+}
243+
244+void tst_TouchGestureArea::topAreaReceivesOwnershipFirstWithMorePoints()
245+{
246+ m_gestureBottom->setEnabled(true);
247+ m_gestureMiddle->setEnabled(true);
248+ m_gestureTop->setEnabled(true);
249+
250+ m_gestureBottom->setMinimumTouchPoints(1);
251+ m_gestureMiddle->setMinimumTouchPoints(1);
252+ m_gestureTop->setMinimumTouchPoints(2);
253+
254+ QPointF touchPoint = calculateInitialTouchPos(m_gestureBottom);
255+
256+ QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
257+ QCOMPARE((int)m_gestureTop->status(), (int)TouchGestureArea::Undecided);
258+ QCOMPARE((int)m_gestureMiddle->status(), (int)TouchGestureArea::Undecided);
259+ // The middle item will accept the events; so the bottom item will not get a chance.
260+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::WaitingForTouch);
261+
262+ QTest::touchEvent(m_view, m_device).stationary(0)
263+ .press(1, touchPoint.toPoint());
264+ QCOMPARE((int)m_gestureTop->status(), (int)TouchGestureArea::Recognized);
265+ QCOMPARE((int)m_gestureMiddle->status(), (int)TouchGestureArea::Rejected);
266+}
267+
268+QTEST_MAIN(tst_TouchGestureArea)
269+
270+#include "tst_TouchGestureArea.moc"
tests/plugins/Ubuntu/Gestures/tst_TouchGestureArea.qmlnew file+90
@@ -0,0 +1,90 @@
1+/*
2+ * Copyright (C) 2016 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+import Ubuntu.Components 1.3
19+import Ubuntu.Gestures 0.1
20+import Unity.Test 0.1
21+
22+Rectangle {
23+ width: units.gu(60)
24+ height: units.gu(60)
25+ color: "white"
26+
27+ Binding {
28+ target: MouseTouchAdaptor
29+ property: "enabled"
30+ value: true
31+ }
32+
33+ Rectangle {
34+ id: blueRect
35+ objectName: "blueRect"
36+ color: "blue"
37+ width: units.gu(50)
38+ height: units.gu(50)
39+
40+ Timer {
41+ id: resetColorTimer
42+ interval: 300
43+ repeat: false
44+ onTriggered: {
45+ blueRect.color = "blue";
46+ }
47+ }
48+
49+ function blink(color) {
50+ blueRect.color = color;
51+ resetColorTimer.start();
52+ }
53+
54+ TouchGestureArea {
55+ anchors.fill: parent
56+ objectName: "touchGestureAreaBottom"
57+ minimumTouchPoints: 1
58+
59+ onStatusChanged: {
60+ if (status == TouchGestureArea.Recognized) {
61+ blueRect.blink("red");
62+ }
63+ }
64+ }
65+
66+ TouchGestureArea {
67+ anchors.fill: parent
68+ objectName: "touchGestureAreaMiddle"
69+ minimumTouchPoints: 2
70+
71+ onStatusChanged: {
72+ if (status == TouchGestureArea.Recognized) {
73+ blueRect.blink("yellow");
74+ }
75+ }
76+ }
77+
78+ TouchGestureArea {
79+ anchors.fill: parent
80+ objectName: "touchGestureAreaTop"
81+ minimumTouchPoints: 3
82+
83+ onStatusChanged: {
84+ if (status == TouchGestureArea.Recognized) {
85+ blueRect.blink("green");
86+ }
87+ }
88+ }
89+ }
90+}
tests/plugins/Unity/Launcher/launchermodeltest.cpp+1
@@ -48,6 +48,7 @@ public:
4848 QString comment() const override { return "this is a mock"; }
4949 QUrl icon() const override { return QUrl(); }
5050 ApplicationInfoInterface::Stage stage() const override { return ApplicationInfoInterface::MainStage; }
51+ void setStage(ApplicationInfoInterface::Stage) override {}
5152 ApplicationInfoInterface::State state() const override { return ApplicationInfoInterface::Running; }
5253 bool focused() const override { return m_focused; }
5354 QString splashTitle() const override { return QString(); }
tests/qmltests/Stages/tst_TabletStage.qml+143
@@ -20,6 +20,7 @@ import Ubuntu.Components 1.3
2020import Ubuntu.Components.ListItems 1.3 as ListItem
2121import Unity.Application 0.1
2222import Unity.Test 0.1
23+import Utils 0.1
2324
2425import ".."
2526import "../../../qml/Stages"
@@ -78,6 +79,7 @@ Rectangle {
7879 Column {
7980 anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
8081 spacing: units.gu(1)
82+
8183 EdgeBarrierControls {
8284 id: edgeBarrierControls
8385 text: "Drag here to pull out spread"
@@ -87,6 +89,16 @@ Rectangle {
8789 edgeBarrierControls.target = testCase.findChild(tabletStageLoader, "edgeBarrierController");
8890 }
8991 }
92+
93+ Button {
94+ text: testCase.sideStage ? testCase.sideStage.shown ? "Hide Side-stage" : "Show Side-stage" : ""
95+ enabled: testCase.sideStage
96+ onClicked: {
97+ if (testCase.sideStage.shown) testCase.sideStage.hide();
98+ else testCase.sideStage.show();
99+ }
100+ }
101+
90102 ApplicationCheckBox {
91103 id: webbrowserCheckBox
92104 appId: "webbrowser-app"
@@ -112,6 +124,7 @@ Rectangle {
112124 when: windowShown
113125
114126 property Item tabletStage: tabletStageLoader.status === Loader.Ready ? tabletStageLoader.item : null
127+ property Item sideStage: tabletStage ? findChild(tabletStage, "sideStage") : null
115128
116129 function init() {
117130 tabletStageLoader.active = true;
@@ -129,6 +142,7 @@ Rectangle {
129142 }
130143
131144 waitUntilAppSurfaceShowsUp("unity8-dash");
145+ sideStage.hideNow()
132146 }
133147
134148 function cleanup() {
@@ -148,6 +162,7 @@ Rectangle {
148162 galleryCheckBox.checked = false;
149163 dialerCheckBox.checked = false;
150164 facebookCheckBox.checked = false;
165+ WindowStateStorage.clear();
151166 }
152167
153168 function waitUntilAppSurfaceShowsUp(appId) {
@@ -187,6 +202,8 @@ Rectangle {
187202 }
188203
189204 function test_tappingSwitchesFocusBetweenStages() {
205+ WindowStateStorage.saveStage(dialerCheckBox.appId, ApplicationInfoInterface.SideStage)
206+
190207 webbrowserCheckBox.checked = true;
191208 waitUntilAppSurfaceShowsUp(webbrowserCheckBox.appId);
192209 var webbrowserApp = ApplicationManager.findApplication(webbrowserCheckBox.appId);
@@ -222,6 +239,8 @@ Rectangle {
222239 }
223240
224241 function test_closeAppInSideStage() {
242+ WindowStateStorage.saveStage(dialerCheckBox.appId, ApplicationInfoInterface.SideStage)
243+
225244 dialerCheckBox.checked = true;
226245 waitUntilAppSurfaceShowsUp(dialerCheckBox.appId);
227246
@@ -293,6 +312,8 @@ Rectangle {
293312
294313
295314 function test_foregroundMainAndSideStageAppsAreKeptRunning() {
315+ WindowStateStorage.saveStage(facebookCheckBox.appId, ApplicationInfoInterface.SideStage)
316+ WindowStateStorage.saveStage(dialerCheckBox.appId, ApplicationInfoInterface.SideStage)
296317
297318 var stagesPriv = findInvisibleChild(tabletStage, "stagesPriv");
298319 verify(stagesPriv);
@@ -351,6 +372,8 @@ Rectangle {
351372 }
352373
353374 function test_foregroundAppsAreSuspendedWhenStageIsSuspended() {
375+ WindowStateStorage.saveStage(dialerCheckBox.appId, ApplicationInfoInterface.SideStage)
376+
354377 webbrowserCheckBox.checked = true;
355378 waitUntilAppSurfaceShowsUp(webbrowserCheckBox.appId)
356379 var webbrowserApp = ApplicationManager.findApplication(webbrowserCheckBox.appId);
@@ -384,5 +407,125 @@ Rectangle {
384407 }
385408 tryCompare(spreadView, "phase", 2);
386409 }
410+
411+ function test_threeFingerTapOpensSideStage_data() {
412+ return [
413+ { tag: "1 finger", touchIds: [0], result: false },
414+ { tag: "2 finger", touchIds: [0, 1], result: false },
415+ { tag: "3 finger", touchIds: [0, 1, 2], result: true },
416+ { tag: "4 finger", touchIds: [0, 1, 2, 3], result: false },
417+ ];
418+ }
419+
420+ function test_threeFingerTapOpensSideStage(data) {
421+ multiTouchTap(data.touchIds, tabletStage, tabletStage.width / 2, tabletStage.height / 2);
422+ wait(200);
423+ tryCompare(sideStage, "shown", data.result);
424+ }
425+
426+
427+ function test_threeFingerTapClosesSideStage_data() {
428+ return [
429+ { tag: "1 finger", touchIds: [0], result: true },
430+ { tag: "2 finger", touchIds: [0, 1], result: true },
431+ { tag: "3 finger", touchIds: [0, 1, 2], result: false },
432+ { tag: "4 finger", touchIds: [0, 1, 2, 3], result: true },
433+ ];
434+ }
435+
436+ function test_threeFingerTapClosesSideStage(data) {
437+ sideStage.showNow();
438+
439+ multiTouchTap(data.touchIds, tabletStage, tabletStage.width / 2, tabletStage.height / 2);
440+ wait(200);
441+ tryCompare(sideStage, "shown", data.result);
442+ }
443+
444+ function test_threeFingerDragOpensSidestage() {
445+ multiTouchDragUntil([0,1,2],
446+ tabletStage,
447+ tabletStage.width / 4,
448+ tabletStage.height / 4,
449+ units.gu(1),
450+ 0,
451+ function() { return sideStage.shown; });
452+ }
453+
454+ function test_applicationLoadsInCorrectStage_data() {
455+ return [
456+ { tag: "MainStage", stage: ApplicationInfoInterface.MainStage, mainStageAppId: "webbrowser-app", sideStageAppId: ""},
457+ { tag: "SideStage", stage: ApplicationInfoInterface.SideStage, mainStageAppId: "unity8-dash", sideStageAppId: "webbrowser-app" },
458+ ];
459+ }
460+
461+ function test_applicationLoadsInCorrectStage(data) {
462+ WindowStateStorage.saveStage(webbrowserCheckBox.appId, data.stage)
463+
464+ var stagesPriv = findInvisibleChild(tabletStage, "stagesPriv");
465+ verify(stagesPriv);
466+
467+ tryCompare(stagesPriv, "mainStageAppId", "unity8-dash");
468+ tryCompare(stagesPriv, "sideStageAppId", "");
469+
470+ webbrowserCheckBox.checked = true;
471+ waitUntilAppSurfaceShowsUp(webbrowserCheckBox.appId)
472+
473+ tryCompare(stagesPriv, "mainStageAppId", data.mainStageAppId);
474+ tryCompare(stagesPriv, "sideStageAppId", data.sideStageAppId);
475+ }
476+
477+ function test_loadSideStageByDragginFromMainStage() {
478+ sideStage.showNow();
479+ webbrowserCheckBox.checked = true;
480+ waitUntilAppSurfaceShowsUp(webbrowserCheckBox.appId);
481+
482+ var appDelegate = findChild(tabletStage, "tabletSpreadDelegate_" + webbrowserCheckBox.appId);
483+ verify(appDelegate);
484+ compare(appDelegate.stage, ApplicationInfoInterface.MainStage);
485+
486+ var pos = tabletStage.width - sideStage.width - (tabletStage.width - sideStage.width) / 2;
487+ var end_pos = tabletStage.width - sideStage.width / 2;
488+
489+ multiTouchDragUntil([0,1,2],
490+ tabletStage,
491+ pos,
492+ tabletStage.height / 2,
493+ units.gu(3),
494+ 0,
495+ function() {
496+ pos += units.gu(3);
497+ return sideStage.shown && !sideStage.showAnimation.running &&
498+ pos >= end_pos;
499+ });
500+
501+ tryCompare(appDelegate, "stage", ApplicationInfoInterface.SideStage);
502+ }
503+
504+ function test_unloadSideStageByDragginFromStageStage() {
505+ sideStage.showNow();
506+ WindowStateStorage.saveStage(webbrowserCheckBox.appId, ApplicationInfoInterface.SideStage)
507+ webbrowserCheckBox.checked = true;
508+ waitUntilAppSurfaceShowsUp(webbrowserCheckBox.appId);
509+
510+ var appDelegate = findChild(tabletStage, "tabletSpreadDelegate_" + webbrowserCheckBox.appId);
511+ verify(appDelegate);
512+ compare(appDelegate.stage, ApplicationInfoInterface.SideStage);
513+
514+ var pos = tabletStage.width - sideStage.width / 2;
515+ var end_pos = tabletStage.width - sideStage.width - (tabletStage.width - sideStage.width) / 2;
516+
517+ multiTouchDragUntil([0,1,2],
518+ tabletStage,
519+ pos,
520+ tabletStage.height / 2,
521+ -units.gu(3),
522+ 0,
523+ function() {
524+ pos -= units.gu(3);
525+ return pos <= end_pos;
526+ });
527+
528+ tryCompare(appDelegate, "stage", ApplicationInfoInterface.MainStage);
529+ }
387530 }
388531}
tests/qmltests/Wizard/tst_Wizard.qml+170−199
@@ -21,6 +21,7 @@ import MeeGo.QOfono 0.2
2121import QMenuModel 0.1
2222import Ubuntu.Components 1.3
2323import Ubuntu.SystemSettings.SecurityPrivacy 1.0
24+import Ubuntu.SystemSettings.TimeDate 1.0
2425import Unity.Test 0.1 as UT
2526import Wizard 0.1
2627import "../../../qml/Wizard"
@@ -39,7 +40,6 @@ Item {
3940 Wizard {
4041 id: wizard
4142 anchors.fill: parent
42− background: Qt.resolvedUrl("../../../qml/graphics/phone_background.jpg")
4343
4444 Component.onDestruction: {
4545 wizardLoader.itemDestroyed = true;
@@ -51,7 +51,7 @@ Item {
5151 SignalSpy {
5252 id: updateSessionLanguageSpy
5353 target: System
54− signalName: "updateSessionLanguageCalled"
54+ signalName: "updateSessionLocaleCalled"
5555 }
5656
5757 SignalSpy {
@@ -69,6 +69,11 @@ Item {
6969 signalName: "activated"
7070 }
7171
72+ SignalSpy {
73+ id: timezoneSpy
74+ signalName: "timeZoneChangedCalled"
75+ }
76+
7277 function setup() {
7378 AccountsService.hereEnabled = false;
7479 AccountsService.hereLicensePath = Qt.resolvedUrl("licenses");
@@ -82,6 +87,7 @@ Item {
8287 setSecuritySpy.clear();
8388 activateLocationSpy.clear();
8489 activateGPSSpy.clear();
90+ timezoneSpy.clear();
8591
8692 ActionData.data = {
8793 "location-detection-enabled": {
@@ -180,11 +186,6 @@ Item {
180186 tap(findChild(page, "forwardButton"));
181187 }
182188
183− page = waitForPage("passwdPage");
184− if (name === page.objectName) return page;
185− tap(findChild(page, "passwdDelegate0"));
186− tap(findChild(page, "forwardButton"));
187−
188189 page = waitForPage("wifiPage");
189190 if (name === page.objectName) return page;
190191 tap(findChild(page, "forwardButton"));
@@ -195,6 +196,31 @@ Item {
195196 tap(findChild(page, "forwardButton"));
196197 }
197198
199+ page = waitForPage("tzPage");
200+ if (name === page.objectName) return page;
201+ var tzList = findChild(page, "tzList");
202+ verify(tzList);
203+ waitForRendering(tzList);
204+ page.selectedTimeZone = "Pacific/Honolulu";
205+ tap(findChild(page, "forwardButton"));
206+
207+ page = waitForPage("accountPage");
208+ if (name === page.objectName) return page;
209+ tap(findChild(page, "nameInput"));
210+ typeString("foobar");
211+ tap(findChild(page, "passInput"));
212+ typeString("12345678");
213+ var pass2Input = findChild(page, "pass2Input");
214+ // give it a bit of time to finish the scroll-into-view animation
215+ tryCompareFunction(function() { return pass2Input !== null; }, true);
216+ tap(pass2Input);
217+ typeString("12345678");
218+ tap(findChild(page, "forwardButton"));
219+
220+ page = waitForPage("passwdPage");
221+ if (name === page.objectName) return page;
222+ tap(findChild(page, "forwardButton"));
223+
198224 if (!skipReporting) {
199225 page = waitForPage("reportingPage");
200226 if (name === page.objectName) return page;
@@ -203,7 +229,8 @@ Item {
203229
204230 page = waitForPage("finishedPage");
205231 if (name === page.objectName) return page;
206− tap(findChild(page, "forwardButton"));
232+ waitUntilTransitionsEnd(page);
233+ tap(findChild(page, "finishButton"));
207234
208235 tryCompare(wizard, "shown", false);
209236 compare(name, null);
@@ -212,100 +239,43 @@ Item {
212239
213240 function test_languageChange() {
214241 var page = goToPage("languagePage");
215− tap(findChild(page, "languageCombo"));
216− waitForRendering(findChild(page, "languageDelegate1"));
217−
218− // For some reason, the delegate *sometimes* (like 1 in 10 maybe)
219− // needs more time before it can process a tap() call. I can't
220− // find a rhyme or reason, its properties all seem the same in
221− // cases where it works and does not. This failure to receive a
222− // tap() call below does *not* happen when running in xvfb, so
223− // jenkins is unaffected (and we don't have to worry about 100 not
224− // being enough time in its slow environment). This wait() call is
225− // just to help local runs not trip up.
226− wait(100);
227− tap(findChild(page, "languageDelegate1"));
228−
229− tryCompare(i18n, "language", "fr");
242+ tap(findChild(page, "languageDelegate1")); // should invoke "fr" lang
243+
244+ tryCompare(i18n, "language", "fr_FR");
230245 tap(findChild(page, "forwardButton"));
231246 tryCompare(updateSessionLanguageSpy, "count", 1);
232− compare(updateSessionLanguageSpy.signalArguments[0][0], "fr");
247+ compare(updateSessionLanguageSpy.signalArguments[0][0], "fr_FR");
233248 }
234249
235250 function test_languageNoChange() {
236− goToPage("simPage"); // one past language page
251+ var page = goToPage("languagePage");
252+ tap(findChild(page, "forwardButton"));
237253 compare(updateSessionLanguageSpy.count, 0);
238254 }
239255
240256 function test_simUnavailableSkip() {
241257 MockQOfono.available = false;
242− goToPage("passwdPage", true);
258+ goToPage("wifiPage", true);
243259 }
244260
245261 function test_simNoModemsSkip() {
246262 MockQOfono.setModems([], [], []);
247− goToPage("passwdPage", true);
263+ goToPage("wifiPage", true);
248264 }
249265
250266 function test_simFirstSkip() {
251267 MockQOfono.setModems(["a", "b"], [true, false], []);
252− goToPage("passwdPage", true);
268+ goToPage("wifiPage", true);
253269 }
254270
255271 function test_simSecondSkip() {
256272 MockQOfono.setModems(["a", "b"], [false, true], []);
257− goToPage("passwdPage", true);
273+ goToPage("wifiPage", true);
258274 }
259275
260276 function test_simBothSkip() {
261277 MockQOfono.setModems(["a", "b"], [true, true], []);
262− goToPage("passwdPage", true);
263− }
264−
265− function test_simWaitOnManagerAsync() {
266− MockQOfono.ready = false;
267− MockQOfono.setModems(["a"], [false], []);
268−
269− // Go to SIM page, which will be waiting for skip to be valid
270− var page = goToPage("languagePage");
271− tap(findChild(page, "forwardButton"));
272− verifyPageIsBlocked("simPage");
273−
274− // Now release QOfono from blocking the page
275− MockQOfono.ready = true;
276−
277− waitForPage("simPage");
278− }
279−
280− function test_simWaitOnCardAsync() {
281− MockQOfono.setModems(["a"], [false], [false]);
282−
283− // Go to SIM page, which will be waiting for skip to be valid
284− var page = goToPage("languagePage");
285− tap(findChild(page, "forwardButton"));
286− verifyPageIsBlocked("simPage");
287−
288− // Now release QOfono from blocking the page
289− MockQOfono.setModems(["a"], [false], [true]);
290−
291− waitForPage("simPage");
292− }
293−
294− function test_simWaitTimeout() {
295− MockQOfono.setModems(["a"], [false], [false]);
296−
297− // Go to SIM page, which will be waiting for skip to be valid
298− var page = goToPage("languagePage");
299− tap(findChild(page, "forwardButton"));
300− verifyPageIsBlocked("simPage");
301−
302− var timeout = findInvisibleChild(wizard, "timeout");
303− timeout.interval = 100; // reduce our delay
304−
305− // Now just wait for timeout
306− compare(timeout.running, true);
307− waitForPage("passwdPage");
308− compare(timeout.running, false);
278+ goToPage("wifiPage", true);
309279 }
310280
311281 function enterPasscode(passcode) {
@@ -332,33 +302,32 @@ Item {
332302
333303 function test_passwdPasscode() {
334304 var page = goToPage("passwdPage");
335−
305+ tap(findChild(page, "passwdDelegate2")); // passcode option
336306 tap(findChild(page, "forwardButton"));
337− page = waitForPage("passwdSetPage");
307+ page = waitForPage("passcodeSetPage");
338308
339309 enterPasscode("1111");
340− page = waitForPage("passwdConfirmPage");
310+ page = waitForPage("passcodeConfirmPage");
341311
342312 // make sure we go back to 'set' page not 'type' page
343313 tap(findChild(page, "backButton"));
344− page = waitForPage("passwdSetPage");
314+ page = waitForPage("passcodeSetPage");
345315
346316 enterPasscode("1111");
347− page = waitForPage("passwdConfirmPage");
317+ page = waitForPage("passcodeConfirmPage");
348318
349319 enterPasscode("1112");
350320 var error = findChild(page, "wrongNoticeLabel");
351321 tryCompareFunction(function() { return error.text !== ""; }, true);
352322
353323 enterPasscode("1111");
354− page = waitForPage("wifiPage");
355324
356325 // now finish up
357− tap(findChild(page, "forwardButton"));
358− page = waitForPage("locationPage");
326+ page = waitForPage("reportingPage");
359327 tap(findChild(page, "forwardButton"));
360328 page = waitForPage("finishedPage");
361− tap(findChild(page, "forwardButton"));
329+ waitUntilTransitionsEnd(page);
330+ tap(findChild(page, "finishButton"));
362331
363332 tryCompare(setSecuritySpy, "count", 1);
364333 compare(setSecuritySpy.signalArguments[0][0], "");
@@ -368,96 +337,85 @@ Item {
368337
369338 function test_passwdPassphrase() {
370339 var page = goToPage("passwdPage");
371− tap(findChild(page, "passwdDelegate2"));
372−
340+ tap(findChild(page, "passwdDelegate1")); // password option
373341 tap(findChild(page, "forwardButton"));
374342 page = waitForPage("passwdSetPage");
375343
344+ var passwdField = findChild(page, "passwordField");
345+ verify(passwdField);
346+ tap(passwdField);
376347 typeString("aaa");
377− var continueButton = findChild(page, "forwardButton");
378− tryCompare(continueButton.item, "enabled", false);
379− keyClick(Qt.Key_Enter);
380− var error = findChild(page, "wrongNoticeLabel");
381− tryCompareFunction(function() { return error.text !== ""; }, true);
348+ var continueButton = findChild(page, "okButton");
349+ tryCompare(continueButton, "enabled", false);
382350
383− typeString("aaaa");
351+ tap(passwdField);
352+ passwdField.selectAll(); // overwrite the text
353+ typeString("12345678");
384354 tap(continueButton);
385− page = waitForPage("passwdConfirmPage");
355+ wait(200);
386356
387357 // make sure we go back to 'set' page not 'type' page
388− var back = findChild(page, "backButton");
358+ var back = findChild(page, "cancelButton");
389359 tap(back);
390− page = waitForPage("passwdSetPage");
391−
392− typeString("aaaa");
393− keyClick(Qt.Key_Enter);
394− page = waitForPage("passwdConfirmPage");
360+ wait(200);
395361
396− continueButton = findChild(page, "forwardButton");
362+ passwdField = findChild(page, "passwordField");
363+ tryCompareFunction(function() {return !!passwdField}, true);
364+ tap(passwdField);
397365 typeString("aaab");
398− tryCompare(continueButton.item, "enabled", false);
399− keyClick(Qt.Key_Enter);
400− var error = findChild(page, "wrongNoticeLabel");
366+
367+ continueButton = findChild(page, "okButton");
368+ tryCompare(continueButton, "enabled", false);
369+ var error = findChild(page, "infoLabel");
401370 tryCompareFunction(function() { return error.text !== ""; }, true);
402371
403− typeString("aaaa");
372+ tap(passwdField);
373+ passwdField.selectAll(); // overwrite the text
374+ typeString("12345678");
375+ tap(continueButton);
376+ wait(200);
377+
378+ tap(passwdField);
379+ passwdField.selectAll(); // overwrite the text
380+ typeString("12345678");
404381 tap(continueButton);
405− page = waitForPage("wifiPage");
406382
407383 // now finish up
408− tap(findChild(page, "forwardButton"));
409− page = waitForPage("locationPage");
384+ page = waitForPage("reportingPage");
410385 tap(findChild(page, "forwardButton"));
411386 page = waitForPage("finishedPage");
412− tap(findChild(page, "forwardButton"));
387+ waitUntilTransitionsEnd(page);
388+ tap(findChild(page, "finishButton"));
413389
414390 tryCompare(setSecuritySpy, "count", 1);
415391 compare(setSecuritySpy.signalArguments[0][0], "");
416− compare(setSecuritySpy.signalArguments[0][1], "aaaa");
392+ compare(setSecuritySpy.signalArguments[0][1], "12345678");
417393 compare(setSecuritySpy.signalArguments[0][2], UbuntuSecurityPrivacyPanel.Passphrase);
418394 }
419395
420396 function test_passwdSwipe() {
421− goToPage(null, false, false, true);
422−
423− tryCompare(setSecuritySpy, "count", 1);
424− compare(setSecuritySpy.signalArguments[0][0], "");
425− compare(setSecuritySpy.signalArguments[0][1], "");
426− compare(setSecuritySpy.signalArguments[0][2], UbuntuSecurityPrivacyPanel.Swipe);
427− }
428−
429− function test_locationSkipNoPath() {
430− AccountsService.hereLicensePath = "";
431− goToPage("finishedPage", false, true, true);
432− }
433−
434− function test_locationSkipNoFiles() {
435− AccountsService.hereLicensePath = Qt.resolvedUrl("nolicenses");
436− goToPage("finishedPage", false, true, true);
437− }
438−
439− function test_locationWaitOnPath() {
440− AccountsService.hereLicensePath = " "; // means we're still getting the path from dbus
441−
442− var page = goToPage("wifiPage");
397+ var page = goToPage("passwdPage");
398+ tap(findChild(page, "passwdDelegate3")); // swipe option
443399
444− var pages = findChild(wizard, "wizardPages");
445− var stack = findChild(pages, "pageStack");
400+ // now finish up
446401 tap(findChild(page, "forwardButton"));
447− // don't simply call tryCompare here, because stack.currentPage will be swapped out itself
448− tryCompareFunction(function() { return stack.currentPage.objectName; }, "locationPage");
449− compare(stack.currentPage.enabled, false);
450− compare(stack.currentPage.skipValid, false);
402+ page = waitForPage("reportingPage");
403+ tap(findChild(page, "forwardButton"));
404+ page = waitForPage("finishedPage");
405+ waitUntilTransitionsEnd(page);
406+ tap(findChild(page, "finishButton"));
451407
452− AccountsService.hereLicensePath = "";
453− waitForPage("finishedPage", false, false, true);
408+ tryCompare(setSecuritySpy, "count", 1);
409+ //compare(setSecuritySpy.signalArguments[0][0], ""); // FIXME the swipe method collides with the account password
410+ //compare(setSecuritySpy.signalArguments[0][1], "");
411+ compare(setSecuritySpy.signalArguments[0][2], UbuntuSecurityPrivacyPanel.Swipe);
454412 }
455413
456414 function test_locationGpsOnly() {
457415 var page = goToPage("locationPage");
458− var gpsCheck = findChild(page, "gpsCheck");
459− var hereCheck = findChild(page, "hereCheck");
460− var nopeCheck = findChild(page, "nopeCheck");
416+ var gpsCheck = findChild(page, "gpsCheckLabel");
417+ var hereCheck = findChild(page, "hereCheckLabel");
418+ var nopeCheck = findChild(page, "nopeCheckLabel");
461419
462420 var locationActionGroup = findInvisibleChild(page, "locationActionGroup");
463421 activateLocationSpy.target = locationActionGroup.location;
@@ -476,9 +434,9 @@ Item {
476434
477435 function test_locationNope() {
478436 var page = goToPage("locationPage");
479− var gpsCheck = findChild(page, "gpsCheck");
480− var hereCheck = findChild(page, "hereCheck");
481− var nopeCheck = findChild(page, "nopeCheck");
437+ var gpsCheck = findChild(page, "gpsCheckLabel");
438+ var hereCheck = findChild(page, "hereCheckLabel");
439+ var nopeCheck = findChild(page, "nopeCheckLabel");
482440
483441 var locationActionGroup = findInvisibleChild(page, "locationActionGroup");
484442 activateLocationSpy.target = locationActionGroup.location;
@@ -491,15 +449,15 @@ Item {
491449
492450 tap(findChild(page, "forwardButton"));
493451 tryCompare(AccountsService, "hereEnabled", false);
494− tryCompare(activateLocationSpy, "count", 0)
495− tryCompare(activateGPSSpy, "count", 0)
452+ tryCompare(activateLocationSpy, "count", 0);
453+ tryCompare(activateGPSSpy, "count", 0);
496454 }
497455
498456 function test_locationHere() {
499457 var page = goToPage("locationPage");
500− var gpsCheck = findChild(page, "gpsCheck");
501− var hereCheck = findChild(page, "hereCheck");
502− var nopeCheck = findChild(page, "nopeCheck");
458+ var gpsCheck = findChild(page, "gpsCheckLabel");
459+ var hereCheck = findChild(page, "hereCheckLabel");
460+ var nopeCheck = findChild(page, "nopeCheckLabel");
503461
504462 var locationActionGroup = findInvisibleChild(page, "locationActionGroup");
505463 activateLocationSpy.target = locationActionGroup.location;
@@ -512,68 +470,81 @@ Item {
512470
513471 tap(findChild(page, "forwardButton"));
514472 tryCompare(AccountsService, "hereEnabled", true);
515− tryCompare(activateLocationSpy, "count", 1)
516− tryCompare(activateGPSSpy, "count", 1)
473+ tryCompare(activateLocationSpy, "count", 1);
474+ tryCompare(activateGPSSpy, "count", 1);
517475 }
518476
519− function test_locationHereTerms() {
520− var page = goToPage("locationPage");
477+ function test_timezonePage() {
478+ var page = goToPage("tzPage");
479+ verify(page);
480+ timezoneSpy.target = page.tdModule;
521481
522− var link = findChild(page, "hereTermsLink");
482+ var tzFilter = findChild(page, "tzFilter");
483+ verify(tzFilter);
484+ tap(tzFilter);
485+ typeString("London");
523486
524− // Test our language lookup code a bit
487+ var tzList = findChild(page, "tzList");
488+ verify(tzList);
525489
526− i18n.language = "fr_FR.UTF-8";
527− link.linkActivated("not-used");
528− page = waitForPage("hereTermsPage");
529− tryCompare(findChild(page, "termsLabel"), "text", "fr_FR\n");
530− tap(findChild(page, "backButton"));
531− waitForPage("locationPage");
490+ // test filtering works and returns some results
491+ tryCompareFunction(function() { return tzList.count > 0; }, true);
532492
533− i18n.language = "fr_CA";
534− link.linkActivated("not-used");
535− page = waitForPage("hereTermsPage");
536− tryCompare(findChild(page, "termsLabel"), "text", "fr_CA\n");
537− tap(findChild(page, "backButton"));
538− waitForPage("locationPage");
493+ // just tap the first one
494+ tap(findChild(page, "tz0"));
539495
540− i18n.language = "fr_US";
541− link.linkActivated("not-used");
542− page = waitForPage("hereTermsPage");
543− tryCompare(findChild(page, "termsLabel"), "text", "fr_FR\n");
544− tap(findChild(page, "backButton"));
545− waitForPage("locationPage");
496+ // go next and verify the (mock) signal got fired
497+ tap(findChild(page, "forwardButton"));
498+ tryCompare(timezoneSpy, "count", 1);
499+ tryCompare(page.tdModule, "timeZone", timezoneSpy.signalArguments[0][0]);
500+ }
546501
547− i18n.language = "fr.utf8";
548− link.linkActivated("not-used");
549− page = waitForPage("hereTermsPage");
550− tryCompare(findChild(page, "termsLabel"), "text", "fr_FR\n");
551− tap(findChild(page, "backButton"));
552− waitForPage("locationPage");
502+ function test_accountPage() {
503+ var page = goToPage("accountPage");
504+ var password1 = findChild(page, "passInput");
505+ var password2 = findChild(page, "pass2Input");
506+ var forwardButton = findChild(page, "forwardButton");
553507
554− i18n.language = "es"; // will not be found
555− link.linkActivated("not-used");
556− page = waitForPage("hereTermsPage");
557− tryCompare(findChild(page, "termsLabel"), "text", "en_US\n");
508+ tap(findChild(page, "nameInput"));
509+ typeString("foobar");
558510
559− // OK, done with languages, back to actual page
511+ verify(forwardButton.enabled, false); // no passwords yet
560512
561− var label = findChild(page, "termsLabel");
562− label.linkActivated(Qt.resolvedUrl("licenses/en_US.html"));
563− tryCompare(label, "visible", false);
513+ tap(password1);
514+ typeString("12345");
564515
565− var webview = findChild(page, "webview");
566− tryCompare(webview, "visible", true);
567− tryCompare(webview, "url", Qt.resolvedUrl("licenses/en_US.html"));
568− tryCompare(webview, "loadProgress", 100);
516+ verify(forwardButton.enabled, false); // password too short
569517
570− tap(findChild(page, "backButton"));
571− waitForPage("hereTermsPage"); // confirm we're on same page
572− tryCompare(webview, "visible", false);
573− tryCompare(label, "visible", true);
518+ tap(password2);
519+ typeString("12345678");
574520
575− tap(findChild(page, "backButton"));
576− waitForPage("locationPage");
521+ verify(forwardButton.enabled, false); // passwords don't match
522+
523+ tap(password1);
524+ password1.selectAll();
525+ typeString("12345678");
526+
527+ verify(forwardButton.enabled, true); // now they match
528+
529+ var pages = findChild(wizard, "wizardPages");
530+ var security = findInvisibleChild(pages, "securityPrivacy");
531+ setSecuritySpy.target = security;
532+
533+ // now finish up
534+ tap(forwardButton);
535+ page = waitForPage("passwdPage");
536+ tap(findChild(page, "passwdDelegate0")); // account option
537+ tap(findChild(page, "forwardButton"));
538+ page = waitForPage("reportingPage");
539+ tap(findChild(page, "forwardButton"));
540+ page = waitForPage("finishedPage");
541+ waitUntilTransitionsEnd(page);
542+ tap(findChild(page, "finishButton"));
543+
544+ tryCompare(setSecuritySpy, "count", 1);
545+ compare(setSecuritySpy.signalArguments[0][0], "");
546+ compare(setSecuritySpy.signalArguments[0][1], "12345678");
547+ compare(setSecuritySpy.signalArguments[0][2], UbuntuSecurityPrivacyPanel.Passphrase);
577548 }
578549 }
579550}
tests/qmltests/tst_OrientedShell.qml+55−11
@@ -232,6 +232,9 @@ Rectangle {
232232 onClicked: {
233233 orientedShellLoader.item.physicalOrientation = root.physicalOrientation0;
234234 }
235+ color: orientedShellLoader.item && orientedShellLoader.item.physicalOrientation === root.physicalOrientation0 ?
236+ UbuntuColors.green :
237+ __styleInstance.defaultColor
235238 }
236239 Button {
237240 id: rotate90Button
@@ -240,6 +243,9 @@ Rectangle {
240243 onClicked: {
241244 orientedShellLoader.item.physicalOrientation = root.physicalOrientation90;
242245 }
246+ color: orientedShellLoader.item && orientedShellLoader.item.physicalOrientation === root.physicalOrientation90 ?
247+ UbuntuColors.green :
248+ __styleInstance.defaultColor
243249 }
244250 Button {
245251 id: rotate180Button
@@ -248,6 +254,9 @@ Rectangle {
248254 onClicked: {
249255 orientedShellLoader.item.physicalOrientation = root.physicalOrientation180;
250256 }
257+ color: orientedShellLoader.item && orientedShellLoader.item.physicalOrientation === root.physicalOrientation180 ?
258+ UbuntuColors.green :
259+ __styleInstance.defaultColor
251260 }
252261 Button {
253262 id: rotate270Button
@@ -256,6 +265,9 @@ Rectangle {
256265 onClicked: {
257266 orientedShellLoader.item.physicalOrientation = root.physicalOrientation270;
258267 }
268+ color: orientedShellLoader.item && orientedShellLoader.item.physicalOrientation === root.physicalOrientation270 ?
269+ UbuntuColors.green :
270+ __styleInstance.defaultColor
259271 }
260272 RowLayout {
261273 Layout.fillWidth: true
@@ -471,14 +483,46 @@ Rectangle {
471483 tearDown();
472484 }
473485
474− function test_appSupportingOnlyPrimaryOrientationMakesShellStayPut_data() {
486+ function test_appSupportingOnlyPrimaryOrientationMakesPhoneShellStayPut() {
487+ loadShell("mako");
488+
489+ // unity8-dash supports only primary orientation and should be already running
490+ var dashAppWindow = findChild(shell, "appWindow_unity8-dash");
491+ verify(dashAppWindow);
492+ compare(ApplicationManager.focusedApplicationId, "unity8-dash");
493+ var dashApp = dashAppWindow.application
494+ verify(dashApp);
495+ compare(dashApp.rotatesWindowContents, false);
496+ compare(dashApp.supportedOrientations, Qt.PrimaryOrientation);
497+ compare(dashApp.stage, ApplicationInfoInterface.MainStage);
498+
499+ tryCompareFunction(function(){return dashApp.session.lastSurface != null;}, true);
500+ verify(checkAppSurfaceOrientation(dashAppWindow, dashApp, root.primaryOrientationAngle));
501+
502+ compare(shell.transformRotationAngle, root.primaryOrientationAngle);
503+ rotateTo(90);
504+
505+ verify(checkAppSurfaceOrientation(dashAppWindow, dashApp, root.primaryOrientationAngle));
506+ compare(shell.transformRotationAngle, root.primaryOrientationAngle);
507+
508+ rotateTo(180);
509+
510+ verify(checkAppSurfaceOrientation(dashAppWindow, dashApp, root.primaryOrientationAngle));
511+ compare(shell.transformRotationAngle, root.primaryOrientationAngle);
512+
513+ rotateTo(270);
514+
515+ verify(checkAppSurfaceOrientation(dashAppWindow, dashApp, root.primaryOrientationAngle));
516+ compare(shell.transformRotationAngle, root.primaryOrientationAngle);
517+ }
518+
519+ function test_appSupportingOnlyPrimaryOrientationWillOnlyRotateInLandscape_data() {
475520 return [
476− {tag: "mako", deviceName: "mako"},
477521 {tag: "manta", deviceName: "manta"},
478522 {tag: "flo", deviceName: "flo"}
479523 ];
480524 }
481− function test_appSupportingOnlyPrimaryOrientationMakesShellStayPut(data) {
525+ function test_appSupportingOnlyPrimaryOrientationWillOnlyRotateInLandscape(data) {
482526 loadShell(data.deviceName);
483527
484528 // unity8-dash supports only primary orientation and should be already running
@@ -502,13 +546,13 @@ Rectangle {
502546
503547 rotateTo(180);
504548
505− verify(checkAppSurfaceOrientation(dashAppWindow, dashApp, root.primaryOrientationAngle));
506− compare(shell.transformRotationAngle, root.primaryOrientationAngle);
549+ verify(checkAppSurfaceOrientation(dashAppWindow, dashApp, root.primaryOrientationAngle + 180));
550+ compare(shell.transformRotationAngle, root.primaryOrientationAngle + 180);
507551
508552 rotateTo(270);
509553
510− verify(checkAppSurfaceOrientation(dashAppWindow, dashApp, root.primaryOrientationAngle));
511− compare(shell.transformRotationAngle, root.primaryOrientationAngle);
554+ verify(checkAppSurfaceOrientation(dashAppWindow, dashApp, root.primaryOrientationAngle + 180));
555+ compare(shell.transformRotationAngle, root.primaryOrientationAngle + 180);
512556 }
513557
514558 function test_greeterRemainsInPrimaryOrientation_data() {
@@ -813,12 +857,12 @@ Rectangle {
813857
814858 var twitterApp = ApplicationManager.startApplication("twitter-webapp");
815859 verify(twitterApp);
860+ twitterApp.stage = ApplicationInfoInterface.SideStage;
816861
817862 // ensure the mock twitter-webapp is as we expect
818863 compare(twitterApp.rotatesWindowContents, false);
819864 compare(twitterApp.supportedOrientations, Qt.PortraitOrientation | Qt.LandscapeOrientation
820865 | Qt.InvertedPortraitOrientation | Qt.InvertedLandscapeOrientation);
821− compare(twitterApp.stage, ApplicationInfoInterface.SideStage);
822866
823867 // Wait until spreadRepeaterConnections hs caught the new SpreadDelegate and
824868 // set up the signalSpy target accordingly.
@@ -852,11 +896,11 @@ Rectangle {
852896
853897 var dialerApp = ApplicationManager.startApplication("dialer-app");
854898 verify(dialerApp);
899+ dialerApp.stage = ApplicationInfoInterface.SideStage;
855900
856901 // ensure the mock dialer-app is as we expect
857902 compare(dialerApp.rotatesWindowContents, false);
858903 compare(dialerApp.supportedOrientations, Qt.PortraitOrientation | Qt.InvertedPortraitOrientation);
859− compare(dialerApp.stage, ApplicationInfoInterface.SideStage);
860904
861905 tryCompareFunction(function(){ return dialerDelegate != null; }, true);
862906 tryCompare(dialerDelegate, "orientationChangesEnabled", true);
@@ -899,11 +943,11 @@ Rectangle {
899943
900944 var dialerApp = ApplicationManager.startApplication("dialer-app");
901945 verify(dialerApp);
946+ dialerApp.stage = ApplicationInfoInterface.SideStage;
902947
903948 // ensure the mock dialer-app is as we expect
904949 compare(dialerApp.rotatesWindowContents, false);
905950 compare(dialerApp.supportedOrientations, Qt.PortraitOrientation | Qt.InvertedPortraitOrientation);
906− compare(dialerApp.stage, ApplicationInfoInterface.SideStage);
907951
908952 tryCompareFunction(function(){ return dialerDelegate != null; }, true);
909953 waitUntilAppDelegateIsFullyInit(dialerDelegate);
@@ -919,12 +963,12 @@ Rectangle {
919963
920964 var twitterApp = ApplicationManager.startApplication("twitter-webapp");
921965 verify(twitterApp);
966+ twitterApp.stage = ApplicationInfoInterface.SideStage;
922967
923968 // ensure the mock twitter-webapp is as we expect
924969 compare(twitterApp.rotatesWindowContents, false);
925970 compare(twitterApp.supportedOrientations, Qt.PortraitOrientation | Qt.LandscapeOrientation
926971 | Qt.InvertedPortraitOrientation | Qt.InvertedLandscapeOrientation);
927− compare(twitterApp.stage, ApplicationInfoInterface.SideStage);
928972
929973 tryCompareFunction(function(){ return twitterDelegate != null; }, true);
930974 waitUntilAppDelegateIsFullyInit(twitterDelegate);
tests/qmltests/tst_Shell.qml+62
@@ -131,6 +131,9 @@ Rectangle {
131131 anchors.right: root.right
132132 width: units.gu(30)
133133
134+ property var focusedApp: ApplicationManager.findApplication(ApplicationManager.focusedApplicationId)
135+ property var focusedSurface: focusedApp && focusedApp.session ? focusedApp.session.lastSurface : null
136+
134137 Rectangle {
135138 id: controlRect
136139 anchors { left: parent.left; right: parent.right }
@@ -251,6 +254,65 @@ Rectangle {
251254 appId: modelData
252255 }
253256 }
257+
258+ Label { text: "Focused Application"; font.bold: true }
259+
260+ Row {
261+ CheckBox {
262+ id: fullscreeAppCheck
263+
264+ onTriggered: {
265+ if (!controls.focusedSurface) return;
266+ if (controls.focusedSurface.state == Mir.FullscreenState) {
267+ controls.focusedSurface.state = Mir.RestoredState;
268+ } else {
269+ controls.focusedSurface.state = Mir.FullscreenState;
270+ }
271+ }
272+
273+ Binding {
274+ target: fullscreeAppCheck
275+ when: controls.focusedSurface
276+ property: "checked"
277+ value: {
278+ if (!controls.focusedSurface) return false;
279+ controls.focusedSurface.state === Mir.FullscreenState
280+ }
281+ }
282+ }
283+ Label {
284+ text: "Fullscreen"
285+ }
286+ }
287+
288+ Row {
289+ CheckBox {
290+ id: chromeAppCheck
291+
292+ onTriggered: {
293+ if (!controls.focusedSurface) return;
294+ if (controls.focusedSurface.shellChrome == Mir.LowChrome) {
295+ controls.focusedSurface.setShellChrome(Mir.NormalChrome);
296+ } else {
297+ controls.focusedSurface.setShellChrome(Mir.LowChrome);
298+ }
299+ }
300+
301+ Binding {
302+ target: chromeAppCheck
303+ when: controls.focusedSurface !== null
304+ property: "checked"
305+ value: {
306+ if (!controls.focusedSurface) return false;
307+ controls.focusedSurface.shellChrome === Mir.LowChrome
308+ }
309+ }
310+ }
311+ Label {
312+ text: "Low Chrome"
313+ }
314+ }
315+
254316 }
255317 }
256318 }
tests/utils/modules/Unity/Test/UnityTestCase.qml+67−11
@@ -313,7 +313,10 @@ TestCase {
313313 // perform a drag in the given direction until the given condition is true
314314 // The condition is a function to be evaluated after every step
315315 function touchDragUntil(item, startX, startY, stepX, stepY, condition) {
316+ multiTouchDragUntil([0], item, startX, startY, stepX, stepY, condition);
317+ }
316318
319+ function multiTouchDragUntil(touchIds, item, startX, startY, stepX, stepY, condition) {
317320 var root = fetchRootItem(item);
318321 var pos = item.mapToItem(root, startX, startY);
319322
@@ -326,7 +329,9 @@ TestCase {
326329 stepY = stepEnd.y - stepStart.y;
327330
328331 var event = touchEvent(item)
329− event.press(0 /* touchId */, pos.x, pos.y)
332+ for (var i = 0; i < touchIds.length; i++) {
333+ event.press(touchIds[i], pos.x, pos.y)
334+ }
330335 event.commit()
331336
332337 // we have to stop at some point
@@ -341,23 +346,30 @@ TestCase {
341346 pos.y += stepY;
342347
343348 event = touchEvent(item);
344− event.move(0 /* touchId */, pos.x, pos.y);
349+ for (i = 0; i < touchIds.length; i++) {
350+ event.move(touchIds[i], pos.x, pos.y);
351+ }
345352 event.commit();
346353
347354 stepsDone += 1;
348355 }
349356
350357 event = touchEvent(item)
351− event.release(0 /* touchId */, pos.x, pos.y)
358+ for (i = 0; i < touchIds.length; i++) {
359+ event.release(touchIds[i], pos.x, pos.y)
360+ }
352361 event.commit()
353362 }
354363
355− function touchMove(item, tox, toy) {
364+ function touchMove(item, tox, toy) { multiTouchMove(0, item, tox, toy); }
365+
366+ function multiTouchMove(touchId, item, tox, toy) {
367+ if (typeof touchId !== "number") touchId = 0;
356368 var root = fetchRootItem(item)
357369 var rootPoint = item.mapToItem(root, tox, toy)
358370
359371 var event = touchEvent(item);
360− event.move(0 /* touchId */, rootPoint.x, rootPoint.y);
372+ event.move(touchId, rootPoint.x, rootPoint.y);
361373 event.commit();
362374 }
363375
@@ -394,21 +406,55 @@ TestCase {
394406 return item
395407 }
396408
397− function touchPress(item, x, y) {
409+ function touchPress(item, x, y) { touchPress(0, item, x, y, []); }
410+
411+ /*! \brief Release a touch point
412+
413+ \param touchId The touchId to be pressed
414+ \param item The item
415+ \param x The x coordinate of the press, defaults to horizontal center
416+ \param y The y coordinate of the press, defaults to vertical center
417+ \param stationaryPoints An array of touchIds which are "already touched"
418+ */
419+ function multiTouchPress(touchId, item, x, y, stationaryPoints) {
420+ if (typeof touchId !== "number") touchId = 0;
421+ if (typeof x !== "number") x = item.width / 2;
422+ if (typeof y !== "number") y = item.height / 2;
423+ if (typeof stationaryPoints !== "object") stationaryPoints = []
398424 var root = fetchRootItem(item)
399425 var rootPoint = item.mapToItem(root, x, y)
400426
401427 var event = touchEvent(item)
402− event.press(0 /* touchId */, rootPoint.x, rootPoint.y)
428+ event.press(touchId, rootPoint.x, rootPoint.y)
429+ for (var i = 0; i < stationaryPoints.length; i++) {
430+ event.stationary(stationaryPoints[i]);
431+ }
403432 event.commit()
404433 }
405434
406− function touchRelease(item, x, y) {
435+ function touchRelease(item, x, y) { multiTouchRelease(0, item, x, y, []); }
436+
437+ /*! \brief Release a touch point
438+
439+ \param touchId The touchId to be released
440+ \param item The item
441+ \param x The x coordinate of the release, defaults to horizontal center
442+ \param y The y coordinate of the release, defaults to vertical center
443+ \param stationaryPoints An array of touchIds which are "still touched"
444+ */
445+ function multiTouchRelease(touchId, item, x, y, stationaryPoints) {
446+ if (typeof touchId !== "number") touchId = 0;
447+ if (typeof x !== "number") x = item.width / 2;
448+ if (typeof y !== "number") y = item.height / 2;
449+ if (typeof stationaryPoints !== "object") stationaryPoints = []
407450 var root = fetchRootItem(item)
408451 var rootPoint = item.mapToItem(root, x, y)
409452
410453 var event = touchEvent(item)
411− event.release(0 /* touchId */, rootPoint.x, rootPoint.y)
454+ event.release(touchId, rootPoint.x, rootPoint.y)
455+ for (var i = 0; i < stationaryPoints.length; i++) {
456+ event.stationary(stationaryPoints[i]);
457+ }
412458 event.commit()
413459 }
414460
@@ -419,6 +465,11 @@ TestCase {
419465 \param y The y coordinate of the tap, defaults to vertical center
420466 */
421467 function tap(item, x, y) {
468+ multiTouchTap([0], item, x, y);
469+ }
470+
471+ function multiTouchTap(touchIds, item, x, y) {
472+ if (typeof touchIds !== "object") touchIds = [0];
422473 if (typeof x !== "number") x = item.width / 2;
423474 if (typeof y !== "number") y = item.height / 2;
424475
@@ -426,14 +477,19 @@ TestCase {
426477 var rootPoint = item.mapToItem(root, x, y)
427478
428479 var event = touchEvent(item)
429− event.press(0 /* touchId */, rootPoint.x, rootPoint.y)
480+ for (var i = 0; i < touchIds.length; i++) {
481+ event.press(touchIds[i], rootPoint.x, rootPoint.y)
482+ }
430483 event.commit()
431484
432485 event = touchEvent(item)
433− event.release(0 /* touchId */, rootPoint.x, rootPoint.y)
486+ for (i = 0; i < touchIds.length; i++) {
487+ event.release(touchIds[i], rootPoint.x, rootPoint.y)
488+ }
434489 event.commit()
435490 }
436491
492+
437493 Component.onCompleted: {
438494 var rootItem = parent;
439495 while (rootItem.parent != undefined) {