on xenial help sign in
Merge side-stage-redesign
14a5635Michael Terry
CMakeLists.txt+3−2
@@ -57,7 +57,8 @@ find_package(Qt5DBus 5.4 REQUIRED)
57find_package(Qt5Concurrent 5.4 REQUIRED)
57find_package(Qt5Concurrent 5.4 REQUIRED)
58find_package(Qt5Sql 5.4 REQUIRED)
58find_package(Qt5Sql 5.4 REQUIRED)
59
59
60−pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=13)
60+pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=14)
61+pkg_check_modules(GEONAMES REQUIRED geonames)
61pkg_check_modules(GIO REQUIRED gio-2.0>=2.32)
62pkg_check_modules(GIO REQUIRED gio-2.0>=2.32)
62pkg_check_modules(GLIB REQUIRED glib-2.0>=2.32)
63pkg_check_modules(GLIB REQUIRED glib-2.0>=2.32)
63pkg_check_modules(QMENUMODEL REQUIRED qmenumodel)
64pkg_check_modules(QMENUMODEL REQUIRED qmenumodel)
@@ -131,7 +132,7 @@ if(NOT QUIET_LOGGER)
131 set(STDOUT_LOGGER "-o" "-,txt")
132 set(STDOUT_LOGGER "-o" "-,txt")
132endif()
133endif()
133
134
134−execute_process(COMMAND arch OUTPUT_VARIABLE ARCH)
135+execute_process(COMMAND dpkg-architecture -qDEB_HOST_ARCH OUTPUT_VARIABLE ARCH)
135if (NOT ARCH MATCHES "arm*")
136if (NOT ARCH MATCHES "arm*")
136 set(ENABLE_TOUCH_EMULATION true)
137 set(ENABLE_TOUCH_EMULATION true)
137 add_definitions(-DUNITY8_ENABLE_TOUCH_EMULATION)
138 add_definitions(-DUNITY8_ENABLE_TOUCH_EMULATION)
CODING+2
@@ -54,6 +54,8 @@ $ ./run.sh -p OR -k
54Notes
54Notes
55-----
55-----
56- to navigate, utilize the mouse left button as you would your finger
56- 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.
57- to get the translations work, currently you have to do make install to
59- to get the translations work, currently you have to do make install to
58 put the *.mo files into correct structure. We'll look for a better solution
60 put the *.mo files into correct structure. We'll look for a better solution
59 later.
61 later.
cmake/modules/QmlTest.cmake+1−1
@@ -183,7 +183,7 @@ function(add_manual_test COMPONENT_NAME TARGET)
183 cmake_parse_arguments(QMLTEST "${QMLTEST_OPTIONS}" "${QMLTEST_SINGLE}" "${QMLTEST_MULTI}" ${ARGN})
183 cmake_parse_arguments(QMLTEST "${QMLTEST_OPTIONS}" "${QMLTEST_SINGLE}" "${QMLTEST_MULTI}" ${ARGN})
184 mangle_arguments()
184 mangle_arguments()
185
185
186− bake_arguments("${QMLTEST_ARG_PREFIX}" args -qmljsdebugger=port:3768)
186+ bake_arguments("${QMLTEST_ARG_PREFIX}" args -qmljsdebugger=port:3768,3800)
187
187
188 set(qmltry_command
188 set(qmltry_command
189 $<TARGET_FILE:${TARGET}>
189 $<TARGET_FILE:${TARGET}>
data/com.canonical.Unity8.gschema.xml+11
@@ -27,6 +27,17 @@
27 <summary>Maximum push needed to overcome edge barrier</summary>
27 <summary>Maximum push needed to overcome edge barrier</summary>
28 <description>How much you have to push (in grid units) the mouse against an edge barrier when sensibility is 1.</description>
28 <description>How much you have to push (in grid units) the mouse against an edge barrier when sensibility is 1.</description>
29 </key>
29 </key>
30+ <key type="b" name="autohide-launcher">
31+ <default>true</default>
32+ <summary>Autohide the launcher</summary>
33+ <description>This will only be applied in windowed mode. In staged mode, the launcher will always hide.</description>
34+ </key>
35+ <key type="u" name="launcher-width">
36+ <default>8</default>
37+ <range min="6" max="12"/>
38+ <summary>Width of the launcher in grid units.</summary>
39+ <description>Changes the width of the launcher in all usage modes.</description>
40+ </key>
30 </schema>
41 </schema>
31
42
32 <schema path="/com/canonical/unity8/greeter/" id="com.canonical.Unity8.Greeter" gettext-domain="unity8">
43 <schema path="/com/canonical/unity8/greeter/" id="com.canonical.Unity8.Greeter" gettext-domain="unity8">
data/unity8-dash.conf+1
@@ -14,6 +14,7 @@ kill timeout 120
14oom score 50
14oom score 50
15
15
16respawn
16respawn
17+respawn limit unlimited
17
18
18env APP_ID=unity8-dash
19env APP_ID=unity8-dash
19
20
debian/changelog+97
@@ -1,3 +1,100 @@
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+
7+unity8 (8.11+16.04.20160310.4-0ubuntu1) xenial; urgency=medium
8+
9+ [ Albert Astals Cid ]
10+ * Add context for Re-dock as asked by translators (LP: #1534608)
11+ * Add emblem to the preview header widget (LP: #1424720)
12+ * Add haptics to ScopesListCategoryItem buttons
13+ * Audio Cards: Make some of the image loading async (LP: #1533432)
14+ * Do not create fallback code for the card tool fake card (LP:
15+ #1545865)
16+ * Fix resizing the dash bringing temp scopes size out of sync (LP:
17+ #1543130)
18+ * Minor fixes for unity-scope-tool
19+ * Resolve title alignment on card creator time instead of on runtime
20+ * Use fixedHeaderHeight only in the non cardtool cards
21+ * Use the new undeprecated connectivityqt::Connectivity
22+ * asynchronous is only false on the fake card in cardtool
23+ * clazy fixes
24+
25+ [ Albert Astals Cid, CI Train Bot ]
26+ * click scope: Add the else branch so we reset the card size in all
27+ situations
28+
29+ [ Andrea Cimitan ]
30+ * PreviewSharing widget now accepts both string and array of
31+ widgetData["share-data"]["uri"] (LP: #1549056)
32+ * Update AP tests for new single preview
33+ * Use Text.Wrap for body notification text (LP: #1544909)
34+
35+ [ Andrea Cimitan, Lukáš Tinkl, Michael Zanetti, Nick Dedekind ]
36+ * some fixes for the new palette (LP: #1554616)
37+
38+ [ CI Train Bot ]
39+ * Resync trunk.
40+ * Update translation template
41+
42+ [ CI Train Bot, Daniel d'Andrada ]
43+ * Ensure the QML engine doesn't delete our mock MirSurfaces on its
44+ own.
45+
46+ [ Daniel d'Andrada ]
47+ * tst_Shell: Remove unused qml items
48+
49+ [ Josh Arenson ]
50+ * Allow the shell to blacklist input devices and force the OSK shown.
51+ (LP: #1542224)
52+
53+ [ Lukáš Tinkl ]
54+ * Disallow resizing windows up, past the Panel (LP: #1544766)
55+ * Elide the window title not to let it overflow into the indicators
56+ area (LP: #1535767)
57+ * Enable the PIN lockscreen to be used with a HW keyboard (LP:
58+ #1550359)
59+ * Fix tiny windows when switching stages
60+ * Provide a range of ports to QML JS Debugger
61+ * Watch for launcher item icon changes (LP: #1543290)
62+
63+ [ Michael Terry ]
64+ * Proxy more mouse and touchpad properties to USC (LP: #1540398) (LP:
65+ #1543344, #1540398)
66+ * Refactor the AccountsService plugin and make it slightly faster.
67+ * To let the user log in if a mouse is connected, hide the greeter
68+ cover page on a mouse click (but NOT a touch click). (LP: #1540497)
69+ * Watch AccountsService for changes to the user's real name. This was
70+ preventing us from noticing when the user set their name in the
71+ welcome wizard.
72+
73+ [ Michael Zanetti ]
74+ * Allow alt+tabbing in staged mode too (LP: #1540502)
75+ * Allow invoking the staged mode spreads by mouse right edge pushes
76+ too (LP: #1540392)
77+ * Allow loading the device configuration from an external file
78+ * Implement Launcher's keyboard navigation and updated pip design
79+ * Make launcher scalable, allow it locking (LP: #1511015)
80+ * Properly parent launcher items in all cases (LP: #1495732)
81+ * Read inputMethod surface from the new property int QtMir (LP:
82+ #1545286)
83+ * Some visual updates and rotation lock for the virtual touchpad (LP:
84+ #1549087)
85+ * Visual updates for the windowed spread (LP: #1488148)
86+ * stabilize swipeAwayGreeter()
87+
88+ [ Michał Sawicz ]
89+ * Add udev rules to make sure we have access to uinput
90+ * Make dash respawn indefinitely (LP: #1550056)
91+ * Use dpkg-architecture, not arch, to disable touch emulation
92+
93+ [ Vesa Rautiainen ]
94+ * Fixing the vertical position of desktop spread item icon.
95+
96+ -- Michał Sawicz <michal.sawicz@canonical.com> Thu, 10 Mar 2016 22:44:16 +0000
97+
1unity8 (8.11+16.04.20160308-0ubuntu1) xenial; urgency=medium
98unity8 (8.11+16.04.20160308-0ubuntu1) xenial; urgency=medium
2
99
3 [ Albert Astals Cid ]
100 [ Albert Astals Cid ]
debian/control+13−11
@@ -8,13 +8,14 @@ Build-Depends: cmake,
8 doxyqml,
8 doxyqml,
9# To allow cross-compiling to work, we also must append :native
9# To allow cross-compiling to work, we also must append :native
10# to g++ so we don't try to run armhf g++
10# 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.
12 g++:native,
12 g++:native,
13 libandroid-properties-dev,
13 libandroid-properties-dev,
14 graphviz,
14 graphviz,
15 gsettings-ubuntu-schemas (>= 0.0.2+14.10.20140815),
15 gsettings-ubuntu-schemas (>= 0.0.2+14.10.20140815),
16− libconnectivity-qt1-dev,
16+ libconnectivity-qt1-dev (>= 0.7.1),
17 libevdev-dev,
17 libevdev-dev,
18+ libgeonames-dev (>= 0.2),
18 libgl1-mesa-dev[!armhf] | libgl-dev[!armhf],
19 libgl1-mesa-dev[!armhf] | libgl-dev[!armhf],
19 libgl1-mesa-dri,
20 libgl1-mesa-dri,
20 libgles2-mesa-dev[armhf],
21 libgles2-mesa-dev[armhf],
@@ -29,7 +30,7 @@ Build-Depends: cmake,
29 libqt5xmlpatterns5-dev,
30 libqt5xmlpatterns5-dev,
30 libsystemsettings-dev,
31 libsystemsettings-dev,
31 libudev-dev,
32 libudev-dev,
32− libunity-api-dev (>= 7.107),
33+ libunity-api-dev (>= 7.108),
33 libusermetricsoutput1-dev,
34 libusermetricsoutput1-dev,
34# Need those X11 libs touch emulation from mouse events in manual QML tests on a X11 desktop
35# Need those X11 libs touch emulation from mouse events in manual QML tests on a X11 desktop
35 libx11-dev[!armhf],
36 libx11-dev[!armhf],
@@ -55,7 +56,7 @@ Build-Depends: cmake,
55 qtdeclarative5-qtmultimedia-plugin (>= 5.4.1-1ubuntu19~overlay2),
56 qtdeclarative5-qtmultimedia-plugin (>= 5.4.1-1ubuntu19~overlay2),
56 qtdeclarative5-ubuntu-content1,
57 qtdeclarative5-ubuntu-content1,
57 qtdeclarative5-ubuntu-settings-components (>= 0.7),
58 qtdeclarative5-ubuntu-settings-components (>= 0.7),
58− qtdeclarative5-ubuntu-ui-toolkit-plugin (>= 1.3.1796) | qtdeclarative5-ubuntu-ui-toolkit-plugin-gles (>= 1.3.1796),
59+ qtdeclarative5-ubuntu-ui-toolkit-plugin (>= 1.3.1845) | qtdeclarative5-ubuntu-ui-toolkit-plugin-gles (>= 1.3.1845),
59 qtdeclarative5-ubuntu-web-plugin,
60 qtdeclarative5-ubuntu-web-plugin,
60 ttf-ubuntu-font-family,
61 ttf-ubuntu-font-family,
61Standards-Version: 3.9.4
62Standards-Version: 3.9.4
@@ -70,7 +71,7 @@ X-Ubuntu-Use-Langpack: yes
70Package: indicators-client
71Package: indicators-client
71Architecture: amd64 armhf i386
72Architecture: amd64 armhf i386
72Depends: qmenumodel-qml (>= 0.2.9),
73Depends: qmenumodel-qml (>= 0.2.9),
73− qtdeclarative5-ubuntu-ui-toolkit-plugin (>= 1.3.1796) | qtdeclarative5-ubuntu-ui-toolkit-plugin-gles (>= 1.3.1796),
74+ qtdeclarative5-ubuntu-ui-toolkit-plugin (>= 1.3.1845) | qtdeclarative5-ubuntu-ui-toolkit-plugin-gles (>= 1.3.1845),
74 unity8 (= ${binary:Version}),
75 unity8 (= ${binary:Version}),
75 ${misc:Depends},
76 ${misc:Depends},
76 ${shlibs:Depends},
77 ${shlibs:Depends},
@@ -101,8 +102,7 @@ Depends: dmz-cursor-theme,
101 qml-module-qt-labs-folderlistmodel,
102 qml-module-qt-labs-folderlistmodel,
102 qml-module-qtquick-xmllistmodel,
103 qml-module-qtquick-xmllistmodel,
103 qml-module-qtsysteminfo,
104 qml-module-qtsysteminfo,
104− qtdeclarative5-gsettings1.0,
105+ qtdeclarative5-qtmir-plugin (>= 0.4.8),
105− qtdeclarative5-qtmir-plugin (>= 0.4.5),
106 qtdeclarative5-ubuntu-telephony0.1,
106 qtdeclarative5-ubuntu-telephony0.1,
107 qtdeclarative5-ubuntu-web-plugin,
107 qtdeclarative5-ubuntu-web-plugin,
108 ubuntu-system-settings,
108 ubuntu-system-settings,
@@ -128,10 +128,10 @@ Architecture: all
128Depends: qml-module-qtquick-layouts,
128Depends: qml-module-qtquick-layouts,
129 qtdeclarative5-ubuntu-settings-components (>= 0.7),
129 qtdeclarative5-ubuntu-settings-components (>= 0.7),
130 qtdeclarative5-ubuntu-thumbnailer0.1 | ubuntu-thumbnailer-impl,
130 qtdeclarative5-ubuntu-thumbnailer0.1 | ubuntu-thumbnailer-impl,
131− qtdeclarative5-ubuntu-ui-toolkit-plugin (>= 1.3.1796) | qtdeclarative5-ubuntu-ui-toolkit-plugin-gles (>= 1.3.1796),
131+ qtdeclarative5-ubuntu-ui-toolkit-plugin (>= 1.3.1845) | qtdeclarative5-ubuntu-ui-toolkit-plugin-gles (>= 1.3.1845),
132 qtdeclarative5-unity-notifications-plugin (>= 0.1.2) | unity-notifications-impl,
132 qtdeclarative5-unity-notifications-plugin (>= 0.1.2) | unity-notifications-impl,
133 ubuntu-thumbnailer-impl-0,
133 ubuntu-thumbnailer-impl-0,
134− unity-application-impl-13,
134+ unity-application-impl-14,
135 unity-notifications-impl-3,
135 unity-notifications-impl-3,
136 unity-plugin-scopes | unity-scopes-impl,
136 unity-plugin-scopes | unity-scopes-impl,
137 unity-scopes-impl-10,
137 unity-scopes-impl-10,
@@ -158,7 +158,7 @@ Depends: autopilot-qt5 (>= 1.4),
158 python3-fixtures,
158 python3-fixtures,
159 python3-gi,
159 python3-gi,
160 qttestability-autopilot (>= 1.4),
160 qttestability-autopilot (>= 1.4),
161− ubuntu-ui-toolkit-autopilot (>= 1.3.1796),
161+ ubuntu-ui-toolkit-autopilot (>= 1.3.1845),
162 unity-scope-click,
162 unity-scope-click,
163 unity8 (= ${source:Version}),
163 unity8 (= ${source:Version}),
164 unity8-fake-env (= ${source:Version}),
164 unity8-fake-env (= ${source:Version}),
@@ -177,7 +177,7 @@ Pre-Depends: ${misc:Pre-Depends},
177Depends: ${misc:Depends},
177Depends: ${misc:Depends},
178 ${shlibs:Depends},
178 ${shlibs:Depends},
179Provides: unity-application-impl,
179Provides: unity-application-impl,
180− unity-application-impl-13,
180+ unity-application-impl-14,
181Replaces: unity8-autopilot (<< 8.02+15.04.20150422-0ubuntu1)
181Replaces: unity8-autopilot (<< 8.02+15.04.20150422-0ubuntu1)
182Description: Fake environment for running Unity 8 shell
182Description: Fake environment for running Unity 8 shell
183 Provides fake implementations of some QML modules used by Unity 8 shell
183 Provides fake implementations of some QML modules used by Unity 8 shell
@@ -192,6 +192,8 @@ Depends: accountsservice-ubuntu-schemas (>= 0.0.5),
192 libhardware2,
192 libhardware2,
193 pay-service,
193 pay-service,
194 unity-schemas (>= 7.3.1+14.10.20140915),
194 unity-schemas (>= 7.3.1+14.10.20140915),
195+ qtdeclarative5-gsettings1.0,
196+ qml-module-qtmultimedia,
195 ${misc:Depends},
197 ${misc:Depends},
196 ${shlibs:Depends},
198 ${shlibs:Depends},
197Provides: unity-launcher-impl,
199Provides: unity-launcher-impl,
debian/unity8-common.udevnew file+2
@@ -0,0 +1,2 @@
1+# Make local foreground session able to inject input
2+KERNEL=="uinput", SUBSYSTEM=="misc", TAG+="uaccess"
debian/unity8-doc.install+1
@@ -1 +1,2 @@
1+doc/devices.conf usr/share/doc/unity8/
1usr/share/doc/unity8/*
2usr/share/doc/unity8/*
doc/devices.confnew file+30
@@ -0,0 +1,30 @@
1+# This file can hold multiple device configs. Devices are separated by sections.
2+#
3+# SupportedOrientations holds a list of all enabled orientations. A standard
4+# phone will usually have Portrait,Landcape,InvertedLandscape in order to
5+# disable upside down usage.
6+#
7+# PrimaryOrientation gives the orientation the device will start up with
8+# when there is no orientations sensor input available (yet) or lock to
9+# when an application specifies to be locked to PrimaryOrientation.
10+#
11+# The other Orientation settings can be used to re-map the orientations.
12+# A device might be used with different orientations than how the screen
13+# is physically mounted on the hardware.
14+#
15+# Category can be phone, tablet, or desktop. This option determines
16+# whether the side stage is shown (tablet) or not (phone). Using
17+# desktop will load the shell in windowed mode. Note that the user
18+# can override/change this by connecting input hardware or change
19+# user settings.
20+#
21+# Any options not listed will default to the values of the example below.
22+
23+[devicename]
24+SupportedOrientations=Portrait,InvertedPortrait,Landscape,InvertedLandscape
25+PrimaryOrientation=PrimaryOrienation
26+PortraitOrientation=Portrait
27+InvertedPortraitOrientation=InvertedPortrait
28+LandscapeOrientation=Landscape
29+InvertedLandscapeOrientation=InvertedLandscape
30+Category=phone
libs/UbuntuGestures/Timer.h+5
@@ -35,6 +35,11 @@ public:
35 virtual int interval() const = 0;
35 virtual int interval() const = 0;
36 virtual void setInterval(int msecs) = 0;
36 virtual void setInterval(int msecs) = 0;
37 virtual void start() { m_isRunning = true; }
37 virtual void start() { m_isRunning = true; }
38+ virtual void start(int msecs)
39+ {
40+ setInterval(msecs);
41+ start();
42+ }
38 virtual void stop() { m_isRunning = false; }
43 virtual void stop() { m_isRunning = false; }
39 bool isRunning() const { return m_isRunning; }
44 bool isRunning() const { return m_isRunning; }
40 virtual bool isSingleShot() const = 0;
45 virtual bool isSingleShot() const = 0;
libs/UbuntuGestures/TouchRegistry.cpp+15−10
@@ -359,10 +359,6 @@ void TouchRegistry::removeCandidateOwnerForTouchByIndex(Pool<TouchRegistry::Touc
359
359
360void TouchRegistry::requestTouchOwnership(int id, QQuickItem *candidate)
360void TouchRegistry::requestTouchOwnership(int id, QQuickItem *candidate)
361{
361{
362− #if TOUCHREGISTRY_DEBUG
363− UG_DEBUG << "requestTouchOwnership id " << id << "candidate" << candidate;
364− #endif
365−
366 Pool<TouchInfo>::Iterator touchInfo = findTouchInfo(id);
362 Pool<TouchInfo>::Iterator touchInfo = findTouchInfo(id);
367 if (!touchInfo) { qFatal("TouchRegistry: Failed to find TouchInfo"); }
363 if (!touchInfo) { qFatal("TouchRegistry: Failed to find TouchInfo"); }
368
364
@@ -379,6 +375,9 @@ void TouchRegistry::requestTouchOwnership(int id, QQuickItem *candidate)
379 break;
375 break;
380 }
376 }
381 }
377 }
378+ #if TOUCHREGISTRY_DEBUG
379+ UG_DEBUG << "requestTouchOwnership id " << id << "candidate" << candidate << "index: " << candidateIndex;
380+ #endif
382
381
383 // add it as a candidate if not present yet
382 // add it as a candidate if not present yet
384 if (candidateIndex < 0) {
383 if (candidateIndex < 0) {
@@ -533,16 +532,22 @@ void TouchRegistry::TouchInfo::notifyCandidatesOfOwnershipResolution()
533 << " gained) to candidate" << candidates[0].item;
532 << " gained) to candidate" << candidates[0].item;
534 #endif
533 #endif
535
534
536− TouchOwnershipEvent gainedOwnershipEvent(id, true /*gained*/);
535+ // need to take a copy of the item list in case
537− QCoreApplication::sendEvent(candidates[0].item, &gainedOwnershipEvent);
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+ }
538
541
542+ TouchOwnershipEvent gainedOwnershipEvent(id, true /*gained*/);
543+ QCoreApplication::sendEvent(items[0], &gainedOwnershipEvent);
539
544
540 TouchOwnershipEvent lostOwnershipEvent(id, false /*gained*/);
545 TouchOwnershipEvent lostOwnershipEvent(id, false /*gained*/);
541− for (int i = 1; i < candidates.count(); ++i) {
546+ for (int i = 1; i < items.count(); ++i) {
542 #if TOUCHREGISTRY_DEBUG
547 #if TOUCHREGISTRY_DEBUG
543− UG_DEBUG << "sending TouchWonershipEvent(id =" << id << " lost) to candidate"
548+ UG_DEBUG << "sending TouchOwnershipEvent(id =" << id << " lost) to candidate"
544− << candidates[i].item;
549+ << items[i];
545 #endif
550 #endif
546− QCoreApplication::sendEvent(candidates[i].item, &lostOwnershipEvent);
551+ QCoreApplication::sendEvent(items[i], &lostOwnershipEvent);
547 }
552 }
548}
553}
plugins/AccountsService/AccountsService.cpp+212−374
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013, 2015 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
5 * it under the terms of the GNU General Public License as published by
@@ -12,8 +12,6 @@
12 *
12 *
13 * You should have received a copy of the GNU General Public License
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/>.
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− *
16− * Author: Michael Terry <michael.terry@canonical.com>
17 */
15 */
18
16
19#include "AccountsService.h"
17#include "AccountsService.h"
@@ -24,18 +22,55 @@
24#include <QStringList>
22#include <QStringList>
25#include <QDebug>
23#include <QDebug>
26
24
25+#define IFACE_ACCOUNTS_USER QStringLiteral("org.freedesktop.Accounts.User")
26+#define IFACE_LOCATION_HERE QStringLiteral("com.ubuntu.location.providers.here.AccountsService")
27+#define IFACE_UBUNTU_INPUT QStringLiteral("com.ubuntu.AccountsService.Input")
28+#define IFACE_UBUNTU_SECURITY QStringLiteral("com.ubuntu.AccountsService.SecurityPrivacy")
29+#define IFACE_UBUNTU_SECURITY_OLD QStringLiteral("com.ubuntu.touch.AccountsService.SecurityPrivacy")
30+#define IFACE_UNITY QStringLiteral("com.canonical.unity.AccountsService")
31+#define IFACE_UNITY_PRIVATE QStringLiteral("com.canonical.unity.AccountsService.Private")
32+
33+#define PROP_BACKGROUND_FILE QStringLiteral("BackgroundFile")
34+#define PROP_DEMO_EDGES QStringLiteral("demo-edges")
35+#define PROP_DEMO_EDGES_COMPLETED QStringLiteral("DemoEdgesCompleted")
36+#define PROP_EMAIL QStringLiteral("Email")
37+#define PROP_ENABLE_INDICATORS_WHILE_LOCKED QStringLiteral("EnableIndicatorsWhileLocked")
38+#define PROP_ENABLE_LAUNCHER_WHILE_LOCKED QStringLiteral("EnableLauncherWhileLocked")
39+#define PROP_FAILED_LOGINS QStringLiteral("FailedLogins")
40+#define PROP_LICENSE_ACCEPTED QStringLiteral("LicenseAccepted")
41+#define PROP_LICENSE_BASE_PATH QStringLiteral("LicenseBasePath")
42+#define PROP_MOUSE_CURSOR_SPEED QStringLiteral("MouseCursorSpeed")
43+#define PROP_MOUSE_DOUBLE_CLICK_SPEED QStringLiteral("MouseDoubleClickSpeed")
44+#define PROP_MOUSE_PRIMARY_BUTTON QStringLiteral("MousePrimaryButton")
45+#define PROP_MOUSE_SCROLL_SPEED QStringLiteral("MouseScrollSpeed")
46+#define PROP_PASSWORD_DISPLAY_HINT QStringLiteral("PasswordDisplayHint")
47+#define PROP_REAL_NAME QStringLiteral("RealName")
48+#define PROP_STATS_WELCOME_SCREEN QStringLiteral("StatsWelcomeScreen")
49+#define PROP_TOUCHPAD_CURSOR_SPEED QStringLiteral("TouchpadCursorSpeed")
50+#define PROP_TOUCHPAD_DISABLE_WHILE_TYPING QStringLiteral("TouchpadDisableWhileTyping")
51+#define PROP_TOUCHPAD_DISABLE_WITH_MOUSE QStringLiteral("TouchpadDisableWithMouse")
52+#define PROP_TOUCHPAD_DOUBLE_CLICK_SPEED QStringLiteral("TouchpadDoubleClickSpeed")
53+#define PROP_TOUCHPAD_PRIMARY_BUTTON QStringLiteral("TouchpadPrimaryButton")
54+#define PROP_TOUCHPAD_SCROLL_SPEED QStringLiteral("TouchpadScrollSpeed")
55+#define PROP_TOUCHPAD_TAP_TO_CLICK QStringLiteral("TouchpadTapToClick")
56+#define PROP_TOUCHPAD_TWO_FINGER_SCROLL QStringLiteral("TouchpadTwoFingerScroll")
57+
58+
59+QVariant primaryButtonConverter(const QVariant &value)
60+{
61+ QString stringValue = value.toString();
62+ if (stringValue == "left") {
63+ return QVariant::fromValue(0);
64+ } else if (stringValue == "right") {
65+ return QVariant::fromValue(1); // Mir is less clear on this -- any non-zero value is the same
66+ } else {
67+ return QVariant::fromValue(0); // default to left
68+ }
69+}
70+
27AccountsService::AccountsService(QObject* parent, const QString &user)
71AccountsService::AccountsService(QObject* parent, const QString &user)
28− : QObject(parent),
72+ : QObject(parent)
29− m_service(new AccountsServiceDBusAdaptor(this)),
73+ , m_service(new AccountsServiceDBusAdaptor(this))
30− m_demoEdges(false),
31− m_demoEdgesCompleted(),
32− m_enableLauncherWhileLocked(false),
33− m_enableIndicatorsWhileLocked(false),
34− m_statsWelcomeScreen(false),
35− m_passwordDisplayHint(Keyboard),
36− m_failedLogins(0),
37− m_hereEnabled(false),
38− m_hereLicensePath() // null means not set yet
39{
74{
40 m_unityInput = new QDBusInterface(QStringLiteral("com.canonical.Unity.Input"),
75 m_unityInput = new QDBusInterface(QStringLiteral("com.canonical.Unity.Input"),
41 QStringLiteral("/com/canonical/Unity/Input"),
76 QStringLiteral("/com/canonical/Unity/Input"),
@@ -45,6 +80,46 @@ AccountsService::AccountsService(QObject* parent, const QString &user)
45 connect(m_service, &AccountsServiceDBusAdaptor::propertiesChanged, this, &AccountsService::onPropertiesChanged);
80 connect(m_service, &AccountsServiceDBusAdaptor::propertiesChanged, this, &AccountsService::onPropertiesChanged);
46 connect(m_service, &AccountsServiceDBusAdaptor::maybeChanged, this, &AccountsService::onMaybeChanged);
81 connect(m_service, &AccountsServiceDBusAdaptor::maybeChanged, this, &AccountsService::onMaybeChanged);
47
82
83+ registerProperty(IFACE_ACCOUNTS_USER, PROP_BACKGROUND_FILE, QStringLiteral("backgroundFileChanged"));
84+ registerProperty(IFACE_ACCOUNTS_USER, PROP_EMAIL, QStringLiteral("emailChanged"));
85+ registerProperty(IFACE_ACCOUNTS_USER, PROP_REAL_NAME, QStringLiteral("realNameChanged"));
86+ registerProperty(IFACE_LOCATION_HERE, PROP_LICENSE_ACCEPTED, QStringLiteral("hereEnabledChanged"));
87+ registerProperty(IFACE_LOCATION_HERE, PROP_LICENSE_BASE_PATH, QStringLiteral("hereLicensePathChanged"));
88+ registerProperty(IFACE_UBUNTU_SECURITY, PROP_ENABLE_LAUNCHER_WHILE_LOCKED, QStringLiteral("enableLauncherWhileLockedChanged"));
89+ registerProperty(IFACE_UBUNTU_SECURITY, PROP_ENABLE_INDICATORS_WHILE_LOCKED, QStringLiteral("enableIndicatorsWhileLockedChanged"));
90+ registerProperty(IFACE_UBUNTU_SECURITY, PROP_PASSWORD_DISPLAY_HINT, QStringLiteral("passwordDisplayHintChanged"));
91+ registerProperty(IFACE_UBUNTU_SECURITY_OLD, PROP_STATS_WELCOME_SCREEN, QStringLiteral("statsWelcomeScreenChanged"));
92+ registerProperty(IFACE_UNITY, PROP_DEMO_EDGES, QStringLiteral("demoEdgesChanged"));
93+ registerProperty(IFACE_UNITY, PROP_DEMO_EDGES_COMPLETED, QStringLiteral("demoEdgesCompletedChanged"));
94+ registerProperty(IFACE_UNITY_PRIVATE, PROP_FAILED_LOGINS, QStringLiteral("failedLoginsChanged"));
95+
96+ registerProxy(IFACE_UBUNTU_INPUT, PROP_MOUSE_CURSOR_SPEED,
97+ m_unityInput, QStringLiteral("setMouseCursorSpeed"));
98+ registerProxy(IFACE_UBUNTU_INPUT, PROP_MOUSE_DOUBLE_CLICK_SPEED,
99+ m_unityInput, QStringLiteral("setMouseDoubleClickSpeed"));
100+ registerProxy(IFACE_UBUNTU_INPUT, PROP_MOUSE_PRIMARY_BUTTON,
101+ m_unityInput, QStringLiteral("setMousePrimaryButton"),
102+ primaryButtonConverter);
103+ registerProxy(IFACE_UBUNTU_INPUT, PROP_MOUSE_SCROLL_SPEED,
104+ m_unityInput, QStringLiteral("setMouseScrollSpeed"));
105+ registerProxy(IFACE_UBUNTU_INPUT, PROP_TOUCHPAD_CURSOR_SPEED,
106+ m_unityInput, QStringLiteral("setTouchpadCursorSpeed"));
107+ registerProxy(IFACE_UBUNTU_INPUT, PROP_TOUCHPAD_SCROLL_SPEED,
108+ m_unityInput, QStringLiteral("setTouchpadScrollSpeed"));
109+ registerProxy(IFACE_UBUNTU_INPUT, PROP_TOUCHPAD_DISABLE_WHILE_TYPING,
110+ m_unityInput, QStringLiteral("setTouchpadDisableWhileTyping"));
111+ registerProxy(IFACE_UBUNTU_INPUT, PROP_TOUCHPAD_DISABLE_WITH_MOUSE,
112+ m_unityInput, QStringLiteral("setTouchpadDisableWithMouse"));
113+ registerProxy(IFACE_UBUNTU_INPUT, PROP_TOUCHPAD_DOUBLE_CLICK_SPEED,
114+ m_unityInput, QStringLiteral("setTouchpadDoubleClickSpeed"));
115+ registerProxy(IFACE_UBUNTU_INPUT, PROP_TOUCHPAD_PRIMARY_BUTTON,
116+ m_unityInput, QStringLiteral("setTouchpadPrimaryButton"),
117+ primaryButtonConverter);
118+ registerProxy(IFACE_UBUNTU_INPUT, PROP_TOUCHPAD_TAP_TO_CLICK,
119+ m_unityInput, QStringLiteral("setTouchpadTapToClick"));
120+ registerProxy(IFACE_UBUNTU_INPUT, PROP_TOUCHPAD_TWO_FINGER_SCROLL,
121+ m_unityInput, QStringLiteral("setTouchpadTwoFingerScroll"));
122+
48 setUser(!user.isEmpty() ? user : QString::fromUtf8(qgetenv("USER")));
123 setUser(!user.isEmpty() ? user : QString::fromUtf8(qgetenv("USER")));
49}
124}
50
125
@@ -58,455 +133,236 @@ void AccountsService::setUser(const QString &user)
58 if (user.isEmpty() || m_user == user)
133 if (user.isEmpty() || m_user == user)
59 return;
134 return;
60
135
136+ bool wasEmpty = m_user.isEmpty();
137+
61 m_user = user;
138 m_user = user;
62 Q_EMIT userChanged();
139 Q_EMIT userChanged();
63
140
64− updateDemoEdgesCompleted(false);
141+ // Do the first update synchronously, as a cheap way to block rendering
65− updateDemoEdges(false);
142+ // until we have the right values on bootup.
66− updateEnableLauncherWhileLocked(false);
143+ refresh(!wasEmpty);
67− updateEnableIndicatorsWhileLocked(false);
68− updateBackgroundFile(false);
69− updateMouseCursorSpeed();
70− updateTouchpadCursorSpeed();
71− updateStatsWelcomeScreen(false);
72− updatePasswordDisplayHint(false);
73− updateFailedLogins(false);
74− updateHereEnabled(false);
75− updateHereLicensePath(false);
76}
144}
77
145
78bool AccountsService::demoEdges() const
146bool AccountsService::demoEdges() const
79{
147{
80− return m_demoEdges;
148+ auto value = getProperty(IFACE_UNITY, PROP_DEMO_EDGES);
149+ return value.toBool();
81}
150}
82
151
83void AccountsService::setDemoEdges(bool demoEdges)
152void AccountsService::setDemoEdges(bool demoEdges)
84{
153{
85− if (m_demoEdges != demoEdges) {
154+ setProperty(IFACE_UNITY, PROP_DEMO_EDGES, demoEdges);
86− m_demoEdges = demoEdges;
87− m_service->setUserPropertyAsync(m_user, QStringLiteral("com.canonical.unity.AccountsService"), QStringLiteral("demo-edges"), demoEdges);
88−
89− Q_EMIT demoEdgesChanged();
90− }
91}
155}
92
156
93QStringList AccountsService::demoEdgesCompleted() const
157QStringList AccountsService::demoEdgesCompleted() const
94{
158{
95− return m_demoEdgesCompleted;
159+ auto value = getProperty(IFACE_UNITY, PROP_DEMO_EDGES_COMPLETED);
160+ return value.toStringList();
96}
161}
97
162
98void AccountsService::markDemoEdgeCompleted(const QString &edge)
163void AccountsService::markDemoEdgeCompleted(const QString &edge)
99{
164{
165+ auto currentList = demoEdgesCompleted();
100− if (!m_demoEdgesCompleted.contains(edge)) {
166+ if (!currentList.contains(edge)) {
101− m_demoEdgesCompleted << edge;
167+ setProperty(IFACE_UNITY, PROP_DEMO_EDGES_COMPLETED, currentList << edge);
102− m_service->setUserPropertyAsync(m_user, "com.canonical.unity.AccountsService", "DemoEdgesCompleted", m_demoEdgesCompleted);
103−
104− Q_EMIT demoEdgesCompletedChanged();
105 }
168 }
106}
169}
107
170
108bool AccountsService::enableLauncherWhileLocked() const
171bool AccountsService::enableLauncherWhileLocked() const
109{
172{
110− return m_enableLauncherWhileLocked;
173+ auto value = getProperty(IFACE_UBUNTU_SECURITY, PROP_ENABLE_LAUNCHER_WHILE_LOCKED);
174+ return value.toBool();
111}
175}
112
176
113bool AccountsService::enableIndicatorsWhileLocked() const
177bool AccountsService::enableIndicatorsWhileLocked() const
114{
178{
115− return m_enableIndicatorsWhileLocked;
179+ auto value = getProperty(IFACE_UBUNTU_SECURITY, PROP_ENABLE_INDICATORS_WHILE_LOCKED);
180+ return value.toBool();
116}
181}
117
182
118QString AccountsService::backgroundFile() const
183QString AccountsService::backgroundFile() const
119{
184{
120− return m_backgroundFile;
185+ auto value = getProperty(IFACE_ACCOUNTS_USER, PROP_BACKGROUND_FILE);
186+ return value.toString();
121}
187}
122
188
123bool AccountsService::statsWelcomeScreen() const
189bool AccountsService::statsWelcomeScreen() const
124{
190{
125− return m_statsWelcomeScreen;
191+ auto value = getProperty(IFACE_UBUNTU_SECURITY_OLD, PROP_STATS_WELCOME_SCREEN);
192+ return value.toBool();
126}
193}
127
194
128AccountsService::PasswordDisplayHint AccountsService::passwordDisplayHint() const
195AccountsService::PasswordDisplayHint AccountsService::passwordDisplayHint() const
129{
196{
130− return m_passwordDisplayHint;
197+ auto value = getProperty(IFACE_UBUNTU_SECURITY, PROP_PASSWORD_DISPLAY_HINT);
198+ return (PasswordDisplayHint)value.toInt();
131}
199}
132
200
133bool AccountsService::hereEnabled() const
201bool AccountsService::hereEnabled() const
134{
202{
135− return m_hereEnabled;
203+ auto value = getProperty(IFACE_LOCATION_HERE, PROP_LICENSE_ACCEPTED);
204+ return value.toBool();
136}
205}
137
206
138void AccountsService::setHereEnabled(bool enabled)
207void AccountsService::setHereEnabled(bool enabled)
139{
208{
140− if (m_hereEnabled != enabled) {
209+ setProperty(IFACE_LOCATION_HERE, PROP_LICENSE_ACCEPTED, enabled);
141− m_hereEnabled = enabled;
142− m_service->setUserPropertyAsync(m_user, QStringLiteral("com.ubuntu.location.providers.here.AccountsService"), QStringLiteral("LicenseAccepted"), enabled);
143−
144− Q_EMIT hereEnabledChanged();
145− }
146}
210}
147
211
148QString AccountsService::hereLicensePath() const
212QString AccountsService::hereLicensePath() const
149{
213{
150− return m_hereLicensePath;
214+ auto value = getProperty(IFACE_LOCATION_HERE, PROP_LICENSE_BASE_PATH);
215+ QString hereLicensePath = value.toString();
216+ if (hereLicensePath.isEmpty() || !QFile::exists(hereLicensePath))
217+ hereLicensePath = QStringLiteral("");
218+ return hereLicensePath;
151}
219}
152
220
153bool AccountsService::hereLicensePathValid() const
221bool AccountsService::hereLicensePathValid() const
154{
222{
223+ auto value = getProperty(IFACE_LOCATION_HERE, PROP_LICENSE_BASE_PATH);
155− return !m_hereLicensePath.isNull();
224+ return !value.toString().isNull();
156}
225}
157
226
158−void AccountsService::updateDemoEdges(bool async)
227+QString AccountsService::realName() const
159{
228{
160− QDBusPendingCall pendingReply = m_service->getUserPropertyAsync(m_user,
229+ auto value = getProperty(IFACE_ACCOUNTS_USER, PROP_REAL_NAME);
161− QStringLiteral("com.canonical.unity.AccountsService"),
162− QStringLiteral("demo-edges"));
163− QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingReply, this);
164−
165− connect(watcher, &QDBusPendingCallWatcher::finished,
166− this, [this](QDBusPendingCallWatcher* watcher) {
167−
168− QDBusPendingReply<QDBusVariant> reply = *watcher;
169− watcher->deleteLater();
170− if (reply.isError()) {
171− qWarning() << "Failed to get 'demo-edges' property - " << reply.error().message();
172− return;
230+ return value.toString();
173− }
174−
175− auto demoEdges = reply.value().variant().toBool();
176− if (m_demoEdges != demoEdges) {
177− m_demoEdges = demoEdges;
178− Q_EMIT demoEdgesChanged();
179− }
180− });
181− if (!async) {
182− watcher->waitForFinished();
183− delete watcher;
184− }
185}
231}
186
232
187−void AccountsService::updateDemoEdgesCompleted(bool async)
233+void AccountsService::setRealName(const QString &realName)
188{
234{
189− QDBusPendingCall pendingReply = m_service->getUserPropertyAsync(m_user,
235+ setProperty(IFACE_ACCOUNTS_USER, PROP_REAL_NAME, realName);
190− "com.canonical.unity.AccountsService",
191− "DemoEdgesCompleted");
192− QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingReply, this);
193−
194− connect(watcher, &QDBusPendingCallWatcher::finished,
195− this, [this](QDBusPendingCallWatcher* watcher) {
196−
197− QDBusPendingReply<QDBusVariant> reply = *watcher;
198− watcher->deleteLater();
199− if (reply.isError()) {
200− qWarning() << "Failed to get 'DemoEdgesCompleted' property - " << reply.error().message();
201− return;
202− }
203−
204− auto demoEdgesCompleted = reply.value().variant().toStringList();
205− if (m_demoEdgesCompleted != demoEdgesCompleted) {
206− m_demoEdgesCompleted = demoEdgesCompleted;
207− Q_EMIT demoEdgesCompletedChanged();
208− }
209− });
210− if (!async) {
211− watcher->waitForFinished();
212− delete watcher;
213− }
214}
236}
215
237
216−void AccountsService::updateEnableLauncherWhileLocked(bool async)
238+QString AccountsService::email() const
217{
239{
218− QDBusPendingCall pendingReply = m_service->getUserPropertyAsync(m_user,
240+ auto value = getProperty(IFACE_ACCOUNTS_USER, PROP_EMAIL);
219− QStringLiteral("com.ubuntu.AccountsService.SecurityPrivacy"),
220− QStringLiteral("EnableLauncherWhileLocked"));
221− QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingReply, this);
222−
223− connect(watcher, &QDBusPendingCallWatcher::finished,
224− this, [this](QDBusPendingCallWatcher* watcher) {
225−
226− QDBusPendingReply<QVariant> reply = *watcher;
227− watcher->deleteLater();
228− if (reply.isError()) {
229− qWarning() << "Failed to get 'EnableLauncherWhileLocked' property - " << reply.error().message();
230− return;
241+ return value.toString();
231− }
232−
233− const bool enableLauncherWhileLocked = reply.value().toBool();
234− if (m_enableLauncherWhileLocked != enableLauncherWhileLocked) {
235− m_enableLauncherWhileLocked = enableLauncherWhileLocked;
236− Q_EMIT enableLauncherWhileLockedChanged();
237− }
238− });
239− if (!async) {
240− watcher->waitForFinished();
241− delete watcher;
242− }
243}
242}
244
243
245−void AccountsService::updateEnableIndicatorsWhileLocked(bool async)
244+void AccountsService::setEmail(const QString &email)
246{
245{
247− QDBusPendingCall pendingReply = m_service->getUserPropertyAsync(m_user,
246+ setProperty(IFACE_ACCOUNTS_USER, PROP_EMAIL, email);
248− QStringLiteral("com.ubuntu.AccountsService.SecurityPrivacy"),
249− QStringLiteral("EnableIndicatorsWhileLocked"));
250− QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingReply, this);
251−
252− connect(watcher, &QDBusPendingCallWatcher::finished,
253− this, [this](QDBusPendingCallWatcher* watcher) {
254−
255− QDBusPendingReply<QVariant> reply = *watcher;
256− watcher->deleteLater();
257− if (reply.isError()) {
258− qWarning() << "Failed to get 'EnableIndicatorsWhileLocked' property - " << reply.error().message();
259− return;
260− }
261−
262− const bool enableIndicatorsWhileLocked = reply.value().toBool();
263− if (m_enableIndicatorsWhileLocked != enableIndicatorsWhileLocked) {
264− m_enableIndicatorsWhileLocked = enableIndicatorsWhileLocked;
265− Q_EMIT enableIndicatorsWhileLockedChanged();
266− }
267− });
268− if (!async) {
269− watcher->waitForFinished();
270− delete watcher;
271− }
272}
247}
273
248
274−void AccountsService::updateBackgroundFile(bool async)
249+uint AccountsService::failedLogins() const
275{
250{
276− QDBusPendingCall pendingReply = m_service->getUserPropertyAsync(m_user,
251+ return getProperty(IFACE_UNITY_PRIVATE, PROP_FAILED_LOGINS).toUInt();
277− QStringLiteral("org.freedesktop.Accounts.User"),
278− QStringLiteral("BackgroundFile"));
279− QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingReply, this);
280−
281− connect(watcher, &QDBusPendingCallWatcher::finished,
282− this, [this](QDBusPendingCallWatcher* watcher) {
283−
284− QDBusPendingReply<QVariant> reply = *watcher;
285− watcher->deleteLater();
286− if (reply.isError()) {
287− qWarning() << "Failed to get 'BackgroundFile' property - " << reply.error().message();
288− return;
289− }
290−
291− const QString backgroundFile = reply.value().toString();
292− if (m_backgroundFile != backgroundFile) {
293− m_backgroundFile = backgroundFile;
294− Q_EMIT backgroundFileChanged();
295− }
296− });
297− if (!async) {
298− watcher->waitForFinished();
299− delete watcher;
300− }
301}
252}
302
253
303−void AccountsService::updateMouseCursorSpeed()
254+void AccountsService::setFailedLogins(uint failedLogins)
304{
255{
305− QDBusPendingCall pendingReply = m_service->getUserPropertyAsync(m_user,
256+ setProperty(IFACE_UNITY_PRIVATE, PROP_FAILED_LOGINS, failedLogins);
306− QStringLiteral("com.ubuntu.AccountsService.Input"),
307− QStringLiteral("MouseCursorSpeed"));
308− QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingReply, this);
309−
310− connect(watcher, &QDBusPendingCallWatcher::finished,
311− this, [this](QDBusPendingCallWatcher* watcher) {
312−
313− QDBusPendingReply<QVariant> reply = *watcher;
314− watcher->deleteLater();
315− if (reply.isError()) {
316− qWarning() << "Failed to get 'MouseCursorSpeed' property - " << reply.error().message();
317− return;
318− }
319−
320− // Merely proxy this along to USC. We don't care about keeping a copy
321− // or exporting it internally.
322− m_unityInput->asyncCall(QStringLiteral("setMouseCursorSpeed"), reply.value());
323− });
324}
257}
325
258
326−void AccountsService::updateTouchpadCursorSpeed()
259+// ====================================================
327−{
260+// Everything below this line is generic helper methods
328− QDBusPendingCall pendingReply = m_service->getUserPropertyAsync(m_user,
261+// ====================================================
329− QStringLiteral("com.ubuntu.AccountsService.Input"),
330− QStringLiteral("TouchpadCursorSpeed"));
331− QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingReply, this);
332−
333− connect(watcher, &QDBusPendingCallWatcher::finished,
334− this, [this](QDBusPendingCallWatcher* watcher) {
335−
336− QDBusPendingReply<QVariant> reply = *watcher;
337− watcher->deleteLater();
338− if (reply.isError()) {
339− qWarning() << "Failed to get 'TouchpadCursorSpeed' property - " << reply.error().message();
340− return;
341− }
342
262
343− // Merely proxy this along to USC. We don't care about keeping a copy
263+void AccountsService::emitChangedForProperty(const QString &interface, const QString &property)
344− // or exporting it internally.
264+{
345− m_unityInput->asyncCall(QStringLiteral("setTouchpadCursorSpeed"), reply.value());
265+ QString signalName = m_properties[interface][property].signal;
346− });
266+ QMetaObject::invokeMethod(this, signalName.toUtf8().data());
347}
267}
348
268
349−void AccountsService::updateStatsWelcomeScreen(bool async)
269+QVariant AccountsService::getProperty(const QString &interface, const QString &property) const
350{
270{
351− QDBusPendingCall pendingReply = m_service->getUserPropertyAsync(m_user,
271+ return m_properties[interface][property].value;
352− QStringLiteral("com.ubuntu.touch.AccountsService.SecurityPrivacy"),
353− QStringLiteral("StatsWelcomeScreen"));
354− QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingReply, this);
355−
356− connect(watcher, &QDBusPendingCallWatcher::finished,
357− this, [this](QDBusPendingCallWatcher* watcher) {
358−
359− QDBusPendingReply<QVariant> reply = *watcher;
360− watcher->deleteLater();
361− if (reply.isError()) {
362− qWarning() << "Failed to get 'StatsWelcomeScreen' property - " << reply.error().message();
363− return;
364− }
365−
366− const bool statsWelcomeScreen = reply.value().toBool();
367− if (m_statsWelcomeScreen != statsWelcomeScreen) {
368− m_statsWelcomeScreen = statsWelcomeScreen;
369− Q_EMIT statsWelcomeScreenChanged();
370− }
371− });
372− if (!async) {
373− watcher->waitForFinished();
374− delete watcher;
375− }
376}
272}
377
273
378−void AccountsService::updatePasswordDisplayHint(bool async)
274+void AccountsService::setProperty(const QString &interface, const QString &property, const QVariant &value)
379{
275{
380− QDBusPendingCall pendingReply = m_service->getUserPropertyAsync(m_user,
276+ if (m_properties[interface][property].value != value) {
381− QStringLiteral("com.ubuntu.AccountsService.SecurityPrivacy"),
277+ m_properties[interface][property].value = value;
382− QStringLiteral("PasswordDisplayHint"));
278+ m_service->setUserPropertyAsync(m_user, interface, property, value);
383− QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingReply, this);
279+ emitChangedForProperty(interface, property);
384−
385− connect(watcher, &QDBusPendingCallWatcher::finished,
386− this, [this](QDBusPendingCallWatcher* watcher) {
387−
388− QDBusPendingReply<QVariant> reply = *watcher;
389− watcher->deleteLater();
390− if (reply.isError()) {
391− qWarning() << "Failed to get 'PasswordDisplayHint' property - " << reply.error().message();
392− return;
393− }
394−
395− const PasswordDisplayHint passwordDisplayHint = (PasswordDisplayHint)reply.value().toInt();
396− if (m_passwordDisplayHint != passwordDisplayHint) {
397− m_passwordDisplayHint = passwordDisplayHint;
398− Q_EMIT passwordDisplayHintChanged();
399− }
400− });
401− if (!async) {
402− watcher->waitForFinished();
403− delete watcher;
404 }
280 }
405}
281}
406
282
407−void AccountsService::updateFailedLogins(bool async)
283+void AccountsService::updateCache(const QString &interface, const QString &property, const QVariant &value)
408{
284{
409− QDBusPendingCall pendingReply = m_service->getUserPropertyAsync(m_user,
285+ PropertyInfo &info = m_properties[interface][property];
410− QStringLiteral("com.canonical.unity.AccountsService.Private"),
411− QStringLiteral("FailedLogins"));
412− QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingReply, this);
413−
414− connect(watcher, &QDBusPendingCallWatcher::finished,
415− this, [this](QDBusPendingCallWatcher* watcher) {
416
286
417− QDBusPendingReply<QVariant> reply = *watcher;
287+ if (info.proxyInterface) {
418− watcher->deleteLater();
288+ QVariant finalValue;
419− if (reply.isError()) {
289+ if (info.proxyConverter) {
420− qWarning() << "Failed to get 'FailedLogins' property - " << reply.error().message();
290+ finalValue = info.proxyConverter(value);
421− return;
291+ } else {
292+ finalValue = value;
422 }
293 }
294+ info.proxyInterface->asyncCall(info.proxyMethod, finalValue);
295+ return; // don't bother saving a copy
296+ }
423
297
424− const uint failedLogins = reply.value().toUInt();
298+ if (info.value != value) {
425− if (m_failedLogins != failedLogins) {
299+ info.value = value;
426− m_failedLogins = failedLogins;
300+ emitChangedForProperty(interface, property);
427− Q_EMIT failedLoginsChanged();
428− }
429− });
430− if (!async) {
431− watcher->waitForFinished();
432− delete watcher;
433 }
301 }
434}
302}
435
303
436−void AccountsService::updateHereEnabled(bool async)
304+void AccountsService::updateProperty(const QString &interface, const QString &property)
437{
305{
438 QDBusPendingCall pendingReply = m_service->getUserPropertyAsync(m_user,
306 QDBusPendingCall pendingReply = m_service->getUserPropertyAsync(m_user,
439− QStringLiteral("com.ubuntu.location.providers.here.AccountsService"),
307+ interface,
440− QStringLiteral("LicenseAccepted"));
308+ property);
441 QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingReply, this);
309 QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingReply, this);
442
310
443 connect(watcher, &QDBusPendingCallWatcher::finished,
311 connect(watcher, &QDBusPendingCallWatcher::finished,
444− this, [this](QDBusPendingCallWatcher* watcher) {
312+ this, [this, interface, property](QDBusPendingCallWatcher* watcher) {
445
313
446 QDBusPendingReply<QVariant> reply = *watcher;
314 QDBusPendingReply<QVariant> reply = *watcher;
447 watcher->deleteLater();
315 watcher->deleteLater();
448 if (reply.isError()) {
316 if (reply.isError()) {
449− qWarning() << "Failed to get 'LicenseAccepted' property - " << reply.error().message();
317+ qWarning() << "Failed to get '" << property << "' property:" << reply.error().message();
450 return;
318 return;
451 }
319 }
452
320
453− const bool hereEnabled = reply.value().toBool();
321+ updateCache(interface, property, reply.value());
454− if (m_hereEnabled != hereEnabled) {
455− m_hereEnabled = hereEnabled;
456− Q_EMIT hereEnabledChanged();
457− }
458 });
322 });
459− if (!async) {
460− watcher->waitForFinished();
461− delete watcher;
462− }
463}
323}
464
324
465−void AccountsService::updateHereLicensePath(bool async)
325+void AccountsService::updateAllProperties(const QString &interface, bool async)
466{
326{
467− QDBusPendingCall pendingReply = m_service->getUserPropertyAsync(m_user,
327+ QDBusPendingCall pendingReply = m_service->getAllPropertiesAsync(m_user,
468− QStringLiteral("com.ubuntu.location.providers.here.AccountsService"),
328+ interface);
469− QStringLiteral("LicenseBasePath"));
470 QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingReply, this);
329 QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingReply, this);
471
330
472 connect(watcher, &QDBusPendingCallWatcher::finished,
331 connect(watcher, &QDBusPendingCallWatcher::finished,
473− this, [this](QDBusPendingCallWatcher* watcher) {
332+ this, [this, interface](QDBusPendingCallWatcher* watcher) {
474
333
475− QDBusPendingReply<QVariant> reply = *watcher;
334+ QDBusPendingReply< QHash<QString, QVariant> > reply = *watcher;
476 watcher->deleteLater();
335 watcher->deleteLater();
477 if (reply.isError()) {
336 if (reply.isError()) {
478− qWarning() << "Failed to get 'LicenseBasePath' property - " << reply.error().message();
337+ qWarning() << "Failed to get all properties for" << interface << ":" << reply.error().message();
479 return;
338 return;
480 }
339 }
481
340
482− QString hereLicensePath = reply.value().toString();
341+ auto valueHash = reply.value();
483− if (hereLicensePath.isEmpty() || !QFile::exists(hereLicensePath))
342+ auto i = valueHash.constBegin();
484− hereLicensePath = QLatin1String("");
343+ while (i != valueHash.constEnd()) {
485−
344+ updateCache(interface, i.key(), i.value());
486− if (m_hereLicensePath.isNull() || m_hereLicensePath != hereLicensePath) {
345+ ++i;
487− m_hereLicensePath = hereLicensePath;
488− Q_EMIT hereLicensePathChanged();
489 }
346 }
490 });
347 });
491 if (!async) {
348 if (!async) {
492 watcher->waitForFinished();
349 watcher->waitForFinished();
493− delete watcher;
494 }
350 }
495}
351}
496
352
497−uint AccountsService::failedLogins() const
353+void AccountsService::registerProxy(const QString &interface, const QString &property, QDBusInterface *iface, const QString &method, ProxyConverter converter)
498{
354{
499− return m_failedLogins;
355+ registerProperty(interface, property, nullptr);
356+
357+ m_properties[interface][property].proxyInterface = iface;
358+ m_properties[interface][property].proxyMethod = method;
359+ m_properties[interface][property].proxyConverter = converter;
500}
360}
501
361
502−void AccountsService::setFailedLogins(uint failedLogins)
362+void AccountsService::registerProperty(const QString &interface, const QString &property, const QString &signal)
503{
363{
504− if (m_failedLogins != failedLogins) {
364+ m_properties[interface][property] = PropertyInfo();
505− m_failedLogins = failedLogins;
365+ m_properties[interface][property].signal = signal;
506− m_service->setUserPropertyAsync(m_user, QStringLiteral("com.canonical.unity.AccountsService.Private"), QStringLiteral("FailedLogins"), failedLogins);
507−
508− Q_EMIT failedLoginsChanged();
509− }
510}
366}
511
367
512void AccountsService::onPropertiesChanged(const QString &user, const QString &interface, const QStringList &changed)
368void AccountsService::onPropertiesChanged(const QString &user, const QString &interface, const QStringList &changed)
@@ -515,45 +371,13 @@ void AccountsService::onPropertiesChanged(const QString &user, const QString &in
515 return;
371 return;
516 }
372 }
517
373
518− if (interface == QLatin1String("com.canonical.unity.AccountsService")) {
374+ auto propHash = m_properties.value(interface);
519− if (changed.contains(QStringLiteral("demo-edges"))) {
375+ auto i = propHash.constBegin();
520− updateDemoEdges();
376+ while (i != propHash.constEnd()) {
521− }
522− if (changed.contains(QStringLiteral("DemoEdgesCompleted"))) {
523− updateDemoEdgesCompleted();
524− }
525− } else if (interface == QLatin1String("com.canonical.unity.AccountsService.Private")) {
526− if (changed.contains(QStringLiteral("FailedLogins"))) {
527− updateFailedLogins();
528− }
529− } else if (interface == QLatin1String("com.ubuntu.AccountsService.Input")) {
530− if (changed.contains(QStringLiteral("MouseCursorSpeed"))) {
531− updateMouseCursorSpeed();
532− }
533− if (changed.contains(QStringLiteral("TouchpadCursorSpeed"))) {
534− updateTouchpadCursorSpeed();
535− }
536− } else if (interface == QLatin1String("com.ubuntu.touch.AccountsService.SecurityPrivacy")) {
537− if (changed.contains(QStringLiteral("StatsWelcomeScreen"))) {
538− updateStatsWelcomeScreen();
539− }
540− } else if (interface == QLatin1String("com.ubuntu.AccountsService.SecurityPrivacy")) {
541− if (changed.contains(QStringLiteral("PasswordDisplayHint"))) {
542− updatePasswordDisplayHint();
543− }
544− if (changed.contains(QStringLiteral("EnableLauncherWhileLocked"))) {
377+ if (changed.contains(i.key())) {
545− updateEnableLauncherWhileLocked();
378+ updateProperty(interface, i.key());
546− }
547− if (changed.contains(QStringLiteral("EnableIndicatorsWhileLocked"))) {
548− updateEnableIndicatorsWhileLocked();
549− }
550− } else if (interface == QLatin1String("com.ubuntu.location.providers.here.AccountsService")) {
551− if (changed.contains(QStringLiteral("LicenseAccepted"))) {
552− updateHereEnabled();
553− }
554− if (changed.contains(QStringLiteral("LicenseBasePath"))) {
555− updateHereLicensePath();
556 }
379 }
380+ ++i;
557 }
381 }
558}
382}
559
383
@@ -563,6 +387,20 @@ void AccountsService::onMaybeChanged(const QString &user)
563 return;
387 return;
564 }
388 }
565
389
566− // Standard properties might have changed
390+ // Any of the standard properties might have changed!
567− updateBackgroundFile();
391+ auto propHash = m_properties.value(IFACE_ACCOUNTS_USER);
392+ auto i = propHash.constBegin();
393+ while (i != propHash.constEnd()) {
394+ updateProperty(IFACE_ACCOUNTS_USER, i.key());
395+ ++i;
396+ }
397+}
398+
399+void AccountsService::refresh(bool async)
400+{
401+ auto i = m_properties.constBegin();
402+ while (i != m_properties.constEnd()) {
403+ updateAllProperties(i.key(), async);
404+ ++i;
405+ }
568}
406}
plugins/AccountsService/AccountsService.h+34−25
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013, 2015 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
5 * it under the terms of the GNU General Public License as published by
@@ -12,16 +12,16 @@
12 *
12 *
13 * You should have received a copy of the GNU General Public License
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/>.
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− *
16− * Authors: Michael Terry <michael.terry@canonical.com>
17 */
15 */
18
16
19#ifndef UNITY_ACCOUNTSSERVICE_H
17#ifndef UNITY_ACCOUNTSSERVICE_H
20#define UNITY_ACCOUNTSSERVICE_H
18#define UNITY_ACCOUNTSSERVICE_H
21
19
20+#include <QHash>
22#include <QObject>
21#include <QObject>
23#include <QString>
22#include <QString>
24#include <QStringList>
23#include <QStringList>
24+#include <QVariant>
25
25
26class AccountsServiceDBusAdaptor;
26class AccountsServiceDBusAdaptor;
27class QDBusInterface;
27class QDBusInterface;
@@ -70,6 +70,8 @@ class AccountsService: public QObject
70 Q_PROPERTY(bool hereLicensePathValid // qml sees a null string as "", so we use proxy setting for that
70 Q_PROPERTY(bool hereLicensePathValid // qml sees a null string as "", so we use proxy setting for that
71 READ hereLicensePathValid
71 READ hereLicensePathValid
72 NOTIFY hereLicensePathChanged)
72 NOTIFY hereLicensePathChanged)
73+ Q_PROPERTY(QString realName READ realName WRITE setRealName NOTIFY realNameChanged)
74+ Q_PROPERTY(QString email READ email WRITE setEmail NOTIFY emailChanged)
73
75
74public:
76public:
75 enum PasswordDisplayHint {
77 enum PasswordDisplayHint {
@@ -97,6 +99,10 @@ public:
97 void setHereEnabled(bool enabled);
99 void setHereEnabled(bool enabled);
98 QString hereLicensePath() const;
100 QString hereLicensePath() const;
99 bool hereLicensePathValid() const;
101 bool hereLicensePathValid() const;
102+ QString realName() const;
103+ void setRealName(const QString &realName);
104+ QString email() const;
105+ void setEmail(const QString &email);
100
106
101Q_SIGNALS:
107Q_SIGNALS:
102 void userChanged();
108 void userChanged();
@@ -110,38 +116,41 @@ Q_SIGNALS:
110 void failedLoginsChanged();
116 void failedLoginsChanged();
111 void hereEnabledChanged();
117 void hereEnabledChanged();
112 void hereLicensePathChanged();
118 void hereLicensePathChanged();
119+ void realNameChanged();
120+ void emailChanged();
113
121
114private Q_SLOTS:
122private Q_SLOTS:
115 void onPropertiesChanged(const QString &user, const QString &interface, const QStringList &changed);
123 void onPropertiesChanged(const QString &user, const QString &interface, const QStringList &changed);
116 void onMaybeChanged(const QString &user);
124 void onMaybeChanged(const QString &user);
117
125
118private:
126private:
119− void updateDemoEdges(bool async = true);
127+ typedef QVariant (*ProxyConverter)(const QVariant &);
128+
120− void updateDemoEdgesCompleted(bool async = true);
129+ void refresh(bool async);
121− void updateEnableLauncherWhileLocked(bool async = true);
130+ void registerProperty(const QString &interface, const QString &property, const QString &signal);
122− void updateEnableIndicatorsWhileLocked(bool async = true);
131+ void registerProxy(const QString &interface, const QString &property, QDBusInterface *iface, const QString &method, ProxyConverter converter = nullptr);
123− void updateBackgroundFile(bool async = true);
132+
124− void updateMouseCursorSpeed();
133+ void updateAllProperties(const QString &interface, bool async);
125− void updateTouchpadCursorSpeed();
134+ void updateProperty(const QString &interface, const QString &property);
126− void updateStatsWelcomeScreen(bool async = true);
135+ void updateCache(const QString &interface, const QString &property, const QVariant &value);
127− void updatePasswordDisplayHint(bool async = true);
128− void updateFailedLogins(bool async = true);
129− void updateHereEnabled(bool async = true);
130− void updateHereLicensePath(bool async = true);
131
136
137+ void setProperty(const QString &interface, const QString &property, const QVariant &value);
138+ QVariant getProperty(const QString &interface, const QString &property) const;
139+
140+ void emitChangedForProperty(const QString &interface, const QString &property);
141+
142+ struct PropertyInfo {
143+ QVariant value{};
144+ QString signal{};
145+ QDBusInterface *proxyInterface{};
146+ QString proxyMethod{};
147+ ProxyConverter proxyConverter{};
148+ };
149+ typedef QHash< QString, QHash<QString, PropertyInfo> > PropertyHash;
150+ PropertyHash m_properties;
132 AccountsServiceDBusAdaptor *m_service;
151 AccountsServiceDBusAdaptor *m_service;
133 QDBusInterface *m_unityInput;
152 QDBusInterface *m_unityInput;
134 QString m_user;
153 QString m_user;
135− bool m_demoEdges;
136− QStringList m_demoEdgesCompleted;
137− bool m_enableLauncherWhileLocked;
138− bool m_enableIndicatorsWhileLocked;
139− QString m_backgroundFile;
140− bool m_statsWelcomeScreen;
141− PasswordDisplayHint m_passwordDisplayHint;
142− uint m_failedLogins;
143− bool m_hereEnabled;
144− QString m_hereLicensePath;
145};
154};
146
155
147#endif
156#endif
plugins/AccountsService/AccountsServiceDBusAdaptor.cpp+11−4
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013-2016 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
5 * it under the terms of the GNU General Public License as published by
@@ -12,8 +12,6 @@
12 *
12 *
13 * You should have received a copy of the GNU General Public License
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/>.
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− *
16− * Author: Michael Terry <michael.terry@canonical.com>
17 */
15 */
18
16
19#include "AccountsServiceDBusAdaptor.h"
17#include "AccountsServiceDBusAdaptor.h"
@@ -37,6 +35,15 @@ AccountsServiceDBusAdaptor::AccountsServiceDBusAdaptor(QObject* parent)
37 connection, this);
35 connection, this);
38}
36}
39
37
38+QDBusPendingReply<QVariantMap> AccountsServiceDBusAdaptor::getAllPropertiesAsync(const QString &user, const QString &interface)
39+{
40+ QDBusInterface *iface = getUserInterface(user);
41+ if (iface != nullptr && iface->isValid()) {
42+ return iface->asyncCall(QStringLiteral("GetAll"), interface);
43+ }
44+ return QDBusPendingReply<QVariantMap>(QDBusMessage::createError(QDBusError::Other, QStringLiteral("Invalid Interface")));
45+}
46+
40QDBusPendingReply<QVariant> AccountsServiceDBusAdaptor::getUserPropertyAsync(const QString &user, const QString &interface, const QString &property)
47QDBusPendingReply<QVariant> AccountsServiceDBusAdaptor::getUserPropertyAsync(const QString &user, const QString &interface, const QString &property)
41{
48{
42 QDBusInterface *iface = getUserInterface(user);
49 QDBusInterface *iface = getUserInterface(user);
@@ -80,7 +87,7 @@ void AccountsServiceDBusAdaptor::maybeChangedSlot()
80 m_ignoreNextChanged = false;
87 m_ignoreNextChanged = false;
81}
88}
82
89
83−QString AccountsServiceDBusAdaptor::getUserForPath(const QString &path)
90+QString AccountsServiceDBusAdaptor::getUserForPath(const QString &path) const
84{
91{
85 QMap<QString, QDBusInterface *>::const_iterator i;
92 QMap<QString, QDBusInterface *>::const_iterator i;
86 for (i = m_users.constBegin(); i != m_users.constEnd(); ++i) {
93 for (i = m_users.constBegin(); i != m_users.constEnd(); ++i) {
plugins/AccountsService/AccountsServiceDBusAdaptor.h+5−6
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013-2016 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
5 * it under the terms of the GNU General Public License as published by
@@ -12,8 +12,6 @@
12 *
12 *
13 * You should have received a copy of the GNU General Public License
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/>.
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− *
16− * Authors: Michael Terry <michael.terry@canonical.com>
17 */
15 */
18
16
19#ifndef UNITY_ACCOUNTSSERVICEDBUSADAPTOR_H
17#ifndef UNITY_ACCOUNTSSERVICEDBUSADAPTOR_H
@@ -35,8 +33,9 @@ public:
35 explicit AccountsServiceDBusAdaptor(QObject *parent = 0);
33 explicit AccountsServiceDBusAdaptor(QObject *parent = 0);
36 ~AccountsServiceDBusAdaptor() = default;
34 ~AccountsServiceDBusAdaptor() = default;
37
35
36+ QDBusPendingReply<QVariantMap> getAllPropertiesAsync(const QString &user, const QString &interface);
38− Q_INVOKABLE QDBusPendingReply<QVariant> getUserPropertyAsync(const QString &user, const QString &interface, const QString &property);
37+ QDBusPendingReply<QVariant> getUserPropertyAsync(const QString &user, const QString &interface, const QString &property);
39− Q_INVOKABLE QDBusPendingCall setUserPropertyAsync(const QString &user, const QString &interface, const QString &property, const QVariant &value);
38+ QDBusPendingCall setUserPropertyAsync(const QString &user, const QString &interface, const QString &property, const QVariant &value);
40
39
41Q_SIGNALS:
40Q_SIGNALS:
42 void propertiesChanged(const QString &user, const QString &interface, const QStringList &changed);
41 void propertiesChanged(const QString &user, const QString &interface, const QStringList &changed);
@@ -48,7 +47,7 @@ private Q_SLOTS:
48
47
49private:
48private:
50 QDBusInterface *getUserInterface(const QString &user);
49 QDBusInterface *getUserInterface(const QString &user);
51− QString getUserForPath(const QString &path);
50+ QString getUserForPath(const QString &path) const;
52
51
53 QDBusInterface *m_accountsManager;
52 QDBusInterface *m_accountsManager;
54 QMap<QString, QDBusInterface *> m_users;
53 QMap<QString, QDBusInterface *> m_users;
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/Dash/AudioProgressBar.qml+1
@@ -31,6 +31,7 @@ Item {
31 anchors { left: parent.left; right: parent.right }
31 anchors { left: parent.left; right: parent.right }
32 height: units.dp(6)
32 height: units.dp(6)
33 source: "graphics/music_progress_bg.png"
33 source: "graphics/music_progress_bg.png"
34+ asynchronous: true
34 sourceSize.width: width
35 sourceSize.width: width
35 sourceSize.height: height
36 sourceSize.height: height
36 }
37 }
plugins/Dash/CardCreator.js+69−34
@@ -18,12 +18,13 @@
18
18
19// %1 is the template["card-background"]["elements"][0]
19// %1 is the template["card-background"]["elements"][0]
20// %2 is the template["card-background"]["elements"][1]
20// %2 is the template["card-background"]["elements"][1]
21+// %3 is whether the loader should be asynchronous or not
21−// %3 is the template["card-background"] string
22+// %4 is the template["card-background"] string
22var kBackgroundLoaderCode = 'Loader {\n\
23var kBackgroundLoaderCode = 'Loader {\n\
23 id: backgroundLoader; \n\
24 id: backgroundLoader; \n\
24 objectName: "backgroundLoader"; \n\
25 objectName: "backgroundLoader"; \n\
25 anchors.fill: parent; \n\
26 anchors.fill: parent; \n\
26− asynchronous: root.asynchronous; \n\
27+ asynchronous: %3; \n\
27 visible: status == Loader.Ready; \n\
28 visible: status == Loader.Ready; \n\
28 sourceComponent: UbuntuShape { \n\
29 sourceComponent: UbuntuShape { \n\
29 objectName: "background"; \n\
30 objectName: "background"; \n\
@@ -46,7 +47,7 @@ var kBackgroundLoaderCode = 'Loader {\n\
46 objectName: "backgroundImage"; \n\
47 objectName: "backgroundImage"; \n\
47 source: { \n\
48 source: { \n\
48 if (cardData && typeof cardData["background"] === "string") return cardData["background"]; \n\
49 if (cardData && typeof cardData["background"] === "string") return cardData["background"]; \n\
49− else return %3; \n\
50+ else return %4; \n\
50 } \n\
51 } \n\
51 } \n\
52 } \n\
52 function getColor(index) { \n\
53 function getColor(index) { \n\
@@ -63,8 +64,9 @@ var kBackgroundLoaderCode = 'Loader {\n\
63// %3 is used as image height
64// %3 is used as image height
64// %4 is used for artShapeSource.hideSource and inner Loader visible
65// %4 is used for artShapeSource.hideSource and inner Loader visible
65// %5 is used as aspect ratio fallback
66// %5 is used as aspect ratio fallback
67+// %6 is whether the loader should be asynchronous or not
66−// %6 is injected as code to artImage
68+// %7 is injected as code to artImage
67−// %7 is used as image fallback
69+// %8 is used as image fallback
68var kArtShapeHolderCode = 'Item { \n\
70var kArtShapeHolderCode = 'Item { \n\
69 id: artShapeHolder; \n\
71 id: artShapeHolder; \n\
70 height: root.fixedArtShapeSize.height > 0 ? root.fixedArtShapeSize.height : artShapeLoader.height; \n\
72 height: root.fixedArtShapeSize.height > 0 ? root.fixedArtShapeSize.height : artShapeLoader.height; \n\
@@ -73,9 +75,9 @@ var kArtShapeHolderCode = 'Item { \n\
73 Loader { \n\
75 Loader { \n\
74 id: artShapeLoader; \n\
76 id: artShapeLoader; \n\
75 objectName: "artShapeLoader"; \n\
77 objectName: "artShapeLoader"; \n\
76− readonly property string cardArt: cardData && cardData["art"] || %7; \n\
78+ readonly property string cardArt: cardData && cardData["art"] || %8; \n\
77 active: cardArt != ""; \n\
79 active: cardArt != ""; \n\
78− asynchronous: root.asynchronous; \n\
80+ asynchronous: %6; \n\
79 visible: status == Loader.Ready; \n\
81 visible: status == Loader.Ready; \n\
80 sourceComponent: Item { \n\
82 sourceComponent: Item { \n\
81 id: artShape; \n\
83 id: artShape; \n\
@@ -132,10 +134,10 @@ var kArtShapeHolderCode = 'Item { \n\
132 id: artImage; \n\
134 id: artImage; \n\
133 objectName: "artImage"; \n\
135 objectName: "artImage"; \n\
134 source: artShapeLoader.cardArt; \n\
136 source: artShapeLoader.cardArt; \n\
135− asynchronous: root.asynchronous; \n\
137+ asynchronous: %6; \n\
136 width: %2; \n\
138 width: %2; \n\
137 height: %3; \n\
139 height: %3; \n\
138− %6 \n\
140+ %7 \n\
139 } \n\
141 } \n\
140 } \n\
142 } \n\
141 } \n\
143 } \n\
@@ -144,6 +146,7 @@ var kArtShapeHolderCode = 'Item { \n\
144// %1 is anchors.fill
146// %1 is anchors.fill
145// %2 is width
147// %2 is width
146// %3 is height
148// %3 is height
149+// %4 is whether the icon should be asynchronous or not
147var kAudioButtonCode = 'AbstractButton { \n\
150var kAudioButtonCode = 'AbstractButton { \n\
148 id: audioButton; \n\
151 id: audioButton; \n\
149 anchors.fill: %1; \n\
152 anchors.fill: %1; \n\
@@ -169,6 +172,7 @@ var kAudioButtonCode = 'AbstractButton { \n\
169 opacity: 0.9; \n\
172 opacity: 0.9; \n\
170 name: DashAudioPlayer.playing && AudioUrlComparer.compare(parent.source, DashAudioPlayer.currentSource) ? "media-playback-pause" : "media-playback-start"; \n\
173 name: DashAudioPlayer.playing && AudioUrlComparer.compare(parent.source, DashAudioPlayer.currentSource) ? "media-playback-pause" : "media-playback-start"; \n\
171 color: "white"; \n\
174 color: "white"; \n\
175+ asynchronous: %4; \n\
172 } \n\
176 } \n\
173 onClicked: { \n\
177 onClicked: { \n\
174 if (AudioUrlComparer.compare(source, DashAudioPlayer.currentSource)) { \n\
178 if (AudioUrlComparer.compare(source, DashAudioPlayer.currentSource)) { \n\
@@ -187,12 +191,14 @@ var kAudioButtonCode = 'AbstractButton { \n\
187 } \n\
191 } \n\
188 }';
192 }';
189
193
194+// %1 is whether the loader should be asynchronous or not
195+// %2 is the header height code
190var kOverlayLoaderCode = 'Loader { \n\
196var kOverlayLoaderCode = 'Loader { \n\
191 id: overlayLoader; \n\
197 id: overlayLoader; \n\
192− readonly property real overlayHeight: (fixedHeaderHeight > 0 ? fixedHeaderHeight : headerHeight) + units.gu(2); \n\
198+ readonly property real overlayHeight: %2 + units.gu(2); \n\
193 anchors.fill: artShapeHolder; \n\
199 anchors.fill: artShapeHolder; \n\
194 active: artShapeLoader.active && artShapeLoader.item && artShapeLoader.item.image.status === Image.Ready || false; \n\
200 active: artShapeLoader.active && artShapeLoader.item && artShapeLoader.item.image.status === Image.Ready || false; \n\
195− asynchronous: root.asynchronous; \n\
201+ asynchronous: %1; \n\
196 visible: showHeader && status == Loader.Ready; \n\
202 visible: showHeader && status == Loader.Ready; \n\
197 sourceComponent: UbuntuShapeOverlay { \n\
203 sourceComponent: UbuntuShapeOverlay { \n\
198 id: overlay; \n\
204 id: overlay; \n\
@@ -211,17 +217,19 @@ function kHeaderRowCodeGenerator() {
211 objectName: "outerRow"; \n\
217 objectName: "outerRow"; \n\
212 property real margins: units.gu(1); \n\
218 property real margins: units.gu(1); \n\
213 spacing: margins; \n\
219 spacing: margins; \n\
214− height: root.fixedHeaderHeight != -1 ? root.fixedHeaderHeight : implicitHeight; \n\
220+ %2\
215 anchors { %1 } \n\
221 anchors { %1 } \n\
216 anchors.right: parent.right; \n\
222 anchors.right: parent.right; \n\
217 anchors.margins: margins; \n\
223 anchors.margins: margins; \n\
218 anchors.rightMargin: 0; \n\
224 anchors.rightMargin: 0; \n\
219 data: [ \n\
225 data: [ \n\
220− %2 \n\
226+ %3 \n\
221 ] \n\
227 ] \n\
222 }\n';
228 }\n';
223 var args = Array.prototype.slice.call(arguments);
229 var args = Array.prototype.slice.call(arguments);
230+ var isCardTool = args.shift();
231+ var heightCode = isCardTool ? "" : "height: root.fixedHeaderHeight; \n";
224− var code = kHeaderRowCodeTemplate.arg(args.shift()).arg(args.join(',\n'));
232+ var code = kHeaderRowCodeTemplate.arg(args.shift()).arg(heightCode).arg(args.join(',\n'));
225 return code;
233 return code;
226}
234}
227
235
@@ -242,10 +250,11 @@ function kHeaderContainerCodeGenerator() {
242}
250}
243
251
244// %1 is used as anchors of mascotShapeLoader
252// %1 is used as anchors of mascotShapeLoader
253+// %2 is whether the loader should be asynchronous or not
245var kMascotShapeLoaderCode = 'Loader { \n\
254var kMascotShapeLoaderCode = 'Loader { \n\
246 id: mascotShapeLoader; \n\
255 id: mascotShapeLoader; \n\
247 objectName: "mascotShapeLoader"; \n\
256 objectName: "mascotShapeLoader"; \n\
248− asynchronous: root.asynchronous; \n\
257+ asynchronous: %2; \n\
249 active: mascotImage.status === Image.Ready; \n\
258 active: mascotImage.status === Image.Ready; \n\
250 visible: showHeader && active && status == Loader.Ready; \n\
259 visible: showHeader && active && status == Loader.Ready; \n\
251 width: units.gu(6); \n\
260 width: units.gu(6); \n\
@@ -275,6 +284,7 @@ var kMascotImageCode = 'CroppedImageMinimumSourceSize { \n\
275// %2 is used as color of titleLabel
284// %2 is used as color of titleLabel
276// %3 is used as extra condition for visible of titleLabel
285// %3 is used as extra condition for visible of titleLabel
277// %4 is used as title width
286// %4 is used as title width
287+// %5 is used as horizontal alignment
278var kTitleLabelCode = 'Label { \n\
288var kTitleLabelCode = 'Label { \n\
279 id: titleLabel; \n\
289 id: titleLabel; \n\
280 objectName: "titleLabel"; \n\
290 objectName: "titleLabel"; \n\
@@ -289,7 +299,7 @@ var kTitleLabelCode = 'Label { \n\
289 width: %4; \n\
299 width: %4; \n\
290 text: root.title; \n\
300 text: root.title; \n\
291 font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal; \n\
301 font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal; \n\
292− horizontalAlignment: root.titleAlignment; \n\
302+ horizontalAlignment: %5; \n\
293 }\n';
303 }\n';
294
304
295// %1 is used as extra anchors of emblemIcon
305// %1 is used as extra anchors of emblemIcon
@@ -398,7 +408,7 @@ function sanitizeColor(colorString) {
398 return colorString;
408 return colorString;
399}
409}
400
410
401−function cardString(template, components) {
411+function cardString(template, components, isCardTool) {
402 var code;
412 var code;
403
413
404 var templateInteractive = (template == null ? true : (template["non-interactive"] !== undefined ? !template["non-interactive"] : true)) ? "true" : "false";
414 var templateInteractive = (template == null ? true : (template["non-interactive"] !== undefined ? !template["non-interactive"] : true)) ? "true" : "false";
@@ -410,16 +420,16 @@ function cardString(template, components) {
410 property string backgroundShapeStyle: "inset"; \n\
420 property string backgroundShapeStyle: "inset"; \n\
411 property real fontScale: 1.0; \n\
421 property real fontScale: 1.0; \n\
412 property var scopeStyle: null; \n\
422 property var scopeStyle: null; \n\
413− property int titleAlignment: Text.AlignLeft; \n\
423+ %2\
414− property int fixedHeaderHeight: -1; \n\
415 property size fixedArtShapeSize: Qt.size(-1, -1); \n\
424 property size fixedArtShapeSize: Qt.size(-1, -1); \n\
416 readonly property string title: cardData && cardData["title"] || ""; \n\
425 readonly property string title: cardData && cardData["title"] || ""; \n\
417− property bool asynchronous: true; \n\
418 property bool showHeader: true; \n\
426 property bool showHeader: true; \n\
419 implicitWidth: childrenRect.width; \n\
427 implicitWidth: childrenRect.width; \n\
420 enabled: %1; \n\
428 enabled: %1; \n\
421 \n'.arg(templateInteractive);
429 \n'.arg(templateInteractive);
422
430
431+ code = code.arg(isCardTool ? "" : "property int fixedHeaderHeight: -1; \n");
432+
423 var hasArt = components["art"] && components["art"]["field"] || false;
433 var hasArt = components["art"] && components["art"]["field"] || false;
424 var hasSummary = components["summary"] || false;
434 var hasSummary = components["summary"] || false;
425 var isConciergeMode = components["art"] && components["art"]["conciergeMode"] || false;
435 var isConciergeMode = components["art"] && components["art"]["conciergeMode"] || false;
@@ -435,6 +445,7 @@ function cardString(template, components) {
435 var hasHeaderRow = hasMascot && hasTitle;
445 var hasHeaderRow = hasMascot && hasTitle;
436 var hasAttributes = hasTitle && components["attributes"] && components["attributes"]["field"] || false;
446 var hasAttributes = hasTitle && components["attributes"] && components["attributes"]["field"] || false;
437 var isAudio = template["quick-preview-type"] === "audio";
447 var isAudio = template["quick-preview-type"] === "audio";
448+ var asynchronous = isCardTool ? "false" : "true";
438
449
439 if (isAudio) {
450 if (isAudio) {
440 // For now we only support audio cards with [optional] art, title, subtitle
451 // For now we only support audio cards with [optional] art, title, subtitle
@@ -468,7 +479,7 @@ function cardString(template, components) {
468 backgroundElements1 = '"%1"'.arg(element1);
479 backgroundElements1 = '"%1"'.arg(element1);
469 }
480 }
470 }
481 }
471− code += kBackgroundLoaderCode.arg(backgroundElements0).arg(backgroundElements1).arg(templateCardBackground);
482+ code += kBackgroundLoaderCode.arg(backgroundElements0).arg(backgroundElements1).arg(asynchronous).arg(templateCardBackground);
472 }
483 }
473
484
474 if (hasArt) {
485 if (hasArt) {
@@ -497,23 +508,31 @@ function cardString(template, components) {
497 if (isNaN(aspectRatio)) {
508 if (isNaN(aspectRatio)) {
498 aspectRatio = 1;
509 aspectRatio = 1;
499 }
510 }
500− var fallback = components["art"] && components["art"]["fallback"] || "";
511+ var fallback = !isCardTool && components["art"] && components["art"]["fallback"] || "";
501 fallback = encodeURI(fallback);
512 fallback = encodeURI(fallback);
502 var fallbackStatusCode = "";
513 var fallbackStatusCode = "";
503 var fallbackURICode = '""';
514 var fallbackURICode = '""';
504 if (fallback !== "") {
515 if (fallback !== "") {
505 // fallbackStatusCode has %6 in it because we want to substitute it for fallbackURICode
516 // fallbackStatusCode has %6 in it because we want to substitute it for fallbackURICode
506− // which in kArtShapeHolderCode is %7
517+ // which in kArtShapeHolderCode is %8
507− fallbackStatusCode += 'onStatusChanged: if (status === Image.Error) source = %7;';
518+ fallbackStatusCode += 'onStatusChanged: if (status === Image.Error) source = %8;';
508 fallbackURICode = 'decodeURI("%1")'.arg(fallback);
519 fallbackURICode = 'decodeURI("%1")'.arg(fallback);
509 }
520 }
510− code += kArtShapeHolderCode.arg(artAnchors).arg(widthCode).arg(heightCode).arg(isConciergeMode ? "false" : "true").arg(aspectRatio).arg(fallbackStatusCode).arg(fallbackURICode);
521+ code += kArtShapeHolderCode.arg(artAnchors)
522+ .arg(widthCode)
523+ .arg(heightCode)
524+ .arg(isConciergeMode ? "false" : "true")
525+ .arg(aspectRatio)
526+ .arg(asynchronous)
527+ .arg(fallbackStatusCode)
528+ .arg(fallbackURICode);
511 } else {
529 } else {
512 code += 'readonly property size artShapeSize: Qt.size(-1, -1);\n'
530 code += 'readonly property size artShapeSize: Qt.size(-1, -1);\n'
513 }
531 }
514
532
515 if (headerAsOverlay) {
533 if (headerAsOverlay) {
534+ var headerHeightCode = isCardTool ? "headerHeight" : "root.fixedHeaderHeight";
516− code += kOverlayLoaderCode;
535+ code += kOverlayLoaderCode.arg(asynchronous).arg(headerHeightCode);
517 }
536 }
518
537
519 var headerVerticalAnchors;
538 var headerVerticalAnchors;
@@ -595,11 +614,11 @@ function cardString(template, components) {
595 }
614 }
596
615
597 if (useMascotShape) {
616 if (useMascotShape) {
598− mascotShapeCode = kMascotShapeLoaderCode.arg(mascotAnchors);
617+ mascotShapeCode = kMascotShapeLoaderCode.arg(mascotAnchors).arg(asynchronous);
599 }
618 }
600
619
601 var mascotImageVisible = useMascotShape ? 'false' : 'showHeader';
620 var mascotImageVisible = useMascotShape ? 'false' : 'showHeader';
602− var fallback = components["mascot"] && components["mascot"]["fallback"] || "";
621+ var fallback = !isCardTool && components["mascot"] && components["mascot"]["fallback"] || "";
603 fallback = encodeURI(fallback);
622 fallback = encodeURI(fallback);
604 var fallbackStatusCode = "";
623 var fallbackStatusCode = "";
605 var fallbackURICode = '""';
624 var fallbackURICode = '""';
@@ -715,9 +734,24 @@ function cardString(template, components) {
715 }
734 }
716 }
735 }
717
736
737+ var titleAlignment = "Text.AlignHCenter";
738+ if (template["card-layout"] === "horizontal"
739+ || typeof components["title"] !== "object"
740+ || components["title"]["align"] === "left") titleAlignment = "Text.AlignLeft";
741+ var keys = ["mascot", "emblem", "subtitle", "attributes", "summary"];
742+ for (var key in keys) {
743+ key = keys[key];
744+ try {
745+ if (typeof components[key] === "string"
746+ || typeof components[key]["field"] === "string") titleAlignment = "Text.AlignLeft";
747+ } catch (e) {
748+ continue;
749+ }
750+ }
751+
718 // code for different elements
752 // code for different elements
719 var titleLabelVisibleExtra = (headerAsOverlay ? '&& overlayLoader.active': '');
753 var titleLabelVisibleExtra = (headerAsOverlay ? '&& overlayLoader.active': '');
720− var titleCode = kTitleLabelCode.arg(titleAnchors).arg(titleColor).arg(titleLabelVisibleExtra).arg(titleWidth);
754+ var titleCode = kTitleLabelCode.arg(titleAnchors).arg(titleColor).arg(titleLabelVisibleExtra).arg(titleWidth).arg(titleAlignment);
721 var subtitleCode;
755 var subtitleCode;
722 var attributesCode;
756 var attributesCode;
723
757
@@ -759,7 +793,7 @@ function cardString(template, components) {
759 if (mascotShapeCode != '') {
793 if (mascotShapeCode != '') {
760 rowCode.unshift(mascotShapeCode);
794 rowCode.unshift(mascotShapeCode);
761 }
795 }
762− code += kHeaderRowCodeGenerator(headerVerticalAnchors + headerLeftAnchor, rowCode)
796+ code += kHeaderRowCodeGenerator(isCardTool, headerVerticalAnchors + headerLeftAnchor, rowCode)
763 } else {
797 } else {
764 code += mascotShapeCode + mascotCode + titleSubtitleCode;
798 code += mascotShapeCode + mascotCode + titleSubtitleCode;
765 }
799 }
@@ -783,9 +817,10 @@ function cardString(template, components) {
783 } else {
817 } else {
784 audioButtonAnchorsFill = 'undefined';
818 audioButtonAnchorsFill = 'undefined';
785 audioButtonWidth = 'height';
819 audioButtonWidth = 'height';
786− audioButtonHeight = '(root.fixedHeaderHeight > 0 ? root.fixedHeaderHeight : headerHeight) + 2 * units.gu(1)';
820+ audioButtonHeight = isCardTool ? 'headerHeight + 2 * units.gu(1)'
821+ : 'root.fixedHeaderHeight + 2 * units.gu(1)';
787 }
822 }
788− code += kAudioButtonCode.arg(audioButtonAnchorsFill).arg(audioButtonWidth).arg(audioButtonHeight);
823+ code += kAudioButtonCode.arg(audioButtonAnchorsFill).arg(audioButtonWidth).arg(audioButtonHeight).arg(asynchronous);
789 }
824 }
790
825
791 if (hasSummary) {
826 if (hasSummary) {
@@ -854,13 +889,13 @@ function cardString(template, components) {
854 return code;
889 return code;
855}
890}
856
891
857−function createCardComponent(parent, template, components, identifier) {
892+function createCardComponent(parent, template, components, isCardTool, identifier) {
858 var imports = 'import QtQuick 2.4; \n\
893 var imports = 'import QtQuick 2.4; \n\
859 import Ubuntu.Components 1.3; \n\
894 import Ubuntu.Components 1.3; \n\
860 import Ubuntu.Settings.Components 0.1; \n\
895 import Ubuntu.Settings.Components 0.1; \n\
861 import Dash 0.1;\n\
896 import Dash 0.1;\n\
862 import Utils 0.1;\n';
897 import Utils 0.1;\n';
863− var card = cardString(template, components);
898+ var card = cardString(template, components, isCardTool);
864 var code = imports + 'Component {\n' + card + '}\n';
899 var code = imports + 'Component {\n' + card + '}\n';
865
900
866 try {
901 try {
plugins/Dash/CardCreatorCache.qml+3−3
@@ -23,16 +23,16 @@ QtObject {
23
23
24 property var cache: new Object();
24 property var cache: new Object();
25
25
26− function getCardComponent(template, components) {
26+ function getCardComponent(template, components, isCardTool) {
27 if (template === undefined || components === undefined)
27 if (template === undefined || components === undefined)
28 return undefined;
28 return undefined;
29
29
30 var tString = JSON.stringify(template);
30 var tString = JSON.stringify(template);
31 var cString = JSON.stringify(components);
31 var cString = JSON.stringify(components);
32− var allString = tString + cString;
32+ var allString = tString + cString + isCardTool;
33 var component = cache[allString];
33 var component = cache[allString];
34 if (component === undefined) {
34 if (component === undefined) {
35− component = CardCreator.createCardComponent(root, template, components, allString);
35+ component = CardCreator.createCardComponent(root, template, components, isCardTool, allString);
36 cache[allString] = component;
36 cache[allString] = component;
37 }
37 }
38 return component;
38 return component;
plugins/Dash/plugin.cpp+6−6
@@ -28,12 +28,12 @@
28static QUrl oauthCleanedUrl(QUrl u)
28static QUrl oauthCleanedUrl(QUrl u)
29{
29{
30 QUrlQuery q(u);
30 QUrlQuery q(u);
31− q.removeQueryItem("oauth_nonce");
31+ q.removeQueryItem(QStringLiteral("oauth_nonce"));
32− q.removeQueryItem("oauth_timestamp");
32+ q.removeQueryItem(QStringLiteral("oauth_timestamp"));
33− q.removeQueryItem("oauth_consumer_key");
33+ q.removeQueryItem(QStringLiteral("oauth_consumer_key"));
34− q.removeQueryItem("oauth_signature_method");
34+ q.removeQueryItem(QStringLiteral("oauth_signature_method"));
35− q.removeQueryItem("oauth_version");
35+ q.removeQueryItem(QStringLiteral("oauth_version"));
36− q.removeQueryItem("oauth_signature");
36+ q.removeQueryItem(QStringLiteral("oauth_signature"));
37 u.setQuery(q);
37 u.setQuery(q);
38 return u;
38 return u;
39}
39}
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/GlobalShortcut/globalshortcutregistry.cpp+4−4
@@ -35,13 +35,13 @@ GlobalShortcutList GlobalShortcutRegistry::shortcuts() const
35
35
36bool GlobalShortcutRegistry::hasShortcut(const QVariant &seq) const
36bool GlobalShortcutRegistry::hasShortcut(const QVariant &seq) const
37{
37{
38− return m_shortcuts.keys().contains(seq);
38+ return m_shortcuts.contains(seq);
39}
39}
40
40
41void GlobalShortcutRegistry::addShortcut(const QVariant &seq, GlobalShortcut *sc)
41void GlobalShortcutRegistry::addShortcut(const QVariant &seq, GlobalShortcut *sc)
42{
42{
43 if (sc) {
43 if (sc) {
44− if (!m_shortcuts.keys().contains(seq)) { // create a new entry
44+ if (!m_shortcuts.contains(seq)) { // create a new entry
45 m_shortcuts.insert(seq, {sc});
45 m_shortcuts.insert(seq, {sc});
46 } else { // append to an existing one
46 } else { // append to an existing one
47 auto shortcuts = m_shortcuts[seq];
47 auto shortcuts = m_shortcuts[seq];
@@ -75,8 +75,8 @@ bool GlobalShortcutRegistry::eventFilter(QObject *obj, QEvent *event)
75
75
76 if (event->type() == QEvent::KeyPress) {
76 if (event->type() == QEvent::KeyPress) {
77 QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
77 QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
78− QKeySequence seq = QKeySequence(keyEvent->key() + keyEvent->modifiers());
78+ int seq = keyEvent->key() + keyEvent->modifiers();
79− if (m_shortcuts.keys().contains(seq)) {
79+ if (m_shortcuts.contains(seq)) {
80 const auto shortcuts = m_shortcuts.value(seq);
80 const auto shortcuts = m_shortcuts.value(seq);
81 Q_FOREACH(const auto &shortcut, shortcuts) {
81 Q_FOREACH(const auto &shortcut, shortcuts) {
82 if (shortcut) {
82 if (shortcut) {
plugins/IntegratedLightDM/liblightdm/CMakeLists.txt+16−3
@@ -5,13 +5,16 @@ set(LibLightDM_SOURCES
5 UsersModel.cpp
5 UsersModel.cpp
6 GreeterPrivate.cpp
6 GreeterPrivate.cpp
7 UsersModelPrivate.cpp
7 UsersModelPrivate.cpp
8+ ${CMAKE_SOURCE_DIR}/plugins/AccountsService/AccountsServiceDBusAdaptor.cpp
8 ${CMAKE_SOURCE_DIR}/plugins/Utils/qvariantlistmodel.cpp
9 ${CMAKE_SOURCE_DIR}/plugins/Utils/qvariantlistmodel.cpp
9 )
10 )
10
11
11add_library(integratedLightDM STATIC ${LibLightDM_SOURCES})
12add_library(integratedLightDM STATIC ${LibLightDM_SOURCES})
13+add_library(integratedLightDMSession STATIC ${LibLightDM_SOURCES})
12add_library(MockLightDM-demo-shared SHARED ${LibLightDM_SOURCES})
14add_library(MockLightDM-demo-shared SHARED ${LibLightDM_SOURCES})
13
15
14include_directories(
16include_directories(
17+ ${CMAKE_SOURCE_DIR}/plugins/AccountsService
15 ${CMAKE_CURRENT_BINARY_DIR}
18 ${CMAKE_CURRENT_BINARY_DIR}
16 ${GLIB_INCLUDE_DIRS}
19 ${GLIB_INCLUDE_DIRS}
17 ${LIBUSERMETRICSOUTPUT_INCLUDE_DIRS}
20 ${LIBUSERMETRICSOUTPUT_INCLUDE_DIRS}
@@ -22,17 +25,27 @@ target_link_libraries(integratedLightDM
22 ${LIBUSERMETRICSOUTPUT_LDFLAGS}
25 ${LIBUSERMETRICSOUTPUT_LDFLAGS}
23 -lpam
26 -lpam
24)
27)
28+target_link_libraries(integratedLightDMSession
29+ ${GLIB_LIBRARIES}
30+ ${LIBUSERMETRICSOUTPUT_LDFLAGS}
31+ -lpam
32+)
25target_link_libraries(MockLightDM-demo-shared
33target_link_libraries(MockLightDM-demo-shared
26 ${GLIB_LIBRARIES}
34 ${GLIB_LIBRARIES}
27 ${LIBUSERMETRICSOUTPUT_LDFLAGS}
35 ${LIBUSERMETRICSOUTPUT_LDFLAGS}
28 -lpam
36 -lpam
29)
37)
30
38
31−qt5_use_modules(integratedLightDM Concurrent Gui)
39+qt5_use_modules(integratedLightDM Concurrent DBus Gui)
40+qt5_use_modules(integratedLightDMSession Concurrent DBus Gui)
32−qt5_use_modules(MockLightDM-demo-shared Concurrent Gui)
41+qt5_use_modules(MockLightDM-demo-shared Concurrent DBus Gui)
33
42
34−set_target_properties(integratedLightDM PROPERTIES COMPILE_FLAGS -fPIC)
43+set_target_properties(integratedLightDM PROPERTIES
44+ COMPILE_FLAGS "-DSM_BUSNAME=systemBus -fPIC")
45+set_target_properties(integratedLightDMSession PROPERTIES
46+ COMPILE_FLAGS "-DSM_BUSNAME=sessionBus -fPIC")
35set_target_properties(MockLightDM-demo-shared PROPERTIES
47set_target_properties(MockLightDM-demo-shared PROPERTIES
48+ COMPILE_FLAGS "-DSM_BUSNAME=systemBus"
36 OUTPUT_NAME lightdm-qt5-2)
49 OUTPUT_NAME lightdm-qt5-2)
37
50
38install(TARGETS MockLightDM-demo-shared
51install(TARGETS MockLightDM-demo-shared
plugins/IntegratedLightDM/liblightdm/UsersModel.cpp+4−18
@@ -54,24 +54,10 @@ UsersModel::UsersModel(QObject *parent) :
54 roles[ImagePathRole] = "imagePath";
54 roles[ImagePathRole] = "imagePath";
55 setRoleNames(roles);
55 setRoleNames(roles);
56
56
57− // Now modify our mock user backgrounds
57+ connect(d_ptr, &UsersModelPrivate::dataChanged, this, [this](int i) {
58− QDir bgdir = QDir(QStringLiteral("/usr/share/demo-assets/shell/backgrounds/"));
58+ QModelIndex index = createIndex(i, 0);
59− QStringList backgrounds = bgdir.entryList(QDir::Files | QDir::NoDotAndDotDot);
59+ Q_EMIT dataChanged(index, index);
60−
60+ });
61− for (int i = 0, j = 0; i < d->entries.size(); i++) {
62− Entry &entry = d->entries[i];
63− if (entry.background.isNull() && !backgrounds.isEmpty()) {
64− entry.background = bgdir.filePath(backgrounds[j++]);
65− if (j >= backgrounds.length()) {
66− j = 0;
67− }
68− }
69− }
70−}
71−
72−UsersModel::~UsersModel()
73−{
74− delete d_ptr;
75}
61}
76
62
77int UsersModel::rowCount(const QModelIndex &parent) const
63int UsersModel::rowCount(const QModelIndex &parent) const
plugins/IntegratedLightDM/liblightdm/UsersModel.h+1−1
@@ -41,7 +41,7 @@ class Q_DECL_EXPORT UsersModel : public QAbstractListModel
41
41
42public:
42public:
43 explicit UsersModel(QObject *parent = 0);
43 explicit UsersModel(QObject *parent = 0);
44− virtual ~UsersModel();
44+ virtual ~UsersModel() = default;
45
45
46 enum UserModelRoles {NameRole = Qt::UserRole,
46 enum UserModelRoles {NameRole = Qt::UserRole,
47 RealNameRole,
47 RealNameRole,
plugins/IntegratedLightDM/liblightdm/UsersModelPrivate.cpp+43−9
@@ -18,7 +18,11 @@
18
18
19#include "UsersModelPrivate.h"
19#include "UsersModelPrivate.h"
20
20
21+#include "AccountsServiceDBusAdaptor.h"
22+#include "UsersModel.h"
23+
21#include <glib.h>
24#include <glib.h>
25+#include <QDebug>
22#include <QDir>
26#include <QDir>
23#include <QSettings>
27#include <QSettings>
24#include <QStringList>
28#include <QStringList>
@@ -27,7 +31,9 @@ namespace QLightDM
27{
31{
28
32
29UsersModelPrivate::UsersModelPrivate(UsersModel* parent)
33UsersModelPrivate::UsersModelPrivate(UsersModel* parent)
34+ : QObject(parent),
30− : q_ptr(parent)
35+ q_ptr(parent),
36+ m_service(new AccountsServiceDBusAdaptor(this))
31{
37{
32 QFileInfo demoFile(QDir::homePath() + "/.unity8-greeter-demo");
38 QFileInfo demoFile(QDir::homePath() + "/.unity8-greeter-demo");
33 QString currentUser = g_get_user_name();
39 QString currentUser = g_get_user_name();
@@ -43,15 +49,43 @@ UsersModelPrivate::UsersModelPrivate(UsersModel* parent)
43 entries.append({user, name, 0, 0, false, false, 0, 0});
49 entries.append({user, name, 0, 0, false, false, 0, 0});
44 }
50 }
45 } else {
51 } else {
46− // If we were using the actual liblightdm, we could just ask it
52+ entries.append({currentUser, 0, 0, 0, false, false, 0, 0});
47− // for the user's real name. But we aren't. We *should* ask
53+
48− // AccountsService for the real name, like liblightdm does internally,
54+ connect(m_service, &AccountsServiceDBusAdaptor::maybeChanged,
49− // but this is close enough since AS and passwd are always in sync.
55+ this, [this](const QString &user) {
50− QString realName = QString::fromUtf8(g_get_real_name()); // gets name from passwd entry
56+ if (user == entries[0].username) {
51− if (realName == QStringLiteral("Unknown")) { // glib doesn't translate this string
57+ updateName(true);
52− realName.clear();
58+ }
59+ });
60+ updateName(false);
61+ }
62+}
63+
64+void UsersModelPrivate::updateName(bool async)
65+{
66+ auto pendingReply = m_service->getUserPropertyAsync(entries[0].username,
67+ QStringLiteral("org.freedesktop.Accounts.User"),
68+ QStringLiteral("RealName"));
69+ auto *watcher = new QDBusPendingCallWatcher(pendingReply, this);
70+
71+ connect(watcher, &QDBusPendingCallWatcher::finished,
72+ this, [this](QDBusPendingCallWatcher* watcher) {
73+
74+ QDBusPendingReply<QVariant> reply = *watcher;
75+ watcher->deleteLater();
76+ if (reply.isError()) {
77+ qWarning() << "Failed to get 'RealName' property - " << reply.error().message();
78+ return;
79+ }
80+
81+ const QString realName = reply.value().toString();
82+ if (entries[0].real_name != realName) {
83+ entries[0].real_name = realName;
84+ Q_EMIT dataChanged(0);
53 }
85 }
54− entries.append({currentUser, realName, 0, 0, false, false, 0, 0});
86+ });
87+ if (!async) {
88+ watcher->waitForFinished();
55 }
89 }
56}
90}
57
91
plugins/IntegratedLightDM/liblightdm/UsersModelPrivate.h+15−3
@@ -19,8 +19,11 @@
19#ifndef UNITY_MOCK_USERSMODEL_PRIVATE_H
19#ifndef UNITY_MOCK_USERSMODEL_PRIVATE_H
20#define UNITY_MOCK_USERSMODEL_PRIVATE_H
20#define UNITY_MOCK_USERSMODEL_PRIVATE_H
21
21
22−#include <QtCore/QList>
22+#include <QList>
23−#include <QtCore/QString>
23+#include <QObject>
24+#include <QString>
25+
26+class AccountsServiceDBusAdaptor;
24
27
25namespace QLightDM
28namespace QLightDM
26{
29{
@@ -39,19 +42,28 @@ public:
39 QString infographic;
42 QString infographic;
40};
43};
41
44
42−class UsersModelPrivate
45+class UsersModelPrivate : public QObject
43{
46{
47+ Q_OBJECT
48+
44public:
49public:
45 explicit UsersModelPrivate(UsersModel *parent = 0);
50 explicit UsersModelPrivate(UsersModel *parent = 0);
46 virtual ~UsersModelPrivate() = default;
51 virtual ~UsersModelPrivate() = default;
47
52
48 QList<Entry> entries;
53 QList<Entry> entries;
49
54
55+Q_SIGNALS:
56+ void dataChanged(int);
57+
50protected:
58protected:
51 UsersModel * const q_ptr;
59 UsersModel * const q_ptr;
52
60
53private:
61private:
54 Q_DECLARE_PUBLIC(UsersModel)
62 Q_DECLARE_PUBLIC(UsersModel)
63+
64+ void updateName(bool async);
65+
66+ AccountsServiceDBusAdaptor *m_service;
55};
67};
56
68
57}
69}
plugins/Ubuntu/Gestures/CMakeLists.txt+1
@@ -11,6 +11,7 @@ set(UbuntuGesturesQml_SOURCES
11 PressedOutsideNotifier.cpp
11 PressedOutsideNotifier.cpp
12 TouchDispatcher.cpp
12 TouchDispatcher.cpp
13 TouchGate.cpp
13 TouchGate.cpp
14+ TouchGestureArea.cpp
14)
15)
15
16
16add_definitions(-DUBUNTUGESTURESQML_LIBRARY)
17add_definitions(-DUBUNTUGESTURESQML_LIBRARY)
plugins/Ubuntu/Gestures/DirectionalDragArea.cpp+3
@@ -640,6 +640,9 @@ void DirectionalDragAreaPrivate::setStatus(Status newStatus)
640 Q_EMIT q->pressedChanged(true);
640 Q_EMIT q->pressedChanged(true);
641 break;
641 break;
642 case Recognized:
642 case Recognized:
643+ if (oldStatus == WaitingForTouch) { // for immediate recognition
644+ Q_EMIT q->pressedChanged(true);
645+ }
643 Q_EMIT q->draggingChanged(true);
646 Q_EMIT q->draggingChanged(true);
644 break;
647 break;
645 default:
648 default:
plugins/Ubuntu/Gestures/Gestures.qmltypes+86−45
@@ -34,7 +34,8 @@ Module {
34 "Leftwards": 1,
34 "Leftwards": 1,
35 "Downwards": 2,
35 "Downwards": 2,
36 "Upwards": 3,
36 "Upwards": 3,
37− "Horizontal": 4
37+ "Horizontal": 4,
38+ "Vertical": 5
38 }
39 }
39 }
40 }
40 Method {
41 Method {
@@ -59,14 +60,6 @@ Module {
59 prototype: "QQuickItem"
60 prototype: "QQuickItem"
60 exports: ["Ubuntu.Gestures/DirectionalDragArea 0.1"]
61 exports: ["Ubuntu.Gestures/DirectionalDragArea 0.1"]
61 exportMetaObjectRevisions: [0]
62 exportMetaObjectRevisions: [0]
62− Enum {
63− name: "Status"
64− values: {
65− "WaitingForTouch": 0,
66− "Undecided": 1,
67− "Recognized": 2
68− }
69− }
70 Property { name: "direction"; type: "Direction::Type" }
63 Property { name: "direction"; type: "Direction::Type" }
71 Property { name: "distance"; type: "double"; isReadonly: true }
64 Property { name: "distance"; type: "double"; isReadonly: true }
72 Property { name: "sceneDistance"; type: "double"; isReadonly: true }
65 Property { name: "sceneDistance"; type: "double"; isReadonly: true }
@@ -74,24 +67,19 @@ Module {
74 Property { name: "touchY"; type: "double"; isReadonly: true }
67 Property { name: "touchY"; type: "double"; isReadonly: true }
75 Property { name: "touchSceneX"; type: "double"; isReadonly: true }
68 Property { name: "touchSceneX"; type: "double"; isReadonly: true }
76 Property { name: "touchSceneY"; type: "double"; isReadonly: true }
69 Property { name: "touchSceneY"; type: "double"; isReadonly: true }
77− Property { name: "status"; type: "Status"; isReadonly: true }
78 Property { name: "dragging"; type: "bool"; isReadonly: true }
70 Property { name: "dragging"; type: "bool"; isReadonly: true }
79− Property { name: "maxDeviation"; type: "double" }
71+ Property { name: "pressed"; type: "bool"; isReadonly: true }
80− Property { name: "wideningAngle"; type: "double" }
81− Property { name: "distanceThreshold"; type: "double" }
72+ Property { name: "immediateRecognition"; type: "bool" }
82− Property { name: "minSpeed"; type: "double" }
83− Property { name: "maxSilenceTime"; type: "int" }
84− Property { name: "compositionTime"; type: "int" }
85 Signal {
73 Signal {
86 name: "directionChanged"
74 name: "directionChanged"
87 Parameter { name: "direction"; type: "Direction::Type" }
75 Parameter { name: "direction"; type: "Direction::Type" }
88 }
76 }
89 Signal {
77 Signal {
90− name: "statusChanged"
78+ name: "draggingChanged"
91− Parameter { name: "value"; type: "Status" }
79+ Parameter { name: "value"; type: "bool" }
92 }
80 }
93 Signal {
81 Signal {
94− name: "draggingChanged"
82+ name: "pressedChanged"
95 Parameter { name: "value"; type: "bool" }
83 Parameter { name: "value"; type: "bool" }
96 }
84 }
97 Signal {
85 Signal {
@@ -102,30 +90,6 @@ Module {
102 name: "sceneDistanceChanged"
90 name: "sceneDistanceChanged"
103 Parameter { name: "value"; type: "double" }
91 Parameter { name: "value"; type: "double" }
104 }
92 }
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− }
129 Signal {
93 Signal {
130 name: "touchXChanged"
94 name: "touchXChanged"
131 Parameter { name: "value"; type: "double" }
95 Parameter { name: "value"; type: "double" }
@@ -142,7 +106,35 @@ Module {
142 name: "touchSceneYChanged"
106 name: "touchSceneYChanged"
143 Parameter { name: "value"; type: "double" }
107 Parameter { name: "value"; type: "double" }
144 }
108 }
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 }
146 }
138 }
147 Component {
139 Component {
148 name: "PressedOutsideNotifier"
140 name: "PressedOutsideNotifier"
@@ -163,6 +155,55 @@ Module {
163 name: "targetItemChanged"
155 name: "targetItemChanged"
164 Parameter { name: "item"; type: "QQuickItem"; isPointer: true }
156 Parameter { name: "item"; type: "QQuickItem"; isPointer: true }
165 }
157 }
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 {
166− Signal { name: "pressed" }
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" }
167 }
208 }
168}
209}
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 @@
21#include "FloatingFlickable.h"
21#include "FloatingFlickable.h"
22#include "PressedOutsideNotifier.h"
22#include "PressedOutsideNotifier.h"
23#include "TouchGate.h"
23#include "TouchGate.h"
24+#include "TouchGestureArea.h"
24
25
25#include <qqml.h>
26#include <qqml.h>
26
27
@@ -38,4 +39,6 @@ void UbuntuGesturesQmlPlugin::registerTypes(const char *uri)
38 qmlRegisterType<FloatingFlickable>(uri, 0, 1, "FloatingFlickable");
39 qmlRegisterType<FloatingFlickable>(uri, 0, 1, "FloatingFlickable");
39 qmlRegisterType<PressedOutsideNotifier>(uri, 0, 1, "PressedOutsideNotifier");
40 qmlRegisterType<PressedOutsideNotifier>(uri, 0, 1, "PressedOutsideNotifier");
40 qmlRegisterType<TouchGate>(uri, 0, 1, "TouchGate");
41 qmlRegisterType<TouchGate>(uri, 0, 1, "TouchGate");
42+ qmlRegisterType<TouchGestureArea>(uri, 0, 1, "TouchGestureArea");
43+ qmlRegisterUncreatableType<GestureTouchPoint>(uri, 0, 1, "GestureTouchPoint", "Cannot create GestureTouchPoints");
41}
44}
plugins/Unity/Indicators/indicatorsmanager.cpp+2−2
@@ -116,7 +116,7 @@ void IndicatorsManager::loadFile(const QFileInfo& file_info)
116 QSettings indicator_settings(file_info.absoluteFilePath(), QSettings::IniFormat, this);
116 QSettings indicator_settings(file_info.absoluteFilePath(), QSettings::IniFormat, this);
117 const QString name = indicator_settings.value(QStringLiteral("Indicator Service/Name")).toString();
117 const QString name = indicator_settings.value(QStringLiteral("Indicator Service/Name")).toString();
118
118
119− if (m_platform.isPC() && name == "indicator-keyboard") {
119+ if (m_platform.isPC() && name == QLatin1String("indicator-keyboard")) {
120 return; // convergence: skip this indicator until it works in Mir
120 return; // convergence: skip this indicator until it works in Mir
121 }
121 }
122
122
@@ -293,7 +293,7 @@ Indicator::Ptr IndicatorsManager::indicator(const QString& indicator_name)
293 // The rest of the indicators respect their default profile (which is "phone", even on desktop PCs)
293 // The rest of the indicators respect their default profile (which is "phone", even on desktop PCs)
294 if ((new_indicator->identifier() == QStringLiteral("indicator-session") && m_platform.isMultiSession())
294 if ((new_indicator->identifier() == QStringLiteral("indicator-session") && m_platform.isMultiSession())
295 || (new_indicator->identifier() == QStringLiteral("indicator-power") && m_platform.isPC())) {
295 || (new_indicator->identifier() == QStringLiteral("indicator-power") && m_platform.isPC())) {
296− new_indicator->setProfile("desktop");
296+ new_indicator->setProfile(QStringLiteral("desktop"));
297 } else {
297 } else {
298 new_indicator->setProfile(m_profile);
298 new_indicator->setProfile(m_profile);
299 }
299 }
plugins/Unity/Indicators/rootstateparser.h+1
@@ -23,6 +23,7 @@
23
23
24class UNITYINDICATORS_EXPORT RootStateParser : public ActionStateParser
24class UNITYINDICATORS_EXPORT RootStateParser : public ActionStateParser
25{
25{
26+Q_OBJECT
26public:
27public:
27 RootStateParser(QObject* parent = nullptr);
28 RootStateParser(QObject* parent = nullptr);
28 virtual QVariant toQVariant(GVariant* state) const override;
29 virtual QVariant toQVariant(GVariant* state) const override;
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/Launcher/desktopfilehandler.cpp+2−2
@@ -146,9 +146,9 @@ QString DesktopFileHandler::icon() const
146 QString iconString = settings.value(QStringLiteral("Icon")).toString();
146 QString iconString = settings.value(QStringLiteral("Icon")).toString();
147 QString pathString = settings.value(QStringLiteral("Path")).toString();
147 QString pathString = settings.value(QStringLiteral("Path")).toString();
148
148
149− if (QFileInfo(iconString).exists()) {
149+ if (QFileInfo::exists(iconString)) {
150 return QFileInfo(iconString).absoluteFilePath();
150 return QFileInfo(iconString).absoluteFilePath();
151− } else if (QFileInfo(pathString + '/' + iconString).exists()) {
151+ } else if (QFileInfo::exists(pathString + '/' + iconString)) {
152 return pathString + '/' + iconString;
152 return pathString + '/' + iconString;
153 }
153 }
154 return "image://theme/" + iconString;
154 return "image://theme/" + iconString;
plugins/Unity/Launcher/launcheritem.cpp+1
@@ -37,6 +37,7 @@ LauncherItem::LauncherItem(const QString &appId, const QString &name, const QStr
37 m_alerting(false),
37 m_alerting(false),
38 m_quickList(new QuickListModel(this))
38 m_quickList(new QuickListModel(this))
39{
39{
40+ Q_ASSERT(parent != nullptr);
40 QuickListEntry nameAction;
41 QuickListEntry nameAction;
41 nameAction.setActionId(QStringLiteral("launch_item"));
42 nameAction.setActionId(QStringLiteral("launch_item"));
42 nameAction.setText(m_name);
43 nameAction.setText(m_name);
plugins/Unity/Launcher/launcheritem.h+1−1
@@ -32,7 +32,7 @@ class LauncherItem: public LauncherItemInterface
32{
32{
33 Q_OBJECT
33 Q_OBJECT
34public:
34public:
35− LauncherItem(const QString &appId, const QString &name, const QString &icon, QObject *parent = 0);
35+ LauncherItem(const QString &appId, const QString &name, const QString &icon, QObject *parent);
36
36
37 QString appId() const override;
37 QString appId() const override;
38 QString name() const override;
38 QString name() const override;
plugins/Unity/Launcher/launchermodel.cpp+15−5
@@ -103,7 +103,7 @@ void LauncherModel::setAlerting(const QString &appId, bool alerting) {
103 LauncherItem *item = m_list.at(index);
103 LauncherItem *item = m_list.at(index);
104 if (!item->focused()) {
104 if (!item->focused()) {
105 item->setAlerting(alerting);
105 item->setAlerting(alerting);
106− Q_EMIT dataChanged(modelIndex, modelIndex, QVector<int>() << RoleAlerting);
106+ Q_EMIT dataChanged(modelIndex, modelIndex, {RoleAlerting});
107 }
107 }
108 }
108 }
109}
109}
@@ -389,7 +389,8 @@ void LauncherModel::countVisibleChanged(const QString &appId, bool countVisible)
389 if (countVisible && desktopFile.isValid()) {
389 if (countVisible && desktopFile.isValid()) {
390 LauncherItem *item = new LauncherItem(appId,
390 LauncherItem *item = new LauncherItem(appId,
391 desktopFile.displayName(),
391 desktopFile.displayName(),
392− desktopFile.icon());
392+ desktopFile.icon(),
393+ this);
393 item->setCountVisible(true);
394 item->setCountVisible(true);
394 beginInsertRows(QModelIndex(), m_list.count(), m_list.count());
395 beginInsertRows(QModelIndex(), m_list.count(), m_list.count());
395 m_list.append(item);
396 m_list.append(item);
@@ -414,10 +415,19 @@ void LauncherModel::refresh()
414 } else {
415 } else {
415 int idx = m_list.indexOf(item);
416 int idx = m_list.indexOf(item);
416 item->setName(desktopFile.displayName());
417 item->setName(desktopFile.displayName());
417− item->setIcon(desktopFile.icon());
418 item->setPinned(item->pinned()); // update pinned text if needed
418 item->setPinned(item->pinned()); // update pinned text if needed
419 item->setRunning(item->running());
419 item->setRunning(item->running());
420− Q_EMIT dataChanged(index(idx), index(idx), {RoleName, RoleIcon, RoleRunning});
420+ Q_EMIT dataChanged(index(idx), index(idx), {RoleName, RoleRunning});
421+
422+ const QString oldIcon = item->icon();
423+ if (oldIcon == desktopFile.icon()) { // same icon file, perhaps different contents, simulate changing the icon name to force reload
424+ item->setIcon(QString());
425+ Q_EMIT dataChanged(index(idx), index(idx), {RoleIcon});
426+ }
427+
428+ // now set the icon for real
429+ item->setIcon(desktopFile.icon());
430+ Q_EMIT dataChanged(index(idx), index(idx), {RoleIcon});
421 }
431 }
422 }
432 }
423
433
@@ -489,7 +499,7 @@ void LauncherModel::alert(const QString &appId)
489 if (idx >= 0) {
499 if (idx >= 0) {
490 LauncherItem *item = m_list.at(idx);
500 LauncherItem *item = m_list.at(idx);
491 setAlerting(item->appId(), true);
501 setAlerting(item->appId(), true);
492− Q_EMIT dataChanged(index(idx), index(idx), QVector<int>() << RoleAlerting);
502+ Q_EMIT dataChanged(index(idx), index(idx), {RoleAlerting});
493 }
503 }
494}
504}
495
505
plugins/Unity/Launcher/launchermodel.h+1−1
@@ -38,7 +38,7 @@ class LauncherModel: public LauncherModelInterface
38 Q_OBJECT
38 Q_OBJECT
39
39
40public:
40public:
41− LauncherModel(QObject *parent = 0);
41+ LauncherModel(QObject *parent = nullptr);
42 ~LauncherModel();
42 ~LauncherModel();
43
43
44 int rowCount(const QModelIndex &parent = QModelIndex()) const override;
44 int rowCount(const QModelIndex &parent = QModelIndex()) const override;
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+2
@@ -23,6 +23,8 @@ set(QMLPLUGIN_SRC
23 windowstatestorage.cpp
23 windowstatestorage.cpp
24 timezoneFormatter.cpp
24 timezoneFormatter.cpp
25 inputeventgenerator.cpp
25 inputeventgenerator.cpp
26+ deviceconfigparser.cpp
27+ globalfunctions.cpp
26 plugin.cpp
28 plugin.cpp
27 )
29 )
28
30
plugins/Utils/deviceconfigparser.cppnew file+150
@@ -0,0 +1,150 @@
1+/*
2+ * Copyright 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 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 "deviceconfigparser.h"
18+
19+#include <QSettings>
20+#include <QFileInfo>
21+#include <QDebug>
22+#include <QStandardPaths>
23+
24+DeviceConfigParser::DeviceConfigParser(QObject *parent): QObject(parent)
25+{
26+ QString path;
27+ Q_FOREACH (const QString &standardPath, QStandardPaths::standardLocations(QStandardPaths::GenericConfigLocation)) {
28+ if (QFileInfo(standardPath + "/devices.conf").exists()) {
29+ path = standardPath + "/devices.conf";
30+ break;
31+ }
32+ }
33+
34+ if (path.isEmpty()) {
35+ path = "/etc/ubuntu/devices.conf";
36+ }
37+ qDebug() << "Using" << path << "as device configuration file";
38+ m_config = new QSettings(path, QSettings::IniFormat, this);
39+}
40+
41+QString DeviceConfigParser::name() const
42+{
43+ return m_name;
44+}
45+
46+void DeviceConfigParser::setName(const QString &name)
47+{
48+ if (m_name == name) {
49+ return;
50+ }
51+ m_name = name;
52+ Q_EMIT changed();
53+}
54+
55+Qt::ScreenOrientation DeviceConfigParser::primaryOrientation() const
56+{
57+ return stringToOrientation(readOrientationFromConfig("PrimaryOrientation"), Qt::PrimaryOrientation);
58+}
59+
60+Qt::ScreenOrientations DeviceConfigParser::supportedOrientations() const
61+{
62+ QStringList values = readOrientationsFromConfig("SupportedOrientations");
63+ if (values.isEmpty()) {
64+ return Qt::PortraitOrientation
65+ | Qt::InvertedPortraitOrientation
66+ | Qt::LandscapeOrientation
67+ | Qt::InvertedLandscapeOrientation;
68+ }
69+
70+ Qt::ScreenOrientations ret = Qt::PrimaryOrientation;
71+ Q_FOREACH(const QString &orientationString, values) {
72+ ret |= stringToOrientation(orientationString, Qt::PrimaryOrientation);
73+ }
74+ return ret;
75+}
76+
77+Qt::ScreenOrientation DeviceConfigParser::landscapeOrientation() const
78+{
79+ return stringToOrientation(readOrientationFromConfig("LandscapeOrientation"), Qt::LandscapeOrientation);
80+}
81+
82+Qt::ScreenOrientation DeviceConfigParser::invertedLandscapeOrientation() const
83+{
84+ return stringToOrientation(readOrientationFromConfig("InvertedLandscapeOrientation"), Qt::InvertedLandscapeOrientation);
85+}
86+
87+Qt::ScreenOrientation DeviceConfigParser::portraitOrientation() const
88+{
89+ return stringToOrientation(readOrientationFromConfig("PortraitOrientation"), Qt::PortraitOrientation);
90+}
91+
92+Qt::ScreenOrientation DeviceConfigParser::invertedPortraitOrientation() const
93+{
94+ return stringToOrientation(readOrientationFromConfig("InvertedPortraitOrientation"), Qt::InvertedPortraitOrientation);
95+}
96+
97+QString DeviceConfigParser::category() const
98+{
99+ QStringList supportedValues = {"phone", "tablet", "desktop"};
100+ m_config->beginGroup(m_name);
101+ QString value = m_config->value("Category", "phone").toString();
102+ if (!supportedValues.contains(value)) {
103+ qWarning().nospace().noquote() << "Unknown option \"" << value << "\" in " << m_config->fileName()
104+ << ". Supported options are: " << supportedValues.join(", ") << ".";
105+ return "phone";
106+ }
107+ m_config->endGroup();
108+ return value;
109+}
110+
111+QStringList DeviceConfigParser::readOrientationsFromConfig(const QString &key) const
112+{
113+ m_config->beginGroup(m_name);
114+
115+ QStringList ret;
116+ if (m_config->contains(key)) {
117+ ret = m_config->value(key).toStringList();
118+ }
119+
120+ m_config->endGroup();
121+ return ret;
122+}
123+
124+QString DeviceConfigParser::readOrientationFromConfig(const QString &key) const
125+{
126+ QStringList ret = readOrientationsFromConfig(key);
127+ return ret.count() > 0 ? ret.first() : QString();
128+}
129+
130+Qt::ScreenOrientation DeviceConfigParser::stringToOrientation(const QString &orientationString, Qt::ScreenOrientation defaultValue) const
131+{
132+ if (orientationString == "Landscape") {
133+ return Qt::LandscapeOrientation;
134+ }
135+ if (orientationString == "InvertedLandscape") {
136+ return Qt::InvertedLandscapeOrientation;
137+ }
138+ if (orientationString == "Portrait") {
139+ return Qt::PortraitOrientation;
140+ }
141+ if (orientationString == "InvertedPortrait") {
142+ return Qt::InvertedPortraitOrientation;
143+ }
144+ if (!orientationString.isEmpty()) {
145+ // Some option we don't know. Give some hint on what went wrong.
146+ qWarning().nospace().noquote() << "Unknown option \"" << orientationString << "\" in " << m_config->fileName()
147+ << ". Supported options are: Landscape, InvertedLandscape, Portrait and InvertedPortrait.";
148+ }
149+ return defaultValue;
150+}
plugins/Utils/deviceconfigparser.hnew file+62
@@ -0,0 +1,62 @@
1+/*
2+ * Copyright 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 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 DEVICECONFIGPARSER_H
18+#define DEVICECONFIGPARSER_H
19+
20+#include <QObject>
21+#include <QSettings>
22+
23+class DeviceConfigParser: public QObject
24+{
25+ Q_OBJECT
26+ Q_PROPERTY(QString name READ name WRITE setName NOTIFY changed)
27+
28+ Q_PROPERTY(Qt::ScreenOrientation primaryOrientation READ primaryOrientation NOTIFY changed)
29+ Q_PROPERTY(Qt::ScreenOrientations supportedOrientations READ supportedOrientations NOTIFY changed)
30+ Q_PROPERTY(Qt::ScreenOrientation landscapeOrientation READ landscapeOrientation NOTIFY changed)
31+ Q_PROPERTY(Qt::ScreenOrientation invertedLandscapeOrientation READ invertedLandscapeOrientation NOTIFY changed)
32+ Q_PROPERTY(Qt::ScreenOrientation portraitOrientation READ portraitOrientation NOTIFY changed)
33+ Q_PROPERTY(Qt::ScreenOrientation invertedPortraitOrientation READ invertedPortraitOrientation NOTIFY changed)
34+ Q_PROPERTY(QString category READ category NOTIFY changed)
35+
36+public:
37+ DeviceConfigParser(QObject *parent = nullptr);
38+
39+ QString name() const;
40+ void setName(const QString &name);
41+
42+ Qt::ScreenOrientation primaryOrientation() const;
43+ Qt::ScreenOrientations supportedOrientations() const;
44+ Qt::ScreenOrientation landscapeOrientation() const;
45+ Qt::ScreenOrientation invertedLandscapeOrientation() const;
46+ Qt::ScreenOrientation portraitOrientation() const;
47+ Qt::ScreenOrientation invertedPortraitOrientation() const;
48+ QString category() const;
49+
50+Q_SIGNALS:
51+ void changed();
52+
53+private:
54+ QString m_name;
55+ QSettings *m_config;
56+
57+ QStringList readOrientationsFromConfig(const QString &key) const;
58+ QString readOrientationFromConfig(const QString &key) const;
59+ Qt::ScreenOrientation stringToOrientation(const QString &orientationString, Qt::ScreenOrientation defaultValue) const;
60+};
61+
62+#endif
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+11
@@ -37,6 +37,8 @@
37#include "timezoneFormatter.h"
37#include "timezoneFormatter.h"
38#include "applicationsfiltermodel.h"
38#include "applicationsfiltermodel.h"
39#include "inputeventgenerator.h"
39#include "inputeventgenerator.h"
40+#include "deviceconfigparser.h"
41+#include "globalfunctions.h"
40
42
41static QObject *createWindowStateStorage(QQmlEngine *engine, QJSEngine *scriptEngine)
43static QObject *createWindowStateStorage(QQmlEngine *engine, QJSEngine *scriptEngine)
42{
44{
@@ -52,6 +54,13 @@ static QObject *createConstants(QQmlEngine *engine, QJSEngine *scriptEngine)
52 return new Constants();
54 return new Constants();
53}
55}
54
56
57+static QObject *createGlobalFunctions(QQmlEngine *engine, QJSEngine *scriptEngine)
58+{
59+ Q_UNUSED(engine)
60+ Q_UNUSED(scriptEngine)
61+ return new GlobalFunctions();
62+}
63+
55void UtilsPlugin::registerTypes(const char *uri)
64void UtilsPlugin::registerTypes(const char *uri)
56{
65{
57 Q_ASSERT(uri == QLatin1String("Utils"));
66 Q_ASSERT(uri == QLatin1String("Utils"));
@@ -70,6 +79,8 @@ void UtilsPlugin::registerTypes(const char *uri)
70 qmlRegisterType<ActiveFocusLogger>(uri, 0, 1, "ActiveFocusLogger");
79 qmlRegisterType<ActiveFocusLogger>(uri, 0, 1, "ActiveFocusLogger");
71 qmlRegisterType<ApplicationsFilterModel>(uri, 0, 1, "ApplicationsFilterModel");
80 qmlRegisterType<ApplicationsFilterModel>(uri, 0, 1, "ApplicationsFilterModel");
72 qmlRegisterType<InputEventGenerator>(uri, 0, 1, "InputEventGenerator");
81 qmlRegisterType<InputEventGenerator>(uri, 0, 1, "InputEventGenerator");
82+ qmlRegisterType<DeviceConfigParser>(uri, 0, 1, "DeviceConfigParser");
83+ qmlRegisterSingletonType<GlobalFunctions>(uri, 0, 1, "Functions", createGlobalFunctions);
73}
84}
74
85
75void UtilsPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
86void UtilsPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
plugins/Utils/timezoneFormatter.cpp+10
@@ -33,3 +33,13 @@ QString TimezoneFormatter::currentTimeInTimezone(const QVariant &tzId) const
33 }
33 }
34 return QString();
34 return QString();
35}
35}
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:
27 ~TimezoneFormatter() = default;
27 ~TimezoneFormatter() = default;
28
28
29 Q_INVOKABLE QString currentTimeInTimezone(const QVariant &tzId) const;
29 Q_INVOKABLE QString currentTimeInTimezone(const QVariant &tzId) const;
30+ Q_INVOKABLE QString currentTimeInTimezoneWithAbbrev(const QVariant &tzId) const;
30};
31};
31
32
32#endif
33#endif
plugins/Utils/windowstatestorage.cpp+36−4
@@ -23,9 +23,14 @@
23#include <QSqlError>
23#include <QSqlError>
24#include <QSqlResult>
24#include <QSqlResult>
25#include <QRect>
25#include <QRect>
26+#include <unity/shell/application/ApplicationInfoInterface.h>
26
27
27QMutex WindowStateStorage::s_mutex;
28QMutex WindowStateStorage::s_mutex;
28
29
30+inline QString sanitiseString(QString string) {
31+ return string.remove("\"").remove("'").remove("\\");
32+}
33+
29WindowStateStorage::WindowStateStorage(QObject *parent):
34WindowStateStorage::WindowStateStorage(QObject *parent):
30 QObject(parent)
35 QObject(parent)
31{
36{
@@ -50,7 +55,7 @@ WindowStateStorage::~WindowStateStorage()
50void WindowStateStorage::saveState(const QString &windowId, WindowStateStorage::WindowState state)
55void WindowStateStorage::saveState(const QString &windowId, WindowStateStorage::WindowState state)
51{
56{
52 const QString queryString = QStringLiteral("INSERT OR REPLACE INTO state (windowId, state) values ('%1', '%2');")
57 const QString queryString = QStringLiteral("INSERT OR REPLACE INTO state (windowId, state) values ('%1', '%2');")
53− .arg(windowId)
58+ .arg(sanitiseString(windowId))
54 .arg((int)state);
59 .arg((int)state);
55
60
56 saveValue(queryString);
61 saveValue(queryString);
@@ -59,7 +64,7 @@ void WindowStateStorage::saveState(const QString &windowId, WindowStateStorage::
59WindowStateStorage::WindowState WindowStateStorage::getState(const QString &windowId, WindowStateStorage::WindowState defaultValue) const
64WindowStateStorage::WindowState WindowStateStorage::getState(const QString &windowId, WindowStateStorage::WindowState defaultValue) const
60{
65{
61 const QString queryString = QStringLiteral("SELECT * FROM state WHERE windowId = '%1';")
66 const QString queryString = QStringLiteral("SELECT * FROM state WHERE windowId = '%1';")
62− .arg(windowId);
67+ .arg(sanitiseString(windowId));
63
68
64 QSqlQuery query = getValue(queryString);
69 QSqlQuery query = getValue(queryString);
65
70
@@ -72,7 +77,7 @@ WindowStateStorage::WindowState WindowStateStorage::getState(const QString &wind
72void WindowStateStorage::saveGeometry(const QString &windowId, const QRect rect)
77void WindowStateStorage::saveGeometry(const QString &windowId, const QRect rect)
73{
78{
74 const QString queryString = QStringLiteral("INSERT OR REPLACE INTO geometry (windowId, x, y, width, height) values ('%1', '%2', '%3', '%4', '%5');")
79 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))
76 .arg(rect.x())
81 .arg(rect.x())
77 .arg(rect.y())
82 .arg(rect.y())
78 .arg(rect.width())
83 .arg(rect.width())
@@ -81,6 +86,28 @@ void WindowStateStorage::saveGeometry(const QString &windowId, const QRect rect)
81 saveValue(queryString);
86 saveValue(queryString);
82}
87}
83
88
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+
84void WindowStateStorage::executeAsyncQuery(const QString &queryString)
111void WindowStateStorage::executeAsyncQuery(const QString &queryString)
85{
112{
86 QMutexLocker l(&s_mutex);
113 QMutexLocker l(&s_mutex);
@@ -97,7 +124,7 @@ void WindowStateStorage::executeAsyncQuery(const QString &queryString)
97QRect WindowStateStorage::getGeometry(const QString &windowId, const QRect defaultValue) const
124QRect WindowStateStorage::getGeometry(const QString &windowId, const QRect defaultValue) const
98{
125{
99 QString queryString = QStringLiteral("SELECT * FROM geometry WHERE windowId = '%1';")
126 QString queryString = QStringLiteral("SELECT * FROM geometry WHERE windowId = '%1';")
100− .arg(windowId);
127+ .arg(sanitiseString(windowId));
101
128
102 QSqlQuery query = getValue(queryString);
129 QSqlQuery query = getValue(queryString);
103
130
@@ -124,6 +151,11 @@ void WindowStateStorage::initdb()
124 QSqlQuery query;
151 QSqlQuery query;
125 query.exec(QStringLiteral("CREATE TABLE state(windowId TEXT UNIQUE, state INTEGER);"));
152 query.exec(QStringLiteral("CREATE TABLE state(windowId TEXT UNIQUE, state INTEGER);"));
126 }
153 }
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+ }
127}
159}
128
160
129void WindowStateStorage::saveValue(const QString &queryString)
161void WindowStateStorage::saveValue(const QString &queryString)
plugins/Utils/windowstatestorage.h+3
@@ -38,6 +38,9 @@ public:
38 Q_INVOKABLE void saveGeometry(const QString &windowId, const QRect rect);
38 Q_INVOKABLE void saveGeometry(const QString &windowId, const QRect rect);
39 Q_INVOKABLE QRect getGeometry(const QString &windowId, const QRect defaultValue) const;
39 Q_INVOKABLE QRect getGeometry(const QString &windowId, const QRect defaultValue) const;
40
40
41+ Q_INVOKABLE void saveStage(const QString &appId, int stage);
42+ Q_INVOKABLE int getStage(const QString &appId) const;
43+
41private:
44private:
42 void initdb();
45 void initdb();
43
46
plugins/Wizard/CMakeLists.txt+7−1
@@ -1,10 +1,16 @@
1+include_directories(${GLIB_INCLUDE_DIRS} ${GEONAMES_INCLUDE_DIRS})
2+
1add_library(Wizard-qml MODULE
3add_library(Wizard-qml MODULE
2 plugin.cpp
4 plugin.cpp
3 PageList.cpp
5 PageList.cpp
4 System.cpp
6 System.cpp
7+ LocalePlugin.cpp
8+ timezonemodel.cpp
9+ Status.cpp
5)
10)
6
11
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})
8add_unity8_plugin(Wizard 0.1 Wizard TARGETS Wizard-qml)
14add_unity8_plugin(Wizard 0.1 Wizard TARGETS Wizard-qml)
9
15
10set(POLKIT_LIB_DIR "${CMAKE_INSTALL_LOCALSTATEDIR}/lib/polkit-1")
16set(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)
40 m_index(-1),
40 m_index(-1),
41 m_pages()
41 m_pages()
42{
42{
43− QString qmlSuffix = QStringLiteral(".qml");
43+ const QString qmlSuffix = QStringLiteral(".qml");
44− QString disabledSuffix = QStringLiteral(".disabled");
44+ const QString disabledSuffix = QStringLiteral(".disabled");
45 QSet<QString> disabledPages;
45 QSet<QString> disabledPages;
46 QStringList dataDirs;
46 QStringList dataDirs;
47
47
48 if (!isRunningInstalled() && getenv("WIZARD_TESTING") == nullptr) {
48 if (!isRunningInstalled() && getenv("WIZARD_TESTING") == nullptr) {
49− dataDirs = QStringList() << qmlDirectory();
49+ dataDirs << qmlDirectory();
50 } else {
50 } else {
51 dataDirs = shellDataDirs();
51 dataDirs = shellDataDirs();
52 }
52 }
53
53
54 Q_FOREACH(const QString &dataDir, dataDirs) {
54 Q_FOREACH(const QString &dataDir, dataDirs) {
55 QDir dir(dataDir + "/Wizard/Pages");
55 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);
57 Q_FOREACH(const QString &entry, entries) {
57 Q_FOREACH(const QString &entry, entries) {
58 if (!m_pages.contains(entry) && entry.endsWith(qmlSuffix))
58 if (!m_pages.contains(entry) && entry.endsWith(qmlSuffix))
59 m_pages.insert(entry, dir.absoluteFilePath(entry));
59 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 @@
1/*
1/*
2− * Copyright (C) 2014 Canonical Ltd.
2+ * Copyright (C) 2014-2015 Canonical Ltd.
3 *
3 *
4 * This program is free software: you can redistribute it and/or modify it
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
5 * under the terms of the GNU General Public License version 3, as published
@@ -25,10 +25,10 @@
25#include <QLocale>
25#include <QLocale>
26#include <QMap>
26#include <QMap>
27#include <QProcess>
27#include <QProcess>
28+#include <QDebug>
28
29
29System::System()
30System::System()
30− : QObject(),
31+ : QObject()
31− m_fsWatcher()
32{
32{
33 // Register the argument needed for UpdateActivationEnvironment below
33 // Register the argument needed for UpdateActivationEnvironment below
34 qDBusRegisterMetaType<QMap<QString,QString>>();
34 qDBusRegisterMetaType<QMap<QString,QString>>();
@@ -88,7 +88,7 @@ void System::setSessionVariable(const QString &variable, const QString &value)
88 QDBusConnection::sessionBus().asyncCall(msg);
88 QDBusConnection::sessionBus().asyncCall(msg);
89}
89}
90
90
91−void System::updateSessionLanguage(const QString &locale)
91+void System::updateSessionLocale(const QString &locale)
92{
92{
93 const QString language = locale.split(QStringLiteral("."))[0];
93 const QString language = locale.split(QStringLiteral("."))[0];
94
94
@@ -106,5 +106,6 @@ void System::updateSessionLanguage(const QString &locale)
106 initctl stop smart-scopes-proxy; \
106 initctl stop smart-scopes-proxy; \
107 initctl emit --no-wait indicator-services-start; \
107 initctl emit --no-wait indicator-services-start; \
108 initctl restart --no-wait maliit-server; \
108 initctl restart --no-wait maliit-server; \
109+ initctl restart --no-wait indicator-messages; \
109 initctl restart --no-wait unity8-dash\""));
110 initctl restart --no-wait unity8-dash\""));
110}
111}
plugins/Wizard/System.h+3−2
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2014 Canonical Ltd.
2+ * Copyright (C) 2014-2015 Canonical Ltd.
3 *
3 *
4 * This program is free software: you can redistribute it and/or modify it
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
5 * under the terms of the GNU General Public License version 3, as published
@@ -28,12 +28,13 @@ class System : public QObject
28
28
29public:
29public:
30 System();
30 System();
31+ ~System() = default;
31
32
32 bool wizardEnabled() const;
33 bool wizardEnabled() const;
33 void setWizardEnabled(bool enabled);
34 void setWizardEnabled(bool enabled);
34
35
35public Q_SLOTS:
36public Q_SLOTS:
36− void updateSessionLanguage(const QString &locale);
37+ void updateSessionLocale(const QString &locale);
37
38
38Q_SIGNALS:
39Q_SIGNALS:
39 void wizardEnabledChanged();
40 void wizardEnabledChanged();
plugins/Wizard/plugin.cpp+7−8
@@ -17,19 +17,18 @@
17#include "plugin.h"
17#include "plugin.h"
18#include "PageList.h"
18#include "PageList.h"
19#include "System.h"
19#include "System.h"
20+#include "timezonemodel.h"
21+#include "LocalePlugin.h"
22+#include "Status.h"
20
23
21#include <QtQml/qqml.h>
24#include <QtQml/qqml.h>
22
25
23−static QObject *system_provider(QQmlEngine *engine, QJSEngine *scriptEngine)
24−{
25− Q_UNUSED(engine)
26− Q_UNUSED(scriptEngine)
27− return new System();
28−}
29−
30void WizardPlugin::registerTypes(const char *uri)
26void WizardPlugin::registerTypes(const char *uri)
31{
27{
32 Q_ASSERT(uri == QLatin1String("Wizard"));
28 Q_ASSERT(uri == QLatin1String("Wizard"));
33 qmlRegisterType<PageList>(uri, 0, 1, "PageList");
29 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");
35}
34}
plugins/Wizard/plugin.h+1−2
@@ -17,13 +17,12 @@
17#ifndef WIZARD_PLUGIN_H
17#ifndef WIZARD_PLUGIN_H
18#define WIZARD_PLUGIN_H
18#define WIZARD_PLUGIN_H
19
19
20−#include <QtQml/QQmlEngine>
21#include <QtQml/QQmlExtensionPlugin>
20#include <QtQml/QQmlExtensionPlugin>
22
21
23class WizardPlugin : public QQmlExtensionPlugin
22class WizardPlugin : public QQmlExtensionPlugin
24{
23{
25 Q_OBJECT
24 Q_OBJECT
26− Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
25+ Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid)
27
26
28public:
27public:
29 void registerTypes(const char *uri) override;
28 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
po/unity8.pot+29−27
@@ -8,7 +8,7 @@ msgid ""
8msgstr ""
8msgstr ""
9"Project-Id-Version: unity8\n"
9"Project-Id-Version: unity8\n"
10"Report-Msgid-Bugs-To: \n"
10"Report-Msgid-Bugs-To: \n"
11−"POT-Creation-Date: 2016-03-08 21:04+0000\n"
11+"POT-Creation-Date: 2016-03-10 22:48+0000\n"
12"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
12"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14"Language-Team: LANGUAGE <LL@li.org>\n"
14"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -22,16 +22,16 @@ msgstr ""
22msgid "Password: "
22msgid "Password: "
23msgstr ""
23msgstr ""
24
24
25−#: plugins/Unity/Launcher/launcheritem.cpp:47
25+#: plugins/Unity/Launcher/launcheritem.cpp:48
26−#: plugins/Unity/Launcher/launcheritem.cpp:106
26+#: plugins/Unity/Launcher/launcheritem.cpp:107
27msgid "Pin shortcut"
27msgid "Pin shortcut"
28msgstr ""
28msgstr ""
29
29
30−#: plugins/Unity/Launcher/launcheritem.cpp:52
30+#: plugins/Unity/Launcher/launcheritem.cpp:53
31msgid "Quit"
31msgid "Quit"
32msgstr ""
32msgstr ""
33
33
34−#: plugins/Unity/Launcher/launcheritem.cpp:106
34+#: plugins/Unity/Launcher/launcheritem.cpp:107
35msgid "Unpin shortcut"
35msgid "Unpin shortcut"
36msgstr ""
36msgstr ""
37
37
@@ -135,15 +135,15 @@ msgctxt "Button: Restart the system"
135msgid "Restart"
135msgid "Restart"
136msgstr ""
136msgstr ""
137
137
138−#: qml/Components/Lockscreen.qml:245
138+#: qml/Components/Lockscreen.qml:231
139msgid "Return to Call"
139msgid "Return to Call"
140msgstr ""
140msgstr ""
141
141
142−#: qml/Components/Lockscreen.qml:245
142+#: qml/Components/Lockscreen.qml:231
143msgid "Emergency Call"
143msgid "Emergency Call"
144msgstr ""
144msgstr ""
145
145
146−#: qml/Components/Lockscreen.qml:277
146+#: qml/Components/Lockscreen.qml:263
147msgid "OK"
147msgid "OK"
148msgstr ""
148msgstr ""
149
149
@@ -152,6 +152,8 @@ msgid "Apps may have unsaved data:"
152msgstr ""
152msgstr ""
153
153
154#: qml/Components/ModeSwitchWarningDialog.qml:57
154#: qml/Components/ModeSwitchWarningDialog.qml:57
155+msgctxt ""
156+"Re-dock means connect the device again to an external screen/mouse/keyboard"
155msgid "Re-dock, save your work and close these apps to continue."
157msgid "Re-dock, save your work and close these apps to continue."
156msgstr ""
158msgstr ""
157
159
@@ -194,15 +196,15 @@ msgstr ""
194msgid "Add to Favorites"
196msgid "Add to Favorites"
195msgstr ""
197msgstr ""
196
198
197−#: qml/Dash/GenericScopeView.qml:565 qml/Dash/GenericScopeView.qml:723
199+#: qml/Dash/GenericScopeView.qml:567 qml/Dash/GenericScopeView.qml:725
198msgid "See less"
200msgid "See less"
199msgstr ""
201msgstr ""
200
202
201−#: qml/Dash/GenericScopeView.qml:565
203+#: qml/Dash/GenericScopeView.qml:567
202msgid "See all"
204msgid "See all"
203msgstr ""
205msgstr ""
204
206
205−#: qml/Dash/GenericScopeView.qml:627
207+#: qml/Dash/GenericScopeView.qml:629
206msgctxt "Label: Hint for dash search line edit"
208msgctxt "Label: Hint for dash search line edit"
207msgid "Search"
209msgid "Search"
208msgstr ""
210msgstr ""
@@ -228,7 +230,7 @@ msgstr ""
228msgid "Add a review"
230msgid "Add a review"
229msgstr ""
231msgstr ""
230
232
231−#: qml/Dash/Previews/PreviewSharing.qml:50
233+#: qml/Dash/Previews/PreviewSharing.qml:66
232msgid "Preview Share Item"
234msgid "Preview Share Item"
233msgstr ""
235msgstr ""
234
236
@@ -256,13 +258,13 @@ msgstr ""
256msgid "Also installed"
258msgid "Also installed"
257msgstr ""
259msgstr ""
258
260
259−#: qml/DisabledScreenNotice.qml:87
261+#: qml/DisabledScreenNotice.qml:104
260msgid ""
262msgid ""
261"Your device is now connected to an external display. Use this screen as a "
263"Your device is now connected to an external display. Use this screen as a "
262−"touch pad to interact with the mouse."
264+"touch pad to interact with the pointer."
263msgstr ""
265msgstr ""
264
266
265−#: qml/Greeter/CoverPage.qml:107
267+#: qml/Greeter/CoverPage.qml:118
266msgid "Unlock"
268msgid "Unlock"
267msgstr ""
269msgstr ""
268
270
@@ -341,62 +343,62 @@ msgstr ""
341msgid "Conference"
343msgid "Conference"
342msgstr ""
344msgstr ""
343
345
344−#: qml/Panel/Indicators/MenuItemFactory.qml:753
346+#: qml/Panel/Indicators/MenuItemFactory.qml:752
345msgid "Nothing is playing"
347msgid "Nothing is playing"
346msgstr ""
348msgstr ""
347
349
348−#: qml/Panel/Indicators/MenuItemFactory.qml:882
350+#: qml/Panel/Indicators/MenuItemFactory.qml:881
349#, qt-format
351#, qt-format
350msgid "%1 hour"
352msgid "%1 hour"
351msgid_plural "%1 hours"
353msgid_plural "%1 hours"
352msgstr[0] ""
354msgstr[0] ""
353msgstr[1] ""
355msgstr[1] ""
354
356
355−#: qml/Panel/Indicators/MenuItemFactory.qml:886
357+#: qml/Panel/Indicators/MenuItemFactory.qml:885
356#, qt-format
358#, qt-format
357msgid "%1 minute"
359msgid "%1 minute"
358msgid_plural "%1 minutes"
360msgid_plural "%1 minutes"
359msgstr[0] ""
361msgstr[0] ""
360msgstr[1] ""
362msgstr[1] ""
361
363
362−#: qml/Panel/Indicators/MenuItemFactory.qml:891
364+#: qml/Panel/Indicators/MenuItemFactory.qml:890
363#, qt-format
365#, qt-format
364msgid "%1 second"
366msgid "%1 second"
365msgid_plural "%1 seconds"
367msgid_plural "%1 seconds"
366msgstr[0] ""
368msgstr[0] ""
367msgstr[1] ""
369msgstr[1] ""
368
370
369−#: qml/Panel/Indicators/MenuItemFactory.qml:894
371+#: qml/Panel/Indicators/MenuItemFactory.qml:893
370msgid "0 seconds"
372msgid "0 seconds"
371msgstr ""
373msgstr ""
372
374
373#. Translators: String like "1 hour, 2 minutes, 3 seconds remaining"
375#. Translators: String like "1 hour, 2 minutes, 3 seconds remaining"
374−#: qml/Panel/Indicators/MenuItemFactory.qml:896
376+#: qml/Panel/Indicators/MenuItemFactory.qml:895
375#, qt-format
377#, qt-format
376msgid "%1 remaining"
378msgid "%1 remaining"
377msgstr ""
379msgstr ""
378
380
379−#: qml/Panel/Indicators/MenuItemFactory.qml:902
381+#: qml/Panel/Indicators/MenuItemFactory.qml:901
380msgid "In queue…"
382msgid "In queue…"
381msgstr ""
383msgstr ""
382
384
383−#: qml/Panel/Indicators/MenuItemFactory.qml:906
385+#: qml/Panel/Indicators/MenuItemFactory.qml:905
384msgid "Downloading"
386msgid "Downloading"
385msgstr ""
387msgstr ""
386
388
387−#: qml/Panel/Indicators/MenuItemFactory.qml:908
389+#: qml/Panel/Indicators/MenuItemFactory.qml:907
388msgid "Paused, tap to resume"
390msgid "Paused, tap to resume"
389msgstr ""
391msgstr ""
390
392
391−#: qml/Panel/Indicators/MenuItemFactory.qml:910
393+#: qml/Panel/Indicators/MenuItemFactory.qml:909
392msgid "Canceled"
394msgid "Canceled"
393msgstr ""
395msgstr ""
394
396
395−#: qml/Panel/Indicators/MenuItemFactory.qml:912
397+#: qml/Panel/Indicators/MenuItemFactory.qml:911
396msgid "Finished"
398msgid "Finished"
397msgstr ""
399msgstr ""
398
400
399−#: qml/Panel/Indicators/MenuItemFactory.qml:914
401+#: qml/Panel/Indicators/MenuItemFactory.qml:913
400msgid "Failed, tap to retry"
402msgid "Failed, tap to retry"
401msgstr ""
403msgstr ""
402
404
qml/Components/EdgeBarrier.qml+1−1
@@ -105,7 +105,7 @@ Item {
105 },
105 },
106 Transition {
106 Transition {
107 from: "resisting"; to: "passed"
107 from: "resisting"; to: "passed"
108− UbuntuNumberAnimation { target: materialContainer; property: "opacity" }
108+ UbuntuNumberAnimation { duration: UbuntuAnimation.BriskDuration; target: materialContainer; property: "opacity" }
109 }
109 }
110 ]
110 ]
111}
111}
qml/Components/InputMethod.qml+1−9
@@ -22,15 +22,6 @@ import Ubuntu.Gestures 0.1
22Item {
22Item {
23 id: root
23 id: root
24
24
25− Connections {
26− target: SurfaceManager
27− onSurfaceCreated: {
28− if (surface.type == Mir.InputMethodType) {
29− surfaceItem.surface = surface;
30− }
31− }
32− }
33−
34 property int transitionDuration: UbuntuAnimation.FastDuration
25 property int transitionDuration: UbuntuAnimation.FastDuration
35
26
36 MirSurfaceItem {
27 MirSurfaceItem {
@@ -41,6 +32,7 @@ Item {
41
32
42 surfaceWidth: width
33 surfaceWidth: width
43 surfaceHeight: height
34 surfaceHeight: height
35+ surface: SurfaceManager.inputMethodSurface
44
36
45 onLiveChanged: {
37 onLiveChanged: {
46 if (surface !== null && !live) {
38 if (surface !== null && !live) {
qml/Components/Lockscreen.qml+80−94
@@ -121,108 +121,94 @@ Showable {
121 opacity: root.darkenBackground
121 opacity: root.darkenBackground
122 }
122 }
123
123
124− MouseArea {
124+ Loader {
125− anchors.fill: root
125+ id: pinPadLoader
126− onClicked: {
126+ objectName: "pinPadLoader"
127− if (pinPadLoader.item)
128− pinPadLoader.item.forceActiveFocus()
129− }
130− }
131−
132− FocusScope {
133− id: loaderScope
134 anchors.fill: parent
127 anchors.fill: parent
135−
136− Loader {
137− id: pinPadLoader
138− objectName: "pinPadLoader"
139− anchors.fill: parent
140− property bool resetting: false
128+ property bool resetting: false
141− property bool waiting: false
129+ property bool waiting: false
142− property bool showWrongText: false
130+ property bool showWrongText: false
143−
131+ focus: true
132+
144− source: {
133+ source: {
145− if (resetting || !root.required) {
134+ if (resetting || !root.required) {
146− return ""
135+ return ""
147− } else if (root.delayMinutes > 0) {
136+ } else if (root.delayMinutes > 0) {
148− return "DelayedLockscreen.qml"
137+ return "DelayedLockscreen.qml"
149− } else if (root.alphaNumeric) {
138+ } else if (root.alphaNumeric) {
150− return "PassphraseLockscreen.qml"
139+ return "PassphraseLockscreen.qml"
151− } else {
140+ } else {
152− return "PinLockscreen.qml"
141+ return "PinLockscreen.qml"
153− }
154− }
155− onSourceChanged: {
156− waiting = false
157− showWrongText = false
158− if (loaderScope.activeFocus && pinPadLoader.item)
159− pinPadLoader.item.forceActiveFocus()
160 }
142 }
143+ }
144+ onSourceChanged: {
145+ waiting = false
146+ showWrongText = false
147+ }
161
148
162− Connections {
149+ Connections {
163− target: pinPadLoader.item
150+ target: pinPadLoader.item
164−
165− onEntered: {
166− pinPadLoader.waiting = true
167− root.entered(passphrase);
168− }
169
151
170− onCancel: {
152+ onEntered: {
171− root.cancel()
153+ pinPadLoader.waiting = true
172− }
154+ root.entered(passphrase);
173 }
155 }
174
156
175− Binding {
157+ onCancel: {
176− target: pinPadLoader.item
158+ root.cancel()
177− property: "minPinLength"
178− value: root.minPinLength
179− }
180− Binding {
181− target: pinPadLoader.item
182− property: "maxPinLength"
183− value: root.maxPinLength
184− }
185− Binding {
186− target: pinPadLoader.item
187− property: "infoText"
188− value: root.infoText
189− }
190− Binding {
191− target: pinPadLoader.item
192− property: "retryText"
193− value: root.retryText
194− }
195− Binding {
196− target: pinPadLoader.item
197− property: "errorText"
198− value: pinPadLoader.showWrongText ? root.errorText : ""
199− }
200− Binding {
201− target: pinPadLoader.item
202− property: "entryEnabled"
203− value: !pinPadLoader.waiting
204− }
205− Binding {
206− target: pinPadLoader.item
207− property: "alphaNumeric"
208− value: root.alphaNumeric
209− }
210− Binding {
211− target: pinPadLoader.item
212− property: "delayMinutes"
213− value: root.delayMinutes
214− }
215− Binding {
216− target: pinPadLoader.item
217− property: "showCancelButton"
218− value: root.showCancelButton
219− }
220− Binding {
221− target: pinPadLoader.item
222− property: "foregroundColor"
223− value: root.foregroundColor
224 }
159 }
225 }
160 }
161+
162+ Binding {
163+ target: pinPadLoader.item
164+ property: "minPinLength"
165+ value: root.minPinLength
166+ }
167+ Binding {
168+ target: pinPadLoader.item
169+ property: "maxPinLength"
170+ value: root.maxPinLength
171+ }
172+ Binding {
173+ target: pinPadLoader.item
174+ property: "infoText"
175+ value: root.infoText
176+ }
177+ Binding {
178+ target: pinPadLoader.item
179+ property: "retryText"
180+ value: root.retryText
181+ }
182+ Binding {
183+ target: pinPadLoader.item
184+ property: "errorText"
185+ value: pinPadLoader.showWrongText ? root.errorText : ""
186+ }
187+ Binding {
188+ target: pinPadLoader.item
189+ property: "entryEnabled"
190+ value: !pinPadLoader.waiting
191+ }
192+ Binding {
193+ target: pinPadLoader.item
194+ property: "alphaNumeric"
195+ value: root.alphaNumeric
196+ }
197+ Binding {
198+ target: pinPadLoader.item
199+ property: "delayMinutes"
200+ value: root.delayMinutes
201+ }
202+ Binding {
203+ target: pinPadLoader.item
204+ property: "showCancelButton"
205+ value: root.showCancelButton
206+ }
207+ Binding {
208+ target: pinPadLoader.item
209+ property: "foregroundColor"
210+ value: root.foregroundColor
211+ }
226 }
212 }
227
213
228 Item {
214 Item {
qml/Components/ModeSwitchWarningDialog.qml+1−1
@@ -54,7 +54,7 @@ ShellDialog {
54 }
54 }
55
55
56 Label {
56 Label {
57− text: i18n.tr("Re-dock, save your work and close these apps to continue.")
57+ text: i18n.ctr("Re-dock means connect the device again to an external screen/mouse/keyboard", "Re-dock, save your work and close these apps to continue.")
58 wrapMode: Text.WordWrap
58 wrapMode: Text.WordWrap
59 color: "#888888"
59 color: "#888888"
60 }
60 }
qml/Components/PassphraseLockscreen.qml+5−4
@@ -18,10 +18,11 @@ import QtQuick 2.4
18import Ubuntu.Components 1.3
18import Ubuntu.Components 1.3
19import "../Components"
19import "../Components"
20
20
21−Item {
21+FocusScope {
22 id: root
22 id: root
23 y: units.gu(4)
23 y: units.gu(4)
24 height: shakeContainer.height
24 height: shakeContainer.height
25+ focus: true
25
26
26 property string infoText
27 property string infoText
27 property string errorText
28 property string errorText
@@ -42,8 +43,6 @@ Item {
42 }
43 }
43 }
44 }
44
45
45− onActiveFocusChanged: if (activeFocus) pinentryField.forceActiveFocus()
46−
47 Column {
46 Column {
48 id: shakeContainer
47 id: shakeContainer
49 anchors.horizontalCenter: parent.horizontalCenter
48 anchors.horizontalCenter: parent.horizontalCenter
@@ -59,14 +58,16 @@ Item {
59 text: root.infoText
58 text: root.infoText
60 }
59 }
61
60
62− Item {
61+ FocusScope {
63 id: entryContainer
62 id: entryContainer
64 anchors { left: parent.left; right: parent.right; margins: units.gu(2) }
63 anchors { left: parent.left; right: parent.right; margins: units.gu(2) }
65 height: units.gu(4)
64 height: units.gu(4)
65+ focus: true
66
66
67 TextInput {
67 TextInput {
68 id: pinentryField
68 id: pinentryField
69 objectName: "pinentryField"
69 objectName: "pinentryField"
70+ focus: true
70
71
71 property bool incorrectOverride: false
72 property bool incorrectOverride: false
72
73
qml/Components/PhysicalKeysMapper.qml+24
@@ -42,12 +42,15 @@ Item {
42 signal screenshotTriggered;
42 signal screenshotTriggered;
43
43
44 readonly property bool altTabPressed: d.altTabPressed
44 readonly property bool altTabPressed: d.altTabPressed
45+ readonly property bool superPressed: d.superPressed
46+ readonly property bool superTabPressed: d.superTabPressed
45
47
46 property int powerKeyLongPressTime: 2000
48 property int powerKeyLongPressTime: 2000
47
49
48 // For testing. If running windowed (e.g. tryShell), Alt+Tab is taken by the
50 // For testing. If running windowed (e.g. tryShell), Alt+Tab is taken by the
49 // running desktop, set this to true to use Ctrl+Tab instead.
51 // running desktop, set this to true to use Ctrl+Tab instead.
50 property bool controlInsteadOfAlt: false
52 property bool controlInsteadOfAlt: false
53+ property bool controlInsteadOfSuper: false
51
54
52 QtObject {
55 QtObject {
53 id: d
56 id: d
@@ -59,6 +62,9 @@ Item {
59 property bool altPressed: false
62 property bool altPressed: false
60 property bool altTabPressed: false
63 property bool altTabPressed: false
61
64
65+ property bool superPressed: false
66+ property bool superTabPressed: false
67+
62 property var powerButtonPressStart: 0
68 property var powerButtonPressStart: 0
63
69
64 // We need to eat ALT presses until we know what they're for (Alt+Tab or going to the app?)
70 // We need to eat ALT presses until we know what they're for (Alt+Tab or going to the app?)
@@ -119,11 +125,23 @@ Item {
119 event.accepted = true;
125 event.accepted = true;
120 d.altPressInjected = false;
126 d.altPressInjected = false;
121 }
127 }
128+
129+ // Adding MetaModifier here because that's what keyboards do. Pressing Super_L actually gives
130+ // Super_L + MetaModifier. This helps to make sure we only invoke superPressed if no other
131+ // Modifier is pressed too.
132+ } else if (((event.key == Qt.Key_Super_L || event.key == Qt.Key_Super_R) && event.modifiers === Qt.MetaModifier)
133+ || (root.controlInsteadOfSuper && event.key == Qt.Key_Control)
134+ ) {
135+ d.superPressed = true;
122 } else if (event.key == Qt.Key_Tab) {
136 } else if (event.key == Qt.Key_Tab) {
123 if (d.altPressed && !d.altTabPressed) {
137 if (d.altPressed && !d.altTabPressed) {
124 d.altTabPressed = true;
138 d.altTabPressed = true;
125 event.accepted = true;
139 event.accepted = true;
126 }
140 }
141+ if (d.superPressed && !d.superTabPressed) {
142+ d.superTabPressed = true;
143+ event.accepted = true;
144+ }
127 }
145 }
128 }
146 }
129
147
@@ -154,6 +172,12 @@ Item {
154 if (d.altTabPressed) {
172 if (d.altTabPressed) {
155 event.accepted = true;
173 event.accepted = true;
156 }
174 }
175+ } else if (event.key == Qt.Key_Super_L || event.key == Qt.Key_Super_R || (root.controlInsteadOfSuper && event.key == Qt.Key_Control)) {
176+ d.superPressed = false;
177+ if (d.superTabPressed) {
178+ d.superTabPressed = false;
179+ event.accepted = true;
180+ }
157 }
181 }
158 }
182 }
159}
183}
qml/Components/PinLockscreen.qml+22−1
@@ -19,8 +19,9 @@ import Ubuntu.Components 1.3
19import Ubuntu.Components.ListItems 1.3
19import Ubuntu.Components.ListItems 1.3
20import "../Components"
20import "../Components"
21
21
22−Item {
22+FocusScope {
23 id: root
23 id: root
24+ focus: true
24
25
25 property string infoText
26 property string infoText
26 property string retryText
27 property string retryText
@@ -45,6 +46,24 @@ Item {
45 }
46 }
46 }
47 }
47
48
49+ Keys.onPressed: {
50+ if (pinentryField.text.length == root.maxPinLength)
51+ return;
52+
53+ if (event.key === Qt.Key_Backspace) {
54+ pinentryField.backspace();
55+ } else if (event.key === Qt.Key_Delete || event.key === Qt.Key_Escape) {
56+ closeButton.clicked()
57+ } else if (event.key === Qt.Key_Enter || event.key === Qt.Key_Return) {
58+ confirmButton.clicked()
59+ } else {
60+ var digit = parseInt(event.text);
61+ if (!isNaN(digit) && typeof digit == "number") {
62+ pinentryField.appendNumber(digit);
63+ }
64+ }
65+ }
66+
48 Column {
67 Column {
49 anchors {
68 anchors {
50 left: parent.left;
69 left: parent.left;
@@ -204,6 +223,7 @@ Item {
204 width: numbersGrid.buttonWidth
223 width: numbersGrid.buttonWidth
205 }
224 }
206 PinPadButton {
225 PinPadButton {
226+ id: closeButton
207 iconName: "close"
227 iconName: "close"
208 height: units.gu(5) // visual spec has this row a little closer in
228 height: units.gu(5) // visual spec has this row a little closer in
209 width: numbersGrid.buttonWidth
229 width: numbersGrid.buttonWidth
@@ -216,6 +236,7 @@ Item {
216 width: numbersGrid.buttonWidth
236 width: numbersGrid.buttonWidth
217 }
237 }
218 PinPadButton {
238 PinPadButton {
239+ id: confirmButton
219 iconName: "tick"
240 iconName: "tick"
220 objectName: "confirmButton"
241 objectName: "confirmButton"
221 height: units.gu(5)
242 height: units.gu(5)
qml/Components/ShellDialog.qml+8
@@ -37,7 +37,15 @@ Dialog {
37
37
38 focus: true
38 focus: true
39
39
40+ // FIXME: this is a hack because Dialog subtheming seems broken atm
41+ // https://bugs.launchpad.net/ubuntu/+source/ubuntu-ui-toolkit/+bug/1555548
42+ ThemeSettings {
43+ id: themeHack
44+ name: "Ubuntu.Components.Themes.Ambiance"
45+ }
46+
40 Component.onCompleted: {
47 Component.onCompleted: {
48+ __foreground.theme = themeHack
41 show();
49 show();
42 }
50 }
43}
51}
qml/Components/Showable.qml+10
@@ -31,6 +31,7 @@ Item {
31 property bool required
31 property bool required
32 property bool __shouldShow: false
32 property bool __shouldShow: false
33 property bool __skipShowAnimation: false
33 property bool __skipShowAnimation: false
34+ property bool __skipHideAnimation: false
34
35
35 property list<QtObject> hides
36 property list<QtObject> hides
36 property var showAnimation
37 property var showAnimation
@@ -120,12 +121,21 @@ Item {
120 if (!hideAnimation.running) {
121 if (!hideAnimation.running) {
121 hideAnimation.restart()
122 hideAnimation.restart()
122 }
123 }
124+ if (__skipHideAnimation) {
125+ hideAnimation.complete();
126+ }
123 } else {
127 } else {
124 visible = false
128 visible = false
125 required = false
129 required = false
126 }
130 }
127
131
128 shown = false
132 shown = false
133+ __skipHideAnimation = false;
134+ }
135+
136+ function hideNow() {
137+ __skipHideAnimation = true;
138+ hide();
129 }
139 }
130
140
131 Connections {
141 Connections {
qml/Dash/CardCarousel.qml−1
@@ -66,7 +66,6 @@ DashRenderer {
66 item.cardData = Qt.binding(function() { return model; });
66 item.cardData = Qt.binding(function() { return model; });
67 item.fontScale = Qt.binding(function() { return carousel.fontScale; });
67 item.fontScale = Qt.binding(function() { return carousel.fontScale; });
68 item.showHeader = Qt.binding(function() { return loader.explicitlyScaled; });
68 item.showHeader = Qt.binding(function() { return loader.explicitlyScaled; });
69− item.titleAlignment = Qt.binding(function() { return cardTool.titleAlignment; });
70 item.artShapeStyle = "shadow";
69 item.artShapeStyle = "shadow";
71 item.scopeStyle = cardCarousel.scopeStyle;
70 item.scopeStyle = cardCarousel.scopeStyle;
72 }
71 }
qml/Dash/CardGrid.qml−1
@@ -70,7 +70,6 @@ DashRenderer {
70 item.fixedHeaderHeight = Qt.binding(function() { return cardTool.headerHeight; });
70 item.fixedHeaderHeight = Qt.binding(function() { return cardTool.headerHeight; });
71 item.fixedArtShapeSize = Qt.binding(function() { return cardTool.artShapeSize; });
71 item.fixedArtShapeSize = Qt.binding(function() { return cardTool.artShapeSize; });
72 item.cardData = Qt.binding(function() { return model; });
72 item.cardData = Qt.binding(function() { return model; });
73− item.titleAlignment = Qt.binding(function() { return cardTool.titleAlignment; });
74 item.scopeStyle = root.scopeStyle;
73 item.scopeStyle = root.scopeStyle;
75 item.artShapeStyle = root.artShapeStyle;
74 item.artShapeStyle = root.artShapeStyle;
76 item.backgroundShapeStyle = root.backgroundShapeStyle;
75 item.backgroundShapeStyle = root.backgroundShapeStyle;
qml/Dash/CardHorizontalList.qml−1
@@ -53,7 +53,6 @@ DashRenderer {
53 item.fixedArtShapeSize = Qt.binding(function() { return cardTool.artShapeSize; });
53 item.fixedArtShapeSize = Qt.binding(function() { return cardTool.artShapeSize; });
54 item.fixedHeaderHeight = Qt.binding(function() { return cardTool.headerHeight; });
54 item.fixedHeaderHeight = Qt.binding(function() { return cardTool.headerHeight; });
55 item.cardData = Qt.binding(function() { return model; });
55 item.cardData = Qt.binding(function() { return model; });
56− item.titleAlignment = Qt.binding(function() { return cardTool.titleAlignment; });
57 item.scopeStyle = root.scopeStyle;
56 item.scopeStyle = root.scopeStyle;
58 }
57 }
59 Connections {
58 Connections {
qml/Dash/CardTool.qml+2−26
@@ -66,7 +66,7 @@ Item {
66 return layout;
66 return layout;
67 }
67 }
68
68
69− property var cardComponent: CardCreatorCache.getCardComponent(cardTool.template, cardTool.components);
69+ property var cardComponent: CardCreatorCache.getCardComponent(cardTool.template, cardTool.components, false);
70
70
71 // FIXME: Saviq
71 // FIXME: Saviq
72 // Only way for the card below to actually be laid out completely.
72 // Only way for the card below to actually be laid out completely.
@@ -139,29 +139,6 @@ Item {
139 readonly property int headerHeight: cardLoader.item ? cardLoader.item.headerHeight : 0
139 readonly property int headerHeight: cardLoader.item ? cardLoader.item.headerHeight : 0
140 property size artShapeSize: cardLoader.item ? cardLoader.item.artShapeSize : 0
140 property size artShapeSize: cardLoader.item ? cardLoader.item.artShapeSize : 0
141
141
142− /*!
143− \brief Desired alignment of title
144− */
145− readonly property int titleAlignment: {
146− if (template["card-layout"] === "horizontal"
147− || typeof components["title"] !== "object"
148− || components["title"]["align"] === "left") return Text.AlignLeft;
149−
150− var keys = ["mascot", "emblem", "subtitle", "attributes", "summary"];
151−
152− for (var key in keys) {
153− key = keys[key];
154− try {
155− if (typeof components[key] === "string"
156− || typeof components[key]["field"] === "string") return Text.AlignLeft;
157− } catch (e) {
158− continue;
159− }
160− }
161−
162− return Text.AlignHCenter;
163− }
164−
165 QtObject {
142 QtObject {
166 id: carouselTool
143 id: carouselTool
167
144
@@ -216,10 +193,9 @@ Item {
216 "summary": "—\n—\n—\n—\n—",
193 "summary": "—\n—\n—\n—\n—",
217 "attributes": attributesModel.model
194 "attributes": attributesModel.model
218 }
195 }
219− sourceComponent: cardTool.cardComponent
196+ sourceComponent: CardCreatorCache.getCardComponent(cardTool.template, cardTool.components, true);
220 onLoaded: {
197 onLoaded: {
221 item.objectName = "cardToolCard";
198 item.objectName = "cardToolCard";
222− item.asynchronous = false;
223 item.width = Qt.binding(function() { return cardTool.cardWidth !== -1 ? cardTool.cardWidth : item.implicitWidth; });
199 item.width = Qt.binding(function() { return cardTool.cardWidth !== -1 ? cardTool.cardWidth : item.implicitWidth; });
224 item.height = Qt.binding(function() { return cardTool.cardHeight !== -1 ? cardTool.cardHeight : item.implicitHeight; });
200 item.height = Qt.binding(function() { return cardTool.cardHeight !== -1 ? cardTool.cardHeight : item.implicitHeight; });
225 }
201 }
qml/Dash/CardVerticalJournal.qml−1
@@ -72,7 +72,6 @@ DashRenderer {
72 item.fixedArtShapeSize = Qt.binding(function() { return cardTool.artShapeSize; });
72 item.fixedArtShapeSize = Qt.binding(function() { return cardTool.artShapeSize; });
73 item.fixedHeaderHeight = Qt.binding(function() { return cardTool.headerHeight; });
73 item.fixedHeaderHeight = Qt.binding(function() { return cardTool.headerHeight; });
74 item.cardData = Qt.binding(function() { return model; });
74 item.cardData = Qt.binding(function() { return model; });
75− item.titleAlignment = Qt.binding(function() { return cardTool.titleAlignment; });
76 item.scopeStyle = root.scopeStyle;
75 item.scopeStyle = root.scopeStyle;
77 }
76 }
78 Connections {
77 Connections {
qml/Dash/Dash.qml+11−11
@@ -37,6 +37,7 @@ Showable {
37 }
37 }
38
38
39 property bool windowActive: window.active
39 property bool windowActive: window.active
40+ property bool showOverlayScope: false
40
41
41 DashCommunicatorService {
42 DashCommunicatorService {
42 objectName: "dashCommunicatorService"
43 objectName: "dashCommunicatorService"
@@ -80,7 +81,7 @@ Showable {
80 return
81 return
81 }
82 }
82
83
83− closeOverlayScope();
84+ dash.showOverlayScope = false;
84
85
85 dashContent.closePreview();
86 dashContent.closePreview();
86
87
@@ -93,12 +94,6 @@ Showable {
93 dashContent.setCurrentScopeAtIndex(scopeIndex, animate, reset)
94 dashContent.setCurrentScopeAtIndex(scopeIndex, animate, reset)
94 }
95 }
95
96
96− function closeOverlayScope() {
97− if (dashContent.x != 0) {
98− dashContent.x = 0;
99− }
100− }
101−
102 Scopes {
97 Scopes {
103 id: scopes
98 id: scopes
104 }
99 }
@@ -133,12 +128,13 @@ Showable {
133 height: dash.height
128 height: dash.height
134 scopes: scopes
129 scopes: scopes
135 visible: x != -width
130 visible: x != -width
131+ x: dash.showOverlayScope ? -width : 0
136 onGotoScope: {
132 onGotoScope: {
137 dash.setCurrentScope(scopeId, true, false);
133 dash.setCurrentScope(scopeId, true, false);
138 }
134 }
139 onOpenScope: {
135 onOpenScope: {
140 scopeItem.scope = scope;
136 scopeItem.scope = scope;
141− x = -width;
137+ dash.showOverlayScope = true;
142 }
138 }
143 Behavior on x {
139 Behavior on x {
144 UbuntuNumberAnimation {
140 UbuntuNumberAnimation {
@@ -207,7 +203,7 @@ Showable {
207 bottomEdgeController.enableAnimation = true;
203 bottomEdgeController.enableAnimation = true;
208 bottomEdgeController.progress = 0;
204 bottomEdgeController.progress = 0;
209 scopeItem.scope = scope;
205 scopeItem.scope = scope;
210− dashContent.x = -dashContent.width;
206+ dash.showOverlayScope = true;
211 }
207 }
212 onGotoScope: {
208 onGotoScope: {
213 bottomEdgeController.enableAnimation = true;
209 bottomEdgeController.enableAnimation = true;
@@ -226,7 +222,7 @@ Showable {
226 id: scopeItem
222 id: scopeItem
227 objectName: "dashTempScopeItem"
223 objectName: "dashTempScopeItem"
228
224
229− x: dashContent.x + width
225+ x: dash.showOverlayScope ? 0 : width
230 y: dashContent.y
226 y: dashContent.y
231 width: parent.width
227 width: parent.width
232 height: parent.height
228 height: parent.height
@@ -234,7 +230,7 @@ Showable {
234 hasBackAction: true
230 hasBackAction: true
235 isCurrent: visible
231 isCurrent: visible
236 onBackClicked: {
232 onBackClicked: {
237− closeOverlayScope();
233+ dash.showOverlayScope = false;
238 closePreview();
234 closePreview();
239 }
235 }
240
236
@@ -250,6 +246,10 @@ Showable {
250 scopes.closeScope(oldScope);
246 scopes.closeScope(oldScope);
251 }
247 }
252 }
248 }
249+
250+ Behavior on x {
251+ UbuntuNumberAnimation { }
252+ }
253 }
253 }
254
254
255 Rectangle {
255 Rectangle {
qml/Dash/GenericScopeView.qml+2
@@ -370,6 +370,8 @@ FocusScope {
370 } else {
370 } else {
371 cardTool.cardWidth = units.gu(10);
371 cardTool.cardWidth = units.gu(10);
372 }
372 }
373+ } else {
374+ cardTool.cardWidth = units.gu(12);
373 }
375 }
374 item.minimumHorizontalSpacing = item.defaultMinimumHorizontalSpacing;
376 item.minimumHorizontalSpacing = item.defaultMinimumHorizontalSpacing;
375 }
377 }
qml/Dash/Previews/PreviewHeader.qml+37−9
@@ -25,6 +25,7 @@ import "../"
25 * The mascot fall back image comes in widgetData["fallback"]
25 * The mascot fall back image comes in widgetData["fallback"]
26 * The subtitle comes in widgetData["subtitle"]
26 * The subtitle comes in widgetData["subtitle"]
27 * The attributes comes in widgetData["attributes"]
27 * The attributes comes in widgetData["attributes"]
28+ * The emblem comes in widgetData["emblem"]
28 */
29 */
29
30
30PreviewWidget {
31PreviewWidget {
@@ -40,6 +41,7 @@ PreviewWidget {
40 readonly property string title: root.widgetData["title"] || ""
41 readonly property string title: root.widgetData["title"] || ""
41 readonly property string subtitle: root.widgetData["subtitle"] || ""
42 readonly property string subtitle: root.widgetData["subtitle"] || ""
42 readonly property var attributes: root.widgetData["attributes"] || null
43 readonly property var attributes: root.widgetData["attributes"] || null
44+ readonly property url emblem: root.widgetData["emblem"] || ""
43 readonly property color fontColor: root.scopeStyle ? root.scopeStyle.foreground : theme.palette.normal.baseText
45 readonly property color fontColor: root.scopeStyle ? root.scopeStyle.foreground : theme.palette.normal.baseText
44
46
45 // Rewire the source since we may have unwired it on onStatusChanged
47 // Rewire the source since we may have unwired it on onStatusChanged
@@ -98,16 +100,42 @@ PreviewWidget {
98 spacing: units.dp(2)
100 spacing: units.dp(2)
99 anchors.verticalCenter: parent.verticalCenter
101 anchors.verticalCenter: parent.verticalCenter
100
102
101− Label {
103+ Item {
102− id: titleLabel
103− objectName: "titleLabel"
104 anchors { left: parent.left; right: parent.right }
104 anchors { left: parent.left; right: parent.right }
105+ height: titleLabel.height
106+
107+ Label {
108+ id: titleLabel
109+ objectName: "titleLabel"
110+ anchors {
111+ left: parent.left;
112+ right: iconLoader.right
113+ rightMargin: iconLoader.width > 0 ? units.gu(0.5) : 0
114+ }
105− elide: Text.ElideRight
115+ elide: Text.ElideRight
106− font.weight: Font.Normal
116+ font.weight: Font.Normal
107− fontSize: "large"
117+ fontSize: "large"
108− wrapMode: Text.Wrap
118+ wrapMode: Text.Wrap
109− color: headerRoot.fontColor
119+ color: headerRoot.fontColor
110− text: headerRoot.title
120+ text: headerRoot.title
121+ }
122+
123+ Loader {
124+ id: iconLoader
125+ active: headerRoot.emblem != ""
126+ anchors {
127+ bottom: titleLabel.baseline
128+ right: parent.right
129+ }
130+ sourceComponent: Icon {
131+ objectName: "emblemIcon"
132+ source: headerRoot.emblem
133+ color: headerRoot.fontColor
134+ height: source != "" ? titleLabel.font.pixelSize : 0
135+ // FIXME Workaround for bug https://bugs.launchpad.net/ubuntu/+source/ubuntu-ui-toolkit/+bug/1421293
136+ width: implicitWidth > 0 && implicitHeight > 0 ? (implicitWidth / implicitHeight * height) : implicitWidth
137+ }
138+ }
111 }
139 }
112
140
113 Loader {
141 Loader {
qml/Dash/Previews/PreviewOverlay.qml+1−1
@@ -158,7 +158,7 @@ Rectangle {
158 anchors.centerIn: parent
158 anchors.centerIn: parent
159 width: units.gu(2.5)
159 width: units.gu(2.5)
160 height: width
160 height: width
161− color: theme.palette.normal.foregroundText
161+ color: "white"
162 name: "close"
162 name: "close"
163 }
163 }
164 }
164 }
qml/Dash/Previews/PreviewSharing.qml+17−7
@@ -44,6 +44,22 @@ Item {
44 }
44 }
45 }
45 }
46
46
47+ function createExportedItems(url) {
48+ var items = new Array();
49+ if (typeof url === "string") {
50+ var exportItem = exportItemComponent.createObject();
51+ exportItem.url = url;
52+ items.push(exportItem);
53+ } else {
54+ for (var i = 0; i < url.length; i++) {
55+ var exportItem = exportItemComponent.createObject();
56+ exportItem.url = url[i];
57+ items.push(exportItem);
58+ }
59+ }
60+ return items;
61+ }
62+
47 Component {
63 Component {
48 id: exportItemComponent
64 id: exportItemComponent
49 ContentItem {
65 ContentItem {
@@ -76,13 +92,7 @@ Item {
76 onPeerSelected: {
92 onPeerSelected: {
77 var transfer = peer.request();
93 var transfer = peer.request();
78 if (transfer.state === ContentTransfer.InProgress) {
94 if (transfer.state === ContentTransfer.InProgress) {
79− var items = new Array();
80− for (var i = 0; i < url.length; i++) {
81− var exportItem = exportItemComponent.createObject();
82− exportItem.url = url[i];
83− items.push(exportItem);
84− }
85− transfer.items = items;
95+ transfer.items = createExportedItems(url);
86 transfer.state = ContentTransfer.Charged;
96 transfer.state = ContentTransfer.Charged;
87 }
97 }
88 peerPicker.visible = false;
98 peerPicker.visible = false;
qml/Dash/ScopesListCategoryItem.qml+8−4
@@ -18,7 +18,7 @@ import QtQuick 2.4
18import QtQuick.Layouts 1.1
18import QtQuick.Layouts 1.1
19import Ubuntu.Components 1.3
19import Ubuntu.Components 1.3
20
20
21−MouseArea {
21+AbstractButton {
22 id: root
22 id: root
23
23
24 signal requestFavorite(string scopeId, bool favorite)
24 signal requestFavorite(string scopeId, bool favorite)
@@ -84,15 +84,19 @@ MouseArea {
84 visible: text != ""
84 visible: text != ""
85 }
85 }
86 }
86 }
87− MouseArea {
87+ AbstractButton {
88 id: starArea
88 id: starArea
89 objectName: "starArea"
89 objectName: "starArea"
90 height: parent.height
90 height: parent.height
91 width: height
91 width: height
92 anchors.right: parent.right
92 anchors.right: parent.right
93 onClicked: if (!editMode) root.requestFavorite(model.scopeId, !isFavorite);
93 onClicked: if (!editMode) root.requestFavorite(model.scopeId, !isFavorite);
94+ onPressedChanged: {
95+ if (editMode) {
94− onPressed: if (editMode) root.handlePressed(starArea);
96+ if (pressed) root.handlePressed(starArea.__mouseArea);
95− onReleased: if (editMode) root.handleReleased(starArea);
97+ else root.handleReleased(starArea.__mouseArea);
98+ }
99+ }
96 visible: editMode || showStar
100 visible: editMode || showStar
97 Icon {
101 Icon {
98 id: star
102 id: star
qml/DeviceConfiguration.qml+54−16
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2015 Canonical, Ltd.
2+ * Copyright (C) 2015-2016 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
5 * it under the terms of the GNU General Public License as published by
@@ -15,6 +15,7 @@
15 */
15 */
16
16
17import QtQuick 2.4
17import QtQuick 2.4
18+import Utils 0.1
18
19
19QtObject {
20QtObject {
20 id: root
21 id: root
@@ -34,57 +35,83 @@ QtObject {
34
35
35 readonly property alias category: priv.category
36 readonly property alias category: priv.category
36
37
38+ readonly property var deviceConfigParser: DeviceConfigParser {
39+ name: root.name
40+ }
41+
37 readonly property var priv: StateGroup {
42 readonly property var priv: StateGroup {
38 id: priv
43 id: priv
39
44
40− property int primaryOrientation: root.useNativeOrientation
45+ property int primaryOrientation: deviceConfigParser.primaryOrientation == Qt.PrimaryOrientation ?
46+ root.useNativeOrientation : deviceConfigParser.primaryOrientation
41
47
42− property int supportedOrientations: Qt.PortraitOrientation
48+ property int supportedOrientations: deviceConfigParser.supportedOrientations
43− | Qt.InvertedPortraitOrientation
44− | Qt.LandscapeOrientation
45− | Qt.InvertedLandscapeOrientation
46
49
47− property int landscapeOrientation: Qt.LandscapeOrientation
50+ property int landscapeOrientation: deviceConfigParser.landscapeOrientation
48− property int invertedLandscapeOrientation: Qt.InvertedLandscapeOrientation
51+ property int invertedLandscapeOrientation: deviceConfigParser.invertedLandscapeOrientation
49− property int portraitOrientation: Qt.PortraitOrientation
52+ property int portraitOrientation: deviceConfigParser.portraitOrientation
50− property int invertedPortraitOrientation: Qt.InvertedPortraitOrientation
53+ property int invertedPortraitOrientation: deviceConfigParser.invertedPortraitOrientation
51−
52− // Supported values so far:
53− // "phone", "tablet" or "desktop"
54− property string category: "phone"
54+ property string category: deviceConfigParser.category
55
55
56 states: [
56 states: [
57 State {
57 State {
58 name: "mako"
58 name: "mako"
59 PropertyChanges {
59 PropertyChanges {
60 target: priv
60 target: priv
61+ primaryOrientation: root.useNativeOrientation
61 supportedOrientations: Qt.PortraitOrientation
62 supportedOrientations: Qt.PortraitOrientation
62 | Qt.LandscapeOrientation
63 | Qt.LandscapeOrientation
63 | Qt.InvertedLandscapeOrientation
64 | Qt.InvertedLandscapeOrientation
65+ landscapeOrientation: Qt.LandscapeOrientation
66+ invertedLandscapeOrientation: Qt.InvertedLandscapeOrientation
67+ portraitOrientation: Qt.PortraitOrientation
68+ invertedPortraitOrientation: Qt.InvertedPortraitOrientation
69+ category: "phone"
64 }
70 }
65 },
71 },
66 State {
72 State {
67 name: "krillin"
73 name: "krillin"
68 PropertyChanges {
74 PropertyChanges {
69 target: priv
75 target: priv
76+ primaryOrientation: root.useNativeOrientation
70 supportedOrientations: Qt.PortraitOrientation
77 supportedOrientations: Qt.PortraitOrientation
71 | Qt.LandscapeOrientation
78 | Qt.LandscapeOrientation
72 | Qt.InvertedLandscapeOrientation
79 | Qt.InvertedLandscapeOrientation
80+ landscapeOrientation: Qt.LandscapeOrientation
81+ invertedLandscapeOrientation: Qt.InvertedLandscapeOrientation
82+ portraitOrientation: Qt.PortraitOrientation
83+ invertedPortraitOrientation: Qt.InvertedPortraitOrientation
84+ category: "phone"
73 }
85 }
74 },
86 },
75 State {
87 State {
76 name: "arale"
88 name: "arale"
77 PropertyChanges {
89 PropertyChanges {
78 target: priv
90 target: priv
91+ primaryOrientation: root.useNativeOrientation
79 supportedOrientations: Qt.PortraitOrientation
92 supportedOrientations: Qt.PortraitOrientation
80 | Qt.LandscapeOrientation
93 | Qt.LandscapeOrientation
81 | Qt.InvertedLandscapeOrientation
94 | Qt.InvertedLandscapeOrientation
95+ landscapeOrientation: Qt.LandscapeOrientation
96+ invertedLandscapeOrientation: Qt.InvertedLandscapeOrientation
97+ portraitOrientation: Qt.PortraitOrientation
98+ invertedPortraitOrientation: Qt.InvertedPortraitOrientation
99+ category: "phone"
82 }
100 }
83 },
101 },
84 State {
102 State {
85 name: "manta"
103 name: "manta"
86 PropertyChanges {
104 PropertyChanges {
87 target: priv
105 target: priv
106+ primaryOrientation: root.useNativeOrientation
107+ supportedOrientations: Qt.PortraitOrientation
108+ | Qt.InvertedPortraitOrientation
109+ | Qt.LandscapeOrientation
110+ | Qt.InvertedLandscapeOrientation
111+ landscapeOrientation: Qt.LandscapeOrientation
112+ invertedLandscapeOrientation: Qt.InvertedLandscapeOrientation
113+ portraitOrientation: Qt.PortraitOrientation
114+ invertedPortraitOrientation: Qt.InvertedPortraitOrientation
88 category: "tablet"
115 category: "tablet"
89 }
116 }
90 },
117 },
@@ -92,9 +119,15 @@ QtObject {
92 name: "flo"
119 name: "flo"
93 PropertyChanges {
120 PropertyChanges {
94 target: priv
121 target: priv
122+ primaryOrientation: Qt.InvertedLandscapeOrientation
123+ supportedOrientations: Qt.PortraitOrientation
124+ | Qt.InvertedPortraitOrientation
125+ | Qt.LandscapeOrientation
126+ | Qt.InvertedLandscapeOrientation
95 landscapeOrientation: Qt.InvertedLandscapeOrientation
127 landscapeOrientation: Qt.InvertedLandscapeOrientation
96 invertedLandscapeOrientation: Qt.LandscapeOrientation
128 invertedLandscapeOrientation: Qt.LandscapeOrientation
97− primaryOrientation: Qt.InvertedLandscapeOrientation
129+ portraitOrientation: Qt.PortraitOrientation
130+ invertedPortraitOrientation: Qt.InvertedPortraitOrientation
98 category: "tablet"
131 category: "tablet"
99 }
132 }
100 },
133 },
@@ -102,8 +135,13 @@ QtObject {
102 name: "desktop"
135 name: "desktop"
103 PropertyChanges {
136 PropertyChanges {
104 target: priv
137 target: priv
105− category: "desktop"
138+ primaryOrientation: root.useNativeOrientation
106 supportedOrientations: root.useNativeOrientation
139 supportedOrientations: root.useNativeOrientation
140+ landscapeOrientation: Qt.LandscapeOrientation
141+ invertedLandscapeOrientation: Qt.InvertedLandscapeOrientation
142+ portraitOrientation: Qt.PortraitOrientation
143+ invertedPortraitOrientation: Qt.InvertedPortraitOrientation
144+ category: "desktop"
107 }
145 }
108 }
146 }
109 ]
147 ]
qml/DisabledScreenNotice.qml+44−20
@@ -17,6 +17,7 @@
17import QtQuick 2.4
17import QtQuick 2.4
18import QtQuick.Layouts 1.1
18import QtQuick.Layouts 1.1
19import Ubuntu.Components 1.3
19import Ubuntu.Components 1.3
20+import Unity.Session 0.1
20import QtQuick.Window 2.2
21import QtQuick.Window 2.2
21import "Components"
22import "Components"
22
23
@@ -25,19 +26,36 @@ Item {
25
26
26 property bool infoNoteDisplayed: true
27 property bool infoNoteDisplayed: true
27
28
28− WallpaperResolver {
29+ // For testing
29− width: root.width
30+ property var screen: Screen
30− id: wallpaperResolver
31+ property var orientationLock: OrientationLock
32+
33+ DeviceConfiguration {
34+ id: deviceConfiguration
35+ name: applicationArguments.deviceName
31 }
36 }
32
37
33 Item {
38 Item {
34 id: contentContainer
39 id: contentContainer
40+ objectName: "contentContainer"
35 anchors.centerIn: parent
41 anchors.centerIn: parent
36 height: rotation == 90 || rotation == 270 ? parent.width : parent.height
42 height: rotation == 90 || rotation == 270 ? parent.width : parent.height
37 width: rotation == 90 || rotation == 270 ? parent.height : parent.width
43 width: rotation == 90 || rotation == 270 ? parent.height : parent.width
38
44
45+ property int savedOrientation: deviceConfiguration.primaryOrientation == deviceConfiguration.useNativeOrientation
46+ ? (root.width > root.height ? Qt.LandscapeOrientation : Qt.PortraitOrientation)
47+ : deviceConfiguration.primaryOrientation
48+
39 rotation: {
49 rotation: {
40− switch (Screen.orientation) {
50+ var usedOrientation = root.screen.orientation;
51+
52+ if (root.orientationLock.enabled) {
53+ usedOrientation = savedOrientation;
54+ }
55+
56+ savedOrientation = usedOrientation;
57+
58+ switch (usedOrientation) {
41 case Qt.PortraitOrientation:
59 case Qt.PortraitOrientation:
42 return 0;
60 return 0;
43 case Qt.LandscapeOrientation:
61 case Qt.LandscapeOrientation:
@@ -47,6 +65,8 @@ Item {
47 case Qt.InvertedLandscapeOrientation:
65 case Qt.InvertedLandscapeOrientation:
48 return 90;
66 return 90;
49 }
67 }
68+
69+ return 0;
50 }
70 }
51 transformOrigin: Item.Center
71 transformOrigin: Item.Center
52
72
@@ -60,9 +80,9 @@ Item {
60 }
80 }
61 }
81 }
62
82
63− Image {
83+ Rectangle {
64 anchors.fill: parent
84 anchors.fill: parent
65− source: wallpaperResolver.background
85+ color: "#3b3b3b"
66 }
86 }
67
87
68 Item {
88 Item {
@@ -74,22 +94,26 @@ Item {
74 UbuntuNumberAnimation { }
94 UbuntuNumberAnimation { }
75 }
95 }
76
96
77− Rectangle {
97+ Column {
78− anchors.fill: parent
79− color: "black"
80− opacity: 0.4
81− }
82−
83− Label {
84− id: text
85 anchors.centerIn: parent
98 anchors.centerIn: parent
86 width: parent.width - units.gu(8)
99 width: parent.width - units.gu(8)
100+ spacing: units.gu(4)
101+
102+ Label {
103+ id: text
87− text: i18n.tr("Your device is now connected to an external display. Use this screen as a touch pad to interact with the mouse.")
104+ text: i18n.tr("Your device is now connected to an external display. Use this screen as a touch pad to interact with the pointer.")
88− color: "white"
105+ color: "white"
89− horizontalAlignment: Text.AlignHCenter
106+ width: parent.width
90− verticalAlignment: Text.AlignVCenter
91− fontSize: "x-large"
107+ fontSize: "large"
92− wrapMode: Text.Wrap
108+ wrapMode: Text.Wrap
109+ }
110+ Icon {
111+ height: units.gu(8)
112+ width: height
113+ name: "input-touchpad-symbolic"
114+ color: "white"
115+ anchors.horizontalCenter: parent.horizontalCenter
116+ }
93 }
117 }
94 }
118 }
95
119
qml/Greeter/CoverPage.qml+13−2
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2013,2014,2015 Canonical, Ltd.
2+ * Copyright (C) 2013-2016 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
5 * it under the terms of the GNU General Public License as published by
@@ -34,6 +34,7 @@ Showable {
34 readonly property real showProgress: MathUtils.clamp((width - Math.abs(x)) / width, 0, 1)
34 readonly property real showProgress: MathUtils.clamp((width - Math.abs(x)) / width, 0, 1)
35
35
36 signal tease()
36 signal tease()
37+ signal clicked()
37
38
38 function hideRight() {
39 function hideRight() {
39 d.forceRightOnNextHideAnimation = true;
40 d.forceRightOnNextHideAnimation = true;
@@ -55,7 +56,17 @@ Showable {
55 // instead, we can get a little extra horizontal push by using transforms.
56 // instead, we can get a little extra horizontal push by using transforms.
56 transform: Translate { id: translation; x: root.draggable ? launcherOffset : 0 }
57 transform: Translate { id: translation; x: root.draggable ? launcherOffset : 0 }
57
58
59+ // Eat events elsewhere on the coverpage, except mouse clicks which we pass
60+ // up (they are used in the NarrowView to hide the cover page)
61+ MouseArea {
62+ anchors.fill: parent
63+ onClicked: root.clicked()
64+
65+ MultiPointTouchArea {
58− MouseArea { anchors.fill: parent; }
66+ anchors.fill: parent
67+ mouseEnabled: false
68+ }
69+ }
59
70
60 Rectangle {
71 Rectangle {
61 // In case background fails to load
72 // In case background fails to load
qml/Greeter/Infographics.qml+2−1
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013-2016 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
5 * it under the terms of the GNU General Public License as published by
@@ -402,6 +402,7 @@ Item {
402
402
403 MouseArea {
403 MouseArea {
404 anchors.fill: dataCircle
404 anchors.fill: dataCircle
405+ enabled: notification.text != ""
405
406
406 onDoubleClicked: {
407 onDoubleClicked: {
407 if (!d.animating) {
408 if (!d.animating) {
qml/Greeter/NarrowView.qml+14−1
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2015 Canonical, Ltd.
2+ * Copyright (C) 2015-2016 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
5 * it under the terms of the GNU General Public License as published by
@@ -125,6 +125,18 @@ FocusScope {
125 onCancel: coverPage.show()
125 onCancel: coverPage.show()
126 onEmergencyCall: root.emergencyCall()
126 onEmergencyCall: root.emergencyCall()
127
127
128+ onEnabledChanged: {
129+ if (enabled) {
130+ lockscreen.forceActiveFocus();
131+ }
132+ }
133+
134+ onVisibleChanged: {
135+ if (visible) {
136+ lockscreen.forceActiveFocus();
137+ }
138+ }
139+
128 function maybeShow() {
140 function maybeShow() {
129 if (root.locked && !shown) {
141 if (root.locked && !shown) {
130 showNow();
142 showNow();
@@ -145,6 +157,7 @@ FocusScope {
145 width: parent.width
157 width: parent.width
146 background: root.background
158 background: root.background
147 onTease: root.tease()
159 onTease: root.tease()
160+ onClicked: hide()
148
161
149 onShowProgressChanged: {
162 onShowProgressChanged: {
150 if (showProgress === 1) {
163 if (showProgress === 1) {
qml/Launcher/Launcher.qml+144−11
@@ -19,20 +19,25 @@ import "../Components"
19import Ubuntu.Components 1.3
19import Ubuntu.Components 1.3
20import Ubuntu.Gestures 0.1
20import Ubuntu.Gestures 0.1
21import Unity.Launcher 0.1
21import Unity.Launcher 0.1
22+import GlobalShortcut 1.0
22
23
23−Item {
24+FocusScope {
24 id: root
25 id: root
25
26
26 property bool autohideEnabled: false
27 property bool autohideEnabled: false
28+ property bool lockedVisible: false
27 property bool available: true // can be used to disable all interactions
29 property bool available: true // can be used to disable all interactions
28 property alias inverted: panel.inverted
30 property alias inverted: panel.inverted
29
31
30− property int panelWidth: units.gu(8)
32+ property int panelWidth: units.gu(10)
31 property int dragAreaWidth: units.gu(1)
33 property int dragAreaWidth: units.gu(1)
32 property int minimizeDistance: units.gu(26)
34 property int minimizeDistance: units.gu(26)
33 property real progress: dragArea.dragging && dragArea.touchX > panelWidth ?
35 property real progress: dragArea.dragging && dragArea.touchX > panelWidth ?
34 (width * (dragArea.touchX-panelWidth) / (width - panelWidth)) : 0
36 (width * (dragArea.touchX-panelWidth) / (width - panelWidth)) : 0
35
37
38+ property bool superPressed: false
39+ property bool superTabPressed: false
40+
36 readonly property bool dragging: dragArea.dragging
41 readonly property bool dragging: dragArea.dragging
37 readonly property real dragDistance: dragArea.dragging ? dragArea.touchX : 0
42 readonly property real dragDistance: dragArea.dragging ? dragArea.touchX : 0
38 readonly property real visibleWidth: panel.width + panel.x
43 readonly property real visibleWidth: panel.width + panel.x
@@ -56,12 +61,55 @@ Item {
56 }
61 }
57 }
62 }
58
63
64+ onSuperPressedChanged: {
65+ if (superPressed) {
66+ superPressTimer.start();
67+ superLongPressTimer.start();
68+ } else {
69+ superPressTimer.stop();
70+ superLongPressTimer.stop();
71+ launcher.switchToNextState("");
72+ panel.shortcutHintsShown = false;
73+ }
74+ }
75+
76+ onSuperTabPressedChanged: {
77+ if (superTabPressed) {
78+ switchToNextState("visible")
79+ panel.highlightIndex = -1;
80+ root.focus = true;
81+ superPressTimer.stop();
82+ superLongPressTimer.stop();
83+ } else {
84+ if (panel.highlightIndex == -1) {
85+ showDashHome();
86+ } else if (panel.highlightIndex >= 0){
87+ launcherApplicationSelected(LauncherModel.get(panel.highlightIndex).appId);
88+ }
89+ panel.highlightIndex = -2;
90+ switchToNextState("");
91+ root.focus = false;
92+ }
93+ }
94+
95+ onLockedVisibleChanged: {
96+ if (lockedVisible && state == "") {
97+ panel.dismissTimer.stop();
98+ fadeOutAnimation.stop();
99+ switchToNextState("visible")
100+ } else if (!lockedVisible && state == "visible") {
101+ hide();
102+ }
103+ }
104+
59 function hide() {
105 function hide() {
60 switchToNextState("")
106 switchToNextState("")
61 }
107 }
62
108
63 function fadeOut() {
109 function fadeOut() {
110+ if (!root.lockedVisible) {
64− fadeOutAnimation.start();
111+ fadeOutAnimation.start();
112+ }
65 }
113 }
66
114
67 function switchToNextState(state) {
115 function switchToNextState(state) {
@@ -89,6 +137,77 @@ Item {
89 }
137 }
90 }
138 }
91
139
140+ function openForKeyboardNavigation() {
141+ panel.highlightIndex = -1; // The BFB
142+ root.focus = true;
143+ switchToNextState("visible")
144+ }
145+
146+ Keys.onPressed: {
147+ switch (event.key) {
148+ case Qt.Key_Backtab:
149+ panel.highlightPrevious();
150+ event.accepted = true;
151+ break;
152+ case Qt.Key_Up:
153+ if (root.inverted) {
154+ panel.highlightNext()
155+ } else {
156+ panel.highlightPrevious();
157+ }
158+ event.accepted = true;
159+ break;
160+ case Qt.Key_Tab:
161+ panel.highlightNext();
162+ event.accepted = true;
163+ break;
164+ case Qt.Key_Down:
165+ if (root.inverted) {
166+ panel.highlightPrevious();
167+ } else {
168+ panel.highlightNext();
169+ }
170+ event.accepted = true;
171+ break;
172+ case Qt.Key_Right:
173+ panel.openQuicklist(panel.highlightIndex)
174+ event.accepted = true;
175+ break;
176+ case Qt.Key_Escape:
177+ panel.highlightIndex = -2;
178+ // Falling through intentionally
179+ case Qt.Key_Enter:
180+ case Qt.Key_Return:
181+ case Qt.Key_Space:
182+ if (panel.highlightIndex == -1) {
183+ showDashHome();
184+ } else if (panel.highlightIndex >= 0) {
185+ launcherApplicationSelected(LauncherModel.get(panel.highlightIndex).appId);
186+ }
187+ root.hide();
188+ panel.highlightIndex = -2
189+ event.accepted = true;
190+ root.focus = false;
191+ }
192+ }
193+
194+ Timer {
195+ id: superPressTimer
196+ interval: 200
197+ onTriggered: {
198+ switchToNextState("visible")
199+ }
200+ }
201+
202+ Timer {
203+ id: superLongPressTimer
204+ interval: 1000
205+ onTriggered: {
206+ switchToNextState("visible")
207+ panel.shortcutHintsShown = true;
208+ }
209+ }
210+
92 Timer {
211 Timer {
93 id: teaseTimer
212 id: teaseTimer
94 interval: mode == "teasing" ? 200 : 300
213 interval: mode == "teasing" ? 200 : 300
@@ -105,6 +224,13 @@ Item {
105 interval: 1
224 interval: 1
106 property string nextState: ""
225 property string nextState: ""
107 onTriggered: {
226 onTriggered: {
227+ if (root.lockedVisible && nextState == "") {
228+ // Due to binding updates when switching between modes
229+ // it could happen that our request to show will be overwritten
230+ // with a hide request. Rewrite it when we know hiding is not allowed.
231+ nextState = "visible"
232+ }
233+
108 // switching to an intermediate state here to make sure all the
234 // switching to an intermediate state here to make sure all the
109 // values are restored, even if we were already in the target state
235 // values are restored, even if we were already in the target state
110 root.state = "tmp"
236 root.state = "tmp"
@@ -150,7 +276,7 @@ Item {
150
276
151 MouseArea {
277 MouseArea {
152 id: launcherDragArea
278 id: launcherDragArea
153− enabled: root.available && (root.state == "visible" || root.state == "visibleTemporary")
279+ enabled: root.available && (root.state == "visible" || root.state == "visibleTemporary") && !root.lockedVisible
154 anchors.fill: panel
280 anchors.fill: panel
155 anchors.rightMargin: -units.gu(2)
281 anchors.rightMargin: -units.gu(2)
156 drag {
282 drag {
@@ -171,9 +297,10 @@ Item {
171 InverseMouseArea {
297 InverseMouseArea {
172 id: closeMouseArea
298 id: closeMouseArea
173 anchors.fill: panel
299 anchors.fill: panel
174− enabled: root.state == "visible"
300+ enabled: root.state == "visible" && (!root.lockedVisible || panel.highlightIndex >= -1)
175 visible: enabled
301 visible: enabled
176 onPressed: {
302 onPressed: {
303+ panel.highlightIndex = -2
177 root.hide();
304 root.hide();
178 }
305 }
179 }
306 }
@@ -182,7 +309,7 @@ Item {
182 id: backgroundShade
309 id: backgroundShade
183 anchors.fill: parent
310 anchors.fill: parent
184 color: "black"
311 color: "black"
185− opacity: root.state == "visible" ? 0.6 : 0
312+ opacity: root.state == "visible" && !root.lockedVisible ? 0.6 : 0
186
313
187 Behavior on opacity { NumberAnimation { duration: UbuntuAnimation.BriskDuration } }
314 Behavior on opacity { NumberAnimation { duration: UbuntuAnimation.BriskDuration } }
188 }
315 }
@@ -201,8 +328,8 @@ Item {
201 rotation: -90
328 rotation: -90
202 anchors.centerIn: parent
329 anchors.centerIn: parent
203 gradient: Gradient {
330 gradient: Gradient {
204− GradientStop { position: 0.0; color: panel.color}
331+ GradientStop { position: 0.0; color: Qt.rgba(panel.color.r, panel.color.g, panel.color.b, .5)}
205− GradientStop { position: 1.0; color: Qt.rgba(panel.r,panel.g,panel.b,0)}
332+ GradientStop { position: 1.0; color: Qt.rgba(panel.color.r,panel.color.g,panel.color.b,0)}
206 }
333 }
207 }
334 }
208 }
335 }
@@ -226,7 +353,7 @@ Item {
226 Connections {
353 Connections {
227 target: panel.dismissTimer
354 target: panel.dismissTimer
228 onTriggered: {
355 onTriggered: {
229− if (root.autohideEnabled) {
356+ if (root.autohideEnabled && !root.lockedVisible) {
230 if (!panel.preventHiding) {
357 if (!panel.preventHiding) {
231 root.state = ""
358 root.state = ""
232 } else {
359 } else {
@@ -239,11 +366,11 @@ Item {
239 property bool animate: true
366 property bool animate: true
240
367
241 onApplicationSelected: {
368 onApplicationSelected: {
242− root.state = ""
369+ root.hide();
243 launcherApplicationSelected(appId)
370 launcherApplicationSelected(appId)
244 }
371 }
245 onShowDashHome: {
372 onShowDashHome: {
246− root.state = ""
373+ root.hide();
247 root.showDashHome();
374 root.showDashHome();
248 }
375 }
249
376
@@ -253,6 +380,12 @@ Item {
253 }
380 }
254 }
381 }
255
382
383+ onKbdNavigationCancelled: {
384+ panel.highlightIndex = -2;
385+ root.hide();
386+ root.focus = false;
387+ }
388+
256 Behavior on x {
389 Behavior on x {
257 enabled: !dragArea.dragging && !launcherDragArea.drag.active && panel.animate;
390 enabled: !dragArea.dragging && !launcherDragArea.drag.active && panel.animate;
258 NumberAnimation {
391 NumberAnimation {
qml/Launcher/LauncherDelegate.qml+55−20
@@ -20,6 +20,7 @@ import Ubuntu.Components 1.3
20Item {
20Item {
21 id: root
21 id: root
22
22
23+ property int itemIndex: 0
23 property string iconName
24 property string iconName
24 property int count: 0
25 property int count: 0
25 property bool countVisible: false
26 property bool countVisible: false
@@ -29,10 +30,12 @@ Item {
29 property real maxAngle: 0
30 property real maxAngle: 0
30 property bool inverted: false
31 property bool inverted: false
31 property bool alerting: false
32 property bool alerting: false
32− readonly property alias wiggling: wiggleAnim.running
33+ property bool highlighted: false
34+ property bool shortcutHintShown: false
33
35
34 readonly property int effectiveHeight: Math.cos(angle * Math.PI / 180) * itemHeight
36 readonly property int effectiveHeight: Math.cos(angle * Math.PI / 180) * itemHeight
35 readonly property real foldedHeight: Math.cos(maxAngle * Math.PI / 180) * itemHeight
37 readonly property real foldedHeight: Math.cos(maxAngle * Math.PI / 180) * itemHeight
38+ readonly property alias wiggling: wiggleAnim.running
36
39
37 property int itemWidth
40 property int itemWidth
38 property int itemHeight
41 property int itemHeight
@@ -121,20 +124,32 @@ Item {
121
124
122 Item {
125 Item {
123 id: iconItem
126 id: iconItem
124− width: parent.itemWidth + units.gu(1)
127+ width: root.width
125 height: parent.itemHeight + units.gu(1)
128 height: parent.itemHeight + units.gu(1)
126 anchors.centerIn: parent
129 anchors.centerIn: parent
127
130
131+ Image {
132+ objectName: "focusRing"
133+ anchors.centerIn: iconShape
134+ height: width * 15 / 16
135+ width: iconShape.width + units.gu(1)
136+ source: "graphics/launcher-app-focus-ring.svg"
137+ sourceSize.width: width
138+ sourceSize.height: height
139+ visible: root.highlighted
140+ }
141+
128 ProportionalShape {
142 ProportionalShape {
129 id: iconShape
143 id: iconShape
130 anchors.centerIn: parent
144 anchors.centerIn: parent
131− width: parent.width - units.gu(2)
145+ width: root.itemWidth
132 aspect: UbuntuShape.DropShadow
146 aspect: UbuntuShape.DropShadow
133 source: Image {
147 source: Image {
134 id: iconImage
148 id: iconImage
135 sourceSize.width: iconShape.width
149 sourceSize.width: iconShape.width
136 sourceSize.height: iconShape.height
150 sourceSize.height: iconShape.height
137 source: root.iconName
151 source: root.iconName
152+ cache: false // see lpbug#1543290 why no cache
138 }
153 }
139 }
154 }
140
155
@@ -144,7 +159,8 @@ Item {
144 anchors {
159 anchors {
145 right: parent.right
160 right: parent.right
146 bottom: parent.bottom
161 bottom: parent.bottom
162+ rightMargin: (iconItem.width - root.itemWidth) / 2 - units.dp(2)
147− margins: units.dp(3)
163+ margins: units.dp(5)
148 }
164 }
149 width: Math.min(root.itemWidth, Math.max(units.gu(2), countLabel.implicitWidth + units.gu(1)))
165 width: Math.min(root.itemWidth, Math.max(units.gu(2), countLabel.implicitWidth + units.gu(1)))
150 height: units.gu(2)
166 height: units.gu(2)
@@ -172,16 +188,11 @@ Item {
172 id: progressOverlay
188 id: progressOverlay
173 objectName: "progressOverlay"
189 objectName: "progressOverlay"
174
190
175− anchors {
191+ anchors.centerIn: parent
176− left: iconItem.left
192+ width: root.itemWidth * .8
177− right: iconItem.right
178− verticalCenter: parent.verticalCenter
179− leftMargin: units.gu(1.5)
180− rightMargin: units.gu(1.5)
181− }
182 height: units.gu(1)
193 height: units.gu(1)
183 visible: root.progress > -1
194 visible: root.progress > -1
184− color: UbuntuColors.darkGrey
195+ backgroundColor: UbuntuColors.darkGrey
185 borderSource: "none"
196 borderSource: "none"
186
197
187 Item {
198 Item {
@@ -199,32 +210,56 @@ Item {
199 top: parent.top
210 top: parent.top
200 bottom: parent.bottom
211 bottom: parent.bottom
201 }
212 }
202− color: "white"
213+ backgroundColor: "white"
203 borderSource: "none"
214 borderSource: "none"
204 width: progressOverlay.width
215 width: progressOverlay.width
205 }
216 }
206 }
217 }
207 }
218 }
208
219
209− Image {
220+ Column {
210− objectName: "runningHighlight"
211 anchors {
221 anchors {
212 left: parent.left
222 left: parent.left
213 verticalCenter: parent.verticalCenter
223 verticalCenter: parent.verticalCenter
214 }
224 }
225+ spacing: units.gu(.5)
226+ Repeater {
227+ model: 1 // TODO: This should be "Math.min(3, app.surfaceCount)" once we have multiple surfaces
228+ Rectangle {
229+ objectName: "runningHighlight" + index
230+ width: units.gu(0.25)
231+ height: units.gu(.5)
232+ color: "white"
215− visible: root.itemRunning
233+ visible: root.itemRunning
216− rotation: 180
234+ }
217− source: "graphics/focused_app_arrow.png"
235+ }
218 }
236 }
219
237
220− Image {
238+ Rectangle {
221 objectName: "focusedHighlight"
239 objectName: "focusedHighlight"
222 anchors {
240 anchors {
223 right: parent.right
241 right: parent.right
224 verticalCenter: parent.verticalCenter
242 verticalCenter: parent.verticalCenter
225 }
243 }
244+ width: units.gu(0.25)
245+ height: units.gu(.5)
246+ color: "white"
226 visible: root.itemFocused
247 visible: root.itemFocused
227− source: "graphics/focused_app_arrow.png"
248+ }
249+
250+ Rectangle {
251+ objectName: "shortcutHint"
252+ anchors.centerIn: parent
253+ width: units.gu(3)
254+ height: width
255+ color: "#E0292929"
256+ visible: root.shortcutHintShown
257+ Label {
258+ anchors.centerIn: parent
259+ text: (itemIndex + 1) % 10
260+ color: "white"
261+ font.weight: Font.DemiBold
262+ }
228 }
263 }
229 }
264 }
230
265
qml/Launcher/LauncherPanel.qml+122−51
@@ -19,12 +19,13 @@ import Ubuntu.Components 1.3
19import Ubuntu.Components.ListItems 1.3 as ListItems
19import Ubuntu.Components.ListItems 1.3 as ListItems
20import Unity.Launcher 0.1
20import Unity.Launcher 0.1
21import Ubuntu.Components.Popups 1.3
21import Ubuntu.Components.Popups 1.3
22+import GlobalShortcut 1.0
22import "../Components/ListItems"
23import "../Components/ListItems"
23import "../Components/"
24import "../Components/"
24
25
25Rectangle {
26Rectangle {
26 id: root
27 id: root
27− color: "#B2000000"
28+ color: "#E0292929"
28
29
29 rotation: inverted ? 180 : 0
30 rotation: inverted ? 180 : 0
30
31
@@ -33,11 +34,13 @@ Rectangle {
33 property bool dragging: false
34 property bool dragging: false
34 property bool moving: launcherListView.moving || launcherListView.flicking
35 property bool moving: launcherListView.moving || launcherListView.flicking
35 property bool preventHiding: moving || dndArea.draggedIndex >= 0 || quickList.state === "open" || dndArea.pressed
36 property bool preventHiding: moving || dndArea.draggedIndex >= 0 || quickList.state === "open" || dndArea.pressed
36− || mouseEventEater.containsMouse || dashItem.hovered
37+ || mouseEventEater.containsMouse || dashItem.hovered
37− property int highlightIndex: -1
38+ property int highlightIndex: -2
39+ property bool shortcutHintsShown: false
38
40
39 signal applicationSelected(string appId)
41 signal applicationSelected(string appId)
40 signal showDashHome()
42 signal showDashHome()
43+ signal kbdNavigationCancelled()
41
44
42 onXChanged: {
45 onXChanged: {
43 if (quickList.state == "open") {
46 if (quickList.state == "open") {
@@ -45,6 +48,26 @@ Rectangle {
45 }
48 }
46 }
49 }
47
50
51+ function highlightNext() {
52+ highlightIndex++;
53+ if (highlightIndex >= launcherListView.count) {
54+ highlightIndex = -1;
55+ }
56+ launcherListView.moveToIndex(Math.max(highlightIndex, 0));
57+ }
58+ function highlightPrevious() {
59+ highlightIndex--;
60+ if (highlightIndex <= -2) {
61+ highlightIndex = launcherListView.count - 1;
62+ }
63+ launcherListView.moveToIndex(Math.max(highlightIndex, 0));
64+ }
65+ function openQuicklist(index) {
66+ quickList.open(index);
67+ quickList.selectedIndex = 0;
68+ quickList.focus = true;
69+ }
70+
48 MouseArea {
71 MouseArea {
49 id: mouseEventEater
72 id: mouseEventEater
50 anchors.fill: parent
73 anchors.fill: parent
@@ -57,24 +80,16 @@ Rectangle {
57 fill: parent
80 fill: parent
58 }
81 }
59
82
60− Item {
83+ Rectangle {
61 objectName: "buttonShowDashHome"
84 objectName: "buttonShowDashHome"
62 width: parent.width
85 width: parent.width
63− height: units.gu(7)
86+ height: width * .9
64− clip: true
65−
66− UbuntuShape {
67− anchors {
68− fill: parent
69− topMargin: -units.gu(2)
70− }
71− aspect: UbuntuShape.Flat
72− backgroundColor: UbuntuColors.orange
87+ color: UbuntuColors.orange
73− }
88+ readonly property bool highlighted: root.highlightIndex == -1;
74
89
75 Image {
90 Image {
76 objectName: "dashItem"
91 objectName: "dashItem"
77− width: units.gu(5)
92+ width: parent.width * .6
78 height: width
93 height: width
79 anchors.centerIn: parent
94 anchors.centerIn: parent
80 source: "graphics/home.png"
95 source: "graphics/home.png"
@@ -85,6 +100,14 @@ Rectangle {
85 anchors.fill: parent
100 anchors.fill: parent
86 onClicked: root.showDashHome()
101 onClicked: root.showDashHome()
87 }
102 }
103+ Rectangle {
104+ objectName: "bfbFocusHighlight"
105+ anchors.fill: parent
106+ border.color: "white"
107+ border.width: units.dp(1)
108+ color: "transparent"
109+ visible: parent.highlighted
110+ }
88 }
111 }
89
112
90 Item {
113 Item {
@@ -102,10 +125,8 @@ Rectangle {
102 objectName: "launcherListView"
125 objectName: "launcherListView"
103 anchors {
126 anchors {
104 fill: parent
127 fill: parent
105− topMargin: -extensionSize + units.gu(0.5)
128+ topMargin: -extensionSize + width * .15
106− bottomMargin: -extensionSize + units.gu(1)
129+ bottomMargin: -extensionSize + width * .15
107− leftMargin: units.gu(0.5)
108− rightMargin: units.gu(0.5)
109 }
130 }
110 topMargin: extensionSize
131 topMargin: extensionSize
111 bottomMargin: extensionSize
132 bottomMargin: extensionSize
@@ -140,11 +161,11 @@ Rectangle {
140 }
161 }
141
162
142 // The height of the area where icons start getting folded
163 // The height of the area where icons start getting folded
143− property int foldingStartHeight: units.gu(6.5)
164+ property int foldingStartHeight: itemHeight
144 // The height of the area where the items reach the final folding angle
165 // The height of the area where the items reach the final folding angle
145 property int foldingStopHeight: foldingStartHeight - itemHeight - spacing
166 property int foldingStopHeight: foldingStartHeight - itemHeight - spacing
146− property int itemWidth: units.gu(7)
167+ property int itemWidth: width * .75
147− property int itemHeight: units.gu(6.5)
168+ property int itemHeight: itemWidth * 15 / 16 + units.gu(1)
148 property int clickFlickSpeed: units.gu(60)
169 property int clickFlickSpeed: units.gu(60)
149 property int draggedIndex: dndArea.draggedIndex
170 property int draggedIndex: dndArea.draggedIndex
150 property real realContentY: contentY - originY + topMargin
171 property real realContentY: contentY - originY + topMargin
@@ -172,12 +193,24 @@ Rectangle {
172
193
173 UbuntuNumberAnimation {
194 UbuntuNumberAnimation {
174 id: moveAnimation
195 id: moveAnimation
196+ objectName: "moveAnimation"
175 target: launcherListView
197 target: launcherListView
176 property: "contentY"
198 property: "contentY"
177 function moveTo(contentY) {
199 function moveTo(contentY) {
178 from = launcherListView.contentY;
200 from = launcherListView.contentY;
179 to = contentY;
201 to = contentY;
180− start();
202+ restart();
203+ }
204+ }
205+ function moveToIndex(index) {
206+ var totalItemHeight = launcherListView.itemHeight + launcherListView.spacing
207+ var itemPosition = index * totalItemHeight;
208+ var height = launcherListView.height - launcherListView.topMargin - launcherListView.bottomMargin
209+ var distanceToEnd = index == 0 || index == launcherListView.count - 1 ? 0 : totalItemHeight
210+ if (itemPosition + totalItemHeight + distanceToEnd > launcherListView.contentY + launcherListView.originY + launcherListView.topMargin + height) {
211+ moveAnimation.moveTo(itemPosition + launcherListView.itemHeight - launcherListView.topMargin - height + distanceToEnd - launcherListView.originY);
212+ } else if (itemPosition - distanceToEnd < launcherListView.contentY - launcherListView.originY + launcherListView.topMargin) {
213+ moveAnimation.moveTo(itemPosition - distanceToEnd - launcherListView.topMargin + launcherListView.originY);
181 }
214 }
182 }
215 }
183
216
@@ -192,9 +225,10 @@ Rectangle {
192 // the right app when running autopilot tests for
225 // the right app when running autopilot tests for
193 // multiple apps.
226 // multiple apps.
194 readonly property string appId: model.appId
227 readonly property string appId: model.appId
228+ itemIndex: index
195 itemHeight: launcherListView.itemHeight
229 itemHeight: launcherListView.itemHeight
196 itemWidth: launcherListView.itemWidth
230 itemWidth: launcherListView.itemWidth
197− width: itemWidth
231+ width: parent.width
198 height: itemHeight
232 height: itemHeight
199 iconName: model.icon
233 iconName: model.icon
200 count: model.count
234 count: model.count
@@ -204,6 +238,8 @@ Rectangle {
204 itemFocused: model.focused
238 itemFocused: model.focused
205 inverted: root.inverted
239 inverted: root.inverted
206 alerting: model.alerting
240 alerting: model.alerting
241+ highlighted: root.highlightIndex == index
242+ shortcutHintShown: root.shortcutHintsShown && index <= 9
207 z: -Math.abs(offset)
243 z: -Math.abs(offset)
208 maxAngle: 55
244 maxAngle: 55
209 property bool dragging: false
245 property bool dragging: false
@@ -241,14 +277,7 @@ Rectangle {
241 onAlertingChanged: {
277 onAlertingChanged: {
242 if(alerting) {
278 if(alerting) {
243 if (!dragging && (launcherListView.peekingIndex === -1 || launcher.visibleWidth > 0)) {
279 if (!dragging && (launcherListView.peekingIndex === -1 || launcher.visibleWidth > 0)) {
244− var itemPosition = index * launcherListView.itemHeight;
280+ launcherListView.moveToIndex(index)
245− var height = launcherListView.height - launcherListView.topMargin - launcherListView.bottomMargin
246− var distanceToEnd = index == 0 || index == launcherListView.count - 1 ? 0 : launcherListView.itemHeight
247− if (itemPosition + launcherListView.itemHeight + distanceToEnd > launcherListView.contentY + launcherListView.topMargin + height) {
248− moveAnimation.moveTo(itemPosition + launcherListView.itemHeight - launcherListView.topMargin - height + distanceToEnd);
249− } else if (itemPosition - distanceToEnd < launcherListView.contentY + launcherListView.topMargin) {
250− moveAnimation.moveTo(itemPosition - distanceToEnd - launcherListView.topMargin);
251− }
252 if (!dragging && launcher.state !== "visible") {
281 if (!dragging && launcher.state !== "visible") {
253 peekingAnimation.start()
282 peekingAnimation.start()
254 }
283 }
@@ -402,10 +431,7 @@ Rectangle {
402
431
403 if (mouse.button & Qt.RightButton) { // context menu
432 if (mouse.button & Qt.RightButton) { // context menu
404 // Opening QuickList
433 // Opening QuickList
405− quickList.item = clickedItem;
434+ quickList.open(index);
406− quickList.model = launcherListView.model.get(index).quickList;
407− quickList.appId = launcherListView.model.get(index).appId;
408− quickList.state = "open";
409 return;
435 return;
410 }
436 }
411
437
@@ -413,10 +439,8 @@ Rectangle {
413
439
414 // First/last item do the scrolling at more than 12 degrees
440 // First/last item do the scrolling at more than 12 degrees
415 if (index == 0 || index == launcherListView.count - 1) {
441 if (index == 0 || index == launcherListView.count - 1) {
416− if (clickedItem.angle > 12) {
442+ if (clickedItem.angle > 12 || clickedItem.angle < -12) {
417− launcherListView.flick(0, -launcherListView.clickFlickSpeed);
443+ launcherListView.moveToIndex(index);
418− } else if (clickedItem.angle < -12) {
419− launcherListView.flick(0, launcherListView.clickFlickSpeed);
420 } else {
444 } else {
421 root.applicationSelected(LauncherModel.get(index).appId);
445 root.applicationSelected(LauncherModel.get(index).appId);
422 }
446 }
@@ -424,10 +448,8 @@ Rectangle {
424 }
448 }
425
449
426 // the rest launches apps up to an angle of 30 degrees
450 // the rest launches apps up to an angle of 30 degrees
427− if (clickedItem.angle > 30) {
451+ if (clickedItem.angle > 30 || clickedItem.angle < -30) {
428− launcherListView.flick(0, -launcherListView.clickFlickSpeed);
452+ launcherListView.moveToIndex(index);
429− } else if (clickedItem.angle < -30) {
430− launcherListView.flick(0, launcherListView.clickFlickSpeed);
431 } else {
453 } else {
432 root.applicationSelected(LauncherModel.get(index).appId);
454 root.applicationSelected(LauncherModel.get(index).appId);
433 }
455 }
@@ -481,11 +503,7 @@ Rectangle {
481
503
482 draggedIndex = Math.floor((mouse.y + launcherListView.realContentY) / launcherListView.realItemHeight);
504 draggedIndex = Math.floor((mouse.y + launcherListView.realContentY) / launcherListView.realItemHeight);
483
505
484− // Opening QuickList
485− quickList.item = selectedItem;
486− quickList.model = launcherListView.model.get(draggedIndex).quickList;
487− quickList.appId = launcherListView.model.get(draggedIndex).appId;
488− quickList.state = "open";
506+ quickList.open(draggedIndex)
489
507
490 launcherListView.interactive = false
508 launcherListView.interactive = false
491
509
@@ -644,7 +662,9 @@ Rectangle {
644 enabled: quickList.state == "open" || pressed
662 enabled: quickList.state == "open" || pressed
645
663
646 onClicked: {
664 onClicked: {
647− quickList.state = ""
665+ quickList.state = "";
666+ quickList.focus = false;
667+ root.kbdNavigationCancelled();
648 }
668 }
649
669
650 // Forward for dragging to work when quickList is open
670 // Forward for dragging to work when quickList is open
@@ -693,12 +713,60 @@ Rectangle {
693 property var model
713 property var model
694 property string appId
714 property string appId
695 property var item
715 property var item
716+ property int selectedIndex: -1
717+
718+ Keys.onPressed: {
719+ switch (event.key) {
720+ case Qt.Key_Down:
721+ selectedIndex++;
722+ if (selectedIndex >= popoverRepeater.count) {
723+ selectedIndex = 0;
724+ }
725+ event.accepted = true;
726+ break;
727+ case Qt.Key_Up:
728+ selectedIndex--;
729+ if (selectedIndex < 0) {
730+ selectedIndex = popoverRepeater.count - 1;
731+ }
732+ event.accepted = true;
733+ break;
734+ case Qt.Key_Left:
735+ case Qt.Key_Escape:
736+ quickList.selectedIndex = -1;
737+ quickList.focus = false;
738+ quickList.state = ""
739+ event.accepted = true;
740+ break;
741+ case Qt.Key_Enter:
742+ case Qt.Key_Return:
743+ case Qt.Key_Space:
744+ if (quickList.selectedIndex >= 0) {
745+ LauncherModel.quickListActionInvoked(quickList.appId, quickList.selectedIndex)
746+ }
747+ quickList.selectedIndex = -1;
748+ quickList.focus = false;
749+ quickList.state = ""
750+ root.kbdNavigationCancelled();
751+ event.accepted = true;
752+ break;
753+ }
754+ }
696
755
697 // internal
756 // internal
698 property int itemCenter: item ? root.mapFromItem(quickList.item).y + (item.height / 2) + quickList.item.offset : units.gu(1)
757 property int itemCenter: item ? root.mapFromItem(quickList.item).y + (item.height / 2) + quickList.item.offset : units.gu(1)
699 property int offset: itemCenter + (height/2) + units.gu(1) > parent.height ? -itemCenter - (height/2) - units.gu(1) + parent.height :
758 property int offset: itemCenter + (height/2) + units.gu(1) > parent.height ? -itemCenter - (height/2) - units.gu(1) + parent.height :
700 itemCenter - (height/2) < units.gu(1) ? (height/2) - itemCenter + units.gu(1) : 0
759 itemCenter - (height/2) < units.gu(1) ? (height/2) - itemCenter + units.gu(1) : 0
701
760
761+ function open(index) {
762+ var itemPosition = index * launcherListView.itemHeight;
763+ var height = launcherListView.height - launcherListView.topMargin - launcherListView.bottomMargin
764+ item = launcherListView.itemAt(launcherListView.width / 2, itemPosition + launcherListView.itemHeight / 2);
765+ quickList.model = launcherListView.model.get(index).quickList;
766+ quickList.appId = launcherListView.model.get(index).appId;
767+ quickList.state = "open";
768+ }
769+
702 Column {
770 Column {
703 id: quickListColumn
771 id: quickListColumn
704 width: parent.width
772 width: parent.width
@@ -712,6 +780,7 @@ Rectangle {
712 objectName: "quickListEntry" + index
780 objectName: "quickListEntry" + index
713 text: (model.clickable ? "" : "<b>") + model.label + (model.clickable ? "" : "</b>")
781 text: (model.clickable ? "" : "<b>") + model.label + (model.clickable ? "" : "</b>")
714 highlightWhenPressed: model.clickable
782 highlightWhenPressed: model.clickable
783+ selected: index === quickList.selectedIndex
715
784
716 // FIXME: This is a workaround for the theme not being context sensitive. I.e. the
785 // FIXME: This is a workaround for the theme not being context sensitive. I.e. the
717 // ListItems don't know that they are sitting in a themed Popover where the color
786 // ListItems don't know that they are sitting in a themed Popover where the color
@@ -727,6 +796,8 @@ Rectangle {
727 // Unsetting model to prevent showing changing entries during fading out
796 // Unsetting model to prevent showing changing entries during fading out
728 // that may happen because of triggering an action.
797 // that may happen because of triggering an action.
729 LauncherModel.quickListActionInvoked(quickList.appId, index);
798 LauncherModel.quickListActionInvoked(quickList.appId, index);
799+ quickList.focus = false;
800+ root.kbdNavigationCancelled();
730 quickList.model = undefined;
801 quickList.model = undefined;
731 }
802 }
732 }
803 }
qml/Launcher/graphics/launcher-app-focus-ring.svgnew file+12
@@ -0,0 +1,12 @@
1+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2+<svg width="172px" height="163px" viewBox="0 0 172 163" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
3+ <!-- Generator: Sketch 3.4.4 (17249) - http://www.bohemiancoding.com/sketch -->
4+ <title>Shape</title>
5+ <desc>Created with Sketch.</desc>
6+ <defs></defs>
7+ <g id="•-Launcher" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
8+ <g id="Artboard-9" sketch:type="MSArtboardGroup" transform="translate(-163.000000, -1436.000000)" fill="#E95420">
9+ <path d="M221.983432,1440 L221.983432,1440 C195.6127,1440 184.708233,1442.4723 177.107949,1450.10734 C169.476819,1457.77336 167,1468.79245 167,1495.3481 L167,1538.9019 C167,1565.45755 169.476819,1576.47664 177.107949,1584.14266 C184.708233,1591.7777 195.6127,1594.25 221.983432,1594.25 L276.016868,1594.25 C302.387595,1594.25 313.291998,1591.77771 320.892221,1584.14264 C328.523252,1576.47663 331,1565.45769 331,1538.9019 L331,1495.3481 C331,1468.79231 328.523252,1457.77337 320.892221,1450.10736 C313.291998,1442.47229 302.387595,1440 276.016868,1440 L221.983432,1440 Z M221.983432,1436 L276.016868,1436 C302.345315,1436 314.848953,1438.36655 323.727108,1447.2854 C332.633306,1456.23243 335,1468.85167 335,1495.3481 L335,1538.9019 C335,1565.39833 332.633306,1578.01757 323.727108,1586.9646 C314.848953,1595.88345 302.345315,1598.25 276.016868,1598.25 L221.983432,1598.25 C195.654985,1598.25 183.151291,1595.88345 174.273077,1586.96463 C165.366772,1578.0176 163,1565.39822 163,1538.9019 L163,1495.3481 C163,1468.85178 165.366772,1456.2324 174.273077,1447.28537 C183.151291,1438.36655 195.654985,1436 221.983432,1436 L221.983432,1436 Z" id="Shape" sketch:type="MSShapeGroup"></path>
10+ </g>
11+ </g>
12+</svg>
qml/Notifications/Notification.qml+6−2
@@ -23,7 +23,7 @@ import QMenuModel 0.1
23import Utils 0.1
23import Utils 0.1
24import "../Components"
24import "../Components"
25
25
26−Item {
26+StyledItem {
27 id: notification
27 id: notification
28
28
29 property alias iconSource: icon.fileSource
29 property alias iconSource: icon.fileSource
@@ -58,6 +58,10 @@ Item {
58 color: (type === Notification.Confirmation && notificationList.useModal && !greeter.shown) || darkOnBright ? sdLightGrey : Qt.rgba(0.132, 0.117, 0.109, 0.97)
58 color: (type === Notification.Confirmation && notificationList.useModal && !greeter.shown) || darkOnBright ? sdLightGrey : Qt.rgba(0.132, 0.117, 0.109, 0.97)
59 opacity: 1 - (x / notification.width) // FIXME: non-zero initially because of LP: #1354406 workaround, we want this to start at 0 upon creation eventually
59 opacity: 1 - (x / notification.width) // FIXME: non-zero initially because of LP: #1354406 workaround, we want this to start at 0 upon creation eventually
60
60
61+ theme: ThemeSettings {
62+ name: "Ubuntu.Components.Themes.Ambiance"
63+ }
64+
61 state: {
65 state: {
62 var result = "";
66 var result = "";
63
67
@@ -297,7 +301,7 @@ Item {
297 visible: body != "" && type !== Notification.Confirmation
301 visible: body != "" && type !== Notification.Confirmation
298 fontSize: "small"
302 fontSize: "small"
299 color: darkOnBright ? sdFontColor : theme.palette.normal.backgroundText
303 color: darkOnBright ? sdFontColor : theme.palette.normal.backgroundText
300− wrapMode: Text.WordWrap
304+ wrapMode: Text.Wrap
301 maximumLineCount: type == Notification.SnapDecision ? 12 : 2
305 maximumLineCount: type == Notification.SnapDecision ? 12 : 2
302 elide: Text.ElideRight
306 elide: Text.ElideRight
303 textFormat: Text.PlainText
307 textFormat: Text.PlainText
qml/OrientedShell.qml+26−1
@@ -19,6 +19,7 @@ import QtQuick.Window 2.2
19import Unity.InputInfo 0.1
19import Unity.InputInfo 0.1
20import Unity.Session 0.1
20import Unity.Session 0.1
21import Unity.Screens 0.1
21import Unity.Screens 0.1
22+import Utils 0.1
22import GSettings 1.0
23import GSettings 1.0
23import "Components"
24import "Components"
24import "Rotation"
25import "Rotation"
@@ -77,6 +78,8 @@ Rectangle {
77 InputDeviceModel {
78 InputDeviceModel {
78 id: keyboardsModel
79 id: keyboardsModel
79 deviceFilter: InputInfo.Keyboard
80 deviceFilter: InputInfo.Keyboard
81+ onDeviceAdded: forceOSKEnabled = autopilotDevicePresent();
82+ onDeviceRemoved: forceOSKEnabled = autopilotDevicePresent();
80 }
83 }
81
84
82 InputDeviceModel {
85 InputDeviceModel {
@@ -84,6 +87,27 @@ Rectangle {
84 deviceFilter: InputInfo.TouchScreen
87 deviceFilter: InputInfo.TouchScreen
85 }
88 }
86
89
90+ /* FIXME: This exposes the NameRole as a work arround for lp:1542224.
91+ * When QInputInfo exposes NameRole to QML, this should be removed.
92+ */
93+ property bool forceOSKEnabled: false
94+ property var autopilotEmulatedDeviceNames: ["py-evdev-uinput"]
95+ UnitySortFilterProxyModel {
96+ id: autopilotDevices
97+ model: keyboardsModel
98+ }
99+
100+ function autopilotDevicePresent() {
101+ for(var i = 0; i < autopilotDevices.count; i++) {
102+ var device = autopilotDevices.get(i);
103+ if (autopilotEmulatedDeviceNames.indexOf(device.name) != -1) {
104+ console.warn("Forcing the OSK to be enabled as there is an autopilot eumlated device present.")
105+ return true;
106+ }
107+ }
108+ return false;
109+ }
110+
87 Screens {
111 Screens {
88 id: screens
112 id: screens
89 }
113 }
@@ -195,7 +219,8 @@ Rectangle {
195 // have multiple keyboards around. For now we only enable one keyboard at a time
219 // have multiple keyboards around. For now we only enable one keyboard at a time
196 // thus hiding it here if there is a physical one around or if we have a second
220 // thus hiding it here if there is a physical one around or if we have a second
197 // screen (the virtual touchpad & osk on the phone) attached.
221 // screen (the virtual touchpad & osk on the phone) attached.
198− oskEnabled: keyboardsModel.count === 0 && screens.count === 1
222+ oskEnabled: (keyboardsModel.count === 0 && screens.count === 1) ||
223+ forceOSKEnabled
199
224
200 // TODO: Factor in the connected input devices (eg: physical keyboard, mouse, touchscreen),
225 // TODO: Factor in the connected input devices (eg: physical keyboard, mouse, touchscreen),
201 // what's the output device (eg: big TV, desktop monitor, phone display), etc.
226 // what's the output device (eg: big TV, desktop monitor, phone display), etc.
qml/Panel/Handle.qml+2−2
@@ -19,7 +19,7 @@ import Ubuntu.Components 1.3
19
19
20Rectangle {
20Rectangle {
21 id: handle
21 id: handle
22− color: UbuntuColors.slate
22+ color: theme.palette.normal.foreground
23 height: units.gu(2)
23 height: units.gu(2)
24 property bool active: false
24 property bool active: false
25
25
@@ -35,7 +35,7 @@ Rectangle {
35 id: dot
35 id: dot
36 width: units.dp(3)
36 width: units.dp(3)
37 height: width
37 height: width
38− color: handle.active ? UbuntuColors.orange : UbuntuColors.ash
38+ color: handle.active ? theme.palette.highlighted.foreground : theme.palette.normal.raised
39 radius: units.dp(1)
39 radius: units.dp(1)
40 }
40 }
41 }
41 }
qml/Panel/Indicators/MenuItemFactory.qml−1
@@ -228,7 +228,6 @@ Item {
228 text: menuData && menuData.label || ""
228 text: menuData && menuData.label || ""
229 iconSource: menuData && menuData.icon || ""
229 iconSource: menuData && menuData.icon || ""
230 value : menuData && menuData.actionState || 0.0
230 value : menuData && menuData.actionState || 0.0
231− enabled: menuData && menuData.sensitive || false
232 highlightWhenPressed: false
231 highlightWhenPressed: false
233 }
232 }
234 }
233 }
qml/Panel/IndicatorsMenu.qml+1−1
@@ -37,7 +37,7 @@ Showable {
37 readonly property bool fullyClosed: unitProgress == 0
37 readonly property bool fullyClosed: unitProgress == 0
38 property bool enableHint: true
38 property bool enableHint: true
39 property bool showOnClick: true
39 property bool showOnClick: true
40− property color panelColor: theme.palette.normal.background
40+ property color panelColor: UbuntuColors.jet
41
41
42 signal showTapped(point position)
42 signal showTapped(point position)
43
43
qml/Panel/Panel.qml+5−1
@@ -89,7 +89,7 @@ Item {
89
89
90 Rectangle {
90 Rectangle {
91 id: indicatorAreaBackground
91 id: indicatorAreaBackground
92− color: callHint.visible ? UbuntuColors.green : theme.palette.normal.background
92+ color: callHint.visible ? UbuntuColors.green : indicators.panelColor
93 anchors {
93 anchors {
94 top: parent.top
94 top: parent.top
95 left: parent.left
95 left: parent.left
@@ -169,8 +169,10 @@ Item {
169 objectName: "windowDecorationTitle"
169 objectName: "windowDecorationTitle"
170 anchors {
170 anchors {
171 left: parent.left
171 left: parent.left
172+ right: __indicators.left
172 top: parent.top
173 top: parent.top
173 leftMargin: units.gu(1)
174 leftMargin: units.gu(1)
175+ rightMargin: units.gu(1)
174 topMargin: units.gu(0.5)
176 topMargin: units.gu(0.5)
175 bottomMargin: units.gu(0.5)
177 bottomMargin: units.gu(0.5)
176 }
178 }
@@ -181,6 +183,8 @@ Item {
181 fontSize: "medium"
183 fontSize: "medium"
182 font.weight: Font.Normal
184 font.weight: Font.Normal
183 text: PanelState.title
185 text: PanelState.title
186+ elide: Text.ElideRight
187+ maximumLineCount: 1
184 }
188 }
185
189
186 // TODO here would the Locally integrated menus come
190 // TODO here would the Locally integrated menus come
qml/ScopeTool.qml−8
@@ -52,14 +52,6 @@ Rectangle {
52 color: "#FCFCFC"
52 color: "#FCFCFC"
53 }
53 }
54
54
55− Image {
56− anchors.fill: dashContent
57− source: root.width > root.height ? "Dash/graphics/paper_landscape.png" : "Dash/graphics/paper_portrait.png"
58− fillMode: Image.PreserveAspectCrop
59− horizontalAlignment: Image.AlignRight
60− verticalAlignment: Image.AlignTop
61− }
62−
63 DashContent {
55 DashContent {
64 id: dashContent
56 id: dashContent
65
57
qml/Shell.qml+53−8
@@ -25,6 +25,7 @@ import Ubuntu.Telephony 0.1 as Telephony
25import Unity.Connectivity 0.1
25import Unity.Connectivity 0.1
26import Unity.Launcher 0.1
26import Unity.Launcher 0.1
27import GlobalShortcut 1.0 // has to be before Utils, because of WindowInputFilter
27import GlobalShortcut 1.0 // has to be before Utils, because of WindowInputFilter
28+import GSettings 1.0
28import Utils 0.1
29import Utils 0.1
29import Powerd 0.1
30import Powerd 0.1
30import SessionBroadcast 0.1
31import SessionBroadcast 0.1
@@ -119,9 +120,7 @@ Item {
119 if (ApplicationManager.findApplication(appId)) {
120 if (ApplicationManager.findApplication(appId)) {
120 ApplicationManager.requestFocusApplication(appId);
121 ApplicationManager.requestFocusApplication(appId);
121 } else {
122 } else {
122− var execFlags = shell.usageScenario === "phone" ? ApplicationManager.ForceMainStage
123− : ApplicationManager.NoFlag;
124− ApplicationManager.startApplication(appId, execFlags);
123+ ApplicationManager.startApplication(appId);
125 }
124 }
126 }
125 }
127
126
@@ -188,6 +187,11 @@ Item {
188 }
187 }
189 }
188 }
190
189
190+ GSettings {
191+ id: settings
192+ schema.id: "com.canonical.Unity8"
193+ }
194+
191 Item {
195 Item {
192 id: stages
196 id: stages
193 objectName: "stages"
197 objectName: "stages"
@@ -343,13 +347,22 @@ Item {
343 property: "altTabPressed"
347 property: "altTabPressed"
344 value: physicalKeysMapper.altTabPressed
348 value: physicalKeysMapper.altTabPressed
345 }
349 }
350+ Binding {
351+ target: applicationsDisplayLoader.item
352+ property: "leftMargin"
353+ value: shell.usageScenario == "desktop" && !settings.autohideLauncher ? launcher.panelWidth: 0
354+ }
346 }
355 }
347 }
356 }
348
357
349 InputMethod {
358 InputMethod {
350 id: inputMethod
359 id: inputMethod
351 objectName: "inputMethod"
360 objectName: "inputMethod"
361+ anchors {
362+ fill: parent
352− anchors { fill: parent; topMargin: panel.panelHeight }
363+ topMargin: panel.panelHeight
364+ leftMargin: launcher.lockedVisible ? launcher.panelWidth : 0
365+ }
353 z: notifications.useModal || panel.indicators.shown || wizard.active || tutorial.running ? overlay.z + 1 : overlay.z - 1
366 z: notifications.useModal || panel.indicators.shown || wizard.active || tutorial.running ? overlay.z + 1 : overlay.z - 1
354 }
367 }
355
368
@@ -503,9 +516,7 @@ Item {
503 greeterShown: greeter.shown
516 greeterShown: greeter.shown
504 }
517 }
505
518
506− readonly property bool topmostApplicationIsFullscreen:
519+ readonly property bool topmostApplicationIsFullscreen: mainApp && mainApp.fullscreen
507− ApplicationManager.focusedApplicationId &&
508− ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
509
520
510 fullscreenMode: (topmostApplicationIsFullscreen && !lightDM.greeter.active && launcher.progress == 0)
521 fullscreenMode: (topmostApplicationIsFullscreen && !lightDM.greeter.active && launcher.progress == 0)
511 || greeter.hasLockedApp
522 || greeter.hasLockedApp
@@ -527,6 +538,10 @@ Item {
527 && (!greeter.locked || AccountsService.enableLauncherWhileLocked)
538 && (!greeter.locked || AccountsService.enableLauncherWhileLocked)
528 && !greeter.hasLockedApp
539 && !greeter.hasLockedApp
529 inverted: shell.usageScenario !== "desktop"
540 inverted: shell.usageScenario !== "desktop"
541+ superPressed: physicalKeysMapper.superPressed
542+ superTabPressed: physicalKeysMapper.superTabPressed
543+ panelWidth: units.gu(settings.launcherWidth)
544+ lockedVisible: shell.usageScenario == "desktop" && !settings.autohideLauncher && !panel.fullscreenMode
530
545
531 onShowDashHome: showHome()
546 onShowDashHome: showHome()
532 onDash: showDash()
547 onDash: showDash()
@@ -544,6 +559,37 @@ Item {
544 panel.indicators.hide()
559 panel.indicators.hide()
545 }
560 }
546 }
561 }
562+ onFocusChanged: {
563+ if (!focus) {
564+ applicationsDisplayLoader.focus = true;
565+ }
566+ }
567+
568+ GlobalShortcut {
569+ shortcut: Qt.AltModifier | Qt.Key_F1
570+ onTriggered: {
571+ launcher.openForKeyboardNavigation();
572+ }
573+ }
574+ GlobalShortcut {
575+ shortcut: Qt.MetaModifier | Qt.Key_0
576+ onTriggered: {
577+ if (LauncherModel.get(9)) {
578+ activateApplication(LauncherModel.get(9).appId);
579+ }
580+ }
581+ }
582+ Repeater {
583+ model: 9
584+ GlobalShortcut {
585+ shortcut: Qt.MetaModifier | (Qt.Key_1 + index)
586+ onTriggered: {
587+ if (LauncherModel.get(index)) {
588+ activateApplication(LauncherModel.get(index).appId);
589+ }
590+ }
591+ }
592+ }
547 }
593 }
548
594
549 Tutorial {
595 Tutorial {
@@ -564,7 +610,6 @@ Item {
564 id: wizard
610 id: wizard
565 objectName: "wizard"
611 objectName: "wizard"
566 anchors.fill: parent
612 anchors.fill: parent
567− background: wallpaperResolver.background
568
613
569 function unlockWhenDoneWithWizard() {
614 function unlockWhenDoneWithWizard() {
570 if (!active) {
615 if (!active) {
qml/Stages/AbstractStage.qml+4−2
@@ -21,7 +21,7 @@ import GSettings 1.0
21Rectangle {
21Rectangle {
22 id: root
22 id: root
23
23
24− color: "#111111"
24+ color: "#060606"
25
25
26 // Controls to be set from outside
26 // Controls to be set from outside
27 property bool altTabPressed
27 property bool altTabPressed
@@ -40,10 +40,12 @@ Rectangle {
40 property int shellOrientationAngle
40 property int shellOrientationAngle
41 property bool spreadEnabled: true // If false, animations and right edge will be disabled
41 property bool spreadEnabled: true // If false, animations and right edge will be disabled
42 property bool suspended
42 property bool suspended
43+ // A Stage should paint a wallpaper etc over its full size but not use the margins for window placement
44+ property int leftMargin: 0
43
45
44 // To be read from outside
46 // To be read from outside
45 property var mainApp: null
47 property var mainApp: null
46− property int mainAppWindowOrientationAngle
48+ property int mainAppWindowOrientationAngle: 0
47 property bool orientationChangesEnabled
49 property bool orientationChangesEnabled
48 property int supportedOrientations: Qt.PortraitOrientation
50 property int supportedOrientations: Qt.PortraitOrientation
49 | Qt.LandscapeOrientation
51 | Qt.LandscapeOrientation
qml/Stages/ApplicationWindow.qml+3−1
@@ -24,12 +24,14 @@ FocusScope {
24 implicitHeight: sessionContainer.implicitHeight
24 implicitHeight: sessionContainer.implicitHeight
25
25
26 // to be read from outside
26 // to be read from outside
27− readonly property bool fullscreen: application ? application.fullscreen : false
28 property alias interactive: sessionContainer.interactive
27 property alias interactive: sessionContainer.interactive
29 property bool orientationChangesEnabled: d.supportsSurfaceResize ? d.surfaceOldEnoughToBeResized : true
28 property bool orientationChangesEnabled: d.supportsSurfaceResize ? d.surfaceOldEnoughToBeResized : true
30 readonly property string title: sessionContainer.surface && sessionContainer.surface.name !== "" ?
29 readonly property string title: sessionContainer.surface && sessionContainer.surface.name !== "" ?
31 sessionContainer.surface.name : d.name
30 sessionContainer.surface.name : d.name
32
31
32+ // overridable from outside
33+ property bool fullscreen: application ? application.fullscreen : false
34+
33 // to be set from outside
35 // to be set from outside
34 property QtObject application
36 property QtObject application
35 property int surfaceOrientationAngle
37 property int surfaceOrientationAngle
qml/Stages/DesktopSpread.qml+64−17
@@ -19,6 +19,7 @@ import QtQuick.Layouts 1.1
19import Ubuntu.Components 1.3
19import Ubuntu.Components 1.3
20import Ubuntu.Gestures 0.1
20import Ubuntu.Gestures 0.1
21import Unity.Application 0.1
21import Unity.Application 0.1
22+import "../Components"
22
23
23FocusScope {
24FocusScope {
24 id: root
25 id: root
@@ -26,8 +27,11 @@ FocusScope {
26 property bool altTabPressed: false
27 property bool altTabPressed: false
27 property Item workspace: null
28 property Item workspace: null
28
29
30+ readonly property alias ready: blurLayer.ready
29 readonly property alias highlightedIndex: spreadRepeater.highlightedIndex
31 readonly property alias highlightedIndex: spreadRepeater.highlightedIndex
30
32
33+ signal playFocusAnimation(int index)
34+
31 function show() {
35 function show() {
32 spreadContainer.animateIn = true;
36 spreadContainer.animateIn = true;
33 root.state = "altTab";
37 root.state = "altTab";
@@ -91,6 +95,9 @@ FocusScope {
91
95
92 function focusSelected() {
96 function focusSelected() {
93 if (spreadRepeater.highlightedIndex != -1) {
97 if (spreadRepeater.highlightedIndex != -1) {
98+ if (spreadContainer.visible) {
99+ root.playFocusAnimation(spreadRepeater.highlightedIndex)
100+ }
94 var application = ApplicationManager.get(spreadRepeater.highlightedIndex);
101 var application = ApplicationManager.get(spreadRepeater.highlightedIndex);
95 ApplicationManager.requestFocusApplication(application.appId);
102 ApplicationManager.requestFocusApplication(application.appId);
96 }
103 }
@@ -101,6 +108,31 @@ FocusScope {
101 state = ""
108 state = ""
102 }
109 }
103
110
111+ BlurLayer {
112+ id: blurLayer
113+ anchors.fill: parent
114+ source: root.workspace
115+ visible: false
116+ }
117+
118+ Rectangle {
119+ id: spreadBackground
120+ anchors.fill: parent
121+ color: "#B2000000"
122+ visible: false
123+ opacity: visible ? 1 : 0
124+ Behavior on opacity {
125+ UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration }
126+ }
127+ }
128+
129+ MouseArea {
130+ id: eventEater
131+ anchors.fill: parent
132+ visible: spreadBackground.visible
133+ enabled: visible
134+ }
135+
104 Item {
136 Item {
105 id: spreadContainer
137 id: spreadContainer
106 objectName: "spreadContainer"
138 objectName: "spreadContainer"
@@ -226,13 +258,20 @@ FocusScope {
226 Transition {
258 Transition {
227 from: ""
259 from: ""
228 to: "altTab"
260 to: "altTab"
261+ SequentialAnimation {
262+ ParallelAnimation {
229− PropertyAction { target: spreadDelegate; properties: "y,height,width,angle,z,itemScale,itemScaleOriginY,visible" }
263+ PropertyAction { target: spreadDelegate; properties: "y,height,width,angle,z,itemScale,itemScaleOriginY,visible" }
230− PropertyAction { target: clippedSpreadDelegate; properties: "anchors.topMargin" }
264+ PropertyAction { target: clippedSpreadDelegate; properties: "anchors.topMargin" }
231− PropertyAnimation {
265+ PropertyAnimation {
232− target: spreadDelegate; properties: "x"
266+ target: spreadDelegate; properties: "x"
233− from: root.width
267+ from: root.width
234− duration: spreadContainer.animateIn ? UbuntuAnimation.FastDuration :0
268+ duration: spreadContainer.animateIn ? UbuntuAnimation.FastDuration :0
235− easing: UbuntuAnimation.StandardEasing
269+ easing: UbuntuAnimation.StandardEasing
270+ }
271+ UbuntuNumberAnimation { target: clippedSpreadDelegate; property: "shadowOpacity"; from: 0; to: spreadMaths.shadowOpacity; duration: spreadContainer.animateIn ? UbuntuAnimation.FastDuration : 0 }
272+ UbuntuNumberAnimation { target: tileInfo; property: "opacity"; from: 0; to: spreadMaths.tileInfoOpacity; duration: spreadContainer.animateIn ? UbuntuAnimation.FastDuration : 0 }
273+ }
274+ PropertyAction { target: spreadSelectArea; property: "enabled" }
236 }
275 }
237 }
276 }
238 ]
277 ]
@@ -240,7 +279,11 @@ FocusScope {
240 MouseArea {
279 MouseArea {
241 id: tileInfo
280 id: tileInfo
242 objectName: "tileInfo"
281 objectName: "tileInfo"
282+ anchors {
283+ left: parent.left
243− anchors { left: parent.left; top: clippedSpreadDelegate.bottom; topMargin: units.gu(5) }
284+ top: clippedSpreadDelegate.bottom
285+ topMargin: ((spreadMaths.sceneHeight - spreadDelegate.y) - clippedSpreadDelegate.height) * 0.2
286+ }
244 property int nextItemX: spreadRepeater.count > index + 1 ? spreadRepeater.itemAt(index + 1).x : spreadDelegate.x + units.gu(30)
287 property int nextItemX: spreadRepeater.count > index + 1 ? spreadRepeater.itemAt(index + 1).x : spreadDelegate.x + units.gu(30)
245 width: Math.min(units.gu(30), nextItemX - spreadDelegate.x)
288 width: Math.min(units.gu(30), nextItemX - spreadDelegate.x)
246 height: titleInfoColumn.height
289 height: titleInfoColumn.height
@@ -262,12 +305,20 @@ FocusScope {
262 anchors { left: parent.left; top: parent.top; right: parent.right }
305 anchors { left: parent.left; top: parent.top; right: parent.right }
263 spacing: units.gu(1)
306 spacing: units.gu(1)
264
307
265− UbuntuShape {
308+ UbuntuShapeForItem {
266 Layout.preferredHeight: Math.min(units.gu(6), root.height * .05)
309 Layout.preferredHeight: Math.min(units.gu(6), root.height * .05)
267 Layout.preferredWidth: height * 8 / 7.6
310 Layout.preferredWidth: height * 8 / 7.6
268 image: Image {
311 image: Image {
269 anchors.fill: parent
312 anchors.fill: parent
270 source: model.icon
313 source: model.icon
314+ Rectangle {
315+ anchors.fill: parent
316+ color: "black"
317+ opacity: clippedSpreadDelegate.highlightShown ? 0 : .1
318+ Behavior on opacity {
319+ UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration }
320+ }
321+ }
271 }
322 }
272 }
323 }
273 Label {
324 Label {
@@ -420,7 +471,7 @@ FocusScope {
420
471
421 property var source: ShaderEffectSource {
472 property var source: ShaderEffectSource {
422 id: shaderEffectSource
473 id: shaderEffectSource
423− sourceItem: appContainer
474+ sourceItem: root.workspace
424 }
475 }
425
476
426 fragmentShader: "
477 fragmentShader: "
@@ -494,20 +545,17 @@ FocusScope {
494 from: "*"
545 from: "*"
495 to: "altTab"
546 to: "altTab"
496 SequentialAnimation {
547 SequentialAnimation {
497− PropertyAction { target: hoverMouseArea; property: "progressiveScrollingEnabled"; value: false }
498 PropertyAction { target: spreadRepeater; property: "highlightedIndex"; value: Math.min(ApplicationManager.count - 1, 1) }
548 PropertyAction { target: spreadRepeater; property: "highlightedIndex"; value: Math.min(ApplicationManager.count - 1, 1) }
499− PauseAnimation { duration: 140 }
549+ PauseAnimation { duration: spreadContainer.animateIn ? 0 : 140 }
500 PropertyAction { target: workspaceSelector; property: "visible" }
550 PropertyAction { target: workspaceSelector; property: "visible" }
501 PropertyAction { target: spreadContainer; property: "visible" }
551 PropertyAction { target: spreadContainer; property: "visible" }
502 ParallelAnimation {
552 ParallelAnimation {
503− UbuntuNumberAnimation {
504− target: blurLayer; properties: "saturation,blurRadius";
553+ UbuntuNumberAnimation { target: blurLayer; properties: "saturation,blurRadius"; duration: UbuntuAnimation.SnapDuration }
505− duration: spreadContainer.animateIn ? UbuntuAnimation.FastDuration : 0
506− }
507 PropertyAction { target: spreadFlickable; property: "visible" }
554 PropertyAction { target: spreadFlickable; property: "visible" }
508 PropertyAction { targets: [currentSelectedLabel,spreadBackground]; property: "visible" }
555 PropertyAction { targets: [currentSelectedLabel,spreadBackground]; property: "visible" }
509 PropertyAction { target: spreadFlickable; property: "contentX"; value: 0 }
556 PropertyAction { target: spreadFlickable; property: "contentX"; value: 0 }
510 }
557 }
558+ PropertyAction { target: hoverMouseArea; properties: "enabled,progressiveScrollingEnabled"; value: false }
511 }
559 }
512 },
560 },
513 Transition {
561 Transition {
@@ -518,6 +566,5 @@ FocusScope {
518 PropertyAction { target: spreadRepeater; property: "highlightedIndex"; value: -1 }
566 PropertyAction { target: spreadRepeater; property: "highlightedIndex"; value: -1 }
519 PropertyAction { target: spreadContainer; property: "animateIn"; value: false }
567 PropertyAction { target: spreadContainer; property: "animateIn"; value: false }
520 }
568 }
521−
522 ]
569 ]
523}
570}
qml/Stages/DesktopSpreadDelegate.qml+15−1
@@ -56,6 +56,11 @@ Item {
56 }
56 }
57 ]
57 ]
58
58
59+ scale: highlightShown ? 1.025 : 1
60+ Behavior on scale {
61+ UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration }
62+ }
63+
59 BorderImage {
64 BorderImage {
60 anchors {
65 anchors {
61 fill: root
66 fill: root
@@ -70,7 +75,7 @@ Item {
70 anchors.fill: parent
75 anchors.fill: parent
71 anchors.margins: -units.gu(1)
76 anchors.margins: -units.gu(1)
72 color: "white"
77 color: "white"
73− opacity: highlightShown ? 0.15 : 0
78+ opacity: highlightShown ? 0.55 : 0
74 antialiasing: true
79 antialiasing: true
75 }
80 }
76
81
@@ -108,4 +113,13 @@ Item {
108 ]
113 ]
109 }
114 }
110 }
115 }
116+
117+ Rectangle {
118+ anchors.fill: parent
119+ color: "black"
120+ opacity: root.highlightShown ? 0 : .1
121+ Behavior on opacity {
122+ UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration }
123+ }
124+ }
111}
125}
qml/Stages/DesktopStage.qml+34−34
@@ -224,6 +224,7 @@ AbstractStage {
224 PanelState.dropShadow = false;
224 PanelState.dropShadow = false;
225 }
225 }
226
226
227+
227 FocusScope {
228 FocusScope {
228 id: appContainer
229 id: appContainer
229 objectName: "appContainer"
230 objectName: "appContainer"
@@ -274,6 +275,7 @@ AbstractStage {
274 readonly property alias maximizedLeft: appDelegatePrivate.maximizedLeft
275 readonly property alias maximizedLeft: appDelegatePrivate.maximizedLeft
275 readonly property alias maximizedRight: appDelegatePrivate.maximizedRight
276 readonly property alias maximizedRight: appDelegatePrivate.maximizedRight
276 readonly property alias minimized: appDelegatePrivate.minimized
277 readonly property alias minimized: appDelegatePrivate.minimized
278+ readonly property alias fullscreen: decoratedWindow.fullscreen
277
279
278 readonly property string appId: model.appId
280 readonly property string appId: model.appId
279 property bool animationsEnabled: true
281 property bool animationsEnabled: true
@@ -293,6 +295,7 @@ AbstractStage {
293 visible: !visuallyMinimized &&
295 visible: !visuallyMinimized &&
294 !greeter.fullyShown &&
296 !greeter.fullyShown &&
295 (priv.foregroundMaximizedAppZ === -1 || priv.foregroundMaximizedAppZ <= z) ||
297 (priv.foregroundMaximizedAppZ === -1 || priv.foregroundMaximizedAppZ <= z) ||
298+ decoratedWindow.fullscreen ||
296 (spread.state == "altTab" && index === spread.highlightedIndex)
299 (spread.state == "altTab" && index === spread.highlightedIndex)
297
300
298 Binding {
301 Binding {
@@ -348,13 +351,26 @@ AbstractStage {
348 ApplicationManager.focusApplication(appId);
351 ApplicationManager.focusApplication(appId);
349 }
352 }
350
353
354+ function playFocusAnimation() {
355+ focusAnimation.start()
356+ }
357+
358+ UbuntuNumberAnimation {
359+ id: focusAnimation
360+ target: appDelegate
361+ property: "scale"
362+ from: 0.98
363+ to: 1
364+ duration: UbuntuAnimation.SnapDuration
365+ }
366+
351 states: [
367 states: [
352 State {
368 State {
353 name: "fullscreen"; when: decoratedWindow.fullscreen
369 name: "fullscreen"; when: decoratedWindow.fullscreen
354− extend: "maximized"
355 PropertyChanges {
370 PropertyChanges {
356 target: appDelegate;
371 target: appDelegate;
357− y: -PanelState.panelHeight
372+ x: 0; y: -PanelState.panelHeight
373+ requestedWidth: appContainer.width; requestedHeight: appContainer.height;
358 }
374 }
359 },
375 },
360 State {
376 State {
@@ -371,21 +387,21 @@ AbstractStage {
371 name: "maximized"; when: appDelegate.maximized && !appDelegate.minimized
387 name: "maximized"; when: appDelegate.maximized && !appDelegate.minimized
372 PropertyChanges {
388 PropertyChanges {
373 target: appDelegate;
389 target: appDelegate;
374− x: 0; y: 0;
390+ x: root.leftMargin; y: 0;
375− requestedWidth: root.width; requestedHeight: root.height;
391+ requestedWidth: appContainer.width - root.leftMargin; requestedHeight: appContainer.height;
376 visuallyMinimized: false;
392 visuallyMinimized: false;
377 visuallyMaximized: true
393 visuallyMaximized: true
378 }
394 }
379 },
395 },
380 State {
396 State {
381 name: "maximizedLeft"; when: appDelegate.maximizedLeft && !appDelegate.minimized
397 name: "maximizedLeft"; when: appDelegate.maximizedLeft && !appDelegate.minimized
382− PropertyChanges { target: appDelegate; x: 0; y: PanelState.panelHeight;
398+ PropertyChanges { target: appDelegate; x: root.leftMargin; y: PanelState.panelHeight;
383− requestedWidth: root.width/2; requestedHeight: root.height - PanelState.panelHeight }
399+ requestedWidth: (appContainer.width - root.leftMargin)/2; requestedHeight: appContainer.height - PanelState.panelHeight }
384 },
400 },
385 State {
401 State {
386 name: "maximizedRight"; when: appDelegate.maximizedRight && !appDelegate.minimized
402 name: "maximizedRight"; when: appDelegate.maximizedRight && !appDelegate.minimized
387− PropertyChanges { target: appDelegate; x: root.width/2; y: PanelState.panelHeight;
403+ PropertyChanges { target: appDelegate; x: (appContainer.width + root.leftMargin)/2; y: PanelState.panelHeight;
388− requestedWidth: root.width/2; requestedHeight: root.height - PanelState.panelHeight }
404+ requestedWidth: (appContainer.width - root.leftMargin)/2; requestedHeight: appContainer.height - PanelState.panelHeight }
389 },
405 },
390 State {
406 State {
391 name: "minimized"; when: appDelegate.minimized
407 name: "minimized"; when: appDelegate.minimized
@@ -438,7 +454,7 @@ AbstractStage {
438 target: appDelegate
454 target: appDelegate
439 property: "z"
455 property: "z"
440 value: ApplicationManager.count + 1
456 value: ApplicationManager.count + 1
441− when: index == spread.highlightedIndex && blurLayer.ready
457+ when: index == spread.highlightedIndex && spread.ready
442 }
458 }
443
459
444 WindowResizeArea {
460 WindowResizeArea {
@@ -448,8 +464,9 @@ AbstractStage {
448 minHeight: units.gu(10)
464 minHeight: units.gu(10)
449 borderThickness: units.gu(2)
465 borderThickness: units.gu(2)
450 windowId: model.appId // FIXME: Change this to point to windowId once we have such a thing
466 windowId: model.appId // FIXME: Change this to point to windowId once we have such a thing
451− screenWidth: root.width
467+ screenWidth: appContainer.width
452− screenHeight: root.height
468+ screenHeight: appContainer.height
469+ leftMargin: root.leftMargin
453
470
454 onPressed: { ApplicationManager.focusApplication(model.appId) }
471 onPressed: { ApplicationManager.focusApplication(model.appId) }
455 }
472 }
@@ -473,27 +490,6 @@ AbstractStage {
473 }
490 }
474 }
491 }
475
492
476− BlurLayer {
477− id: blurLayer
478− anchors.fill: parent
479− source: appContainer
480− visible: false
481− }
482−
483− Rectangle {
484− id: spreadBackground
485− anchors.fill: parent
486− color: "#55000000"
487− visible: false
488− }
489−
490− MouseArea {
491− id: eventEater
492− anchors.fill: parent
493− visible: spreadBackground.visible
494− enabled: visible
495− }
496−
497 EdgeBarrier {
493 EdgeBarrier {
498 id: edgeBarrier
494 id: edgeBarrier
499
495
@@ -509,7 +505,7 @@ AbstractStage {
509 rotation: 90
505 rotation: 90
510 anchors.centerIn: parent
506 anchors.centerIn: parent
511 gradient: Gradient {
507 gradient: Gradient {
512− GradientStop { position: 0.0; color: Qt.rgba(0.16,0.16,0.16,0.7)}
508+ GradientStop { position: 0.0; color: Qt.rgba(0.16,0.16,0.16,0.5)}
513 GradientStop { position: 1.0; color: Qt.rgba(0.16,0.16,0.16,0)}
509 GradientStop { position: 1.0; color: Qt.rgba(0.16,0.16,0.16,0)}
514 }
510 }
515 }
511 }
@@ -527,9 +523,13 @@ AbstractStage {
527 DesktopSpread {
523 DesktopSpread {
528 id: spread
524 id: spread
529 objectName: "spread"
525 objectName: "spread"
530− anchors.fill: parent
526+ anchors.fill: appContainer
531 workspace: appContainer
527 workspace: appContainer
532 focus: state == "altTab"
528 focus: state == "altTab"
533 altTabPressed: root.altTabPressed
529 altTabPressed: root.altTabPressed
530+
531+ onPlayFocusAnimation: {
532+ appRepeater.itemAt(index).playFocusAnimation();
533+ }
534 }
534 }
535}
535}
qml/Stages/PhoneStage.qml+77−3
@@ -44,6 +44,33 @@ AbstractStage {
44 }
44 }
45 }
45 }
46
46
47+ onAltTabPressedChanged: {
48+ if (!spreadEnabled || !altTabEnabled) {
49+ return;
50+ }
51+ if (altTabPressed) {
52+ spreadView.snapToSpread();
53+ priv.highlightIndex = Math.min(spreadRepeater.count - 1, 1);
54+ } else {
55+ spreadView.snapTo(priv.highlightIndex)
56+ }
57+ }
58+
59+ FocusScope {
60+ focus: root.altTabPressed
61+
62+ Keys.onPressed: {
63+ switch (event.key) {
64+ case Qt.Key_Tab:
65+ priv.highlightIndex = (priv.highlightIndex + 1) % spreadRepeater.count
66+ break;
67+ case Qt.Key_Backtab:
68+ priv.highlightIndex = (priv.highlightIndex + spreadRepeater.count - 1) % spreadRepeater.count
69+ break;
70+ }
71+ }
72+ }
73+
47 // Functions to be called from outside
74 // Functions to be called from outside
48 function updateFocusedAppOrientation() {
75 function updateFocusedAppOrientation() {
49 if (spreadRepeater.count > 0) {
76 if (spreadRepeater.count > 0) {
@@ -74,6 +101,12 @@ AbstractStage {
74 }
101 }
75 }
102 }
76
103
104+ function pushRightEdge(amount) {
105+ if (spreadView.contentX == -spreadView.shift) {
106+ edgeBarrier.push(amount);
107+ }
108+ }
109+
77 mainApp: applicationManager.focusedApplicationId
110 mainApp: applicationManager.focusedApplicationId
78 ? applicationManager.findApplication(applicationManager.focusedApplicationId)
111 ? applicationManager.findApplication(applicationManager.focusedApplicationId)
79 : null
112 : null
@@ -177,6 +210,7 @@ AbstractStage {
177
210
178 property real oldInverseProgress: 0
211 property real oldInverseProgress: 0
179 property bool animateX: false
212 property bool animateX: false
213+ property int highlightIndex: 0
180
214
181 onFocusedAppDelegateChanged: {
215 onFocusedAppDelegateChanged: {
182 if (focusedAppDelegate) {
216 if (focusedAppDelegate) {
@@ -211,6 +245,10 @@ AbstractStage {
211 spreadView.phase = 0;
245 spreadView.phase = 0;
212 spreadView.contentX = -spreadView.shift;
246 spreadView.contentX = -spreadView.shift;
213 }
247 }
248+
249+ onHighlightIndexChanged: {
250+ spreadView.contentX = highlightIndex * spreadView.contentWidth / (spreadRepeater.count + 2)
251+ }
214 }
252 }
215 Timer {
253 Timer {
216 id: fullyShowingFocusedAppUpdateTimer
254 id: fullyShowingFocusedAppUpdateTimer
@@ -278,6 +316,11 @@ AbstractStage {
278 }
316 }
279 }
317 }
280
318
319+ Behavior on contentX {
320+ enabled: root.altTabPressed
321+ UbuntuNumberAnimation {}
322+ }
323+
281 onShiftedContentXChanged: {
324 onShiftedContentXChanged: {
282 if (root.beingResized) {
325 if (root.beingResized) {
283 // Flickabe.contentX wiggles during resizes. Don't react to it.
326 // Flickabe.contentX wiggles during resizes. Don't react to it.
@@ -316,12 +359,17 @@ AbstractStage {
316 } else if (shiftedContentX < positionMarker3 * width) {
359 } else if (shiftedContentX < positionMarker3 * width) {
317 snapTo(1);
360 snapTo(1);
318 } else if (phase < 2){
361 } else if (phase < 2){
319− // Add 1 pixel to make sure we definitely hit positionMarker4 even with rounding errors of the animation.
362+ snapToSpread();
320− snapAnimation.targetContentX = width * positionMarker4 + 1 - shift;
321− snapAnimation.start();
322 root.opened();
363 root.opened();
323 }
364 }
324 }
365 }
366+
367+ function snapToSpread() {
368+ // Add 1 pixel to make sure we definitely hit positionMarker4 even with rounding errors of the animation.
369+ snapAnimation.targetContentX = (root.width * spreadView.positionMarker4) + 1 - spreadView.shift;
370+ snapAnimation.start();
371+ }
372+
325 function snapTo(index) {
373 function snapTo(index) {
326 if (!root.altTabEnabled) {
374 if (!root.altTabEnabled) {
327 // Reset to start instead
375 // Reset to start instead
@@ -424,6 +472,7 @@ AbstractStage {
424 maximizedAppTopMargin: root.maximizedAppTopMargin
472 maximizedAppTopMargin: root.maximizedAppTopMargin
425 dropShadow: spreadView.active || priv.focusedAppDelegateIsDislocated
473 dropShadow: spreadView.active || priv.focusedAppDelegateIsDislocated
426 focusFirstApp: root.focusFirstApp
474 focusFirstApp: root.focusFirstApp
475+ highlightShown: root.altTabPressed && index === priv.highlightIndex
427
476
428 readonly property bool isDash: model.appId == "unity8-dash"
477 readonly property bool isDash: model.appId == "unity8-dash"
429
478
@@ -648,4 +697,29 @@ AbstractStage {
648 }
697 }
649 }
698 }
650 }
699 }
700+
701+ EdgeBarrier {
702+ id: edgeBarrier
703+
704+ // NB: it does its own positioning according to the specified edge
705+ edge: Qt.RightEdge
706+
707+ onPassed: {
708+ spreadView.snapToSpread();
709+ }
710+ material: Component {
711+ Item {
712+ Rectangle {
713+ width: parent.height
714+ height: parent.width
715+ rotation: 90
716+ anchors.centerIn: parent
717+ gradient: Gradient {
718+ GradientStop { position: 0.0; color: Qt.rgba(0.16,0.16,0.16,0.7)}
719+ GradientStop { position: 1.0; color: Qt.rgba(0.16,0.16,0.16,0)}
720+ }
721+ }
722+ }
723+ }
724+ }
651}
725}
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+30−1
@@ -32,6 +32,11 @@ FocusScope {
32 readonly property alias appWindowOrientationAngle: appWindowWithShadow.orientationAngle
32 readonly property alias appWindowOrientationAngle: appWindowWithShadow.orientationAngle
33 readonly property alias appWindowRotation: appWindowWithShadow.rotation
33 readonly property alias appWindowRotation: appWindowWithShadow.rotation
34 readonly property alias orientationChangesEnabled: appWindow.orientationChangesEnabled
34 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
35
40
36 // to be set from outside
41 // to be set from outside
37 property bool interactive: true
42 property bool interactive: true
@@ -43,6 +48,10 @@ FocusScope {
43 property int shellOrientationAngle
48 property int shellOrientationAngle
44 property int shellOrientation
49 property int shellOrientation
45 property QtObject orientations
50 property QtObject orientations
51+ property bool highlightShown: false
52+
53+ // overrideable from outside
54+ property alias fullscreen: appWindow.fullscreen
46
55
47 function matchShellOrientation() {
56 function matchShellOrientation() {
48 if (!root.application)
57 if (!root.application)
@@ -153,7 +162,8 @@ FocusScope {
153 if (!root.application || root.application.rotatesWindowContents) {
162 if (!root.application || root.application.rotatesWindowContents) {
154 return 0;
163 return 0;
155 }
164 }
165+
156− var supportedOrientations = root.application.supportedOrientations;
166+ var supportedOrientations = root.supportedOrientations;
157
167
158 if (supportedOrientations === Qt.PrimaryOrientation) {
168 if (supportedOrientations === Qt.PrimaryOrientation) {
159 supportedOrientations = root.orientations.primary;
169 supportedOrientations = root.orientations.primary;
@@ -261,6 +271,25 @@ FocusScope {
261 Behavior on opacity { UbuntuNumberAnimation {} }
271 Behavior on opacity { UbuntuNumberAnimation {} }
262 }
272 }
263
273
274+ Rectangle {
275+ id: selectionHighlight
276+ objectName: "selectionHighlight"
277+ anchors.fill: appWindow
278+ anchors.margins: -units.gu(1)
279+ color: "white"
280+ opacity: root.highlightShown ? 0.15 : 0
281+ antialiasing: true
282+ visible: opacity > 0
283+ }
284+
285+ Rectangle {
286+ anchors { left: selectionHighlight.left; right: selectionHighlight.right; bottom: selectionHighlight.bottom; }
287+ height: units.dp(2)
288+ color: UbuntuColors.orange
289+ visible: root.highlightShown
290+ antialiasing: true
291+ }
292+
264 ApplicationWindow {
293 ApplicationWindow {
265 id: appWindow
294 id: appWindow
266 objectName: application ? "appWindow_" + application.appId : "appWindow_null"
295 objectName: application ? "appWindow_" + application.appId : "appWindow_null"
qml/Stages/SurfaceContainer.qml+1
@@ -118,6 +118,7 @@ FocusScope {
118
118
119
119
120 TouchGate {
120 TouchGate {
121+ objectName: "touchGate-"+name
121 targetItem: surfaceItem
122 targetItem: surfaceItem
122 anchors.fill: root
123 anchors.fill: root
123 enabled: surfaceItem.enabled
124 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+469−173
@@ -49,7 +49,7 @@ AbstractStage {
49 if (delta < 0) { delta += 360; }
49 if (delta < 0) { delta += 360; }
50 delta = delta % 360;
50 delta = delta % 360;
51
51
52− var supportedOrientations = spreadDelegate.application.supportedOrientations;
52+ var supportedOrientations = spreadDelegate.supportedOrientations;
53 if (supportedOrientations === Qt.PrimaryOrientation) {
53 if (supportedOrientations === Qt.PrimaryOrientation) {
54 supportedOrientations = spreadDelegate.orientations.primary;
54 supportedOrientations = spreadDelegate.orientations.primary;
55 }
55 }
@@ -73,11 +73,32 @@ AbstractStage {
73 }
73 }
74 }
74 }
75
75
76+ function pushRightEdge(amount) {
77+ if (spreadView.contentX == -spreadView.shift) {
78+ edgeBarrier.push(amount);
79+ }
80+ }
81+
76 orientationChangesEnabled: priv.mainAppOrientationChangesEnabled
82 orientationChangesEnabled: priv.mainAppOrientationChangesEnabled
77
83
84+ supportedOrientations: {
85+ if (mainApp) {
78− supportedOrientations: mainApp ? mainApp.supportedOrientations
86+ var orientations = mainApp.supportedOrientations;
87+ orientations |= Qt.LandscapeOrientation | Qt.InvertedLandscapeOrientation;
88+ if (priv.sideStageAppId && !spreadView.surfaceDragging) {
89+ // If we have a sidestage app, support Portrait orientation
90+ // so that it will switch the sidestage app to mainstage on rotate
79− : (Qt.PortraitOrientation | Qt.LandscapeOrientation
91+ orientations |= Qt.PortraitOrientation|Qt.InvertedPortraitOrientation;
92+ }
93+ return orientations;
94+ } else {
95+ // we just don't care
96+ return Qt.PortraitOrientation |
97+ Qt.LandscapeOrientation |
98+ Qt.InvertedPortraitOrientation |
80− | Qt.InvertedPortraitOrientation | Qt.InvertedLandscapeOrientation)
99+ Qt.InvertedLandscapeOrientation;
100+ }
101+ }
81
102
82 // How far left the stage has been dragged, used externally by tutorial code
103 // How far left the stage has been dragged, used externally by tutorial code
83 dragProgress: spreadRepeater.count > 0 ? spreadRepeater.itemAt(0).animatedProgress : 0
104 dragProgress: spreadRepeater.count > 0 ? spreadRepeater.itemAt(0).animatedProgress : 0
@@ -88,13 +109,6 @@ AbstractStage {
88 spreadView.contentX = -spreadView.shift;
109 spreadView.contentX = -spreadView.shift;
89 }
110 }
90
111
91− onShellOrientationChanged: {
92− if (shellOrientation == Qt.PortraitOrientation || shellOrientation == Qt.InvertedPortraitOrientation) {
93− ApplicationManager.focusApplication(priv.mainStageAppId);
94− priv.sideStageAppId = "";
95− }
96− }
97−
98 onInverseProgressChanged: {
112 onInverseProgressChanged: {
99 // This can't be a simple binding because that would be triggered after this handler
113 // This can't be a simple binding because that would be triggered after this handler
100 // while we need it active before doing the anition left/right
114 // while we need it active before doing the anition left/right
@@ -108,6 +122,38 @@ AbstractStage {
108 priv.oldInverseProgress = inverseProgress;
122 priv.oldInverseProgress = inverseProgress;
109 }
123 }
110
124
125+ onAltTabPressedChanged: {
126+ if (!spreadEnabled) {
127+ return;
128+ }
129+ if (altTabPressed) {
130+ priv.highlightIndex = Math.min(spreadRepeater.count - 1, 1);
131+ spreadView.snapToSpread();
132+ } else {
133+ for (var i = 0; i < spreadRepeater.count; i++) {
134+ if (spreadRepeater.itemAt(i).zIndex === priv.highlightIndex) {
135+ spreadView.snapTo(i);
136+ return;
137+ }
138+ }
139+ }
140+ }
141+
142+ FocusScope {
143+ focus: root.altTabPressed
144+
145+ Keys.onPressed: {
146+ switch (event.key) {
147+ case Qt.Key_Tab:
148+ priv.highlightIndex = (priv.highlightIndex + 1) % spreadRepeater.count
149+ break;
150+ case Qt.Key_Backtab:
151+ priv.highlightIndex = (priv.highlightIndex + spreadRepeater.count - 1) % spreadRepeater.count
152+ break;
153+ }
154+ }
155+ }
156+
111 QtObject {
157 QtObject {
112 id: priv
158 id: priv
113 objectName: "stagesPriv"
159 objectName: "stagesPriv"
@@ -136,20 +182,9 @@ AbstractStage {
136
182
137 property int oldInverseProgress: 0
183 property int oldInverseProgress: 0
138
184
139− onFocusedAppIdChanged: {
185+ property int highlightIndex: 0
140− if (priv.focusedAppId.length > 0) {
141− var focusedApp = ApplicationManager.findApplication(focusedAppId);
142− if (focusedApp.stage == ApplicationInfoInterface.SideStage) {
143− priv.sideStageAppId = focusedAppId;
144− } else {
145− priv.mainStageAppId = focusedAppId;
146− root.mainApp = focusedApp;
147− }
148− }
149
186
150− appId0 = ApplicationManager.count >= 1 ? ApplicationManager.get(0).appId : "";
187+ onFocusedAppIdChanged: updateStageApps()
151− appId1 = ApplicationManager.count > 1 ? ApplicationManager.get(1).appId : "";
152− }
153
188
154 onFocusedAppDelegateChanged: {
189 onFocusedAppDelegateChanged: {
155 if (focusedAppDelegate) {
190 if (focusedAppDelegate) {
@@ -191,6 +226,50 @@ AbstractStage {
191 }
226 }
192 return oneWayFlick;
227 return oneWayFlick;
193 }
228 }
229+
230+ onHighlightIndexChanged: {
231+ spreadView.contentX = highlightIndex * spreadView.contentWidth / (spreadRepeater.count + 2)
232+ }
233+
234+ function getTopApp(stage) {
235+ for (var i = 0; i < ApplicationManager.count; i++) {
236+ var app = ApplicationManager.get(i)
237+ if (app.stage === stage) {
238+ return app;
239+ }
240+ }
241+ return null;
242+ }
243+
244+ function setAppStage(appId, stage, save) {
245+ var app = ApplicationManager.findApplication(appId);
246+ if (app) {
247+ app.stage = stage;
248+ if (save) {
249+ WindowStateStorage.saveStage(appId, stage);
250+ }
251+ }
252+ }
253+
254+ function updateStageApps() {
255+ var app = priv.getTopApp(ApplicationInfoInterface.MainStage);
256+ priv.mainStageAppId = app ? app.appId : ""
257+ root.mainApp = app;
258+
259+ if (sideStage.shown) {
260+ app = priv.getTopApp(ApplicationInfoInterface.SideStage);
261+ priv.sideStageAppId = app ? app.appId : ""
262+ } else {
263+ priv.sideStageAppId = "";
264+ }
265+
266+ appId0 = ApplicationManager.count >= 1 ? ApplicationManager.get(0).appId : "";
267+ appId1 = ApplicationManager.count > 1 ? ApplicationManager.get(1).appId : "";
268+ }
269+
270+ readonly property bool sideStageEnabled: root.shellOrientation == Qt.LandscapeOrientation ||
271+ root.shellOrientation == Qt.InvertedLandscapeOrientation
272+ Component.onCompleted: updateStageApps();
194 }
273 }
195
274
196 Connections {
275 Connections {
@@ -218,7 +297,8 @@ AbstractStage {
218 ApplicationManager.focusApplication("unity8-dash")
297 ApplicationManager.focusApplication("unity8-dash")
219 }
298 }
220 if (priv.sideStageAppId == appId) {
299 if (priv.sideStageAppId == appId) {
300+ var app = priv.getTopApp(ApplicationInfoInterface.SideStage);
221− priv.sideStageAppId = "";
301+ priv.sideStageAppId = app === null ? "" : app.appId;
222 }
302 }
223
303
224 if (ApplicationManager.count == 0) {
304 if (ApplicationManager.count == 0) {
@@ -229,6 +309,7 @@ AbstractStage {
229 // lets make sure the spread doesn't mess up by the changing app list.
309 // lets make sure the spread doesn't mess up by the changing app list.
230 spreadView.phase = 0;
310 spreadView.phase = 0;
231 spreadView.contentX = -spreadView.shift;
311 spreadView.contentX = -spreadView.shift;
312+
232 ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
313 ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
233 }
314 }
234 }
315 }
@@ -282,6 +363,7 @@ AbstractStage {
282 property int selectedIndex: -1
363 property int selectedIndex: -1
283 property int draggedDelegateCount: 0
364 property int draggedDelegateCount: 0
284 property int closingIndex: -1
365 property int closingIndex: -1
366+ property var selectedApplication: selectedIndex !== -1 ? ApplicationManager.get(selectedIndex) : null
285
367
286 // FIXME: Workaround Flickable's not keepping its contentX still when resized
368 // FIXME: Workaround Flickable's not keepping its contentX still when resized
287 onContentXChanged: { forceItToRemainStillIfBeingResized(); }
369 onContentXChanged: { forceItToRemainStillIfBeingResized(); }
@@ -303,15 +385,8 @@ AbstractStage {
303 }
385 }
304 }
386 }
305
387
306− property bool sideStageDragging: sideStageDragHandle.dragging
307− property real sideStageDragProgress: sideStageDragHandle.progress
388+ property real sideStageDragProgress: sideStage.progress
308−
389+ property bool surfaceDragging: triGestureArea.recognisedDrag
309− onSideStageDragProgressChanged: {
310− if (sideStageDragProgress == 1) {
311− ApplicationManager.focusApplication(priv.mainStageAppId);
312− priv.sideStageAppId = "";
313− }
314− }
315
390
316 // In case the ApplicationManager already holds an app when starting up we're missing animations
391 // In case the ApplicationManager already holds an app when starting up we're missing animations
317 // Make sure we end up in the same state
392 // Make sure we end up in the same state
@@ -362,7 +437,7 @@ AbstractStage {
362 }
437 }
363 ]
438 ]
364 state: {
439 state: {
365− if (priv.mainStageAppId && !priv.sideStageAppId) {
440+ if ((priv.mainStageAppId && !priv.sideStageAppId) || !priv.sideStageEnabled) {
366 return "main";
441 return "main";
367 }
442 }
368 if (!priv.mainStageAppId && priv.sideStageAppId) {
443 if (!priv.mainStageAppId && priv.sideStageAppId) {
@@ -379,12 +454,26 @@ AbstractStage {
379 // Flickabe.contentX wiggles during resizes. Don't react to it.
454 // Flickabe.contentX wiggles during resizes. Don't react to it.
380 return;
455 return;
381 }
456 }
457+
458+ switch (phase) {
459+ case 0:
460+ // the "spreadEnabled" part is because when code does "phase = 0; contentX = -shift" to
461+ // dismiss the spread because spreadEnabled went to false, for some reason, during tests,
462+ // Flickable might jump in and change contentX value back, causing the code below to do
463+ // "phase = 1" which will make the spread stay.
464+ // It sucks that we have no control whatsoever over whether or when Flickable animates its
465+ // contentX.
382− if (spreadView.phase == 0 && spreadView.shiftedContentX > spreadView.width * spreadView.positionMarker2) {
466+ if (root.spreadEnabled && shiftedContentX > width * positionMarker2) {
383− spreadView.phase = 1;
467+ phase = 1;
468+ }
469+ break;
470+ case 1:
471+ if (shiftedContentX < width * positionMarker2) {
472+ phase = 0;
384− } else if (spreadView.phase == 1 && spreadView.shiftedContentX > spreadView.width * spreadView.positionMarker4) {
473+ } else if (shiftedContentX >= width * positionMarker4 && !spreadDragArea.dragging) {
385− spreadView.phase = 2;
474+ phase = 2;
386− } else if (spreadView.phase == 1 && spreadView.shiftedContentX < spreadView.width * spreadView.positionMarker2) {
475+ }
387− spreadView.phase = 0;
476+ break;
388 }
477 }
389 }
478 }
390
479
@@ -395,12 +484,18 @@ AbstractStage {
395 } else if (shiftedContentX < phase1Width) {
484 } else if (shiftedContentX < phase1Width) {
396 snapTo(1);
485 snapTo(1);
397 } else {
486 } else {
398− // Add 1 pixel to make sure we definitely hit positionMarker4 even with rounding errors of the animation.
487+ snapToSpread();
399− snapAnimation.targetContentX = spreadView.width * spreadView.positionMarker4 + 1 - shift;
400− snapAnimation.start();
401 }
488 }
402 }
489 }
490+
491+ function snapToSpread() {
492+ // Add 1 pixel to make sure we definitely hit positionMarker4 even with rounding errors of the animation.
493+ snapAnimation.targetContentX = (spreadView.width * spreadView.positionMarker4) + 1 - shift;
494+ snapAnimation.start();
495+ }
496+
403 function snapTo(index) {
497 function snapTo(index) {
498+ snapAnimation.stop();
404 spreadView.selectedIndex = index;
499 spreadView.selectedIndex = index;
405 snapAnimation.targetContentX = -shift;
500 snapAnimation.targetContentX = -shift;
406 snapAnimation.start();
501 snapAnimation.start();
@@ -410,55 +505,41 @@ AbstractStage {
410 // We don't want to really reorder them in the model because that allows us to keep track
505 // We don't want to really reorder them in the model because that allows us to keep track
411 // of the last focused order.
506 // of the last focused order.
412 function indexToZIndex(index) {
507 function indexToZIndex(index) {
508+ // only shuffle when we've got a main and overlay
509+ if (state !== "mainAndOverlay") return index;
510+
413 var app = ApplicationManager.get(index);
511 var app = ApplicationManager.get(index);
414 if (!app) {
512 if (!app) {
415 return index;
513 return index;
416 }
514 }
417
515
516+ // don't shuffle indexes greater than "actives or next"
517+ if (index > 2) return index;
518+
418− var active = app.appId == priv.mainStageAppId || app.appId == priv.sideStageAppId;
519+ if (app.appId === priv.mainStageAppId) {
419− if (active && app.stage == ApplicationInfoInterface.MainStage) {
520+ // Active main stage always at 0
420− // if this app is active, and its the MainStage, always put it to index 0
421 return 0;
521 return 0;
422 }
522 }
423− if (active && app.stage == ApplicationInfoInterface.SideStage) {
424− if (!priv.mainStageAppId) {
425− // Only have SS apps running. Put the active one at 0
426− return 0;
427− }
428
523
429− // Precondition now: There's an active MS app and this is SS app:
524+ if (spreadView.nextInStack > 0) {
430− if (spreadView.nextInStack >= 0 && ApplicationManager.get(spreadView.nextInStack).stage == ApplicationInfoInterface.MainStage) {
525+ var nextAppInStack = ApplicationManager.get(spreadView.nextInStack);
431− // If the next app coming from the right is a MS app, we need to elevate this SS ap above it.
526+
432− // Put it to at least level 2, or higher if there's more apps coming in before this one.
527+ if (index === spreadView.nextInStack) {
433− return Math.max(index, 2);
528+ // this is the next app in stack.
434− } else {
529+
435− // 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.
530+ if (app.stage === ApplicationInfoInterface.SideStage) {
531+ // if the next app in stack is a sidestage app, it must order on top of other side stage app
532+ return Math.min(2, ApplicationManager.count-1);
533+ }
436 return 1;
534 return 1;
437 }
535 }
438− }
439− if (index <= 2 && app.stage == ApplicationInfoInterface.MainStage && priv.sideStageAppId) {
440− // Ok, this is an inactive MS app. If there's an active SS app around, we need to place this one
441− // in between the active MS app and the active SS app, so that it comes in from there when dragging from the right.
442− // If there's now active SS app, just leave it where it is.
443− return priv.indexOf(priv.sideStageAppId) < index ? index - 1 : index;
444− }
445− if (index == spreadView.nextInStack && app.stage == ApplicationInfoInterface.SideStage) {
536+ if (nextAppInStack.stage === ApplicationInfoInterface.SideStage) {
446− // This is a SS app and the next one to come in from the right:
537+ // if the next app in stack is a sidestage app, it must order on top of other side stage app
447− if (priv.sideStageAppId && priv.mainStageAppId) {
448− // If there's both, an active MS and an active SS app, put this one right on top of that
449− return 2;
538+ return 1;
450 }
539 }
451− // Or if there's only one other active app, put it on top of that.
540+ return Math.min(2, ApplicationManager.count-1);
452− // The case that there isn't any other active app is already handled above.
453− return 1;
454 }
541 }
455− if (index == 2 && spreadView.nextInStack == 1 && priv.sideStageAppId) {
542+ return Math.min(index+1, ApplicationManager.count-1);
456− // If its index 2 but not the next one to come in, it means
457− // we've pulled another one down to index 2. Move this one up to 2 instead.
458− return 3;
459− }
460− // don't touch all others... (mostly index > 3 + simple cases where the above doesn't shuffle much)
461− return index;
462 }
543 }
463
544
464 SequentialAnimation {
545 SequentialAnimation {
@@ -476,8 +557,12 @@ AbstractStage {
476 script: {
557 script: {
477 if (spreadView.selectedIndex >= 0) {
558 if (spreadView.selectedIndex >= 0) {
478 var newIndex = spreadView.selectedIndex;
559 var newIndex = spreadView.selectedIndex;
560+ var application = ApplicationManager.get(newIndex);
561+ if (application.stage === ApplicationInfoInterface.SideStage) {
562+ sideStage.showNow();
563+ }
479 spreadView.selectedIndex = -1;
564 spreadView.selectedIndex = -1;
480− ApplicationManager.focusApplication(ApplicationManager.get(newIndex).appId);
565+ ApplicationManager.focusApplication(application.appId);
481 spreadView.phase = 0;
566 spreadView.phase = 0;
482 spreadView.contentX = -spreadView.shift;
567 spreadView.contentX = -spreadView.shift;
483 }
568 }
@@ -485,6 +570,11 @@ AbstractStage {
485 }
570 }
486 }
571 }
487
572
573+ Behavior on contentX {
574+ enabled: root.altTabPressed
575+ UbuntuNumberAnimation {}
576+ }
577+
488 MouseArea {
578 MouseArea {
489 id: spreadRow
579 id: spreadRow
490 x: spreadView.contentX
580 x: spreadView.contentX
@@ -495,87 +585,82 @@ AbstractStage {
495 spreadView.snapTo(0);
585 spreadView.snapTo(0);
496 }
586 }
497
587
498− Rectangle {
588+ DropArea {
499− id: sideStageBackground
589+ objectName: "MainStageDropArea"
500− color: "black"
590+ anchors {
501− width: root.sideStageWidth * (1 - sideStageDragHandle.progress)
591+ left: parent.left
502− height: priv.landscapeHeight
592+ top: parent.top
593+ bottom: parent.bottom
594+ }
503− x: spreadView.width - width
595+ width: spreadView.width - sideStage.width
504− z: spreadView.indexToZIndex(priv.indexOf(priv.sideStageAppId))
596+ enabled: priv.sideStageEnabled
505− opacity: spreadView.phase == 0 ? 1 : 0
597+
506− Behavior on opacity { UbuntuNumberAnimation {} }
598+ onDropped: {
599+ priv.setAppStage(drag.source.appId, ApplicationInfoInterface.MainStage, true);
600+ ApplicationManager.focusApplication(drag.source.appId);
601+ }
602+ keys: "SideStage"
507 }
603 }
508
604
509− Item {
605+ SideStage {
510− id: sideStageDragHandle
606+ id: sideStage
511− anchors.right: sideStageBackground.left
607+ objectName: "sideStage"
512− anchors.top: sideStageBackground.top
513− width: units.gu(2)
514 height: priv.landscapeHeight
608 height: priv.landscapeHeight
515− z: sideStageBackground.z
609+ x: spreadView.width - width
516− opacity: spreadView.phase <= 0 && root.sideStageVisible ? 1 : 0
610+ z: {
517− property real progress: 0
611+ if (!priv.mainStageAppId) return 0;
518− property bool dragging: false
519
612
520− Behavior on opacity { UbuntuNumberAnimation {} }
613+ if (priv.sideStageAppId && spreadView.nextInStack > 0) {
614+ var nextAppInStack = ApplicationManager.get(spreadView.nextInStack);
521
615
522− Connections {
616+ if (nextAppInStack.stage === ApplicationInfoInterface.MainStage) {
523− target: root
617+ // if the next app in stack is a main stage app, put the sidestage on top of it.
524− onSideStageVisibleChanged: {
618+ return 2;
525− if (root.sideStageVisible) {
526− sideStageDragHandle.progress = 0;
527 }
619 }
620+ return 1;
528 }
621 }
622+
623+ return 1;
529 }
624 }
625+ visible: progress != 0
626+ enabled: priv.sideStageEnabled && sideStageDropArea.dropAllowed
627+ opacity: priv.sideStageEnabled && !spreadView.active ? 1 : 0
628+ Behavior on opacity { UbuntuNumberAnimation {} }
530
629
531− Image {
630+ onShownChanged: {
532− anchors.centerIn: parent
631+ if (!shown && ApplicationManager.focusedApplicationId == priv.sideStageAppId) {
533− width: sideStageDragHandleMouseArea.pressed ? parent.width * 2 : parent.width
632+ ApplicationManager.requestFocusApplication(priv.mainStageAppId);
534− height: parent.height
633+ }
535− source: "graphics/sidestage_handle@20.png"
634+ priv.updateStageApps();
536− Behavior on width { UbuntuNumberAnimation {} }
635+ if (shown && priv.sideStageAppId) {
636+ ApplicationManager.requestFocusApplication(priv.sideStageAppId);
637+ }
537 }
638 }
538
639
539− MouseArea {
640+ DropArea {
540− id: sideStageDragHandleMouseArea
641+ id: sideStageDropArea
642+ objectName: "SideStageDropArea"
541 anchors.fill: parent
643 anchors.fill: parent
542− enabled: spreadView.shiftedContentX == 0
644+
543− property int startX
645+ property bool dropAllowed: true
544− property var gesturePoints: new Array()
646+
545− property real totalDiff
647+ onEntered: {
546−
648+ dropAllowed = drag.keys != "Disabled";
547− onPressed: {
548− gesturePoints = [];
549− startX = mouseX;
550− totalDiff = 0.0;
551− sideStageDragHandle.progress = 0;
552− sideStageDragHandle.dragging = true;
553− }
554− onMouseXChanged: {
555− totalDiff += mouseX - startX;
556− if (priv.mainStageAppId) {
557− sideStageDragHandle.progress = Math.max(0, totalDiff / root.sideStageWidth);
558− }
559− gesturePoints.push(mouseX);
560 }
649 }
561− onReleased: {
650+ onExited: {
562− if (priv.mainStageAppId) {
651+ dropAllowed = true;
563− var oneWayFlick = priv.evaluateOneWayFlick(gesturePoints);
652+ }
564− sideStageDragSnapAnimation.to = sideStageDragHandle.progress > 0.5 || oneWayFlick ? 1 : 0;
653+ onDropped: {
565− sideStageDragSnapAnimation.start();
654+ if (drop.keys == "MainStage") {
566− } else {
655+ priv.setAppStage(drop.source.appId, ApplicationInfoInterface.SideStage, true);
567− sideStageDragHandle.dragging = false;
656+ ApplicationManager.requestFocusApplication(drop.source.appId);
568 }
657 }
569 }
658 }
570− }
659+ drag {
571− UbuntuNumberAnimation {
660+ onSourceChanged: {
572− id: sideStageDragSnapAnimation
661+ if (!sideStageDropArea.drag.source) {
573− target: sideStageDragHandle
662+ dropAllowed = true;
574− property: "progress"
663+ }
575−
576− onRunningChanged: {
577− if (!running) {
578− sideStageDragHandle.dragging = false;
579 }
664 }
580 }
665 }
581 }
666 }
@@ -590,36 +675,54 @@ AbstractStage {
590 id: spreadTile
675 id: spreadTile
591 objectName: model.appId ? "tabletSpreadDelegate_" + model.appId
676 objectName: model.appId ? "tabletSpreadDelegate_" + model.appId
592 : "tabletSpreadDelegate_null";
677 : "tabletSpreadDelegate_null";
593− width: {
594− if (wantsMainStage) {
595− return spreadView.width;
678+ width: spreadView.width
596− } else {
597− return root.sideStageWidth;
598− }
599− }
600− height: {
601− if (wantsMainStage) {
602− return spreadView.height;
679+ height: spreadView.height
603− } else {
604− return priv.landscapeHeight;
605− }
606− }
607− active: model.appId == priv.mainStageAppId || model.appId == priv.sideStageAppId
680+ active: appId == priv.mainStageAppId || appId == priv.sideStageAppId
608− zIndex: spreadView.indexToZIndex(index)
681+ zIndex: selected && stage == ApplicationInfoInterface.MainStage ? 0 : spreadView.indexToZIndex(index)
609 selected: spreadView.selectedIndex == index
682 selected: spreadView.selectedIndex == index
610 otherSelected: spreadView.selectedIndex >= 0 && !selected
683 otherSelected: spreadView.selectedIndex >= 0 && !selected
611− isInSideStage: priv.sideStageAppId == model.appId
684+ isInSideStage: priv.sideStageAppId === appId
612 interactive: !spreadView.interactive && spreadView.phase === 0 && root.interactive
685 interactive: !spreadView.interactive && spreadView.phase === 0 && root.interactive
613 swipeToCloseEnabled: spreadView.interactive && !snapAnimation.running
686 swipeToCloseEnabled: spreadView.interactive && !snapAnimation.running
614 maximizedAppTopMargin: root.maximizedAppTopMargin
687 maximizedAppTopMargin: root.maximizedAppTopMargin
615− dragOffset: !isDash && model.appId == priv.mainStageAppId && root.inverseProgress > 0 && spreadView.phase === 0 ? root.inverseProgress : 0
688+ dragOffset: !isDash && appId == priv.mainStageAppId && root.inverseProgress > 0 && spreadView.phase === 0 ? root.inverseProgress : 0
616 application: ApplicationManager.get(index)
689 application: ApplicationManager.get(index)
617 closeable: !isDash
690 closeable: !isDash
691+ highlightShown: root.altTabPressed && priv.highlightIndex == zIndex
618
692
619 readonly property bool wantsMainStage: model.stage == ApplicationInfoInterface.MainStage
693 readonly property bool wantsMainStage: model.stage == ApplicationInfoInterface.MainStage
620
694
621 readonly property bool isDash: model.appId == "unity8-dash"
695 readonly property bool isDash: model.appId == "unity8-dash"
622
696
697+ stage: model.stage
698+ fullscreen: {
699+ if (mainApp && stage === ApplicationInfoInterface.SideStage) {
700+ return mainApp.fullscreen;
701+ }
702+ return application ? application.fullscreen : false;
703+ }
704+
705+ supportedOrientations: {
706+ if (application) {
707+ var orientations = application.supportedOrientations;
708+ if (stage == ApplicationInfoInterface.MainStage) {
709+ // When an app is in the mainstage, it always supports Landscape|InvertedLandscape
710+ // so that we can drag it from the main stage to the side stage
711+ orientations |= Qt.LandscapeOrientation | Qt.InvertedLandscapeOrientation;
712+ }
713+ return orientations;
714+ } else {
715+ // we just don't care
716+ return Qt.PortraitOrientation |
717+ Qt.LandscapeOrientation |
718+ Qt.InvertedPortraitOrientation |
719+ Qt.InvertedLandscapeOrientation;
720+ }
721+ }
722+
723+ readonly property string appId: model.appId
724+ readonly property bool isDash: appId == "unity8-dash"
725+
623 Binding {
726 Binding {
624 target: spreadTile.application
727 target: spreadTile.application
625 property: "exemptFromLifecycle"
728 property: "exemptFromLifecycle"
@@ -650,6 +753,28 @@ AbstractStage {
650 enabled: spreadView.closingIndex >= 0
753 enabled: spreadView.closingIndex >= 0
651 UbuntuNumberAnimation {}
754 UbuntuNumberAnimation {}
652 }
755 }
756+ Connections {
757+ target: priv
758+ onSideStageEnabledChanged: refreshStage()
759+ }
760+
761+ Component.onCompleted: {
762+ refreshStage()
763+ stageChanged.connect(priv.updateStageApps);
764+ }
765+
766+ function refreshStage() {
767+ var stage = ApplicationInfoInterface.MainStage;
768+ if (priv.sideStageEnabled) {
769+ if (application && application.supportedOrientations & (Qt.PortraitOrientation|Qt.InvertedPortraitOrientation)) {
770+ stage = WindowStateStorage.getStage(appId);
771+ }
772+ }
773+
774+ if (model.stage !== stage) {
775+ priv.setAppStage(appId, stage, false);
776+ }
777+ }
653
778
654 // This is required because none of the bindings are triggered in some cases:
779 // This is required because none of the bindings are triggered in some cases:
655 // When an app is closed, it might happen that ApplicationManager.get(nextInStack)
780 // When an app is closed, it might happen that ApplicationManager.get(nextInStack)
@@ -674,7 +799,7 @@ AbstractStage {
674
799
675 // TODO: Hiding tile when progress is such that it will be off screen.
800 // TODO: Hiding tile when progress is such that it will be off screen.
676 property bool occluded: {
801 property bool occluded: {
677− if (spreadView.active) return false;
802+ if (spreadView.active && !offScreen) return false;
678 else if (spreadTile.active) return false;
803 else if (spreadTile.active) return false;
679 else if (xTranslateAnimating) return false;
804 else if (xTranslateAnimating) return false;
680 else if (z <= 1 && priv.focusedAppDelegateIsDislocated) return false;
805 else if (z <= 1 && priv.focusedAppDelegateIsDislocated) return false;
@@ -698,17 +823,87 @@ AbstractStage {
698 return progress;
823 return progress;
699 }
824 }
700
825
701− shellOrientationAngle: wantsMainStage ? root.shellOrientationAngle : 0
826+ shellOrientationAngle: root.shellOrientationAngle
827+ shellOrientation: root.shellOrientation
828+ orientations: root.orientations
829+
830+ states: [
831+ State {
832+ name: "MainStage"
833+ when: spreadTile.stage == ApplicationInfoInterface.MainStage
834+ },
835+ State {
836+ name: "SideStage"
837+ when: spreadTile.stage == ApplicationInfoInterface.SideStage
838+
839+ PropertyChanges {
840+ target: spreadTile
841+ width: root.sideStageWidth
842+ height: priv.landscapeHeight
843+
844+ supportedOrientations: Qt.PortraitOrientation
845+ shellOrientationAngle: 0
702− shellOrientation: wantsMainStage ? root.shellOrientation : Qt.PortraitOrientation
846+ shellOrientation: Qt.PortraitOrientation
847+ orientations: sideStageOrientations
848+ }
849+ }
850+ ]
851+
703− orientations: Orientations {
852+ Orientations {
853+ id: sideStageOrientations
704− primary: spreadTile.wantsMainStage ? root.orientations.primary : Qt.PortraitOrientation
854+ primary: Qt.PortraitOrientation
705− native_: spreadTile.wantsMainStage ? root.orientations.native_ : Qt.PortraitOrientation
855+ native_: Qt.PortraitOrientation
706 portrait: root.orientations.portrait
856 portrait: root.orientations.portrait
707 invertedPortrait: root.orientations.invertedPortrait
857 invertedPortrait: root.orientations.invertedPortrait
708 landscape: root.orientations.landscape
858 landscape: root.orientations.landscape
709 invertedLandscape: root.orientations.invertedLandscape
859 invertedLandscape: root.orientations.invertedLandscape
710 }
860 }
711
861
862+ transitions: [
863+ Transition {
864+ to: "SideStage"
865+ SequentialAnimation {
866+ PropertyAction {
867+ target: spreadTile
868+ properties: "width,height,supportedOrientations,shellOrientationAngle,shellOrientation,orientations"
869+ }
870+ ScriptAction {
871+ script: {
872+ // rotate immediately.
873+ spreadTile.matchShellOrientation();
874+ if (ApplicationManager.focusedApplicationId === spreadTile.appId &&
875+ priv.sideStageEnabled && !sideStage.shown) {
876+ // Sidestage was focused, so show the side stage.
877+ sideStage.show();
878+ // if we've switched to a main app which doesnt support portrait, hide the side stage.
879+ } else if (mainApp && (mainApp.supportedOrientations & (Qt.PortraitOrientation|Qt.InvertedPortraitOrientation)) == 0) {
880+ sideStage.hideNow();
881+ }
882+ }
883+ }
884+ }
885+ },
886+ Transition {
887+ from: "SideStage"
888+ SequentialAnimation {
889+ ScriptAction {
890+ script: {
891+ if (priv.sideStageAppId === spreadTile.appId &&
892+ mainApp && (mainApp.supportedOrientations & (Qt.PortraitOrientation|Qt.InvertedPortraitOrientation)) == 0) {
893+ // The mainstage app did not natively support portrait orientation, so focus the sidestage.
894+ ApplicationManager.requestFocusApplication(spreadTile.appId);
895+ }
896+ }
897+ }
898+ PropertyAction {
899+ target: spreadTile
900+ properties: "width,height,supportedOrientations,shellOrientationAngle,shellOrientation,orientations"
901+ }
902+ ScriptAction { script: { spreadTile.matchShellOrientation(); } }
903+ }
904+ }
905+ ]
906+
712 onClicked: {
907 onClicked: {
713 if (spreadView.phase == 2) {
908 if (spreadView.phase == 2) {
714 spreadView.snapTo(index);
909 spreadView.snapTo(index);
@@ -728,6 +923,16 @@ AbstractStage {
728 ApplicationManager.stopApplication(ApplicationManager.get(index).appId);
923 ApplicationManager.stopApplication(ApplicationManager.get(index).appId);
729 }
924 }
730
925
926+ onFocusChanged: {
927+ if (focus && ApplicationManager.focusedApplicationId !== appId) {
928+ ApplicationManager.focusApplication(appId);
929+ }
930+
931+ if (focus && priv.sideStageEnabled && stage === ApplicationInfoInterface.SideStage) {
932+ sideStage.show();
933+ }
934+ }
935+
731 Binding {
936 Binding {
732 target: root
937 target: root
733 when: model.appId == priv.mainStageAppId
938 when: model.appId == priv.mainStageAppId
@@ -752,6 +957,71 @@ AbstractStage {
752 }
957 }
753 }
958 }
754
959
960+ TabletSideStageTouchGesture {
961+ id: triGestureArea
962+ anchors.fill: parent
963+ enabled: priv.sideStageEnabled && !spreadView.active
964+ property var dragObject: null
965+ property string appId: ""
966+ dragComponent: dragComponent
967+ dragComponentProperties: { "appId": appId }
968+
969+ onPressed: {
970+ function matchDelegate(obj) { return String(obj.objectName).indexOf("tabletSpreadDelegate") >= 0; }
971+
972+ var delegateAtCenter = Functions.itemAt(spreadRow, x, y, matchDelegate);
973+ if (!delegateAtCenter) return;
974+
975+ appId = delegateAtCenter.appId;
976+ }
977+
978+ onClicked: {
979+ if (sideStage.shown) {
980+ sideStage.hide();
981+ } else {
982+ sideStage.show();
983+ }
984+ }
985+
986+ onDragStarted: {
987+ // If we're dragging to the sidestage.
988+ if (!sideStage.shown) {
989+ sideStage.show();
990+ }
991+ }
992+
993+ Component {
994+ id: dragComponent
995+ SessionContainer {
996+ property string appId: ""
997+ property var application: ApplicationManager.findApplication(appId)
998+
999+ session: application ? application.session : null
1000+ interactive: false
1001+ resizeSurface: false
1002+ focus: false
1003+
1004+ width: units.gu(40)
1005+ height: units.gu(40)
1006+
1007+ Drag.hotSpot.x: width/2
1008+ Drag.hotSpot.y: height/2
1009+ // only accept opposite stage.
1010+ Drag.keys: {
1011+ if (!application) return "Disabled";
1012+
1013+ if (application.stage === ApplicationInfo.MainStage) {
1014+ if (application.supportedOrientations & (Qt.PortraitOrientation|Qt.InvertedPortraitOrientation)) {
1015+ return "MainStage";
1016+ }
1017+ return "Disabled";
1018+ }
1019+ return "SideStage";
1020+ }
1021+ }
1022+ }
1023+ }
1024+
755 //eat touch events during the right edge gesture
1025 //eat touch events during the right edge gesture
756 MouseArea {
1026 MouseArea {
757 anchors.fill: parent
1027 anchors.fill: parent
@@ -761,7 +1031,8 @@ AbstractStage {
761 DirectionalDragArea {
1031 DirectionalDragArea {
762 id: spreadDragArea
1032 id: spreadDragArea
763 objectName: "spreadDragArea"
1033 objectName: "spreadDragArea"
1034+ x: parent.width - root.dragAreaWidth
764− anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
1035+ anchors { top: parent.top; bottom: parent.bottom }
765 width: root.dragAreaWidth
1036 width: root.dragAreaWidth
766 direction: Direction.Leftwards
1037 direction: Direction.Leftwards
767 enabled: (spreadView.phase != 2 && root.spreadEnabled) || dragging
1038 enabled: (spreadView.phase != 2 && root.spreadEnabled) || dragging
@@ -809,4 +1080,29 @@ AbstractStage {
809 }
1080 }
810 }
1081 }
811 }
1082 }
1083+
1084+ EdgeBarrier {
1085+ id: edgeBarrier
1086+
1087+ // NB: it does its own positioning according to the specified edge
1088+ edge: Qt.RightEdge
1089+
1090+ onPassed: {
1091+ spreadView.snapToSpread();
1092+ }
1093+ material: Component {
1094+ Item {
1095+ Rectangle {
1096+ width: parent.height
1097+ height: parent.width
1098+ rotation: 90
1099+ anchors.centerIn: parent
1100+ gradient: Gradient {
1101+ GradientStop { position: 0.0; color: Qt.rgba(0.16,0.16,0.16,0.7)}
1102+ GradientStop { position: 1.0; color: Qt.rgba(0.16,0.16,0.16,0)}
1103+ }
1104+ }
1105+ }
1106+ }
1107+ }
812}
1108}
qml/Stages/TransformedTabletSpreadDelegate.qml+84−56
@@ -31,6 +31,7 @@ SpreadDelegate {
31 // Set this to true when this tile a currently active on either the MS or the SS.
31 // Set this to true when this tile a currently active on either the MS or the SS.
32 property bool active: false
32 property bool active: false
33
33
34+ property int stage
34 property int zIndex
35 property int zIndex
35 property real progress: 0
36 property real progress: 0
36 property real animatedProgress: 0
37 property real animatedProgress: 0
@@ -49,10 +50,11 @@ SpreadDelegate {
49
50
50 property int dragOffset: 0
51 property int dragOffset: 0
51 readonly property alias xTranslateAnimating: xTranslateAnimation.running
52 readonly property alias xTranslateAnimating: xTranslateAnimation.running
53+ readonly property bool offScreen: priv.xTranslate >= 0
52
54
53 dropShadow: spreadView.active ||
55 dropShadow: spreadView.active ||
54 (active
56 (active
55− && (model.stage == ApplicationInfoInterface.MainStage || !priv.shellIsLandscape)
57+ && (stage == ApplicationInfoInterface.MainStage || !priv.shellIsLandscape)
56 && priv.xTranslate != 0)
58 && priv.xTranslate != 0)
57
59
58 onSelectedChanged: {
60 onSelectedChanged: {
@@ -141,9 +143,9 @@ SpreadDelegate {
141 enabled: !spreadView.active &&
143 enabled: !spreadView.active &&
142 !snapAnimation.running &&
144 !snapAnimation.running &&
143 model.appId !== "unity8-dash" &&
145 model.appId !== "unity8-dash" &&
144− !spreadView.sideStageDragging &&
145 spreadView.animateX &&
146 spreadView.animateX &&
146− !spreadView.beingResized
147+ !spreadView.beingResized &&
148+ priv.state !== "sideStage"
147 UbuntuNumberAnimation {
149 UbuntuNumberAnimation {
148 id: xTranslateAnimation
150 id: xTranslateAnimation
149 duration: UbuntuAnimation.FastDuration
151 duration: UbuntuAnimation.FastDuration
@@ -153,21 +155,20 @@ SpreadDelegate {
153 property real xTranslate: {
155 property real xTranslate: {
154 var newTranslate = 0;
156 var newTranslate = 0;
155
157
156− if (otherSelected) {
158+ // selected app or opposite stage active app.
157− return priv.selectedXTranslate;
159+ if (isSelected || (otherSelected && root.active && spreadView.selectedApplication && spreadView.selectedApplication.stage !== stage)) {
158− }
159−
160− if (isSelected) {
161− if (model.stage == ApplicationInfoInterface.MainStage) {
160+ if (stage == ApplicationInfoInterface.MainStage) {
162 return linearAnimation(selectedProgress, negativeProgress, selectedXTranslate, -spreadView.width, root.progress);
161 return linearAnimation(selectedProgress, negativeProgress, selectedXTranslate, -spreadView.width, root.progress);
163 } else {
162 } else {
164 return linearAnimation(selectedProgress, negativeProgress, selectedXTranslate, -spreadView.sideStageWidth, root.progress);
163 return linearAnimation(selectedProgress, negativeProgress, selectedXTranslate, -spreadView.sideStageWidth, root.progress);
165 }
164 }
165+ } else if (otherSelected) {
166+ return selectedXTranslate;
166 }
167 }
167
168
168 // 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
169 // 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
169 // when we're only dragging the side stage in on top of a main stage app
170 // 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 &&
171 ApplicationManager.get(spreadView.nextInStack).stage === ApplicationInfoInterface.MainStage;
172 ApplicationManager.get(spreadView.nextInStack).stage === ApplicationInfoInterface.MainStage;
172
173
173 if (active) {
174 if (active) {
@@ -177,36 +178,45 @@ SpreadDelegate {
177 newTranslate += linearAnimation(0, spreadView.positionMarker2, 0, -units.gu(4), root.animatedProgress);
178 newTranslate += linearAnimation(0, spreadView.positionMarker2, 0, -units.gu(4), root.animatedProgress);
178 }
179 }
179 newTranslate += root.dragOffset;
180 newTranslate += root.dragOffset;
180− }
181− if (!spreadView.active && model.appId == "unity8-dash" && !root.active) {
181+ } else if (!spreadView.active && model.appId == "unity8-dash") {
182 newTranslate -= root.width;
182 newTranslate -= root.width;
183 }
183 }
184
184
185+ if (!spreadView.active) {
186+ return newTranslate;
187+ }
188+
189+ var shadowOffset = units.gu(2);
190+
185− if (nextInStack && spreadView.phase == 0) {
191+ if (spreadView.phase == 0) {
192+ if (nextInStack) {
186− if (model.stage == ApplicationInfoInterface.MainStage) {
193+ if (stage == ApplicationInfoInterface.MainStage) {
187− if (spreadView.sideStageVisible && root.progress > 0) {
194+ if (spreadView.sideStageVisible && root.progress > 0) {
188− // Move it so it appears from behind the side stage immediately
195+ // Move it so it appears from behind the side stage immediately
189− newTranslate += -spreadView.sideStageWidth;
196+ newTranslate += -spreadView.sideStageWidth;
197+ }
190 }
198 }
191− }
192
199
193− if (model.stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
200+ if (stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
194− // This is when we only drag the side stage in, without rotation or snapping
201+ // 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);
202+ newTranslate = linearAnimation(0, spreadView.positionMarker2, 0, -spreadView.sideStageWidth, root.animatedProgress);
196− } else {
203+ } else {
197− newTranslate += linearAnimation(0, spreadView.positionMarker2, 0, -spreadView.sideStageWidth * spreadView.snapPosition, root.animatedProgress);
204+ newTranslate += linearAnimation(0, spreadView.positionMarker2, 0, -spreadView.sideStageWidth * spreadView.snapPosition, root.animatedProgress);
205+ }
206+ } else if (!root.active) {
207+ newTranslate += shadowOffset;
198 }
208 }
199 }
209 }
200
210
201 if (spreadView.phase == 1) {
211 if (spreadView.phase == 1) {
202 if (nextInStack) {
212 if (nextInStack) {
203− if (model.stage == ApplicationInfoInterface.MainStage) {
213+ if (stage == ApplicationInfoInterface.MainStage) {
204 var startValue = -spreadView.sideStageWidth * spreadView.snapPosition + (spreadView.sideStageVisible ? -spreadView.sideStageWidth : 0);
214 var startValue = -spreadView.sideStageWidth * spreadView.snapPosition + (spreadView.sideStageVisible ? -spreadView.sideStageWidth : 0);
205 newTranslate += linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, startValue, priv.phase2StartTranslate, root.animatedProgress);
215 newTranslate += linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, startValue, priv.phase2StartTranslate, root.animatedProgress);
206 } else {
216 } else {
207 var endValue = -spreadView.width + spreadView.width * root.zIndex / 6;
217 var endValue = -spreadView.width + spreadView.width * root.zIndex / 6;
208 if (!spreadView.sideStageVisible) {
218 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);
210 } else {
220 } else {
211 newTranslate += linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, -spreadView.sideStageWidth * spreadView.snapPosition, priv.phase2StartTranslate, root.animatedProgress);
221 newTranslate += linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, -spreadView.sideStageWidth * spreadView.snapPosition, priv.phase2StartTranslate, root.animatedProgress);
212 }
222 }
@@ -215,16 +225,20 @@ SpreadDelegate {
215 var startProgress = spreadView.positionMarker2 - (zIndex * spreadView.positionMarker2 / 2);
225 var startProgress = spreadView.positionMarker2 - (zIndex * spreadView.positionMarker2 / 2);
216 var endProgress = spreadView.positionMarker4 - (zIndex * spreadView.tileDistance / spreadView.width);
226 var endProgress = spreadView.positionMarker4 - (zIndex * spreadView.tileDistance / spreadView.width);
217 var startTranslate = -root.width + (shouldMoveAway ? -units.gu(4) : 0);
227 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);
219 } else {
229 } else {
220 var startProgress = spreadView.positionMarker2 - (zIndex * spreadView.positionMarker2 / 2);
230 var startProgress = spreadView.positionMarker2 - (zIndex * spreadView.positionMarker2 / 2);
221 var endProgress = spreadView.positionMarker4 - (zIndex * spreadView.tileDistance / spreadView.width);
231 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);
223 }
233 }
224 }
234 }
225
235
226 if (spreadView.phase == 2) {
236 if (spreadView.phase == 2) {
237+ if (easingCurve.value == 0 && !nextInStack) {
238+ newTranslate = shadowOffset;
239+ } else {
227− newTranslate = -easingCurve.value * (spreadView.width - root.zIndex * animatedEndDistance);
240+ newTranslate = -easingCurve.value * (spreadView.width - root.zIndex * animatedEndDistance);
241+ }
228 }
242 }
229
243
230 return newTranslate;
244 return newTranslate;
@@ -235,33 +249,30 @@ SpreadDelegate {
235 return 1;
249 return 1;
236 }
250 }
237
251
238− if (otherSelected) {
252+ // selected app or opposite stage active app.
239− return selectedScale;
253+ if (isSelected || (otherSelected && root.active && spreadView.selectedApplication && spreadView.selectedApplication.stage !== stage)) {
240− }
241−
242− if (isSelected) {
243 return linearAnimation(selectedProgress, negativeProgress, selectedScale, 1, root.progress);
254 return linearAnimation(selectedProgress, negativeProgress, selectedScale, 1, root.progress);
255+ } else if (otherSelected) {
256+ return selectedScale;
244 }
257 }
245
258
246 if (spreadView.phase == 0) {
259 if (spreadView.phase == 0) {
247 if (nextInStack) {
260 if (nextInStack) {
248− if (model.stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
261+ if (stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
249 return 1;
262 return 1;
250 } else {
263 } else {
251 var targetScale = root.dragStartScale - ((root.dragStartScale - 1) * spreadView.snapPosition);
264 var targetScale = root.dragStartScale - ((root.dragStartScale - 1) * spreadView.snapPosition);
252 return linearAnimation(0, spreadView.positionMarker2, root.dragStartScale, targetScale, root.animatedProgress);
265 return linearAnimation(0, spreadView.positionMarker2, root.dragStartScale, targetScale, root.animatedProgress);
253 }
266 }
254− } else if (active) {
255− return 1;
256 } else {
267 } else {
257− return linearAnimation(0, spreadView.positionMarker2, root.startScale, root.endScale, root.progress);
268+ return 1;
258 }
269 }
259 }
270 }
260
271
261 if (spreadView.phase == 1) {
272 if (spreadView.phase == 1) {
262 if (nextInStack) {
273 if (nextInStack) {
263 var startScale = 1;
274 var startScale = 1;
264− if (model.stage !== ApplicationInfoInterface.SideStage || spreadView.sideStageVisible) {
275+ if (stage !== ApplicationInfoInterface.SideStage || spreadView.sideStageVisible) {
265 startScale = root.dragStartScale - ((root.dragStartScale - 1) * spreadView.snapPosition);
276 startScale = root.dragStartScale - ((root.dragStartScale - 1) * spreadView.snapPosition);
266 }
277 }
267 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, startScale, priv.phase2StartScale, root.animatedProgress);
278 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, startScale, priv.phase2StartScale, root.animatedProgress);
@@ -283,21 +294,22 @@ SpreadDelegate {
283 return 0;
294 return 0;
284 }
295 }
285
296
286− if (otherSelected) {
297+ // selected app or opposite stage active app.
287− return selectedAngle;
298+ if (isSelected || (otherSelected && root.active && spreadView.selectedApplication && spreadView.selectedApplication.stage !== stage)) {
288− }
289− if (isSelected) {
290 return linearAnimation(selectedProgress, negativeProgress, selectedAngle, 0, root.progress);
299 return linearAnimation(selectedProgress, negativeProgress, selectedAngle, 0, root.progress);
300+ } else if (otherSelected) {
301+ return selectedAngle;
291 }
302 }
292
303
293 // The tile should rotate a bit when another one comes on top, but not when only dragging the side stage in
304 // 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 &&
295 (ApplicationManager.get(spreadView.nextInStack).stage === ApplicationInfoInterface.MainStage ||
307 (ApplicationManager.get(spreadView.nextInStack).stage === ApplicationInfoInterface.MainStage ||
296− model.stage == ApplicationInfoInterface.SideStage);
308+ stage == ApplicationInfoInterface.SideStage);
297
309
298 if (spreadView.phase == 0) {
310 if (spreadView.phase == 0) {
299 if (nextInStack) {
311 if (nextInStack) {
300− if (model.stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
312+ if (stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
301 return 0;
313 return 0;
302 } else {
314 } else {
303 return linearAnimation(0, spreadView.positionMarker2, root.startAngle, root.startAngle * (1-spreadView.snapPosition), root.animatedProgress);
315 return linearAnimation(0, spreadView.positionMarker2, root.startAngle, root.startAngle * (1-spreadView.snapPosition), root.animatedProgress);
@@ -309,7 +321,7 @@ SpreadDelegate {
309 }
321 }
310 if (spreadView.phase == 1) {
322 if (spreadView.phase == 1) {
311 if (nextInStack) {
323 if (nextInStack) {
312− if (model.stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
324+ if (stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
313 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, 0, priv.phase2StartAngle, root.animatedProgress);
325 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, 0, priv.phase2StartAngle, root.animatedProgress);
314 } else {
326 } else {
315 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, root.startAngle * (1-spreadView.snapPosition), priv.phase2StartAngle, root.animatedProgress);
327 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, root.startAngle * (1-spreadView.snapPosition), priv.phase2StartAngle, root.animatedProgress);
@@ -328,31 +340,47 @@ SpreadDelegate {
328 }
340 }
329
341
330 property real opacityTransform: {
342 property real opacityTransform: {
343+ // animate opacity for items not snapping into view.
331− if (otherSelected && spreadView.phase == 2) {
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+ }
332 return linearAnimation(selectedProgress, negativeProgress, selectedOpacity, 0, root.progress);
351 return linearAnimation(selectedProgress, negativeProgress, selectedOpacity, 0, root.progress);
333 }
352 }
334−
335 return 1;
353 return 1;
336 }
354 }
337
355
338 property real topMarginProgress: {
356 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)) {
340 return linearAnimation(selectedProgress, negativeProgress, selectedTopMarginProgress, 0, root.progress);
359 return linearAnimation(selectedProgress, negativeProgress, selectedTopMarginProgress, 0, root.progress);
341 }
360 }
361+
342− switch (spreadView.phase) {
362+ if (spreadView.phase == 0) {
343− case 0:
344 return 0;
363 return 0;
345− case 1:
364+ } else if (spreadView.phase == 1) {
365+ if (nextInStack) {
346− return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
366+ return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
347− 0, priv.phase2StartTopMarginProgress, root.progress);
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);
348 }
372 }
349 return 1;
373 return 1;
350 }
374 }
351
375
352 states: [
376 states: [
353 State {
377 State {
354− name: "sideStageDragging"; when: spreadView.sideStageDragging && root.isInSideStage
378+ name: "sideStage";
379+ when: root.isInSideStage && spreadView.shiftedContentX == 0 && spreadView.phase == 0
380+ PropertyChanges {
381+ target: priv;
355− PropertyChanges { target: priv; xTranslate: -spreadView.sideStageWidth + spreadView.sideStageWidth * spreadView.sideStageDragProgress }
382+ xTranslate: -spreadView.sideStageWidth + spreadView.sideStageWidth * (1-spreadView.sideStageDragProgress)
383+ }
356 }
384 }
357 ]
385 ]
358 }
386 }
qml/Stages/WindowResizeArea.qml+8−5
@@ -40,6 +40,7 @@ MouseArea {
40 property int defaultHeight: units.gu(50)
40 property int defaultHeight: units.gu(50)
41 property int screenWidth: 0
41 property int screenWidth: 0
42 property int screenHeight: 0
42 property int screenHeight: 0
43+ property int leftMargin: 0
43
44
44 QtObject {
45 QtObject {
45 id: priv
46 id: priv
@@ -72,10 +73,12 @@ MouseArea {
72 var windowGeometry = windowStateStorage.getGeometry(root.windowId,
73 var windowGeometry = windowStateStorage.getGeometry(root.windowId,
73 Qt.rect(target.x, target.y, defaultWidth, defaultHeight));
74 Qt.rect(target.x, target.y, defaultWidth, defaultHeight));
74
75
75− target.requestedWidth = Math.min(Math.max(windowGeometry.width, d.minimumWidth), screenWidth);
76+ target.requestedWidth = Qt.binding(function() { return Math.min(Math.max(windowGeometry.width, d.minimumWidth), screenWidth - root.leftMargin); });
76− target.requestedHeight = Math.min(Math.max(windowGeometry.height, d.minimumHeight), root.screenHeight - PanelState.panelHeight);
77+ target.requestedHeight = Qt.binding(function() { return Math.min(Math.max(windowGeometry.height, d.minimumHeight),
78+ root.screenHeight - (target.fullscreen ? 0 : PanelState.panelHeight)); });
77− target.x = Math.max(Math.min(windowGeometry.x, root.screenWidth - target.requestedWidth), 0)
79+ target.x = Qt.binding(function() { return Math.max(Math.min(windowGeometry.x, root.screenWidth - root.leftMargin - target.requestedWidth),
80+ (target.fullscreen ? 0 : root.leftMargin)); });
78− target.y = Math.max(Math.min(windowGeometry.y, root.screenHeight - target.requestedHeight), PanelState.panelHeight)
81+ target.y = Qt.binding(function() { return Math.max(Math.min(windowGeometry.y, root.screenHeight - target.requestedHeight), PanelState.panelHeight); });
79
82
80 var windowState = windowStateStorage.getState(root.windowId, WindowStateStorage.WindowStateNormal)
83 var windowState = windowStateStorage.getState(root.windowId, WindowStateStorage.WindowStateNormal)
81 if (windowState === WindowStateStorage.WindowStateMaximized) {
84 if (windowState === WindowStateStorage.WindowStateMaximized) {
@@ -298,7 +301,7 @@ MouseArea {
298 }
301 }
299
302
300 if (d.topBorder) {
303 if (d.topBorder) {
301− var newTargetY = d.startY + deltaY;
304+ var newTargetY = Math.max(d.startY + deltaY, PanelState.panelHeight); // disallow resizing up past Panel
302 var bottomBorderY = target.y + target.height;
305 var bottomBorderY = target.y + target.height;
303 if (bottomBorderY > newTargetY + d.minimumHeight) {
306 if (bottomBorderY > newTargetY + d.minimumHeight) {
304 if (bottomBorderY < newTargetY + d.maximumHeight) {
307 if (bottomBorderY < newTargetY + d.maximumHeight) {
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 {
31 signal linkActivated(string link)
31 signal linkActivated(string link)
32
32
33 implicitHeight: Math.max(label.height, checkBox.height)
33 implicitHeight: Math.max(label.height, checkBox.height)
34+ highlightWhenPressed: false
34
35
35− Item {
36+ FocusScope {
36 anchors.fill: parent
37 anchors.fill: parent
38+ focus: true
37
39
38 CheckBox {
40 CheckBox {
39 id: checkBox
41 id: checkBox
@@ -77,8 +79,14 @@ ListItem.Empty {
77 rightMargin: listItem.rightMargin
79 rightMargin: listItem.rightMargin
78 }
80 }
79 wrapMode: Text.Wrap
81 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
81 onLinkActivated: listItem.linkActivated(link)
87 onLinkActivated: listItem.linkActivated(link)
82 }
88 }
89+
90+ Keys.onSpacePressed: checkBox.trigger()
83 }
91 }
84}
92}
qml/Wizard/Page.qml+260−44
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2013,2015 Canonical, Ltd.
2+ * Copyright (C) 2013-2016 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
5 * it under the terms of the GNU General Public License as published by
@@ -16,14 +16,33 @@
16
16
17import QtQuick 2.4
17import QtQuick 2.4
18import Ubuntu.Components 1.3
18import Ubuntu.Components 1.3
19+import Wizard 0.1
19
20
20Item {
21Item {
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+
21− readonly property real buttonMargin: units.gu(2)
26+ readonly property real buttonMargin: units.gu(3)
22 readonly property real buttonWidth: (width - buttonMargin * 2) / 2 -
27 readonly property real buttonWidth: (width - buttonMargin * 2) / 2 -
23 buttonMargin / 2
28 buttonMargin / 2
24− readonly property real topMargin: units.gu(8)
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)
25− readonly property real leftMargin: units.gu(2)
34+ readonly property real leftMargin: Math.max(units.gu(3), (width - maximumContentWidth) / 3)
26− readonly property real rightMargin: units.gu(2)
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
27
46
28 // If you want to skip a page, mark skipValid false while you figure out
47 // If you want to skip a page, mark skipValid false while you figure out
29 // whether to skip, then set it to true once you've determined the value
48 // whether to skip, then set it to true once you've determined the value
@@ -32,9 +51,13 @@ Item {
32 property bool skip: false
51 property bool skip: false
33
52
34 property bool hasBackButton: true
53 property bool hasBackButton: true
54+ property string backButtonText: i18n.ctr("Button: Go back one page in the Wizard", "Back")
35 property bool customBack: false
55 property bool customBack: false
56+ property bool customTitle: false
36 property alias forwardButtonSourceComponent: forwardButton.sourceComponent
57 property alias forwardButtonSourceComponent: forwardButton.sourceComponent
37 property alias content: contentHolder
58 property alias content: contentHolder
59+ property bool lastPage: false
60+ property bool buttonBarVisible: true
38
61
39 property string title: ""
62 property string title: ""
40
63
@@ -43,66 +66,259 @@ Item {
43 visible: false
66 visible: false
44 anchors.fill: parent
67 anchors.fill: parent
45
68
46− // We want larger than even fontSize: "x-large", so we use a Text instead
69+ Timer {
47− // of a Label.
70+ id: indicatorTimer
48− Text {
71+ interval: 1000
49− id: titleLabel
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
50 anchors {
84 anchors {
51 top: parent.top
85 top: parent.top
52 left: parent.left
86 left: parent.left
53 right: parent.right
87 right: parent.right
54− topMargin: topMargin
55− leftMargin: leftMargin
56− rightMargin: rightMargin
57 }
88 }
58− wrapMode: Text.Wrap
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+ }
59− text: title
106+ text: title
60− color: theme.palette.normal.baseText
107+ color: customTitle ? textColor : backgroundColor
61− font.pixelSize: units.gu(4)
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+ }
62 }
163 }
63
164
165+ // content
64 Item {
166 Item {
65 id: contentHolder
167 id: contentHolder
168+ property real animatedMargin: 0
169+ property real animatedTopMargin: 0
66 anchors {
170 anchors {
67− top: titleLabel.bottom
171+ top: titleRect.bottom
68 left: parent.left
172 left: parent.left
69 right: parent.right
173 right: parent.right
70− bottom: backButton.top
174+ bottom: buttonBarVisible ? buttonRect.top : parent.bottom
71− topMargin: units.gu(4)
175+ leftMargin: content.animatedMargin
72− leftMargin: leftMargin
176+ rightMargin: -content.animatedMargin
73− rightMargin: rightMargin
177+ topMargin: content.animatedTopMargin
74− bottomMargin: buttonMargin
75 }
178 }
179+ visible: opacity > 0
76 }
180 }
77
181
78− StackButton {
182+ // button bar
79− id: backButton
183+ Rectangle {
80− objectName: "backButton"
184+ id: buttonRect
81− width: buttonWidth
185+ visible: buttonBarVisible
82 anchors {
186 anchors {
83− left: parent.left
84 bottom: parent.bottom
187 bottom: parent.bottom
85− leftMargin: buttonMargin
188+ bottomMargin: Qt.inputMethod.visible ? Qt.inputMethod.keyboardRectangle.height : 0
86− bottomMargin: buttonMargin
189+ left: parent.left
190+ right: parent.right
87 }
191 }
88− z: 1
192+ height: buttonBarHeight
89− text: i18n.ctr("Button: Go back one page in the Wizard", "Back")
193+ color: "#f5f5f5"
90− visible: pageStack.depth > 1 && hasBackButton
194+ z: content.z + 1
91− backArrow: true
92
195
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+
93− onClicked: customBack ? backClicked() : pageStack.prev()
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+ }
94 }
224 }
95
225
96− Loader {
226+ // transitions
97− id: forwardButton
227+ function aboutToShow(duration, direction) {
98− objectName: "forwardButton"
228+ startContentAnimation(duration, direction)
99− width: buttonWidth
229+ startControlsAnimation(duration)
100− anchors {
230+ }
101− right: parent.right
231+
102− bottom: parent.bottom
232+ function aboutToShowSecondary(duration) {
103− rightMargin: buttonMargin
233+ secondaryAnimation.restart()
104− bottomMargin: buttonMargin
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
105 }
322 }
106− z: 1
107 }
323 }
108}
324}
qml/Wizard/Pages.qml+75−31
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2013,2014,2015 Canonical, Ltd.
2+ * Copyright (C) 2013-2016 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
5 * it under the terms of the GNU General Public License as published by
@@ -15,40 +15,77 @@
15 */
15 */
16
16
17import QtQuick 2.4
17import QtQuick 2.4
18+import MeeGo.QOfono 0.2
18import Ubuntu.Components 1.3
19import Ubuntu.Components 1.3
19import Ubuntu.SystemSettings.SecurityPrivacy 1.0
20import Ubuntu.SystemSettings.SecurityPrivacy 1.0
21+import Ubuntu.SystemSettings.Diagnostics 1.0
20import Wizard 0.1
22import Wizard 0.1
21import "../Components"
23import "../Components"
22
24
23−Item {
25+StyledItem {
24 id: root
26 id: root
25 objectName: "wizardPages"
27 objectName: "wizardPages"
26 focus: true
28 focus: true
27
29
28− // The background wallpaper to use
29− property string background
30−
31 signal quit()
30 signal quit()
32
31
33 // These should be set by a security page and we apply the settings when
32 // These should be set by a security page and we apply the settings when
34 // the user exits the wizard.
33 // the user exits the wizard.
35− property int passwordMethod: UbuntuSecurityPrivacyPanel.Passcode
34+ property int passwordMethod: UbuntuSecurityPrivacyPanel.Passphrase
36 property string password: ""
35 property string password: ""
37
36
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+
38 UbuntuSecurityPrivacyPanel {
47 UbuntuSecurityPrivacyPanel {
39 id: securityPrivacy
48 id: securityPrivacy
40 objectName: "securityPrivacy"
49 objectName: "securityPrivacy"
41 }
50 }
42
51
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+
43 function quitWizard() {
78 function quitWizard() {
44 pageStack.currentPage.enabled = false;
79 pageStack.currentPage.enabled = false;
45
80
81+ if (password != "") {
46− var errorMsg = securityPrivacy.setSecurity("", password, passwordMethod)
82+ var errorMsg = securityPrivacy.setSecurity("", password, passwordMethod)
47− if (errorMsg !== "") {
83+ if (errorMsg !== "") {
48− // Ignore (but log) any errors, since we're past where the user set
84+ // 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
85+ // the method. Worst case, we just leave the user with a swipe
50− // security method and they fix it in the system settings.
86+ // security method and they fix it in the system settings.
51− console.log("Error setting security method:", errorMsg)
87+ console.log("Error setting security method:", errorMsg)
88+ }
52 }
89 }
53
90
54 quit();
91 quit();
@@ -58,19 +95,10 @@ Item {
58 anchors.fill: parent
95 anchors.fill: parent
59 }
96 }
60
97
61− Image {
98+ Rectangle {
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
99+ id: background
72− fillMode: Image.PreserveAspectCrop
100+ anchors.fill: root
73− visible: status === Image.Ready
101+ color: "#fdfdfd"
74 }
102 }
75
103
76 PageList {
104 PageList {
@@ -86,18 +114,26 @@ Item {
86 // If we've opened any extra (non-main) pages, pop them before
114 // If we've opened any extra (non-main) pages, pop them before
87 // continuing so back button returns to the previous main page.
115 // continuing so back button returns to the previous main page.
88 while (pageList.index < pageStack.depth - 1)
116 while (pageList.index < pageStack.depth - 1)
89− pop()
117+ pop();
90− load(pageList.next())
118+ load(pageList.next());
91 }
119 }
92
120
93 function prev() {
121 function prev() {
122+ var isPrimaryPage = currentPage && !currentPage.customTitle;
94− if (pageList.index >= pageStack.depth - 1)
123+ if (pageList.index >= pageStack.depth - 1) {
95− pageList.prev() // update pageList.index, but not for extra pages
124+ pageList.prev(); // update pageList.index, but not for extra pages
125+ }
96 pop()
126 pop()
97 if (!currentPage || currentPage.opacity === 0) { // undo skipped pages
127 if (!currentPage || currentPage.opacity === 0) { // undo skipped pages
98− prev()
128+ prev();
99 } else {
129 } 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);
101 }
137 }
102 }
138 }
103
139
@@ -115,6 +151,13 @@ Item {
115 // Check for immediate skip or not. We may have to wait for
151 // Check for immediate skip or not. We may have to wait for
116 // skipValid to be assigned (see Connections object below)
152 // skipValid to be assigned (see Connections object below)
117 checkSkip()
153 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+ }
118 }
161 }
119
162
120 function checkSkip() {
163 function checkSkip() {
@@ -136,6 +179,7 @@ Item {
136 objectName: "timeout"
179 objectName: "timeout"
137 interval: 2000 // wizard pages shouldn't take long
180 interval: 2000 // wizard pages shouldn't take long
138 onTriggered: {
181 onTriggered: {
182+ console.warn("Wizard page " + pageStack.currentPage.objectName + " skipped due to taking too long!!!");
139 pageStack.currentPage.skip = true;
183 pageStack.currentPage.skip = true;
140 pageStack.currentPage.skipValid = true;
184 pageStack.currentPage.skipValid = true;
141 }
185 }
qml/Wizard/Pages/10-welcome.qml+121−48
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013-2015 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
5 * it under the terms of the GNU General Public License as published by
@@ -24,60 +24,124 @@ import ".." as LocalComponents
24LocalComponents.Page {
24LocalComponents.Page {
25 objectName: "languagePage"
25 objectName: "languagePage"
26
26
27− title: i18n.tr("Hi!")
27+ title: i18n.tr("Language")
28 forwardButtonSourceComponent: forwardButton
28 forwardButtonSourceComponent: forwardButton
29
29
30 UbuntuLanguagePlugin {
30 UbuntuLanguagePlugin {
31 id: plugin
31 id: plugin
32 }
32 }
33
33
34− Column {
34+ function init()
35− id: column
35+ {
36− anchors.fill: content
36+ var detectedLang = "";
37− spacing: units.gu(1)
37+ // try to detect the language+country from the SIM card
38−
38+ if (root.simManager0.present && root.simManager0.preferredLanguages.length > 0) {
39− Label {
39+ detectedLang = root.simManager0.preferredLanguages[0] + "_" + LocalePlugin.mccToCountryCode(root.simManager0.mobileCountryCode);
40− id: label1
40+ } else if (root.simManager1.present && root.simManager1.preferredLanguages.length > 0) {
41− anchors.left: parent.left
41+ detectedLang = root.simManager1.preferredLanguages[0] + "_" + LocalePlugin.mccToCountryCode(root.simManager1.mobileCountryCode);
42− anchors.right: parent.right
42+ } else if (plugin.currentLanguage != -1) {
43− wrapMode: Text.Wrap
43+ detectedLang = plugin.languageCodes[plugin.currentLanguage].split(".")[0]; // remove the encoding part, after dot (en_US.utf8 -> en_US)
44− text: i18n.tr("Welcome to your Ubuntu device.")
44+ } else {
45+ detectedLang = "en_US"; // fallback to default lang
45 }
46 }
46
47
47− Label {
48+ // preselect the detected language
48− id: label2
49+ for (var i = 0; i < plugin.languageCodes.length; i++) {
49− anchors.left: parent.left
50+ var code = plugin.languageCodes[i].split(".")[0]; // remove the encoding part, after dot (en_US.utf8 -> en_US)
50− anchors.right: parent.right
51+ if (detectedLang === code) {
51− wrapMode: Text.Wrap
52+ languagesListView.currentIndex = i;
52− text: i18n.tr("Let’s get started.")
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
53 }
82 }
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
54
96
55− Item { // spacer
97+ anchors {
56− height: units.gu(2)
98+ fill: content
57− width: units.gu(1) // needed else it will be ignored
99+ leftMargin: wideMode ? parent.leftMargin : 0
100+ rightMargin: wideMode ? parent.rightMargin : 0
101+ topMargin: wideMode ? parent.customMargin : 0
58 }
102 }
59
103
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
104+ model: plugin.languageNames
71− currentIndex: plugin.currentLanguage
105+
72− delegate: Standard {
106+ delegate: ListItem {
107+ id: itemDelegate
73− objectName: "languageDelegate" + index
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
74− text: modelData
116+ text: modelData
75− onClicked: {
117+
76− listview.currentIndex = index
118+ anchors {
77− combo.expanded = false
119+ left: parent.left
78− i18n.language = plugin.languageCodes[index]
120+ verticalCenter: parent.verticalCenter
79− }
121+ leftMargin: languagesListView.anchors.leftMargin == 0 ? staticMargin : 0
80 }
122 }
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];
81 }
145 }
82 }
146 }
83 }
147 }
@@ -85,14 +149,23 @@ LocalComponents.Page {
85 Component {
149 Component {
86 id: forwardButton
150 id: forwardButton
87 LocalComponents.StackButton {
151 LocalComponents.StackButton {
88− text: i18n.tr("Continue")
152+ text: i18n.tr("Next")
153+ enabled: languagesListView.currentIndex != -1
89 onClicked: {
154 onClicked: {
90− if (plugin.currentLanguage !== listview.currentIndex) {
155+ if (plugin.currentLanguage !== languagesListView.currentIndex) {
91− plugin.currentLanguage = listview.currentIndex;
156+ plugin.currentLanguage = languagesListView.currentIndex;
92− System.updateSessionLanguage(plugin.languageCodes[listview.currentIndex]);
157+ System.updateSessionLocale(plugin.languageCodes[plugin.currentLanguage]);
158+ }
93− i18n.language = i18n.language; // re-notify of change after above call (for qlocale change)
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
94 }
168 }
95− pageStack.next()
96 }
169 }
97 }
170 }
98 }
171 }
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 @@
1/*
1/*
2− * Copyright (C) 2013,2014 Canonical, Ltd.
2+ * Copyright (C) 2015-2016 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
5 * it under the terms of the GNU General Public License as published by
@@ -16,49 +16,59 @@
16
16
17import QtQuick 2.4
17import QtQuick 2.4
18import Ubuntu.Components 1.3
18import Ubuntu.Components 1.3
19−import Ubuntu.SystemSettings.Diagnostics 1.0
19+import AccountsService 0.1
20import ".." as LocalComponents
20import ".." as LocalComponents
21
21
22LocalComponents.Page {
22LocalComponents.Page {
23− id: reportingPage
23+ objectName: "accountPage"
24− objectName: "reportingPage"
24+ title: i18n.tr("Personalize Your Device")
25
25
26− title: i18n.tr("Improving your experience")
27 forwardButtonSourceComponent: forwardButton
26 forwardButtonSourceComponent: forwardButton
28
27
29− skipValid: false
28+ QtObject {
30− skip: !diagnostics.reportCrashes // skip the page when the system is configured not to report crashes
29+ id: d
31−
30+ readonly property string validName: nameInput.text.trim()
32− UbuntuDiagnostics {
33− id: diagnostics
34− Component.onCompleted: reportingPage.skipValid = true;
35 }
31 }
36
32
37 Column {
33 Column {
38 id: column
34 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+ }
41
42
43+ // name
42 Label {
44 Label {
45+ id: nameLabel
43 anchors.left: parent.left
46 anchors.left: parent.left
44 anchors.right: parent.right
47 anchors.right: parent.right
45− wrapMode: Text.Wrap
48+ text: i18n.tr("Preferred Name")
46− text: i18n.tr("Your phone is set up to automatically report errors to Canonical and its partners, the makers of the operating system.")
49+ color: textColor
50+ font.weight: Font.Light
47 }
51 }
48
52
49− Label {
53+ LocalComponents.WizardTextField {
54+ id: nameInput
55+ objectName: "nameInput"
50 anchors.left: parent.left
56 anchors.left: parent.left
51 anchors.right: parent.right
57 anchors.right: parent.right
52− wrapMode: Text.Wrap
58+ inputMethodHints: Qt.ImhNoPredictiveText
53− text: i18n.tr("This can be disabled in <b>System Settings</b> under <b>Security &amp; Privacy</b>")
54 }
59 }
55 }
60 }
56
61
57 Component {
62 Component {
58 id: forwardButton
63 id: forwardButton
59 LocalComponents.StackButton {
64 LocalComponents.StackButton {
60− text: i18n.tr("Continue")
65+ text: d.validName ? i18n.tr("Next") : i18n.tr("Skip")
66+ onClicked: {
67+ if (d.validName) {
68+ AccountsService.realName = d.validName;
69+ }
61− onClicked: pageStack.next()
70+ pageStack.next();
71+ }
62 }
72 }
63 }
73 }
64}
74}
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 @@
1/*
1/*
2− * Copyright (C) 2013,2014 Canonical, Ltd.
2+ * Copyright (C) 2013-2015 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
5 * it under the terms of the GNU General Public License as published by
@@ -21,37 +21,115 @@ import ".." as LocalComponents
21LocalComponents.Page {
21LocalComponents.Page {
22 objectName: "finishedPage"
22 objectName: "finishedPage"
23
23
24− title: i18n.tr("All done")
25− forwardButtonSourceComponent: forwardButton
26 hasBackButton: false
24 hasBackButton: false
25+ customTitle: true
26+ lastPage: true
27+ buttonBarVisible: false
27
28
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 {
29 id: column
71 id: column
30− anchors.fill: content
72+ anchors.leftMargin: leftMargin
31− spacing: units.gu(1)
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
32
79
33 Label {
80 Label {
81+ id: welcomeLabel
34 anchors.left: parent.left
82 anchors.left: parent.left
35 anchors.right: parent.right
83 anchors.right: parent.right
84+ horizontalAlignment: Text.AlignHCenter
36 wrapMode: Text.Wrap
85 wrapMode: Text.Wrap
37− fontSize: "large"
86+ fontSize: "x-large"
38− font.bold: true
87+ font.weight: Font.Light
88+ lineHeight: 1.2
39− text: i18n.tr("Nice work!")
89+ text: i18n.tr("Welcome to Ubuntu")
90+ color: whiteColor
40 }
91 }
41
92
42 Label {
93 Label {
94+ id: welcomeText
43 anchors.left: parent.left
95 anchors.left: parent.left
44 anchors.right: parent.right
96 anchors.right: parent.right
97+ anchors.top: welcomeLabel.bottom
98+ anchors.topMargin: units.gu(2)
99+ horizontalAlignment: Text.AlignHCenter
45 wrapMode: Text.Wrap
100 wrapMode: Text.Wrap
101+ fontSize: "large"
102+ font.weight: Font.Light
103+ lineHeight: 1.2
46− text: i18n.tr("Your device is now ready to use.")
104+ text: i18n.tr("You are ready to use your device now")
105+ color: whiteColor
47 }
106 }
48− }
49
107
50− Component {
108+ Rectangle {
51− id: forwardButton
109+ anchors {
52− LocalComponents.StackButton {
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
53− text: i18n.tr("Finish")
124+ text: i18n.tr("Get Started")
125+ fontSize: "medium"
126+ anchors.centerIn: parent
127+ }
128+ AbstractButton {
129+ objectName: "finishButton"
130+ anchors.fill: parent
54− onClicked: root.quitWizard()
131+ onClicked: root.quitWizard()
132+ }
55 }
133 }
56 }
134 }
57}
135}
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 @@
15 */
15 */
16
16
17import QtQuick 2.4
17import QtQuick 2.4
18−import AccountsService 0.1
19−import Qt.labs.folderlistmodel 2.1
20import Ubuntu.Components 1.3
18import Ubuntu.Components 1.3
21import Ubuntu.Web 0.2
19import Ubuntu.Web 0.2
22import ".." as LocalComponents
20import ".." as LocalComponents
@@ -26,86 +24,93 @@ LocalComponents.Page {
26
24
27 title: i18n.tr("Terms & Conditions")
25 title: i18n.tr("Terms & Conditions")
28 customBack: true
26 customBack: true
27+ customTitle: true
29
28
30− FolderListModel {
29+ onBackClicked: {
31− id: termsModel
30+ if (webview.visible) {
32− folder: AccountsService.hereLicensePath
31+ showBrowser(false);
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
71 } else {
32 } else {
72− return makeFileName("en", "US")
33+ pageStack.prev();
73 }
34 }
74 }
35 }
75
36
76− function loadFileContent() {
37+ function showBrowser(show) {
77− var xhr = new XMLHttpRequest
38+ if (show) {
78− xhr.open("GET", AccountsService.hereLicensePath + "/" + determineFileName())
39+ label1.visible = false;
79− xhr.onreadystatechange = function() {
40+ label2.visible = false;
80− if (xhr.readyState == XMLHttpRequest.DONE) {
41+ label3.visible = false;
81− termsLabel.text = xhr.responseText
42+ label4.visible = false;
82− }
83− }
84− xhr.send()
85− }
86−
87− onBackClicked: {
88− if (webview.visible) {
89− termsLabel.visible = true
43+ webview.visible = true;
90 } else {
44 } else {
91− pageStack.prev()
45+ webview.visible = false;
46+ label1.visible = true;
47+ label2.visible = true;
48+ label3.visible = true;
49+ label4.visible = true;
92 }
50 }
93 }
51 }
94
52
95 Column {
53 Column {
96 id: column
54 id: column
97 anchors.fill: content
55 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+ }
98
98
99 Label {
99 Label {
100− id: termsLabel
100+ id: label4
101− objectName: "termsLabel"
102 anchors.left: parent.left
101 anchors.left: parent.left
103 anchors.right: parent.right
102 anchors.right: parent.right
104 wrapMode: Text.Wrap
103 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>.");
106 onLinkActivated: {
111 onLinkActivated: {
112+ showBrowser(true);
107− webview.url = link
113+ webview.url = link;
108− termsLabel.visible = false
109 }
114 }
110 }
115 }
111
116
@@ -114,8 +119,10 @@ LocalComponents.Page {
114 objectName: "webview"
119 objectName: "webview"
115 anchors.left: parent.left
120 anchors.left: parent.left
116 anchors.right: parent.right
121 anchors.right: parent.right
122+ anchors.leftMargin: -leftMargin
123+ anchors.rightMargin: -rightMargin
117 height: parent.height
124 height: parent.height
118− visible: !termsLabel.visible
125+ visible: false
119 }
126 }
120 }
127 }
121}
128}
qml/Wizard/Pages/passwd-confirm.qml → qml/Wizard/Pages/passcode-confirm.qmlrenamed+12−29
@@ -26,11 +26,10 @@ import "../../Components" as UnityComponents
26 */
26 */
27
27
28LocalComponents.Page {
28LocalComponents.Page {
29− id: passwdConfirmPage
29+ id: passcodeConfirmPage
30− objectName: "passwdConfirmPage"
30+ objectName: "passcodeConfirmPage"
31− forwardButtonSourceComponent: forwardButton
31+ customTitle: true
32−
32+ backButtonText: i18n.tr("Cancel")
33− skip: root.passwordMethod === UbuntuSecurityPrivacyPanel.Swipe
34
33
35 // If we are entering this page, clear any saved password and get focus
34 // If we are entering this page, clear any saved password and get focus
36 onEnabledChanged: if (enabled) lockscreen.clear(false)
35 onEnabledChanged: if (enabled) lockscreen.clear(false)
@@ -38,32 +37,26 @@ LocalComponents.Page {
38 UnityComponents.Lockscreen {
37 UnityComponents.Lockscreen {
39 id: lockscreen
38 id: lockscreen
40 anchors {
39 anchors {
41− fill: parent
40+ fill: content
42− topMargin: topMargin
43− leftMargin: leftMargin
44− rightMargin: rightMargin
45− bottomMargin: buttonMargin
46 }
41 }
47
42
48− infoText: root.passwordMethod === UbuntuSecurityPrivacyPanel.Passphrase ?
43+ infoText: i18n.tr("Confirm passcode")
49− i18n.tr("Confirm passphrase") :
44+
50− i18n.tr("Confirm passcode")
45+ errorText: i18n.tr("Incorrect passcode.") + "\n" + i18n.ctr("Enter the passcode again", "Please re-enter.")
51
46
52− errorText: root.passwordMethod === UbuntuSecurityPrivacyPanel.Passphrase ?
47+ foregroundColor: textColor
53− i18n.tr("Sorry, incorrect passphrase.") + "\n" + i18n.tr("Please try again.") :
54− i18n.tr("Sorry, incorrect passcode.") + "\n" + i18n.tr("Please try again.")
55
48
56 showEmergencyCallButton: false
49 showEmergencyCallButton: false
57 showCancelButton: false
50 showCancelButton: false
58− alphaNumeric: root.passwordMethod === UbuntuSecurityPrivacyPanel.Passphrase
51+ alphaNumeric: false
59 minPinLength: 4
52 minPinLength: 4
60 maxPinLength: 4
53 maxPinLength: 4
61
54
62 onEntered: {
55 onEntered: {
63 if (passphrase === root.password) {
56 if (passphrase === root.password) {
64− confirmTimer.start()
57+ confirmTimer.start();
65 } else {
58 } else {
66− clear(true)
59+ clear(true);
67 }
60 }
68 }
61 }
69
62
@@ -73,14 +66,4 @@ LocalComponents.Page {
73 onTriggered: pageStack.next()
66 onTriggered: pageStack.next()
74 }
67 }
75 }
68 }
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− }
86}
69}
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
26 */
26 */
27
27
28LocalComponents.Page {
28LocalComponents.Page {
29− id: passwdSetPage
29+ id: passcodeSetPage
30− objectName: "passwdSetPage"
30+ objectName: "passcodeSetPage"
31− forwardButtonSourceComponent: forwardButton
31+ customTitle: true
32−
32+ backButtonText: i18n.tr("Cancel")
33− skip: root.passwordMethod === UbuntuSecurityPrivacyPanel.Swipe
34
33
35 // If we are entering this page, clear any saved password and get focus
34 // If we are entering this page, clear any saved password and get focus
36 onEnabledChanged: if (enabled) lockscreen.clear(false)
35 onEnabledChanged: if (enabled) lockscreen.clear(false)
@@ -43,52 +42,37 @@ LocalComponents.Page {
43 Timer {
42 Timer {
44 id: confirmTimer
43 id: confirmTimer
45 interval: UbuntuAnimation.SnapDuration
44 interval: UbuntuAnimation.SnapDuration
46− onTriggered: pageStack.load(Qt.resolvedUrl("passwd-confirm.qml"));
45+ onTriggered: pageStack.load(Qt.resolvedUrl("passcode-confirm.qml"));
47 }
46 }
48
47
49 UnityComponents.Lockscreen {
48 UnityComponents.Lockscreen {
50 id: lockscreen
49 id: lockscreen
51 anchors {
50 anchors {
52− fill: parent
51+ fill: content
53− topMargin: topMargin
54− leftMargin: leftMargin
55− rightMargin: rightMargin
56− bottomMargin: buttonMargin
57 }
52 }
58
53
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
62
56
63 // Note that the number four comes from PAM settings,
57 // Note that the number four comes from PAM settings,
64 // which we don't have a good way to interrogate. We
58 // which we don't have a good way to interrogate. We
65 // only do this matching instead of PAM because we want
59 // only do this matching instead of PAM because we want
66 // to set the password via PAM in a different place
60 // to set the password via PAM in a different place
67 // than this page. See comments at top of passwd-type file.
61 // 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")
69
63
70 showEmergencyCallButton: false
64 showEmergencyCallButton: false
71 showCancelButton: false
65 showCancelButton: false
72− alphaNumeric: root.passwordMethod === UbuntuSecurityPrivacyPanel.Passphrase
66+ alphaNumeric: false
73 minPinLength: 4
67 minPinLength: 4
74 maxPinLength: 4
68 maxPinLength: 4
75
69
76 onEntered: {
70 onEntered: {
77 if (passphrase.length >= 4) {
71 if (passphrase.length >= 4) {
78− passwdSetPage.confirm();
72+ passcodeSetPage.confirm();
79 } else {
73 } else {
80− lockscreen.clear(true)
74+ lockscreen.clear(true);
81 }
75 }
82 }
76 }
83 }
77 }
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− }
94}
78}
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 {
25 property bool backArrow: false
25 property bool backArrow: false
26
26
27 width: label.width
27 width: label.width
28− height: label.height + units.gu(4)
28+ height: units.gu(5)
29
29
30 Label {
30 Label {
31 id: label
31 id: label
32 anchors.verticalCenter: parent.verticalCenter
32 anchors.verticalCenter: parent.verticalCenter
33 anchors.left: parent.left
33 anchors.left: parent.left
34 anchors.right: parent.right
34 anchors.right: parent.right
35− color: enabled ? theme.palette.normal.backgroundText : Qt.darker(theme.palette.normal.backgroundText, 1.5)
35+ color: "#333333"
36− text: {
36+ opacity: enabled ? 1.0 : 0.5
37− if (backArrow) {
37+ fontSize: "medium"
38− // Translators: This is the arrow for "Back" buttons
38+ font.weight: backArrow ? Font.Light : Font.DemiBold
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))
39+ text: stackButton.text
43− }
44− }
45 horizontalAlignment: backArrow ? Text.AlignLeft : Text.AlignRight
40 horizontalAlignment: backArrow ? Text.AlignLeft : Text.AlignRight
46 }
41 }
47}
42}
qml/Wizard/Wizard.qml−9
@@ -22,9 +22,6 @@ import "../Components"
22Showable {
22Showable {
23 id: root
23 id: root
24
24
25− // The background wallpaper to use
26− property string background
27−
28 // This is a bool instead of an alias because Loader classes like to emit
25 // This is a bool instead of an alias because Loader classes like to emit
29 // changed signals for 'active' during startup even if they aren't actually
26 // changed signals for 'active' during startup even if they aren't actually
30 // changing values. Having it cached as a proper Qml bool property prevents
27 // changing values. Having it cached as a proper Qml bool property prevents
@@ -46,12 +43,6 @@ Showable {
46 active: System.wizardEnabled
43 active: System.wizardEnabled
47 source: "Pages.qml"
44 source: "Pages.qml"
48
45
49− Binding {
50− target: loader.item
51− property: "background"
52− value: root.background
53− }
54−
55 Connections {
46 Connections {
56 target: loader.item
47 target: loader.item
57 onQuit: root.hide()
48 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/CMakeLists.txt+1−1
@@ -33,7 +33,7 @@ endif()
33
33
34pkg_check_modules(ANDROID_PROPERTIES REQUIRED libandroid-properties)
34pkg_check_modules(ANDROID_PROPERTIES REQUIRED libandroid-properties)
35add_executable(${SHELL_APP} ${SOURCE_FILES})
35add_executable(${SHELL_APP} ${SOURCE_FILES})
36−qt5_use_modules(${SHELL_APP} Gui Qml Quick Test)
36+qt5_use_modules(${SHELL_APP} DBus Gui Qml Quick Test)
37
37
38if (NOT "${ANDROID_PROPERTIES_INCLUDE_DIRS}" STREQUAL "")
38if (NOT "${ANDROID_PROPERTIES_INCLUDE_DIRS}" STREQUAL "")
39 set_target_properties(${SHELL_APP} PROPERTIES INCLUDE_DIRECTORIES ${ANDROID_PROPERTIES_INCLUDE_DIRS})
39 set_target_properties(${SHELL_APP} PROPERTIES INCLUDE_DIRECTORIES ${ANDROID_PROPERTIES_INCLUDE_DIRS})
src/CachingNetworkManagerFactory.cpp+2−2
@@ -24,12 +24,12 @@
24CachingNetworkAccessManager::CachingNetworkAccessManager(QObject *parent)
24CachingNetworkAccessManager::CachingNetworkAccessManager(QObject *parent)
25 : QNetworkAccessManager(parent)
25 : QNetworkAccessManager(parent)
26{
26{
27− m_networkingStatus = new ubuntu::connectivity::NetworkingStatus(this);
27+ m_networkingStatus = new connectivityqt::Connectivity(QDBusConnection::sessionBus(), this);
28}
28}
29
29
30QNetworkReply* CachingNetworkAccessManager::createRequest(Operation op, const QNetworkRequest &request, QIODevice *outgoingData)
30QNetworkReply* CachingNetworkAccessManager::createRequest(Operation op, const QNetworkRequest &request, QIODevice *outgoingData)
31{
31{
32− if (m_networkingStatus->status() != ubuntu::connectivity::NetworkingStatus::Status::Online) {
32+ if (m_networkingStatus->status() != connectivityqt::Connectivity::Status::Online) {
33 qDebug() << "Not connected to the internet. Request for" << request.url().toString() << "will be served only from the cache.";
33 qDebug() << "Not connected to the internet. Request for" << request.url().toString() << "will be served only from the cache.";
34 QNetworkRequest req(request);
34 QNetworkRequest req(request);
35 req.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysCache);
35 req.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysCache);
src/CachingNetworkManagerFactory.h+3−2
@@ -21,10 +21,11 @@
21#include <QQmlNetworkAccessManagerFactory>
21#include <QQmlNetworkAccessManagerFactory>
22#include <QNetworkAccessManager>
22#include <QNetworkAccessManager>
23
23
24−#include <ubuntu/connectivity/networking-status.h>
24+#include <connectivityqt/connectivity.h>
25
25
26class CachingNetworkAccessManager : public QNetworkAccessManager
26class CachingNetworkAccessManager : public QNetworkAccessManager
27{
27{
28+Q_OBJECT
28public:
29public:
29 CachingNetworkAccessManager(QObject *parent = 0);
30 CachingNetworkAccessManager(QObject *parent = 0);
30
31
@@ -32,7 +33,7 @@ protected:
32 QNetworkReply* createRequest(Operation op, const QNetworkRequest &req, QIODevice *outgoingData = 0) override;
33 QNetworkReply* createRequest(Operation op, const QNetworkRequest &req, QIODevice *outgoingData = 0) override;
33
34
34private:
35private:
35− ubuntu::connectivity::NetworkingStatus* m_networkingStatus;
36+ connectivityqt::Connectivity* m_networkingStatus;
36};
37};
37
38
38class CachingNetworkManagerFactory : public QQmlNetworkAccessManagerFactory
39class CachingNetworkManagerFactory : public QQmlNetworkAccessManagerFactory
src/Dash/CMakeLists.txt+1−1
@@ -9,7 +9,7 @@ endif()
9
9
10add_executable(unity8-dash ${DASH_SRCS})
10add_executable(unity8-dash ${DASH_SRCS})
11
11
12−qt5_use_modules(unity8-dash Gui Qml Quick Test)
12+qt5_use_modules(unity8-dash DBus Gui Qml Quick Test)
13
13
14# For it to find libUbuntuGestures.so, needed by Ubuntu.Gestures QML module.
14# For it to find libUbuntuGestures.so, needed by Ubuntu.Gestures QML module.
15set_target_properties(unity8-dash PROPERTIES INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${SHELL_PRIVATE_LIBDIR}")
15set_target_properties(unity8-dash PROPERTIES INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${SHELL_PRIVATE_LIBDIR}")
src/MouseTouchAdaptor.cpp+53−9
@@ -71,6 +71,8 @@ using QTest::QTouchEventSequence;
71namespace {
71namespace {
72MouseTouchAdaptor *g_instance = nullptr;
72MouseTouchAdaptor *g_instance = nullptr;
73
73
74+const Qt::KeyboardModifiers TRI_PRESS_MODIFIER = Qt::ShiftModifier|Qt::ControlModifier|Qt::AltModifier;
75+
74Qt::MouseButton translateMouseButton(xcb_button_t detail)
76Qt::MouseButton translateMouseButton(xcb_button_t detail)
75{
77{
76 switch (detail) {
78 switch (detail) {
@@ -81,10 +83,25 @@ Qt::MouseButton translateMouseButton(xcb_button_t detail)
81 default: return Qt::NoButton;
83 default: return Qt::NoButton;
82 }
84 }
83}
85}
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+}
84} // end of anonymous namespace
98} // end of anonymous namespace
85
99
86MouseTouchAdaptor::MouseTouchAdaptor()
100MouseTouchAdaptor::MouseTouchAdaptor()
101+ : QObject(nullptr)
87− : QObject(nullptr), m_leftButtonIsPressed(false), m_enabled(true)
102+ , m_leftButtonIsPressed(false)
103+ , m_triPressModifier(false)
104+ , m_enabled(true)
88{
105{
89 QCoreApplication::instance()->installNativeEventFilter(this);
106 QCoreApplication::instance()->installNativeEventFilter(this);
90
107
@@ -192,17 +209,20 @@ bool MouseTouchAdaptor::xi2HandleEvent(xcb_ge_event_t *event)
192 return handleButtonPress(
209 return handleButtonPress(
193 static_cast<WId>(xiDeviceEvent->event),
210 static_cast<WId>(xiDeviceEvent->event),
194 xiDeviceEvent->detail,
211 xiDeviceEvent->detail,
212+ xiDeviceEvent->mods.base_mods,
195 fixed1616ToReal(xiDeviceEvent->event_x),
213 fixed1616ToReal(xiDeviceEvent->event_x),
196 fixed1616ToReal(xiDeviceEvent->event_y));
214 fixed1616ToReal(xiDeviceEvent->event_y));
197 case XI_ButtonRelease:
215 case XI_ButtonRelease:
198 return handleButtonRelease(
216 return handleButtonRelease(
199 static_cast<WId>(xiDeviceEvent->event),
217 static_cast<WId>(xiDeviceEvent->event),
200 xiDeviceEvent->detail,
218 xiDeviceEvent->detail,
219+ xiDeviceEvent->mods.base_mods,
201 fixed1616ToReal(xiDeviceEvent->event_x),
220 fixed1616ToReal(xiDeviceEvent->event_x),
202 fixed1616ToReal(xiDeviceEvent->event_y));
221 fixed1616ToReal(xiDeviceEvent->event_y));
203 case XI_Motion:
222 case XI_Motion:
204 return handleMotionNotify(
223 return handleMotionNotify(
205 static_cast<WId>(xiDeviceEvent->event),
224 static_cast<WId>(xiDeviceEvent->event),
225+ xiDeviceEvent->mods.base_mods,
206 fixed1616ToReal(xiDeviceEvent->event_x),
226 fixed1616ToReal(xiDeviceEvent->event_x),
207 fixed1616ToReal(xiDeviceEvent->event_y));
227 fixed1616ToReal(xiDeviceEvent->event_y));
208 return true;
228 return true;
@@ -232,17 +252,18 @@ bool MouseTouchAdaptor::nativeEventFilter(const QByteArray & eventType,
232 switch (xcbEvent->response_type & ~0x80) {
252 switch (xcbEvent->response_type & ~0x80) {
233 case XCB_BUTTON_PRESS: {
253 case XCB_BUTTON_PRESS: {
234 auto pressEvent = reinterpret_cast<xcb_button_press_event_t *>(xcbEvent);
254 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,
236 pressEvent->event_x, pressEvent->event_y);
256 pressEvent->event_x, pressEvent->event_y);
237 }
257 }
238 case XCB_BUTTON_RELEASE: {
258 case XCB_BUTTON_RELEASE: {
239 auto releaseEvent = reinterpret_cast<xcb_button_release_event_t *>(xcbEvent);
259 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,
241 releaseEvent->event_x, releaseEvent->event_y);
261 releaseEvent->event_x, releaseEvent->event_y);
242 }
262 }
243 case XCB_MOTION_NOTIFY: {
263 case XCB_MOTION_NOTIFY: {
244 auto motionEvent = reinterpret_cast<xcb_motion_notify_event_t *>(xcbEvent);
264 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);
246 }
267 }
247 case XCB_GE_GENERIC:
268 case XCB_GE_GENERIC:
248 if (m_xi2Enabled) {
269 if (m_xi2Enabled) {
@@ -255,9 +276,10 @@ bool MouseTouchAdaptor::nativeEventFilter(const QByteArray & eventType,
255 };
276 };
256}
277}
257
278
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)
259{
280{
260 Qt::MouseButton button = translateMouseButton(detail);
281 Qt::MouseButton button = translateMouseButton(detail);
282+ Qt::KeyboardModifiers qtMod = translateMofidier(modifiers);
261
283
262 // Just eat the event if it wasn't a left mouse press
284 // Just eat the event if it wasn't a left mouse press
263 if (button != Qt::LeftButton)
285 if (button != Qt::LeftButton)
@@ -270,19 +292,24 @@ bool MouseTouchAdaptor::handleButtonPress(WId windowId, uint32_t detail, int x,
270 QTouchEventSequence touchEvent = QTest::touchEvent(targetWindow, m_touchDevice,
292 QTouchEventSequence touchEvent = QTest::touchEvent(targetWindow, m_touchDevice,
271 false /* autoCommit */);
293 false /* autoCommit */);
272 touchEvent.press(0 /* touchId */, windowPos);
294 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+ }
273 touchEvent.commit(false /* processEvents */);
300 touchEvent.commit(false /* processEvents */);
274
301
275 m_leftButtonIsPressed = true;
302 m_leftButtonIsPressed = true;
276 return true;
303 return true;
277}
304}
278
305
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)
280{
307{
281 Qt::MouseButton button = translateMouseButton(detail);
308 Qt::MouseButton button = translateMouseButton(detail);
282
309
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
284 if (button != Qt::LeftButton)
311 if (button != Qt::LeftButton)
285− return true;
312+ return false;
286
313
287 QWindow *targetWindow = findQWindowWithXWindowID(windowId);
314 QWindow *targetWindow = findQWindowWithXWindowID(windowId);
288
315
@@ -291,17 +318,23 @@ bool MouseTouchAdaptor::handleButtonRelease(WId windowId, uint32_t detail, int x
291 QTouchEventSequence touchEvent = QTest::touchEvent(targetWindow, m_touchDevice,
318 QTouchEventSequence touchEvent = QTest::touchEvent(targetWindow, m_touchDevice,
292 false /* autoCommit */);
319 false /* autoCommit */);
293 touchEvent.release(0 /* touchId */, windowPos);
320 touchEvent.release(0 /* touchId */, windowPos);
321+ if (m_triPressModifier) {
322+ touchEvent.release(1, windowPos);
323+ touchEvent.release(2, windowPos);
324+ }
294 touchEvent.commit(false /* processEvents */);
325 touchEvent.commit(false /* processEvents */);
295
326
296 m_leftButtonIsPressed = false;
327 m_leftButtonIsPressed = false;
328+ m_triPressModifier = false;
297 return true;
329 return true;
298}
330}
299
331
300−bool MouseTouchAdaptor::handleMotionNotify(WId windowId, int x, int y)
332+bool MouseTouchAdaptor::handleMotionNotify(WId windowId, uint32_t modifiers, int x, int y)
301{
333{
302 if (!m_leftButtonIsPressed) {
334 if (!m_leftButtonIsPressed) {
303 return true;
335 return true;
304 }
336 }
337+ Qt::KeyboardModifiers qtMod = translateMofidier(modifiers);
305
338
306 QWindow *targetWindow = findQWindowWithXWindowID(windowId);
339 QWindow *targetWindow = findQWindowWithXWindowID(windowId);
307
340
@@ -310,6 +343,17 @@ bool MouseTouchAdaptor::handleMotionNotify(WId windowId, int x, int y)
310 QTouchEventSequence touchEvent = QTest::touchEvent(targetWindow, m_touchDevice,
343 QTouchEventSequence touchEvent = QTest::touchEvent(targetWindow, m_touchDevice,
311 false /* autoCommit */);
344 false /* autoCommit */);
312 touchEvent.move(0 /* touchId */, windowPos);
345 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+ }
313 touchEvent.commit(false /* processEvents */);
357 touchEvent.commit(false /* processEvents */);
314
358
315 return true;
359 return true;
src/MouseTouchAdaptor.h+5−3
@@ -51,13 +51,15 @@ private:
51 void fetchXInput2Info();
51 void fetchXInput2Info();
52 bool xi2HandleEvent(xcb_ge_event_t *event);
52 bool xi2HandleEvent(xcb_ge_event_t *event);
53
53
54− bool handleButtonPress(WId windowId, uint32_t detail, 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, int x, int y);
55+ bool handleButtonRelease(WId windowId, uint32_t detail, uint32_t modifiers, int x, int y);
56− bool handleMotionNotify(WId windowId, int x, int y);
56+ bool handleMotionNotify(WId windowId, uint32_t modifiers, int x, int y);
57 QWindow *findQWindowWithXWindowID(WId windowId);
57 QWindow *findQWindowWithXWindowID(WId windowId);
58
58
59 QTouchDevice *m_touchDevice;
59 QTouchDevice *m_touchDevice;
60 bool m_leftButtonIsPressed;
60 bool m_leftButtonIsPressed;
61+ bool m_triPressModifier;
62+
61
63
62 bool m_enabled;
64 bool m_enabled;
63
65
tests/autopilot/unity8/dash.py+1−2
@@ -211,8 +211,7 @@ class GenericScopeView(ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase):
211 self.get_root_instance().select_single(
211 self.get_root_instance().select_single(
212 objectName='processingIndicator').visible.wait_for(False)
212 objectName='processingIndicator').visible.wait_for(False)
213 return preview_list.select_single(
213 return preview_list.select_single(
214− Preview, objectName='preview{}'.format(
214+ Preview, objectName='preview')
215− preview_list.initialIndex))
216
215
217 @autopilot_logging.log_action(logger.debug)
216 @autopilot_logging.log_action(logger.debug)
218 def click_scope_item(self, category, title, press_duration=0.10):
217 def click_scope_item(self, category, title, press_duration=0.10):
tests/autopilot/unity8/shell/tests/test_helpers.py−2
@@ -117,13 +117,11 @@ class GenericScopeViewHelperTestCase(tests.DashBaseTestCase):
117 def test_open_preview(self):
117 def test_open_preview(self):
118 preview = self.generic_scope.open_preview('0', 'Title.0.0')
118 preview = self.generic_scope.open_preview('0', 'Title.0.0')
119 self.assertIsInstance(preview, dash_helpers.Preview)
119 self.assertIsInstance(preview, dash_helpers.Preview)
120− self.assertTrue(preview.isCurrent)
121
120
122 def test_open_preview_of_non_visible_item(self):
121 def test_open_preview_of_non_visible_item(self):
123 """Open an item that requires swiping to make it visible."""
122 """Open an item that requires swiping to make it visible."""
124 preview = self.generic_scope.open_preview('2', 'Title.2.0')
123 preview = self.generic_scope.open_preview('2', 'Title.2.0')
125 self.assertIsInstance(preview, dash_helpers.Preview)
124 self.assertIsInstance(preview, dash_helpers.Preview)
126− self.assertTrue(preview.isCurrent)
127
125
128
126
129class DashAppsHelperTestCase(tests.DashBaseTestCase):
127class DashAppsHelperTestCase(tests.DashBaseTestCase):
tests/libs/UbuntuGestures/tst_TouchRegistry.cpp+59
@@ -42,6 +42,10 @@ public:
42 QSet<int> ownedTouches;
42 QSet<int> ownedTouches;
43 QSet<int> lostTouches;
43 QSet<int> lostTouches;
44 QList<TouchMemento> unownedTouchEvents;
44 QList<TouchMemento> unownedTouchEvents;
45+
46+Q_SIGNALS:
47+ void gainedOwnership();
48+ void lostOwnership();
45};
49};
46
50
47class tst_TouchRegistry : public QObject
51class tst_TouchRegistry : public QObject
@@ -68,6 +72,7 @@ private Q_SLOTS:
68 void removeOldUndecidedCandidates();
72 void removeOldUndecidedCandidates();
69 void interimOwnerWontGetUnownedTouchEvents();
73 void interimOwnerWontGetUnownedTouchEvents();
70 void candidateVanishes();
74 void candidateVanishes();
75+ void candicateOwnershipReentrace();
71
76
72private:
77private:
73 TouchRegistry *touchRegistry;
78 TouchRegistry *touchRegistry;
@@ -864,6 +869,58 @@ void tst_TouchRegistry::candidateVanishes()
864 QVERIFY(mainCandidate.ownedTouches.contains(0));
869 QVERIFY(mainCandidate.ownedTouches.contains(0));
865}
870}
866
871
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+
867////////////// TouchMemento //////////
924////////////// TouchMemento //////////
868
925
869TouchMemento::TouchMemento(const QTouchEvent *touchEvent)
926TouchMemento::TouchMemento(const QTouchEvent *touchEvent)
@@ -897,8 +954,10 @@ bool DummyCandidate::event(QEvent *e)
897
954
898 if (touchOwnershipEvent->gained()) {
955 if (touchOwnershipEvent->gained()) {
899 ownedTouches.insert(touchOwnershipEvent->touchId());
956 ownedTouches.insert(touchOwnershipEvent->touchId());
957+ Q_EMIT gainedOwnership();
900 } else {
958 } else {
901 lostTouches.insert(touchOwnershipEvent->touchId());
959 lostTouches.insert(touchOwnershipEvent->touchId());
960+ Q_EMIT lostOwnership();
902 }
961 }
903 return true;
962 return true;
904 } else if (e->type() == UnownedTouchEvent::unownedTouchEventType()) {
963 } else if (e->type() == UnownedTouchEvent::unownedTouchEventType()) {
tests/mocks/AccountsService/AccountsService.cpp+23−3
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013, 2015 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
5 * it under the terms of the GNU General Public License as published by
@@ -12,8 +12,6 @@
12 *
12 *
13 * You should have received a copy of the GNU General Public License
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/>.
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− *
16− * Author: Michael Terry <michael.terry@canonical.com>
17 */
15 */
18
16
19#include "AccountsService.h"
17#include "AccountsService.h"
@@ -172,3 +170,25 @@ bool AccountsService::hereLicensePathValid() const
172{
170{
173 return !m_hereLicensePath.isNull();
171 return !m_hereLicensePath.isNull();
174}
172}
173+
174+QString AccountsService::realName() const
175+{
176+ return m_realName;
177+}
178+
179+void AccountsService::setRealName(const QString &realName)
180+{
181+ m_realName = realName;
182+ Q_EMIT realNameChanged();
183+}
184+
185+QString AccountsService::email() const
186+{
187+ return m_email;
188+}
189+
190+void AccountsService::setEmail(const QString &email)
191+{
192+ m_email = email;
193+ Q_EMIT emailChanged();
194+}
tests/mocks/AccountsService/AccountsService.h+12−5
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013, 2015 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
5 * it under the terms of the GNU General Public License as published by
@@ -12,9 +12,6 @@
12 *
12 *
13 * You should have received a copy of the GNU General Public License
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/>.
14 * 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>
18 */
15 */
19
16
20#ifndef UNITY_MOCK_ACCOUNTSSERVICE_H
17#ifndef UNITY_MOCK_ACCOUNTSSERVICE_H
@@ -75,11 +72,13 @@ class AccountsService: public QObject
75 Q_PROPERTY(bool hereLicensePathValid // qml sees a null string as "", so we use proxy setting for that
72 Q_PROPERTY(bool hereLicensePathValid // qml sees a null string as "", so we use proxy setting for that
76 READ hereLicensePathValid
73 READ hereLicensePathValid
77 NOTIFY hereLicensePathChanged)
74 NOTIFY hereLicensePathChanged)
75+ Q_PROPERTY(QString realName READ realName WRITE setRealName NOTIFY realNameChanged)
76+ Q_PROPERTY(QString email READ email WRITE setEmail NOTIFY emailChanged)
78
77
79public:
78public:
80 enum PasswordDisplayHint {
79 enum PasswordDisplayHint {
81 Keyboard,
80 Keyboard,
82− Numeric,
81+ Numeric
83 };
82 };
84
83
85 explicit AccountsService(QObject *parent = 0);
84 explicit AccountsService(QObject *parent = 0);
@@ -107,6 +106,10 @@ public:
107 QString hereLicensePath() const;
106 QString hereLicensePath() const;
108 void setHereLicensePath(const QString &path);
107 void setHereLicensePath(const QString &path);
109 bool hereLicensePathValid() const;
108 bool hereLicensePathValid() const;
109+ QString realName() const;
110+ void setRealName(const QString &realName);
111+ QString email() const;
112+ void setEmail(const QString &email);
110
113
111Q_SIGNALS:
114Q_SIGNALS:
112 void userChanged();
115 void userChanged();
@@ -120,6 +123,8 @@ Q_SIGNALS:
120 void failedLoginsChanged();
123 void failedLoginsChanged();
121 void hereEnabledChanged();
124 void hereEnabledChanged();
122 void hereLicensePathChanged();
125 void hereLicensePathChanged();
126+ void realNameChanged();
127+ void emailChanged();
123
128
124private:
129private:
125 bool m_enableLauncherWhileLocked;
130 bool m_enableLauncherWhileLocked;
@@ -132,6 +137,8 @@ private:
132 QStringList m_demoEdgesCompleted;
137 QStringList m_demoEdgesCompleted;
133 bool m_hereEnabled;
138 bool m_hereEnabled;
134 QString m_hereLicensePath;
139 QString m_hereLicensePath;
140+ QString m_realName;
141+ QString m_email;
135};
142};
136
143
137#endif
144#endif
tests/mocks/GSettings.1.0/fake_gsettings.cpp+64
@@ -22,6 +22,8 @@ GSettingsControllerQml* GSettingsControllerQml::s_controllerInstance = 0;
22
22
23GSettingsControllerQml::GSettingsControllerQml()
23GSettingsControllerQml::GSettingsControllerQml()
24 : m_usageMode("Staged")
24 : m_usageMode("Staged")
25+ , m_autohideLauncher(false)
26+ , m_launcherWidth(8)
25{
27{
26}
28}
27
29
@@ -88,6 +90,32 @@ void GSettingsControllerQml::setLifecycleExemptAppids(const QStringList &appIds)
88 }
90 }
89}
91}
90
92
93+bool GSettingsControllerQml::autohideLauncher() const
94+{
95+ return m_autohideLauncher;
96+}
97+
98+void GSettingsControllerQml::setAutohideLauncher(bool autohideLauncher)
99+{
100+ if (m_autohideLauncher != autohideLauncher) {
101+ m_autohideLauncher = autohideLauncher;
102+ Q_EMIT autohideLauncherChanged(autohideLauncher);
103+ }
104+}
105+
106+int GSettingsControllerQml::launcherWidth() const
107+{
108+ return m_launcherWidth;
109+}
110+
111+void GSettingsControllerQml::setLauncherWidth(int launcherWidth)
112+{
113+ if (m_launcherWidth != launcherWidth) {
114+ m_launcherWidth = launcherWidth;
115+ Q_EMIT launcherWidthChanged(launcherWidth);
116+ }
117+}
118+
91GSettingsSchemaQml::GSettingsSchemaQml(QObject *parent): QObject(parent) {
119GSettingsSchemaQml::GSettingsSchemaQml(QObject *parent): QObject(parent) {
92}
120}
93
121
@@ -129,6 +157,10 @@ GSettingsQml::GSettingsQml(QObject *parent)
129 this, &GSettingsQml::lockedOutTimeChanged);
157 this, &GSettingsQml::lockedOutTimeChanged);
130 connect(GSettingsControllerQml::instance(), &GSettingsControllerQml::lifecycleExemptAppidsChanged,
158 connect(GSettingsControllerQml::instance(), &GSettingsControllerQml::lifecycleExemptAppidsChanged,
131 this, &GSettingsQml::lifecycleExemptAppidsChanged);
159 this, &GSettingsQml::lifecycleExemptAppidsChanged);
160+ connect(GSettingsControllerQml::instance(), &GSettingsControllerQml::autohideLauncherChanged,
161+ this, &GSettingsQml::autohideLauncherChanged);
162+ connect(GSettingsControllerQml::instance(), &GSettingsControllerQml::launcherWidthChanged,
163+ this, &GSettingsQml::launcherWidthChanged);
132}
164}
133
165
134GSettingsSchemaQml * GSettingsQml::schema() const {
166GSettingsSchemaQml * GSettingsQml::schema() const {
@@ -192,9 +224,41 @@ QStringList GSettingsQml::lifecycleExemptAppids() const
192 }
224 }
193}
225}
194
226
227+bool GSettingsQml::autohideLauncher() const
228+{
229+ if (m_schema->id() == "com.canonical.Unity8") {
230+ return GSettingsControllerQml::instance()->autohideLauncher();
231+ } else {
232+ return false;
233+ }
234+}
235+
236+int GSettingsQml::launcherWidth() const
237+{
238+ if (m_schema->id() == "com.canonical.Unity8") {
239+ return GSettingsControllerQml::instance()->launcherWidth();
240+ } else {
241+ return false;
242+ }
243+}
244+
195void GSettingsQml::setLifecycleExemptAppids(const QStringList &appIds)
245void GSettingsQml::setLifecycleExemptAppids(const QStringList &appIds)
196{
246{
197 if (m_schema->id() == "com.canonical.qtmir") {
247 if (m_schema->id() == "com.canonical.qtmir") {
198 GSettingsControllerQml::instance()->setLifecycleExemptAppids(appIds);
248 GSettingsControllerQml::instance()->setLifecycleExemptAppids(appIds);
199 }
249 }
200}
250}
251+
252+void GSettingsQml::setAutohideLauncher(bool autohideLauncher)
253+{
254+ if (m_schema->id() == "com.canonical.Unity8") {
255+ GSettingsControllerQml::instance()->setAutohideLauncher(autohideLauncher);
256+ }
257+}
258+
259+void GSettingsQml::setLauncherWidth(int launcherWidth)
260+{
261+ if (m_schema->id() == "com.canonical.Unity8") {
262+ GSettingsControllerQml::instance()->setLauncherWidth(launcherWidth);
263+ }
264+}
tests/mocks/GSettings.1.0/fake_gsettings.h+18
@@ -50,6 +50,8 @@ class GSettingsQml: public QObject
50 Q_PROPERTY(QString usageMode READ usageMode WRITE setUsageMode NOTIFY usageModeChanged)
50 Q_PROPERTY(QString usageMode READ usageMode WRITE setUsageMode NOTIFY usageModeChanged)
51 Q_PROPERTY(qint64 lockedOutTime READ lockedOutTime WRITE setLockedOutTime NOTIFY lockedOutTimeChanged)
51 Q_PROPERTY(qint64 lockedOutTime READ lockedOutTime WRITE setLockedOutTime NOTIFY lockedOutTimeChanged)
52 Q_PROPERTY(QStringList lifecycleExemptAppids READ lifecycleExemptAppids WRITE setLifecycleExemptAppids NOTIFY lifecycleExemptAppidsChanged)
52 Q_PROPERTY(QStringList lifecycleExemptAppids READ lifecycleExemptAppids WRITE setLifecycleExemptAppids NOTIFY lifecycleExemptAppidsChanged)
53+ Q_PROPERTY(bool autohideLauncher READ autohideLauncher WRITE setAutohideLauncher NOTIFY autohideLauncherChanged)
54+ Q_PROPERTY(int launcherWidth READ launcherWidth WRITE setLauncherWidth NOTIFY launcherWidthChanged)
53
55
54public:
56public:
55 GSettingsQml(QObject *parent = nullptr);
57 GSettingsQml(QObject *parent = nullptr);
@@ -59,11 +61,15 @@ public:
59 QString usageMode() const;
61 QString usageMode() const;
60 qint64 lockedOutTime() const;
62 qint64 lockedOutTime() const;
61 QStringList lifecycleExemptAppids() const;
63 QStringList lifecycleExemptAppids() const;
64+ bool autohideLauncher() const;
65+ int launcherWidth() const;
62
66
63 void setPictureUri(const QString &str);
67 void setPictureUri(const QString &str);
64 void setUsageMode(const QString &usageMode);
68 void setUsageMode(const QString &usageMode);
65 void setLockedOutTime(qint64 timestamp);
69 void setLockedOutTime(qint64 timestamp);
66 void setLifecycleExemptAppids(const QStringList &appIds);
70 void setLifecycleExemptAppids(const QStringList &appIds);
71+ void setAutohideLauncher(bool autohideLauncher);
72+ void setLauncherWidth(int launcherWidth);
67
73
68Q_SIGNALS:
74Q_SIGNALS:
69 void schemaChanged();
75 void schemaChanged();
@@ -71,6 +77,8 @@ Q_SIGNALS:
71 void usageModeChanged(const QString&);
77 void usageModeChanged(const QString&);
72 void lockedOutTimeChanged(qint64);
78 void lockedOutTimeChanged(qint64);
73 void lifecycleExemptAppidsChanged(const QStringList &);
79 void lifecycleExemptAppidsChanged(const QStringList &);
80+ void autohideLauncherChanged(bool);
81+ void launcherWidthChanged(int launcherWidth);
74
82
75private:
83private:
76 GSettingsSchemaQml* m_schema;
84 GSettingsSchemaQml* m_schema;
@@ -98,11 +106,19 @@ public:
98 QStringList lifecycleExemptAppids() const;
106 QStringList lifecycleExemptAppids() const;
99 Q_INVOKABLE void setLifecycleExemptAppids(const QStringList &appIds);
107 Q_INVOKABLE void setLifecycleExemptAppids(const QStringList &appIds);
100
108
109+ bool autohideLauncher() const;
110+ Q_INVOKABLE void setAutohideLauncher(bool autohideLauncher);
111+
112+ int launcherWidth() const;
113+ Q_INVOKABLE void setLauncherWidth(int launcherWidth);
114+
101Q_SIGNALS:
115Q_SIGNALS:
102 void pictureUriChanged(const QString&);
116 void pictureUriChanged(const QString&);
103 void usageModeChanged(const QString&);
117 void usageModeChanged(const QString&);
104 void lockedOutTimeChanged(qint64 timestamp);
118 void lockedOutTimeChanged(qint64 timestamp);
105 void lifecycleExemptAppidsChanged(const QStringList&);
119 void lifecycleExemptAppidsChanged(const QStringList&);
120+ void autohideLauncherChanged(bool autohideLauncher);
121+ void launcherWidthChanged(int launcherWidth);
106
122
107private:
123private:
108 GSettingsControllerQml();
124 GSettingsControllerQml();
@@ -111,6 +127,8 @@ private:
111 QString m_usageMode;
127 QString m_usageMode;
112 qint64 m_lockedOutTime;
128 qint64 m_lockedOutTime;
113 QStringList m_lifecycleExemptAppids;
129 QStringList m_lifecycleExemptAppids;
130+ bool m_autohideLauncher;
131+ int m_launcherWidth;
114
132
115 static GSettingsControllerQml* s_controllerInstance;
133 static GSettingsControllerQml* s_controllerInstance;
116 QList<GSettingsQml *> m_registeredGSettings;
134 QList<GSettingsQml *> m_registeredGSettings;
tests/mocks/IntegratedLightDM/liblightdm/UsersModel.cpp−14
@@ -47,20 +47,6 @@ UsersModel::UsersModel(QObject *parent) :
47 roles[HasMessagesRole] = "hasMessages";
47 roles[HasMessagesRole] = "hasMessages";
48 roles[ImagePathRole] = "imagePath";
48 roles[ImagePathRole] = "imagePath";
49 setRoleNames(roles);
49 setRoleNames(roles);
50−
51− // Now modify our mock user backgrounds
52− QDir bgdir = QDir("/usr/share/demo-assets/shell/backgrounds/");
53− QStringList backgrounds = bgdir.entryList(QDir::Files | QDir::NoDotAndDotDot);
54−
55− for (int i = 0, j = 0; i < d->entries.size(); i++) {
56− Entry &entry = d->entries[i];
57− if (entry.background.isNull() && !backgrounds.isEmpty()) {
58− entry.background = bgdir.filePath(backgrounds[j++]);
59− if (j >= backgrounds.length()) {
60− j = 0;
61− }
62− }
63− }
64}
50}
65
51
66UsersModel::~UsersModel()
52UsersModel::~UsersModel()
tests/mocks/MeeGo/QOfono/MockOfonoSimManager.qml+1
@@ -23,6 +23,7 @@ Item {
23 property string modemPath
23 property string modemPath
24 readonly property alias present: d.present
24 readonly property alias present: d.present
25 readonly property alias ready: d.ready
25 readonly property alias ready: d.ready
26+ readonly property var preferredLanguages: ["de", "fr", "it", "en"]
26
27
27 QtObject {
28 QtObject {
28 id: d
29 id: d
tests/mocks/Ubuntu/Connectivity/plugin.cpp+1
@@ -36,6 +36,7 @@ QmlConnectivityNetworkingPlugin::registerTypes(const char *uri)
36{
36{
37 // @uri Ubuntu.Connectivity
37 // @uri Ubuntu.Connectivity
38 qmlRegisterSingletonType<NetworkingStatus>(uri, 1, 0, "NetworkingStatus", networkingStatusSingletonProvider);
38 qmlRegisterSingletonType<NetworkingStatus>(uri, 1, 0, "NetworkingStatus", networkingStatusSingletonProvider);
39+ qmlRegisterSingletonType<NetworkingStatus>(uri, 1, 0, "Connectivity", networkingStatusSingletonProvider);
39}
40}
40
41
41void
42void
tests/mocks/Ubuntu/SystemSettings/CMakeLists.txt+2
@@ -1,2 +1,4 @@
1add_subdirectory(LanguagePlugin)
1add_subdirectory(LanguagePlugin)
2add_subdirectory(SecurityPrivacy)
2add_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 @@
17import QtQuick 2.4
17import QtQuick 2.4
18
18
19Item {
19Item {
20− property var languageNames: ["English", "French", "Spanish"]
20+ property var languageNames: ["English (United States)", "French (France)", "Spanish (Spain)"]
21− property var languageCodes: ["en", "fr", "es"]
21+ property var languageCodes: ["en_US", "fr_FR", "es_ES"]
22 property int currentLanguage: 0
22 property int currentLanguage: 0
23}
23}
tests/mocks/Ubuntu/SystemSettings/SecurityPrivacy/MockSecurityPrivacy.cpp+1−1
@@ -31,5 +31,5 @@ QString MockSecurityPrivacy::setSecurity(const QString &oldPasswd, const QString
31{
31{
32 m_type = newType;
32 m_type = newType;
33 Q_EMIT setSecurityCalled(oldPasswd, newPasswd, newType);
33 Q_EMIT setSecurityCalled(oldPasswd, newPasswd, newType);
34− return "";
34+ return QString();
35}
35}
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/ApplicationManager.cpp+10−25
@@ -151,10 +151,6 @@ void ApplicationManager::add(ApplicationInfo *application) {
151
151
152 beginInsertRows(QModelIndex(), m_runningApplications.size(), m_runningApplications.size());
152 beginInsertRows(QModelIndex(), m_runningApplications.size(), m_runningApplications.size());
153 m_runningApplications.append(application);
153 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
158
154
159 connect(application, &ApplicationInfo::sessionChanged, this, [application, this]() {
155 connect(application, &ApplicationInfo::sessionChanged, this, [application, this]() {
160 QModelIndex appIndex = findIndex(application);
156 QModelIndex appIndex = findIndex(application);
@@ -171,6 +167,16 @@ void ApplicationManager::add(ApplicationInfo *application) {
171 if (!appIndex.isValid()) return;
167 if (!appIndex.isValid()) return;
172 Q_EMIT dataChanged(appIndex, appIndex, QVector<int>() << ApplicationManager::RoleState);
168 Q_EMIT dataChanged(appIndex, appIndex, QVector<int>() << ApplicationManager::RoleState);
173 });
169 });
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
174}
180}
175
181
176void ApplicationManager::remove(ApplicationInfo *application) {
182void ApplicationManager::remove(ApplicationInfo *application) {
@@ -202,23 +208,11 @@ void ApplicationManager::move(int from, int to) {
202
208
203ApplicationInfo* ApplicationManager::startApplication(const QString &appId,
209ApplicationInfo* ApplicationManager::startApplication(const QString &appId,
204 const QStringList &arguments)
210 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)
212{
211{
213 Q_UNUSED(arguments)
212 Q_UNUSED(arguments)
214 ApplicationInfo *application = add(appId);
213 ApplicationInfo *application = add(appId);
215 if (!application)
214 if (!application)
216 return 0;
215 return 0;
217−
218− if (flags.testFlag(ApplicationManager::ForceMainStage)
219− && application->stage() == ApplicationInfo::SideStage) {
220− application->setStage(ApplicationInfo::MainStage);
221− }
222 application->setState(ApplicationInfo::Starting);
216 application->setState(ApplicationInfo::Starting);
223
217
224 return application;
218 return application;
@@ -317,7 +311,6 @@ void ApplicationManager::buildListOfAvailableApplications()
317 application->setAppId("unity8-dash");
311 application->setAppId("unity8-dash");
318 application->setName("Unity 8 Mock Dash");
312 application->setName("Unity 8 Mock Dash");
319 application->setScreenshotId("unity8-dash");
313 application->setScreenshotId("unity8-dash");
320− application->setStage(ApplicationInfo::MainStage);
321 application->setSupportedOrientations(Qt::PrimaryOrientation);
314 application->setSupportedOrientations(Qt::PrimaryOrientation);
322 m_availableApplications.append(application);
315 m_availableApplications.append(application);
323
316
@@ -326,7 +319,6 @@ void ApplicationManager::buildListOfAvailableApplications()
326 application->setName("Dialer");
319 application->setName("Dialer");
327 application->setScreenshotId("dialer");
320 application->setScreenshotId("dialer");
328 application->setIconId("dialer-app");
321 application->setIconId("dialer-app");
329− application->setStage(ApplicationInfo::SideStage);
330 application->setSupportedOrientations(Qt::PortraitOrientation
322 application->setSupportedOrientations(Qt::PortraitOrientation
331 | Qt::InvertedPortraitOrientation);
323 | Qt::InvertedPortraitOrientation);
332 m_availableApplications.append(application);
324 m_availableApplications.append(application);
@@ -350,7 +342,6 @@ void ApplicationManager::buildListOfAvailableApplications()
350 application->setScreenshotId("gallery");
342 application->setScreenshotId("gallery");
351 application->setIconId("gallery");
343 application->setIconId("gallery");
352 application->setFullscreen(true);
344 application->setFullscreen(true);
353− application->setStage(ApplicationInfo::MainStage);
354 m_availableApplications.append(application);
345 m_availableApplications.append(application);
355
346
356 application = new ApplicationInfo(this);
347 application = new ApplicationInfo(this);
@@ -358,7 +349,6 @@ void ApplicationManager::buildListOfAvailableApplications()
358 application->setName("Facebook");
349 application->setName("Facebook");
359 application->setScreenshotId("facebook");
350 application->setScreenshotId("facebook");
360 application->setIconId("facebook");
351 application->setIconId("facebook");
361− application->setStage(ApplicationInfo::SideStage);
362 m_availableApplications.append(application);
352 m_availableApplications.append(application);
363
353
364 application = new ApplicationInfo(this);
354 application = new ApplicationInfo(this);
@@ -374,7 +364,6 @@ void ApplicationManager::buildListOfAvailableApplications()
374 application->setName("Twitter");
364 application->setName("Twitter");
375 application->setScreenshotId("twitter");
365 application->setScreenshotId("twitter");
376 application->setIconId("twitter");
366 application->setIconId("twitter");
377− application->setStage(ApplicationInfo::SideStage);
378 m_availableApplications.append(application);
367 m_availableApplications.append(application);
379
368
380 application = new ApplicationInfo(this);
369 application = new ApplicationInfo(this);
@@ -390,7 +379,6 @@ void ApplicationManager::buildListOfAvailableApplications()
390 application->setIconId("gmail");
379 application->setIconId("gmail");
391 application->setScreenshotId("gmail-webapp.svg");
380 application->setScreenshotId("gmail-webapp.svg");
392 application->setFullscreen(false);
381 application->setFullscreen(false);
393− application->setStage(ApplicationInfo::MainStage);
394 application->setSupportedOrientations(Qt::PortraitOrientation
382 application->setSupportedOrientations(Qt::PortraitOrientation
395 | Qt::LandscapeOrientation
383 | Qt::LandscapeOrientation
396 | Qt::InvertedPortraitOrientation
384 | Qt::InvertedPortraitOrientation
@@ -403,7 +391,6 @@ void ApplicationManager::buildListOfAvailableApplications()
403 application->setIconId("soundcloud");
391 application->setIconId("soundcloud");
404 application->setScreenshotId("music");
392 application->setScreenshotId("music");
405 application->setFullscreen(false);
393 application->setFullscreen(false);
406− application->setStage(ApplicationInfo::MainStage);
407 application->setSupportedOrientations(Qt::PortraitOrientation
394 application->setSupportedOrientations(Qt::PortraitOrientation
408 | Qt::LandscapeOrientation
395 | Qt::LandscapeOrientation
409 | Qt::InvertedPortraitOrientation
396 | Qt::InvertedPortraitOrientation
@@ -423,14 +410,12 @@ void ApplicationManager::buildListOfAvailableApplications()
423 application->setAppId("notes-app");
410 application->setAppId("notes-app");
424 application->setName("Notepad");
411 application->setName("Notepad");
425 application->setIconId("notepad");
412 application->setIconId("notepad");
426− application->setStage(ApplicationInfo::SideStage);
427 m_availableApplications.append(application);
413 m_availableApplications.append(application);
428
414
429 application = new ApplicationInfo(this);
415 application = new ApplicationInfo(this);
430 application->setAppId("calendar-app");
416 application->setAppId("calendar-app");
431 application->setName("Calendar");
417 application->setName("Calendar");
432 application->setIconId("calendar");
418 application->setIconId("calendar");
433− application->setStage(ApplicationInfo::SideStage);
434 m_availableApplications.append(application);
419 m_availableApplications.append(application);
435
420
436 application = new ApplicationInfo(this);
421 application = new ApplicationInfo(this);
tests/mocks/Unity/Application/ApplicationManager.h−8
@@ -46,11 +46,6 @@ class ApplicationManager : public ApplicationManagerInterface {
46 RoleSession = RoleExemptFromLifecycle+1,
46 RoleSession = RoleExemptFromLifecycle+1,
47 RoleFullscreen,
47 RoleFullscreen,
48 };
48 };
49− enum Flag {
50− NoFlag = 0x0,
51− ForceMainStage = 0x1,
52− };
53− Q_DECLARE_FLAGS(ExecFlags, Flag)
54
49
55 // QAbstractItemModel methods.
50 // QAbstractItemModel methods.
56 int rowCount(const QModelIndex& parent = QModelIndex()) const override;
51 int rowCount(const QModelIndex& parent = QModelIndex()) const override;
@@ -65,7 +60,6 @@ class ApplicationManager : public ApplicationManagerInterface {
65 Q_INVOKABLE bool focusApplication(const QString &appId) override;
60 Q_INVOKABLE bool focusApplication(const QString &appId) override;
66 Q_INVOKABLE void unfocusCurrentApplication() override;
61 Q_INVOKABLE void unfocusCurrentApplication() override;
67 Q_INVOKABLE ApplicationInfo *startApplication(const QString &appId, const QStringList &arguments = QStringList()) override;
62 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());
69 Q_INVOKABLE bool stopApplication(const QString &appId) override;
63 Q_INVOKABLE bool stopApplication(const QString &appId) override;
70
64
71 QString focusedApplicationId() const override;
65 QString focusedApplicationId() const override;
@@ -98,8 +92,6 @@ class ApplicationManager : public ApplicationManagerInterface {
98 static ApplicationManager *the_application_manager;
92 static ApplicationManager *the_application_manager;
99};
93};
100
94
101−Q_DECLARE_OPERATORS_FOR_FLAGS(ApplicationManager::ExecFlags)
102−
103Q_DECLARE_METATYPE(ApplicationManager*)
95Q_DECLARE_METATYPE(ApplicationManager*)
104
96
105#endif // APPLICATION_MANAGER_H
97#endif // APPLICATION_MANAGER_H
tests/mocks/Unity/Application/MirSurface.cpp+6−2
@@ -17,13 +17,15 @@
17#include "MirSurface.h"
17#include "MirSurface.h"
18
18
19#include <QDebug>
19#include <QDebug>
20+#include <QQmlEngine>
20
21
21MirSurface::MirSurface(const QString& name,
22MirSurface::MirSurface(const QString& name,
22 Mir::Type type,
23 Mir::Type type,
23 Mir::State state,
24 Mir::State state,
24 const QUrl& screenshot,
25 const QUrl& screenshot,
25− const QUrl &qmlFilePath)
26+ const QUrl &qmlFilePath,
27+ QObject *parent)
26− : unity::shell::application::MirSurfaceInterface(nullptr)
28+ : unity::shell::application::MirSurfaceInterface(parent)
27 , m_name(name)
29 , m_name(name)
28 , m_type(type)
30 , m_type(type)
29 , m_state(state)
31 , m_state(state)
@@ -38,6 +40,8 @@ MirSurface::MirSurface(const QString& name,
38 , m_slowToResize(false)
40 , m_slowToResize(false)
39{
41{
40// qDebug() << "MirSurface::MirSurface() " << name;
42// qDebug() << "MirSurface::MirSurface() " << name;
43+ QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership);
44+
41 m_delayedResizeTimer.setInterval(600);
45 m_delayedResizeTimer.setInterval(600);
42 m_delayedResizeTimer.setSingleShot(true);
46 m_delayedResizeTimer.setSingleShot(true);
43 connect(&m_delayedResizeTimer, &QTimer::timeout, this, &MirSurface::applyDelayedResize);
47 connect(&m_delayedResizeTimer, &QTimer::timeout, this, &MirSurface::applyDelayedResize);
tests/mocks/Unity/Application/MirSurface.h+2−1
@@ -41,7 +41,8 @@ public:
41 Mir::Type type,
41 Mir::Type type,
42 Mir::State state,
42 Mir::State state,
43 const QUrl& screenshot,
43 const QUrl& screenshot,
44− const QUrl &qmlFilePath = QUrl());
44+ const QUrl &qmlFilePath = QUrl(),
45+ QObject *parent = nullptr);
45 virtual ~MirSurface();
46 virtual ~MirSurface();
46
47
47 ////
48 ////
tests/mocks/Unity/Application/MirSurfaceItem.cpp+17
@@ -156,6 +156,10 @@ void MirSurfaceItem::createQmlContentItem()
156
156
157void MirSurfaceItem::touchEvent(QTouchEvent * event)
157void MirSurfaceItem::touchEvent(QTouchEvent * event)
158{
158{
159+ if (event->type() == QEvent::TouchBegin) {
160+ m_touchTrail.clear();
161+ }
162+
159 if (event->touchPointStates() & Qt::TouchPointPressed) {
163 if (event->touchPointStates() & Qt::TouchPointPressed) {
160 ++m_touchPressCount;
164 ++m_touchPressCount;
161 Q_EMIT touchPressCountChanged(m_touchPressCount);
165 Q_EMIT touchPressCountChanged(m_touchPressCount);
@@ -163,6 +167,19 @@ void MirSurfaceItem::touchEvent(QTouchEvent * event)
163 ++m_touchReleaseCount;
167 ++m_touchReleaseCount;
164 Q_EMIT touchReleaseCountChanged(m_touchReleaseCount);
168 Q_EMIT touchReleaseCountChanged(m_touchReleaseCount);
165 }
169 }
170+
171+ Q_FOREACH(QTouchEvent::TouchPoint touchPoint, event->touchPoints()) {
172+ QString id(touchPoint.id());
173+ QVariantList list = m_touchTrail[id].toList();
174+ list.append(QVariant::fromValue(touchPoint.pos()));
175+ if (list.count() > 100) list.pop_front();
176+ m_touchTrail[id] = list;
177+ }
178+
179+ if (m_qmlItem) {
180+ QQmlProperty touchTrail(m_qmlItem, "touchTrail");
181+ touchTrail.write(m_touchTrail);
182+ }
166}
183}
167
184
168void MirSurfaceItem::mousePressEvent(QMouseEvent * event)
185void MirSurfaceItem::mousePressEvent(QMouseEvent * event)
tests/mocks/Unity/Application/MirSurfaceItem.h+1
@@ -123,6 +123,7 @@ private:
123 int m_touchReleaseCount;
123 int m_touchReleaseCount;
124 int m_mousePressCount;
124 int m_mousePressCount;
125 int m_mouseReleaseCount;
125 int m_mouseReleaseCount;
126+ QVariantMap m_touchTrail;
126
127
127 FillMode m_fillMode{Stretch};
128 FillMode m_fillMode{Stretch};
128
129
tests/mocks/Unity/Application/SurfaceManager.cpp+8−10
@@ -34,6 +34,13 @@ SurfaceManager::SurfaceManager(QObject *parent) :
34 QObject(parent)
34 QObject(parent)
35 , m_virtualKeyboard(nullptr)
35 , m_virtualKeyboard(nullptr)
36{
36{
37+ m_virtualKeyboard = new VirtualKeyboard(this);
38+ connect(m_virtualKeyboard, &QObject::destroyed, this, [this](QObject *obj) {
39+ MirSurface* surface = qobject_cast<MirSurface*>(obj);
40+ m_virtualKeyboard = nullptr;
41+ Q_EMIT inputMethodSurfaceChanged();
42+ Q_EMIT surfaceDestroyed(surface);
43+ });
37}
44}
38
45
39MirSurface *SurfaceManager::createSurface(const QString& name,
46MirSurface *SurfaceManager::createSurface(const QString& name,
@@ -58,17 +65,8 @@ MirSurface *SurfaceManager::createSurface(const QString& name,
58 return surface;
65 return surface;
59}
66}
60
67
61−MirSurface *SurfaceManager::inputMethodSurface()
68+MirSurface *SurfaceManager::inputMethodSurface() const
62{
69{
63− if (!m_virtualKeyboard) {
64− m_virtualKeyboard = new VirtualKeyboard;
65− connect(m_virtualKeyboard, &QObject::destroyed, this, [this](QObject *obj) {
66− m_virtualKeyboard = nullptr;
67− MirSurface* surface = qobject_cast<MirSurface*>(obj);
68− Q_EMIT surfaceDestroyed(surface);
69− });
70− Q_EMIT surfaceCreated(m_virtualKeyboard);
71− }
72 return m_virtualKeyboard;
70 return m_virtualKeyboard;
73}
71}
74
72
tests/mocks/Unity/Application/SurfaceManager.h+3−3
@@ -25,7 +25,7 @@
25class SurfaceManager : public QObject
25class SurfaceManager : public QObject
26{
26{
27 Q_OBJECT
27 Q_OBJECT
28−
28+ Q_PROPERTY(MirSurface* inputMethodSurface READ inputMethodSurface NOTIFY inputMethodSurfaceChanged)
29 Q_PROPERTY(int newSurfaceMinimumWidth READ newSurfaceMinimumWidth WRITE setNewSurfaceMinimumWidth NOTIFY newSurfaceMinimumWidthChanged)
29 Q_PROPERTY(int newSurfaceMinimumWidth READ newSurfaceMinimumWidth WRITE setNewSurfaceMinimumWidth NOTIFY newSurfaceMinimumWidthChanged)
30 Q_PROPERTY(int newSurfaceMaximumWidth READ newSurfaceMaximumWidth WRITE setNewSurfaceMaximumWidth NOTIFY newSurfaceMaximumWidthChanged)
30 Q_PROPERTY(int newSurfaceMaximumWidth READ newSurfaceMaximumWidth WRITE setNewSurfaceMaximumWidth NOTIFY newSurfaceMaximumWidthChanged)
31 Q_PROPERTY(int newSurfaceMinimumHeight READ newSurfaceMinimumHeight WRITE setNewSurfaceMinimumHeight NOTIFY newSurfaceMinimumHeightChanged)
31 Q_PROPERTY(int newSurfaceMinimumHeight READ newSurfaceMinimumHeight WRITE setNewSurfaceMinimumHeight NOTIFY newSurfaceMinimumHeightChanged)
@@ -43,8 +43,7 @@ public:
43 Mir::State state,
43 Mir::State state,
44 const QUrl& screenshot);
44 const QUrl& screenshot);
45
45
46− // To be used in the tests
47− Q_INVOKABLE MirSurface* inputMethodSurface();
46+ MirSurface* inputMethodSurface() const;
48
47
49 int newSurfaceMinimumWidth() const { return m_newSurfaceMinimumWidth; }
48 int newSurfaceMinimumWidth() const { return m_newSurfaceMinimumWidth; }
50 void setNewSurfaceMinimumWidth(int value);
49 void setNewSurfaceMinimumWidth(int value);
@@ -65,6 +64,7 @@ public:
65 void setNewSurfaceHeightIncrement(int);
64 void setNewSurfaceHeightIncrement(int);
66
65
67Q_SIGNALS:
66Q_SIGNALS:
67+ void inputMethodSurfaceChanged();
68 void countChanged();
68 void countChanged();
69 void surfaceCreated(MirSurface *surface);
69 void surfaceCreated(MirSurface *surface);
70 void surfaceDestroyed(MirSurface*surface);
70 void surfaceDestroyed(MirSurface*surface);
tests/mocks/Unity/Application/VirtualKeyboard.cpp+3−2
@@ -22,12 +22,13 @@
22
22
23#include <QDebug>
23#include <QDebug>
24
24
25−VirtualKeyboard::VirtualKeyboard()
25+VirtualKeyboard::VirtualKeyboard(QObject *parent)
26 : MirSurface("input-method",
26 : MirSurface("input-method",
27 Mir::InputMethodType,
27 Mir::InputMethodType,
28 Mir::MinimizedState,
28 Mir::MinimizedState,
29 QUrl("qrc:///Unity/Application/vkb_portrait.png"),
29 QUrl("qrc:///Unity/Application/vkb_portrait.png"),
30− QUrl("qrc:///Unity/Application/VirtualKeyboard.qml"))
30+ QUrl("qrc:///Unity/Application/VirtualKeyboard.qml"),
31+ parent)
31{
32{
32}
33}
33
34
tests/mocks/Unity/Application/VirtualKeyboard.h+1−1
@@ -25,7 +25,7 @@ class VirtualKeyboard : public MirSurface
25{
25{
26 Q_OBJECT
26 Q_OBJECT
27public:
27public:
28− explicit VirtualKeyboard();
28+ explicit VirtualKeyboard(QObject *parent = nullptr);
29 ~VirtualKeyboard();
29 ~VirtualKeyboard();
30};
30};
31
31
tests/mocks/Unity/Application/plugin.cpp+2−1
@@ -78,8 +78,9 @@ void FakeUnityApplicationQmlPlugin::registerTypes(const char *uri)
78 qmlRegisterUncreatableType<unity::shell::application::ApplicationInfoInterface>(uri, 0, 1, "ApplicationInfoInterface", "Abstract interface. Cannot be created in QML");
78 qmlRegisterUncreatableType<unity::shell::application::ApplicationInfoInterface>(uri, 0, 1, "ApplicationInfoInterface", "Abstract interface. Cannot be created in QML");
79 qmlRegisterUncreatableType<Session>(uri, 0, 1, "Session", "Session can't be instantiated from QML");
79 qmlRegisterUncreatableType<Session>(uri, 0, 1, "Session", "Session can't be instantiated from QML");
80 qmlRegisterUncreatableType<MirSurface>(uri, 0, 1, "MirSurface", "MirSurface can't be instantiated from QML");
80 qmlRegisterUncreatableType<MirSurface>(uri, 0, 1, "MirSurface", "MirSurface can't be instantiated from QML");
81+ qmlRegisterUncreatableType<unity::shell::application::MirSurfaceInterface>(
82+ uri, 0, 1, "MirSurface", "MirSurface can't be instantiated from QML");
81 qmlRegisterType<MirSurfaceItem>(uri, 0, 1, "MirSurfaceItem");
83 qmlRegisterType<MirSurfaceItem>(uri, 0, 1, "MirSurfaceItem");
82−
83 qmlRegisterType<ApplicationInfo>(uri, 0, 1, "ApplicationInfo");
84 qmlRegisterType<ApplicationInfo>(uri, 0, 1, "ApplicationInfo");
84
85
85 qmlRegisterSingletonType<ApplicationManager>(uri, 0, 1, "ApplicationManager", applicationManagerSingleton);
86 qmlRegisterSingletonType<ApplicationManager>(uri, 0, 1, "ApplicationManager", applicationManagerSingleton);
tests/mocks/Unity/Application/resources/MirSurfaceItem.qml+59
@@ -29,6 +29,9 @@ Rectangle {
29 property alias screenshotSource: screenshotImage.source
29 property alias screenshotSource: screenshotImage.source
30 property int orientationAngle
30 property int orientationAngle
31
31
32+ property var touchTrail
33+ onTouchTrailChanged: canvas.requestPaint()
34+
32 Image {
35 Image {
33 id: screenshotImage
36 id: screenshotImage
34 anchors.fill: parent
37 anchors.fill: parent
@@ -67,4 +70,60 @@ Rectangle {
67 width: (rotation == 0 || rotation == 180 ? parent.width : parent.height)
70 width: (rotation == 0 || rotation == 180 ? parent.width : parent.height)
68 height:(rotation == 0 || rotation == 180 ? parent.height : parent.width)
71 height:(rotation == 0 || rotation == 180 ? parent.height : parent.width)
69 }
72 }
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+ }
70}
129}
tests/mocks/Unity/Launcher/MockLauncherModel.cpp+2
@@ -25,6 +25,7 @@ MockLauncherModel::MockLauncherModel(QObject* parent): LauncherModelInterface(pa
25 MockLauncherItem *item = new MockLauncherItem("dialer-app", "/usr/share/applications/dialer-app.desktop", "Dialer", "dialer-app", this);
25 MockLauncherItem *item = new MockLauncherItem("dialer-app", "/usr/share/applications/dialer-app.desktop", "Dialer", "dialer-app", this);
26 item->setProgress(0);
26 item->setProgress(0);
27 item->setPinned(true);
27 item->setPinned(true);
28+ item->setRunning(true);
28 item->setFocused(true);
29 item->setFocused(true);
29 m_list.append(item);
30 m_list.append(item);
30 item = new MockLauncherItem("camera-app", "/usr/share/applications/camera-app.desktop", "Camera", "camera", this);
31 item = new MockLauncherItem("camera-app", "/usr/share/applications/camera-app.desktop", "Camera", "camera", this);
@@ -34,6 +35,7 @@ MockLauncherModel::MockLauncherModel(QObject* parent): LauncherModelInterface(pa
34 item = new MockLauncherItem("gallery-app", "/usr/share/applications/gallery-app.desktop", "Gallery", "gallery", this);
35 item = new MockLauncherItem("gallery-app", "/usr/share/applications/gallery-app.desktop", "Gallery", "gallery", this);
35 item->setProgress(50);
36 item->setProgress(50);
36 item->setCountVisible(true);
37 item->setCountVisible(true);
38+ item->setRunning(true);
37 item->setAlerting(false);
39 item->setAlerting(false);
38 m_list.append(item);
40 m_list.append(item);
39 item = new MockLauncherItem("music-app", "/usr/share/applications/music-app.desktop", "Music", "soundcloud", this);
41 item = new MockLauncherItem("music-app", "/usr/share/applications/music-app.desktop", "Music", "soundcloud", this);
tests/mocks/Utils/CMakeLists.txt+3−1
@@ -16,13 +16,15 @@ set(QMLPLUGIN_SRC
16 ${CMAKE_SOURCE_DIR}/plugins/Utils/windowscreenshotprovider.cpp
16 ${CMAKE_SOURCE_DIR}/plugins/Utils/windowscreenshotprovider.cpp
17 ${CMAKE_SOURCE_DIR}/plugins/Utils/easingcurve.cpp
17 ${CMAKE_SOURCE_DIR}/plugins/Utils/easingcurve.cpp
18 ${CMAKE_SOURCE_DIR}/plugins/Utils/inputwatcher.cpp
18 ${CMAKE_SOURCE_DIR}/plugins/Utils/inputwatcher.cpp
19+ ${CMAKE_SOURCE_DIR}/plugins/Utils/timezoneFormatter.cpp
19 ${CMAKE_SOURCE_DIR}/plugins/Utils/applicationsfiltermodel.cpp
20 ${CMAKE_SOURCE_DIR}/plugins/Utils/applicationsfiltermodel.cpp
20 ${CMAKE_SOURCE_DIR}/plugins/Utils/inputeventgenerator.cpp
21 ${CMAKE_SOURCE_DIR}/plugins/Utils/inputeventgenerator.cpp
22+ ${CMAKE_SOURCE_DIR}/plugins/Utils/deviceconfigparser.cpp
23+ ${CMAKE_SOURCE_DIR}/plugins/Utils/globalfunctions.cpp
21 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationManagerInterface.h
24 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationManagerInterface.h
22 constants.cpp
25 constants.cpp
23 plugin.cpp
26 plugin.cpp
24 windowstatestorage.cpp
27 windowstatestorage.cpp
25− plugin.cpp
26 )
28 )
27
29
28add_library(FakeUtils-qml SHARED
30add_library(FakeUtils-qml SHARED
tests/mocks/Utils/plugin.cpp+14
@@ -35,8 +35,11 @@
35#include <windowinputfilter.h>
35#include <windowinputfilter.h>
36#include <windowscreenshotprovider.h>
36#include <windowscreenshotprovider.h>
37#include <easingcurve.h>
37#include <easingcurve.h>
38+#include <timezoneFormatter.h>
38#include <applicationsfiltermodel.h>
39#include <applicationsfiltermodel.h>
39#include <inputeventgenerator.h>
40#include <inputeventgenerator.h>
41+#include <deviceconfigparser.h>
42+#include <globalfunctions.h>
40
43
41static QObject *createWindowStateStorage(QQmlEngine *engine, QJSEngine *scriptEngine)
44static QObject *createWindowStateStorage(QQmlEngine *engine, QJSEngine *scriptEngine)
42{
45{
@@ -52,6 +55,13 @@ static QObject *createConstants(QQmlEngine *engine, QJSEngine *scriptEngine)
52 return new Constants();
55 return new Constants();
53}
56}
54
57
58+static QObject *createGlobalFunctions(QQmlEngine *engine, QJSEngine *scriptEngine)
59+{
60+ Q_UNUSED(engine)
61+ Q_UNUSED(scriptEngine)
62+ return new GlobalFunctions();
63+}
64+
55void FakeUtilsPlugin::registerTypes(const char *uri)
65void FakeUtilsPlugin::registerTypes(const char *uri)
56{
66{
57 Q_ASSERT(uri == QLatin1String("Utils"));
67 Q_ASSERT(uri == QLatin1String("Utils"));
@@ -64,9 +74,13 @@ void FakeUtilsPlugin::registerTypes(const char *uri)
64 qmlRegisterSingletonType<WindowStateStorage>(uri, 0, 1, "WindowStateStorage", createWindowStateStorage);
74 qmlRegisterSingletonType<WindowStateStorage>(uri, 0, 1, "WindowStateStorage", createWindowStateStorage);
65 qmlRegisterType<InputWatcher>(uri, 0, 1, "InputWatcher");
75 qmlRegisterType<InputWatcher>(uri, 0, 1, "InputWatcher");
66 qmlRegisterSingletonType<Constants>(uri, 0, 1, "Constants", createConstants);
76 qmlRegisterSingletonType<Constants>(uri, 0, 1, "Constants", createConstants);
77+ qmlRegisterSingletonType<TimezoneFormatter>(uri, 0, 1, "TimezoneFormatter",
78+ [](QQmlEngine*, QJSEngine*) -> QObject* { return new TimezoneFormatter; });
67 qmlRegisterType<ActiveFocusLogger>(uri, 0, 1, "ActiveFocusLogger");
79 qmlRegisterType<ActiveFocusLogger>(uri, 0, 1, "ActiveFocusLogger");
68 qmlRegisterType<ApplicationsFilterModel>(uri, 0, 1, "ApplicationsFilterModel");
80 qmlRegisterType<ApplicationsFilterModel>(uri, 0, 1, "ApplicationsFilterModel");
69 qmlRegisterType<InputEventGenerator>(uri, 0, 1, "InputEventGenerator");
81 qmlRegisterType<InputEventGenerator>(uri, 0, 1, "InputEventGenerator");
82+ qmlRegisterType<DeviceConfigParser>(uri, 0, 1, "DeviceConfigParser");
83+ qmlRegisterSingletonType<GlobalFunctions>(uri, 0, 1, "Functions", createGlobalFunctions);
70}
84}
71
85
72void FakeUtilsPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
86void FakeUtilsPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
tests/mocks/Utils/windowstatestorage.cpp+13
@@ -16,6 +16,8 @@
16
16
17#include "windowstatestorage.h"
17#include "windowstatestorage.h"
18
18
19+#include <unity/shell/application/ApplicationInfoInterface.h>
20+
19WindowStateStorage::WindowStateStorage(QObject *parent):
21WindowStateStorage::WindowStateStorage(QObject *parent):
20 QObject(parent)
22 QObject(parent)
21{
23{
@@ -45,10 +47,21 @@ QRect WindowStateStorage::getGeometry(const QString &windowId, const QRect &defa
45 return m_geometry.value(windowId).toRect();
47 return m_geometry.value(windowId).toRect();
46}
48}
47
49
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+
48void WindowStateStorage::clear()
60void WindowStateStorage::clear()
49{
61{
50 m_state.clear();
62 m_state.clear();
51 m_geometry.clear();
63 m_geometry.clear();
64+ m_stage.clear();
52}
65}
53
66
54void WindowStateStorage::saveState(const QString &windowId, WindowState state)
67void WindowStateStorage::saveState(const QString &windowId, WindowState state)
tests/mocks/Utils/windowstatestorage.h+4
@@ -36,6 +36,9 @@ public:
36 Q_INVOKABLE void saveGeometry(const QString &windowId, const QRect &rect);
36 Q_INVOKABLE void saveGeometry(const QString &windowId, const QRect &rect);
37 Q_INVOKABLE QRect getGeometry(const QString &windowId, const QRect &defaultValue);
37 Q_INVOKABLE QRect getGeometry(const QString &windowId, const QRect &defaultValue);
38
38
39+ Q_INVOKABLE void saveStage(const QString &appId, int stage);
40+ Q_INVOKABLE int getStage(const QString &appId) const;
41+
39 // Only in the mock, to easily restore a fresh state
42 // Only in the mock, to easily restore a fresh state
40 Q_INVOKABLE void clear();
43 Q_INVOKABLE void clear();
41
44
@@ -47,5 +50,6 @@ private:
47 QVariantMap geometry() const;
50 QVariantMap geometry() const;
48
51
49 QHash<QString, WindowState> m_state;
52 QHash<QString, WindowState> m_state;
53+ QHash<QString, int> m_stage;
50 QVariantMap m_geometry;
54 QVariantMap m_geometry;
51};
55};
tests/mocks/Wizard/CMakeLists.txt+6−1
@@ -1,4 +1,5 @@
1include_directories(
1include_directories(
2+ ${GLIB_INCLUDE_DIRS} ${GEONAMES_INCLUDE_DIRS}
2 ${CMAKE_SOURCE_DIR}/plugins/Wizard
3 ${CMAKE_SOURCE_DIR}/plugins/Wizard
3)
4)
4
5
@@ -6,7 +7,11 @@ add_library(MockWizard-qml MODULE
6 mockplugin.cpp
7 mockplugin.cpp
7 MockSystem.cpp
8 MockSystem.cpp
8 ${CMAKE_SOURCE_DIR}/plugins/Wizard/PageList.cpp
9 ${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
9)
13)
10
14
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})
12add_unity8_mock(Wizard 0.1 Wizard TARGETS MockWizard-qml)
17add_unity8_mock(Wizard 0.1 Wizard TARGETS MockWizard-qml)
tests/mocks/Wizard/MockSystem.cpp+4−2
@@ -14,6 +14,8 @@
14 * with this program. If not, see <http://www.gnu.org/licenses/>.
14 * with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
15 */
16
16
17+#include <QDebug>
18+
17#include "MockSystem.h"
19#include "MockSystem.h"
18
20
19MockSystem::MockSystem()
21MockSystem::MockSystem()
@@ -33,7 +35,7 @@ void MockSystem::setWizardEnabled(bool enabled)
33 Q_EMIT wizardEnabledChanged();
35 Q_EMIT wizardEnabledChanged();
34}
36}
35
37
36−void MockSystem::updateSessionLanguage(const QString &locale)
38+void MockSystem::updateSessionLocale(const QString &locale)
37{
39{
38− Q_EMIT updateSessionLanguageCalled(locale);
40+ Q_EMIT updateSessionLocaleCalled(locale);
39}
41}
tests/mocks/Wizard/MockSystem.h+2−2
@@ -32,11 +32,11 @@ public:
32 void setWizardEnabled(bool enabled);
32 void setWizardEnabled(bool enabled);
33
33
34public Q_SLOTS:
34public Q_SLOTS:
35− void updateSessionLanguage(const QString &locale);
35+ void updateSessionLocale(const QString &locale);
36
36
37Q_SIGNALS:
37Q_SIGNALS:
38 void wizardEnabledChanged();
38 void wizardEnabledChanged();
39− void updateSessionLanguageCalled(const QString &locale); // only in mock
39+ void updateSessionLocaleCalled(const QString &locale); // only in mock
40
40
41private:
41private:
42 Q_DISABLE_COPY(MockSystem)
42 Q_DISABLE_COPY(MockSystem)
tests/mocks/Wizard/mockplugin.cpp+7−8
@@ -17,19 +17,18 @@
17#include "mockplugin.h"
17#include "mockplugin.h"
18#include "MockSystem.h"
18#include "MockSystem.h"
19#include "PageList.h"
19#include "PageList.h"
20+#include "timezonemodel.h"
21+#include "LocalePlugin.h"
22+#include "Status.h"
20
23
21#include <QtQml/qqml.h>
24#include <QtQml/qqml.h>
22
25
23−static QObject *system_provider(QQmlEngine *engine, QJSEngine *scriptEngine)
24−{
25− Q_UNUSED(engine)
26− Q_UNUSED(scriptEngine)
27− return new MockSystem();
28−}
29−
30void MockWizardPlugin::registerTypes(const char *uri)
26void MockWizardPlugin::registerTypes(const char *uri)
31{
27{
32 Q_ASSERT(uri == QLatin1String("Wizard"));
28 Q_ASSERT(uri == QLatin1String("Wizard"));
33 qmlRegisterType<PageList>(uri, 0, 1, "PageList");
29 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");
35}
34}
tests/plugins/AccountsService/CMakeLists.txt+5
@@ -10,11 +10,13 @@ macro(make_dbus_class NAME INTERFACE)
10endmacro(make_dbus_class)
10endmacro(make_dbus_class)
11
11
12make_dbus_class(Accounts org.freedesktop.Accounts)
12make_dbus_class(Accounts org.freedesktop.Accounts)
13+make_dbus_class(Input com.ubuntu.AccountsService.Input)
13make_dbus_class(Properties org.freedesktop.DBus.Properties)
14make_dbus_class(Properties org.freedesktop.DBus.Properties)
14make_dbus_class(SecurityPrivacy com.ubuntu.touch.AccountsService.SecurityPrivacy)
15make_dbus_class(SecurityPrivacy com.ubuntu.touch.AccountsService.SecurityPrivacy)
15make_dbus_class(Location com.ubuntu.location.providers.here.AccountsService)
16make_dbus_class(Location com.ubuntu.location.providers.here.AccountsService)
16make_dbus_class(AccountsUser org.freedesktop.Accounts.User)
17make_dbus_class(AccountsUser org.freedesktop.Accounts.User)
17make_dbus_class(AccountsPrivate com.canonical.unity.AccountsService.Private)
18make_dbus_class(AccountsPrivate com.canonical.unity.AccountsService.Private)
19+make_dbus_class(UscInput com.canonical.Unity.Input)
18
20
19include_directories(
21include_directories(
20 ${CMAKE_CURRENT_BINARY_DIR}
22 ${CMAKE_CURRENT_BINARY_DIR}
@@ -27,12 +29,15 @@ add_executable(mock-server
27 ${CMAKE_CURRENT_BINARY_DIR}/AccountsAdaptor.cpp
29 ${CMAKE_CURRENT_BINARY_DIR}/AccountsAdaptor.cpp
28 ${CMAKE_CURRENT_BINARY_DIR}/AccountsPrivateAdaptor.cpp
30 ${CMAKE_CURRENT_BINARY_DIR}/AccountsPrivateAdaptor.cpp
29 ${CMAKE_CURRENT_BINARY_DIR}/AccountsUserAdaptor.cpp
31 ${CMAKE_CURRENT_BINARY_DIR}/AccountsUserAdaptor.cpp
32+ ${CMAKE_CURRENT_BINARY_DIR}/InputAdaptor.cpp
30 ${CMAKE_CURRENT_BINARY_DIR}/PropertiesAdaptor.cpp
33 ${CMAKE_CURRENT_BINARY_DIR}/PropertiesAdaptor.cpp
31 ${CMAKE_CURRENT_BINARY_DIR}/LocationAdaptor.cpp
34 ${CMAKE_CURRENT_BINARY_DIR}/LocationAdaptor.cpp
32 ${CMAKE_CURRENT_BINARY_DIR}/SecurityPrivacyAdaptor.cpp
35 ${CMAKE_CURRENT_BINARY_DIR}/SecurityPrivacyAdaptor.cpp
36+ ${CMAKE_CURRENT_BINARY_DIR}/UscInputAdaptor.cpp
33 server.cpp
37 server.cpp
34 AccountsServer.cpp
38 AccountsServer.cpp
35 PropertiesServer.cpp
39 PropertiesServer.cpp
40+ UscServer.cpp
36 )
41 )
37qt5_use_modules(mock-server Core DBus)
42qt5_use_modules(mock-server Core DBus)
38
43
tests/plugins/AccountsService/PropertiesServer.cpp+16−4
@@ -32,9 +32,9 @@ PropertiesServer::PropertiesServer(QObject *parent)
32 Reset();
32 Reset();
33}
33}
34
34
35−QDBusVariant PropertiesServer::Get(const QString &interface, const QString &property)
35+QDBusVariant PropertiesServer::Get(const QString &interface, const QString &property) const
36{
36{
37− if (m_properties[interface].contains(property)) {
37+ if (m_properties.contains(interface) && m_properties[interface].contains(property)) {
38 return QDBusVariant(m_properties[interface][property]);
38 return QDBusVariant(m_properties[interface][property]);
39 } else {
39 } else {
40 sendErrorReply(QDBusError::InvalidArgs, "Bad interface or property");
40 sendErrorReply(QDBusError::InvalidArgs, "Bad interface or property");
@@ -42,6 +42,16 @@ QDBusVariant PropertiesServer::Get(const QString &interface, const QString &prop
42 }
42 }
43}
43}
44
44
45+QVariantMap PropertiesServer::GetAll(const QString &interface) const
46+{
47+ if (m_properties.contains(interface)) {
48+ return m_properties[interface];
49+ } else {
50+ sendErrorReply(QDBusError::InvalidArgs, "Bad interface");
51+ return QVariantMap();
52+ }
53+}
54+
45void PropertiesServer::Set(const QString &interface, const QString &property, const QDBusVariant &variant)
55void PropertiesServer::Set(const QString &interface, const QString &property, const QDBusVariant &variant)
46{
56{
47 QVariant newValue = variant.variant();
57 QVariant newValue = variant.variant();
@@ -57,8 +67,8 @@ void PropertiesServer::Set(const QString &interface, const QString &property, co
57
67
58 oldValue = newValue;
68 oldValue = newValue;
59
69
60− // Special case for Background file.
70+ // Special case for user properties.
61− if (interface == "org.freedesktop.Accounts.User" && property == "BackgroundFile") {
71+ if (interface == "org.freedesktop.Accounts.User") {
62 Q_EMIT Changed();
72 Q_EMIT Changed();
63 } else {
73 } else {
64 QVariantMap propertyChanges;
74 QVariantMap propertyChanges;
@@ -83,10 +93,12 @@ void PropertiesServer::Reset()
83 m_properties["com.canonical.unity.AccountsService"]["LauncherItems"] = QVariant::fromValue(QList<QVariantMap>());
93 m_properties["com.canonical.unity.AccountsService"]["LauncherItems"] = QVariant::fromValue(QList<QVariantMap>());
84 m_properties["com.canonical.unity.AccountsService.Private"]["FailedLogins"] = 0;
94 m_properties["com.canonical.unity.AccountsService.Private"]["FailedLogins"] = 0;
85 m_properties["com.ubuntu.touch.AccountsService.SecurityPrivacy"]["StatsWelcomeScreen"] = true;
95 m_properties["com.ubuntu.touch.AccountsService.SecurityPrivacy"]["StatsWelcomeScreen"] = true;
96+ m_properties["com.ubuntu.AccountsService.Input"]["MousePrimaryButton"] = "right";
86 m_properties["com.ubuntu.AccountsService.SecurityPrivacy"]["EnableLauncherWhileLocked"] = true;
97 m_properties["com.ubuntu.AccountsService.SecurityPrivacy"]["EnableLauncherWhileLocked"] = true;
87 m_properties["com.ubuntu.AccountsService.SecurityPrivacy"]["EnableIndicatorsWhileLocked"] = true;
98 m_properties["com.ubuntu.AccountsService.SecurityPrivacy"]["EnableIndicatorsWhileLocked"] = true;
88 m_properties["com.ubuntu.AccountsService.SecurityPrivacy"]["PasswordDisplayHint"] = AccountsService::Keyboard;
99 m_properties["com.ubuntu.AccountsService.SecurityPrivacy"]["PasswordDisplayHint"] = AccountsService::Keyboard;
89 m_properties["com.ubuntu.location.providers.here.AccountsService"]["LicenseAccepted"] = false;
100 m_properties["com.ubuntu.location.providers.here.AccountsService"]["LicenseAccepted"] = false;
90 m_properties["com.ubuntu.location.providers.here.AccountsService"]["LicenseBasePath"] = "";
101 m_properties["com.ubuntu.location.providers.here.AccountsService"]["LicenseBasePath"] = "";
91 m_properties["org.freedesktop.Accounts.User"]["BackgroundFile"] = "";
102 m_properties["org.freedesktop.Accounts.User"]["BackgroundFile"] = "";
103+ m_properties["org.freedesktop.Accounts.User"]["RealName"] = "";
92}
104}
tests/plugins/AccountsService/PropertiesServer.h+2−1
@@ -34,7 +34,8 @@ public:
34 explicit PropertiesServer(QObject *parent = 0);
34 explicit PropertiesServer(QObject *parent = 0);
35
35
36public Q_SLOTS:
36public Q_SLOTS:
37− QDBusVariant Get(const QString &interface, const QString &property);
37+ QDBusVariant Get(const QString &interface, const QString &property) const;
38+ QVariantMap GetAll(const QString &interface) const;
38 void Set(const QString &interface, const QString &property, const QDBusVariant &variant);
39 void Set(const QString &interface, const QString &property, const QDBusVariant &variant);
39
40
40 // mock only.
41 // mock only.
tests/plugins/AccountsService/UscServer.cppnew file+28
@@ -0,0 +1,28 @@
1+/*
2+ * Copyright 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
14+ * version 3 along with this program. If not, see
15+ * <http://www.gnu.org/licenses/>
16+ */
17+
18+#include "UscServer.h"
19+
20+UscServer::UscServer(QObject *parent)
21+ : QObject(parent)
22+{
23+}
24+
25+void UscServer::setMousePrimaryButton(int button)
26+{
27+ Q_EMIT setMousePrimaryButtonCalled(button);
28+}
tests/plugins/AccountsService/UscServer.hnew file+39
@@ -0,0 +1,39 @@
1+/*
2+ * Copyright 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
14+ * version 3 along with this program. If not, see
15+ * <http://www.gnu.org/licenses/>
16+ */
17+
18+#ifndef UNITY_USCSERVER_H
19+#define UNITY_USCSERVER_H
20+
21+#include <QDBusContext>
22+#include <QObject>
23+
24+class UscServer: public QObject, protected QDBusContext
25+{
26+ Q_OBJECT
27+
28+public:
29+ explicit UscServer(QObject *parent = nullptr);
30+
31+public Q_SLOTS:
32+ void setMousePrimaryButton(int button);
33+
34+Q_SIGNALS:
35+ // mock only.
36+ void setMousePrimaryButtonCalled(int button);
37+};
38+
39+#endif
tests/plugins/AccountsService/client.cpp+63−9
@@ -42,7 +42,16 @@ public:
42 : QObject(parent)
42 : QObject(parent)
43 , m_userInterface(nullptr)
43 , m_userInterface(nullptr)
44 , m_spy(this, &AccountsServiceTest::propertiesChanged)
44 , m_spy(this, &AccountsServiceTest::propertiesChanged)
45+ , m_mousePrimaryButtonSpy(this, &AccountsServiceTest::setMousePrimaryButtonCalled)
45 {
46 {
47+ m_uscInputInterface = new QDBusInterface("com.canonical.Unity.Input",
48+ "/com/canonical/Unity/Input",
49+ "com.canonical.Unity.Input",
50+ QDBusConnection::sessionBus(),
51+ this);
52+
53+ QObject::connect(m_uscInputInterface, SIGNAL(setMousePrimaryButtonCalled(int)),
54+ this, SIGNAL(setMousePrimaryButtonCalled(int)));
46 }
55 }
47
56
48private Q_SLOTS:
57private Q_SLOTS:
@@ -71,6 +80,7 @@ private Q_SLOTS:
71
80
72 delete m_userInterface;
81 delete m_userInterface;
73 m_spy.clear();
82 m_spy.clear();
83+ m_mousePrimaryButtonSpy.clear();
74 }
84 }
75
85
76 void testInvalids()
86 void testInvalids()
@@ -144,7 +154,7 @@ private Q_SLOTS:
144 QCOMPARE(session.demoEdgesCompleted(), QStringList() << "testedge");
154 QCOMPARE(session.demoEdgesCompleted(), QStringList() << "testedge");
145 }
155 }
146
156
147− void testAsynchornousChangeForDemoEdges()
157+ void testAsynchronousChangeForDemoEdges()
148 {
158 {
149 AccountsService session(this, QTest::currentTestFunction());
159 AccountsService session(this, QTest::currentTestFunction());
150
160
@@ -156,7 +166,7 @@ private Q_SLOTS:
156 QTRY_COMPARE(session.demoEdges(), true);
166 QTRY_COMPARE(session.demoEdges(), true);
157 }
167 }
158
168
159− void testAsynchornousChangeForFailedLogins()
169+ void testAsynchronousChangeForFailedLogins()
160 {
170 {
161 AccountsService session(this, QTest::currentTestFunction());
171 AccountsService session(this, QTest::currentTestFunction());
162
172
@@ -168,7 +178,7 @@ private Q_SLOTS:
168 QTRY_COMPARE(session.failedLogins(), (uint)5);
178 QTRY_COMPARE(session.failedLogins(), (uint)5);
169 }
179 }
170
180
171− void testAsynchornousChangeForStatsWelcomeScreen()
181+ void testAsynchronousChangeForStatsWelcomeScreen()
172 {
182 {
173 AccountsService session(this, QTest::currentTestFunction());
183 AccountsService session(this, QTest::currentTestFunction());
174
184
@@ -180,7 +190,7 @@ private Q_SLOTS:
180 QTRY_COMPARE(session.statsWelcomeScreen(), false);
190 QTRY_COMPARE(session.statsWelcomeScreen(), false);
181 }
191 }
182
192
183− void testAsynchornousChangeForStatsEnableLauncherWhileLocked()
193+ void testAsynchronousChangeForEnableLauncherWhileLocked()
184 {
194 {
185 AccountsService session(this, QTest::currentTestFunction());
195 AccountsService session(this, QTest::currentTestFunction());
186
196
@@ -192,7 +202,7 @@ private Q_SLOTS:
192 QTRY_COMPARE(session.enableLauncherWhileLocked(), false);
202 QTRY_COMPARE(session.enableLauncherWhileLocked(), false);
193 }
203 }
194
204
195− void testAsynchornousChangeForStatsEnableIndicatorsWhileLocked()
205+ void testAsynchronousChangeForEnableIndicatorsWhileLocked()
196 {
206 {
197 AccountsService session(this, QTest::currentTestFunction());
207 AccountsService session(this, QTest::currentTestFunction());
198
208
@@ -204,7 +214,7 @@ private Q_SLOTS:
204 QTRY_COMPARE(session.enableIndicatorsWhileLocked(), false);
214 QTRY_COMPARE(session.enableIndicatorsWhileLocked(), false);
205 }
215 }
206
216
207− void testAsynchornousChangeForStatsPasswordDisplayHint()
217+ void testAsynchronousChangeForPasswordDisplayHint()
208 {
218 {
209 AccountsService session(this, QTest::currentTestFunction());
219 AccountsService session(this, QTest::currentTestFunction());
210
220
@@ -216,7 +226,7 @@ private Q_SLOTS:
216 QTRY_COMPARE(session.passwordDisplayHint(), AccountsService::Numeric);
226 QTRY_COMPARE(session.passwordDisplayHint(), AccountsService::Numeric);
217 }
227 }
218
228
219− void testAsynchornousChangeForStatsLicenseAccepted()
229+ void testAsynchronousChangeForLicenseAccepted()
220 {
230 {
221 AccountsService session(this, QTest::currentTestFunction());
231 AccountsService session(this, QTest::currentTestFunction());
222
232
@@ -228,7 +238,7 @@ private Q_SLOTS:
228 QTRY_COMPARE(session.hereEnabled(), true);
238 QTRY_COMPARE(session.hereEnabled(), true);
229 }
239 }
230
240
231− void testAsynchornousChangeForLicenseBasePath()
241+ void testAsynchronousChangeForLicenseBasePath()
232 {
242 {
233 AccountsService session(this, QTest::currentTestFunction());
243 AccountsService session(this, QTest::currentTestFunction());
234
244
@@ -240,7 +250,7 @@ private Q_SLOTS:
240 QTRY_COMPARE(session.hereLicensePath(), QString("/"));
250 QTRY_COMPARE(session.hereLicensePath(), QString("/"));
241 }
251 }
242
252
243− void testAsynchornousChangeForStatsBackgroundFile()
253+ void testAsynchronousChangeForBackgroundFile()
244 {
254 {
245 AccountsService session(this, QTest::currentTestFunction());
255 AccountsService session(this, QTest::currentTestFunction());
246
256
@@ -252,12 +262,56 @@ private Q_SLOTS:
252 QTRY_COMPARE(session.backgroundFile(), QString("/test/BackgroundFile"));
262 QTRY_COMPARE(session.backgroundFile(), QString("/test/BackgroundFile"));
253 }
263 }
254
264
265+ void testProxyOnStartup()
266+ {
267+ AccountsService session(this, QTest::currentTestFunction());
268+
269+ QTRY_COMPARE(m_mousePrimaryButtonSpy.count(), 1);
270+ QList<QVariant> arguments = m_mousePrimaryButtonSpy.takeFirst();
271+ QCOMPARE(arguments.at(0).toInt(), 1);
272+ }
273+
274+ void testProxyOnChange()
275+ {
276+ AccountsService session(this, QTest::currentTestFunction());
277+ QTRY_COMPARE(m_mousePrimaryButtonSpy.count(), 1);
278+ m_mousePrimaryButtonSpy.clear();
279+
280+ ASSERT_DBUS_CALL(m_userInterface->asyncCall("Set",
281+ "com.ubuntu.AccountsService.Input",
282+ "MousePrimaryButton",
283+ dbusVariant("left")));
284+
285+ QTRY_COMPARE(m_mousePrimaryButtonSpy.count(), 1);
286+ QList<QVariant> arguments = m_mousePrimaryButtonSpy.takeFirst();
287+ QCOMPARE(arguments.at(0).toInt(), 0);
288+ }
289+
290+ void testInvalidPrimaryButton()
291+ {
292+ AccountsService session(this, QTest::currentTestFunction());
293+ QTRY_COMPARE(m_mousePrimaryButtonSpy.count(), 1);
294+ m_mousePrimaryButtonSpy.clear();
295+
296+ ASSERT_DBUS_CALL(m_userInterface->asyncCall("Set",
297+ "com.ubuntu.AccountsService.Input",
298+ "MousePrimaryButton",
299+ dbusVariant("NOPE")));
300+
301+ QTRY_COMPARE(m_mousePrimaryButtonSpy.count(), 1);
302+ QList<QVariant> arguments = m_mousePrimaryButtonSpy.takeFirst();
303+ QCOMPARE(arguments.at(0).toInt(), 0);
304+ }
305+
255Q_SIGNALS:
306Q_SIGNALS:
256 void propertiesChanged(const QString &interface, const QVariantMap &changed, const QStringList &invalid);
307 void propertiesChanged(const QString &interface, const QVariantMap &changed, const QStringList &invalid);
308+ void setMousePrimaryButtonCalled(int button);
257
309
258private:
310private:
311+ QDBusInterface* m_uscInputInterface;
259 QDBusInterface* m_userInterface;
312 QDBusInterface* m_userInterface;
260 QSignalSpy m_spy;
313 QSignalSpy m_spy;
314+ QSignalSpy m_mousePrimaryButtonSpy;
261};
315};
262
316
263QTEST_MAIN(AccountsServiceTest)
317QTEST_MAIN(AccountsServiceTest)
tests/plugins/AccountsService/interfaces.xml+16
@@ -5,6 +5,11 @@
5 <arg name="property" type="s" direction="in" />
5 <arg name="property" type="s" direction="in" />
6 <arg name="value" type="v" direction="out" />
6 <arg name="value" type="v" direction="out" />
7 </method>
7 </method>
8+ <method name="GetAll">
9+ <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QVariantMap"/>
10+ <arg name="interface" type="s" direction="in" />
11+ <arg name="value" type="a{sv}" direction="out" />
12+ </method>
8 <method name="Set">
13 <method name="Set">
9 <arg name="interface" type="s" direction="in" />
14 <arg name="interface" type="s" direction="in" />
10 <arg name="property" type="s" direction="in" />
15 <arg name="property" type="s" direction="in" />
@@ -32,10 +37,21 @@
32 <arg name="success" type="b" direction="out" />
37 <arg name="success" type="b" direction="out" />
33 </method>
38 </method>
34 </interface>
39 </interface>
40+ <interface name="com.ubuntu.AccountsService.Input" />
35 <interface name="com.ubuntu.touch.AccountsService.SecurityPrivacy" />
41 <interface name="com.ubuntu.touch.AccountsService.SecurityPrivacy" />
36 <interface name="com.ubuntu.location.providers.here.AccountsService" />
42 <interface name="com.ubuntu.location.providers.here.AccountsService" />
37 <interface name="org.freedesktop.Accounts.User">
43 <interface name="org.freedesktop.Accounts.User">
38 <signal name="Changed" />
44 <signal name="Changed" />
39 </interface>
45 </interface>
40 <interface name="com.canonical.unity.AccountsService.Private" />
46 <interface name="com.canonical.unity.AccountsService.Private" />
47+
48+ <interface name="com.canonical.Unity.Input">
49+ <method name="setMousePrimaryButton">
50+ <arg name="button" type="i" direction="in" />
51+ </method>
52+ <!-- Just for testing -->
53+ <signal name="setMousePrimaryButtonCalled">
54+ <arg name="button" type="i" direction="out" />
55+ </signal>
56+ </interface>
41</node>
57</node>
tests/plugins/AccountsService/server.cpp+12
@@ -19,10 +19,13 @@
19
19
20#include "AccountsAdaptor.h"
20#include "AccountsAdaptor.h"
21#include "AccountsServer.h"
21#include "AccountsServer.h"
22+#include "InputAdaptor.h"
22#include "PropertiesServer.h"
23#include "PropertiesServer.h"
23#include "SecurityPrivacyAdaptor.h"
24#include "SecurityPrivacyAdaptor.h"
24#include "LocationAdaptor.h"
25#include "LocationAdaptor.h"
25#include "AccountsPrivateAdaptor.h"
26#include "AccountsPrivateAdaptor.h"
27+#include "UscInputAdaptor.h"
28+#include "UscServer.h"
26#include <QCoreApplication>
29#include <QCoreApplication>
27
30
28int main(int argc, char *argv[])
31int main(int argc, char *argv[])
@@ -31,11 +34,20 @@ int main(int argc, char *argv[])
31
34
32 auto accounts = new AccountsServer(&a);
35 auto accounts = new AccountsServer(&a);
33 new AccountsAdaptor(accounts);
36 new AccountsAdaptor(accounts);
37+ new InputAdaptor(accounts);
34 new SecurityPrivacyAdaptor(accounts);
38 new SecurityPrivacyAdaptor(accounts);
35 new LocationAdaptor(accounts);
39 new LocationAdaptor(accounts);
36 new AccountsPrivateAdaptor(accounts);
40 new AccountsPrivateAdaptor(accounts);
41+
42+ auto usc = new UscServer(&a);
43+ new UscInputAdaptor(usc);
44+
37 // We use the session bus for testing. The real plugin uses the system bus
45 // We use the session bus for testing. The real plugin uses the system bus
38 auto connection = QDBusConnection::sessionBus();
46 auto connection = QDBusConnection::sessionBus();
47+ if (!connection.registerObject("/com/canonical/Unity/Input", usc))
48+ return 1;
49+ if (!connection.registerService("com.canonical.Unity.Input"))
50+ return 1;
39 if (!connection.registerObject("/org/freedesktop/Accounts", accounts))
51 if (!connection.registerObject("/org/freedesktop/Accounts", accounts))
40 return 1;
52 return 1;
41 if (!connection.registerService("org.freedesktop.Accounts"))
53 if (!connection.registerService("org.freedesktop.Accounts"))
tests/plugins/Dash/cardcreator/1.res+4−6
@@ -5,12 +5,10 @@ AbstractButton {
5 property string backgroundShapeStyle: "inset";
5 property string backgroundShapeStyle: "inset";
6 property real fontScale: 1.0;
6 property real fontScale: 1.0;
7 property var scopeStyle: null;
7 property var scopeStyle: null;
8− property int titleAlignment: Text.AlignLeft;
9 property int fixedHeaderHeight: -1;
8 property int fixedHeaderHeight: -1;
10 property size fixedArtShapeSize: Qt.size(-1, -1);
9 property size fixedArtShapeSize: Qt.size(-1, -1);
11 readonly property string title: cardData && cardData["title"] || "";
10 readonly property string title: cardData && cardData["title"] || "";
12− property bool asynchronous: true;
13− property bool showHeader: true;
11+ property bool showHeader: true;
14 implicitWidth: childrenRect.width;
12 implicitWidth: childrenRect.width;
15 enabled: true;
13 enabled: true;
16
14
@@ -25,7 +23,7 @@ Item {
25 objectName: "artShapeLoader";
23 objectName: "artShapeLoader";
26 readonly property string cardArt: cardData && cardData["art"] || "";
24 readonly property string cardArt: cardData && cardData["art"] || "";
27 active: cardArt != "";
25 active: cardArt != "";
28− asynchronous: root.asynchronous;
26+ asynchronous: true;
29 visible: status == Loader.Ready;
27 visible: status == Loader.Ready;
30 sourceComponent: Item {
28 sourceComponent: Item {
31 id: artShape;
29 id: artShape;
@@ -82,7 +80,7 @@ Item {
82 id: artImage;
80 id: artImage;
83 objectName: "artImage";
81 objectName: "artImage";
84 source: artShapeLoader.cardArt;
82 source: artShapeLoader.cardArt;
85− asynchronous: root.asynchronous;
83+ asynchronous: true;
86 width: root.width;
84 width: root.width;
87 height: width / artShape.aspect;
85 height: width / artShape.aspect;
88 }
86 }
@@ -108,7 +106,7 @@ Label {
108 width: undefined;
106 width: undefined;
109 text: root.title;
107 text: root.title;
110 font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
108 font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
111− horizontalAlignment: root.titleAlignment;
109+ horizontalAlignment: Text.AlignHCenter;
112 }
110 }
113UbuntuShape {
111UbuntuShape {
114 id: touchdown;
112 id: touchdown;
tests/plugins/Dash/cardcreator/1.res.cardcreatornew file+119
@@ -0,0 +1,119 @@
1+AbstractButton {
2+ id: root;
3+ property var cardData;
4+ property string artShapeStyle: "inset";
5+ property string backgroundShapeStyle: "inset";
6+ property real fontScale: 1.0;
7+ property var scopeStyle: null;
8+ property size fixedArtShapeSize: Qt.size(-1, -1);
9+ readonly property string title: cardData && cardData["title"] || "";
10+ property bool showHeader: true;
11+ implicitWidth: childrenRect.width;
12+ enabled: true;
13+
14+readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);
15+Item {
16+ id: artShapeHolder;
17+ height: root.fixedArtShapeSize.height > 0 ? root.fixedArtShapeSize.height : artShapeLoader.height;
18+ width: root.fixedArtShapeSize.width > 0 ? root.fixedArtShapeSize.width : artShapeLoader.width;
19+ anchors { horizontalCenter: parent.horizontalCenter; }
20+ Loader {
21+ id: artShapeLoader;
22+ objectName: "artShapeLoader";
23+ readonly property string cardArt: cardData && cardData["art"] || "";
24+ active: cardArt != "";
25+ asynchronous: false;
26+ visible: status == Loader.Ready;
27+ sourceComponent: Item {
28+ id: artShape;
29+ objectName: "artShape";
30+ visible: image.status == Image.Ready;
31+ readonly property alias image: artImage;
32+ ShaderEffectSource {
33+ id: artShapeSource;
34+ sourceItem: artImage;
35+ anchors.centerIn: parent;
36+ width: 1;
37+ height: 1;
38+ hideSource: true;
39+ }
40+ Loader {
41+ anchors.fill: parent;
42+ visible: true;
43+ sourceComponent: root.artShapeStyle === "icon" ? artShapeIconComponent : artShapeShapeComponent;
44+ Component {
45+ id: artShapeShapeComponent;
46+ UbuntuShape {
47+ source: artShapeSource;
48+ sourceFillMode: UbuntuShape.PreserveAspectCrop;
49+ radius: "medium";
50+ aspect: {
51+ switch (root.artShapeStyle) {
52+ case "inset": return UbuntuShape.Inset;
53+ case "shadow": return UbuntuShape.DropShadow;
54+ default:
55+ case "flat": return UbuntuShape.Flat;
56+ }
57+ }
58+ }
59+ }
60+ Component {
61+ id: artShapeIconComponent;
62+ ProportionalShape { source: artShapeSource; aspect: UbuntuShape.DropShadow; }
63+ }
64+ }
65+ readonly property real fixedArtShapeSizeAspect: (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) ? root.fixedArtShapeSize.width / root.fixedArtShapeSize.height : -1;
66+ readonly property real aspect: fixedArtShapeSizeAspect > 0 ? fixedArtShapeSizeAspect : 1.6;
67+ Component.onCompleted: { updateWidthHeightBindings(); }
68+ Connections { target: root; onFixedArtShapeSizeChanged: updateWidthHeightBindings(); }
69+ function updateWidthHeightBindings() {
70+ if (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) {
71+ width = root.fixedArtShapeSize.width;
72+ height = root.fixedArtShapeSize.height;
73+ } else {
74+ width = Qt.binding(function() { return image.status !== Image.Ready ? 0 : image.width });
75+ height = Qt.binding(function() { return image.status !== Image.Ready ? 0 : image.height });
76+ }
77+ }
78+ CroppedImageMinimumSourceSize {
79+ id: artImage;
80+ objectName: "artImage";
81+ source: artShapeLoader.cardArt;
82+ asynchronous: false;
83+ width: root.width;
84+ height: width / artShape.aspect;
85+ }
86+ }
87+ }
88+ }
89+readonly property int headerHeight: titleLabel.height;
90+Label {
91+ id: titleLabel;
92+ objectName: "titleLabel";
93+ anchors { right: parent.right;
94+ left: parent.left;
95+ top: artShapeHolder.bottom;
96+ topMargin: units.gu(1);
97+ }
98+ elide: Text.ElideRight;
99+ fontSize: "small";
100+ wrapMode: Text.Wrap;
101+ maximumLineCount: 2;
102+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
103+ color: root.scopeStyle ? root.scopeStyle.foreground : theme.palette.normal.baseText;
104+ visible: showHeader ;
105+ width: undefined;
106+ text: root.title;
107+ font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
108+ horizontalAlignment: Text.AlignHCenter;
109+ }
110+UbuntuShape {
111+ id: touchdown;
112+ objectName: "touchdown";
113+ anchors { fill: artShapeHolder }
114+ visible: root.artShapeStyle != "shadow" && root.artShapeStyle != "icon" && root.pressed;
115+ radius: "medium";
116+ borderSource: "radius_pressed.sci"
117+}
118+implicitHeight: titleLabel.y + titleLabel.height + units.gu(1);
119+}
tests/plugins/Dash/cardcreator/10.res+3−5
@@ -5,11 +5,9 @@ AbstractButton {
5 property string backgroundShapeStyle: "inset";
5 property string backgroundShapeStyle: "inset";
6 property real fontScale: 1.0;
6 property real fontScale: 1.0;
7 property var scopeStyle: null;
7 property var scopeStyle: null;
8− property int titleAlignment: Text.AlignLeft;
9 property int fixedHeaderHeight: -1;
8 property int fixedHeaderHeight: -1;
10 property size fixedArtShapeSize: Qt.size(-1, -1);
9 property size fixedArtShapeSize: Qt.size(-1, -1);
11 readonly property string title: cardData && cardData["title"] || "";
10 readonly property string title: cardData && cardData["title"] || "";
12− property bool asynchronous: true;
13 property bool showHeader: true;
11 property bool showHeader: true;
14 implicitWidth: childrenRect.width;
12 implicitWidth: childrenRect.width;
15 enabled: true;
13 enabled: true;
@@ -18,7 +16,7 @@ Loader {
18 id: backgroundLoader;
16 id: backgroundLoader;
19 objectName: "backgroundLoader";
17 objectName: "backgroundLoader";
20 anchors.fill: parent;
18 anchors.fill: parent;
21− asynchronous: root.asynchronous;
19+ asynchronous: true;
22 visible: status == Loader.Ready;
20 visible: status == Loader.Ready;
23 sourceComponent: UbuntuShape {
21 sourceComponent: UbuntuShape {
24 objectName: "background";
22 objectName: "background";
@@ -59,7 +57,7 @@ Row {
59 objectName: "outerRow";
57 objectName: "outerRow";
60 property real margins: units.gu(1);
58 property real margins: units.gu(1);
61 spacing: margins;
59 spacing: margins;
62− height: root.fixedHeaderHeight != -1 ? root.fixedHeaderHeight : implicitHeight;
60+ height: root.fixedHeaderHeight;
63 anchors { top: parent.top;
61 anchors { top: parent.top;
64 topMargin: units.gu(1);
62 topMargin: units.gu(1);
65left: parent.left;
63left: parent.left;
@@ -103,7 +101,7 @@ left: parent.left;
103 width: undefined;
101 width: undefined;
104 text: root.title;
102 text: root.title;
105 font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
103 font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
106− horizontalAlignment: root.titleAlignment;
104+ horizontalAlignment: Text.AlignLeft;
107 }
105 }
108,Label {
106,Label {
109 id: subtitleLabel;
107 id: subtitleLabel;
tests/plugins/Dash/cardcreator/10.res.cardcreatornew file+137
@@ -0,0 +1,137 @@
1+AbstractButton {
2+ id: root;
3+ property var cardData;
4+ property string artShapeStyle: "inset";
5+ property string backgroundShapeStyle: "inset";
6+ property real fontScale: 1.0;
7+ property var scopeStyle: null;
8+ property size fixedArtShapeSize: Qt.size(-1, -1);
9+ readonly property string title: cardData && cardData["title"] || "";
10+ property bool showHeader: true;
11+ implicitWidth: childrenRect.width;
12+ enabled: true;
13+
14+Loader {
15+ id: backgroundLoader;
16+ objectName: "backgroundLoader";
17+ anchors.fill: parent;
18+ asynchronous: false;
19+ visible: status == Loader.Ready;
20+ sourceComponent: UbuntuShape {
21+ objectName: "background";
22+ radius: "medium";
23+ aspect: {
24+ switch (root.backgroundShapeStyle) {
25+ case "inset": return UbuntuShape.Inset;
26+ case "shadow": return UbuntuShape.DropShadow;
27+ default:
28+ case "flat": return UbuntuShape.Flat;
29+ }
30+ }
31+ backgroundColor: getColor(0) || "white";
32+ secondaryBackgroundColor: getColor(1) || backgroundColor;
33+ backgroundMode: UbuntuShape.VerticalGradient;
34+ anchors.fill: parent;
35+ source: backgroundImage.source ? backgroundImage : null;
36+ property real luminance: Style.luminance(backgroundColor);
37+ property Image backgroundImage: Image {
38+ objectName: "backgroundImage";
39+ source: {
40+ if (cardData && typeof cardData["background"] === "string") return cardData["background"];
41+ else return "";
42+ }
43+ }
44+ function getColor(index) {
45+ if (cardData && typeof cardData["background"] === "object"
46+ && (cardData["background"]["type"] === "color" || cardData["background"]["type"] === "gradient")) {
47+ return cardData["background"]["elements"][index];
48+ } else return index === 0 ? "" : "";
49+ }
50+ }
51+ }
52+readonly property size artShapeSize: Qt.size(-1, -1);
53+readonly property int headerHeight: row.height;
54+Row {
55+ id: row;
56+ objectName: "outerRow";
57+ property real margins: units.gu(1);
58+ spacing: margins;
59+ anchors { top: parent.top;
60+ topMargin: units.gu(1);
61+left: parent.left;
62+ }
63+ anchors.right: parent.right;
64+ anchors.margins: margins;
65+ anchors.rightMargin: 0;
66+ data: [
67+ CroppedImageMinimumSourceSize {
68+ id: mascotImage;
69+ objectName: "mascotImage";
70+ anchors { verticalCenter: parent.verticalCenter; }
71+ source: cardData && cardData["mascot"] || "";
72+ width: units.gu(6);
73+ height: units.gu(5.625);
74+ horizontalAlignment: Image.AlignHCenter;
75+ verticalAlignment: Image.AlignVCenter;
76+ visible: showHeader;
77+
78+ }
79+,Item {
80+ id: headerTitleContainer;
81+ anchors { verticalCenter: parent.verticalCenter; }
82+ width: parent.width - x;
83+ implicitHeight: titleLabel.height + subtitleLabel.height;
84+ data: [
85+ Label {
86+ id: titleLabel;
87+ objectName: "titleLabel";
88+ anchors { right: parent.right;
89+rightMargin: units.gu(1);
90+left: parent.left;
91+ top: parent.top; }
92+ elide: Text.ElideRight;
93+ fontSize: "small";
94+ wrapMode: Text.Wrap;
95+ maximumLineCount: 2;
96+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
97+ color: backgroundLoader.active && backgroundLoader.item && root.scopeStyle ? root.scopeStyle.getTextColor(backgroundLoader.item.luminance) : (backgroundLoader.item && backgroundLoader.item.luminance > 0.7 ? theme.palette.normal.baseText : "white");
98+ visible: showHeader ;
99+ width: undefined;
100+ text: root.title;
101+ font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
102+ horizontalAlignment: Text.AlignLeft;
103+ }
104+,Label {
105+ id: subtitleLabel;
106+ objectName: "subtitleLabel";
107+ anchors { right: parent.right;
108+ left: parent.left;
109+rightMargin: units.gu(1);
110+top: titleLabel.bottom;
111+ }
112+ anchors.topMargin: units.dp(2);
113+ elide: Text.ElideRight;
114+ maximumLineCount: 1;
115+ fontSize: "x-small";
116+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
117+ color: backgroundLoader.active && backgroundLoader.item && root.scopeStyle ? root.scopeStyle.getTextColor(backgroundLoader.item.luminance) : (backgroundLoader.item && backgroundLoader.item.luminance > 0.7 ? theme.palette.normal.baseText : "white");
118+ visible: titleLabel.visible && titleLabel.text;
119+ text: cardData && cardData["subtitle"] || "";
120+ font.weight: Font.Light;
121+ }
122+
123+ ]
124+ }
125+
126+ ]
127+ }
128+UbuntuShape {
129+ id: touchdown;
130+ objectName: "touchdown";
131+ anchors { fill: backgroundLoader }
132+ visible: root.artShapeStyle != "shadow" && root.artShapeStyle != "icon" && root.pressed;
133+ radius: "medium";
134+ borderSource: "radius_pressed.sci"
135+ }
136+implicitHeight: row.y + row.height + units.gu(1);
137+}
tests/plugins/Dash/cardcreator/11.res+5−7
@@ -5,11 +5,9 @@ AbstractButton {
5 property string backgroundShapeStyle: "inset";
5 property string backgroundShapeStyle: "inset";
6 property real fontScale: 1.0;
6 property real fontScale: 1.0;
7 property var scopeStyle: null;
7 property var scopeStyle: null;
8− property int titleAlignment: Text.AlignLeft;
9 property int fixedHeaderHeight: -1;
8 property int fixedHeaderHeight: -1;
10 property size fixedArtShapeSize: Qt.size(-1, -1);
9 property size fixedArtShapeSize: Qt.size(-1, -1);
11 readonly property string title: cardData && cardData["title"] || "";
10 readonly property string title: cardData && cardData["title"] || "";
12− property bool asynchronous: true;
13 property bool showHeader: true;
11 property bool showHeader: true;
14 implicitWidth: childrenRect.width;
12 implicitWidth: childrenRect.width;
15 enabled: true;
13 enabled: true;
@@ -18,7 +16,7 @@ Loader {
18 id: backgroundLoader;
16 id: backgroundLoader;
19 objectName: "backgroundLoader";
17 objectName: "backgroundLoader";
20 anchors.fill: parent;
18 anchors.fill: parent;
21− asynchronous: root.asynchronous;
19+ asynchronous: true;
22 visible: status == Loader.Ready;
20 visible: status == Loader.Ready;
23 sourceComponent: UbuntuShape {
21 sourceComponent: UbuntuShape {
24 objectName: "background";
22 objectName: "background";
@@ -63,7 +61,7 @@ Item {
63 objectName: "artShapeLoader";
61 objectName: "artShapeLoader";
64 readonly property string cardArt: cardData && cardData["art"] || decodeURI("%5C");
62 readonly property string cardArt: cardData && cardData["art"] || decodeURI("%5C");
65 active: cardArt != "";
63 active: cardArt != "";
66− asynchronous: root.asynchronous;
64+ asynchronous: true;
67 visible: status == Loader.Ready;
65 visible: status == Loader.Ready;
68 sourceComponent: Item {
66 sourceComponent: Item {
69 id: artShape;
67 id: artShape;
@@ -120,7 +118,7 @@ Item {
120 id: artImage;
118 id: artImage;
121 objectName: "artImage";
119 objectName: "artImage";
122 source: artShapeLoader.cardArt;
120 source: artShapeLoader.cardArt;
123− asynchronous: root.asynchronous;
121+ asynchronous: true;
124 width: root.width;
122 width: root.width;
125 height: width / artShape.aspect;
123 height: width / artShape.aspect;
126 onStatusChanged: if (status === Image.Error) source = decodeURI("%5C");
124 onStatusChanged: if (status === Image.Error) source = decodeURI("%5C");
@@ -134,7 +132,7 @@ Row {
134 objectName: "outerRow";
132 objectName: "outerRow";
135 property real margins: units.gu(1);
133 property real margins: units.gu(1);
136 spacing: margins;
134 spacing: margins;
137− height: root.fixedHeaderHeight != -1 ? root.fixedHeaderHeight : implicitHeight;
135+ height: root.fixedHeaderHeight;
138 anchors { top: artShapeHolder.bottom;
136 anchors { top: artShapeHolder.bottom;
139 topMargin: units.gu(1);
137 topMargin: units.gu(1);
140left: parent.left;
138left: parent.left;
@@ -178,7 +176,7 @@ left: parent.left;
178 width: undefined;
176 width: undefined;
179 text: root.title;
177 text: root.title;
180 font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
178 font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
181− horizontalAlignment: root.titleAlignment;
179+ horizontalAlignment: Text.AlignLeft;
182 }
180 }
183,Label {
181,Label {
184 id: subtitleLabel;
182 id: subtitleLabel;
tests/plugins/Dash/cardcreator/11.res.cardcreatornew file+210
@@ -0,0 +1,210 @@
1+AbstractButton {
2+ id: root;
3+ property var cardData;
4+ property string artShapeStyle: "inset";
5+ property string backgroundShapeStyle: "inset";
6+ property real fontScale: 1.0;
7+ property var scopeStyle: null;
8+ property size fixedArtShapeSize: Qt.size(-1, -1);
9+ readonly property string title: cardData && cardData["title"] || "";
10+ property bool showHeader: true;
11+ implicitWidth: childrenRect.width;
12+ enabled: true;
13+
14+Loader {
15+ id: backgroundLoader;
16+ objectName: "backgroundLoader";
17+ anchors.fill: parent;
18+ asynchronous: false;
19+ visible: status == Loader.Ready;
20+ sourceComponent: UbuntuShape {
21+ objectName: "background";
22+ radius: "medium";
23+ aspect: {
24+ switch (root.backgroundShapeStyle) {
25+ case "inset": return UbuntuShape.Inset;
26+ case "shadow": return UbuntuShape.DropShadow;
27+ default:
28+ case "flat": return UbuntuShape.Flat;
29+ }
30+ }
31+ backgroundColor: getColor(0) || "white";
32+ secondaryBackgroundColor: getColor(1) || backgroundColor;
33+ backgroundMode: UbuntuShape.VerticalGradient;
34+ anchors.fill: parent;
35+ source: backgroundImage.source ? backgroundImage : null;
36+ property real luminance: Style.luminance(backgroundColor);
37+ property Image backgroundImage: Image {
38+ objectName: "backgroundImage";
39+ source: {
40+ if (cardData && typeof cardData["background"] === "string") return cardData["background"];
41+ else return "";
42+ }
43+ }
44+ function getColor(index) {
45+ if (cardData && typeof cardData["background"] === "object"
46+ && (cardData["background"]["type"] === "color" || cardData["background"]["type"] === "gradient")) {
47+ return cardData["background"]["elements"][index];
48+ } else return index === 0 ? "#E9E9E9" : undefined;
49+ }
50+ }
51+ }
52+readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);
53+Item {
54+ id: artShapeHolder;
55+ height: root.fixedArtShapeSize.height > 0 ? root.fixedArtShapeSize.height : artShapeLoader.height;
56+ width: root.fixedArtShapeSize.width > 0 ? root.fixedArtShapeSize.width : artShapeLoader.width;
57+ anchors { horizontalCenter: parent.horizontalCenter; }
58+ Loader {
59+ id: artShapeLoader;
60+ objectName: "artShapeLoader";
61+ readonly property string cardArt: cardData && cardData["art"] || "";
62+ active: cardArt != "";
63+ asynchronous: false;
64+ visible: status == Loader.Ready;
65+ sourceComponent: Item {
66+ id: artShape;
67+ objectName: "artShape";
68+ visible: image.status == Image.Ready;
69+ readonly property alias image: artImage;
70+ ShaderEffectSource {
71+ id: artShapeSource;
72+ sourceItem: artImage;
73+ anchors.centerIn: parent;
74+ width: 1;
75+ height: 1;
76+ hideSource: true;
77+ }
78+ Loader {
79+ anchors.fill: parent;
80+ visible: true;
81+ sourceComponent: root.artShapeStyle === "icon" ? artShapeIconComponent : artShapeShapeComponent;
82+ Component {
83+ id: artShapeShapeComponent;
84+ UbuntuShape {
85+ source: artShapeSource;
86+ sourceFillMode: UbuntuShape.PreserveAspectCrop;
87+ radius: "medium";
88+ aspect: {
89+ switch (root.artShapeStyle) {
90+ case "inset": return UbuntuShape.Inset;
91+ case "shadow": return UbuntuShape.DropShadow;
92+ default:
93+ case "flat": return UbuntuShape.Flat;
94+ }
95+ }
96+ }
97+ }
98+ Component {
99+ id: artShapeIconComponent;
100+ ProportionalShape { source: artShapeSource; aspect: UbuntuShape.DropShadow; }
101+ }
102+ }
103+ readonly property real fixedArtShapeSizeAspect: (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) ? root.fixedArtShapeSize.width / root.fixedArtShapeSize.height : -1;
104+ readonly property real aspect: fixedArtShapeSizeAspect > 0 ? fixedArtShapeSizeAspect : 1;
105+ Component.onCompleted: { updateWidthHeightBindings(); }
106+ Connections { target: root; onFixedArtShapeSizeChanged: updateWidthHeightBindings(); }
107+ function updateWidthHeightBindings() {
108+ if (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) {
109+ width = root.fixedArtShapeSize.width;
110+ height = root.fixedArtShapeSize.height;
111+ } else {
112+ width = Qt.binding(function() { return image.status !== Image.Ready ? 0 : image.width });
113+ height = Qt.binding(function() { return image.status !== Image.Ready ? 0 : image.height });
114+ }
115+ }
116+ CroppedImageMinimumSourceSize {
117+ id: artImage;
118+ objectName: "artImage";
119+ source: artShapeLoader.cardArt;
120+ asynchronous: false;
121+ width: root.width;
122+ height: width / artShape.aspect;
123+ }
124+ }
125+ }
126+ }
127+readonly property int headerHeight: row.height;
128+Row {
129+ id: row;
130+ objectName: "outerRow";
131+ property real margins: units.gu(1);
132+ spacing: margins;
133+ anchors { top: artShapeHolder.bottom;
134+ topMargin: units.gu(1);
135+left: parent.left;
136+ }
137+ anchors.right: parent.right;
138+ anchors.margins: margins;
139+ anchors.rightMargin: 0;
140+ data: [
141+ CroppedImageMinimumSourceSize {
142+ id: mascotImage;
143+ objectName: "mascotImage";
144+ anchors { verticalCenter: parent.verticalCenter; }
145+ source: cardData && cardData["mascot"] || "";
146+ width: units.gu(6);
147+ height: units.gu(5.625);
148+ horizontalAlignment: Image.AlignHCenter;
149+ verticalAlignment: Image.AlignVCenter;
150+ visible: showHeader;
151+ }
152+,Item {
153+ id: headerTitleContainer;
154+ anchors { verticalCenter: parent.verticalCenter; }
155+ width: parent.width - x;
156+ implicitHeight: titleLabel.height + subtitleLabel.height;
157+ data: [
158+ Label {
159+ id: titleLabel;
160+ objectName: "titleLabel";
161+ anchors { right: parent.right;
162+rightMargin: units.gu(1);
163+left: parent.left;
164+ top: parent.top; }
165+ elide: Text.ElideRight;
166+ fontSize: "small";
167+ wrapMode: Text.Wrap;
168+ maximumLineCount: 2;
169+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
170+ color: backgroundLoader.active && backgroundLoader.item && root.scopeStyle ? root.scopeStyle.getTextColor(backgroundLoader.item.luminance) : (backgroundLoader.item && backgroundLoader.item.luminance > 0.7 ? theme.palette.normal.baseText : "white");
171+ visible: showHeader ;
172+ width: undefined;
173+ text: root.title;
174+ font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
175+ horizontalAlignment: Text.AlignLeft;
176+ }
177+,Label {
178+ id: subtitleLabel;
179+ objectName: "subtitleLabel";
180+ anchors { right: parent.right;
181+ left: parent.left;
182+rightMargin: units.gu(1);
183+top: titleLabel.bottom;
184+ }
185+ anchors.topMargin: units.dp(2);
186+ elide: Text.ElideRight;
187+ maximumLineCount: 1;
188+ fontSize: "x-small";
189+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
190+ color: backgroundLoader.active && backgroundLoader.item && root.scopeStyle ? root.scopeStyle.getTextColor(backgroundLoader.item.luminance) : (backgroundLoader.item && backgroundLoader.item.luminance > 0.7 ? theme.palette.normal.baseText : "white");
191+ visible: titleLabel.visible && titleLabel.text;
192+ text: cardData && cardData["subtitle"] || "";
193+ font.weight: Font.Light;
194+ }
195+
196+ ]
197+ }
198+
199+ ]
200+ }
201+UbuntuShape {
202+ id: touchdown;
203+ objectName: "touchdown";
204+ anchors { fill: backgroundLoader }
205+ visible: root.artShapeStyle != "shadow" && root.artShapeStyle != "icon" && root.pressed;
206+ radius: "medium";
207+ borderSource: "radius_pressed.sci"
208+ }
209+implicitHeight: row.y + row.height + units.gu(1);
210+}
tests/plugins/Dash/cardcreator/2.res+3−5
@@ -5,11 +5,9 @@ AbstractButton {
5 property string backgroundShapeStyle: "inset";
5 property string backgroundShapeStyle: "inset";
6 property real fontScale: 1.0;
6 property real fontScale: 1.0;
7 property var scopeStyle: null;
7 property var scopeStyle: null;
8− property int titleAlignment: Text.AlignLeft;
9 property int fixedHeaderHeight: -1;
8 property int fixedHeaderHeight: -1;
10 property size fixedArtShapeSize: Qt.size(-1, -1);
9 property size fixedArtShapeSize: Qt.size(-1, -1);
11 readonly property string title: cardData && cardData["title"] || "";
10 readonly property string title: cardData && cardData["title"] || "";
12− property bool asynchronous: true;
13 property bool showHeader: true;
11 property bool showHeader: true;
14 implicitWidth: childrenRect.width;
12 implicitWidth: childrenRect.width;
15 enabled: true;
13 enabled: true;
@@ -18,7 +16,7 @@ Loader {
18 id: backgroundLoader;
16 id: backgroundLoader;
19 objectName: "backgroundLoader";
17 objectName: "backgroundLoader";
20 anchors.fill: parent;
18 anchors.fill: parent;
21− asynchronous: root.asynchronous;
19+ asynchronous: true;
22 visible: status == Loader.Ready;
20 visible: status == Loader.Ready;
23 sourceComponent: UbuntuShape {
21 sourceComponent: UbuntuShape {
24 objectName: "background";
22 objectName: "background";
@@ -59,7 +57,7 @@ Row {
59 objectName: "outerRow";
57 objectName: "outerRow";
60 property real margins: units.gu(1);
58 property real margins: units.gu(1);
61 spacing: margins;
59 spacing: margins;
62− height: root.fixedHeaderHeight != -1 ? root.fixedHeaderHeight : implicitHeight;
60+ height: root.fixedHeaderHeight;
63 anchors { top: parent.top;
61 anchors { top: parent.top;
64 topMargin: units.gu(1);
62 topMargin: units.gu(1);
65 left: parent.left;
63 left: parent.left;
@@ -102,7 +100,7 @@ CroppedImageMinimumSourceSize {
102 width: undefined;
100 width: undefined;
103 text: root.title;
101 text: root.title;
104 font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
102 font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
105− horizontalAlignment: root.titleAlignment;
103+ horizontalAlignment: Text.AlignLeft;
106 }
104 }
107 ,Label {
105 ,Label {
108 id: subtitleLabel;
106 id: subtitleLabel;
tests/plugins/Dash/cardcreator/2.res.cardcreatornew file+136
@@ -0,0 +1,136 @@
1+AbstractButton {
2+ id: root;
3+ property var cardData;
4+ property string artShapeStyle: "inset";
5+ property string backgroundShapeStyle: "inset";
6+ property real fontScale: 1.0;
7+ property var scopeStyle: null;
8+ property size fixedArtShapeSize: Qt.size(-1, -1);
9+ readonly property string title: cardData && cardData["title"] || "";
10+ property bool showHeader: true;
11+ implicitWidth: childrenRect.width;
12+ enabled: true;
13+
14+Loader {
15+ id: backgroundLoader;
16+ objectName: "backgroundLoader";
17+ anchors.fill: parent;
18+ asynchronous: false;
19+ visible: status == Loader.Ready;
20+ sourceComponent: UbuntuShape {
21+ objectName: "background";
22+ radius: "medium";
23+ aspect: {
24+ switch (root.backgroundShapeStyle) {
25+ case "inset": return UbuntuShape.Inset;
26+ case "shadow": return UbuntuShape.DropShadow;
27+ default:
28+ case "flat": return UbuntuShape.Flat;
29+ }
30+ }
31+ backgroundColor: getColor(0) || "white";
32+ secondaryBackgroundColor: getColor(1) || backgroundColor;
33+ backgroundMode: UbuntuShape.VerticalGradient;
34+ anchors.fill: parent;
35+ source: backgroundImage.source ? backgroundImage : null;
36+ property real luminance: Style.luminance(backgroundColor);
37+ property Image backgroundImage: Image {
38+ objectName: "backgroundImage";
39+ source: {
40+ if (cardData && typeof cardData["background"] === "string") return cardData["background"];
41+ else return "";
42+ }
43+ }
44+ function getColor(index) {
45+ if (cardData && typeof cardData["background"] === "object"
46+ && (cardData["background"]["type"] === "color" || cardData["background"]["type"] === "gradient")) {
47+ return cardData["background"]["elements"][index];
48+ } else return index === 0 ? "#E9E9E9" : undefined;
49+ }
50+ }
51+ }
52+readonly property size artShapeSize: Qt.size(-1, -1);
53+readonly property int headerHeight: row.height;
54+Row {
55+ id: row;
56+ objectName: "outerRow";
57+ property real margins: units.gu(1);
58+ spacing: margins;
59+ anchors { top: parent.top;
60+ topMargin: units.gu(1);
61+ left: parent.left;
62+ }
63+ anchors.right: parent.right;
64+ anchors.margins: margins;
65+ anchors.rightMargin: 0;
66+ data: [
67+CroppedImageMinimumSourceSize {
68+ id: mascotImage;
69+ objectName: "mascotImage";
70+ anchors { verticalCenter: parent.verticalCenter; }
71+ source: cardData && cardData["mascot"] || "";
72+ width: units.gu(6);
73+ height: units.gu(5.625);
74+ horizontalAlignment: Image.AlignHCenter;
75+ verticalAlignment: Image.AlignVCenter;
76+ visible: showHeader;
77+ }
78+,Item {
79+ id: headerTitleContainer;
80+ anchors { verticalCenter: parent.verticalCenter; }
81+ width: parent.width - x;
82+ implicitHeight: titleLabel.height + subtitleLabel.height;
83+ data: [
84+ Label {
85+ id: titleLabel;
86+ objectName: "titleLabel";
87+ anchors { right: parent.right;
88+ rightMargin: units.gu(1);
89+ left: parent.left;
90+ top: parent.top; }
91+ elide: Text.ElideRight;
92+ fontSize: "small";
93+ wrapMode: Text.Wrap;
94+ maximumLineCount: 2;
95+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
96+ color: backgroundLoader.active && backgroundLoader.item && root.scopeStyle ? root.scopeStyle.getTextColor(backgroundLoader.item.luminance) : (backgroundLoader.item && backgroundLoader.item.luminance > 0.7 ? theme.palette.normal.baseText : "white");
97+ visible: showHeader ;
98+ width: undefined;
99+ text: root.title;
100+ font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
101+ horizontalAlignment: Text.AlignLeft;
102+ }
103+ ,Label {
104+ id: subtitleLabel;
105+ objectName: "subtitleLabel";
106+ anchors { right: parent.right;
107+ left: parent.left;
108+ rightMargin: units.gu(1);
109+ top: titleLabel.bottom;
110+ }
111+ anchors.topMargin: units.dp(2);
112+ elide: Text.ElideRight;
113+ maximumLineCount: 1;
114+ fontSize: "x-small";
115+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
116+ color: backgroundLoader.active && backgroundLoader.item && root.scopeStyle ? root.scopeStyle.getTextColor(backgroundLoader.item.luminance) : (backgroundLoader.item && backgroundLoader.item.luminance > 0.7 ? theme.palette.normal.baseText : "white");
117+ visible: titleLabel.visible && titleLabel.text;
118+ text: cardData && cardData["subtitle"] || "";
119+ font.weight: Font.Light;
120+ }
121+
122+ ]
123+ }
124+
125+ ]
126+ }
127+UbuntuShape {
128+ id: touchdown;
129+ objectName: "touchdown";
130+ anchors { fill: backgroundLoader }
131+ visible: root.artShapeStyle != "shadow" && root.artShapeStyle != "icon" && root.pressed;
132+ radius: "medium";
133+ borderSource: "radius_pressed.sci"
134+}
135+implicitHeight: row.y + row.height + units.gu(1);
136+}
tests/plugins/Dash/cardcreator/3.res+3−5
@@ -5,11 +5,9 @@ AbstractButton {
5 property string backgroundShapeStyle: "inset";
5 property string backgroundShapeStyle: "inset";
6 property real fontScale: 1.0;
6 property real fontScale: 1.0;
7 property var scopeStyle: null;
7 property var scopeStyle: null;
8− property int titleAlignment: Text.AlignLeft;
9 property int fixedHeaderHeight: -1;
8 property int fixedHeaderHeight: -1;
10 property size fixedArtShapeSize: Qt.size(-1, -1);
9 property size fixedArtShapeSize: Qt.size(-1, -1);
11 readonly property string title: cardData && cardData["title"] || "";
10 readonly property string title: cardData && cardData["title"] || "";
12− property bool asynchronous: true;
13 property bool showHeader: true;
11 property bool showHeader: true;
14 implicitWidth: childrenRect.width;
12 implicitWidth: childrenRect.width;
15 enabled: true;
13 enabled: true;
@@ -25,7 +23,7 @@ Item {
25 objectName: "artShapeLoader";
23 objectName: "artShapeLoader";
26 readonly property string cardArt: cardData && cardData["art"] || decodeURI("IHAVE%5C%22ESCAPED%5C%22QUOTES%5C%22");
24 readonly property string cardArt: cardData && cardData["art"] || decodeURI("IHAVE%5C%22ESCAPED%5C%22QUOTES%5C%22");
27 active: cardArt != "";
25 active: cardArt != "";
28− asynchronous: root.asynchronous;
26+ asynchronous: true;
29 visible: status == Loader.Ready;
27 visible: status == Loader.Ready;
30 sourceComponent: Item {
28 sourceComponent: Item {
31 id: artShape;
29 id: artShape;
@@ -82,7 +80,7 @@ Item {
82 id: artImage;
80 id: artImage;
83 objectName: "artImage";
81 objectName: "artImage";
84 source: artShapeLoader.cardArt;
82 source: artShapeLoader.cardArt;
85− asynchronous: root.asynchronous;
83+ asynchronous: true;
86 width: root.width;
84 width: root.width;
87 height: width / artShape.aspect;
85 height: width / artShape.aspect;
88 onStatusChanged: if (status === Image.Error) source = decodeURI("IHAVE%5C%22ESCAPED%5C%22QUOTES%5C%22");
86 onStatusChanged: if (status === Image.Error) source = decodeURI("IHAVE%5C%22ESCAPED%5C%22QUOTES%5C%22");
@@ -109,7 +107,7 @@ Label {
109 width: undefined;
107 width: undefined;
110 text: root.title;
108 text: root.title;
111 font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
109 font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
112− horizontalAlignment: root.titleAlignment;
110+ horizontalAlignment: Text.AlignLeft;
113 }
111 }
114Label {
112Label {
115 id: subtitleLabel;
113 id: subtitleLabel;
tests/plugins/Dash/cardcreator/3.res.cardcreatornew file+137
@@ -0,0 +1,137 @@
1+AbstractButton {
2+ id: root;
3+ property var cardData;
4+ property string artShapeStyle: "inset";
5+ property string backgroundShapeStyle: "inset";
6+ property real fontScale: 1.0;
7+ property var scopeStyle: null;
8+ property size fixedArtShapeSize: Qt.size(-1, -1);
9+ readonly property string title: cardData && cardData["title"] || "";
10+ property bool showHeader: true;
11+ implicitWidth: childrenRect.width;
12+ enabled: true;
13+
14+readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);
15+Item {
16+ id: artShapeHolder;
17+ height: root.fixedArtShapeSize.height > 0 ? root.fixedArtShapeSize.height : artShapeLoader.height;
18+ width: root.fixedArtShapeSize.width > 0 ? root.fixedArtShapeSize.width : artShapeLoader.width;
19+ anchors { horizontalCenter: parent.horizontalCenter; }
20+ Loader {
21+ id: artShapeLoader;
22+ objectName: "artShapeLoader";
23+ readonly property string cardArt: cardData && cardData["art"] || "";
24+ active: cardArt != "";
25+ asynchronous: false;
26+ visible: status == Loader.Ready;
27+ sourceComponent: Item {
28+ id: artShape;
29+ objectName: "artShape";
30+ visible: image.status == Image.Ready;
31+ readonly property alias image: artImage;
32+ ShaderEffectSource {
33+ id: artShapeSource;
34+ sourceItem: artImage;
35+ anchors.centerIn: parent;
36+ width: 1;
37+ height: 1;
38+ hideSource: true;
39+ }
40+ Loader {
41+ anchors.fill: parent;
42+ visible: true;
43+ sourceComponent: root.artShapeStyle === "icon" ? artShapeIconComponent : artShapeShapeComponent;
44+ Component {
45+ id: artShapeShapeComponent;
46+ UbuntuShape {
47+ source: artShapeSource;
48+ sourceFillMode: UbuntuShape.PreserveAspectCrop;
49+ radius: "medium";
50+ aspect: {
51+ switch (root.artShapeStyle) {
52+ case "inset": return UbuntuShape.Inset;
53+ case "shadow": return UbuntuShape.DropShadow;
54+ default:
55+ case "flat": return UbuntuShape.Flat;
56+ }
57+ }
58+ }
59+ }
60+ Component {
61+ id: artShapeIconComponent;
62+ ProportionalShape { source: artShapeSource; aspect: UbuntuShape.DropShadow; }
63+ }
64+ }
65+ readonly property real fixedArtShapeSizeAspect: (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) ? root.fixedArtShapeSize.width / root.fixedArtShapeSize.height : -1;
66+ readonly property real aspect: fixedArtShapeSizeAspect > 0 ? fixedArtShapeSizeAspect : 0.75;
67+ Component.onCompleted: { updateWidthHeightBindings(); }
68+ Connections { target: root; onFixedArtShapeSizeChanged: updateWidthHeightBindings(); }
69+ function updateWidthHeightBindings() {
70+ if (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) {
71+ width = root.fixedArtShapeSize.width;
72+ height = root.fixedArtShapeSize.height;
73+ } else {
74+ width = Qt.binding(function() { return image.status !== Image.Ready ? 0 : image.width });
75+ height = Qt.binding(function() { return image.status !== Image.Ready ? 0 : image.height });
76+ }
77+ }
78+ CroppedImageMinimumSourceSize {
79+ id: artImage;
80+ objectName: "artImage";
81+ source: artShapeLoader.cardArt;
82+ asynchronous: false;
83+ width: root.width;
84+ height: width / artShape.aspect;
85+ }
86+ }
87+ }
88+ }
89+readonly property int headerHeight: titleLabel.height + subtitleLabel.height + subtitleLabel.anchors.topMargin;
90+Label {
91+ id: titleLabel;
92+ objectName: "titleLabel";
93+ anchors { right: parent.right;
94+ left: parent.left;
95+ top: artShapeHolder.bottom;
96+ topMargin: units.gu(1);
97+ }
98+ elide: Text.ElideRight;
99+ fontSize: "small";
100+ wrapMode: Text.Wrap;
101+ maximumLineCount: 2;
102+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
103+ color: root.scopeStyle ? root.scopeStyle.foreground : theme.palette.normal.baseText;
104+ visible: showHeader ;
105+ width: undefined;
106+ text: root.title;
107+ font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
108+ horizontalAlignment: Text.AlignLeft;
109+ }
110+Label {
111+ id: subtitleLabel;
112+ objectName: "subtitleLabel";
113+ anchors { left: titleLabel.left;
114+ leftMargin: titleLabel.leftMargin;
115+ right: titleLabel.right;
116+ top: titleLabel.bottom;
117+ }
118+ anchors.topMargin: units.dp(2);
119+ elide: Text.ElideRight;
120+ maximumLineCount: 1;
121+ fontSize: "x-small";
122+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
123+ color: root.scopeStyle ? root.scopeStyle.foreground : theme.palette.normal.baseText;
124+ visible: titleLabel.visible && titleLabel.text;
125+ text: cardData && cardData["subtitle"] || "";
126+ font.weight: Font.Light;
127+ }
128+UbuntuShape {
129+ id: touchdown;
130+ objectName: "touchdown";
131+ anchors { fill: artShapeHolder }
132+ visible: root.artShapeStyle != "shadow" && root.artShapeStyle != "icon" && root.pressed;
133+ radius: "medium";
134+ borderSource: "radius_pressed.sci"
135+}
136+implicitHeight: subtitleLabel.y + subtitleLabel.height + units.gu(1);
137+}
tests/plugins/Dash/cardcreator/4.res+3−5
@@ -5,11 +5,9 @@ AbstractButton {
5 property string backgroundShapeStyle: "inset";
5 property string backgroundShapeStyle: "inset";
6 property real fontScale: 1.0;
6 property real fontScale: 1.0;
7 property var scopeStyle: null;
7 property var scopeStyle: null;
8− property int titleAlignment: Text.AlignLeft;
9 property int fixedHeaderHeight: -1;
8 property int fixedHeaderHeight: -1;
10 property size fixedArtShapeSize: Qt.size(-1, -1);
9 property size fixedArtShapeSize: Qt.size(-1, -1);
11 readonly property string title: cardData && cardData["title"] || "";
10 readonly property string title: cardData && cardData["title"] || "";
12− property bool asynchronous: true;
13 property bool showHeader: true;
11 property bool showHeader: true;
14 implicitWidth: childrenRect.width;
12 implicitWidth: childrenRect.width;
15 enabled: true;
13 enabled: true;
@@ -21,7 +19,7 @@ Row {
21 objectName: "outerRow";
19 objectName: "outerRow";
22 property real margins: units.gu(1);
20 property real margins: units.gu(1);
23 spacing: margins;
21 spacing: margins;
24− height: root.fixedHeaderHeight != -1 ? root.fixedHeaderHeight : implicitHeight;
22+ height: root.fixedHeaderHeight;
25 anchors { top: parent.top;
23 anchors { top: parent.top;
26 topMargin: units.gu(1);
24 topMargin: units.gu(1);
27 left: parent.left;
25 left: parent.left;
@@ -33,7 +31,7 @@ data: [
33Loader {
31Loader {
34 id: mascotShapeLoader;
32 id: mascotShapeLoader;
35 objectName: "mascotShapeLoader";
33 objectName: "mascotShapeLoader";
36− asynchronous: root.asynchronous;
34+ asynchronous: true;
37 active: mascotImage.status === Image.Ready;
35 active: mascotImage.status === Image.Ready;
38 visible: showHeader && active && status == Loader.Ready;
36 visible: showHeader && active && status == Loader.Ready;
39 width: units.gu(6);
37 width: units.gu(6);
@@ -77,7 +75,7 @@ Label {
77 width: undefined;
75 width: undefined;
78 text: root.title;
76 text: root.title;
79 font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
77 font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
80− horizontalAlignment: root.titleAlignment;
78+ horizontalAlignment: Text.AlignLeft;
81 }
79 }
82,Label {
80,Label {
83 id: subtitleLabel;
81 id: subtitleLabel;
tests/plugins/Dash/cardcreator/4.res.cardcreatornew file+109
@@ -0,0 +1,109 @@
1+AbstractButton {
2+ id: root;
3+ property var cardData;
4+ property string artShapeStyle: "inset";
5+ property string backgroundShapeStyle: "inset";
6+ property real fontScale: 1.0;
7+ property var scopeStyle: null;
8+ property size fixedArtShapeSize: Qt.size(-1, -1);
9+ readonly property string title: cardData && cardData["title"] || "";
10+ property bool showHeader: true;
11+ implicitWidth: childrenRect.width;
12+ enabled: true;
13+
14+readonly property size artShapeSize: Qt.size(-1, -1);
15+readonly property int headerHeight: row.height;
16+Row {
17+ id: row;
18+ objectName: "outerRow";
19+ property real margins: units.gu(1);
20+ spacing: margins;
21+ anchors { top: parent.top;
22+ topMargin: units.gu(1);
23+ left: parent.left;
24+}
25+ anchors.right: parent.right;
26+ anchors.margins: margins;
27+ anchors.rightMargin: 0;
28+data: [
29+Loader {
30+ id: mascotShapeLoader;
31+ objectName: "mascotShapeLoader";
32+ asynchronous: false;
33+ active: mascotImage.status === Image.Ready;
34+ visible: showHeader && active && status == Loader.Ready;
35+ width: units.gu(6);
36+ height: units.gu(5.625);
37+ sourceComponent: UbuntuShape { image: mascotImage }
38+ anchors { verticalCenter: parent.verticalCenter; }
39+ }
40+
41+,CroppedImageMinimumSourceSize {
42+ id: mascotImage;
43+ objectName: "mascotImage";
44+ anchors { verticalCenter: parent.verticalCenter; }
45+ source: cardData && cardData["mascot"] || "";
46+ width: units.gu(6);
47+ height: units.gu(5.625);
48+ horizontalAlignment: Image.AlignHCenter;
49+ verticalAlignment: Image.AlignVCenter;
50+ visible: false;
51+ }
52+
53+,Item {
54+ id: headerTitleContainer;
55+ anchors { verticalCenter: parent.verticalCenter; }
56+ width: parent.width - x;
57+ implicitHeight: titleLabel.height + subtitleLabel.height;
58+ data: [
59+Label {
60+ id: titleLabel;
61+ objectName: "titleLabel";
62+ anchors { right: parent.right;
63+ rightMargin: units.gu(1);
64+ left: parent.left;
65+ top: parent.top; }
66+ elide: Text.ElideRight;
67+ fontSize: "small";
68+ wrapMode: Text.Wrap;
69+ maximumLineCount: 2;
70+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
71+ color: root.scopeStyle ? root.scopeStyle.foreground : theme.palette.normal.baseText;
72+ visible: showHeader ;
73+ width: undefined;
74+ text: root.title;
75+ font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
76+ horizontalAlignment: Text.AlignLeft;
77+ }
78+,Label {
79+ id: subtitleLabel;
80+ objectName: "subtitleLabel";
81+ anchors { right: parent.right;
82+ left: parent.left;
83+ rightMargin: units.gu(1);
84+ top: titleLabel.bottom;
85+ }
86+ anchors.topMargin: units.dp(2);
87+ elide: Text.ElideRight;
88+ maximumLineCount: 1;
89+ fontSize: "x-small";
90+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
91+ color: root.scopeStyle ? root.scopeStyle.foreground : theme.palette.normal.baseText;
92+ visible: titleLabel.visible && titleLabel.text;
93+ text: cardData && cardData["subtitle"] || "";
94+ font.weight: Font.Light;
95+ }
96+]
97+}
98+]
99+}
100+UbuntuShape {
101+ id: touchdown;
102+ objectName: "touchdown";
103+ anchors { fill: root }
104+ visible: root.artShapeStyle != "shadow" && root.artShapeStyle != "icon" && root.pressed;
105+ radius: "medium";
106+ borderSource: "radius_pressed.sci"
107+}
108+implicitHeight: row.y + row.height + units.gu(1);
109+}
tests/plugins/Dash/cardcreator/5.res+5−7
@@ -5,11 +5,9 @@ AbstractButton {
5 property string backgroundShapeStyle: "inset";
5 property string backgroundShapeStyle: "inset";
6 property real fontScale: 1.0;
6 property real fontScale: 1.0;
7 property var scopeStyle: null;
7 property var scopeStyle: null;
8− property int titleAlignment: Text.AlignLeft;
9 property int fixedHeaderHeight: -1;
8 property int fixedHeaderHeight: -1;
10 property size fixedArtShapeSize: Qt.size(-1, -1);
9 property size fixedArtShapeSize: Qt.size(-1, -1);
11 readonly property string title: cardData && cardData["title"] || "";
10 readonly property string title: cardData && cardData["title"] || "";
12− property bool asynchronous: true;
13 property bool showHeader: true;
11 property bool showHeader: true;
14 implicitWidth: childrenRect.width;
12 implicitWidth: childrenRect.width;
15 enabled: false;
13 enabled: false;
@@ -25,7 +23,7 @@ Item {
25 objectName: "artShapeLoader";
23 objectName: "artShapeLoader";
26 readonly property string cardArt: cardData && cardData["art"] || "";
24 readonly property string cardArt: cardData && cardData["art"] || "";
27 active: cardArt != "";
25 active: cardArt != "";
28− asynchronous: root.asynchronous;
26+ asynchronous: true;
29 visible: status == Loader.Ready;
27 visible: status == Loader.Ready;
30 sourceComponent: Item {
28 sourceComponent: Item {
31 id: artShape;
29 id: artShape;
@@ -82,7 +80,7 @@ Item {
82 id: artImage;
80 id: artImage;
83 objectName: "artImage";
81 objectName: "artImage";
84 source: artShapeLoader.cardArt;
82 source: artShapeLoader.cardArt;
85− asynchronous: root.asynchronous;
83+ asynchronous: true;
86 width: root.width;
84 width: root.width;
87 height: width / artShape.aspect;
85 height: width / artShape.aspect;
88 }
86 }
@@ -91,10 +89,10 @@ Item {
91 }
89 }
92Loader {
90Loader {
93 id: overlayLoader;
91 id: overlayLoader;
94− readonly property real overlayHeight: (fixedHeaderHeight > 0 ? fixedHeaderHeight : headerHeight) + units.gu(2);
92+ readonly property real overlayHeight: root.fixedHeaderHeight + units.gu(2);
95 anchors.fill: artShapeHolder;
93 anchors.fill: artShapeHolder;
96 active: artShapeLoader.active && artShapeLoader.item && artShapeLoader.item.image.status === Image.Ready || false;
94 active: artShapeLoader.active && artShapeLoader.item && artShapeLoader.item.image.status === Image.Ready || false;
97− asynchronous: root.asynchronous;
95+ asynchronous: true;
98 visible: showHeader && status == Loader.Ready;
96 visible: showHeader && status == Loader.Ready;
99 sourceComponent: UbuntuShapeOverlay {
97 sourceComponent: UbuntuShapeOverlay {
100 id: overlay;
98 id: overlay;
@@ -126,7 +124,7 @@ Label {
126 width: undefined;
124 width: undefined;
127 text: root.title;
125 text: root.title;
128 font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
126 font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
129− horizontalAlignment: root.titleAlignment;
127+ horizontalAlignment: Text.AlignLeft;
130 }
128 }
131Label {
129Label {
132 id: subtitleLabel;
130 id: subtitleLabel;
tests/plugins/Dash/cardcreator/5.res.cardcreatornew file+156
@@ -0,0 +1,156 @@
1+AbstractButton {
2+ id: root;
3+ property var cardData;
4+ property string artShapeStyle: "inset";
5+ property string backgroundShapeStyle: "inset";
6+ property real fontScale: 1.0;
7+ property var scopeStyle: null;
8+ property size fixedArtShapeSize: Qt.size(-1, -1);
9+ readonly property string title: cardData && cardData["title"] || "";
10+ property bool showHeader: true;
11+ implicitWidth: childrenRect.width;
12+ enabled: false;
13+
14+readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);
15+Item {
16+ id: artShapeHolder;
17+ height: root.fixedArtShapeSize.height > 0 ? root.fixedArtShapeSize.height : artShapeLoader.height;
18+ width: root.fixedArtShapeSize.width > 0 ? root.fixedArtShapeSize.width : artShapeLoader.width;
19+ anchors { horizontalCenter: parent.horizontalCenter; }
20+ Loader {
21+ id: artShapeLoader;
22+ objectName: "artShapeLoader";
23+ readonly property string cardArt: cardData && cardData["art"] || "";
24+ active: cardArt != "";
25+ asynchronous: false;
26+ visible: status == Loader.Ready;
27+ sourceComponent: Item {
28+ id: artShape;
29+ objectName: "artShape";
30+ visible: image.status == Image.Ready;
31+ readonly property alias image: artImage;
32+ ShaderEffectSource {
33+ id: artShapeSource;
34+ sourceItem: artImage;
35+ anchors.centerIn: parent;
36+ width: 1;
37+ height: 1;
38+ hideSource: false;
39+ }
40+ Loader {
41+ anchors.fill: parent;
42+ visible: false;
43+ sourceComponent: root.artShapeStyle === "icon" ? artShapeIconComponent : artShapeShapeComponent;
44+ Component {
45+ id: artShapeShapeComponent;
46+ UbuntuShape {
47+ source: artShapeSource;
48+ sourceFillMode: UbuntuShape.PreserveAspectCrop;
49+ radius: "medium";
50+ aspect: {
51+ switch (root.artShapeStyle) {
52+ case "inset": return UbuntuShape.Inset;
53+ case "shadow": return UbuntuShape.DropShadow;
54+ default:
55+ case "flat": return UbuntuShape.Flat;
56+ }
57+ }
58+ }
59+ }
60+ Component {
61+ id: artShapeIconComponent;
62+ ProportionalShape { source: artShapeSource; aspect: UbuntuShape.DropShadow; }
63+ }
64+ }
65+ readonly property real fixedArtShapeSizeAspect: (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) ? root.fixedArtShapeSize.width / root.fixedArtShapeSize.height : -1;
66+ readonly property real aspect: fixedArtShapeSizeAspect > 0 ? fixedArtShapeSizeAspect : 1;
67+ Component.onCompleted: { updateWidthHeightBindings(); }
68+ Connections { target: root; onFixedArtShapeSizeChanged: updateWidthHeightBindings(); }
69+ function updateWidthHeightBindings() {
70+ if (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) {
71+ width = root.fixedArtShapeSize.width;
72+ height = root.fixedArtShapeSize.height;
73+ } else {
74+ width = Qt.binding(function() { return image.status !== Image.Ready ? 0 : image.width });
75+ height = Qt.binding(function() { return image.status !== Image.Ready ? 0 : image.height });
76+ }
77+ }
78+ CroppedImageMinimumSourceSize {
79+ id: artImage;
80+ objectName: "artImage";
81+ source: artShapeLoader.cardArt;
82+ asynchronous: false;
83+ width: root.width;
84+ height: width / artShape.aspect;
85+ }
86+ }
87+ }
88+ }
89+Loader {
90+ id: overlayLoader;
91+ readonly property real overlayHeight: headerHeight + units.gu(2);
92+ anchors.fill: artShapeHolder;
93+ active: artShapeLoader.active && artShapeLoader.item && artShapeLoader.item.image.status === Image.Ready || false;
94+ asynchronous: false;
95+ visible: showHeader && status == Loader.Ready;
96+ sourceComponent: UbuntuShapeOverlay {
97+ id: overlay;
98+ property real luminance: Style.luminance(overlayColor);
99+ aspect: UbuntuShape.Flat;
100+ radius: "medium";
101+ overlayColor: cardData && cardData["overlayColor"] || "#99000000";
102+ overlayRect: Qt.rect(0, 1 - overlayLoader.overlayHeight / height, 1, 1);
103+ }
104+ }
105+readonly property int headerHeight: titleLabel.height + subtitleLabel.height + subtitleLabel.anchors.topMargin;
106+Label {
107+ id: titleLabel;
108+ objectName: "titleLabel";
109+ anchors { right: parent.right;
110+ rightMargin: units.gu(1);
111+ left: parent.left;
112+ leftMargin: units.gu(1);
113+ top: overlayLoader.top;
114+ topMargin: units.gu(1) + overlayLoader.height - overlayLoader.overlayHeight;
115+ }
116+ elide: Text.ElideRight;
117+ fontSize: "small";
118+ wrapMode: Text.Wrap;
119+ maximumLineCount: 2;
120+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
121+ color: root.scopeStyle && overlayLoader.item ? root.scopeStyle.getTextColor(overlayLoader.item.luminance) : (overlayLoader.item && overlayLoader.item.luminance > 0.7 ? theme.palette.normal.baseText : "white");
122+ visible: showHeader && overlayLoader.active;
123+ width: undefined;
124+ text: root.title;
125+ font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
126+ horizontalAlignment: Text.AlignLeft;
127+ }
128+Label {
129+ id: subtitleLabel;
130+ objectName: "subtitleLabel";
131+ anchors { left: titleLabel.left;
132+ leftMargin: titleLabel.leftMargin;
133+ rightMargin: units.gu(1);
134+ right: titleLabel.right;
135+ top: titleLabel.bottom;
136+ }
137+ anchors.topMargin: units.dp(2);
138+ elide: Text.ElideRight;
139+ maximumLineCount: 1;
140+ fontSize: "x-small";
141+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
142+ color: root.scopeStyle && overlayLoader.item ? root.scopeStyle.getTextColor(overlayLoader.item.luminance) : (overlayLoader.item && overlayLoader.item.luminance > 0.7 ? theme.palette.normal.baseText : "white");
143+ visible: titleLabel.visible && titleLabel.text;
144+ text: cardData && cardData["subtitle"] || "";
145+ font.weight: Font.Light;
146+ }
147+UbuntuShape {
148+ id: touchdown;
149+ objectName: "touchdown";
150+ anchors { fill: artShapeHolder }
151+ visible: root.artShapeStyle != "shadow" && root.artShapeStyle != "icon" && root.pressed;
152+ radius: "medium";
153+ borderSource: "radius_pressed.sci"
154+}
155+implicitHeight: artShapeHolder.height;
156+}
tests/plugins/Dash/cardcreator/6.res+2−4
@@ -5,11 +5,9 @@ AbstractButton {
5 property string backgroundShapeStyle: "inset";
5 property string backgroundShapeStyle: "inset";
6 property real fontScale: 1.0;
6 property real fontScale: 1.0;
7 property var scopeStyle: null;
7 property var scopeStyle: null;
8− property int titleAlignment: Text.AlignLeft;
9 property int fixedHeaderHeight: -1;
8 property int fixedHeaderHeight: -1;
10 property size fixedArtShapeSize: Qt.size(-1, -1);
9 property size fixedArtShapeSize: Qt.size(-1, -1);
11 readonly property string title: cardData && cardData["title"] || "";
10 readonly property string title: cardData && cardData["title"] || "";
12− property bool asynchronous: true;
13 property bool showHeader: true;
11 property bool showHeader: true;
14 implicitWidth: childrenRect.width;
12 implicitWidth: childrenRect.width;
15 enabled: true;
13 enabled: true;
@@ -18,7 +16,7 @@ Loader {
18 id: backgroundLoader;
16 id: backgroundLoader;
19 objectName: "backgroundLoader";
17 objectName: "backgroundLoader";
20 anchors.fill: parent;
18 anchors.fill: parent;
21− asynchronous: root.asynchronous;
19+ asynchronous: true;
22 visible: status == Loader.Ready;
20 visible: status == Loader.Ready;
23 sourceComponent: UbuntuShape {
21 sourceComponent: UbuntuShape {
24 objectName: "background";
22 objectName: "background";
@@ -81,7 +79,7 @@ Item {
81 width: undefined;
79 width: undefined;
82 text: root.title;
80 text: root.title;
83 font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
81 font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
84− horizontalAlignment: root.titleAlignment;
82+ horizontalAlignment: Text.AlignLeft;
85 }
83 }
86,Label {
84,Label {
87 id: subtitleLabel;
85 id: subtitleLabel;
tests/plugins/Dash/cardcreator/6.res.cardcreatornew file+126
@@ -0,0 +1,126 @@
1+AbstractButton {
2+ id: root;
3+ property var cardData;
4+ property string artShapeStyle: "inset";
5+ property string backgroundShapeStyle: "inset";
6+ property real fontScale: 1.0;
7+ property var scopeStyle: null;
8+ property size fixedArtShapeSize: Qt.size(-1, -1);
9+ readonly property string title: cardData && cardData["title"] || "";
10+ property bool showHeader: true;
11+ implicitWidth: childrenRect.width;
12+ enabled: true;
13+
14+Loader {
15+ id: backgroundLoader;
16+ objectName: "backgroundLoader";
17+ anchors.fill: parent;
18+ asynchronous: false;
19+ visible: status == Loader.Ready;
20+ sourceComponent: UbuntuShape {
21+ objectName: "background";
22+ radius: "medium";
23+ aspect: {
24+ switch (root.backgroundShapeStyle) {
25+ case "inset": return UbuntuShape.Inset;
26+ case "shadow": return UbuntuShape.DropShadow;
27+ default:
28+ case "flat": return UbuntuShape.Flat;
29+ }
30+ }
31+ backgroundColor: getColor(0) || "white";
32+ secondaryBackgroundColor: getColor(1) || backgroundColor;
33+ backgroundMode: UbuntuShape.VerticalGradient;
34+ anchors.fill: parent;
35+ source: backgroundImage.source ? backgroundImage : null;
36+ property real luminance: Style.luminance(backgroundColor);
37+ property Image backgroundImage: Image {
38+ objectName: "backgroundImage";
39+ source: {
40+ if (cardData && typeof cardData["background"] === "string") return cardData["background"];
41+ else return decodeURI("http://assets.ubuntu.com/sites/ubuntu/latest/u/img/logos/logo-ubuntu-grey.png");
42+ }
43+ }
44+ function getColor(index) {
45+ if (cardData && typeof cardData["background"] === "object"
46+ && (cardData["background"]["type"] === "color" || cardData["background"]["type"] === "gradient")) {
47+ return cardData["background"]["elements"][index];
48+ } else return index === 0 ? undefined : undefined;
49+ }
50+ }
51+ }
52+readonly property size artShapeSize: Qt.size(-1, -1);
53+readonly property int headerHeight: titleLabel.height + subtitleLabel.height + subtitleLabel.anchors.topMargin;
54+Item {
55+ id: headerTitleContainer;
56+ anchors { right: parent.right; left: parent.left;
57+ top: parent.top;
58+ topMargin: units.gu(1);
59+ leftMargin: units.gu(1);
60+ }
61+ width: parent.width - x;
62+ implicitHeight: titleLabel.height + subtitleLabel.height;
63+ data: [
64+ Label {
65+ id: titleLabel;
66+ objectName: "titleLabel";
67+ anchors { right: emblemIcon.left;
68+ rightMargin: emblemIcon.width > 0 ? units.gu(0.5) : 0;
69+ left: parent.left;
70+ top: parent.top; }
71+ elide: Text.ElideRight;
72+ fontSize: "small";
73+ wrapMode: Text.Wrap;
74+ maximumLineCount: 2;
75+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
76+ color: backgroundLoader.active && backgroundLoader.item && root.scopeStyle ? root.scopeStyle.getTextColor(backgroundLoader.item.luminance) : (backgroundLoader.item && backgroundLoader.item.luminance > 0.7 ? theme.palette.normal.baseText : "white");
77+ visible: showHeader ;
78+ width: undefined;
79+ text: root.title;
80+ font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
81+ horizontalAlignment: Text.AlignLeft;
82+ }
83+,Label {
84+ id: subtitleLabel;
85+ objectName: "subtitleLabel";
86+ anchors { right: parent.right;
87+ left: parent.left;
88+ rightMargin: units.gu(1);
89+ top: titleLabel.bottom;
90+ }
91+ anchors.topMargin: units.dp(2);
92+ elide: Text.ElideRight;
93+ maximumLineCount: 1;
94+ fontSize: "x-small";
95+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
96+ color: backgroundLoader.active && backgroundLoader.item && root.scopeStyle ? root.scopeStyle.getTextColor(backgroundLoader.item.luminance) : (backgroundLoader.item && backgroundLoader.item.luminance > 0.7 ? theme.palette.normal.baseText : "white");
97+ visible: titleLabel.visible && titleLabel.text;
98+ text: cardData && cardData["subtitle"] || "";
99+ font.weight: Font.Light;
100+ }
101+,Icon {
102+ id: emblemIcon;
103+ objectName: "emblemIcon";
104+ anchors {
105+ bottom: titleLabel.baseline;
106+ right: parent.right;
107+ rightMargin: units.gu(1);
108+ }
109+ source: cardData && cardData["emblem"] || "";
110+ color: backgroundLoader.active && backgroundLoader.item && root.scopeStyle ? root.scopeStyle.getTextColor(backgroundLoader.item.luminance) : (backgroundLoader.item && backgroundLoader.item.luminance > 0.7 ? theme.palette.normal.baseText : "white");
111+ height: source != "" ? titleLabel.font.pixelSize : 0;
112+ width: implicitWidth > 0 && implicitHeight > 0 ? (implicitWidth / implicitHeight * height) : implicitWidth;
113+ }
114+
115+ ]
116+ }
117+UbuntuShape {
118+ id: touchdown;
119+ objectName: "touchdown";
120+ anchors { fill: backgroundLoader }
121+ visible: root.artShapeStyle != "shadow" && root.artShapeStyle != "icon" && root.pressed;
122+ radius: "medium";
123+ borderSource: "radius_pressed.sci"
124+}
125+implicitHeight: headerTitleContainer.y + headerTitleContainer.height + units.gu(1);
126+}
tests/plugins/Dash/cardcreator/7.res+3−5
@@ -5,11 +5,9 @@ AbstractButton {
5 property string backgroundShapeStyle: "inset";
5 property string backgroundShapeStyle: "inset";
6 property real fontScale: 1.0;
6 property real fontScale: 1.0;
7 property var scopeStyle: null;
7 property var scopeStyle: null;
8− property int titleAlignment: Text.AlignLeft;
9 property int fixedHeaderHeight: -1;
8 property int fixedHeaderHeight: -1;
10 property size fixedArtShapeSize: Qt.size(-1, -1);
9 property size fixedArtShapeSize: Qt.size(-1, -1);
11 readonly property string title: cardData && cardData["title"] || "";
10 readonly property string title: cardData && cardData["title"] || "";
12− property bool asynchronous: true;
13 property bool showHeader: true;
11 property bool showHeader: true;
14 implicitWidth: childrenRect.width;
12 implicitWidth: childrenRect.width;
15 enabled: true;
13 enabled: true;
@@ -18,7 +16,7 @@ Loader {
18 id: backgroundLoader;
16 id: backgroundLoader;
19 objectName: "backgroundLoader";
17 objectName: "backgroundLoader";
20 anchors.fill: parent;
18 anchors.fill: parent;
21− asynchronous: root.asynchronous;
19+ asynchronous: true;
22 visible: status == Loader.Ready;
20 visible: status == Loader.Ready;
23 sourceComponent: UbuntuShape {
21 sourceComponent: UbuntuShape {
24 objectName: "background";
22 objectName: "background";
@@ -59,7 +57,7 @@ Row {
59 objectName: "outerRow";
57 objectName: "outerRow";
60 property real margins: units.gu(1);
58 property real margins: units.gu(1);
61 spacing: margins;
59 spacing: margins;
62− height: root.fixedHeaderHeight != -1 ? root.fixedHeaderHeight : implicitHeight;
60+ height: root.fixedHeaderHeight;
63 anchors { top: parent.top;
61 anchors { top: parent.top;
64 topMargin: units.gu(1);
62 topMargin: units.gu(1);
65 left: parent.left;
63 left: parent.left;
@@ -103,7 +101,7 @@ CroppedImageMinimumSourceSize {
103 width: undefined;
101 width: undefined;
104 text: root.title;
102 text: root.title;
105 font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
103 font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
106− horizontalAlignment: root.titleAlignment;
104+ horizontalAlignment: Text.AlignLeft;
107 }
105 }
108,Label {
106,Label {
109 id: subtitleLabel;
107 id: subtitleLabel;
tests/plugins/Dash/cardcreator/7.res.cardcreatornew file+149
@@ -0,0 +1,149 @@
1+AbstractButton {
2+ id: root;
3+ property var cardData;
4+ property string artShapeStyle: "inset";
5+ property string backgroundShapeStyle: "inset";
6+ property real fontScale: 1.0;
7+ property var scopeStyle: null;
8+ property size fixedArtShapeSize: Qt.size(-1, -1);
9+ readonly property string title: cardData && cardData["title"] || "";
10+ property bool showHeader: true;
11+ implicitWidth: childrenRect.width;
12+ enabled: true;
13+
14+Loader {
15+ id: backgroundLoader;
16+ objectName: "backgroundLoader";
17+ anchors.fill: parent;
18+ asynchronous: false;
19+ visible: status == Loader.Ready;
20+ sourceComponent: UbuntuShape {
21+ objectName: "background";
22+ radius: "medium";
23+ aspect: {
24+ switch (root.backgroundShapeStyle) {
25+ case "inset": return UbuntuShape.Inset;
26+ case "shadow": return UbuntuShape.DropShadow;
27+ default:
28+ case "flat": return UbuntuShape.Flat;
29+ }
30+ }
31+ backgroundColor: getColor(0) || "white";
32+ secondaryBackgroundColor: getColor(1) || backgroundColor;
33+ backgroundMode: UbuntuShape.VerticalGradient;
34+ anchors.fill: parent;
35+ source: backgroundImage.source ? backgroundImage : null;
36+ property real luminance: Style.luminance(backgroundColor);
37+ property Image backgroundImage: Image {
38+ objectName: "backgroundImage";
39+ source: {
40+ if (cardData && typeof cardData["background"] === "string") return cardData["background"];
41+ else return "";
42+ }
43+ }
44+ function getColor(index) {
45+ if (cardData && typeof cardData["background"] === "object"
46+ && (cardData["background"]["type"] === "color" || cardData["background"]["type"] === "gradient")) {
47+ return cardData["background"]["elements"][index];
48+ } else return index === 0 ? "#E9E9E9" : "#E9AAE9";
49+ }
50+ }
51+ }
52+readonly property size artShapeSize: Qt.size(-1, -1);
53+readonly property int headerHeight: row.height;
54+Row {
55+ id: row;
56+ objectName: "outerRow";
57+ property real margins: units.gu(1);
58+ spacing: margins;
59+ anchors { top: parent.top;
60+ topMargin: units.gu(1);
61+ left: parent.left;
62+ }
63+ anchors.right: parent.right;
64+ anchors.margins: margins;
65+ anchors.rightMargin: 0;
66+ data: [
67+CroppedImageMinimumSourceSize {
68+ id: mascotImage;
69+ objectName: "mascotImage";
70+ anchors { verticalCenter: parent.verticalCenter; }
71+ source: cardData && cardData["mascot"] || "";
72+ width: units.gu(6);
73+ height: units.gu(5.625);
74+ horizontalAlignment: Image.AlignHCenter;
75+ verticalAlignment: Image.AlignVCenter;
76+ visible: showHeader;
77+ }
78+
79+,Item {
80+ id: headerTitleContainer;
81+ anchors { verticalCenter: parent.verticalCenter; }
82+ width: parent.width - x;
83+ implicitHeight: titleLabel.height + subtitleLabel.height + attributesRow.height;
84+ data: [
85+ Label {
86+ id: titleLabel;
87+ objectName: "titleLabel";
88+ anchors { right: parent.right;
89+ rightMargin: units.gu(1);
90+ left: parent.left;
91+ top: parent.top; }
92+ elide: Text.ElideRight;
93+ fontSize: "small";
94+ wrapMode: Text.Wrap;
95+ maximumLineCount: 2;
96+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
97+ color: backgroundLoader.active && backgroundLoader.item && root.scopeStyle ? root.scopeStyle.getTextColor(backgroundLoader.item.luminance) : (backgroundLoader.item && backgroundLoader.item.luminance > 0.7 ? theme.palette.normal.baseText : "white");
98+ visible: showHeader ;
99+ width: undefined;
100+ text: root.title;
101+ font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
102+ horizontalAlignment: Text.AlignLeft;
103+ }
104+,Label {
105+ id: subtitleLabel;
106+ objectName: "subtitleLabel";
107+ anchors { right: parent.right;
108+ left: parent.left;
109+ rightMargin: units.gu(1);
110+ top: titleLabel.bottom;
111+ }
112+ anchors.topMargin: units.dp(2);
113+ elide: Text.ElideRight;
114+ maximumLineCount: 1;
115+ fontSize: "x-small";
116+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
117+ color: backgroundLoader.active && backgroundLoader.item && root.scopeStyle ? root.scopeStyle.getTextColor(backgroundLoader.item.luminance) : (backgroundLoader.item && backgroundLoader.item.luminance > 0.7 ? theme.palette.normal.baseText : "white");
118+ visible: titleLabel.visible && titleLabel.text;
119+ text: cardData && cardData["subtitle"] || "";
120+ font.weight: Font.Light;
121+ }
122+,CardAttributes {
123+ id: attributesRow;
124+ objectName: "attributesRow";
125+ anchors { right: parent.right;
126+ left: parent.left;
127+ rightMargin: units.gu(1);
128+ top: subtitleLabel.bottom;
129+ }
130+ color: backgroundLoader.active && backgroundLoader.item && root.scopeStyle ? root.scopeStyle.getTextColor(backgroundLoader.item.luminance) : (backgroundLoader.item && backgroundLoader.item.luminance > 0.7 ? theme.palette.normal.baseText : "white");
131+ fontScale: root.fontScale;
132+ model: cardData && cardData["attributes"];
133+ }
134+
135+ ]
136+ }
137+
138+ ]
139+ }
140+UbuntuShape {
141+ id: touchdown;
142+ objectName: "touchdown";
143+ anchors { fill: backgroundLoader }
144+ visible: root.artShapeStyle != "shadow" && root.artShapeStyle != "icon" && root.pressed;
145+ radius: "medium";
146+ borderSource: "radius_pressed.sci"
147+}
148+implicitHeight: row.y + row.height + units.gu(1);
149+}
tests/plugins/Dash/cardcreator/8.res+3−5
@@ -5,11 +5,9 @@ AbstractButton {
5 property string backgroundShapeStyle: "inset";
5 property string backgroundShapeStyle: "inset";
6 property real fontScale: 1.0;
6 property real fontScale: 1.0;
7 property var scopeStyle: null;
7 property var scopeStyle: null;
8− property int titleAlignment: Text.AlignLeft;
9 property int fixedHeaderHeight: -1;
8 property int fixedHeaderHeight: -1;
10 property size fixedArtShapeSize: Qt.size(-1, -1);
9 property size fixedArtShapeSize: Qt.size(-1, -1);
11 readonly property string title: cardData && cardData["title"] || "";
10 readonly property string title: cardData && cardData["title"] || "";
12− property bool asynchronous: true;
13 property bool showHeader: true;
11 property bool showHeader: true;
14 implicitWidth: childrenRect.width;
12 implicitWidth: childrenRect.width;
15 enabled: true;
13 enabled: true;
@@ -18,7 +16,7 @@ Loader {
18 id: backgroundLoader;
16 id: backgroundLoader;
19 objectName: "backgroundLoader";
17 objectName: "backgroundLoader";
20 anchors.fill: parent;
18 anchors.fill: parent;
21− asynchronous: root.asynchronous;
19+ asynchronous: true;
22 visible: status == Loader.Ready;
20 visible: status == Loader.Ready;
23 sourceComponent: UbuntuShape {
21 sourceComponent: UbuntuShape {
24 objectName: "background";
22 objectName: "background";
@@ -59,7 +57,7 @@ Row {
59 objectName: "outerRow";
57 objectName: "outerRow";
60 property real margins: units.gu(1);
58 property real margins: units.gu(1);
61 spacing: margins;
59 spacing: margins;
62− height: root.fixedHeaderHeight != -1 ? root.fixedHeaderHeight : implicitHeight;
60+ height: root.fixedHeaderHeight;
63 anchors { top: parent.top;
61 anchors { top: parent.top;
64 topMargin: units.gu(1);
62 topMargin: units.gu(1);
65left: parent.left;
63left: parent.left;
@@ -94,7 +92,7 @@ left: parent.left;
94 width: parent.width - x;
92 width: parent.width - x;
95 text: root.title;
93 text: root.title;
96 font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
94 font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
97− horizontalAlignment: root.titleAlignment;
95+ horizontalAlignment: Text.AlignLeft;
98 }
96 }
99
97
100 ]
98 ]
tests/plugins/Dash/cardcreator/8.res.cardcreatornew file+107
@@ -0,0 +1,107 @@
1+AbstractButton {
2+ id: root;
3+ property var cardData;
4+ property string artShapeStyle: "inset";
5+ property string backgroundShapeStyle: "inset";
6+ property real fontScale: 1.0;
7+ property var scopeStyle: null;
8+ property size fixedArtShapeSize: Qt.size(-1, -1);
9+ readonly property string title: cardData && cardData["title"] || "";
10+ property bool showHeader: true;
11+ implicitWidth: childrenRect.width;
12+ enabled: true;
13+
14+Loader {
15+ id: backgroundLoader;
16+ objectName: "backgroundLoader";
17+ anchors.fill: parent;
18+ asynchronous: false;
19+ visible: status == Loader.Ready;
20+ sourceComponent: UbuntuShape {
21+ objectName: "background";
22+ radius: "medium";
23+ aspect: {
24+ switch (root.backgroundShapeStyle) {
25+ case "inset": return UbuntuShape.Inset;
26+ case "shadow": return UbuntuShape.DropShadow;
27+ default:
28+ case "flat": return UbuntuShape.Flat;
29+ }
30+ }
31+ backgroundColor: getColor(0) || "white";
32+ secondaryBackgroundColor: getColor(1) || backgroundColor;
33+ backgroundMode: UbuntuShape.VerticalGradient;
34+ anchors.fill: parent;
35+ source: backgroundImage.source ? backgroundImage : null;
36+ property real luminance: Style.luminance(backgroundColor);
37+ property Image backgroundImage: Image {
38+ objectName: "backgroundImage";
39+ source: {
40+ if (cardData && typeof cardData["background"] === "string") return cardData["background"];
41+ else return "";
42+ }
43+ }
44+ function getColor(index) {
45+ if (cardData && typeof cardData["background"] === "object"
46+ && (cardData["background"]["type"] === "color" || cardData["background"]["type"] === "gradient")) {
47+ return cardData["background"]["elements"][index];
48+ } else return index === 0 ? "#E9E9E9" : "#E9AAE9";
49+ }
50+ }
51+ }
52+readonly property size artShapeSize: Qt.size(-1, -1);
53+readonly property int headerHeight: row.height;
54+Row {
55+ id: row;
56+ objectName: "outerRow";
57+ property real margins: units.gu(1);
58+ spacing: margins;
59+ anchors { top: parent.top;
60+ topMargin: units.gu(1);
61+left: parent.left;
62+ }
63+ anchors.right: parent.right;
64+ anchors.margins: margins;
65+ anchors.rightMargin: 0;
66+ data: [
67+ CroppedImageMinimumSourceSize {
68+ id: mascotImage;
69+ objectName: "mascotImage";
70+ anchors { verticalCenter: parent.verticalCenter; }
71+ source: cardData && cardData["mascot"] || "";
72+ width: units.gu(6);
73+ height: units.gu(5.625);
74+ horizontalAlignment: Image.AlignHCenter;
75+ verticalAlignment: Image.AlignVCenter;
76+ visible: showHeader;
77+ }
78+,Label {
79+ id: titleLabel;
80+ objectName: "titleLabel";
81+ anchors { verticalCenter: parent.verticalCenter;
82+ }
83+ elide: Text.ElideRight;
84+ fontSize: "small";
85+ wrapMode: Text.Wrap;
86+ maximumLineCount: 2;
87+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
88+ color: backgroundLoader.active && backgroundLoader.item && root.scopeStyle ? root.scopeStyle.getTextColor(backgroundLoader.item.luminance) : (backgroundLoader.item && backgroundLoader.item.luminance > 0.7 ? theme.palette.normal.baseText : "white");
89+ visible: showHeader ;
90+ width: parent.width - x;
91+ text: root.title;
92+ font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
93+ horizontalAlignment: Text.AlignLeft;
94+ }
95+
96+ ]
97+ }
98+UbuntuShape {
99+ id: touchdown;
100+ objectName: "touchdown";
101+ anchors { fill: backgroundLoader }
102+ visible: root.artShapeStyle != "shadow" && root.artShapeStyle != "icon" && root.pressed;
103+ radius: "medium";
104+ borderSource: "radius_pressed.sci"
105+}
106+implicitHeight: row.y + row.height + units.gu(1);
107+}
tests/plugins/Dash/cardcreator/9.res+4−5
@@ -5,11 +5,9 @@ AbstractButton {
5 property string backgroundShapeStyle: "inset";
5 property string backgroundShapeStyle: "inset";
6 property real fontScale: 1.0;
6 property real fontScale: 1.0;
7 property var scopeStyle: null;
7 property var scopeStyle: null;
8− property int titleAlignment: Text.AlignLeft;
9 property int fixedHeaderHeight: -1;
8 property int fixedHeaderHeight: -1;
10 property size fixedArtShapeSize: Qt.size(-1, -1);
9 property size fixedArtShapeSize: Qt.size(-1, -1);
11 readonly property string title: cardData && cardData["title"] || "";
10 readonly property string title: cardData && cardData["title"] || "";
12− property bool asynchronous: true;
13 property bool showHeader: true;
11 property bool showHeader: true;
14 implicitWidth: childrenRect.width;
12 implicitWidth: childrenRect.width;
15 enabled: true;
13 enabled: true;
@@ -36,7 +34,7 @@ top: parent.top;
36 width: undefined;
34 width: undefined;
37 text: root.title;
35 text: root.title;
38 font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
36 font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
39− horizontalAlignment: root.titleAlignment;
37+ horizontalAlignment: Text.AlignLeft;
40 }
38 }
41Label {
39Label {
42 id: subtitleLabel;
40 id: subtitleLabel;
@@ -72,7 +70,7 @@ CardAudioProgress {
72 id: audioButton;
70 id: audioButton;
73 anchors.fill: undefined;
71 anchors.fill: undefined;
74 width: height;
72 width: height;
75− height: (root.fixedHeaderHeight > 0 ? root.fixedHeaderHeight : headerHeight) + 2 * units.gu(1);
73+ height: root.fixedHeaderHeight + 2 * units.gu(1);
76 readonly property url source: (cardData["quickPreviewData"] && cardData["quickPreviewData"]["uri"]) || "";
74 readonly property url source: (cardData["quickPreviewData"] && cardData["quickPreviewData"]["uri"]) || "";
77 UbuntuShape {
75 UbuntuShape {
78 anchors.fill: parent;
76 anchors.fill: parent;
@@ -93,7 +91,8 @@ CardAudioProgress {
93 opacity: 0.9;
91 opacity: 0.9;
94 name: DashAudioPlayer.playing && AudioUrlComparer.compare(parent.source, DashAudioPlayer.currentSource) ? "media-playback-pause" : "media-playback-start";
92 name: DashAudioPlayer.playing && AudioUrlComparer.compare(parent.source, DashAudioPlayer.currentSource) ? "media-playback-pause" : "media-playback-start";
95 color: "white";
93 color: "white";
94+ asynchronous: true;
96− }
95+ }
97 onClicked: {
96 onClicked: {
98 if (AudioUrlComparer.compare(source, DashAudioPlayer.currentSource)) {
97 if (AudioUrlComparer.compare(source, DashAudioPlayer.currentSource)) {
99 if (DashAudioPlayer.playing) {
98 if (DashAudioPlayer.playing) {
tests/plugins/Dash/cardcreator/9.res.cardcreatornew file+119
@@ -0,0 +1,119 @@
1+AbstractButton {
2+ id: root;
3+ property var cardData;
4+ property string artShapeStyle: "inset";
5+ property string backgroundShapeStyle: "inset";
6+ property real fontScale: 1.0;
7+ property var scopeStyle: null;
8+ property size fixedArtShapeSize: Qt.size(-1, -1);
9+ readonly property string title: cardData && cardData["title"] || "";
10+ property bool showHeader: true;
11+ implicitWidth: childrenRect.width;
12+ enabled: true;
13+
14+readonly property size artShapeSize: Qt.size(-1, -1);
15+readonly property int headerHeight: titleLabel.height + subtitleLabel.height + subtitleLabel.anchors.topMargin + audioProgressBar.height + audioProgressBar.anchors.topMargin;
16+Label {
17+ id: titleLabel;
18+ objectName: "titleLabel";
19+ anchors { right: parent.right;
20+rightMargin: units.gu(1);
21+left: audioButton.right;
22+ leftMargin: units.gu(1);
23+top: parent.top;
24+ topMargin: units.gu(1);
25+ }
26+ elide: Text.ElideRight;
27+ fontSize: "small";
28+ wrapMode: Text.Wrap;
29+ maximumLineCount: 2;
30+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
31+ color: root.scopeStyle ? root.scopeStyle.foreground : theme.palette.normal.baseText;
32+ visible: showHeader ;
33+ width: undefined;
34+ text: root.title;
35+ font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
36+ horizontalAlignment: Text.AlignLeft;
37+ }
38+Label {
39+ id: subtitleLabel;
40+ objectName: "subtitleLabel";
41+ anchors { left: titleLabel.left;
42+ leftMargin: titleLabel.leftMargin;
43+rightMargin: units.gu(1);
44+right: titleLabel.right;
45+top: titleLabel.bottom;
46+ }
47+ anchors.topMargin: units.dp(2);
48+ elide: Text.ElideRight;
49+ maximumLineCount: 1;
50+ fontSize: "x-small";
51+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
52+ color: root.scopeStyle ? root.scopeStyle.foreground : theme.palette.normal.baseText;
53+ visible: titleLabel.visible && titleLabel.text;
54+ text: cardData && cardData["subtitle"] || "";
55+ font.weight: Font.Light;
56+ }
57+CardAudioProgress {
58+ id: audioProgressBar;
59+ duration: (cardData["quickPreviewData"] && cardData["quickPreviewData"]["duration"]) || 0;
60+ source: (cardData["quickPreviewData"] && cardData["quickPreviewData"]["uri"]) || "";
61+ anchors {
62+ bottom: audioButton.bottom;
63+ left: audioButton.right;
64+ right: parent.right;
65+ margins: units.gu(1);
66+ }
67+ color: root.scopeStyle ? root.scopeStyle.foreground : theme.palette.normal.baseText;
68+ }AbstractButton {
69+ id: audioButton;
70+ anchors.fill: undefined;
71+ width: height;
72+ height: headerHeight + 2 * units.gu(1);
73+ readonly property url source: (cardData["quickPreviewData"] && cardData["quickPreviewData"]["uri"]) || "";
74+ UbuntuShape {
75+ anchors.fill: parent;
76+ visible: parent.pressed;
77+ radius: "medium";
78+ }
79+ Rectangle {
80+ color: Qt.rgba(0, 0, 0, 0.5);
81+ anchors.centerIn: parent;
82+ width: parent.width * 0.5;
83+ height: width;
84+ radius: width / 2;
85+ }
86+ Icon {
87+ anchors.centerIn: parent;
88+ width: parent.width * 0.3;
89+ height: width;
90+ opacity: 0.9;
91+ name: DashAudioPlayer.playing && AudioUrlComparer.compare(parent.source, DashAudioPlayer.currentSource) ? "media-playback-pause" : "media-playback-start";
92+ color: "white";
93+ asynchronous: false;
94+ }
95+ onClicked: {
96+ if (AudioUrlComparer.compare(source, DashAudioPlayer.currentSource)) {
97+ if (DashAudioPlayer.playing) {
98+ DashAudioPlayer.pause();
99+ } else {
100+ DashAudioPlayer.play();
101+ }
102+ } else {
103+ var playlist = (cardData["quickPreviewData"] && cardData["quickPreviewData"]["playlist"]) || null;
104+ DashAudioPlayer.playSource(source, playlist);
105+ }
106+ }
107+ onPressAndHold: {
108+ root.pressAndHold();
109+ }
110+ }UbuntuShape {
111+ id: touchdown;
112+ objectName: "touchdown";
113+ anchors { fill: root }
114+ visible: root.artShapeStyle != "shadow" && root.artShapeStyle != "icon" && root.pressed;
115+ radius: "medium";
116+ borderSource: "radius_pressed.sci"
117+ }
118+implicitHeight: audioButton.height;
119+}
tests/plugins/Dash/cardcreatortest.cpp+32−22
@@ -45,6 +45,30 @@ private Q_SLOTS:
45 delete view;
45 delete view;
46 }
46 }
47
47
48+ void compareFileContents(const QString &filePath, const QString cardStringResult)
49+ {
50+ QFile testResultFile(filePath);
51+ QVERIFY(testResultFile.open(QIODevice::ReadOnly));
52+ QTextStream ts2(&testResultFile);
53+
54+ // Record failed results to /tmp
55+ const QString executedResult = cardStringResult;
56+ QTemporaryFile tmpFile(QDir::tempPath() + QDir::separator() + "testCardCreatorFailedResultXXXXXX");
57+ tmpFile.open();
58+ tmpFile.setAutoRemove(false);
59+ tmpFile.write(executedResult.toUtf8().constData());
60+
61+ // Line by line comparison
62+ const QStringList expectedLines = ts2.readAll().trimmed().replace(QRegExp("\n\\s*\n"),"\n").split("\n");
63+ const QStringList cardStringResultLines = cardStringResult.trimmed().replace(QRegExp("\n\\s*\n"),"\n").split("\n");
64+ for (int i = 0; i < expectedLines.size(); ++i) {
65+ QCOMPARE(cardStringResultLines[i].simplified(), expectedLines[i].simplified());
66+ }
67+
68+ // Remove the result if it passed
69+ tmpFile.setAutoRemove(true);
70+ }
71+
48 void testKnownCases()
72 void testKnownCases()
49 {
73 {
50 const QString templateString("template: ");
74 const QString templateString("template: ");
@@ -68,33 +92,19 @@ private Q_SLOTS:
68 const QString templateJSON = lines[0].mid(templateString.length());
92 const QString templateJSON = lines[0].mid(templateString.length());
69 const QString componentsJSON = lines[1].mid(componentsString.length());
93 const QString componentsJSON = lines[1].mid(componentsString.length());
70 const QString resultFileName = lines[2].mid(resultString.length());
94 const QString resultFileName = lines[2].mid(resultString.length());
71−
72 QVariant cardStringResult;
95 QVariant cardStringResult;
73− QMetaObject::invokeMethod(view->rootObject(), "cardString", Q_RETURN_ARG(QVariant, cardStringResult), Q_ARG(QVariant, templateJSON), Q_ARG(QVariant, componentsJSON));
96+ QVariant createCardComponentResult;
74−
75− QFile testResultFile(testDirPath + resultFileName);
76− QVERIFY(testResultFile.open(QIODevice::ReadOnly));
77− QTextStream ts2(&testResultFile);
78
97
79− // Record failed results to /tmp
98+ QMetaObject::invokeMethod(view->rootObject(), "cardString", Q_RETURN_ARG(QVariant, cardStringResult), Q_ARG(QVariant, templateJSON), Q_ARG(QVariant, componentsJSON), Q_ARG(QVariant, false));
80− const QString executedResult = cardStringResult.toString();
99+ compareFileContents(testDirPath + resultFileName, cardStringResult.toString());
81− QTemporaryFile tmpFile(QDir::tempPath() + QDir::separator() + "testCardCreatorFailedResultXXXXXX");
82− tmpFile.open();
83− tmpFile.setAutoRemove(false);
84− tmpFile.write(executedResult.toUtf8().constData());
85
100
86− // Line by line comparison
101+ QMetaObject::invokeMethod(view->rootObject(), "cardString", Q_RETURN_ARG(QVariant, cardStringResult), Q_ARG(QVariant, templateJSON), Q_ARG(QVariant, componentsJSON), Q_ARG(QVariant, true));
87− const QStringList expectedLines = ts2.readAll().trimmed().replace(QRegExp("\n\\s*\n"),"\n").split("\n");
102+ compareFileContents(testDirPath + resultFileName + ".cardcreator", cardStringResult.toString());
88− const QStringList cardStringResultLines = cardStringResult.toString().trimmed().replace(QRegExp("\n\\s*\n"),"\n").split("\n");
89− for (int i = 0; i < expectedLines.size(); ++i) {
90− QCOMPARE(cardStringResultLines[i].simplified(), expectedLines[i].simplified());
91− }
92
103
93− // Remove the result if it passed
104+ QMetaObject::invokeMethod(view->rootObject(), "createCardComponent", Q_RETURN_ARG(QVariant, createCardComponentResult), Q_ARG(QVariant, templateJSON), Q_ARG(QVariant, componentsJSON), Q_ARG(QVariant, false));
94− tmpFile.setAutoRemove(true);
105+ QVERIFY(createCardComponentResult.toBool());
95
106
96− QVariant createCardComponentResult;
97− QMetaObject::invokeMethod(view->rootObject(), "createCardComponent", Q_RETURN_ARG(QVariant, createCardComponentResult), Q_ARG(QVariant, templateJSON), Q_ARG(QVariant, componentsJSON));
107+ QMetaObject::invokeMethod(view->rootObject(), "createCardComponent", Q_RETURN_ARG(QVariant, createCardComponentResult), Q_ARG(QVariant, templateJSON), Q_ARG(QVariant, componentsJSON), Q_ARG(QVariant, true));
98 QVERIFY(createCardComponentResult.toBool());
108 QVERIFY(createCardComponentResult.toBool());
99 }
109 }
100 }
110 }
tests/plugins/Dash/cardcreatortest.qml+4−4
@@ -19,11 +19,11 @@ import "../../../plugins/Dash/CardCreator.js" as CardCreator
19
19
20Item {
20Item {
21 id: root
21 id: root
22− function cardString(template, components) {
22+ function cardString(template, components, isCardCreator) {
23− return CardCreator.cardString(JSON.parse(template), JSON.parse(components));
23+ return CardCreator.cardString(JSON.parse(template), JSON.parse(components), isCardCreator);
24 }
24 }
25
25
26− function createCardComponent(template, components) {
26+ function createCardComponent(template, components, isCardCreator) {
27− return CardCreator.createCardComponent(root, JSON.parse(template), JSON.parse(components)) !== null;
27+ return CardCreator.createCardComponent(root, JSON.parse(template), JSON.parse(components), isCardCreator) !== null;
28 }
28 }
29}
29}
tests/plugins/IntegratedLightDM/CMakeLists.txt+25
@@ -65,3 +65,28 @@ add_unity8_uitest(GreeterUsers GreeterUsersModelTestExec
65 DEPENDS MockLightDM
65 DEPENDS MockLightDM
66 LIGHTDM
66 LIGHTDM
67 )
67 )
68+
69+add_executable(GreeterIntegratedTestExec
70+ integrated.cpp
71+ )
72+qt5_use_modules(GreeterIntegratedTestExec Core DBus Quick Test)
73+target_link_libraries(GreeterIntegratedTestExec
74+ ${GLIB_LIBRARIES}
75+ integratedLightDMSession
76+ )
77+target_include_directories(GreeterIntegratedTestExec PUBLIC
78+ ${GLIB_INCLUDE_DIRS}
79+ ${CMAKE_SOURCE_DIR}/plugins/IntegratedLightDM/liblightdm
80+ )
81+add_unity8_unittest(GreeterIntegrated dbus-test-runner
82+ ENVIRONMENT LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/plugins/IntegratedLightDM/liblightdm/
83+ ARG_PREFIX "--parameter"
84+ ARGS
85+ --task $<TARGET_FILE:mock-server>
86+ --task-name server
87+ --ignore-return
88+ --task $<TARGET_FILE:GreeterIntegratedTestExec>
89+ --task-name client
90+ --wait-for org.freedesktop.Accounts
91+ DEPENDS integratedLightDMSession
92+ )
tests/plugins/IntegratedLightDM/integrated.cppnew file+91
@@ -0,0 +1,91 @@
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 "UsersModel.h"
18+
19+#include <glib.h>
20+#include <QDBusInterface>
21+#include <QDBusReply>
22+#include <QtTest>
23+
24+class GreeterIntegratedTest : public QObject
25+{
26+ Q_OBJECT
27+
28+private Q_SLOTS:
29+
30+ void init()
31+ {
32+ m_accounts = new QDBusInterface(QStringLiteral("org.freedesktop.Accounts"),
33+ QStringLiteral("/org/freedesktop/Accounts"),
34+ QStringLiteral("org.freedesktop.Accounts"),
35+ QDBusConnection::sessionBus());
36+ QDBusReply<bool> addReply = m_accounts->call(QStringLiteral("AddUser"),
37+ g_get_user_name());
38+ QVERIFY(addReply.isValid());
39+ QCOMPARE(addReply.value(), true);
40+
41+ m_user = new QDBusInterface(QStringLiteral("org.freedesktop.Accounts"),
42+ QStringLiteral("/%1").arg(g_get_user_name()),
43+ QStringLiteral("org.freedesktop.DBus.Properties"),
44+ QDBusConnection::sessionBus());
45+
46+ m_model = new QLightDM::UsersModel();
47+ QVERIFY(m_model);
48+ }
49+
50+ void cleanup()
51+ {
52+ QDBusReply<bool> addReply = m_accounts->call(QStringLiteral("RemoveUser"),
53+ g_get_user_name());
54+ QVERIFY(addReply.isValid());
55+ QCOMPARE(addReply.value(), true);
56+
57+ delete m_model;
58+ delete m_accounts;
59+ delete m_user;
60+ }
61+
62+ void testWatchRealName()
63+ {
64+ auto index = m_model->index(0, 0);
65+
66+ QCOMPARE(m_model->data(index, QLightDM::UsersModel::RealNameRole).toString(),
67+ QStringLiteral(""));
68+
69+ // The real AccountsService doesn't let you set via dbus properties. You
70+ // have to call SetRealName instead (so that they can protect the call
71+ // via policykit). But our test server does let us.
72+ QVariant wrapped = QVariant::fromValue(QDBusVariant(QStringLiteral("Test User")));
73+ QDBusReply<void> reply = m_user->call(QStringLiteral("Set"),
74+ QStringLiteral("org.freedesktop.Accounts.User"),
75+ QStringLiteral("RealName"),
76+ wrapped);
77+ QVERIFY(reply.isValid());
78+
79+ QTRY_COMPARE(m_model->data(index, QLightDM::UsersModel::RealNameRole).toString(),
80+ QStringLiteral("Test User"));
81+ }
82+
83+private:
84+ QLightDM::UsersModel *m_model;
85+ QDBusInterface *m_accounts;
86+ QDBusInterface *m_user;
87+};
88+
89+QTEST_MAIN(GreeterIntegratedTest)
90+
91+#include "integrated.moc"
tests/plugins/Ubuntu/Gestures/CMakeLists.txt+7−1
@@ -20,6 +20,11 @@ macro(build_gesture_test CLASSNAME)
20 target_link_libraries(${CLASSNAME}TestExec UbuntuGesturesQml UbuntuGestures)
20 target_link_libraries(${CLASSNAME}TestExec UbuntuGesturesQml UbuntuGestures)
21endmacro()
21endmacro()
22
22
23+set(UNITY_IMPORT_PATHS
24+ ${CMAKE_BINARY_DIR}/tests/utils/modules
25+ ${UNITY_PLUGINPATH}
26+)
27+
23macro(add_gesture_unit_test CLASSNAME)
28macro(add_gesture_unit_test CLASSNAME)
24 build_gesture_test(${CLASSNAME})
29 build_gesture_test(${CLASSNAME})
25 add_unity8_unittest(${CLASSNAME} ${CLASSNAME}TestExec
30 add_unity8_unittest(${CLASSNAME} ${CLASSNAME}TestExec
@@ -36,7 +41,7 @@ macro(add_gesture_ui_test CLASSNAME)
36 DEPENDS UbuntuGesturesTest-qmlfiles
41 DEPENDS UbuntuGesturesTest-qmlfiles
37 ${ARGN}
42 ${ARGN}
38 )
43 )
39− add_manual_qml_test(. ${CLASSNAME} IMPORT_PATHS ${UNITY_PLUGINPATH})
44+ add_manual_qml_test(. ${CLASSNAME} IMPORT_PATHS ${UNITY_IMPORT_PATHS})
40endmacro()
45endmacro()
41
46
42add_gesture_ui_test(DirectionalDragArea)
47add_gesture_ui_test(DirectionalDragArea)
@@ -46,3 +51,4 @@ add_gesture_ui_test(TouchDispatcher)
46add_gesture_ui_test(TouchGate)
51add_gesture_ui_test(TouchGate)
47add_gesture_unit_test(Damper)
52add_gesture_unit_test(Damper)
48add_gesture_unit_test(AxisVelocityCalculator)
53add_gesture_unit_test(AxisVelocityCalculator)
54+add_gesture_ui_test(TouchGestureArea)
tests/plugins/Ubuntu/Gestures/GestureTest.cpp+44
@@ -17,6 +17,7 @@
17#include "GestureTest.h"
17#include "GestureTest.h"
18
18
19#include <qpa/qwindowsysteminterface.h>
19#include <qpa/qwindowsysteminterface.h>
20+#include <private/qquickwindow_p.h>
20#include <QQmlEngine>
21#include <QQmlEngine>
21#include <QQuickView>
22#include <QQuickView>
22#include <QtTest>
23#include <QtTest>
@@ -72,6 +73,49 @@ void GestureTest::cleanup()
72 m_view = nullptr;
73 m_view = nullptr;
73}
74}
74
75
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+
75////////////////////////// TouchMemento /////////////////////////////
119////////////////////////// TouchMemento /////////////////////////////
76
120
77TouchMemento::TouchMemento(const QTouchEvent *touchEvent)
121TouchMemento::TouchMemento(const QTouchEvent *touchEvent)
tests/plugins/Ubuntu/Gestures/GestureTest.h+13−1
@@ -72,7 +72,7 @@ class GestureTest : public QObject
72{
72{
73 Q_OBJECT
73 Q_OBJECT
74public:
74public:
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
76 GestureTest(const QString &qmlFilename);
76 GestureTest(const QString &qmlFilename);
77
77
78protected Q_SLOTS:
78protected Q_SLOTS:
@@ -80,6 +80,18 @@ protected Q_SLOTS:
80 virtual void init(); // called right before each and every test function is executed
80 virtual void init(); // called right before each and every test function is executed
81 virtual void cleanup(); // called right after each and every test function is executed
81 virtual void cleanup(); // called right after each and every test function is executed
82
82
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+
83protected:
95protected:
84 QTouchDevice *m_device;
96 QTouchDevice *m_device;
85 QQuickView *m_view;
97 QQuickView *m_view;
tests/plugins/Ubuntu/Gestures/tst_DirectionalDragArea.cpp−56
@@ -20,7 +20,6 @@
20#include <QtQml/QQmlEngine>
20#include <QtQml/QQmlEngine>
21#include <QPointer>
21#include <QPointer>
22#include <private/qquickmousearea_p.h>
22#include <private/qquickmousearea_p.h>
23−#include <private/qquickwindow_p.h>
24
23
25
24
26#include <DirectionalDragArea.h>
25#include <DirectionalDragArea.h>
@@ -109,18 +108,6 @@ private Q_SLOTS:
109 void makoRightEdgeDrag_verticalDownwards();
108 void makoRightEdgeDrag_verticalDownwards();
110 void makoLeftEdgeDrag_slowStart();
109 void makoLeftEdgeDrag_slowStart();
111 void makoLeftEdgeDrag_movesSlightlyBackwardsOnStart();
110 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);
124};
111};
125
112
126tst_DirectionalDragArea::tst_DirectionalDragArea()
113tst_DirectionalDragArea::tst_DirectionalDragArea()
@@ -141,49 +128,6 @@ void tst_DirectionalDragArea::init()
141 QTRY_COMPARE(m_view->height(), (int)m_view->rootObject()->height());
128 QTRY_COMPARE(m_view->height(), (int)m_view->rootObject()->height());
142}
129}
143
130
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−
187namespace {
131namespace {
188QPointF calculateInitialTouchPos(DirectionalDragArea *edgeDragArea)
132QPointF calculateInitialTouchPos(DirectionalDragArea *edgeDragArea)
189{
133{
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:
48 QString comment() const override { return "this is a mock"; }
48 QString comment() const override { return "this is a mock"; }
49 QUrl icon() const override { return QUrl(); }
49 QUrl icon() const override { return QUrl(); }
50 ApplicationInfoInterface::Stage stage() const override { return ApplicationInfoInterface::MainStage; }
50 ApplicationInfoInterface::Stage stage() const override { return ApplicationInfoInterface::MainStage; }
51+ void setStage(ApplicationInfoInterface::Stage) override {}
51 ApplicationInfoInterface::State state() const override { return ApplicationInfoInterface::Running; }
52 ApplicationInfoInterface::State state() const override { return ApplicationInfoInterface::Running; }
52 bool focused() const override { return m_focused; }
53 bool focused() const override { return m_focused; }
53 QString splashTitle() const override { return QString(); }
54 QString splashTitle() const override { return QString(); }
tests/plugins/Utils/CMakeLists.txt+7−1
@@ -4,15 +4,21 @@ include_directories(
4 ${CMAKE_CURRENT_BINARY_DIR}
4 ${CMAKE_CURRENT_BINARY_DIR}
5 )
5 )
6
6
7+add_definitions(-DCONFIG_TESTING)
8+
7foreach(util_test
9foreach(util_test
8 QLimitProxyModel
10 QLimitProxyModel
9 UnitySortFilterProxyModel
11 UnitySortFilterProxyModel
10 WindowInputMonitor
12 WindowInputMonitor
13+ DeviceConfigParser
11)
14)
12 add_executable(${util_test}TestExec ${util_test}Test.cpp ModelTest.cpp)
15 add_executable(${util_test}TestExec ${util_test}Test.cpp ModelTest.cpp)
13 qt5_use_modules(${util_test}TestExec Test Core Qml)
16 qt5_use_modules(${util_test}TestExec Test Core Qml)
14 target_link_libraries(${util_test}TestExec Utils-qml)
17 target_link_libraries(${util_test}TestExec Utils-qml)
15− add_unity8_unittest(${util_test} ${util_test}TestExec ADD_TEST)
18+ add_unity8_unittest(${util_test} ${util_test}TestExec ADD_TEST
19+ ENVIRONMENT XDG_CONFIG_HOME=${CMAKE_CURRENT_BINARY_DIR}
20+ )
21+
16endforeach()
22endforeach()
17
23
18# plain qml test
24# plain qml test
tests/plugins/Utils/DeviceConfigParserTest.cppnew file+70
@@ -0,0 +1,70 @@
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+// local
18+#include "deviceconfigparser.h"
19+
20+// Qt
21+#include <QTest>
22+#include <QDebug>
23+#include <QSettings>
24+
25+class DeviceConfigParserTest : public QObject
26+{
27+ Q_OBJECT
28+
29+private Q_SLOTS:
30+
31+ void initTestCase() {
32+ }
33+
34+ void testDefaults()
35+ {
36+ DeviceConfigParser p;
37+ p.setName("nonexistent");
38+
39+ QCOMPARE(p.supportedOrientations(), Qt::PortraitOrientation | Qt::InvertedPortraitOrientation | Qt::LandscapeOrientation | Qt::InvertedLandscapeOrientation);
40+ QCOMPARE(p.primaryOrientation(), Qt::PrimaryOrientation);
41+ QCOMPARE(p.portraitOrientation(), Qt::PortraitOrientation);
42+ QCOMPARE(p.invertedPortraitOrientation(), Qt::InvertedPortraitOrientation);
43+ QCOMPARE(p.landscapeOrientation(), Qt::LandscapeOrientation);
44+ QCOMPARE(p.invertedLandscapeOrientation(), Qt::InvertedLandscapeOrientation);
45+ }
46+
47+ void testCustomFile() {
48+ QSettings s("./devices.conf", QSettings::IniFormat);
49+ s.beginGroup("fakedevice");
50+
51+ s.setValue("SupportedOrientations", QStringList() << "Portrait" << "Landscape" << "InvertedLandscape");
52+ s.setValue("PrimaryOrientation", "InvertedLandscape");
53+ s.sync();
54+
55+ DeviceConfigParser p;
56+ p.setName("fakedevice");
57+
58+ QCOMPARE(p.supportedOrientations(), Qt::PortraitOrientation | Qt::LandscapeOrientation | Qt::InvertedLandscapeOrientation);
59+ QCOMPARE(p.primaryOrientation(), Qt::InvertedLandscapeOrientation);
60+ QCOMPARE(p.portraitOrientation(), Qt::PortraitOrientation);
61+ QCOMPARE(p.invertedPortraitOrientation(), Qt::InvertedPortraitOrientation);
62+ QCOMPARE(p.landscapeOrientation(), Qt::LandscapeOrientation);
63+ QCOMPARE(p.invertedLandscapeOrientation(), Qt::InvertedLandscapeOrientation);
64+ }
65+
66+};
67+
68+QTEST_GUILESS_MAIN(DeviceConfigParserTest)
69+
70+#include "DeviceConfigParserTest.moc"
tests/qmltests/CMakeLists.txt+1
@@ -5,6 +5,7 @@ add_unity8_qmltest(. OrientedShell LIGHTDM)
5add_unity8_qmltest(. DisabledScreenNotice)
5add_unity8_qmltest(. DisabledScreenNotice)
6add_unity8_qmltest(. Shell LIGHTDM)
6add_unity8_qmltest(. Shell LIGHTDM)
7add_unity8_qmltest(. ShellWithPin LIGHTDM)
7add_unity8_qmltest(. ShellWithPin LIGHTDM)
8+add_unity8_qmltest(. DeviceConfiguration)
8add_unity8_qmltest(Components Background)
9add_unity8_qmltest(Components Background)
9add_unity8_qmltest(Components Carousel)
10add_unity8_qmltest(Components Carousel)
10add_unity8_qmltest(Components Dialogs LIGHTDM)
11add_unity8_qmltest(Components Dialogs LIGHTDM)
tests/qmltests/Components/tst_Lockscreen.qml+14−7
@@ -235,11 +235,14 @@ Rectangle {
235
235
236 function test_unlock_data() {
236 function test_unlock_data() {
237 return [
237 return [
238− {tag: "numeric", alphanumeric: false, password: "1234", minPinLength: 4, maxPinLength: 4},
238+ {tag: "numeric", alphanumeric: false, password: "1234", minPinLength: 4, maxPinLength: 4, keyboard: false},
239− {tag: "alphanumeric", alphanumeric: true, password: "password", minPinLength: -1, maxPinLength: -1},
239+ {tag: "alphanumeric", alphanumeric: true, password: "password", minPinLength: -1, maxPinLength: -1, keyboard: false},
240− {tag: "numeric (wrong)", alphanumeric: false, password: "4321", minPinLength: 4, maxPinLength: 4},
240+ {tag: "numeric (wrong)", alphanumeric: false, password: "4321", minPinLength: 4, maxPinLength: 4, keyboard: false},
241− {tag: "alphanumeric (wrong)", alphanumeric: true, password: "drowssap", minPinLength: -1, maxPinLength: -1},
241+ {tag: "alphanumeric (wrong)", alphanumeric: true, password: "drowssap", minPinLength: -1, maxPinLength: -1, keyboard: false},
242− {tag: "flexible length", alphanumeric: false, password: "1234", minPinLength: -1, maxPinLength: -1},
242+ {tag: "flexible length", alphanumeric: false, password: "1234", minPinLength: -1, maxPinLength: -1, keyboard: false},
243+ {tag: "numeric", alphanumeric: false, password: "1234", minPinLength: 4, maxPinLength: 4, keyboard: true},
244+ {tag: "numeric (wrong)", alphanumeric: false, password: "4321", minPinLength: 4, maxPinLength: 4, keyboard: true},
245+ {tag: "flexible length", alphanumeric: false, password: "1234", minPinLength: -1, maxPinLength: -1, keyboard: true}
243 ]
246 ]
244 }
247 }
245
248
@@ -259,8 +262,12 @@ Rectangle {
259 } else {
262 } else {
260 for (var i = 0; i < data.password.length; ++i) {
263 for (var i = 0; i < data.password.length; ++i) {
261 var character = data.password.charAt(i)
264 var character = data.password.charAt(i)
265+ if (data.keyboard) {
266+ typeString(character)
267+ } else {
262− var button = findChild(lockscreen, "pinPadButton" + character)
268+ var button = findChild(lockscreen, "pinPadButton" + character)
263− mouseClick(button, units.gu(1), units.gu(1))
269+ mouseClick(button, units.gu(1), units.gu(1))
270+ }
264 }
271 }
265 var confirmButton = findChild(lockscreen, "confirmButton");
272 var confirmButton = findChild(lockscreen, "confirmButton");
266 mouseClick(confirmButton, units.gu(1), units.gu(1));
273 mouseClick(confirmButton, units.gu(1), units.gu(1));
tests/qmltests/Dash/Previews/tst_PreviewHeader.qml+18−1
@@ -38,6 +38,14 @@ Rectangle {
38 "attributes": [{"value":"text1","icon":"image://theme/ok"},{"value":"text2","icon":"image://theme/cancel"}]
38 "attributes": [{"value":"text1","icon":"image://theme/ok"},{"value":"text2","icon":"image://theme/cancel"}]
39 }
39 }
40
40
41+ property var emblemheaderjson: {
42+ "title": "THE TITLE",
43+ "subtitle": "Something catchy",
44+ "mascot": "../graphics/play_button.png",
45+ "attributes": [{"value":"text1","icon":"image://theme/ok"},{"value":"text2","icon":"image://theme/cancel"}],
46+ "emblem": Qt.resolvedUrl("../artwork/emblem.png")
47+ }
48+
41 property var brokenheaderjson: {
49 property var brokenheaderjson: {
42 "title": "THE TITLE",
50 "title": "THE TITLE",
43 "subtitle": "Something catchy",
51 "subtitle": "Something catchy",
@@ -64,7 +72,7 @@ Rectangle {
64
72
65 PreviewHeader {
73 PreviewHeader {
66 id: previewHeader
74 id: previewHeader
67− widgetData: headerjson
75+ widgetData: emblemheaderjson
68 width: units.gu(30)
76 width: units.gu(30)
69
77
70 Rectangle {
78 Rectangle {
@@ -136,6 +144,15 @@ Rectangle {
136 }
144 }
137 }
145 }
138
146
147+ function test_emblem() {
148+ previewHeader.widgetData = emblemheaderjson;
149+
150+ var emblemIcon = findChild(previewHeader, "emblemIcon");
151+ var titleLabel = findChild(previewHeader, "titleLabel");
152+ verify(emblemIcon.height > 0);
153+ compare(emblemIcon.height, titleLabel.font.pixelSize);
154+ }
155+
139 function test_json() {
156 function test_json() {
140 headerjson = origHeaderjson;
157 headerjson = origHeaderjson;
141 previewHeader.widgetData = headerjson;
158 previewHeader.widgetData = headerjson;
tests/qmltests/Dash/Previews/tst_PreviewSharing.qml+17−3
@@ -28,13 +28,18 @@ Rectangle {
28
28
29 property var shareData: {
29 property var shareData: {
30 "uri": [
30 "uri": [
31− "Text here",
31+ "file:///this/is/an/url",
32− "text here 2",
32+ "file:///this/is/an/url/2",
33− "text here 3"
33+ "file:///this/is/an/url/3"
34 ],
34 ],
35 "content-type": "text"
35 "content-type": "text"
36 }
36 }
37
37
38+ property var shareDataString: {
39+ "uri": "file:///this/is/an/url",
40+ "content-type": "text"
41+ }
42+
38 PreviewSharing {
43 PreviewSharing {
39 id: previewSharing
44 id: previewSharing
40 anchors { left: parent.left; bottom: parent.bottom; }
45 anchors { left: parent.left; bottom: parent.bottom; }
@@ -55,5 +60,14 @@ Rectangle {
55 mouseClick(previewSharing);
60 mouseClick(previewSharing);
56 compare(peerPicker.visible, true);
61 compare(peerPicker.visible, true);
57 }
62 }
63+
64+ function test_createExportedItems() {
65+ var exportedItems = previewSharing.createExportedItems(shareData["uri"]);
66+ for (var i = 0; i < exportedItems.length; i++) {
67+ compare(exportedItems[i].url, shareData["uri"][i]);
68+ }
69+ exportedItems = previewSharing.createExportedItems(shareDataString["uri"]);
70+ compare(exportedItems[0].url, shareDataString["uri"]);
71+ }
58 }
72 }
59}
73}
tests/qmltests/Dash/tst_CardTool.qml+4−4
@@ -339,22 +339,22 @@ Rectangle {
339 function test_card_title_alignment(data) {
339 function test_card_title_alignment(data) {
340 selector.selectedIndex = data.index;
340 selector.selectedIndex = data.index;
341
341
342− tryCompare(cardTool, "titleAlignment", Text.AlignLeft);
342+ tryCompareFunction(function() { return findChild(internalCard, "titleLabel").horizontalAlignment == Text.AlignLeft; }, true);
343
343
344 cardTool.components['title'] = { "field": "title", "align": "center" };
344 cardTool.components['title'] = { "field": "title", "align": "center" };
345 cardTool.componentsChanged();
345 cardTool.componentsChanged();
346
346
347− tryCompare(cardTool, "titleAlignment", data.value);
347+ tryCompareFunction(function() { return findChild(internalCard, "titleLabel").horizontalAlignment == data.value; }, true);
348
348
349 cardTool.components['title'] = { "field": "title" };
349 cardTool.components['title'] = { "field": "title" };
350 cardTool.componentsChanged();
350 cardTool.componentsChanged();
351
351
352− tryCompare(cardTool, "titleAlignment", data.value);
352+ tryCompareFunction(function() { return findChild(internalCard, "titleLabel").horizontalAlignment == data.value; }, true);
353
353
354 cardTool.components['title'] = { "field": "title", "align": "left" };
354 cardTool.components['title'] = { "field": "title", "align": "left" };
355 cardTool.componentsChanged();
355 cardTool.componentsChanged();
356
356
357− tryCompare(cardTool, "titleAlignment", Text.AlignLeft);
357+ tryCompareFunction(function() { return findChild(internalCard, "titleLabel").horizontalAlignment == Text.AlignLeft; }, true);
358 }
358 }
359
359
360 function test_categoryLayout_data() {
360 function test_categoryLayout_data() {
tests/qmltests/Dash/tst_Dash.qml+89
@@ -70,6 +70,42 @@ Item {
70 waitForRendering(findChild(dash, "scopeLoader0").item);
70 waitForRendering(findChild(dash, "scopeLoader0").item);
71 }
71 }
72
72
73+ function scrollToCategory(categoryName) {
74+ var dashContentList = findChild(dash, "dashContentList");
75+ var genericScopeView = dashContentList.currentItem;
76+ var categoryListView = findChild(genericScopeView, "categoryListView");
77+ tryCompareFunction(function() {
78+ var category = findChild(genericScopeView, categoryName);
79+ if (category && category.y > 0 && category.y < genericScopeView.height) return true;
80+ touchFlick(genericScopeView, genericScopeView.width/2, units.gu(20),
81+ genericScopeView.width/2, genericScopeView.y)
82+ tryCompare(categoryListView, "moving", false);
83+ return false;
84+ }, true);
85+
86+ tryCompareFunction(function() { return findChild(genericScopeView, "delegate0") !== null; }, true);
87+ return findChild(genericScopeView, categoryName);
88+ }
89+
90+ function clickCategoryDelegate(category, delegate) {
91+ var dashContentList = findChild(dash, "dashContentList");
92+ var genericScopeView = dashContentList.currentItem;
93+ if (category === undefined) category = 0;
94+ if (delegate === undefined) delegate = 0;
95+ tryCompareFunction(function() {
96+ var cardGrid = findChild(genericScopeView, "dashCategory"+category);
97+ if (cardGrid != null) {
98+ var tile = findChild(cardGrid, "delegate"+delegate);
99+ return tile != null;
100+ }
101+ return false;
102+ },
103+ true);
104+ var tile = findChild(findChild(genericScopeView, "dashCategory"+category), "delegate"+delegate);
105+ waitForRendering(tile);
106+ mouseClick(tile);
107+ }
108+
73 function test_manage_dash_clickscope_unfavoritable() {
109 function test_manage_dash_clickscope_unfavoritable() {
74 // Show the manage dash
110 // Show the manage dash
75 touchFlick(dash, dash.width / 2, dash.height - 1, dash.width / 2, units.gu(2));
111 touchFlick(dash, dash.width / 2, dash.height - 1, dash.width / 2, units.gu(2));
@@ -565,5 +601,58 @@ Item {
565 tryCompare(dashTempScopeItem, "x", dashTempScopeItem.width);
601 tryCompare(dashTempScopeItem, "x", dashTempScopeItem.width);
566 tryCompare(dashTempScopeItem, "visible", false);
602 tryCompare(dashTempScopeItem, "visible", false);
567 }
603 }
604+
605+ function test_openScope()
606+ {
607+ scrollToCategory("dashCategory2");
608+ clickCategoryDelegate(2, 2);
609+
610+ var dashTempScopeItem = findChild(dash, "dashTempScopeItem");
611+ tryCompare(dashTempScopeItem, "x", 0);
612+ tryCompare(dashTempScopeItem, "visible", true);
613+ tryCompare(dashContent, "x", -dash.width);
614+
615+ // Go back
616+ var dashTempScopeItemHeader = findChild(dashTempScopeItem, "scopePageHeader");
617+ var backButton = findChild(findChild(dashTempScopeItemHeader, "innerPageHeader"), "customBackButton");
618+ mouseClick(backButton);
619+
620+ // Check temp scope is gone
621+ tryCompare(dashTempScopeItem, "x", dash.width);
622+ tryCompare(dashTempScopeItem, "visible", false);
623+ tryCompare(dashContent, "x", 0);
624+ }
625+
626+ function test_tempScopeItemXOnResize()
627+ {
628+ // Go to a temp scope
629+ touchFlick(dash, dash.width / 2, dash.height - 1, dash.width / 2, units.gu(2));
630+ var bottomEdgeController = findInvisibleChild(dash, "bottomEdgeController");
631+ tryCompare(bottomEdgeController, "progress", 1);
632+ var nonfavScopesListCategory = findChild(dash, "scopesListCategoryother");
633+ var nonfavScopesListCategoryList = findChild(nonfavScopesListCategory, "scopesListCategoryInnerList");
634+ tryCompare(nonfavScopesListCategoryList, "currentIndex", 0);
635+ mouseClick(nonfavScopesListCategoryList.currentItem);
636+ var dashTempScopeItem = findChild(dash, "dashTempScopeItem");
637+ tryCompare(dashTempScopeItem, "x", 0);
638+ tryCompare(dashTempScopeItem, "visible", true);
639+
640+ shell.width = units.gu(80);
641+ tryCompare(dashTempScopeItem, "x", 0);
642+ tryCompare(dashContent, "x", -dash.width);
643+
644+ shell.width = units.gu(40);
645+ tryCompare(dashTempScopeItem, "x", 0);
646+
647+ // Go back
648+ var dashTempScopeItemHeader = findChild(dashTempScopeItem, "scopePageHeader");
649+ var backButton = findChild(findChild(dashTempScopeItemHeader, "innerPageHeader"), "customBackButton");
650+ mouseClick(backButton);
651+
652+ // Check temp scope is gone
653+ tryCompare(dashTempScopeItem, "x", dash.width);
654+ tryCompare(dashTempScopeItem, "visible", false);
655+ tryCompare(dashContent, "x", 0);
656+ }
568 }
657 }
569}
658}
tests/qmltests/Greeter/tst_NarrowView.qml+9
@@ -518,5 +518,14 @@ Item {
518 tryCompare(coverPage, "shown", false);
518 tryCompare(coverPage, "shown", false);
519 tryCompare(coverPage, "showProgress", 0);
519 tryCompare(coverPage, "showProgress", 0);
520 }
520 }
521+
522+ // Make sure that if user has a mouse, they can still get rid of cover page
523+ function test_mouseClickHidesCoverPage() {
524+ var coverPage = findChild(view, "coverPage");
525+
526+ verify(coverPage.shown);
527+ mouseClick(coverPage, coverPage.width/2, coverPage.height - units.gu(2));
528+ verify(!coverPage.shown);
529+ }
521 }
530 }
522}
531}
tests/qmltests/Launcher/tst_Launcher.qml+254−18
@@ -28,12 +28,13 @@ import Utils 0.1 // For EdgeBarrierSettings
28 launcher. */
28 launcher. */
29Item {
29Item {
30 id: root
30 id: root
31− width: units.gu(50)
31+ width: units.gu(140)
32− height: units.gu(55)
32+ height: units.gu(70)
33
33
34 Loader {
34 Loader {
35 id: launcherLoader
35 id: launcherLoader
36 anchors.fill: parent
36 anchors.fill: parent
37+ focus: true
37 property bool itemDestroyed: false
38 property bool itemDestroyed: false
38 sourceComponent: Component {
39 sourceComponent: Component {
39 Launcher {
40 Launcher {
@@ -68,6 +69,7 @@ Item {
68
69
69 Component.onCompleted: {
70 Component.onCompleted: {
70 launcherLoader.itemDestroyed = false;
71 launcherLoader.itemDestroyed = false;
72+ launcherLoader.focus = true
71 edgeBarrierControls.target = testCase.findChild(this, "edgeBarrierController");
73 edgeBarrierControls.target = testCase.findChild(this, "edgeBarrierController");
72 }
74 }
73 Component.onDestruction: {
75 Component.onDestruction: {
@@ -77,11 +79,40 @@ Item {
77 }
79 }
78 }
80 }
79
81
82+ Binding {
83+ target: launcherLoader.item
84+ property: "lockedVisible"
85+ value: lockedVisibleCheckBox.checked
86+ }
87+ Binding {
88+ target: launcherLoader.item
89+ property: "panelWidth"
90+ value: units.gu(Math.round(widthSlider.value))
91+ }
92+
80 ColumnLayout {
93 ColumnLayout {
81 anchors { bottom: parent.bottom; right: parent.right; margins: units.gu(1) }
94 anchors { bottom: parent.bottom; right: parent.right; margins: units.gu(1) }
82 spacing: units.gu(1)
95 spacing: units.gu(1)
83 width: childrenRect.width
96 width: childrenRect.width
84
97
98+ RowLayout {
99+ CheckBox {
100+ id: lockedVisibleCheckBox
101+ checked: false
102+ }
103+ Label {
104+ text: "Launcher always visible"
105+ }
106+ }
107+
108+ Slider {
109+ id: widthSlider
110+ Layout.fillWidth: true
111+ minimumValue: 6
112+ maximumValue: 12
113+ value: 10
114+ }
115+
85 MouseTouchEmulationCheckbox {}
116 MouseTouchEmulationCheckbox {}
86
117
87 EdgeBarrierControls {
118 EdgeBarrierControls {
@@ -102,6 +133,15 @@ Item {
102 Layout.fillWidth: true
133 Layout.fillWidth: true
103 }
134 }
104
135
136+ Button {
137+ text: "open for kbd navigation"
138+ onClicked: {
139+ launcherLoader.item.openForKeyboardNavigation()
140+ launcherLoader.item.forceActiveFocus();// = true
141+ }
142+ Layout.fillWidth: true
143+ }
144+
105 Row {
145 Row {
106 spacing: units.gu(1)
146 spacing: units.gu(1)
107
147
@@ -206,10 +246,6 @@ Item {
206 // growing while populating it with icons etc.
246 // growing while populating it with icons etc.
207 tryCompare(listView, "flicking", false);
247 tryCompare(listView, "flicking", false);
208
248
209− // Make sure noone changed the height of the window. The issue this test case
210− // is verifying only happens on certain heights of the Launcher
211− compare(root.height, units.gu(55));
212−
213 compare(listView.contentY, -listView.topMargin, "Launcher did not start up with first item unfolded");
249 compare(listView.contentY, -listView.topMargin, "Launcher did not start up with first item unfolded");
214
250
215 // Now do check that snapping is in fact enabled
251 // Now do check that snapping is in fact enabled
@@ -266,13 +302,39 @@ Item {
266
302
267 function positionLauncherListAtBeginning() {
303 function positionLauncherListAtBeginning() {
268 var listView = testCase.findChild(launcherLoader.item, "launcherListView");
304 var listView = testCase.findChild(launcherLoader.item, "launcherListView");
269− listView.contentY = -listView.topMargin;
305+ var moveAnimation = findInvisibleChild(listView, "moveAnimation")
306+
307+ listView.moveToIndex(0);
308+
309+ waitForRendering(listView);
310+ tryCompare(moveAnimation, "running", false);
270 }
311 }
271 function positionLauncherListAtEnd() {
312 function positionLauncherListAtEnd() {
272 var listView = testCase.findChild(launcherLoader.item, "launcherListView");
313 var listView = testCase.findChild(launcherLoader.item, "launcherListView");
273− if ((listView.contentHeight + listView.topMargin + listView.bottomMargin) > listView.height) {
314+ var moveAnimation = findInvisibleChild(listView, "moveAnimation")
274− listView.contentY = listView.topMargin + listView.contentHeight
315+
275− - listView.height;
316+ listView.moveToIndex(listView.count -1);
317+
318+ waitForRendering(listView);
319+ tryCompare(moveAnimation, "running", false);
320+ }
321+
322+ function assertFocusOnIndex(index) {
323+ var launcherPanel = findChild(launcher, "launcherPanel");
324+ var launcherListView = findChild(launcher, "launcherListView");
325+ var bfbFocusHighlight = findChild(launcher, "bfbFocusHighlight");
326+
327+ waitForRendering(launcher);
328+ tryCompare(launcherPanel, "highlightIndex", index);
329+ compare(bfbFocusHighlight.visible, index === -1);
330+ for (var i = 0; i < launcherListView.count; i++) {
331+ var item = findChild(launcher, "launcherDelegate" + i);
332+ // Delegates might be destroyed when not visible. We can't check if they paint a focus highlight.
333+ // Make sure the requested index does have focus. for the others, try best effort to check if they don't
334+ if (index === i || item) {
335+ var focusRing = findChild(item, "focusRing")
336+ tryCompare(focusRing, "visible", index === i);
337+ }
276 }
338 }
277 }
339 }
278
340
@@ -290,10 +352,10 @@ Item {
290 dragLauncherIntoView()
352 dragLauncherIntoView()
291
353
292 // tapping on the center of the screen should dismiss the launcher
354 // tapping on the center of the screen should dismiss the launcher
293− mouseClick(launcher)
355+ mouseClick(launcher, panel.width + units.gu(5), launcher.height / 2)
294
356
295 // should eventually get fully retracted (hidden)
357 // should eventually get fully retracted (hidden)
296− tryCompare(panel, "x", -launcher.panelWidth, 1000)
358+ tryCompare(panel, "x", -launcher.panelWidth, 2000)
297 }
359 }
298
360
299 /* If I click on the icon of an application on the launcher
361 /* If I click on the icon of an application on the launcher
@@ -377,6 +439,7 @@ Item {
377 wait(100)
439 wait(100)
378 compare(launcher.maxPanelX, -launcher.panelWidth, "Launcher moved even if it shouldn't")
440 compare(launcher.maxPanelX, -launcher.panelWidth, "Launcher moved even if it shouldn't")
379 }
441 }
442+
380 waitUntilLauncherDisappears();
443 waitUntilLauncherDisappears();
381 launcher.available = true;
444 launcher.available = true;
382 }
445 }
@@ -400,6 +463,8 @@ Item {
400 dragLauncherIntoView();
463 dragLauncherIntoView();
401 var launcherListView = findChild(launcher, "launcherListView");
464 var launcherListView = findChild(launcher, "launcherListView");
402 for (var i = 0; i < launcherListView.count; ++i) {
465 for (var i = 0; i < launcherListView.count; ++i) {
466+ launcherListView.moveToIndex(i);
467+ waitForRendering(launcherListView);
403 var delegate = findChild(launcherListView, "launcherDelegate" + i)
468 var delegate = findChild(launcherListView, "launcherDelegate" + i)
404 compare(findChild(delegate, "countEmblem").visible, LauncherModel.get(i).countVisible)
469 compare(findChild(delegate, "countEmblem").visible, LauncherModel.get(i).countVisible)
405 // Intentionally allow type coercion (string/number)
470 // Intentionally allow type coercion (string/number)
@@ -421,7 +486,7 @@ Item {
421 var launcherListView = findChild(launcher, "launcherListView");
486 var launcherListView = findChild(launcher, "launcherListView");
422 for (var i = 0; i < launcherListView.count; ++i) {
487 for (var i = 0; i < launcherListView.count; ++i) {
423 var delegate = findChild(launcherListView, "launcherDelegate" + i)
488 var delegate = findChild(launcherListView, "launcherDelegate" + i)
424− compare(findChild(delegate, "runningHighlight").visible, LauncherModel.get(i).running)
489+ compare(findChild(delegate, "runningHighlight0").visible, LauncherModel.get(i).running)
425 }
490 }
426 }
491 }
427
492
@@ -460,6 +525,7 @@ Item {
460 launcher.lastSelectedApplication = "";
525 launcher.lastSelectedApplication = "";
461 dragLauncherIntoView();
526 dragLauncherIntoView();
462 var listView = findChild(launcher, "launcherListView");
527 var listView = findChild(launcher, "launcherListView");
528+ var moveAnimation = findInvisibleChild(listView, "moveAnimation")
463
529
464 // flicking is unreliable. sometimes it works, sometimes the
530 // flicking is unreliable. sometimes it works, sometimes the
465 // list view moves just a tiny bit or not at all, making tests fail.
531 // list view moves just a tiny bit or not at all, making tests fail.
@@ -470,12 +536,14 @@ Item {
470 } else {
536 } else {
471 positionLauncherListAtEnd();
537 positionLauncherListAtEnd();
472 }
538 }
473− tryCompare(listView, "flicking", false);
474−
475 var oldY = listView.contentY;
539 var oldY = listView.contentY;
476
540
477 mouseClick(listView, listView.width / 2, data.clickY);
541 mouseClick(listView, listView.width / 2, data.clickY);
542+
543+ if (data.expectFlick) {
544+ tryCompare(moveAnimation, "running", true);
545+ }
478− tryCompare(listView, "flicking", false);
546+ tryCompare(moveAnimation, "running", false);
479
547
480 if (data.expectFlick) {
548 if (data.expectFlick) {
481 verify(listView.contentY != oldY);
549 verify(listView.contentY != oldY);
@@ -764,14 +832,15 @@ Item {
764 function test_launcher_dismiss() {
832 function test_launcher_dismiss() {
765 dragLauncherIntoView();
833 dragLauncherIntoView();
766 verify(launcher.state == "visible");
834 verify(launcher.state == "visible");
835+
767− mouseClick(root);
836+ mouseClick(root, root.width / 2, units.gu(1));
768 waitUntilLauncherDisappears();
837 waitUntilLauncherDisappears();
769 verify(launcher.state == "");
838 verify(launcher.state == "");
770
839
771 // and repeat, as a test for regression in lpbug#1531339
840 // and repeat, as a test for regression in lpbug#1531339
772 dragLauncherIntoView();
841 dragLauncherIntoView();
773 verify(launcher.state == "visible");
842 verify(launcher.state == "visible");
774− mouseClick(root);
843+ mouseClick(root, root.width / 2, units.gu(1));
775 waitUntilLauncherDisappears();
844 waitUntilLauncherDisappears();
776 verify(launcher.state == "");
845 verify(launcher.state == "");
777 }
846 }
@@ -1044,5 +1113,172 @@ Item {
1044 LauncherModel.setCountVisible(LauncherModel.get(1).appId, 0)
1113 LauncherModel.setCountVisible(LauncherModel.get(1).appId, 0)
1045 LauncherModel.setCount(LauncherModel.get(1).appId, oldCount)
1114 LauncherModel.setCount(LauncherModel.get(1).appId, oldCount)
1046 }
1115 }
1116+
1117+ function test_longpressSuperKeyShowsHints() {
1118+ var shortCutHint0 = findChild(findChild(launcher, "launcherDelegate0"), "shortcutHint");
1119+
1120+ tryCompare(shortCutHint0, "visible", false);
1121+
1122+ launcher.superPressed = true;
1123+ tryCompare(launcher, "state", "visible");
1124+ tryCompare(shortCutHint0, "visible", true);
1125+
1126+ launcher.superPressed = false;
1127+ tryCompare(launcher, "state", "");
1128+ tryCompare(shortCutHint0, "visible", false);
1129+ }
1130+
1131+ function test_keyboardNavigation() {
1132+ var bfbFocusHighlight = findChild(launcher, "bfbFocusHighlight");
1133+ var quickList = findChild(launcher, "quickList");
1134+ var launcherPanel = findChild(launcher, "launcherPanel");
1135+ var launcherListView = findChild(launcher, "launcherListView");
1136+ var last = launcherListView.count - 1;
1137+
1138+ compare(bfbFocusHighlight.visible, false);
1139+ launcher.openForKeyboardNavigation();
1140+ tryCompare(launcherPanel, "x", 0);
1141+ waitForRendering(launcher);
1142+
1143+ assertFocusOnIndex(-1);
1144+
1145+ // Down should go down
1146+ keyClick(Qt.Key_Down);
1147+ assertFocusOnIndex(0);
1148+
1149+ // Tab should go down
1150+ keyClick(Qt.Key_Tab);
1151+ assertFocusOnIndex(1);
1152+
1153+ // Up should go up
1154+ keyClick(Qt.Key_Up);
1155+ assertFocusOnIndex(0);
1156+
1157+ // Backtab should go up
1158+ keyClick(Qt.Key_Backtab);
1159+ assertFocusOnIndex(-1); // BFB
1160+
1161+ // The list should wrap around
1162+ keyClick(Qt.Key_Up);
1163+ waitForRendering(launcher);
1164+ assertFocusOnIndex(last);
1165+
1166+ keyClick(Qt.Key_Down);
1167+ waitForRendering(launcher);
1168+ keyClick(Qt.Key_Down);
1169+ assertFocusOnIndex(0); // Back to Top
1170+
1171+ // Right opens the quicklist
1172+ keyClick(Qt.Key_Right);
1173+ assertFocusOnIndex(0); // Navigating the quicklist... the launcher focus should not move
1174+ tryCompare(quickList, "visible", true);
1175+ tryCompare(quickList, "selectedIndex", 0)
1176+
1177+ // Down should move down the quicklist
1178+ keyClick(Qt.Key_Down);
1179+ tryCompare(quickList, "selectedIndex", 1)
1180+
1181+ // The quicklist should wrap around too
1182+ keyClick(Qt.Key_Down);
1183+ keyClick(Qt.Key_Down);
1184+ keyClick(Qt.Key_Down);
1185+ tryCompare(quickList, "selectedIndex", 0)
1186+
1187+ // Left gets us back to the launcher
1188+ keyClick(Qt.Key_Left);
1189+ assertFocusOnIndex(0);
1190+ tryCompare(quickList, "visible", false);
1191+
1192+ // Launcher navigation should still work
1193+ // Go bar to top by wrapping around
1194+ keyClick(Qt.Key_Down);
1195+ assertFocusOnIndex(1);
1196+
1197+ keyClick(Qt.Key_Enter);
1198+ assertFocusOnIndex(-2);
1199+ }
1200+
1201+ function test_selectQuicklistItemByKeyboard() {
1202+ launcher.openForKeyboardNavigation();
1203+ waitForRendering(launcher);
1204+
1205+ signalSpy.clear();
1206+ signalSpy.signalName = "quickListTriggered"
1207+
1208+ keyClick(Qt.Key_Down); // Down to launcher item 0
1209+ keyClick(Qt.Key_Down); // Down to launcher item 1
1210+ keyClick(Qt.Key_Right); // Into quicklist
1211+ keyClick(Qt.Key_Down); // Down to quicklist item 1
1212+ keyClick(Qt.Key_Down); // Down to quicklist item 2
1213+ keyClick(Qt.Key_Enter); // Trigger it
1214+
1215+ compare(signalSpy.count, 1, "Quicklist signal wasn't triggered")
1216+ compare(signalSpy.signalArguments[0][0], LauncherModel.get(1).appId)
1217+ compare(signalSpy.signalArguments[0][1], 2)
1218+ assertFocusOnIndex(-2);
1219+ }
1220+
1221+ function test_hideNotWorkingWhenLockedOut_data() {
1222+ return [
1223+ {tag: "locked visible", locked: true},
1224+ {tag: "no locked visible", locked: false},
1225+ ]
1226+ }
1227+
1228+ function test_hideNotWorkingWhenLockedOut(data) {
1229+ launcher.lockedVisible = data.locked;
1230+ if (data.locked) {
1231+ tryCompare(launcher, "state", "visible");
1232+ } else {
1233+ tryCompare(launcher, "state", "");
1234+ }
1235+
1236+ launcher.hide();
1237+ waitForRendering(launcher);
1238+ if (data.locked) {
1239+ verify(launcher.state == "visible");
1240+ } else {
1241+ verify(launcher.state == "");
1242+ }
1243+ }
1244+
1245+ function test_cancelKbdNavigationWitMouse_data() {
1246+ return [
1247+ {tag: "locked out - no quicklist", autohide: false, withQuickList: false },
1248+ {tag: "locked out - with quicklist", autohide: false, withQuickList: true },
1249+ {tag: "autohide - no quicklist", autohide: true, withQuickList: false },
1250+ {tag: "autohide - with quicklist", autohide: true, withQuickList: true },
1251+ ]
1252+ }
1253+
1254+ function test_cancelKbdNavigationWitMouse(data) {
1255+ launcher.autohideEnabled = data.autohide;
1256+ launcher.openForKeyboardNavigation();
1257+ waitForRendering(launcher);
1258+
1259+ var launcherPanel = findChild(launcher, "launcherPanel");
1260+ tryCompare(launcherPanel, "x", 0);
1261+
1262+ var quickList = findChild(launcher, "quickList");
1263+
1264+ keyClick(Qt.Key_Down); // Down to launcher item 0
1265+ keyClick(Qt.Key_Down); // Down to launcher item 1
1266+
1267+ if (data.withQuickList) {
1268+ keyClick(Qt.Key_Right); // Into quicklist
1269+ tryCompare(quickList, "visible", true)
1270+ }
1271+ waitForRendering(launcher)
1272+
1273+ mouseClick(root);
1274+
1275+ if (data.autohide) {
1276+ tryCompare(launcher, "state", "");
1277+ } else {
1278+ tryCompare(launcher, "state", "visible");
1279+ }
1280+
1281+ assertFocusOnIndex(-2);
1282+ }
1047 }
1283 }
1048}
1284}
tests/qmltests/Panel/Indicators/tst_MenuItemFactory.qml+2−4
@@ -274,15 +274,14 @@ Item {
274
274
275 function test_create_progressMenu_data() {
275 function test_create_progressMenu_data() {
276 return [
276 return [
277− {label: "testLabel1", enabled: true, value: 10 },
277+ {label: "testLabel1", value: 10 },
278− {label: "testLabel2", enabled: false, value: 55 },
278+ {label: "testLabel2", value: 55 },
279 ];
279 ];
280 }
280 }
281
281
282 function test_create_progressMenu(data) {
282 function test_create_progressMenu(data) {
283 menuData.type = "com.canonical.indicator.progress"
283 menuData.type = "com.canonical.indicator.progress"
284 menuData.label = data.label;
284 menuData.label = data.label;
285− menuData.sensitive = data.enabled;
286 menuData.actionState = data.value;
285 menuData.actionState = data.value;
287
286
288 loader.data = menuData;
287 loader.data = menuData;
@@ -292,7 +291,6 @@ Item {
292
291
293 compare(loader.item.text, data.label, "Label does not match data");
292 compare(loader.item.text, data.label, "Label does not match data");
294 compare(loader.item.value, data.value, "Value does not match data");
293 compare(loader.item.value, data.value, "Value does not match data");
295− compare(loader.item.enabled, data.enabled, "Enabled does not match data");
296 }
294 }
297
295
298 function test_create_standardMenu_data() {
296 function test_create_standardMenu_data() {
tests/qmltests/Stages/tst_DesktopStage.qml+2−1
@@ -65,8 +65,9 @@ Item {
65 property bool itemDestroyed: false
65 property bool itemDestroyed: false
66 sourceComponent: Component {
66 sourceComponent: Component {
67 DesktopStage {
67 DesktopStage {
68− color: "darkblue"
68+ color: "white"
69 anchors.fill: parent
69 anchors.fill: parent
70+ background: "../../../qml/graphics/tablet_background.jpg"
70
71
71 Component.onCompleted: {
72 Component.onCompleted: {
72 edgeBarrierControls.target = testCase.findChild(this, "edgeBarrierController");
73 edgeBarrierControls.target = testCase.findChild(this, "edgeBarrierController");
tests/qmltests/Stages/tst_PhoneStage.qml+19−6
@@ -40,12 +40,6 @@ Item {
40 orientations: Orientations {}
40 orientations: Orientations {}
41 }
41 }
42
42
43− Binding {
44− target: ApplicationManager
45− property: "rightMargin"
46− value: phoneStage.anchors.rightMargin
47− }
48−
49 Rectangle {
43 Rectangle {
50 anchors { fill: parent; leftMargin: phoneStage.width }
44 anchors { fill: parent; leftMargin: phoneStage.width }
51
45
@@ -77,6 +71,15 @@ Item {
77 ApplicationManager.get(appList.selectedAppIndex).setState(ApplicationInfoInterface.Stopped);
71 ApplicationManager.get(appList.selectedAppIndex).setState(ApplicationInfoInterface.Stopped);
78 }
72 }
79 }
73 }
74+ EdgeBarrierControls {
75+ id: edgeBarrierControls
76+ text: "Drag here to pull out spread"
77+ backgroundColor: "blue"
78+ onDragged: { phoneStage.pushRightEdge(amount); }
79+ Component.onCompleted: {
80+ edgeBarrierControls.target = testCase.findChild(phoneStage, "edgeBarrierController");
81+ }
82+ }
80 }
83 }
81 ListView {
84 ListView {
82 id: appList
85 id: appList
@@ -463,5 +466,15 @@ Item {
463
466
464 tryCompare(delegate.application, "requestedState", ApplicationInfoInterface.RequestedRunning);
467 tryCompare(delegate.application, "requestedState", ApplicationInfoInterface.RequestedRunning);
465 }
468 }
469+
470+ function test_mouseEdgePush() {
471+ var spreadView = findChild(phoneStage, "spreadView")
472+ addApps(1);
473+ mouseMove(phoneStage, phoneStage.width - 1, units.gu(10));
474+ for (var i = 0; i < units.gu(10); i++) {
475+ phoneStage.pushRightEdge(1);
476+ }
477+ tryCompare(spreadView, "phase", 2);
478+ }
466 }
479 }
467}
480}
tests/qmltests/Stages/tst_SpreadDelegate.qml+7
@@ -308,5 +308,12 @@ Rectangle {
308 tryCompare(closeAnimation, "running", false);
308 tryCompare(closeAnimation, "running", false);
309 }
309 }
310
310
311+ function test_showHighLight() {
312+ loadWithGalleryApp.clicked();
313+ var highlightRect = findChild(spreadDelegateLoader.item, "selectionHighlight")
314+ tryCompare(highlightRect, "visible", false)
315+ spreadDelegateLoader.item.highlightShown = true;
316+ tryCompare(highlightRect, "visible", true)
317+ }
311 }
318 }
312}
319}
tests/qmltests/Stages/tst_TabletStage.qml+162−1
@@ -20,7 +20,9 @@ import Ubuntu.Components 1.3
20import Ubuntu.Components.ListItems 1.3 as ListItem
20import Ubuntu.Components.ListItems 1.3 as ListItem
21import Unity.Application 0.1
21import Unity.Application 0.1
22import Unity.Test 0.1
22import Unity.Test 0.1
23+import Utils 0.1
23
24
25+import ".."
24import "../../../qml/Stages"
26import "../../../qml/Stages"
25import "../../../qml/Components"
27import "../../../qml/Components"
26
28
@@ -77,6 +79,26 @@ Rectangle {
77 Column {
79 Column {
78 anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
80 anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
79 spacing: units.gu(1)
81 spacing: units.gu(1)
82+
83+ EdgeBarrierControls {
84+ id: edgeBarrierControls
85+ text: "Drag here to pull out spread"
86+ backgroundColor: "blue"
87+ onDragged: { tabletStageLoader.item.pushRightEdge(amount); }
88+ Component.onCompleted: {
89+ edgeBarrierControls.target = testCase.findChild(tabletStageLoader, "edgeBarrierController");
90+ }
91+ }
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+
80 ApplicationCheckBox {
102 ApplicationCheckBox {
81 id: webbrowserCheckBox
103 id: webbrowserCheckBox
82 appId: "webbrowser-app"
104 appId: "webbrowser-app"
@@ -102,6 +124,7 @@ Rectangle {
102 when: windowShown
124 when: windowShown
103
125
104 property Item tabletStage: tabletStageLoader.status === Loader.Ready ? tabletStageLoader.item : null
126 property Item tabletStage: tabletStageLoader.status === Loader.Ready ? tabletStageLoader.item : null
127+ property Item sideStage: tabletStage ? findChild(tabletStage, "sideStage") : null
105
128
106 function init() {
129 function init() {
107 tabletStageLoader.active = true;
130 tabletStageLoader.active = true;
@@ -119,6 +142,7 @@ Rectangle {
119 }
142 }
120
143
121 waitUntilAppSurfaceShowsUp("unity8-dash");
144 waitUntilAppSurfaceShowsUp("unity8-dash");
145+ sideStage.hideNow()
122 }
146 }
123
147
124 function cleanup() {
148 function cleanup() {
@@ -138,6 +162,7 @@ Rectangle {
138 galleryCheckBox.checked = false;
162 galleryCheckBox.checked = false;
139 dialerCheckBox.checked = false;
163 dialerCheckBox.checked = false;
140 facebookCheckBox.checked = false;
164 facebookCheckBox.checked = false;
165+ WindowStateStorage.clear();
141 }
166 }
142
167
143 function waitUntilAppSurfaceShowsUp(appId) {
168 function waitUntilAppSurfaceShowsUp(appId) {
@@ -177,6 +202,8 @@ Rectangle {
177 }
202 }
178
203
179 function test_tappingSwitchesFocusBetweenStages() {
204 function test_tappingSwitchesFocusBetweenStages() {
205+ WindowStateStorage.saveStage(dialerCheckBox.appId, ApplicationInfoInterface.SideStage)
206+
180 webbrowserCheckBox.checked = true;
207 webbrowserCheckBox.checked = true;
181 waitUntilAppSurfaceShowsUp(webbrowserCheckBox.appId);
208 waitUntilAppSurfaceShowsUp(webbrowserCheckBox.appId);
182 var webbrowserApp = ApplicationManager.findApplication(webbrowserCheckBox.appId);
209 var webbrowserApp = ApplicationManager.findApplication(webbrowserCheckBox.appId);
@@ -212,6 +239,8 @@ Rectangle {
212 }
239 }
213
240
214 function test_closeAppInSideStage() {
241 function test_closeAppInSideStage() {
242+ WindowStateStorage.saveStage(dialerCheckBox.appId, ApplicationInfoInterface.SideStage)
243+
215 dialerCheckBox.checked = true;
244 dialerCheckBox.checked = true;
216 waitUntilAppSurfaceShowsUp(dialerCheckBox.appId);
245 waitUntilAppSurfaceShowsUp(dialerCheckBox.appId);
217
246
@@ -283,6 +312,8 @@ Rectangle {
283
312
284
313
285 function test_foregroundMainAndSideStageAppsAreKeptRunning() {
314 function test_foregroundMainAndSideStageAppsAreKeptRunning() {
315+ WindowStateStorage.saveStage(facebookCheckBox.appId, ApplicationInfoInterface.SideStage)
316+ WindowStateStorage.saveStage(dialerCheckBox.appId, ApplicationInfoInterface.SideStage)
286
317
287 var stagesPriv = findInvisibleChild(tabletStage, "stagesPriv");
318 var stagesPriv = findInvisibleChild(tabletStage, "stagesPriv");
288 verify(stagesPriv);
319 verify(stagesPriv);
@@ -341,6 +372,8 @@ Rectangle {
341 }
372 }
342
373
343 function test_foregroundAppsAreSuspendedWhenStageIsSuspended() {
374 function test_foregroundAppsAreSuspendedWhenStageIsSuspended() {
375+ WindowStateStorage.saveStage(dialerCheckBox.appId, ApplicationInfoInterface.SideStage)
376+
344 webbrowserCheckBox.checked = true;
377 webbrowserCheckBox.checked = true;
345 waitUntilAppSurfaceShowsUp(webbrowserCheckBox.appId)
378 waitUntilAppSurfaceShowsUp(webbrowserCheckBox.appId)
346 var webbrowserApp = ApplicationManager.findApplication(webbrowserCheckBox.appId);
379 var webbrowserApp = ApplicationManager.findApplication(webbrowserCheckBox.appId);
@@ -365,6 +398,134 @@ Rectangle {
365 tryCompare(webbrowserApp, "requestedState", ApplicationInfoInterface.RequestedRunning);
398 tryCompare(webbrowserApp, "requestedState", ApplicationInfoInterface.RequestedRunning);
366 tryCompare(dialerApp, "requestedState", ApplicationInfoInterface.RequestedRunning);
399 tryCompare(dialerApp, "requestedState", ApplicationInfoInterface.RequestedRunning);
367 }
400 }
368− }
369
401
402+ function test_mouseEdgePush() {
403+ var spreadView = findChild(tabletStageLoader, "spreadView")
404+ mouseMove(tabletStageLoader, tabletStageLoader.width - 1, units.gu(10));
405+ for (var i = 0; i < units.gu(10); i++) {
406+ tabletStageLoader.item.pushRightEdge(1);
407+ }
408+ tryCompare(spreadView, "phase", 2);
409+ }
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+ }
530+ }
370}
531}
tests/qmltests/Tutorial/tst_Tutorial.qml+3−4
@@ -191,7 +191,7 @@ Rectangle {
191 CheckBox {
191 CheckBox {
192 activeFocusOnPress: false
192 activeFocusOnPress: false
193 onCheckedChanged: {
193 onCheckedChanged: {
194− var surface = SurfaceManager.inputMethodSurface();
194+ var surface = SurfaceManager.inputMethodSurface;
195 if (checked) {
195 if (checked) {
196 surface.setState(Mir.RestoredState);
196 surface.setState(Mir.RestoredState);
197 } else {
197 } else {
@@ -256,7 +256,6 @@ Rectangle {
256 if (app) {
256 if (app) {
257 app.setStage(ApplicationInfoInterface.SideStage);
257 app.setStage(ApplicationInfoInterface.SideStage);
258 }
258 }
259− SurfaceManager.inputMethodSurface().destroy(); // so that InputMethod will notice new one
260 // kill all (fake) running apps
259 // kill all (fake) running apps
261 killApps();
260 killApps();
262
261
@@ -742,7 +741,7 @@ Rectangle {
742 var tutorialLeft = findChild(shell, "tutorialLeft");
741 var tutorialLeft = findChild(shell, "tutorialLeft");
743 verify(tutorialLeft.shown);
742 verify(tutorialLeft.shown);
744
743
745− var surface = SurfaceManager.inputMethodSurface();
744+ var surface = SurfaceManager.inputMethodSurface;
746 surface.setState(Mir.RestoredState);
745 surface.setState(Mir.RestoredState);
747
746
748 var inputMethod = findInvisibleChild(shell, "inputMethod");
747 var inputMethod = findInvisibleChild(shell, "inputMethod");
@@ -752,7 +751,7 @@ Rectangle {
752 }
751 }
753
752
754 function test_oskPreventsTutorial() {
753 function test_oskPreventsTutorial() {
755− var surface = SurfaceManager.inputMethodSurface();
754+ var surface = SurfaceManager.inputMethodSurface;
756 var inputMethod = findInvisibleChild(shell, "inputMethod");
755 var inputMethod = findInvisibleChild(shell, "inputMethod");
757
756
758 AccountsService.demoEdges = false;
757 AccountsService.demoEdges = false;
tests/qmltests/Wizard/tst_Wizard.qml+170−199
@@ -21,6 +21,7 @@ import MeeGo.QOfono 0.2
21import QMenuModel 0.1
21import QMenuModel 0.1
22import Ubuntu.Components 1.3
22import Ubuntu.Components 1.3
23import Ubuntu.SystemSettings.SecurityPrivacy 1.0
23import Ubuntu.SystemSettings.SecurityPrivacy 1.0
24+import Ubuntu.SystemSettings.TimeDate 1.0
24import Unity.Test 0.1 as UT
25import Unity.Test 0.1 as UT
25import Wizard 0.1
26import Wizard 0.1
26import "../../../qml/Wizard"
27import "../../../qml/Wizard"
@@ -39,7 +40,6 @@ Item {
39 Wizard {
40 Wizard {
40 id: wizard
41 id: wizard
41 anchors.fill: parent
42 anchors.fill: parent
42− background: Qt.resolvedUrl("../../../qml/graphics/phone_background.jpg")
43
43
44 Component.onDestruction: {
44 Component.onDestruction: {
45 wizardLoader.itemDestroyed = true;
45 wizardLoader.itemDestroyed = true;
@@ -51,7 +51,7 @@ Item {
51 SignalSpy {
51 SignalSpy {
52 id: updateSessionLanguageSpy
52 id: updateSessionLanguageSpy
53 target: System
53 target: System
54− signalName: "updateSessionLanguageCalled"
54+ signalName: "updateSessionLocaleCalled"
55 }
55 }
56
56
57 SignalSpy {
57 SignalSpy {
@@ -69,6 +69,11 @@ Item {
69 signalName: "activated"
69 signalName: "activated"
70 }
70 }
71
71
72+ SignalSpy {
73+ id: timezoneSpy
74+ signalName: "timeZoneChangedCalled"
75+ }
76+
72 function setup() {
77 function setup() {
73 AccountsService.hereEnabled = false;
78 AccountsService.hereEnabled = false;
74 AccountsService.hereLicensePath = Qt.resolvedUrl("licenses");
79 AccountsService.hereLicensePath = Qt.resolvedUrl("licenses");
@@ -82,6 +87,7 @@ Item {
82 setSecuritySpy.clear();
87 setSecuritySpy.clear();
83 activateLocationSpy.clear();
88 activateLocationSpy.clear();
84 activateGPSSpy.clear();
89 activateGPSSpy.clear();
90+ timezoneSpy.clear();
85
91
86 ActionData.data = {
92 ActionData.data = {
87 "location-detection-enabled": {
93 "location-detection-enabled": {
@@ -180,11 +186,6 @@ Item {
180 tap(findChild(page, "forwardButton"));
186 tap(findChild(page, "forwardButton"));
181 }
187 }
182
188
183− page = waitForPage("passwdPage");
184− if (name === page.objectName) return page;
185− tap(findChild(page, "passwdDelegate0"));
186− tap(findChild(page, "forwardButton"));
187−
188 page = waitForPage("wifiPage");
189 page = waitForPage("wifiPage");
189 if (name === page.objectName) return page;
190 if (name === page.objectName) return page;
190 tap(findChild(page, "forwardButton"));
191 tap(findChild(page, "forwardButton"));
@@ -195,6 +196,31 @@ Item {
195 tap(findChild(page, "forwardButton"));
196 tap(findChild(page, "forwardButton"));
196 }
197 }
197
198
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+
198 if (!skipReporting) {
224 if (!skipReporting) {
199 page = waitForPage("reportingPage");
225 page = waitForPage("reportingPage");
200 if (name === page.objectName) return page;
226 if (name === page.objectName) return page;
@@ -203,7 +229,8 @@ Item {
203
229
204 page = waitForPage("finishedPage");
230 page = waitForPage("finishedPage");
205 if (name === page.objectName) return page;
231 if (name === page.objectName) return page;
232+ waitUntilTransitionsEnd(page);
206− tap(findChild(page, "forwardButton"));
233+ tap(findChild(page, "finishButton"));
207
234
208 tryCompare(wizard, "shown", false);
235 tryCompare(wizard, "shown", false);
209 compare(name, null);
236 compare(name, null);
@@ -212,100 +239,43 @@ Item {
212
239
213 function test_languageChange() {
240 function test_languageChange() {
214 var page = goToPage("languagePage");
241 var page = goToPage("languagePage");
215− tap(findChild(page, "languageCombo"));
242+ tap(findChild(page, "languageDelegate1")); // should invoke "fr" lang
216− waitForRendering(findChild(page, "languageDelegate1"));
243+
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");
244+ tryCompare(i18n, "language", "fr_FR");
230 tap(findChild(page, "forwardButton"));
245 tap(findChild(page, "forwardButton"));
231 tryCompare(updateSessionLanguageSpy, "count", 1);
246 tryCompare(updateSessionLanguageSpy, "count", 1);
232− compare(updateSessionLanguageSpy.signalArguments[0][0], "fr");
247+ compare(updateSessionLanguageSpy.signalArguments[0][0], "fr_FR");
233 }
248 }
234
249
235 function test_languageNoChange() {
250 function test_languageNoChange() {
236− goToPage("simPage"); // one past language page
251+ var page = goToPage("languagePage");
252+ tap(findChild(page, "forwardButton"));
237 compare(updateSessionLanguageSpy.count, 0);
253 compare(updateSessionLanguageSpy.count, 0);
238 }
254 }
239
255
240 function test_simUnavailableSkip() {
256 function test_simUnavailableSkip() {
241 MockQOfono.available = false;
257 MockQOfono.available = false;
242− goToPage("passwdPage", true);
258+ goToPage("wifiPage", true);
243 }
259 }
244
260
245 function test_simNoModemsSkip() {
261 function test_simNoModemsSkip() {
246 MockQOfono.setModems([], [], []);
262 MockQOfono.setModems([], [], []);
247− goToPage("passwdPage", true);
263+ goToPage("wifiPage", true);
248 }
264 }
249
265
250 function test_simFirstSkip() {
266 function test_simFirstSkip() {
251 MockQOfono.setModems(["a", "b"], [true, false], []);
267 MockQOfono.setModems(["a", "b"], [true, false], []);
252− goToPage("passwdPage", true);
268+ goToPage("wifiPage", true);
253 }
269 }
254
270
255 function test_simSecondSkip() {
271 function test_simSecondSkip() {
256 MockQOfono.setModems(["a", "b"], [false, true], []);
272 MockQOfono.setModems(["a", "b"], [false, true], []);
257− goToPage("passwdPage", true);
273+ goToPage("wifiPage", true);
258 }
274 }
259
275
260 function test_simBothSkip() {
276 function test_simBothSkip() {
261 MockQOfono.setModems(["a", "b"], [true, true], []);
277 MockQOfono.setModems(["a", "b"], [true, true], []);
262− goToPage("passwdPage", true);
278+ goToPage("wifiPage", 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);
309 }
279 }
310
280
311 function enterPasscode(passcode) {
281 function enterPasscode(passcode) {
@@ -332,33 +302,32 @@ Item {
332
302
333 function test_passwdPasscode() {
303 function test_passwdPasscode() {
334 var page = goToPage("passwdPage");
304 var page = goToPage("passwdPage");
335−
305+ tap(findChild(page, "passwdDelegate2")); // passcode option
336 tap(findChild(page, "forwardButton"));
306 tap(findChild(page, "forwardButton"));
337− page = waitForPage("passwdSetPage");
307+ page = waitForPage("passcodeSetPage");
338
308
339 enterPasscode("1111");
309 enterPasscode("1111");
340− page = waitForPage("passwdConfirmPage");
310+ page = waitForPage("passcodeConfirmPage");
341
311
342 // make sure we go back to 'set' page not 'type' page
312 // make sure we go back to 'set' page not 'type' page
343 tap(findChild(page, "backButton"));
313 tap(findChild(page, "backButton"));
344− page = waitForPage("passwdSetPage");
314+ page = waitForPage("passcodeSetPage");
345
315
346 enterPasscode("1111");
316 enterPasscode("1111");
347− page = waitForPage("passwdConfirmPage");
317+ page = waitForPage("passcodeConfirmPage");
348
318
349 enterPasscode("1112");
319 enterPasscode("1112");
350 var error = findChild(page, "wrongNoticeLabel");
320 var error = findChild(page, "wrongNoticeLabel");
351 tryCompareFunction(function() { return error.text !== ""; }, true);
321 tryCompareFunction(function() { return error.text !== ""; }, true);
352
322
353 enterPasscode("1111");
323 enterPasscode("1111");
354− page = waitForPage("wifiPage");
355
324
356 // now finish up
325 // now finish up
357− tap(findChild(page, "forwardButton"));
358− page = waitForPage("locationPage");
326+ page = waitForPage("reportingPage");
359 tap(findChild(page, "forwardButton"));
327 tap(findChild(page, "forwardButton"));
360 page = waitForPage("finishedPage");
328 page = waitForPage("finishedPage");
329+ waitUntilTransitionsEnd(page);
361− tap(findChild(page, "forwardButton"));
330+ tap(findChild(page, "finishButton"));
362
331
363 tryCompare(setSecuritySpy, "count", 1);
332 tryCompare(setSecuritySpy, "count", 1);
364 compare(setSecuritySpy.signalArguments[0][0], "");
333 compare(setSecuritySpy.signalArguments[0][0], "");
@@ -368,96 +337,85 @@ Item {
368
337
369 function test_passwdPassphrase() {
338 function test_passwdPassphrase() {
370 var page = goToPage("passwdPage");
339 var page = goToPage("passwdPage");
371− tap(findChild(page, "passwdDelegate2"));
340+ tap(findChild(page, "passwdDelegate1")); // password option
372−
373 tap(findChild(page, "forwardButton"));
341 tap(findChild(page, "forwardButton"));
374 page = waitForPage("passwdSetPage");
342 page = waitForPage("passwdSetPage");
375
343
344+ var passwdField = findChild(page, "passwordField");
345+ verify(passwdField);
346+ tap(passwdField);
376 typeString("aaa");
347 typeString("aaa");
377− var continueButton = findChild(page, "forwardButton");
348+ var continueButton = findChild(page, "okButton");
378− tryCompare(continueButton.item, "enabled", false);
349+ tryCompare(continueButton, "enabled", false);
379− keyClick(Qt.Key_Enter);
380− var error = findChild(page, "wrongNoticeLabel");
381− tryCompareFunction(function() { return error.text !== ""; }, true);
382
350
351+ tap(passwdField);
352+ passwdField.selectAll(); // overwrite the text
383− typeString("aaaa");
353+ typeString("12345678");
384 tap(continueButton);
354 tap(continueButton);
385− page = waitForPage("passwdConfirmPage");
355+ wait(200);
386
356
387 // make sure we go back to 'set' page not 'type' page
357 // make sure we go back to 'set' page not 'type' page
388− var back = findChild(page, "backButton");
358+ var back = findChild(page, "cancelButton");
389 tap(back);
359 tap(back);
390− page = waitForPage("passwdSetPage");
360+ wait(200);
391−
392− typeString("aaaa");
393− keyClick(Qt.Key_Enter);
394− page = waitForPage("passwdConfirmPage");
395
361
396− continueButton = findChild(page, "forwardButton");
362+ passwdField = findChild(page, "passwordField");
363+ tryCompareFunction(function() {return !!passwdField}, true);
364+ tap(passwdField);
397 typeString("aaab");
365 typeString("aaab");
366+
367+ continueButton = findChild(page, "okButton");
398− tryCompare(continueButton.item, "enabled", false);
368+ tryCompare(continueButton, "enabled", false);
399− keyClick(Qt.Key_Enter);
400− var error = findChild(page, "wrongNoticeLabel");
369+ var error = findChild(page, "infoLabel");
401 tryCompareFunction(function() { return error.text !== ""; }, true);
370 tryCompareFunction(function() { return error.text !== ""; }, true);
402
371
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
403− typeString("aaaa");
380+ typeString("12345678");
404 tap(continueButton);
381 tap(continueButton);
405− page = waitForPage("wifiPage");
406
382
407 // now finish up
383 // now finish up
408− tap(findChild(page, "forwardButton"));
409− page = waitForPage("locationPage");
384+ page = waitForPage("reportingPage");
410 tap(findChild(page, "forwardButton"));
385 tap(findChild(page, "forwardButton"));
411 page = waitForPage("finishedPage");
386 page = waitForPage("finishedPage");
387+ waitUntilTransitionsEnd(page);
412− tap(findChild(page, "forwardButton"));
388+ tap(findChild(page, "finishButton"));
413
389
414 tryCompare(setSecuritySpy, "count", 1);
390 tryCompare(setSecuritySpy, "count", 1);
415 compare(setSecuritySpy.signalArguments[0][0], "");
391 compare(setSecuritySpy.signalArguments[0][0], "");
416− compare(setSecuritySpy.signalArguments[0][1], "aaaa");
392+ compare(setSecuritySpy.signalArguments[0][1], "12345678");
417 compare(setSecuritySpy.signalArguments[0][2], UbuntuSecurityPrivacyPanel.Passphrase);
393 compare(setSecuritySpy.signalArguments[0][2], UbuntuSecurityPrivacyPanel.Passphrase);
418 }
394 }
419
395
420 function test_passwdSwipe() {
396 function test_passwdSwipe() {
421− goToPage(null, false, false, true);
397+ var page = goToPage("passwdPage");
422−
398+ tap(findChild(page, "passwdDelegate3")); // swipe option
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");
443
399
444− var pages = findChild(wizard, "wizardPages");
400+ // now finish up
445− var stack = findChild(pages, "pageStack");
446 tap(findChild(page, "forwardButton"));
401 tap(findChild(page, "forwardButton"));
447− // don't simply call tryCompare here, because stack.currentPage will be swapped out itself
402+ page = waitForPage("reportingPage");
448− tryCompareFunction(function() { return stack.currentPage.objectName; }, "locationPage");
403+ tap(findChild(page, "forwardButton"));
449− compare(stack.currentPage.enabled, false);
404+ page = waitForPage("finishedPage");
450− compare(stack.currentPage.skipValid, false);
405+ waitUntilTransitionsEnd(page);
406+ tap(findChild(page, "finishButton"));
451
407
452− AccountsService.hereLicensePath = "";
408+ tryCompare(setSecuritySpy, "count", 1);
453− waitForPage("finishedPage", false, false, true);
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);
454 }
412 }
455
413
456 function test_locationGpsOnly() {
414 function test_locationGpsOnly() {
457 var page = goToPage("locationPage");
415 var page = goToPage("locationPage");
458− var gpsCheck = findChild(page, "gpsCheck");
416+ var gpsCheck = findChild(page, "gpsCheckLabel");
459− var hereCheck = findChild(page, "hereCheck");
417+ var hereCheck = findChild(page, "hereCheckLabel");
460− var nopeCheck = findChild(page, "nopeCheck");
418+ var nopeCheck = findChild(page, "nopeCheckLabel");
461
419
462 var locationActionGroup = findInvisibleChild(page, "locationActionGroup");
420 var locationActionGroup = findInvisibleChild(page, "locationActionGroup");
463 activateLocationSpy.target = locationActionGroup.location;
421 activateLocationSpy.target = locationActionGroup.location;
@@ -476,9 +434,9 @@ Item {
476
434
477 function test_locationNope() {
435 function test_locationNope() {
478 var page = goToPage("locationPage");
436 var page = goToPage("locationPage");
479− var gpsCheck = findChild(page, "gpsCheck");
437+ var gpsCheck = findChild(page, "gpsCheckLabel");
480− var hereCheck = findChild(page, "hereCheck");
438+ var hereCheck = findChild(page, "hereCheckLabel");
481− var nopeCheck = findChild(page, "nopeCheck");
439+ var nopeCheck = findChild(page, "nopeCheckLabel");
482
440
483 var locationActionGroup = findInvisibleChild(page, "locationActionGroup");
441 var locationActionGroup = findInvisibleChild(page, "locationActionGroup");
484 activateLocationSpy.target = locationActionGroup.location;
442 activateLocationSpy.target = locationActionGroup.location;
@@ -491,15 +449,15 @@ Item {
491
449
492 tap(findChild(page, "forwardButton"));
450 tap(findChild(page, "forwardButton"));
493 tryCompare(AccountsService, "hereEnabled", false);
451 tryCompare(AccountsService, "hereEnabled", false);
494− tryCompare(activateLocationSpy, "count", 0)
452+ tryCompare(activateLocationSpy, "count", 0);
495− tryCompare(activateGPSSpy, "count", 0)
453+ tryCompare(activateGPSSpy, "count", 0);
496 }
454 }
497
455
498 function test_locationHere() {
456 function test_locationHere() {
499 var page = goToPage("locationPage");
457 var page = goToPage("locationPage");
500− var gpsCheck = findChild(page, "gpsCheck");
458+ var gpsCheck = findChild(page, "gpsCheckLabel");
501− var hereCheck = findChild(page, "hereCheck");
459+ var hereCheck = findChild(page, "hereCheckLabel");
502− var nopeCheck = findChild(page, "nopeCheck");
460+ var nopeCheck = findChild(page, "nopeCheckLabel");
503
461
504 var locationActionGroup = findInvisibleChild(page, "locationActionGroup");
462 var locationActionGroup = findInvisibleChild(page, "locationActionGroup");
505 activateLocationSpy.target = locationActionGroup.location;
463 activateLocationSpy.target = locationActionGroup.location;
@@ -512,68 +470,81 @@ Item {
512
470
513 tap(findChild(page, "forwardButton"));
471 tap(findChild(page, "forwardButton"));
514 tryCompare(AccountsService, "hereEnabled", true);
472 tryCompare(AccountsService, "hereEnabled", true);
515− tryCompare(activateLocationSpy, "count", 1)
473+ tryCompare(activateLocationSpy, "count", 1);
516− tryCompare(activateGPSSpy, "count", 1)
474+ tryCompare(activateGPSSpy, "count", 1);
517 }
475 }
518
476
519− function test_locationHereTerms() {
477+ function test_timezonePage() {
520− var page = goToPage("locationPage");
478+ var page = goToPage("tzPage");
479+ verify(page);
480+ timezoneSpy.target = page.tdModule;
521
481
522− var link = findChild(page, "hereTermsLink");
482+ var tzFilter = findChild(page, "tzFilter");
483+ verify(tzFilter);
484+ tap(tzFilter);
485+ typeString("London");
523
486
524− // Test our language lookup code a bit
487+ var tzList = findChild(page, "tzList");
488+ verify(tzList);
525
489
526− i18n.language = "fr_FR.UTF-8";
490+ // test filtering works and returns some results
527− link.linkActivated("not-used");
491+ tryCompareFunction(function() { return tzList.count > 0; }, true);
528− page = waitForPage("hereTermsPage");
529− tryCompare(findChild(page, "termsLabel"), "text", "fr_FR\n");
530− tap(findChild(page, "backButton"));
531− waitForPage("locationPage");
532
492
533− i18n.language = "fr_CA";
493+ // just tap the first one
534− link.linkActivated("not-used");
535− page = waitForPage("hereTermsPage");
536− tryCompare(findChild(page, "termsLabel"), "text", "fr_CA\n");
537− tap(findChild(page, "backButton"));
494+ tap(findChild(page, "tz0"));
538− waitForPage("locationPage");
539
495
540− i18n.language = "fr_US";
496+ // go next and verify the (mock) signal got fired
541− link.linkActivated("not-used");
542− page = waitForPage("hereTermsPage");
543− tryCompare(findChild(page, "termsLabel"), "text", "fr_FR\n");
544− tap(findChild(page, "backButton"));
497+ tap(findChild(page, "forwardButton"));
545− waitForPage("locationPage");
498+ tryCompare(timezoneSpy, "count", 1);
499+ tryCompare(page.tdModule, "timeZone", timezoneSpy.signalArguments[0][0]);
500+ }
546
501
547− i18n.language = "fr.utf8";
502+ function test_accountPage() {
548− link.linkActivated("not-used");
503+ var page = goToPage("accountPage");
549− page = waitForPage("hereTermsPage");
504+ var password1 = findChild(page, "passInput");
550− tryCompare(findChild(page, "termsLabel"), "text", "fr_FR\n");
551− tap(findChild(page, "backButton"));
505+ var password2 = findChild(page, "pass2Input");
552− waitForPage("locationPage");
506+ var forwardButton = findChild(page, "forwardButton");
553
507
554− i18n.language = "es"; // will not be found
508+ tap(findChild(page, "nameInput"));
555− link.linkActivated("not-used");
509+ typeString("foobar");
556− page = waitForPage("hereTermsPage");
557− tryCompare(findChild(page, "termsLabel"), "text", "en_US\n");
558
510
559− // OK, done with languages, back to actual page
511+ verify(forwardButton.enabled, false); // no passwords yet
560
512
561− var label = findChild(page, "termsLabel");
513+ tap(password1);
562− label.linkActivated(Qt.resolvedUrl("licenses/en_US.html"));
514+ typeString("12345");
563− tryCompare(label, "visible", false);
564
515
565− var webview = findChild(page, "webview");
516+ verify(forwardButton.enabled, false); // password too short
566− tryCompare(webview, "visible", true);
567− tryCompare(webview, "url", Qt.resolvedUrl("licenses/en_US.html"));
568− tryCompare(webview, "loadProgress", 100);
569
517
570− tap(findChild(page, "backButton"));
518+ tap(password2);
571− waitForPage("hereTermsPage"); // confirm we're on same page
519+ typeString("12345678");
572− tryCompare(webview, "visible", false);
573− tryCompare(label, "visible", true);
574
520
575− tap(findChild(page, "backButton"));
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"));
576− waitForPage("locationPage");
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);
577 }
548 }
578 }
549 }
579}
550}
tests/qmltests/tst_DeviceConfiguration.qmlnew file+49
@@ -0,0 +1,49 @@
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 QtTest 1.0
19+import Unity.Test 0.1
20+import "../../qml"
21+
22+Item {
23+ id: root
24+ width: units.gu(40)
25+ height: units.gu(70)
26+
27+ DeviceConfiguration {
28+ id: deviceConfiguration
29+ }
30+
31+ UnityTestCase {
32+ id: testCase
33+ name: "DeviceConfiguration"
34+ when: windowShown
35+
36+ function test_defaults() {
37+ deviceConfiguration.name = "nonexisting"
38+ compare(deviceConfiguration.primaryOrientation, -1)
39+ compare(deviceConfiguration.landscapeOrientation, Qt.LandscapeOrientation)
40+ compare(deviceConfiguration.portraitOrientation, Qt.PortraitOrientation)
41+ compare(deviceConfiguration.invertedLandscapeOrientation, Qt.InvertedLandscapeOrientation)
42+ compare(deviceConfiguration.invertedPortraitOrientation, Qt.InvertedPortraitOrientation)
43+ compare(deviceConfiguration.supportedOrientations, Qt.PortraitOrientation
44+ | Qt.InvertedPortraitOrientation
45+ | Qt.LandscapeOrientation
46+ | Qt.InvertedLandscapeOrientation)
47+ }
48+ }
49+}
tests/qmltests/tst_DisabledScreenNotice.qml+76−2
@@ -15,18 +15,62 @@
15 */
15 */
16
16
17import QtQuick 2.4
17import QtQuick 2.4
18+import QtQuick.Layouts 1.1
18import QtTest 1.0
19import QtTest 1.0
19import Unity.Test 0.1
20import Unity.Test 0.1
21+import Ubuntu.Components 1.3
22+import Ubuntu.Components.ListItems 1.3
20import "../../qml"
23import "../../qml"
21
24
22Item {
25Item {
23 id: root
26 id: root
24− width: units.gu(40)
27+ width: units.gu(80)
25 height: units.gu(70)
28 height: units.gu(70)
26
29
27 DisabledScreenNotice {
30 DisabledScreenNotice {
28 id: touchScreenPad
31 id: touchScreenPad
29 anchors.fill: parent
32 anchors.fill: parent
33+ anchors.rightMargin: units.gu(40)
34+
35+ screen: QtObject {
36+ property int orientation: {
37+ switch (orientationSelector.selectedIndex) {
38+ case 0:
39+ return Qt.PortraitOrientation;
40+ case 1:
41+ return Qt.LandscapeOrientation;
42+ case 2:
43+ return Qt.InvertedPortraitOrientation;
44+ case 3:
45+ return Qt.InvertedLandscapeOrientation;
46+ }
47+ }
48+ }
49+ orientationLock: QtObject {
50+ property bool enabled: orientationLockCheckBox.checked
51+ }
52+ }
53+
54+ Column {
55+ anchors.fill: parent
56+ anchors.leftMargin: units.gu(40)
57+
58+ ItemSelector {
59+ id: orientationSelector
60+ model: ["Portrait", "Landscape", "InvertedPortrait", "InvertedLandscape"]
61+ selectedIndex: 0
62+ }
63+
64+ RowLayout {
65+ CheckBox {
66+ id: orientationLockCheckBox
67+ checked: false
68+ }
69+ Label {
70+ text: "Rotation lock"
71+ Layout.fillWidth: true
72+ }
73+ }
30 }
74 }
31
75
32 UnityTestCase {
76 UnityTestCase {
@@ -36,10 +80,40 @@ Item {
36
80
37 function test_mouseAreaHidesOnFirstTap() {
81 function test_mouseAreaHidesOnFirstTap() {
38 var noticeArea = findChild(touchScreenPad, "infoNoticeArea")
82 var noticeArea = findChild(touchScreenPad, "infoNoticeArea")
39− print("noticeArea", noticeArea.visible)
40 compare(noticeArea.visible, true)
83 compare(noticeArea.visible, true)
41 tap(root)
84 tap(root)
42 tryCompare(noticeArea, "visible", false)
85 tryCompare(noticeArea, "visible", false)
43 }
86 }
87+
88+ function test_rotation_data() {
89+ return [
90+ {tag: "portrait", selectedOrientation: 0, expectedAngle: 0},
91+ {tag: "landscape", selectedOrientation: 1, expectedAngle: 270},
92+ {tag: "invertedportrait", selectedOrientation: 2, expectedAngle: 180},
93+ {tag: "invertetlandscape", selectedOrientation: 3, expectedAngle: 90}
94+ ];
95+ }
96+
97+ function test_rotation(data) {
98+ var content = findChild(touchScreenPad, "contentContainer");
99+
100+ var oldRotation = content.rotation;
101+
102+ // Turn on orientation lock
103+ orientationLockCheckBox.checked = true;
104+
105+ // simulate the rotation
106+ orientationSelector.selectedIndex = data.selectedOrientation;
107+
108+ // Make sure it is still in the old rotation (as we have the lock turned on)
109+ expectFail("", "Rotation lock set. Expecting automatic rotation to fail.");
110+ tryCompareFunction(function() { return content.rotation != oldRotation; }, true, 300);
111+
112+ // Now uncheck the rotation lock
113+ orientationLockCheckBox.checked = false;
114+
115+ // And make sure it catches up to the expected angle
116+ tryCompare(content, "rotation", data.expectedAngle);
117+ }
44 }
118 }
45}
119}
tests/qmltests/tst_OrientedShell.qml+56−12
@@ -232,6 +232,9 @@ Rectangle {
232 onClicked: {
232 onClicked: {
233 orientedShellLoader.item.physicalOrientation = root.physicalOrientation0;
233 orientedShellLoader.item.physicalOrientation = root.physicalOrientation0;
234 }
234 }
235+ color: orientedShellLoader.item && orientedShellLoader.item.physicalOrientation === root.physicalOrientation0 ?
236+ UbuntuColors.green :
237+ __styleInstance.defaultColor
235 }
238 }
236 Button {
239 Button {
237 id: rotate90Button
240 id: rotate90Button
@@ -240,6 +243,9 @@ Rectangle {
240 onClicked: {
243 onClicked: {
241 orientedShellLoader.item.physicalOrientation = root.physicalOrientation90;
244 orientedShellLoader.item.physicalOrientation = root.physicalOrientation90;
242 }
245 }
246+ color: orientedShellLoader.item && orientedShellLoader.item.physicalOrientation === root.physicalOrientation90 ?
247+ UbuntuColors.green :
248+ __styleInstance.defaultColor
243 }
249 }
244 Button {
250 Button {
245 id: rotate180Button
251 id: rotate180Button
@@ -248,6 +254,9 @@ Rectangle {
248 onClicked: {
254 onClicked: {
249 orientedShellLoader.item.physicalOrientation = root.physicalOrientation180;
255 orientedShellLoader.item.physicalOrientation = root.physicalOrientation180;
250 }
256 }
257+ color: orientedShellLoader.item && orientedShellLoader.item.physicalOrientation === root.physicalOrientation180 ?
258+ UbuntuColors.green :
259+ __styleInstance.defaultColor
251 }
260 }
252 Button {
261 Button {
253 id: rotate270Button
262 id: rotate270Button
@@ -256,6 +265,9 @@ Rectangle {
256 onClicked: {
265 onClicked: {
257 orientedShellLoader.item.physicalOrientation = root.physicalOrientation270;
266 orientedShellLoader.item.physicalOrientation = root.physicalOrientation270;
258 }
267 }
268+ color: orientedShellLoader.item && orientedShellLoader.item.physicalOrientation === root.physicalOrientation270 ?
269+ UbuntuColors.green :
270+ __styleInstance.defaultColor
259 }
271 }
260 RowLayout {
272 RowLayout {
261 Layout.fillWidth: true
273 Layout.fillWidth: true
@@ -314,7 +326,7 @@ Rectangle {
314 checked: false
326 checked: false
315 activeFocusOnPress: false
327 activeFocusOnPress: false
316 onCheckedChanged: {
328 onCheckedChanged: {
317− var surface = SurfaceManager.inputMethodSurface();
329+ var surface = SurfaceManager.inputMethodSurface;
318 if (checked) {
330 if (checked) {
319 surface.setState(Mir.RestoredState);
331 surface.setState(Mir.RestoredState);
320 } else {
332 } else {
@@ -471,14 +483,46 @@ Rectangle {
471 tearDown();
483 tearDown();
472 }
484 }
473
485
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() {
475 return [
520 return [
476− {tag: "mako", deviceName: "mako"},
477 {tag: "manta", deviceName: "manta"},
521 {tag: "manta", deviceName: "manta"},
478 {tag: "flo", deviceName: "flo"}
522 {tag: "flo", deviceName: "flo"}
479 ];
523 ];
480 }
524 }
481− function test_appSupportingOnlyPrimaryOrientationMakesShellStayPut(data) {
525+ function test_appSupportingOnlyPrimaryOrientationWillOnlyRotateInLandscape(data) {
482 loadShell(data.deviceName);
526 loadShell(data.deviceName);
483
527
484 // unity8-dash supports only primary orientation and should be already running
528 // unity8-dash supports only primary orientation and should be already running
@@ -502,13 +546,13 @@ Rectangle {
502
546
503 rotateTo(180);
547 rotateTo(180);
504
548
505− verify(checkAppSurfaceOrientation(dashAppWindow, dashApp, root.primaryOrientationAngle));
549+ verify(checkAppSurfaceOrientation(dashAppWindow, dashApp, root.primaryOrientationAngle + 180));
506− compare(shell.transformRotationAngle, root.primaryOrientationAngle);
550+ compare(shell.transformRotationAngle, root.primaryOrientationAngle + 180);
507
551
508 rotateTo(270);
552 rotateTo(270);
509
553
510− verify(checkAppSurfaceOrientation(dashAppWindow, dashApp, root.primaryOrientationAngle));
554+ verify(checkAppSurfaceOrientation(dashAppWindow, dashApp, root.primaryOrientationAngle + 180));
511− compare(shell.transformRotationAngle, root.primaryOrientationAngle);
555+ compare(shell.transformRotationAngle, root.primaryOrientationAngle + 180);
512 }
556 }
513
557
514 function test_greeterRemainsInPrimaryOrientation_data() {
558 function test_greeterRemainsInPrimaryOrientation_data() {
@@ -813,12 +857,12 @@ Rectangle {
813
857
814 var twitterApp = ApplicationManager.startApplication("twitter-webapp");
858 var twitterApp = ApplicationManager.startApplication("twitter-webapp");
815 verify(twitterApp);
859 verify(twitterApp);
860+ twitterApp.stage = ApplicationInfoInterface.SideStage;
816
861
817 // ensure the mock twitter-webapp is as we expect
862 // ensure the mock twitter-webapp is as we expect
818 compare(twitterApp.rotatesWindowContents, false);
863 compare(twitterApp.rotatesWindowContents, false);
819 compare(twitterApp.supportedOrientations, Qt.PortraitOrientation | Qt.LandscapeOrientation
864 compare(twitterApp.supportedOrientations, Qt.PortraitOrientation | Qt.LandscapeOrientation
820 | Qt.InvertedPortraitOrientation | Qt.InvertedLandscapeOrientation);
865 | Qt.InvertedPortraitOrientation | Qt.InvertedLandscapeOrientation);
821− compare(twitterApp.stage, ApplicationInfoInterface.SideStage);
822
866
823 // Wait until spreadRepeaterConnections hs caught the new SpreadDelegate and
867 // Wait until spreadRepeaterConnections hs caught the new SpreadDelegate and
824 // set up the signalSpy target accordingly.
868 // set up the signalSpy target accordingly.
@@ -852,11 +896,11 @@ Rectangle {
852
896
853 var dialerApp = ApplicationManager.startApplication("dialer-app");
897 var dialerApp = ApplicationManager.startApplication("dialer-app");
854 verify(dialerApp);
898 verify(dialerApp);
899+ dialerApp.stage = ApplicationInfoInterface.SideStage;
855
900
856 // ensure the mock dialer-app is as we expect
901 // ensure the mock dialer-app is as we expect
857 compare(dialerApp.rotatesWindowContents, false);
902 compare(dialerApp.rotatesWindowContents, false);
858 compare(dialerApp.supportedOrientations, Qt.PortraitOrientation | Qt.InvertedPortraitOrientation);
903 compare(dialerApp.supportedOrientations, Qt.PortraitOrientation | Qt.InvertedPortraitOrientation);
859− compare(dialerApp.stage, ApplicationInfoInterface.SideStage);
860
904
861 tryCompareFunction(function(){ return dialerDelegate != null; }, true);
905 tryCompareFunction(function(){ return dialerDelegate != null; }, true);
862 tryCompare(dialerDelegate, "orientationChangesEnabled", true);
906 tryCompare(dialerDelegate, "orientationChangesEnabled", true);
@@ -899,11 +943,11 @@ Rectangle {
899
943
900 var dialerApp = ApplicationManager.startApplication("dialer-app");
944 var dialerApp = ApplicationManager.startApplication("dialer-app");
901 verify(dialerApp);
945 verify(dialerApp);
946+ dialerApp.stage = ApplicationInfoInterface.SideStage;
902
947
903 // ensure the mock dialer-app is as we expect
948 // ensure the mock dialer-app is as we expect
904 compare(dialerApp.rotatesWindowContents, false);
949 compare(dialerApp.rotatesWindowContents, false);
905 compare(dialerApp.supportedOrientations, Qt.PortraitOrientation | Qt.InvertedPortraitOrientation);
950 compare(dialerApp.supportedOrientations, Qt.PortraitOrientation | Qt.InvertedPortraitOrientation);
906− compare(dialerApp.stage, ApplicationInfoInterface.SideStage);
907
951
908 tryCompareFunction(function(){ return dialerDelegate != null; }, true);
952 tryCompareFunction(function(){ return dialerDelegate != null; }, true);
909 waitUntilAppDelegateIsFullyInit(dialerDelegate);
953 waitUntilAppDelegateIsFullyInit(dialerDelegate);
@@ -919,12 +963,12 @@ Rectangle {
919
963
920 var twitterApp = ApplicationManager.startApplication("twitter-webapp");
964 var twitterApp = ApplicationManager.startApplication("twitter-webapp");
921 verify(twitterApp);
965 verify(twitterApp);
966+ twitterApp.stage = ApplicationInfoInterface.SideStage;
922
967
923 // ensure the mock twitter-webapp is as we expect
968 // ensure the mock twitter-webapp is as we expect
924 compare(twitterApp.rotatesWindowContents, false);
969 compare(twitterApp.rotatesWindowContents, false);
925 compare(twitterApp.supportedOrientations, Qt.PortraitOrientation | Qt.LandscapeOrientation
970 compare(twitterApp.supportedOrientations, Qt.PortraitOrientation | Qt.LandscapeOrientation
926 | Qt.InvertedPortraitOrientation | Qt.InvertedLandscapeOrientation);
971 | Qt.InvertedPortraitOrientation | Qt.InvertedLandscapeOrientation);
927− compare(twitterApp.stage, ApplicationInfoInterface.SideStage);
928
972
929 tryCompareFunction(function(){ return twitterDelegate != null; }, true);
973 tryCompareFunction(function(){ return twitterDelegate != null; }, true);
930 waitUntilAppDelegateIsFullyInit(twitterDelegate);
974 waitUntilAppDelegateIsFullyInit(twitterDelegate);
tests/qmltests/tst_Shell.qml+241−59
@@ -26,6 +26,7 @@ import Unity.Application 0.1
26import Unity.Connectivity 0.1
26import Unity.Connectivity 0.1
27import Unity.Indicators 0.1
27import Unity.Indicators 0.1
28import Unity.Notifications 1.0
28import Unity.Notifications 1.0
29+import Unity.Launcher 0.1
29import Unity.Test 0.1
30import Unity.Test 0.1
30import Powerd 0.1
31import Powerd 0.1
31import Wizard 0.1 as Wizard
32import Wizard 0.1 as Wizard
@@ -49,11 +50,6 @@ Rectangle {
49 shellLoader.active = true;
50 shellLoader.active = true;
50 }
51 }
51
52
52− MouseArea {
53− id: clickThroughCatcher
54− anchors.fill: shellContainer
55− }
56−
57 Item {
53 Item {
58 id: shellContainer
54 id: shellContainer
59 anchors.left: root.left
55 anchors.left: root.left
@@ -121,10 +117,6 @@ Rectangle {
121 Component.onDestruction: {
117 Component.onDestruction: {
122 shellLoader.itemDestroyed = true;
118 shellLoader.itemDestroyed = true;
123 }
119 }
124− Component.onCompleted: {
125− var keyMapper = testCase.findChild(__shell, "physicalKeysMapper");
126− keyMapper.controlInsteadOfAlt = true;
127− }
128 }
120 }
129 }
121 }
130 }
122 }
@@ -213,6 +205,31 @@ Rectangle {
213 checked: true
205 checked: true
214 color: "white"
206 color: "white"
215 }
207 }
208+ ListItem.ItemSelector {
209+ id: ctrlModifier
210+ anchors { left: parent.left; right: parent.right }
211+ activeFocusOnPress: false
212+ text: "Ctrl key as"
213+ model: ["Ctrl", "Alt", "Super"]
214+ onSelectedIndexChanged: {
215+ var keyMapper = testCase.findChild(shellContainer, "physicalKeysMapper");
216+ keyMapper.controlInsteadOfAlt = selectedIndex == 1;
217+ keyMapper.controlInsteadOfSuper = selectedIndex == 2;
218+ }
219+ }
220+
221+ Row {
222+ anchors { left: parent.left; right: parent.right }
223+ CheckBox {
224+ id: autohideLauncherCheckbox
225+ onCheckedChanged: {
226+ GSettingsController.setAutohideLauncher(checked)
227+ }
228+ }
229+ Label {
230+ text: "Autohide launcher"
231+ }
232+ }
216
233
217 Label { text: "Applications"; font.bold: true }
234 Label { text: "Applications"; font.bold: true }
218
235
@@ -296,12 +313,6 @@ Rectangle {
296 signalName: "applicationRemoved"
313 signalName: "applicationRemoved"
297 }
314 }
298
315
299− SignalSpy {
300− id: clickThroughSpy
301− target: clickThroughCatcher
302− signalName: "clicked"
303− }
304−
305 Telephony.CallEntry {
316 Telephony.CallEntry {
306 id: phoneCall
317 id: phoneCall
307 phoneNumber: "+447812221111"
318 phoneNumber: "+447812221111"
@@ -666,6 +677,7 @@ Rectangle {
666 function swipeAwayGreeter() {
677 function swipeAwayGreeter() {
667 var greeter = findChild(shell, "greeter");
678 var greeter = findChild(shell, "greeter");
668 tryCompare(greeter, "fullyShown", true);
679 tryCompare(greeter, "fullyShown", true);
680+ waitForGreeterToStabilize();
669 removeTimeConstraintsFromDirectionalDragAreas(greeter);
681 removeTimeConstraintsFromDirectionalDragAreas(greeter);
670
682
671 var touchX = shell.width - (shell.edgeSize / 2);
683 var touchX = shell.width - (shell.edgeSize / 2);
@@ -769,7 +781,7 @@ Rectangle {
769 loadShell("phone");
781 loadShell("phone");
770 swipeAwayGreeter();
782 swipeAwayGreeter();
771 var item = findChild(shell, "inputMethod");
783 var item = findChild(shell, "inputMethod");
772− var surface = SurfaceManager.inputMethodSurface();
784+ var surface = SurfaceManager.inputMethodSurface;
773
785
774 surface.setState(Mir.MinimizedState);
786 surface.setState(Mir.MinimizedState);
775 tryCompare(item, "visible", false);
787 tryCompare(item, "visible", false);
@@ -840,7 +852,6 @@ Rectangle {
840 function test_launchedAppHasActiveFocus(data) {
852 function test_launchedAppHasActiveFocus(data) {
841 loadShell(data.formFactor);
853 loadShell(data.formFactor);
842 shell.usageScenario = data.usageScenario;
854 shell.usageScenario = data.usageScenario;
843− waitForGreeterToStabilize();
844 swipeAwayGreeter();
855 swipeAwayGreeter();
845
856
846 var webApp = ApplicationManager.startApplication("webbrowser-app");
857 var webApp = ApplicationManager.startApplication("webbrowser-app");
@@ -908,6 +919,7 @@ Rectangle {
908 function dragLauncherIntoView() {
919 function dragLauncherIntoView() {
909 var launcher = findChild(shell, "launcher");
920 var launcher = findChild(shell, "launcher");
910 var launcherPanel = findChild(launcher, "launcherPanel");
921 var launcherPanel = findChild(launcher, "launcherPanel");
922+ waitForRendering(launcher);
911 verify(launcherPanel.x = - launcherPanel.width);
923 verify(launcherPanel.x = - launcherPanel.width);
912
924
913 var touchStartX = 2;
925 var touchStartX = 2;
@@ -1419,9 +1431,17 @@ Rectangle {
1419 tryCompare(galleryApp, "requestedState", ApplicationInfoInterface.RequestedRunning);
1431 tryCompare(galleryApp, "requestedState", ApplicationInfoInterface.RequestedRunning);
1420 }
1432 }
1421
1433
1434+ function test_altTabSwitchesFocus_data() {
1435+ return [
1436+ { tag: "windowed", shellType: "desktop" },
1437+ { tag: "staged", shellType: "phone" },
1438+ { tag: "sidestaged", shellType: "tablet" }
1439+ ];
1440+ }
1441+
1422− function test_altTabSwitchesFocus() {
1442+ function test_altTabSwitchesFocus(data) {
1423− loadShell("desktop");
1443+ loadShell(data.shellType);
1424− shell.usageScenario = "desktop"
1444+ shell.usageScenario = data.shellType;
1425 waitForRendering(root)
1445 waitForRendering(root)
1426
1446
1427 var desktopStage = findChild(shell, "stage");
1447 var desktopStage = findChild(shell, "stage");
@@ -1436,21 +1456,13 @@ Rectangle {
1436
1456
1437 // Do a quick alt-tab and see if focus changes
1457 // Do a quick alt-tab and see if focus changes
1438 tryCompare(app3.session.lastSurface, "activeFocus", true)
1458 tryCompare(app3.session.lastSurface, "activeFocus", true)
1439− keyClick(Qt.Key_Tab, Qt.ControlModifier)
1459+ keyClick(Qt.Key_Tab, Qt.AltModifier)
1440 tryCompare(app2.session.lastSurface, "activeFocus", true)
1460 tryCompare(app2.session.lastSurface, "activeFocus", true)
1441
1461
1442− var desktopSpread = findChild(shell, "spread")
1462+ // Press Alt+Tab
1443−
1444− tryCompare(desktopSpread, "state", "")
1445−
1446− // Just press Alt, make sure the spread comes up
1447− keyPress(Qt.Key_Control);
1463+ keyPress(Qt.Key_Alt);
1448 keyClick(Qt.Key_Tab);
1464 keyClick(Qt.Key_Tab);
1449− tryCompare(desktopSpread, "state", "altTab")
1450−
1451− // Release control, check if spread disappears
1452− keyRelease(Qt.Key_Control)
1465+ keyRelease(Qt.Key_Alt)
1453− tryCompare(desktopSpread, "state", "")
1454
1466
1455 // Focus should have switched back now
1467 // Focus should have switched back now
1456 tryCompare(app3.session.lastSurface, "activeFocus", true)
1468 tryCompare(app3.session.lastSurface, "activeFocus", true)
@@ -1477,7 +1489,7 @@ Rectangle {
1477 tryCompare(desktopSpread, "state", "")
1489 tryCompare(desktopSpread, "state", "")
1478
1490
1479 // Just press Alt, make sure the spread comes up
1491 // Just press Alt, make sure the spread comes up
1480− keyPress(Qt.Key_Control);
1492+ keyPress(Qt.Key_Alt);
1481 keyClick(Qt.Key_Tab);
1493 keyClick(Qt.Key_Tab);
1482 tryCompare(desktopSpread, "state", "altTab")
1494 tryCompare(desktopSpread, "state", "altTab")
1483 tryCompare(spreadRepeater, "highlightedIndex", 1)
1495 tryCompare(spreadRepeater, "highlightedIndex", 1)
@@ -1498,7 +1510,7 @@ Rectangle {
1498 tryCompare(spreadRepeater, "highlightedIndex", 0)
1510 tryCompare(spreadRepeater, "highlightedIndex", 0)
1499
1511
1500 // Release control, check if spread disappears
1512 // Release control, check if spread disappears
1501− keyRelease(Qt.Key_Control)
1513+ keyRelease(Qt.Key_Alt)
1502 tryCompare(desktopSpread, "state", "")
1514 tryCompare(desktopSpread, "state", "")
1503
1515
1504 // Make sure that after wrapping around once, we have the same one focused as at the beginning
1516 // Make sure that after wrapping around once, we have the same one focused as at the beginning
@@ -1511,7 +1523,7 @@ Rectangle {
1511 var spreadRepeater = findInvisibleChild(shell, "spreadRepeater");
1523 var spreadRepeater = findInvisibleChild(shell, "spreadRepeater");
1512 verify(spreadRepeater !== null);
1524 verify(spreadRepeater !== null);
1513
1525
1514− keyPress(Qt.Key_Control)
1526+ keyPress(Qt.Key_Alt)
1515 keyClick(Qt.Key_Tab);
1527 keyClick(Qt.Key_Tab);
1516 tryCompare(spreadRepeater, "highlightedIndex", 1);
1528 tryCompare(spreadRepeater, "highlightedIndex", 1);
1517
1529
@@ -1533,7 +1545,7 @@ Rectangle {
1533 keyClick(Qt.Key_Backtab);
1545 keyClick(Qt.Key_Backtab);
1534 tryCompare(spreadRepeater, "highlightedIndex", 1);
1546 tryCompare(spreadRepeater, "highlightedIndex", 1);
1535
1547
1536− keyRelease(Qt.Key_Control);
1548+ keyRelease(Qt.Key_Alt);
1537 }
1549 }
1538
1550
1539 function test_highlightFollowsMouse() {
1551 function test_highlightFollowsMouse() {
@@ -1542,7 +1554,7 @@ Rectangle {
1542 var spreadRepeater = findInvisibleChild(shell, "spreadRepeater");
1554 var spreadRepeater = findInvisibleChild(shell, "spreadRepeater");
1543 verify(spreadRepeater !== null);
1555 verify(spreadRepeater !== null);
1544
1556
1545− keyPress(Qt.Key_Control)
1557+ keyPress(Qt.Key_Alt)
1546 keyClick(Qt.Key_Tab);
1558 keyClick(Qt.Key_Tab);
1547
1559
1548 tryCompare(spreadRepeater, "highlightedIndex", 1);
1560 tryCompare(spreadRepeater, "highlightedIndex", 1);
@@ -1561,7 +1573,7 @@ Rectangle {
1561
1573
1562 verify(y < 4000);
1574 verify(y < 4000);
1563
1575
1564− keyRelease(Qt.Key_Control);
1576+ keyRelease(Qt.Key_Alt);
1565 }
1577 }
1566
1578
1567 function test_closeFromSpread() {
1579 function test_closeFromSpread() {
@@ -1570,7 +1582,7 @@ Rectangle {
1570 var spreadRepeater = findInvisibleChild(shell, "spreadRepeater");
1582 var spreadRepeater = findInvisibleChild(shell, "spreadRepeater");
1571 verify(spreadRepeater !== null);
1583 verify(spreadRepeater !== null);
1572
1584
1573− keyPress(Qt.Key_Control)
1585+ keyPress(Qt.Key_Alt)
1574 keyClick(Qt.Key_Tab);
1586 keyClick(Qt.Key_Tab);
1575
1587
1576 appRemovedSpy.clear();
1588 appRemovedSpy.clear();
@@ -1597,7 +1609,7 @@ Rectangle {
1597 tryCompare(appRemovedSpy, "count", 1)
1609 tryCompare(appRemovedSpy, "count", 1)
1598 compare(appRemovedSpy.signalArguments[0][0], closedAppId);
1610 compare(appRemovedSpy.signalArguments[0][0], closedAppId);
1599
1611
1600− keyRelease(Qt.Key_Control);
1612+ keyRelease(Qt.Key_Alt);
1601 }
1613 }
1602
1614
1603 function test_selectFromSpreadWithMouse_data() {
1615 function test_selectFromSpreadWithMouse_data() {
@@ -1617,7 +1629,7 @@ Rectangle {
1617 var spreadRepeater = findInvisibleChild(shell, "spreadRepeater");
1629 var spreadRepeater = findInvisibleChild(shell, "spreadRepeater");
1618 verify(spreadRepeater !== null);
1630 verify(spreadRepeater !== null);
1619
1631
1620− keyPress(Qt.Key_Control)
1632+ keyPress(Qt.Key_Alt)
1621 keyClick(Qt.Key_Tab);
1633 keyClick(Qt.Key_Tab);
1622
1634
1623 var focusAppId = ApplicationManager.get(2).appId;
1635 var focusAppId = ApplicationManager.get(2).appId;
@@ -1644,7 +1656,7 @@ Rectangle {
1644 tryCompare(stage, "state", "");
1656 tryCompare(stage, "state", "");
1645 tryCompare(ApplicationManager, "focusedApplicationId", focusAppId);
1657 tryCompare(ApplicationManager, "focusedApplicationId", focusAppId);
1646
1658
1647− keyRelease(Qt.Key_Control);
1659+ keyRelease(Qt.Key_Alt);
1648 }
1660 }
1649
1661
1650 function test_progressiveAutoScrolling() {
1662 function test_progressiveAutoScrolling() {
@@ -1653,7 +1665,7 @@ Rectangle {
1653 var appRepeater = findInvisibleChild(shell, "appRepeater");
1665 var appRepeater = findInvisibleChild(shell, "appRepeater");
1654 verify(appRepeater !== null);
1666 verify(appRepeater !== null);
1655
1667
1656− keyPress(Qt.Key_Control)
1668+ keyPress(Qt.Key_Alt)
1657 keyClick(Qt.Key_Tab);
1669 keyClick(Qt.Key_Tab);
1658
1670
1659 var spreadFlickable = findChild(shell, "spreadFlickable")
1671 var spreadFlickable = findChild(shell, "spreadFlickable")
@@ -1664,7 +1676,7 @@ Rectangle {
1664 var x = 0;
1676 var x = 0;
1665 var y = shell.height * .5
1677 var y = shell.height * .5
1666 mouseMove(shell, x, y)
1678 mouseMove(shell, x, y)
1667− while (x <= spreadFlickable.width) {
1679+ while (x <= shell.width) {
1668 x+=10;
1680 x+=10;
1669 mouseMove(shell, x, y)
1681 mouseMove(shell, x, y)
1670 wait(0); // spin the loop so bindings get evaluated
1682 wait(0); // spin the loop so bindings get evaluated
@@ -1679,7 +1691,7 @@ Rectangle {
1679 }
1691 }
1680 tryCompare(spreadFlickable, "contentX", 0);
1692 tryCompare(spreadFlickable, "contentX", 0);
1681
1693
1682− keyRelease(Qt.Key_Control);
1694+ keyRelease(Qt.Key_Alt);
1683 }
1695 }
1684
1696
1685 // This makes sure the hoverMouseArea is set to invisible AND disabled
1697 // This makes sure the hoverMouseArea is set to invisible AND disabled
@@ -1691,13 +1703,13 @@ Rectangle {
1691 tryCompare(hoverMouseArea, "enabled", false)
1703 tryCompare(hoverMouseArea, "enabled", false)
1692 tryCompare(hoverMouseArea, "visible", false)
1704 tryCompare(hoverMouseArea, "visible", false)
1693
1705
1694− keyPress(Qt.Key_Control)
1706+ keyPress(Qt.Key_Alt)
1695 keyClick(Qt.Key_Tab);
1707 keyClick(Qt.Key_Tab);
1696
1708
1697 tryCompare(hoverMouseArea, "enabled", true)
1709 tryCompare(hoverMouseArea, "enabled", true)
1698 tryCompare(hoverMouseArea, "visible", true)
1710 tryCompare(hoverMouseArea, "visible", true)
1699
1711
1700− keyRelease(Qt.Key_Control)
1712+ keyRelease(Qt.Key_Alt)
1701
1713
1702 tryCompare(hoverMouseArea, "enabled", false)
1714 tryCompare(hoverMouseArea, "enabled", false)
1703 tryCompare(hoverMouseArea, "visible", false)
1715 tryCompare(hoverMouseArea, "visible", false)
@@ -1714,7 +1726,7 @@ Rectangle {
1714 var appRepeater = findInvisibleChild(shell, "appRepeater");
1726 var appRepeater = findInvisibleChild(shell, "appRepeater");
1715 verify(appRepeater !== null);
1727 verify(appRepeater !== null);
1716
1728
1717− keyPress(Qt.Key_Control)
1729+ keyPress(Qt.Key_Alt)
1718 keyClick(Qt.Key_Tab);
1730 keyClick(Qt.Key_Tab);
1719
1731
1720 tryCompare(spreadRepeater, "highlightedIndex", 1);
1732 tryCompare(spreadRepeater, "highlightedIndex", 1);
@@ -1735,33 +1747,46 @@ Rectangle {
1735
1747
1736 verify(y < 4000);
1748 verify(y < 4000);
1737
1749
1738− keyRelease(Qt.Key_Control);
1750+ keyRelease(Qt.Key_Alt);
1739 }
1751 }
1740
1752
1741− function test_focusAppFromLauncherExitsSpread() {
1753+ function test_focusAppFromLauncherExitsSpread_data() {
1742− loadDesktopShellWithApps()
1754+ return [
1755+ {tag: "autohide launcher", launcherLocked: false },
1756+ {tag: "locked launcher", launcherLocked: true }
1757+ ]
1758+ }
1743
1759
1744− var desktopSpread = findChild(shell, "spread");
1760+ function test_focusAppFromLauncherExitsSpread(data) {
1761+ loadDesktopShellWithApps()
1745 var launcher = findChild(shell, "launcher");
1762 var launcher = findChild(shell, "launcher");
1763+ var desktopSpread = findChild(shell, "spread");
1746 var bfb = findChild(launcher, "buttonShowDashHome");
1764 var bfb = findChild(launcher, "buttonShowDashHome");
1747
1765
1766+ GSettingsController.setAutohideLauncher(!data.launcherLocked);
1767+ waitForRendering(shell);
1768+
1748− keyPress(Qt.Key_Control)
1769+ keyPress(Qt.Key_Alt)
1749 keyClick(Qt.Key_Tab);
1770 keyClick(Qt.Key_Tab);
1750
1771
1751 tryCompare(desktopSpread, "state", "altTab")
1772 tryCompare(desktopSpread, "state", "altTab")
1752
1773
1774+ if (!data.launcherLocked) {
1753− revealLauncherByEdgePushWithMouse();
1775+ revealLauncherByEdgePushWithMouse();
1754− tryCompare(launcher, "x", 0);
1776+ tryCompare(launcher, "x", 0);
1755− mouseMove(bfb, bfb.width / 2, bfb.height / 2)
1777+ mouseMove(bfb, bfb.width / 2, bfb.height / 2)
1756− waitForRendering(shell)
1778+ waitForRendering(shell)
1779+ }
1757
1780
1758 mouseClick(bfb, bfb.width / 2, bfb.height / 2)
1781 mouseClick(bfb, bfb.width / 2, bfb.height / 2)
1782+ if (!data.launcherLocked) {
1759− tryCompare(launcher, "state", "")
1783+ tryCompare(launcher, "state", "")
1784+ }
1760 tryCompare(desktopSpread, "state", "")
1785 tryCompare(desktopSpread, "state", "")
1761
1786
1762 tryCompare(ApplicationManager, "focusedApplicationId", "unity8-dash")
1787 tryCompare(ApplicationManager, "focusedApplicationId", "unity8-dash")
1763
1788
1764− keyRelease(Qt.Key_Control);
1789+ keyRelease(Qt.Key_Alt);
1765 }
1790 }
1766
1791
1767 // regression test for http://pad.lv/1443319
1792 // regression test for http://pad.lv/1443319
@@ -1849,6 +1874,31 @@ Rectangle {
1849 compare(appDelegate.y >= PanelState.panelHeight, true);
1874 compare(appDelegate.y >= PanelState.panelHeight, true);
1850 }
1875 }
1851
1876
1877+ function test_cantResizeWindowUnderPanel() {
1878+ loadShell("desktop");
1879+ shell.usageScenario = "desktop";
1880+ waitForRendering(shell);
1881+
1882+ var app = ApplicationManager.startApplication("dialer-app")
1883+ waitUntilAppWindowIsFullyLoaded(app);
1884+
1885+ var appContainer = findChild(shell, "appContainer");
1886+ var appDelegate = findChild(appContainer, "appDelegate_dialer-app");
1887+ var decoration = findChild(appDelegate, "appWindowDecoration_dialer-app");
1888+ verify(decoration);
1889+
1890+ // move it away from launcher and panel
1891+ appDelegate.x = units.gu(10)
1892+ appDelegate.y = units.gu(10)
1893+
1894+ // drag-resize the area up
1895+ mousePress(decoration, decoration.width/2, -units.gu(1));
1896+ mouseMove(decoration, decoration.width/2, -units.gu(100));
1897+
1898+ // verify we don't go past the panel
1899+ compare(appDelegate.y >= PanelState.panelHeight, true);
1900+ }
1901+
1852 function test_restoreWindowStateFixesIfUnderPanel() {
1902 function test_restoreWindowStateFixesIfUnderPanel() {
1853 loadDesktopShellWithApps();
1903 loadDesktopShellWithApps();
1854 var appRepeater = findChild(shell, "appRepeater")
1904 var appRepeater = findChild(shell, "appRepeater")
@@ -1984,6 +2034,138 @@ Rectangle {
1984 }
2034 }
1985 }
2035 }
1986
2036
2037+ function test_superTabToCycleLauncher_data() {
2038+ return [
2039+ {tag: "autohide launcher", launcherLocked: false},
2040+ {tag: "locked launcher", launcherLocked: true}
2041+ ]
2042+ }
2043+
2044+ function test_superTabToCycleLauncher(data) {
2045+ loadShell("desktop");
2046+ shell.usageScenario = "desktop";
2047+ GSettingsController.setAutohideLauncher(!data.launcherLocked);
2048+ waitForRendering(shell);
2049+
2050+ var launcher = findChild(shell, "launcher");
2051+ var launcherPanel = findChild(launcher, "launcherPanel");
2052+ var firstAppInLauncher = LauncherModel.get(0).appId;
2053+
2054+ compare(launcher.state, data.launcherLocked ? "visible": "");
2055+ compare(launcherPanel.highlightIndex, -2);
2056+ compare(ApplicationManager.focusedApplicationId, "unity8-dash");
2057+
2058+ // Use Super + Tab Tab to cycle to the first entry in the launcher
2059+ keyPress(Qt.Key_Super_L, Qt.MetaModifier);
2060+ keyClick(Qt.Key_Tab);
2061+ tryCompare(launcher, "state", "visible");
2062+ tryCompare(launcherPanel, "highlightIndex", -1);
2063+ keyClick(Qt.Key_Tab);
2064+ tryCompare(launcherPanel, "highlightIndex", 0);
2065+ keyRelease(Qt.Key_Super_L, Qt.MetaModifier);
2066+ tryCompare(launcher, "state", data.launcherLocked ? "visible" : "");
2067+ tryCompare(launcherPanel, "highlightIndex", -2);
2068+ tryCompare(ApplicationManager, "focusedApplicationId", firstAppInLauncher);
2069+
2070+ // Now go back to the dash
2071+ keyPress(Qt.Key_Super_L, Qt.MetaModifier);
2072+ keyClick(Qt.Key_Tab);
2073+ tryCompare(launcher, "state", "visible");
2074+ tryCompare(launcherPanel, "highlightIndex", -1);
2075+ keyRelease(Qt.Key_Super_L, Qt.MetaModifier);
2076+ tryCompare(launcher, "state", data.launcherLocked ? "visible" : "");
2077+ tryCompare(launcherPanel, "highlightIndex", -2);
2078+ tryCompare(ApplicationManager, "focusedApplicationId", "unity8-dash");
2079+ }
2080+
2081+ function test_longpressSuperOpensLauncher() {
2082+ loadShell("desktop");
2083+ var launcher = findChild(shell, "launcher");
2084+ var shortcutHint = findChild(findChild(launcher, "launcherDelegate0"), "shortcutHint")
2085+
2086+ compare(launcher.state, "");
2087+ keyPress(Qt.Key_Super_L, Qt.MetaModifier);
2088+ tryCompare(launcher, "state", "visible");
2089+ tryCompare(shortcutHint, "visible", true);
2090+
2091+ keyRelease(Qt.Key_Super_L, Qt.MetaModifier);
2092+ tryCompare(launcher, "state", "");
2093+ tryCompare(shortcutHint, "visible", false);
2094+ }
2095+
2096+ function test_metaNumberLaunchesFromLauncher_data() {
2097+ return [
2098+ {tag: "Meta+1", key: Qt.Key_1, index: 0},
2099+ {tag: "Meta+2", key: Qt.Key_2, index: 1},
2100+ {tag: "Meta+4", key: Qt.Key_5, index: 4},
2101+ {tag: "Meta+0", key: Qt.Key_0, index: 9},
2102+ ]
2103+ }
2104+
2105+ function test_metaNumberLaunchesFromLauncher(data) {
2106+ loadShell("desktop");
2107+ var launcher = findChild(shell, "launcher");
2108+ var appId = LauncherModel.get(data.index).appId;
2109+ waitForRendering(shell);
2110+
2111+ keyClick(data.key, Qt.MetaModifier);
2112+ tryCompare(ApplicationManager, "focusedApplicationId", appId);
2113+ }
2114+
2115+ function test_altF1OpensLauncherForKeyboardNavigation() {
2116+ loadShell("desktop");
2117+ waitForRendering(shell);
2118+ var launcher = findChild(shell, "launcher");
2119+
2120+ keyClick(Qt.Key_F1, Qt.AltModifier);
2121+ tryCompare(launcher, "state", "visible");
2122+ tryCompare(launcher, "focus", true)
2123+ }
2124+
2125+ function test_lockedOutLauncherAddsMarginsToMaximized() {
2126+ loadShell("desktop");
2127+ shell.usageScenario = "desktop";
2128+ waitForRendering(shell);
2129+ var appContainer = findChild(shell, "appContainer");
2130+ var launcher = findChild(shell, "launcher");
2131+
2132+ var app = ApplicationManager.startApplication("music-app");
2133+ waitUntilAppWindowIsFullyLoaded(app);
2134+ var appDelegate = findChild(appContainer, "appDelegate_music-app");
2135+ appDelegate.maximize();
2136+ tryCompare(appDelegate, "visuallyMaximized", true);
2137+ waitForRendering(shell);
2138+
2139+ GSettingsController.setAutohideLauncher(true);
2140+ waitForRendering(shell)
2141+ var hiddenSize = appDelegate.width;
2142+
2143+ GSettingsController.setAutohideLauncher(false);
2144+ waitForRendering(shell)
2145+ var shownSize = appDelegate.width;
2146+
2147+ compare(shownSize + launcher.panelWidth, hiddenSize);
2148+ }
2149+
2150+ function test_fullscreenAppHidesLockedOutLauncher() {
2151+ loadShell("desktop");
2152+ shell.usageScenario = "desktop";
2153+
2154+ var launcher = findChild(shell, "launcher");
2155+ var launcherPanel = findChild(launcher, "launcherPanel");
2156+
2157+ GSettingsController.setAutohideLauncher(false);
2158+ waitForRendering(shell)
2159+
2160+ tryCompare(launcher, "lockedVisible", true);
2161+
2162+ var cameraApp = ApplicationManager.startApplication("camera-app");
2163+ waitUntilAppWindowIsFullyLoaded(cameraApp);
2164+
2165+ tryCompare(launcher, "lockedVisible", false);
2166+ }
2167+
2168+
1987 function test_inputEventsOnEdgesEndUpInAppSurface_data() {
2169 function test_inputEventsOnEdgesEndUpInAppSurface_data() {
1988 return [
2170 return [
1989 { tag: "phone", repeaterName: "spreadRepeater" },
2171 { tag: "phone", repeaterName: "spreadRepeater" },
tests/utils/modules/Unity/Test/UnityTestCase.qml+67−11
@@ -313,7 +313,10 @@ TestCase {
313 // perform a drag in the given direction until the given condition is true
313 // perform a drag in the given direction until the given condition is true
314 // The condition is a function to be evaluated after every step
314 // The condition is a function to be evaluated after every step
315 function touchDragUntil(item, startX, startY, stepX, stepY, condition) {
315 function touchDragUntil(item, startX, startY, stepX, stepY, condition) {
316+ multiTouchDragUntil([0], item, startX, startY, stepX, stepY, condition);
317+ }
316
318
319+ function multiTouchDragUntil(touchIds, item, startX, startY, stepX, stepY, condition) {
317 var root = fetchRootItem(item);
320 var root = fetchRootItem(item);
318 var pos = item.mapToItem(root, startX, startY);
321 var pos = item.mapToItem(root, startX, startY);
319
322
@@ -326,7 +329,9 @@ TestCase {
326 stepY = stepEnd.y - stepStart.y;
329 stepY = stepEnd.y - stepStart.y;
327
330
328 var event = touchEvent(item)
331 var event = touchEvent(item)
332+ for (var i = 0; i < touchIds.length; i++) {
329− event.press(0 /* touchId */, pos.x, pos.y)
333+ event.press(touchIds[i], pos.x, pos.y)
334+ }
330 event.commit()
335 event.commit()
331
336
332 // we have to stop at some point
337 // we have to stop at some point
@@ -341,23 +346,30 @@ TestCase {
341 pos.y += stepY;
346 pos.y += stepY;
342
347
343 event = touchEvent(item);
348 event = touchEvent(item);
349+ for (i = 0; i < touchIds.length; i++) {
344− event.move(0 /* touchId */, pos.x, pos.y);
350+ event.move(touchIds[i], pos.x, pos.y);
351+ }
345 event.commit();
352 event.commit();
346
353
347 stepsDone += 1;
354 stepsDone += 1;
348 }
355 }
349
356
350 event = touchEvent(item)
357 event = touchEvent(item)
358+ for (i = 0; i < touchIds.length; i++) {
351− event.release(0 /* touchId */, pos.x, pos.y)
359+ event.release(touchIds[i], pos.x, pos.y)
360+ }
352 event.commit()
361 event.commit()
353 }
362 }
354
363
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;
356 var root = fetchRootItem(item)
368 var root = fetchRootItem(item)
357 var rootPoint = item.mapToItem(root, tox, toy)
369 var rootPoint = item.mapToItem(root, tox, toy)
358
370
359 var event = touchEvent(item);
371 var event = touchEvent(item);
360− event.move(0 /* touchId */, rootPoint.x, rootPoint.y);
372+ event.move(touchId, rootPoint.x, rootPoint.y);
361 event.commit();
373 event.commit();
362 }
374 }
363
375
@@ -394,21 +406,55 @@ TestCase {
394 return item
406 return item
395 }
407 }
396
408
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 = []
398 var root = fetchRootItem(item)
424 var root = fetchRootItem(item)
399 var rootPoint = item.mapToItem(root, x, y)
425 var rootPoint = item.mapToItem(root, x, y)
400
426
401 var event = touchEvent(item)
427 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+ }
403 event.commit()
432 event.commit()
404 }
433 }
405
434
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 = []
407 var root = fetchRootItem(item)
450 var root = fetchRootItem(item)
408 var rootPoint = item.mapToItem(root, x, y)
451 var rootPoint = item.mapToItem(root, x, y)
409
452
410 var event = touchEvent(item)
453 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+ }
412 event.commit()
458 event.commit()
413 }
459 }
414
460
@@ -419,6 +465,11 @@ TestCase {
419 \param y The y coordinate of the tap, defaults to vertical center
465 \param y The y coordinate of the tap, defaults to vertical center
420 */
466 */
421 function tap(item, x, y) {
467 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];
422 if (typeof x !== "number") x = item.width / 2;
473 if (typeof x !== "number") x = item.width / 2;
423 if (typeof y !== "number") y = item.height / 2;
474 if (typeof y !== "number") y = item.height / 2;
424
475
@@ -426,14 +477,19 @@ TestCase {
426 var rootPoint = item.mapToItem(root, x, y)
477 var rootPoint = item.mapToItem(root, x, y)
427
478
428 var event = touchEvent(item)
479 var event = touchEvent(item)
480+ for (var i = 0; i < touchIds.length; i++) {
429− event.press(0 /* touchId */, rootPoint.x, rootPoint.y)
481+ event.press(touchIds[i], rootPoint.x, rootPoint.y)
482+ }
430 event.commit()
483 event.commit()
431
484
432 event = touchEvent(item)
485 event = touchEvent(item)
486+ for (i = 0; i < touchIds.length; i++) {
433− event.release(0 /* touchId */, rootPoint.x, rootPoint.y)
487+ event.release(touchIds[i], rootPoint.x, rootPoint.y)
488+ }
434 event.commit()
489 event.commit()
435 }
490 }
436
491
492+
437 Component.onCompleted: {
493 Component.onCompleted: {
438 var rootItem = parent;
494 var rootItem = parent;
439 while (rootItem.parent != undefined) {
495 while (rootItem.parent != undefined) {