on xenial help sign in
Merge silo 64 (soon to be trunk)
commit62d1763ec200bb5fc6fc3ef41bc15d15f76989a0 authorMichael Terry <michael.terry@canonical.com> dateFri 11 Mar 2016, 12:51 (11 years ago) parenta7dcbae8855d4b
151 files changed+4782−1192
CMakeLists.txt+1−1 cmake/modules/QmlTest.cmake+1−1 data/com.canonical.Unity8.gschema.xml+11 data/unity8-dash.conf+1 debian/changelog+91 debian/control+7−6 debian/unity8-common.udev+2 debian/unity8-doc.install+1 doc/devices.conf+30 plugins/AccountsService/AccountsService.cpp+197−406 plugins/AccountsService/AccountsService.h+25−24 plugins/AccountsService/AccountsServiceDBusAdaptor.cpp+11−4 plugins/AccountsService/AccountsServiceDBusAdaptor.h+5−6 plugins/Dash/AudioProgressBar.qml+1 plugins/Dash/CardCreator.js+69−34 plugins/Dash/CardCreatorCache.qml+3−3 plugins/Dash/plugin.cpp+6−6 plugins/GlobalShortcut/globalshortcutregistry.cpp+4−4 plugins/IntegratedLightDM/liblightdm/CMakeLists.txt+16−3 plugins/IntegratedLightDM/liblightdm/UsersModel.cpp+4−18 plugins/IntegratedLightDM/liblightdm/UsersModel.h+1−1 plugins/IntegratedLightDM/liblightdm/UsersModelPrivate.cpp+43−9 plugins/IntegratedLightDM/liblightdm/UsersModelPrivate.h+15−3 plugins/Unity/Indicators/indicatorsmanager.cpp+2−2 plugins/Unity/Indicators/rootstateparser.h+1 plugins/Unity/Launcher/desktopfilehandler.cpp+2−2 plugins/Unity/Launcher/launcheritem.cpp+1 plugins/Unity/Launcher/launcheritem.h+1−1 plugins/Unity/Launcher/launchermodel.cpp+15−5 plugins/Unity/Launcher/launchermodel.h+1−1 plugins/Utils/CMakeLists.txt+1 plugins/Utils/deviceconfigparser.cpp+150 plugins/Utils/deviceconfigparser.h+62 plugins/Utils/plugin.cpp+2 po/unity8.pot+29−27 qml/Components/EdgeBarrier.qml+1−1 qml/Components/InputMethod.qml+1−9 qml/Components/Lockscreen.qml+80−94 qml/Components/ModeSwitchWarningDialog.qml+1−1 qml/Components/PassphraseLockscreen.qml+5−4 qml/Components/PhysicalKeysMapper.qml+24 qml/Components/PinLockscreen.qml+22−1 qml/Components/ShellDialog.qml+8 qml/Dash/CardCarousel.qml−1 qml/Dash/CardGrid.qml−1 qml/Dash/CardHorizontalList.qml−1 qml/Dash/CardTool.qml+2−26 qml/Dash/CardVerticalJournal.qml−1 qml/Dash/Dash.qml+11−11 qml/Dash/GenericScopeView.qml+2 qml/Dash/Previews/PreviewHeader.qml+37−9 qml/Dash/Previews/PreviewOverlay.qml+1−1 qml/Dash/Previews/PreviewSharing.qml+17−7 qml/Dash/ScopesListCategoryItem.qml+8−4 qml/DeviceConfiguration.qml+54−16 qml/DisabledScreenNotice.qml+44−20 qml/Greeter/CoverPage.qml+13−2 qml/Greeter/Infographics.qml+2−1 qml/Greeter/NarrowView.qml+14−1 qml/Launcher/Launcher.qml+144−11 qml/Launcher/LauncherDelegate.qml+55−20 qml/Launcher/LauncherPanel.qml+122−51 qml/Launcher/graphics/launcher-app-focus-ring.svg+12 qml/Notifications/Notification.qml+6−2 qml/OrientedShell.qml+26−1 qml/Panel/Handle.qml+2−2 qml/Panel/Indicators/MenuItemFactory.qml−1 qml/Panel/IndicatorsMenu.qml+1−1 qml/Panel/Panel.qml+5−1 qml/ScopeTool.qml−8 qml/Shell.qml+51−1 qml/Stages/AbstractStage.qml+3−1 qml/Stages/DesktopSpread.qml+64−17 qml/Stages/DesktopSpreadDelegate.qml+15−1 qml/Stages/DesktopStage.qml+34−34 qml/Stages/PhoneStage.qml+77−3 qml/Stages/SpreadDelegate.qml+20 qml/Stages/TabletStage.qml+83−3 qml/Stages/WindowResizeArea.qml+8−5 src/CMakeLists.txt+1−1 src/CachingNetworkManagerFactory.cpp+2−2 src/CachingNetworkManagerFactory.h+3−2 src/Dash/CMakeLists.txt+1−1 tests/autopilot/unity8/dash.py+1−2 tests/autopilot/unity8/shell/tests/test_helpers.py−2 tests/mocks/GSettings.1.0/fake_gsettings.cpp+64 tests/mocks/GSettings.1.0/fake_gsettings.h+18 tests/mocks/IntegratedLightDM/liblightdm/UsersModel.cpp−14 tests/mocks/Unity/Application/MirSurface.cpp+6−2 tests/mocks/Unity/Application/MirSurface.h+2−1 tests/mocks/Unity/Application/SurfaceManager.cpp+8−9 tests/mocks/Unity/Application/SurfaceManager.h+3−3 tests/mocks/Unity/Application/VirtualKeyboard.cpp+3−2 tests/mocks/Unity/Application/VirtualKeyboard.h+1−1 tests/mocks/Unity/Application/plugin.cpp+2−1 tests/mocks/Unity/Launcher/MockLauncherModel.cpp+2 tests/mocks/Utils/CMakeLists.txt+1 tests/mocks/Utils/plugin.cpp+2 tests/plugins/AccountsService/CMakeLists.txt+5 tests/plugins/AccountsService/PropertiesServer.cpp+16−4 tests/plugins/AccountsService/PropertiesServer.h+2−1 tests/plugins/AccountsService/UscServer.cpp+28 tests/plugins/AccountsService/UscServer.h+39 tests/plugins/AccountsService/client.cpp+63−9 tests/plugins/AccountsService/interfaces.xml+16 tests/plugins/AccountsService/server.cpp+12 tests/plugins/Dash/cardcreator/1.res+4−6 tests/plugins/Dash/cardcreator/1.res.cardcreator+119 tests/plugins/Dash/cardcreator/10.res+3−5 tests/plugins/Dash/cardcreator/10.res.cardcreator+137 tests/plugins/Dash/cardcreator/11.res+5−7 tests/plugins/Dash/cardcreator/11.res.cardcreator+210 tests/plugins/Dash/cardcreator/2.res+3−5 tests/plugins/Dash/cardcreator/2.res.cardcreator+136 tests/plugins/Dash/cardcreator/3.res+3−5 tests/plugins/Dash/cardcreator/3.res.cardcreator+137 tests/plugins/Dash/cardcreator/4.res+3−5 tests/plugins/Dash/cardcreator/4.res.cardcreator+109 tests/plugins/Dash/cardcreator/5.res+5−7 tests/plugins/Dash/cardcreator/5.res.cardcreator+156 tests/plugins/Dash/cardcreator/6.res+2−4 tests/plugins/Dash/cardcreator/6.res.cardcreator+126 tests/plugins/Dash/cardcreator/7.res+3−5 tests/plugins/Dash/cardcreator/7.res.cardcreator+149 tests/plugins/Dash/cardcreator/8.res+3−5 tests/plugins/Dash/cardcreator/8.res.cardcreator+107 tests/plugins/Dash/cardcreator/9.res+4−5 tests/plugins/Dash/cardcreator/9.res.cardcreator+119 tests/plugins/Dash/cardcreatortest.cpp+32−22 tests/plugins/Dash/cardcreatortest.qml+4−4 tests/plugins/IntegratedLightDM/CMakeLists.txt+25 tests/plugins/IntegratedLightDM/integrated.cpp+91 tests/plugins/Utils/CMakeLists.txt+7−1 tests/plugins/Utils/DeviceConfigParserTest.cpp+70 tests/qmltests/CMakeLists.txt+1 tests/qmltests/Components/tst_Lockscreen.qml+14−7 tests/qmltests/Dash/Previews/tst_PreviewHeader.qml+18−1 tests/qmltests/Dash/Previews/tst_PreviewSharing.qml+17−3 tests/qmltests/Dash/tst_CardTool.qml+4−4 tests/qmltests/Dash/tst_Dash.qml+89 tests/qmltests/Greeter/tst_NarrowView.qml+9 tests/qmltests/Launcher/tst_Launcher.qml+254−18 tests/qmltests/Panel/Indicators/tst_MenuItemFactory.qml+2−4 tests/qmltests/Stages/tst_DesktopStage.qml+2−1 tests/qmltests/Stages/tst_PhoneStage.qml+19−6 tests/qmltests/Stages/tst_SpreadDelegate.qml+7 tests/qmltests/Stages/tst_TabletStage.qml+19−1 tests/qmltests/tst_DeviceConfiguration.qml+49 tests/qmltests/tst_DisabledScreenNotice.qml+76−2 tests/qmltests/tst_OrientedShell.qml+1−1 tests/qmltests/tst_Shell.qml+241−59
CMakeLists.txt+1−1
@@ -132,7 +132,7 @@ if(NOT QUIET_LOGGER)
132132 set(STDOUT_LOGGER "-o" "-,txt")
133133endif()
134134
135−execute_process(COMMAND arch OUTPUT_VARIABLE ARCH)
135+execute_process(COMMAND dpkg-architecture -qDEB_HOST_ARCH OUTPUT_VARIABLE ARCH)
136136if (NOT ARCH MATCHES "arm*")
137137 set(ENABLE_TOUCH_EMULATION true)
138138 add_definitions(-DUNITY8_ENABLE_TOUCH_EMULATION)
cmake/modules/QmlTest.cmake+1−1
@@ -183,7 +183,7 @@ function(add_manual_test COMPONENT_NAME TARGET)
183183 cmake_parse_arguments(QMLTEST "${QMLTEST_OPTIONS}" "${QMLTEST_SINGLE}" "${QMLTEST_MULTI}" ${ARGN})
184184 mangle_arguments()
185185
186− bake_arguments("${QMLTEST_ARG_PREFIX}" args -qmljsdebugger=port:3768)
186+ bake_arguments("${QMLTEST_ARG_PREFIX}" args -qmljsdebugger=port:3768,3800)
187187
188188 set(qmltry_command
189189 $<TARGET_FILE:${TARGET}>
data/com.canonical.Unity8.gschema.xml+11
@@ -27,6 +27,17 @@
2727 <summary>Maximum push needed to overcome edge barrier</summary>
2828 <description>How much you have to push (in grid units) the mouse against an edge barrier when sensibility is 1.</description>
2929 </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>
3041 </schema>
3142
3243 <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
1414oom score 50
1515
1616respawn
17+respawn limit unlimited
1718
1819env APP_ID=unity8-dash
1920
debian/changelog+91
@@ -4,6 +4,97 @@ unity8 (8.13+15.04.20150914-0ubuntu1) UNRELEASED; urgency=medium
44
55 -- Lukas Tinkl <ltinkl@canonical.com> Mon, 14 Sep 2015 19:19:23 +0200
66
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+
798unity8 (8.11+16.04.20160308-0ubuntu1) xenial; urgency=medium
899
9100 [ Albert Astals Cid ]
debian/control+7−6
@@ -13,7 +13,7 @@ Build-Depends: cmake,
1313 libandroid-properties-dev,
1414 graphviz,
1515 gsettings-ubuntu-schemas (>= 0.0.2+14.10.20140815),
16− libconnectivity-qt1-dev,
16+ libconnectivity-qt1-dev (>= 0.7.1),
1717 libevdev-dev,
1818 libgeonames-dev (>= 0.2),
1919 libgl1-mesa-dev[!armhf] | libgl-dev[!armhf],
@@ -56,7 +56,7 @@ Build-Depends: cmake,
5656 qtdeclarative5-qtmultimedia-plugin (>= 5.4.1-1ubuntu19~overlay2),
5757 qtdeclarative5-ubuntu-content1,
5858 qtdeclarative5-ubuntu-settings-components (>= 0.7),
59− 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),
6060 qtdeclarative5-ubuntu-web-plugin,
6161 ttf-ubuntu-font-family,
6262Standards-Version: 3.9.4
@@ -71,7 +71,7 @@ X-Ubuntu-Use-Langpack: yes
7171Package: indicators-client
7272Architecture: amd64 armhf i386
7373Depends: qmenumodel-qml (>= 0.2.9),
74− 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),
7575 unity8 (= ${binary:Version}),
7676 ${misc:Depends},
7777 ${shlibs:Depends},
@@ -102,7 +102,6 @@ Depends: dmz-cursor-theme,
102102 qml-module-qt-labs-folderlistmodel,
103103 qml-module-qtquick-xmllistmodel,
104104 qml-module-qtsysteminfo,
105− qtdeclarative5-gsettings1.0,
106105 qtdeclarative5-qtmir-plugin (>= 0.4.5),
107106 qtdeclarative5-ubuntu-telephony0.1,
108107 qtdeclarative5-ubuntu-web-plugin,
@@ -129,7 +128,7 @@ Architecture: all
129128Depends: qml-module-qtquick-layouts,
130129 qtdeclarative5-ubuntu-settings-components (>= 0.7),
131130 qtdeclarative5-ubuntu-thumbnailer0.1 | ubuntu-thumbnailer-impl,
132− 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),
133132 qtdeclarative5-unity-notifications-plugin (>= 0.1.2) | unity-notifications-impl,
134133 ubuntu-thumbnailer-impl-0,
135134 unity-application-impl-13,
@@ -159,7 +158,7 @@ Depends: autopilot-qt5 (>= 1.4),
159158 python3-fixtures,
160159 python3-gi,
161160 qttestability-autopilot (>= 1.4),
162− ubuntu-ui-toolkit-autopilot (>= 1.3.1796),
161+ ubuntu-ui-toolkit-autopilot (>= 1.3.1845),
163162 unity-scope-click,
164163 unity8 (= ${source:Version}),
165164 unity8-fake-env (= ${source:Version}),
@@ -193,6 +192,8 @@ Depends: accountsservice-ubuntu-schemas (>= 0.0.5),
193192 libhardware2,
194193 pay-service,
195194 unity-schemas (>= 7.3.1+14.10.20140915),
195+ qtdeclarative5-gsettings1.0,
196+ qml-module-qtmultimedia,
196197 ${misc:Depends},
197198 ${shlibs:Depends},
198199Provides: 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/
12usr/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
plugins/AccountsService/AccountsService.cpp+197−406
@@ -22,17 +22,54 @@
2222#include <QStringList>
2323#include <QDebug>
2424
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_EMAIL QStringLiteral("Email")
36+#define PROP_ENABLE_INDICATORS_WHILE_LOCKED QStringLiteral("EnableIndicatorsWhileLocked")
37+#define PROP_ENABLE_LAUNCHER_WHILE_LOCKED QStringLiteral("EnableLauncherWhileLocked")
38+#define PROP_FAILED_LOGINS QStringLiteral("FailedLogins")
39+#define PROP_LICENSE_ACCEPTED QStringLiteral("LicenseAccepted")
40+#define PROP_LICENSE_BASE_PATH QStringLiteral("LicenseBasePath")
41+#define PROP_MOUSE_CURSOR_SPEED QStringLiteral("MouseCursorSpeed")
42+#define PROP_MOUSE_DOUBLE_CLICK_SPEED QStringLiteral("MouseDoubleClickSpeed")
43+#define PROP_MOUSE_PRIMARY_BUTTON QStringLiteral("MousePrimaryButton")
44+#define PROP_MOUSE_SCROLL_SPEED QStringLiteral("MouseScrollSpeed")
45+#define PROP_PASSWORD_DISPLAY_HINT QStringLiteral("PasswordDisplayHint")
46+#define PROP_REAL_NAME QStringLiteral("RealName")
47+#define PROP_STATS_WELCOME_SCREEN QStringLiteral("StatsWelcomeScreen")
48+#define PROP_TOUCHPAD_CURSOR_SPEED QStringLiteral("TouchpadCursorSpeed")
49+#define PROP_TOUCHPAD_DISABLE_WHILE_TYPING QStringLiteral("TouchpadDisableWhileTyping")
50+#define PROP_TOUCHPAD_DISABLE_WITH_MOUSE QStringLiteral("TouchpadDisableWithMouse")
51+#define PROP_TOUCHPAD_DOUBLE_CLICK_SPEED QStringLiteral("TouchpadDoubleClickSpeed")
52+#define PROP_TOUCHPAD_PRIMARY_BUTTON QStringLiteral("TouchpadPrimaryButton")
53+#define PROP_TOUCHPAD_SCROLL_SPEED QStringLiteral("TouchpadScrollSpeed")
54+#define PROP_TOUCHPAD_TAP_TO_CLICK QStringLiteral("TouchpadTapToClick")
55+#define PROP_TOUCHPAD_TWO_FINGER_SCROLL QStringLiteral("TouchpadTwoFingerScroll")
56+
57+
58+QVariant primaryButtonConverter(const QVariant &value)
59+{
60+ QString stringValue = value.toString();
61+ if (stringValue == "left") {
62+ return QVariant::fromValue(0);
63+ } else if (stringValue == "right") {
64+ return QVariant::fromValue(1); // Mir is less clear on this -- any non-zero value is the same
65+ } else {
66+ return QVariant::fromValue(0); // default to left
67+ }
68+}
69+
2570AccountsService::AccountsService(QObject* parent, const QString &user)
26− : QObject(parent),
27− m_service(new AccountsServiceDBusAdaptor(this)),
28− m_demoEdges(false),
29− m_enableLauncherWhileLocked(false),
30− m_enableIndicatorsWhileLocked(false),
31− m_statsWelcomeScreen(false),
32− m_passwordDisplayHint(Keyboard),
33− m_failedLogins(0),
34− m_hereEnabled(false),
35− m_hereLicensePath() // null means not set yet
71+ : QObject(parent)
72+ , m_service(new AccountsServiceDBusAdaptor(this))
3673{
3774 m_unityInput = new QDBusInterface(QStringLiteral("com.canonical.Unity.Input"),
3875 QStringLiteral("/com/canonical/Unity/Input"),
@@ -42,6 +79,45 @@ AccountsService::AccountsService(QObject* parent, const QString &user)
4279 connect(m_service, &AccountsServiceDBusAdaptor::propertiesChanged, this, &AccountsService::onPropertiesChanged);
4380 connect(m_service, &AccountsServiceDBusAdaptor::maybeChanged, this, &AccountsService::onMaybeChanged);
4481
82+ registerProperty(IFACE_ACCOUNTS_USER, PROP_BACKGROUND_FILE, QStringLiteral("backgroundFileChanged"));
83+ registerProperty(IFACE_ACCOUNTS_USER, PROP_EMAIL, QStringLiteral("emailChanged"));
84+ registerProperty(IFACE_ACCOUNTS_USER, PROP_REAL_NAME, QStringLiteral("realNameChanged"));
85+ registerProperty(IFACE_LOCATION_HERE, PROP_LICENSE_ACCEPTED, QStringLiteral("hereEnabledChanged"));
86+ registerProperty(IFACE_LOCATION_HERE, PROP_LICENSE_BASE_PATH, QStringLiteral("hereLicensePathChanged"));
87+ registerProperty(IFACE_UBUNTU_SECURITY, PROP_ENABLE_LAUNCHER_WHILE_LOCKED, QStringLiteral("enableLauncherWhileLockedChanged"));
88+ registerProperty(IFACE_UBUNTU_SECURITY, PROP_ENABLE_INDICATORS_WHILE_LOCKED, QStringLiteral("enableIndicatorsWhileLockedChanged"));
89+ registerProperty(IFACE_UBUNTU_SECURITY, PROP_PASSWORD_DISPLAY_HINT, QStringLiteral("passwordDisplayHintChanged"));
90+ registerProperty(IFACE_UBUNTU_SECURITY_OLD, PROP_STATS_WELCOME_SCREEN, QStringLiteral("statsWelcomeScreenChanged"));
91+ registerProperty(IFACE_UNITY, PROP_DEMO_EDGES, QStringLiteral("demoEdgesChanged"));
92+ registerProperty(IFACE_UNITY_PRIVATE, PROP_FAILED_LOGINS, QStringLiteral("failedLoginsChanged"));
93+
94+ registerProxy(IFACE_UBUNTU_INPUT, PROP_MOUSE_CURSOR_SPEED,
95+ m_unityInput, QStringLiteral("setMouseCursorSpeed"));
96+ registerProxy(IFACE_UBUNTU_INPUT, PROP_MOUSE_DOUBLE_CLICK_SPEED,
97+ m_unityInput, QStringLiteral("setMouseDoubleClickSpeed"));
98+ registerProxy(IFACE_UBUNTU_INPUT, PROP_MOUSE_PRIMARY_BUTTON,
99+ m_unityInput, QStringLiteral("setMousePrimaryButton"),
100+ primaryButtonConverter);
101+ registerProxy(IFACE_UBUNTU_INPUT, PROP_MOUSE_SCROLL_SPEED,
102+ m_unityInput, QStringLiteral("setMouseScrollSpeed"));
103+ registerProxy(IFACE_UBUNTU_INPUT, PROP_TOUCHPAD_CURSOR_SPEED,
104+ m_unityInput, QStringLiteral("setTouchpadCursorSpeed"));
105+ registerProxy(IFACE_UBUNTU_INPUT, PROP_TOUCHPAD_SCROLL_SPEED,
106+ m_unityInput, QStringLiteral("setTouchpadScrollSpeed"));
107+ registerProxy(IFACE_UBUNTU_INPUT, PROP_TOUCHPAD_DISABLE_WHILE_TYPING,
108+ m_unityInput, QStringLiteral("setTouchpadDisableWhileTyping"));
109+ registerProxy(IFACE_UBUNTU_INPUT, PROP_TOUCHPAD_DISABLE_WITH_MOUSE,
110+ m_unityInput, QStringLiteral("setTouchpadDisableWithMouse"));
111+ registerProxy(IFACE_UBUNTU_INPUT, PROP_TOUCHPAD_DOUBLE_CLICK_SPEED,
112+ m_unityInput, QStringLiteral("setTouchpadDoubleClickSpeed"));
113+ registerProxy(IFACE_UBUNTU_INPUT, PROP_TOUCHPAD_PRIMARY_BUTTON,
114+ m_unityInput, QStringLiteral("setTouchpadPrimaryButton"),
115+ primaryButtonConverter);
116+ registerProxy(IFACE_UBUNTU_INPUT, PROP_TOUCHPAD_TAP_TO_CLICK,
117+ m_unityInput, QStringLiteral("setTouchpadTapToClick"));
118+ registerProxy(IFACE_UBUNTU_INPUT, PROP_TOUCHPAD_TWO_FINGER_SCROLL,
119+ m_unityInput, QStringLiteral("setTouchpadTwoFingerScroll"));
120+
45121 setUser(!user.isEmpty() ? user : QString::fromUtf8(qgetenv("USER")));
46122}
47123
@@ -55,490 +131,222 @@ void AccountsService::setUser(const QString &user)
55131 if (user.isEmpty() || m_user == user)
56132 return;
57133
134+ bool wasEmpty = m_user.isEmpty();
135+
58136 m_user = user;
59137 Q_EMIT userChanged();
60138
61− updateDemoEdges(false);
62− updateEnableLauncherWhileLocked(false);
63− updateEnableIndicatorsWhileLocked(false);
64− updateBackgroundFile(false);
65− updateMouseCursorSpeed();
66− updateTouchpadCursorSpeed();
67− updateStatsWelcomeScreen(false);
68− updatePasswordDisplayHint(false);
69− updateFailedLogins(false);
70− updateHereEnabled(false);
71− updateHereLicensePath(false);
139+ // Do the first update synchronously, as a cheap way to block rendering
140+ // until we have the right values on bootup.
141+ refresh(!wasEmpty);
72142}
73143
74144bool AccountsService::demoEdges() const
75145{
76− return m_demoEdges;
146+ auto value = getProperty(IFACE_UNITY, PROP_DEMO_EDGES);
147+ return value.toBool();
77148}
78149
79150void AccountsService::setDemoEdges(bool demoEdges)
80151{
81− if (m_demoEdges != demoEdges) {
82− m_demoEdges = demoEdges;
83− m_service->setUserPropertyAsync(m_user, QStringLiteral("com.canonical.unity.AccountsService"), QStringLiteral("demo-edges"), demoEdges);
84−
85− Q_EMIT demoEdgesChanged();
86− }
152+ setProperty(IFACE_UNITY, PROP_DEMO_EDGES, demoEdges);
87153}
88154
89155bool AccountsService::enableLauncherWhileLocked() const
90156{
91− return m_enableLauncherWhileLocked;
157+ auto value = getProperty(IFACE_UBUNTU_SECURITY, PROP_ENABLE_LAUNCHER_WHILE_LOCKED);
158+ return value.toBool();
92159}
93160
94161bool AccountsService::enableIndicatorsWhileLocked() const
95162{
96− return m_enableIndicatorsWhileLocked;
163+ auto value = getProperty(IFACE_UBUNTU_SECURITY, PROP_ENABLE_INDICATORS_WHILE_LOCKED);
164+ return value.toBool();
97165}
98166
99167QString AccountsService::backgroundFile() const
100168{
101− return m_backgroundFile;
169+ auto value = getProperty(IFACE_ACCOUNTS_USER, PROP_BACKGROUND_FILE);
170+ return value.toString();
102171}
103172
104173bool AccountsService::statsWelcomeScreen() const
105174{
106− return m_statsWelcomeScreen;
175+ auto value = getProperty(IFACE_UBUNTU_SECURITY_OLD, PROP_STATS_WELCOME_SCREEN);
176+ return value.toBool();
107177}
108178
109179AccountsService::PasswordDisplayHint AccountsService::passwordDisplayHint() const
110180{
111− return m_passwordDisplayHint;
181+ auto value = getProperty(IFACE_UBUNTU_SECURITY, PROP_PASSWORD_DISPLAY_HINT);
182+ return (PasswordDisplayHint)value.toInt();
112183}
113184
114185bool AccountsService::hereEnabled() const
115186{
116− return m_hereEnabled;
187+ auto value = getProperty(IFACE_LOCATION_HERE, PROP_LICENSE_ACCEPTED);
188+ return value.toBool();
117189}
118190
119191void AccountsService::setHereEnabled(bool enabled)
120192{
121− if (m_hereEnabled != enabled) {
122− m_hereEnabled = enabled;
123− m_service->setUserPropertyAsync(m_user, QStringLiteral("com.ubuntu.location.providers.here.AccountsService"), QStringLiteral("LicenseAccepted"), enabled);
124−
125− Q_EMIT hereEnabledChanged();
126− }
193+ setProperty(IFACE_LOCATION_HERE, PROP_LICENSE_ACCEPTED, enabled);
127194}
128195
129196QString AccountsService::hereLicensePath() const
130197{
131− return m_hereLicensePath;
198+ auto value = getProperty(IFACE_LOCATION_HERE, PROP_LICENSE_BASE_PATH);
199+ QString hereLicensePath = value.toString();
200+ if (hereLicensePath.isEmpty() || !QFile::exists(hereLicensePath))
201+ hereLicensePath = QStringLiteral("");
202+ return hereLicensePath;
132203}
133204
134205bool AccountsService::hereLicensePathValid() const
135206{
136− return !m_hereLicensePath.isNull();
207+ auto value = getProperty(IFACE_LOCATION_HERE, PROP_LICENSE_BASE_PATH);
208+ return !value.toString().isNull();
137209}
138210
139211QString AccountsService::realName() const
140212{
141− return m_realName;
213+ auto value = getProperty(IFACE_ACCOUNTS_USER, PROP_REAL_NAME);
214+ return value.toString();
142215}
143216
144217void AccountsService::setRealName(const QString &realName)
145218{
146− if (realName != m_realName) {
147− m_realName = realName;
148− m_service->setUserPropertyAsync(m_user, "org.freedesktop.Accounts.User", "RealName", m_realName);
149− Q_EMIT realNameChanged();
150− }
219+ setProperty(IFACE_ACCOUNTS_USER, PROP_REAL_NAME, realName);
151220}
152221
153222QString AccountsService::email() const
154223{
155− return m_email;
224+ auto value = getProperty(IFACE_ACCOUNTS_USER, PROP_EMAIL);
225+ return value.toString();
156226}
157227
158228void AccountsService::setEmail(const QString &email)
159229{
160− if (email != m_email) {
161− m_email = email;
162− m_service->setUserPropertyAsync(m_user, "org.freedesktop.Accounts.User", "Email", m_email);
163− Q_EMIT emailChanged();
164− }
230+ setProperty(IFACE_ACCOUNTS_USER, PROP_EMAIL, email);
165231}
166232
167−void AccountsService::updateDemoEdges(bool async)
233+uint AccountsService::failedLogins() const
168234{
169− QDBusPendingCall pendingReply = m_service->getUserPropertyAsync(m_user,
170− QStringLiteral("com.canonical.unity.AccountsService"),
171− QStringLiteral("demo-edges"));
172− QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingReply, this);
173−
174− connect(watcher, &QDBusPendingCallWatcher::finished,
175− this, [this](QDBusPendingCallWatcher* watcher) {
176−
177− QDBusPendingReply<QVariant> reply = *watcher;
178− watcher->deleteLater();
179− if (reply.isError()) {
180− qWarning() << "Failed to get 'demo-edges' property - " << reply.error().message();
181− return;
182− }
183−
184− const bool demoEdges = reply.value().toBool();
185− if (m_demoEdges != demoEdges) {
186− m_demoEdges = demoEdges;
187− Q_EMIT demoEdgesChanged();
188− }
189− });
190− if (!async) {
191− watcher->waitForFinished();
192− delete watcher;
193− }
235+ return getProperty(IFACE_UNITY_PRIVATE, PROP_FAILED_LOGINS).toUInt();
194236}
195237
196−void AccountsService::updateEnableLauncherWhileLocked(bool async)
238+void AccountsService::setFailedLogins(uint failedLogins)
197239{
198− QDBusPendingCall pendingReply = m_service->getUserPropertyAsync(m_user,
199− QStringLiteral("com.ubuntu.AccountsService.SecurityPrivacy"),
200− QStringLiteral("EnableLauncherWhileLocked"));
201− QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingReply, this);
202−
203− connect(watcher, &QDBusPendingCallWatcher::finished,
204− this, [this](QDBusPendingCallWatcher* watcher) {
205−
206− QDBusPendingReply<QVariant> reply = *watcher;
207− watcher->deleteLater();
208− if (reply.isError()) {
209− qWarning() << "Failed to get 'EnableLauncherWhileLocked' property - " << reply.error().message();
210− return;
211− }
212−
213− const bool enableLauncherWhileLocked = reply.value().toBool();
214− if (m_enableLauncherWhileLocked != enableLauncherWhileLocked) {
215− m_enableLauncherWhileLocked = enableLauncherWhileLocked;
216− Q_EMIT enableLauncherWhileLockedChanged();
217− }
218− });
219− if (!async) {
220− watcher->waitForFinished();
221− delete watcher;
222− }
240+ setProperty(IFACE_UNITY_PRIVATE, PROP_FAILED_LOGINS, failedLogins);
223241}
224242
225−void AccountsService::updateEnableIndicatorsWhileLocked(bool async)
226−{
227− QDBusPendingCall pendingReply = m_service->getUserPropertyAsync(m_user,
228− QStringLiteral("com.ubuntu.AccountsService.SecurityPrivacy"),
229− QStringLiteral("EnableIndicatorsWhileLocked"));
230− QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingReply, this);
231−
232− connect(watcher, &QDBusPendingCallWatcher::finished,
233− this, [this](QDBusPendingCallWatcher* watcher) {
243+// ====================================================
244+// Everything below this line is generic helper methods
245+// ====================================================
234246
235− QDBusPendingReply<QVariant> reply = *watcher;
236− watcher->deleteLater();
237− if (reply.isError()) {
238− qWarning() << "Failed to get 'EnableIndicatorsWhileLocked' property - " << reply.error().message();
239− return;
240− }
241−
242− const bool enableIndicatorsWhileLocked = reply.value().toBool();
243− if (m_enableIndicatorsWhileLocked != enableIndicatorsWhileLocked) {
244− m_enableIndicatorsWhileLocked = enableIndicatorsWhileLocked;
245− Q_EMIT enableIndicatorsWhileLockedChanged();
246− }
247− });
248− if (!async) {
249− watcher->waitForFinished();
250− delete watcher;
251− }
252−}
253−
254−void AccountsService::updateBackgroundFile(bool async)
247+void AccountsService::emitChangedForProperty(const QString &interface, const QString &property)
255248{
256− QDBusPendingCall pendingReply = m_service->getUserPropertyAsync(m_user,
257− QStringLiteral("org.freedesktop.Accounts.User"),
258− QStringLiteral("BackgroundFile"));
259− QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingReply, this);
260−
261− connect(watcher, &QDBusPendingCallWatcher::finished,
262− this, [this](QDBusPendingCallWatcher* watcher) {
263−
264− QDBusPendingReply<QVariant> reply = *watcher;
265− watcher->deleteLater();
266− if (reply.isError()) {
267− qWarning() << "Failed to get 'BackgroundFile' property - " << reply.error().message();
268− return;
269− }
270−
271− const QString backgroundFile = reply.value().toString();
272− if (m_backgroundFile != backgroundFile) {
273− m_backgroundFile = backgroundFile;
274− Q_EMIT backgroundFileChanged();
275− }
276− });
277− if (!async) {
278− watcher->waitForFinished();
279− delete watcher;
280− }
249+ QString signalName = m_properties[interface][property].signal;
250+ QMetaObject::invokeMethod(this, signalName.toUtf8().data());
281251}
282252
283−void AccountsService::updateMouseCursorSpeed()
253+QVariant AccountsService::getProperty(const QString &interface, const QString &property) const
284254{
285− QDBusPendingCall pendingReply = m_service->getUserPropertyAsync(m_user,
286− QStringLiteral("com.ubuntu.AccountsService.Input"),
287− QStringLiteral("MouseCursorSpeed"));
288− QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingReply, this);
289−
290− connect(watcher, &QDBusPendingCallWatcher::finished,
291− this, [this](QDBusPendingCallWatcher* watcher) {
292−
293− QDBusPendingReply<QVariant> reply = *watcher;
294− watcher->deleteLater();
295− if (reply.isError()) {
296− qWarning() << "Failed to get 'MouseCursorSpeed' property - " << reply.error().message();
297− return;
298− }
299−
300− // Merely proxy this along to USC. We don't care about keeping a copy
301− // or exporting it internally.
302− m_unityInput->asyncCall(QStringLiteral("setMouseCursorSpeed"), reply.value());
303− });
255+ return m_properties[interface][property].value;
304256}
305257
306−void AccountsService::updateTouchpadCursorSpeed()
258+void AccountsService::setProperty(const QString &interface, const QString &property, const QVariant &value)
307259{
308− QDBusPendingCall pendingReply = m_service->getUserPropertyAsync(m_user,
309− QStringLiteral("com.ubuntu.AccountsService.Input"),
310− QStringLiteral("TouchpadCursorSpeed"));
311− QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingReply, this);
312−
313− connect(watcher, &QDBusPendingCallWatcher::finished,
314− this, [this](QDBusPendingCallWatcher* watcher) {
315−
316− QDBusPendingReply<QVariant> reply = *watcher;
317− watcher->deleteLater();
318− if (reply.isError()) {
319− qWarning() << "Failed to get 'TouchpadCursorSpeed' property - " << reply.error().message();
320− return;
321− }
322−
323− // Merely proxy this along to USC. We don't care about keeping a copy
324− // or exporting it internally.
325− m_unityInput->asyncCall(QStringLiteral("setTouchpadCursorSpeed"), reply.value());
326− });
327−}
328−
329−void AccountsService::updateStatsWelcomeScreen(bool async)
330−{
331− QDBusPendingCall pendingReply = m_service->getUserPropertyAsync(m_user,
332− QStringLiteral("com.ubuntu.touch.AccountsService.SecurityPrivacy"),
333− QStringLiteral("StatsWelcomeScreen"));
334− QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingReply, this);
335−
336− connect(watcher, &QDBusPendingCallWatcher::finished,
337− this, [this](QDBusPendingCallWatcher* watcher) {
338−
339− QDBusPendingReply<QVariant> reply = *watcher;
340− watcher->deleteLater();
341− if (reply.isError()) {
342− qWarning() << "Failed to get 'StatsWelcomeScreen' property - " << reply.error().message();
343− return;
344− }
345−
346− const bool statsWelcomeScreen = reply.value().toBool();
347− if (m_statsWelcomeScreen != statsWelcomeScreen) {
348− m_statsWelcomeScreen = statsWelcomeScreen;
349− Q_EMIT statsWelcomeScreenChanged();
350− }
351− });
352− if (!async) {
353− watcher->waitForFinished();
354− delete watcher;
260+ if (m_properties[interface][property].value != value) {
261+ m_properties[interface][property].value = value;
262+ m_service->setUserPropertyAsync(m_user, interface, property, value);
263+ emitChangedForProperty(interface, property);
355264 }
356265}
357266
358−void AccountsService::updatePasswordDisplayHint(bool async)
267+void AccountsService::updateCache(const QString &interface, const QString &property, const QVariant &value)
359268{
360− QDBusPendingCall pendingReply = m_service->getUserPropertyAsync(m_user,
361− QStringLiteral("com.ubuntu.AccountsService.SecurityPrivacy"),
362− QStringLiteral("PasswordDisplayHint"));
363− QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingReply, this);
364−
365− connect(watcher, &QDBusPendingCallWatcher::finished,
366− this, [this](QDBusPendingCallWatcher* watcher) {
367−
368− QDBusPendingReply<QVariant> reply = *watcher;
369− watcher->deleteLater();
370− if (reply.isError()) {
371− qWarning() << "Failed to get 'PasswordDisplayHint' property - " << reply.error().message();
372− return;
373− }
269+ PropertyInfo &info = m_properties[interface][property];
374270
375− const PasswordDisplayHint passwordDisplayHint = (PasswordDisplayHint)reply.value().toInt();
376− if (m_passwordDisplayHint != passwordDisplayHint) {
377− m_passwordDisplayHint = passwordDisplayHint;
378− Q_EMIT passwordDisplayHintChanged();
271+ if (info.proxyInterface) {
272+ QVariant finalValue;
273+ if (info.proxyConverter) {
274+ finalValue = info.proxyConverter(value);
275+ } else {
276+ finalValue = value;
379277 }
380− });
381− if (!async) {
382− watcher->waitForFinished();
383− delete watcher;
278+ info.proxyInterface->asyncCall(info.proxyMethod, finalValue);
279+ return; // don't bother saving a copy
384280 }
385−}
386−
387−void AccountsService::updateFailedLogins(bool async)
388−{
389− QDBusPendingCall pendingReply = m_service->getUserPropertyAsync(m_user,
390− QStringLiteral("com.canonical.unity.AccountsService.Private"),
391− QStringLiteral("FailedLogins"));
392− QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingReply, this);
393−
394− connect(watcher, &QDBusPendingCallWatcher::finished,
395− this, [this](QDBusPendingCallWatcher* watcher) {
396−
397− QDBusPendingReply<QVariant> reply = *watcher;
398− watcher->deleteLater();
399− if (reply.isError()) {
400− qWarning() << "Failed to get 'FailedLogins' property - " << reply.error().message();
401− return;
402− }
403281
404− const uint failedLogins = reply.value().toUInt();
405− if (m_failedLogins != failedLogins) {
406− m_failedLogins = failedLogins;
407− Q_EMIT failedLoginsChanged();
408− }
409− });
410− if (!async) {
411− watcher->waitForFinished();
412− delete watcher;
282+ if (info.value != value) {
283+ info.value = value;
284+ emitChangedForProperty(interface, property);
413285 }
414286}
415287
416−void AccountsService::updateHereEnabled(bool async)
288+void AccountsService::updateProperty(const QString &interface, const QString &property)
417289{
418290 QDBusPendingCall pendingReply = m_service->getUserPropertyAsync(m_user,
419− QStringLiteral("com.ubuntu.location.providers.here.AccountsService"),
420− QStringLiteral("LicenseAccepted"));
291+ interface,
292+ property);
421293 QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingReply, this);
422294
423295 connect(watcher, &QDBusPendingCallWatcher::finished,
424− this, [this](QDBusPendingCallWatcher* watcher) {
296+ this, [this, interface, property](QDBusPendingCallWatcher* watcher) {
425297
426298 QDBusPendingReply<QVariant> reply = *watcher;
427299 watcher->deleteLater();
428300 if (reply.isError()) {
429− qWarning() << "Failed to get 'LicenseAccepted' property - " << reply.error().message();
301+ qWarning() << "Failed to get '" << property << "' property:" << reply.error().message();
430302 return;
431303 }
432304
433− const bool hereEnabled = reply.value().toBool();
434− if (m_hereEnabled != hereEnabled) {
435− m_hereEnabled = hereEnabled;
436− Q_EMIT hereEnabledChanged();
437− }
305+ updateCache(interface, property, reply.value());
438306 });
439− if (!async) {
440− watcher->waitForFinished();
441− delete watcher;
442− }
443307}
444308
445−void AccountsService::updateHereLicensePath(bool async)
309+void AccountsService::updateAllProperties(const QString &interface, bool async)
446310{
447− QDBusPendingCall pendingReply = m_service->getUserPropertyAsync(m_user,
448− QStringLiteral("com.ubuntu.location.providers.here.AccountsService"),
449− QStringLiteral("LicenseBasePath"));
311+ QDBusPendingCall pendingReply = m_service->getAllPropertiesAsync(m_user,
312+ interface);
450313 QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingReply, this);
451314
452315 connect(watcher, &QDBusPendingCallWatcher::finished,
453− this, [this](QDBusPendingCallWatcher* watcher) {
454−
455− QDBusPendingReply<QVariant> reply = *watcher;
456− watcher->deleteLater();
457− if (reply.isError()) {
458− qWarning() << "Failed to get 'LicenseBasePath' property - " << reply.error().message();
459− return;
460− }
461−
462− QString hereLicensePath = reply.value().toString();
463− if (hereLicensePath.isEmpty() || !QFile::exists(hereLicensePath))
464− hereLicensePath = QLatin1String("");
465−
466− if (m_hereLicensePath.isNull() || m_hereLicensePath != hereLicensePath) {
467− m_hereLicensePath = hereLicensePath;
468− Q_EMIT hereLicensePathChanged();
469− }
470− });
471− if (!async) {
472− watcher->waitForFinished();
473− delete watcher;
474− }
475−}
316+ this, [this, interface](QDBusPendingCallWatcher* watcher) {
476317
477−void AccountsService::updateRealName(bool async)
478−{
479− QDBusPendingCall pendingReply = m_service->getUserPropertyAsync(m_user, "org.freedesktop.Accounts.User", "RealName");
480− QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingReply, this);
481−
482− connect(watcher, &QDBusPendingCallWatcher::finished,
483− this, [this](QDBusPendingCallWatcher* watcher) {
484− QDBusPendingReply<QVariant> reply = *watcher;
318+ QDBusPendingReply< QHash<QString, QVariant> > reply = *watcher;
485319 watcher->deleteLater();
486320 if (reply.isError()) {
487− qWarning() << "Failed to get 'RealName' property - " << reply.error().message();
321+ qWarning() << "Failed to get all properties for" << interface << ":" << reply.error().message();
488322 return;
489323 }
490324
491− const QString realName = reply.value().toString();
492− if (m_realName != realName) {
493− m_realName = realName;
494− Q_EMIT realNameChanged();
325+ auto valueHash = reply.value();
326+ auto i = valueHash.constBegin();
327+ while (i != valueHash.constEnd()) {
328+ updateCache(interface, i.key(), i.value());
329+ ++i;
495330 }
496331 });
497332 if (!async) {
498333 watcher->waitForFinished();
499− delete watcher;
500334 }
501335}
502336
503−void AccountsService::updateEmail(bool async)
337+void AccountsService::registerProxy(const QString &interface, const QString &property, QDBusInterface *iface, const QString &method, ProxyConverter converter)
504338{
505− QDBusPendingCall pendingReply = m_service->getUserPropertyAsync(m_user, "org.freedesktop.Accounts.User", "Email");
506− QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingReply, this);
507−
508− connect(watcher, &QDBusPendingCallWatcher::finished,
509− this, [this](QDBusPendingCallWatcher* watcher) {
510− QDBusPendingReply<QVariant> reply = *watcher;
511− watcher->deleteLater();
512− if (reply.isError()) {
513− qWarning() << "Failed to get 'Email' property - " << reply.error().message();
514− return;
515− }
339+ registerProperty(interface, property, nullptr);
516340
517− const QString email = reply.value().toString();
518− if (m_email != email) {
519− m_email = email;
520− Q_EMIT emailChanged();
521− }
522− });
523− if (!async) {
524− watcher->waitForFinished();
525− delete watcher;
526− }
341+ m_properties[interface][property].proxyInterface = iface;
342+ m_properties[interface][property].proxyMethod = method;
343+ m_properties[interface][property].proxyConverter = converter;
527344}
528345
529−uint AccountsService::failedLogins() const
346+void AccountsService::registerProperty(const QString &interface, const QString &property, const QString &signal)
530347{
531− return m_failedLogins;
532−}
533−
534−void AccountsService::setFailedLogins(uint failedLogins)
535−{
536− if (m_failedLogins != failedLogins) {
537− m_failedLogins = failedLogins;
538− m_service->setUserPropertyAsync(m_user, QStringLiteral("com.canonical.unity.AccountsService.Private"), QStringLiteral("FailedLogins"), failedLogins);
539−
540− Q_EMIT failedLoginsChanged();
541− }
348+ m_properties[interface][property] = PropertyInfo();
349+ m_properties[interface][property].signal = signal;
542350}
543351
544352void AccountsService::onPropertiesChanged(const QString &user, const QString &interface, const QStringList &changed)
@@ -547,42 +355,13 @@ void AccountsService::onPropertiesChanged(const QString &user, const QString &in
547355 return;
548356 }
549357
550− if (interface == QLatin1String("com.canonical.unity.AccountsService")) {
551− if (changed.contains(QStringLiteral("demo-edges"))) {
552− updateDemoEdges();
553− }
554− } else if (interface == QLatin1String("com.canonical.unity.AccountsService.Private")) {
555− if (changed.contains(QStringLiteral("FailedLogins"))) {
556− updateFailedLogins();
557− }
558− } else if (interface == QLatin1String("com.ubuntu.AccountsService.Input")) {
559− if (changed.contains(QStringLiteral("MouseCursorSpeed"))) {
560− updateMouseCursorSpeed();
561− }
562− if (changed.contains(QStringLiteral("TouchpadCursorSpeed"))) {
563− updateTouchpadCursorSpeed();
564− }
565− } else if (interface == QLatin1String("com.ubuntu.touch.AccountsService.SecurityPrivacy")) {
566− if (changed.contains(QStringLiteral("StatsWelcomeScreen"))) {
567− updateStatsWelcomeScreen();
568− }
569− } else if (interface == QLatin1String("com.ubuntu.AccountsService.SecurityPrivacy")) {
570− if (changed.contains(QStringLiteral("PasswordDisplayHint"))) {
571− updatePasswordDisplayHint();
572− }
573− if (changed.contains(QStringLiteral("EnableLauncherWhileLocked"))) {
574− updateEnableLauncherWhileLocked();
575− }
576− if (changed.contains(QStringLiteral("EnableIndicatorsWhileLocked"))) {
577− updateEnableIndicatorsWhileLocked();
578− }
579− } else if (interface == QLatin1String("com.ubuntu.location.providers.here.AccountsService")) {
580− if (changed.contains(QStringLiteral("LicenseAccepted"))) {
581− updateHereEnabled();
582− }
583− if (changed.contains(QStringLiteral("LicenseBasePath"))) {
584− updateHereLicensePath();
358+ auto propHash = m_properties.value(interface);
359+ auto i = propHash.constBegin();
360+ while (i != propHash.constEnd()) {
361+ if (changed.contains(i.key())) {
362+ updateProperty(interface, i.key());
585363 }
364+ ++i;
586365 }
587366}
588367
@@ -592,8 +371,20 @@ void AccountsService::onMaybeChanged(const QString &user)
592371 return;
593372 }
594373
595− // Standard properties might have changed
596− updateBackgroundFile();
597− updateRealName();
598− updateEmail();
374+ // Any of the standard properties might have changed!
375+ auto propHash = m_properties.value(IFACE_ACCOUNTS_USER);
376+ auto i = propHash.constBegin();
377+ while (i != propHash.constEnd()) {
378+ updateProperty(IFACE_ACCOUNTS_USER, i.key());
379+ ++i;
380+ }
381+}
382+
383+void AccountsService::refresh(bool async)
384+{
385+ auto i = m_properties.constBegin();
386+ while (i != m_properties.constEnd()) {
387+ updateAllProperties(i.key(), async);
388+ ++i;
389+ }
599390}
plugins/AccountsService/AccountsService.h+25−24
@@ -17,8 +17,10 @@
1717#ifndef UNITY_ACCOUNTSSERVICE_H
1818#define UNITY_ACCOUNTSSERVICE_H
1919
20+#include <QHash>
2021#include <QObject>
2122#include <QString>
23+#include <QVariant>
2224
2325class AccountsServiceDBusAdaptor;
2426class QDBusInterface;
@@ -115,34 +117,33 @@ private Q_SLOTS:
115117 void onMaybeChanged(const QString &user);
116118
117119private:
118− void updateDemoEdges(bool async = true);
119− void updateEnableLauncherWhileLocked(bool async = true);
120− void updateEnableIndicatorsWhileLocked(bool async = true);
121− void updateBackgroundFile(bool async = true);
122− void updateMouseCursorSpeed();
123− void updateTouchpadCursorSpeed();
124− void updateStatsWelcomeScreen(bool async = true);
125− void updatePasswordDisplayHint(bool async = true);
126− void updateFailedLogins(bool async = true);
127− void updateHereEnabled(bool async = true);
128− void updateHereLicensePath(bool async = true);
129− void updateRealName(bool async = true);
130− void updateEmail(bool async = true);
120+ typedef QVariant (*ProxyConverter)(const QVariant &);
131121
122+ void refresh(bool async);
123+ void registerProperty(const QString &interface, const QString &property, const QString &signal);
124+ void registerProxy(const QString &interface, const QString &property, QDBusInterface *iface, const QString &method, ProxyConverter converter = nullptr);
125+
126+ void updateAllProperties(const QString &interface, bool async);
127+ void updateProperty(const QString &interface, const QString &property);
128+ void updateCache(const QString &interface, const QString &property, const QVariant &value);
129+
130+ void setProperty(const QString &interface, const QString &property, const QVariant &value);
131+ QVariant getProperty(const QString &interface, const QString &property) const;
132+
133+ void emitChangedForProperty(const QString &interface, const QString &property);
134+
135+ struct PropertyInfo {
136+ QVariant value{};
137+ QString signal{};
138+ QDBusInterface *proxyInterface{};
139+ QString proxyMethod{};
140+ ProxyConverter proxyConverter{};
141+ };
142+ typedef QHash< QString, QHash<QString, PropertyInfo> > PropertyHash;
143+ PropertyHash m_properties;
132144 AccountsServiceDBusAdaptor *m_service;
133145 QDBusInterface *m_unityInput;
134146 QString m_user;
135− bool m_demoEdges;
136− bool m_enableLauncherWhileLocked;
137− bool m_enableIndicatorsWhileLocked;
138− QString m_backgroundFile;
139− bool m_statsWelcomeScreen;
140− PasswordDisplayHint m_passwordDisplayHint;
141− uint m_failedLogins;
142− bool m_hereEnabled;
143− QString m_hereLicensePath;
144− QString m_realName;
145− QString m_email;
146147};
147148
148149#endif
plugins/AccountsService/AccountsServiceDBusAdaptor.cpp+11−4
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013-2016 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -12,8 +12,6 @@
1212 *
1313 * You should have received a copy of the GNU General Public License
1414 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− *
16− * Author: Michael Terry <michael.terry@canonical.com>
1715 */
1816
1917#include "AccountsServiceDBusAdaptor.h"
@@ -37,6 +35,15 @@ AccountsServiceDBusAdaptor::AccountsServiceDBusAdaptor(QObject* parent)
3735 connection, this);
3836}
3937
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+
4047QDBusPendingReply<QVariant> AccountsServiceDBusAdaptor::getUserPropertyAsync(const QString &user, const QString &interface, const QString &property)
4148{
4249 QDBusInterface *iface = getUserInterface(user);
@@ -80,7 +87,7 @@ void AccountsServiceDBusAdaptor::maybeChangedSlot()
8087 m_ignoreNextChanged = false;
8188}
8289
83−QString AccountsServiceDBusAdaptor::getUserForPath(const QString &path)
90+QString AccountsServiceDBusAdaptor::getUserForPath(const QString &path) const
8491{
8592 QMap<QString, QDBusInterface *>::const_iterator i;
8693 for (i = m_users.constBegin(); i != m_users.constEnd(); ++i) {
plugins/AccountsService/AccountsServiceDBusAdaptor.h+5−6
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013-2016 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -12,8 +12,6 @@
1212 *
1313 * You should have received a copy of the GNU General Public License
1414 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− *
16− * Authors: Michael Terry <michael.terry@canonical.com>
1715 */
1816
1917#ifndef UNITY_ACCOUNTSSERVICEDBUSADAPTOR_H
@@ -35,8 +33,9 @@ public:
3533 explicit AccountsServiceDBusAdaptor(QObject *parent = 0);
3634 ~AccountsServiceDBusAdaptor() = default;
3735
38− Q_INVOKABLE 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);
36+ QDBusPendingReply<QVariantMap> getAllPropertiesAsync(const QString &user, const QString &interface);
37+ QDBusPendingReply<QVariant> getUserPropertyAsync(const QString &user, const QString &interface, const QString &property);
38+ QDBusPendingCall setUserPropertyAsync(const QString &user, const QString &interface, const QString &property, const QVariant &value);
4039
4140Q_SIGNALS:
4241 void propertiesChanged(const QString &user, const QString &interface, const QStringList &changed);
@@ -48,7 +47,7 @@ private Q_SLOTS:
4847
4948private:
5049 QDBusInterface *getUserInterface(const QString &user);
51− QString getUserForPath(const QString &path);
50+ QString getUserForPath(const QString &path) const;
5251
5352 QDBusInterface *m_accountsManager;
5453 QMap<QString, QDBusInterface *> m_users;
plugins/Dash/AudioProgressBar.qml+1
@@ -31,6 +31,7 @@ Item {
3131 anchors { left: parent.left; right: parent.right }
3232 height: units.dp(6)
3333 source: "graphics/music_progress_bg.png"
34+ asynchronous: true
3435 sourceSize.width: width
3536 sourceSize.height: height
3637 }
plugins/Dash/CardCreator.js+69−34
@@ -18,12 +18,13 @@
1818
1919// %1 is the template["card-background"]["elements"][0]
2020// %2 is the template["card-background"]["elements"][1]
21−// %3 is the template["card-background"] string
21+// %3 is whether the loader should be asynchronous or not
22+// %4 is the template["card-background"] string
2223var kBackgroundLoaderCode = 'Loader {\n\
2324 id: backgroundLoader; \n\
2425 objectName: "backgroundLoader"; \n\
2526 anchors.fill: parent; \n\
26− asynchronous: root.asynchronous; \n\
27+ asynchronous: %3; \n\
2728 visible: status == Loader.Ready; \n\
2829 sourceComponent: UbuntuShape { \n\
2930 objectName: "background"; \n\
@@ -46,7 +47,7 @@ var kBackgroundLoaderCode = 'Loader {\n\
4647 objectName: "backgroundImage"; \n\
4748 source: { \n\
4849 if (cardData && typeof cardData["background"] === "string") return cardData["background"]; \n\
49− else return %3; \n\
50+ else return %4; \n\
5051 } \n\
5152 } \n\
5253 function getColor(index) { \n\
@@ -63,8 +64,9 @@ var kBackgroundLoaderCode = 'Loader {\n\
6364// %3 is used as image height
6465// %4 is used for artShapeSource.hideSource and inner Loader visible
6566// %5 is used as aspect ratio fallback
66−// %6 is injected as code to artImage
67−// %7 is used as image fallback
67+// %6 is whether the loader should be asynchronous or not
68+// %7 is injected as code to artImage
69+// %8 is used as image fallback
6870var kArtShapeHolderCode = 'Item { \n\
6971 id: artShapeHolder; \n\
7072 height: root.fixedArtShapeSize.height > 0 ? root.fixedArtShapeSize.height : artShapeLoader.height; \n\
@@ -73,9 +75,9 @@ var kArtShapeHolderCode = 'Item { \n\
7375 Loader { \n\
7476 id: artShapeLoader; \n\
7577 objectName: "artShapeLoader"; \n\
76− readonly property string cardArt: cardData && cardData["art"] || %7; \n\
78+ readonly property string cardArt: cardData && cardData["art"] || %8; \n\
7779 active: cardArt != ""; \n\
78− asynchronous: root.asynchronous; \n\
80+ asynchronous: %6; \n\
7981 visible: status == Loader.Ready; \n\
8082 sourceComponent: Item { \n\
8183 id: artShape; \n\
@@ -132,10 +134,10 @@ var kArtShapeHolderCode = 'Item { \n\
132134 id: artImage; \n\
133135 objectName: "artImage"; \n\
134136 source: artShapeLoader.cardArt; \n\
135− asynchronous: root.asynchronous; \n\
137+ asynchronous: %6; \n\
136138 width: %2; \n\
137139 height: %3; \n\
138− %6 \n\
140+ %7 \n\
139141 } \n\
140142 } \n\
141143 } \n\
@@ -144,6 +146,7 @@ var kArtShapeHolderCode = 'Item { \n\
144146// %1 is anchors.fill
145147// %2 is width
146148// %3 is height
149+// %4 is whether the icon should be asynchronous or not
147150var kAudioButtonCode = 'AbstractButton { \n\
148151 id: audioButton; \n\
149152 anchors.fill: %1; \n\
@@ -169,6 +172,7 @@ var kAudioButtonCode = 'AbstractButton { \n\
169172 opacity: 0.9; \n\
170173 name: DashAudioPlayer.playing && AudioUrlComparer.compare(parent.source, DashAudioPlayer.currentSource) ? "media-playback-pause" : "media-playback-start"; \n\
171174 color: "white"; \n\
175+ asynchronous: %4; \n\
172176 } \n\
173177 onClicked: { \n\
174178 if (AudioUrlComparer.compare(source, DashAudioPlayer.currentSource)) { \n\
@@ -187,12 +191,14 @@ var kAudioButtonCode = 'AbstractButton { \n\
187191 } \n\
188192 }';
189193
194+// %1 is whether the loader should be asynchronous or not
195+// %2 is the header height code
190196var kOverlayLoaderCode = 'Loader { \n\
191197 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\
193199 anchors.fill: artShapeHolder; \n\
194200 active: artShapeLoader.active && artShapeLoader.item && artShapeLoader.item.image.status === Image.Ready || false; \n\
195− asynchronous: root.asynchronous; \n\
201+ asynchronous: %1; \n\
196202 visible: showHeader && status == Loader.Ready; \n\
197203 sourceComponent: UbuntuShapeOverlay { \n\
198204 id: overlay; \n\
@@ -211,17 +217,19 @@ function kHeaderRowCodeGenerator() {
211217 objectName: "outerRow"; \n\
212218 property real margins: units.gu(1); \n\
213219 spacing: margins; \n\
214− height: root.fixedHeaderHeight != -1 ? root.fixedHeaderHeight : implicitHeight; \n\
220+ %2\
215221 anchors { %1 } \n\
216222 anchors.right: parent.right; \n\
217223 anchors.margins: margins; \n\
218224 anchors.rightMargin: 0; \n\
219225 data: [ \n\
220− %2 \n\
226+ %3 \n\
221227 ] \n\
222228 }\n';
223229 var args = Array.prototype.slice.call(arguments);
224− var code = kHeaderRowCodeTemplate.arg(args.shift()).arg(args.join(',\n'));
230+ var isCardTool = args.shift();
231+ var heightCode = isCardTool ? "" : "height: root.fixedHeaderHeight; \n";
232+ var code = kHeaderRowCodeTemplate.arg(args.shift()).arg(heightCode).arg(args.join(',\n'));
225233 return code;
226234}
227235
@@ -242,10 +250,11 @@ function kHeaderContainerCodeGenerator() {
242250}
243251
244252// %1 is used as anchors of mascotShapeLoader
253+// %2 is whether the loader should be asynchronous or not
245254var kMascotShapeLoaderCode = 'Loader { \n\
246255 id: mascotShapeLoader; \n\
247256 objectName: "mascotShapeLoader"; \n\
248− asynchronous: root.asynchronous; \n\
257+ asynchronous: %2; \n\
249258 active: mascotImage.status === Image.Ready; \n\
250259 visible: showHeader && active && status == Loader.Ready; \n\
251260 width: units.gu(6); \n\
@@ -275,6 +284,7 @@ var kMascotImageCode = 'CroppedImageMinimumSourceSize { \n\
275284// %2 is used as color of titleLabel
276285// %3 is used as extra condition for visible of titleLabel
277286// %4 is used as title width
287+// %5 is used as horizontal alignment
278288var kTitleLabelCode = 'Label { \n\
279289 id: titleLabel; \n\
280290 objectName: "titleLabel"; \n\
@@ -289,7 +299,7 @@ var kTitleLabelCode = 'Label { \n\
289299 width: %4; \n\
290300 text: root.title; \n\
291301 font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal; \n\
292− horizontalAlignment: root.titleAlignment; \n\
302+ horizontalAlignment: %5; \n\
293303 }\n';
294304
295305// %1 is used as extra anchors of emblemIcon
@@ -398,7 +408,7 @@ function sanitizeColor(colorString) {
398408 return colorString;
399409}
400410
401−function cardString(template, components) {
411+function cardString(template, components, isCardTool) {
402412 var code;
403413
404414 var templateInteractive = (template == null ? true : (template["non-interactive"] !== undefined ? !template["non-interactive"] : true)) ? "true" : "false";
@@ -410,16 +420,16 @@ function cardString(template, components) {
410420 property string backgroundShapeStyle: "inset"; \n\
411421 property real fontScale: 1.0; \n\
412422 property var scopeStyle: null; \n\
413− property int titleAlignment: Text.AlignLeft; \n\
414− property int fixedHeaderHeight: -1; \n\
423+ %2\
415424 property size fixedArtShapeSize: Qt.size(-1, -1); \n\
416425 readonly property string title: cardData && cardData["title"] || ""; \n\
417− property bool asynchronous: true; \n\
418426 property bool showHeader: true; \n\
419427 implicitWidth: childrenRect.width; \n\
420428 enabled: %1; \n\
421429 \n'.arg(templateInteractive);
422430
431+ code = code.arg(isCardTool ? "" : "property int fixedHeaderHeight: -1; \n");
432+
423433 var hasArt = components["art"] && components["art"]["field"] || false;
424434 var hasSummary = components["summary"] || false;
425435 var isConciergeMode = components["art"] && components["art"]["conciergeMode"] || false;
@@ -435,6 +445,7 @@ function cardString(template, components) {
435445 var hasHeaderRow = hasMascot && hasTitle;
436446 var hasAttributes = hasTitle && components["attributes"] && components["attributes"]["field"] || false;
437447 var isAudio = template["quick-preview-type"] === "audio";
448+ var asynchronous = isCardTool ? "false" : "true";
438449
439450 if (isAudio) {
440451 // For now we only support audio cards with [optional] art, title, subtitle
@@ -468,7 +479,7 @@ function cardString(template, components) {
468479 backgroundElements1 = '"%1"'.arg(element1);
469480 }
470481 }
471− code += kBackgroundLoaderCode.arg(backgroundElements0).arg(backgroundElements1).arg(templateCardBackground);
482+ code += kBackgroundLoaderCode.arg(backgroundElements0).arg(backgroundElements1).arg(asynchronous).arg(templateCardBackground);
472483 }
473484
474485 if (hasArt) {
@@ -497,23 +508,31 @@ function cardString(template, components) {
497508 if (isNaN(aspectRatio)) {
498509 aspectRatio = 1;
499510 }
500− var fallback = components["art"] && components["art"]["fallback"] || "";
511+ var fallback = !isCardTool && components["art"] && components["art"]["fallback"] || "";
501512 fallback = encodeURI(fallback);
502513 var fallbackStatusCode = "";
503514 var fallbackURICode = '""';
504515 if (fallback !== "") {
505516 // fallbackStatusCode has %6 in it because we want to substitute it for fallbackURICode
506− // which in kArtShapeHolderCode is %7
507− fallbackStatusCode += 'onStatusChanged: if (status === Image.Error) source = %7;';
517+ // which in kArtShapeHolderCode is %8
518+ fallbackStatusCode += 'onStatusChanged: if (status === Image.Error) source = %8;';
508519 fallbackURICode = 'decodeURI("%1")'.arg(fallback);
509520 }
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);
511529 } else {
512530 code += 'readonly property size artShapeSize: Qt.size(-1, -1);\n'
513531 }
514532
515533 if (headerAsOverlay) {
516− code += kOverlayLoaderCode;
534+ var headerHeightCode = isCardTool ? "headerHeight" : "root.fixedHeaderHeight";
535+ code += kOverlayLoaderCode.arg(asynchronous).arg(headerHeightCode);
517536 }
518537
519538 var headerVerticalAnchors;
@@ -595,11 +614,11 @@ function cardString(template, components) {
595614 }
596615
597616 if (useMascotShape) {
598− mascotShapeCode = kMascotShapeLoaderCode.arg(mascotAnchors);
617+ mascotShapeCode = kMascotShapeLoaderCode.arg(mascotAnchors).arg(asynchronous);
599618 }
600619
601620 var mascotImageVisible = useMascotShape ? 'false' : 'showHeader';
602− var fallback = components["mascot"] && components["mascot"]["fallback"] || "";
621+ var fallback = !isCardTool && components["mascot"] && components["mascot"]["fallback"] || "";
603622 fallback = encodeURI(fallback);
604623 var fallbackStatusCode = "";
605624 var fallbackURICode = '""';
@@ -715,9 +734,24 @@ function cardString(template, components) {
715734 }
716735 }
717736
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+
718752 // code for different elements
719753 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);
721755 var subtitleCode;
722756 var attributesCode;
723757
@@ -759,7 +793,7 @@ function cardString(template, components) {
759793 if (mascotShapeCode != '') {
760794 rowCode.unshift(mascotShapeCode);
761795 }
762− code += kHeaderRowCodeGenerator(headerVerticalAnchors + headerLeftAnchor, rowCode)
796+ code += kHeaderRowCodeGenerator(isCardTool, headerVerticalAnchors + headerLeftAnchor, rowCode)
763797 } else {
764798 code += mascotShapeCode + mascotCode + titleSubtitleCode;
765799 }
@@ -783,9 +817,10 @@ function cardString(template, components) {
783817 } else {
784818 audioButtonAnchorsFill = 'undefined';
785819 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)';
787822 }
788− code += kAudioButtonCode.arg(audioButtonAnchorsFill).arg(audioButtonWidth).arg(audioButtonHeight);
823+ code += kAudioButtonCode.arg(audioButtonAnchorsFill).arg(audioButtonWidth).arg(audioButtonHeight).arg(asynchronous);
789824 }
790825
791826 if (hasSummary) {
@@ -854,13 +889,13 @@ function cardString(template, components) {
854889 return code;
855890}
856891
857−function createCardComponent(parent, template, components, identifier) {
892+function createCardComponent(parent, template, components, isCardTool, identifier) {
858893 var imports = 'import QtQuick 2.4; \n\
859894 import Ubuntu.Components 1.3; \n\
860895 import Ubuntu.Settings.Components 0.1; \n\
861896 import Dash 0.1;\n\
862897 import Utils 0.1;\n';
863− var card = cardString(template, components);
898+ var card = cardString(template, components, isCardTool);
864899 var code = imports + 'Component {\n' + card + '}\n';
865900
866901 try {
plugins/Dash/CardCreatorCache.qml+3−3
@@ -23,16 +23,16 @@ QtObject {
2323
2424 property var cache: new Object();
2525
26− function getCardComponent(template, components) {
26+ function getCardComponent(template, components, isCardTool) {
2727 if (template === undefined || components === undefined)
2828 return undefined;
2929
3030 var tString = JSON.stringify(template);
3131 var cString = JSON.stringify(components);
32− var allString = tString + cString;
32+ var allString = tString + cString + isCardTool;
3333 var component = cache[allString];
3434 if (component === undefined) {
35− component = CardCreator.createCardComponent(root, template, components, allString);
35+ component = CardCreator.createCardComponent(root, template, components, isCardTool, allString);
3636 cache[allString] = component;
3737 }
3838 return component;
plugins/Dash/plugin.cpp+6−6
@@ -28,12 +28,12 @@
2828static QUrl oauthCleanedUrl(QUrl u)
2929{
3030 QUrlQuery q(u);
31− q.removeQueryItem("oauth_nonce");
32− q.removeQueryItem("oauth_timestamp");
33− q.removeQueryItem("oauth_consumer_key");
34− q.removeQueryItem("oauth_signature_method");
35− q.removeQueryItem("oauth_version");
36− q.removeQueryItem("oauth_signature");
31+ q.removeQueryItem(QStringLiteral("oauth_nonce"));
32+ q.removeQueryItem(QStringLiteral("oauth_timestamp"));
33+ q.removeQueryItem(QStringLiteral("oauth_consumer_key"));
34+ q.removeQueryItem(QStringLiteral("oauth_signature_method"));
35+ q.removeQueryItem(QStringLiteral("oauth_version"));
36+ q.removeQueryItem(QStringLiteral("oauth_signature"));
3737 u.setQuery(q);
3838 return u;
3939}
plugins/GlobalShortcut/globalshortcutregistry.cpp+4−4
@@ -35,13 +35,13 @@ GlobalShortcutList GlobalShortcutRegistry::shortcuts() const
3535
3636bool GlobalShortcutRegistry::hasShortcut(const QVariant &seq) const
3737{
38− return m_shortcuts.keys().contains(seq);
38+ return m_shortcuts.contains(seq);
3939}
4040
4141void GlobalShortcutRegistry::addShortcut(const QVariant &seq, GlobalShortcut *sc)
4242{
4343 if (sc) {
44− if (!m_shortcuts.keys().contains(seq)) { // create a new entry
44+ if (!m_shortcuts.contains(seq)) { // create a new entry
4545 m_shortcuts.insert(seq, {sc});
4646 } else { // append to an existing one
4747 auto shortcuts = m_shortcuts[seq];
@@ -75,8 +75,8 @@ bool GlobalShortcutRegistry::eventFilter(QObject *obj, QEvent *event)
7575
7676 if (event->type() == QEvent::KeyPress) {
7777 QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
78− QKeySequence seq = QKeySequence(keyEvent->key() + keyEvent->modifiers());
79− if (m_shortcuts.keys().contains(seq)) {
78+ int seq = keyEvent->key() + keyEvent->modifiers();
79+ if (m_shortcuts.contains(seq)) {
8080 const auto shortcuts = m_shortcuts.value(seq);
8181 Q_FOREACH(const auto &shortcut, shortcuts) {
8282 if (shortcut) {
plugins/IntegratedLightDM/liblightdm/CMakeLists.txt+16−3
@@ -5,13 +5,16 @@ set(LibLightDM_SOURCES
55 UsersModel.cpp
66 GreeterPrivate.cpp
77 UsersModelPrivate.cpp
8+ ${CMAKE_SOURCE_DIR}/plugins/AccountsService/AccountsServiceDBusAdaptor.cpp
89 ${CMAKE_SOURCE_DIR}/plugins/Utils/qvariantlistmodel.cpp
910 )
1011
1112add_library(integratedLightDM STATIC ${LibLightDM_SOURCES})
13+add_library(integratedLightDMSession STATIC ${LibLightDM_SOURCES})
1214add_library(MockLightDM-demo-shared SHARED ${LibLightDM_SOURCES})
1315
1416include_directories(
17+ ${CMAKE_SOURCE_DIR}/plugins/AccountsService
1518 ${CMAKE_CURRENT_BINARY_DIR}
1619 ${GLIB_INCLUDE_DIRS}
1720 ${LIBUSERMETRICSOUTPUT_INCLUDE_DIRS}
@@ -22,17 +25,27 @@ target_link_libraries(integratedLightDM
2225 ${LIBUSERMETRICSOUTPUT_LDFLAGS}
2326 -lpam
2427)
28+target_link_libraries(integratedLightDMSession
29+ ${GLIB_LIBRARIES}
30+ ${LIBUSERMETRICSOUTPUT_LDFLAGS}
31+ -lpam
32+)
2533target_link_libraries(MockLightDM-demo-shared
2634 ${GLIB_LIBRARIES}
2735 ${LIBUSERMETRICSOUTPUT_LDFLAGS}
2836 -lpam
2937)
3038
31−qt5_use_modules(integratedLightDM Concurrent Gui)
32−qt5_use_modules(MockLightDM-demo-shared Concurrent Gui)
39+qt5_use_modules(integratedLightDM Concurrent DBus Gui)
40+qt5_use_modules(integratedLightDMSession Concurrent DBus Gui)
41+qt5_use_modules(MockLightDM-demo-shared Concurrent DBus Gui)
3342
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")
3547set_target_properties(MockLightDM-demo-shared PROPERTIES
48+ COMPILE_FLAGS "-DSM_BUSNAME=systemBus"
3649 OUTPUT_NAME lightdm-qt5-2)
3750
3851install(TARGETS MockLightDM-demo-shared
plugins/IntegratedLightDM/liblightdm/UsersModel.cpp+4−18
@@ -54,24 +54,10 @@ UsersModel::UsersModel(QObject *parent) :
5454 roles[ImagePathRole] = "imagePath";
5555 setRoleNames(roles);
5656
57− // Now modify our mock user backgrounds
58− QDir bgdir = QDir(QStringLiteral("/usr/share/demo-assets/shell/backgrounds/"));
59− QStringList backgrounds = bgdir.entryList(QDir::Files | QDir::NoDotAndDotDot);
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;
57+ connect(d_ptr, &UsersModelPrivate::dataChanged, this, [this](int i) {
58+ QModelIndex index = createIndex(i, 0);
59+ Q_EMIT dataChanged(index, index);
60+ });
7561}
7662
7763int UsersModel::rowCount(const QModelIndex &parent) const
plugins/IntegratedLightDM/liblightdm/UsersModel.h+1−1
@@ -41,7 +41,7 @@ class Q_DECL_EXPORT UsersModel : public QAbstractListModel
4141
4242public:
4343 explicit UsersModel(QObject *parent = 0);
44− virtual ~UsersModel();
44+ virtual ~UsersModel() = default;
4545
4646 enum UserModelRoles {NameRole = Qt::UserRole,
4747 RealNameRole,
plugins/IntegratedLightDM/liblightdm/UsersModelPrivate.cpp+43−9
@@ -18,7 +18,11 @@
1818
1919#include "UsersModelPrivate.h"
2020
21+#include "AccountsServiceDBusAdaptor.h"
22+#include "UsersModel.h"
23+
2124#include <glib.h>
25+#include <QDebug>
2226#include <QDir>
2327#include <QSettings>
2428#include <QStringList>
@@ -27,7 +31,9 @@ namespace QLightDM
2731{
2832
2933UsersModelPrivate::UsersModelPrivate(UsersModel* parent)
30− : q_ptr(parent)
34+ : QObject(parent),
35+ q_ptr(parent),
36+ m_service(new AccountsServiceDBusAdaptor(this))
3137{
3238 QFileInfo demoFile(QDir::homePath() + "/.unity8-greeter-demo");
3339 QString currentUser = g_get_user_name();
@@ -43,15 +49,43 @@ UsersModelPrivate::UsersModelPrivate(UsersModel* parent)
4349 entries.append({user, name, 0, 0, false, false, 0, 0});
4450 }
4551 } else {
46− // If we were using the actual liblightdm, we could just ask it
47− // for the user's real name. But we aren't. We *should* ask
48− // AccountsService for the real name, like liblightdm does internally,
49− // but this is close enough since AS and passwd are always in sync.
50− QString realName = QString::fromUtf8(g_get_real_name()); // gets name from passwd entry
51− if (realName == QStringLiteral("Unknown")) { // glib doesn't translate this string
52− realName.clear();
52+ entries.append({currentUser, 0, 0, 0, false, false, 0, 0});
53+
54+ connect(m_service, &AccountsServiceDBusAdaptor::maybeChanged,
55+ this, [this](const QString &user) {
56+ if (user == entries[0].username) {
57+ updateName(true);
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);
5385 }
54− entries.append({currentUser, realName, 0, 0, false, false, 0, 0});
86+ });
87+ if (!async) {
88+ watcher->waitForFinished();
5589 }
5690}
5791
plugins/IntegratedLightDM/liblightdm/UsersModelPrivate.h+15−3
@@ -19,8 +19,11 @@
1919#ifndef UNITY_MOCK_USERSMODEL_PRIVATE_H
2020#define UNITY_MOCK_USERSMODEL_PRIVATE_H
2121
22−#include <QtCore/QList>
23−#include <QtCore/QString>
22+#include <QList>
23+#include <QObject>
24+#include <QString>
25+
26+class AccountsServiceDBusAdaptor;
2427
2528namespace QLightDM
2629{
@@ -39,19 +42,28 @@ public:
3942 QString infographic;
4043};
4144
42−class UsersModelPrivate
45+class UsersModelPrivate : public QObject
4346{
47+ Q_OBJECT
48+
4449public:
4550 explicit UsersModelPrivate(UsersModel *parent = 0);
4651 virtual ~UsersModelPrivate() = default;
4752
4853 QList<Entry> entries;
4954
55+Q_SIGNALS:
56+ void dataChanged(int);
57+
5058protected:
5159 UsersModel * const q_ptr;
5260
5361private:
5462 Q_DECLARE_PUBLIC(UsersModel)
63+
64+ void updateName(bool async);
65+
66+ AccountsServiceDBusAdaptor *m_service;
5567};
5668
5769}
plugins/Unity/Indicators/indicatorsmanager.cpp+2−2
@@ -116,7 +116,7 @@ void IndicatorsManager::loadFile(const QFileInfo& file_info)
116116 QSettings indicator_settings(file_info.absoluteFilePath(), QSettings::IniFormat, this);
117117 const QString name = indicator_settings.value(QStringLiteral("Indicator Service/Name")).toString();
118118
119− if (m_platform.isPC() && name == "indicator-keyboard") {
119+ if (m_platform.isPC() && name == QLatin1String("indicator-keyboard")) {
120120 return; // convergence: skip this indicator until it works in Mir
121121 }
122122
@@ -293,7 +293,7 @@ Indicator::Ptr IndicatorsManager::indicator(const QString& indicator_name)
293293 // The rest of the indicators respect their default profile (which is "phone", even on desktop PCs)
294294 if ((new_indicator->identifier() == QStringLiteral("indicator-session") && m_platform.isMultiSession())
295295 || (new_indicator->identifier() == QStringLiteral("indicator-power") && m_platform.isPC())) {
296− new_indicator->setProfile("desktop");
296+ new_indicator->setProfile(QStringLiteral("desktop"));
297297 } else {
298298 new_indicator->setProfile(m_profile);
299299 }
plugins/Unity/Indicators/rootstateparser.h+1
@@ -23,6 +23,7 @@
2323
2424class UNITYINDICATORS_EXPORT RootStateParser : public ActionStateParser
2525{
26+Q_OBJECT
2627public:
2728 RootStateParser(QObject* parent = nullptr);
2829 virtual QVariant toQVariant(GVariant* state) const override;
plugins/Unity/Launcher/desktopfilehandler.cpp+2−2
@@ -146,9 +146,9 @@ QString DesktopFileHandler::icon() const
146146 QString iconString = settings.value(QStringLiteral("Icon")).toString();
147147 QString pathString = settings.value(QStringLiteral("Path")).toString();
148148
149− if (QFileInfo(iconString).exists()) {
149+ if (QFileInfo::exists(iconString)) {
150150 return QFileInfo(iconString).absoluteFilePath();
151− } else if (QFileInfo(pathString + '/' + iconString).exists()) {
151+ } else if (QFileInfo::exists(pathString + '/' + iconString)) {
152152 return pathString + '/' + iconString;
153153 }
154154 return "image://theme/" + iconString;
plugins/Unity/Launcher/launcheritem.cpp+1
@@ -37,6 +37,7 @@ LauncherItem::LauncherItem(const QString &appId, const QString &name, const QStr
3737 m_alerting(false),
3838 m_quickList(new QuickListModel(this))
3939{
40+ Q_ASSERT(parent != nullptr);
4041 QuickListEntry nameAction;
4142 nameAction.setActionId(QStringLiteral("launch_item"));
4243 nameAction.setText(m_name);
plugins/Unity/Launcher/launcheritem.h+1−1
@@ -32,7 +32,7 @@ class LauncherItem: public LauncherItemInterface
3232{
3333 Q_OBJECT
3434public:
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);
3636
3737 QString appId() const override;
3838 QString name() const override;
plugins/Unity/Launcher/launchermodel.cpp+15−5
@@ -103,7 +103,7 @@ void LauncherModel::setAlerting(const QString &appId, bool alerting) {
103103 LauncherItem *item = m_list.at(index);
104104 if (!item->focused()) {
105105 item->setAlerting(alerting);
106− Q_EMIT dataChanged(modelIndex, modelIndex, QVector<int>() << RoleAlerting);
106+ Q_EMIT dataChanged(modelIndex, modelIndex, {RoleAlerting});
107107 }
108108 }
109109}
@@ -389,7 +389,8 @@ void LauncherModel::countVisibleChanged(const QString &appId, bool countVisible)
389389 if (countVisible && desktopFile.isValid()) {
390390 LauncherItem *item = new LauncherItem(appId,
391391 desktopFile.displayName(),
392− desktopFile.icon());
392+ desktopFile.icon(),
393+ this);
393394 item->setCountVisible(true);
394395 beginInsertRows(QModelIndex(), m_list.count(), m_list.count());
395396 m_list.append(item);
@@ -414,10 +415,19 @@ void LauncherModel::refresh()
414415 } else {
415416 int idx = m_list.indexOf(item);
416417 item->setName(desktopFile.displayName());
417− item->setIcon(desktopFile.icon());
418418 item->setPinned(item->pinned()); // update pinned text if needed
419419 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});
421431 }
422432 }
423433
@@ -489,7 +499,7 @@ void LauncherModel::alert(const QString &appId)
489499 if (idx >= 0) {
490500 LauncherItem *item = m_list.at(idx);
491501 setAlerting(item->appId(), true);
492− Q_EMIT dataChanged(index(idx), index(idx), QVector<int>() << RoleAlerting);
502+ Q_EMIT dataChanged(index(idx), index(idx), {RoleAlerting});
493503 }
494504}
495505
plugins/Unity/Launcher/launchermodel.h+1−1
@@ -38,7 +38,7 @@ class LauncherModel: public LauncherModelInterface
3838 Q_OBJECT
3939
4040public:
41− LauncherModel(QObject *parent = 0);
41+ LauncherModel(QObject *parent = nullptr);
4242 ~LauncherModel();
4343
4444 int rowCount(const QModelIndex &parent = QModelIndex()) const override;
plugins/Utils/CMakeLists.txt+1
@@ -23,6 +23,7 @@ set(QMLPLUGIN_SRC
2323 windowstatestorage.cpp
2424 timezoneFormatter.cpp
2525 inputeventgenerator.cpp
26+ deviceconfigparser.cpp
2627 plugin.cpp
2728 )
2829
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/plugin.cpp+2
@@ -37,6 +37,7 @@
3737#include "timezoneFormatter.h"
3838#include "applicationsfiltermodel.h"
3939#include "inputeventgenerator.h"
40+#include "deviceconfigparser.h"
4041
4142static QObject *createWindowStateStorage(QQmlEngine *engine, QJSEngine *scriptEngine)
4243{
@@ -70,6 +71,7 @@ void UtilsPlugin::registerTypes(const char *uri)
7071 qmlRegisterType<ActiveFocusLogger>(uri, 0, 1, "ActiveFocusLogger");
7172 qmlRegisterType<ApplicationsFilterModel>(uri, 0, 1, "ApplicationsFilterModel");
7273 qmlRegisterType<InputEventGenerator>(uri, 0, 1, "InputEventGenerator");
74+ qmlRegisterType<DeviceConfigParser>(uri, 0, 1, "DeviceConfigParser");
7375}
7476
7577void UtilsPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
po/unity8.pot+29−27
@@ -8,7 +8,7 @@ msgid ""
88msgstr ""
99"Project-Id-Version: unity8\n"
1010"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"
1212"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
1313"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
1414"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -22,16 +22,16 @@ msgstr ""
2222msgid "Password: "
2323msgstr ""
2424
25−#: plugins/Unity/Launcher/launcheritem.cpp:47
26−#: plugins/Unity/Launcher/launcheritem.cpp:106
25+#: plugins/Unity/Launcher/launcheritem.cpp:48
26+#: plugins/Unity/Launcher/launcheritem.cpp:107
2727msgid "Pin shortcut"
2828msgstr ""
2929
30−#: plugins/Unity/Launcher/launcheritem.cpp:52
30+#: plugins/Unity/Launcher/launcheritem.cpp:53
3131msgid "Quit"
3232msgstr ""
3333
34−#: plugins/Unity/Launcher/launcheritem.cpp:106
34+#: plugins/Unity/Launcher/launcheritem.cpp:107
3535msgid "Unpin shortcut"
3636msgstr ""
3737
@@ -135,15 +135,15 @@ msgctxt "Button: Restart the system"
135135msgid "Restart"
136136msgstr ""
137137
138−#: qml/Components/Lockscreen.qml:245
138+#: qml/Components/Lockscreen.qml:231
139139msgid "Return to Call"
140140msgstr ""
141141
142−#: qml/Components/Lockscreen.qml:245
142+#: qml/Components/Lockscreen.qml:231
143143msgid "Emergency Call"
144144msgstr ""
145145
146−#: qml/Components/Lockscreen.qml:277
146+#: qml/Components/Lockscreen.qml:263
147147msgid "OK"
148148msgstr ""
149149
@@ -152,6 +152,8 @@ msgid "Apps may have unsaved data:"
152152msgstr ""
153153
154154#: qml/Components/ModeSwitchWarningDialog.qml:57
155+msgctxt ""
156+"Re-dock means connect the device again to an external screen/mouse/keyboard"
155157msgid "Re-dock, save your work and close these apps to continue."
156158msgstr ""
157159
@@ -194,15 +196,15 @@ msgstr ""
194196msgid "Add to Favorites"
195197msgstr ""
196198
197−#: qml/Dash/GenericScopeView.qml:565 qml/Dash/GenericScopeView.qml:723
199+#: qml/Dash/GenericScopeView.qml:567 qml/Dash/GenericScopeView.qml:725
198200msgid "See less"
199201msgstr ""
200202
201−#: qml/Dash/GenericScopeView.qml:565
203+#: qml/Dash/GenericScopeView.qml:567
202204msgid "See all"
203205msgstr ""
204206
205−#: qml/Dash/GenericScopeView.qml:627
207+#: qml/Dash/GenericScopeView.qml:629
206208msgctxt "Label: Hint for dash search line edit"
207209msgid "Search"
208210msgstr ""
@@ -228,7 +230,7 @@ msgstr ""
228230msgid "Add a review"
229231msgstr ""
230232
231−#: qml/Dash/Previews/PreviewSharing.qml:50
233+#: qml/Dash/Previews/PreviewSharing.qml:66
232234msgid "Preview Share Item"
233235msgstr ""
234236
@@ -256,13 +258,13 @@ msgstr ""
256258msgid "Also installed"
257259msgstr ""
258260
259−#: qml/DisabledScreenNotice.qml:87
261+#: qml/DisabledScreenNotice.qml:104
260262msgid ""
261263"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."
263265msgstr ""
264266
265−#: qml/Greeter/CoverPage.qml:107
267+#: qml/Greeter/CoverPage.qml:118
266268msgid "Unlock"
267269msgstr ""
268270
@@ -341,62 +343,62 @@ msgstr ""
341343msgid "Conference"
342344msgstr ""
343345
344−#: qml/Panel/Indicators/MenuItemFactory.qml:753
346+#: qml/Panel/Indicators/MenuItemFactory.qml:752
345347msgid "Nothing is playing"
346348msgstr ""
347349
348−#: qml/Panel/Indicators/MenuItemFactory.qml:882
350+#: qml/Panel/Indicators/MenuItemFactory.qml:881
349351#, qt-format
350352msgid "%1 hour"
351353msgid_plural "%1 hours"
352354msgstr[0] ""
353355msgstr[1] ""
354356
355−#: qml/Panel/Indicators/MenuItemFactory.qml:886
357+#: qml/Panel/Indicators/MenuItemFactory.qml:885
356358#, qt-format
357359msgid "%1 minute"
358360msgid_plural "%1 minutes"
359361msgstr[0] ""
360362msgstr[1] ""
361363
362−#: qml/Panel/Indicators/MenuItemFactory.qml:891
364+#: qml/Panel/Indicators/MenuItemFactory.qml:890
363365#, qt-format
364366msgid "%1 second"
365367msgid_plural "%1 seconds"
366368msgstr[0] ""
367369msgstr[1] ""
368370
369−#: qml/Panel/Indicators/MenuItemFactory.qml:894
371+#: qml/Panel/Indicators/MenuItemFactory.qml:893
370372msgid "0 seconds"
371373msgstr ""
372374
373375#. Translators: String like "1 hour, 2 minutes, 3 seconds remaining"
374−#: qml/Panel/Indicators/MenuItemFactory.qml:896
376+#: qml/Panel/Indicators/MenuItemFactory.qml:895
375377#, qt-format
376378msgid "%1 remaining"
377379msgstr ""
378380
379−#: qml/Panel/Indicators/MenuItemFactory.qml:902
381+#: qml/Panel/Indicators/MenuItemFactory.qml:901
380382msgid "In queue…"
381383msgstr ""
382384
383−#: qml/Panel/Indicators/MenuItemFactory.qml:906
385+#: qml/Panel/Indicators/MenuItemFactory.qml:905
384386msgid "Downloading"
385387msgstr ""
386388
387−#: qml/Panel/Indicators/MenuItemFactory.qml:908
389+#: qml/Panel/Indicators/MenuItemFactory.qml:907
388390msgid "Paused, tap to resume"
389391msgstr ""
390392
391−#: qml/Panel/Indicators/MenuItemFactory.qml:910
393+#: qml/Panel/Indicators/MenuItemFactory.qml:909
392394msgid "Canceled"
393395msgstr ""
394396
395−#: qml/Panel/Indicators/MenuItemFactory.qml:912
397+#: qml/Panel/Indicators/MenuItemFactory.qml:911
396398msgid "Finished"
397399msgstr ""
398400
399−#: qml/Panel/Indicators/MenuItemFactory.qml:914
401+#: qml/Panel/Indicators/MenuItemFactory.qml:913
400402msgid "Failed, tap to retry"
401403msgstr ""
402404
qml/Components/EdgeBarrier.qml+1−1
@@ -105,7 +105,7 @@ Item {
105105 },
106106 Transition {
107107 from: "resisting"; to: "passed"
108− UbuntuNumberAnimation { target: materialContainer; property: "opacity" }
108+ UbuntuNumberAnimation { duration: UbuntuAnimation.BriskDuration; target: materialContainer; property: "opacity" }
109109 }
110110 ]
111111}
qml/Components/InputMethod.qml+1−9
@@ -22,15 +22,6 @@ import Ubuntu.Gestures 0.1
2222Item {
2323 id: root
2424
25− Connections {
26− target: SurfaceManager
27− onSurfaceCreated: {
28− if (surface.type == Mir.InputMethodType) {
29− surfaceItem.surface = surface;
30− }
31− }
32− }
33−
3425 property int transitionDuration: UbuntuAnimation.FastDuration
3526
3627 MirSurfaceItem {
@@ -41,6 +32,7 @@ Item {
4132
4233 surfaceWidth: width
4334 surfaceHeight: height
35+ surface: SurfaceManager.inputMethodSurface
4436
4537 onLiveChanged: {
4638 if (surface !== null && !live) {
qml/Components/Lockscreen.qml+80−94
@@ -121,108 +121,94 @@ Showable {
121121 opacity: root.darkenBackground
122122 }
123123
124− MouseArea {
125− anchors.fill: root
126− onClicked: {
127− if (pinPadLoader.item)
128− pinPadLoader.item.forceActiveFocus()
129− }
130− }
131−
132− FocusScope {
133− id: loaderScope
124+ Loader {
125+ id: pinPadLoader
126+ objectName: "pinPadLoader"
134127 anchors.fill: parent
135−
136− Loader {
137− id: pinPadLoader
138− objectName: "pinPadLoader"
139− anchors.fill: parent
140− property bool resetting: false
141− property bool waiting: false
142− property bool showWrongText: false
143−
144− source: {
145− if (resetting || !root.required) {
146− return ""
147− } else if (root.delayMinutes > 0) {
148− return "DelayedLockscreen.qml"
149− } else if (root.alphaNumeric) {
150− return "PassphraseLockscreen.qml"
151− } else {
152− return "PinLockscreen.qml"
153− }
154− }
155− onSourceChanged: {
156− waiting = false
157− showWrongText = false
158− if (loaderScope.activeFocus && pinPadLoader.item)
159− pinPadLoader.item.forceActiveFocus()
128+ property bool resetting: false
129+ property bool waiting: false
130+ property bool showWrongText: false
131+ focus: true
132+
133+ source: {
134+ if (resetting || !root.required) {
135+ return ""
136+ } else if (root.delayMinutes > 0) {
137+ return "DelayedLockscreen.qml"
138+ } else if (root.alphaNumeric) {
139+ return "PassphraseLockscreen.qml"
140+ } else {
141+ return "PinLockscreen.qml"
160142 }
143+ }
144+ onSourceChanged: {
145+ waiting = false
146+ showWrongText = false
147+ }
161148
162− Connections {
163− target: pinPadLoader.item
164−
165− onEntered: {
166− pinPadLoader.waiting = true
167− root.entered(passphrase);
168− }
149+ Connections {
150+ target: pinPadLoader.item
169151
170− onCancel: {
171− root.cancel()
172− }
152+ onEntered: {
153+ pinPadLoader.waiting = true
154+ root.entered(passphrase);
173155 }
174156
175− Binding {
176− target: pinPadLoader.item
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
157+ onCancel: {
158+ root.cancel()
224159 }
225160 }
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+ }
226212 }
227213
228214 Item {
qml/Components/ModeSwitchWarningDialog.qml+1−1
@@ -54,7 +54,7 @@ ShellDialog {
5454 }
5555
5656 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.")
5858 wrapMode: Text.WordWrap
5959 color: "#888888"
6060 }
qml/Components/PassphraseLockscreen.qml+5−4
@@ -18,10 +18,11 @@ import QtQuick 2.4
1818import Ubuntu.Components 1.3
1919import "../Components"
2020
21−Item {
21+FocusScope {
2222 id: root
2323 y: units.gu(4)
2424 height: shakeContainer.height
25+ focus: true
2526
2627 property string infoText
2728 property string errorText
@@ -42,8 +43,6 @@ Item {
4243 }
4344 }
4445
45− onActiveFocusChanged: if (activeFocus) pinentryField.forceActiveFocus()
46−
4746 Column {
4847 id: shakeContainer
4948 anchors.horizontalCenter: parent.horizontalCenter
@@ -59,14 +58,16 @@ Item {
5958 text: root.infoText
6059 }
6160
62− Item {
61+ FocusScope {
6362 id: entryContainer
6463 anchors { left: parent.left; right: parent.right; margins: units.gu(2) }
6564 height: units.gu(4)
65+ focus: true
6666
6767 TextInput {
6868 id: pinentryField
6969 objectName: "pinentryField"
70+ focus: true
7071
7172 property bool incorrectOverride: false
7273
qml/Components/PhysicalKeysMapper.qml+24
@@ -42,12 +42,15 @@ Item {
4242 signal screenshotTriggered;
4343
4444 readonly property bool altTabPressed: d.altTabPressed
45+ readonly property bool superPressed: d.superPressed
46+ readonly property bool superTabPressed: d.superTabPressed
4547
4648 property int powerKeyLongPressTime: 2000
4749
4850 // For testing. If running windowed (e.g. tryShell), Alt+Tab is taken by the
4951 // running desktop, set this to true to use Ctrl+Tab instead.
5052 property bool controlInsteadOfAlt: false
53+ property bool controlInsteadOfSuper: false
5154
5255 QtObject {
5356 id: d
@@ -59,6 +62,9 @@ Item {
5962 property bool altPressed: false
6063 property bool altTabPressed: false
6164
65+ property bool superPressed: false
66+ property bool superTabPressed: false
67+
6268 property var powerButtonPressStart: 0
6369
6470 // 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 {
119125 event.accepted = true;
120126 d.altPressInjected = false;
121127 }
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;
122136 } else if (event.key == Qt.Key_Tab) {
123137 if (d.altPressed && !d.altTabPressed) {
124138 d.altTabPressed = true;
125139 event.accepted = true;
126140 }
141+ if (d.superPressed && !d.superTabPressed) {
142+ d.superTabPressed = true;
143+ event.accepted = true;
144+ }
127145 }
128146 }
129147
@@ -154,6 +172,12 @@ Item {
154172 if (d.altTabPressed) {
155173 event.accepted = true;
156174 }
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+ }
157181 }
158182 }
159183}
qml/Components/PinLockscreen.qml+22−1
@@ -19,8 +19,9 @@ import Ubuntu.Components 1.3
1919import Ubuntu.Components.ListItems 1.3
2020import "../Components"
2121
22−Item {
22+FocusScope {
2323 id: root
24+ focus: true
2425
2526 property string infoText
2627 property string retryText
@@ -45,6 +46,24 @@ Item {
4546 }
4647 }
4748
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+
4867 Column {
4968 anchors {
5069 left: parent.left;
@@ -204,6 +223,7 @@ Item {
204223 width: numbersGrid.buttonWidth
205224 }
206225 PinPadButton {
226+ id: closeButton
207227 iconName: "close"
208228 height: units.gu(5) // visual spec has this row a little closer in
209229 width: numbersGrid.buttonWidth
@@ -216,6 +236,7 @@ Item {
216236 width: numbersGrid.buttonWidth
217237 }
218238 PinPadButton {
239+ id: confirmButton
219240 iconName: "tick"
220241 objectName: "confirmButton"
221242 height: units.gu(5)
qml/Components/ShellDialog.qml+8
@@ -37,7 +37,15 @@ Dialog {
3737
3838 focus: true
3939
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+
4047 Component.onCompleted: {
48+ __foreground.theme = themeHack
4149 show();
4250 }
4351}
qml/Dash/CardCarousel.qml−1
@@ -66,7 +66,6 @@ DashRenderer {
6666 item.cardData = Qt.binding(function() { return model; });
6767 item.fontScale = Qt.binding(function() { return carousel.fontScale; });
6868 item.showHeader = Qt.binding(function() { return loader.explicitlyScaled; });
69− item.titleAlignment = Qt.binding(function() { return cardTool.titleAlignment; });
7069 item.artShapeStyle = "shadow";
7170 item.scopeStyle = cardCarousel.scopeStyle;
7271 }
qml/Dash/CardGrid.qml−1
@@ -70,7 +70,6 @@ DashRenderer {
7070 item.fixedHeaderHeight = Qt.binding(function() { return cardTool.headerHeight; });
7171 item.fixedArtShapeSize = Qt.binding(function() { return cardTool.artShapeSize; });
7272 item.cardData = Qt.binding(function() { return model; });
73− item.titleAlignment = Qt.binding(function() { return cardTool.titleAlignment; });
7473 item.scopeStyle = root.scopeStyle;
7574 item.artShapeStyle = root.artShapeStyle;
7675 item.backgroundShapeStyle = root.backgroundShapeStyle;
qml/Dash/CardHorizontalList.qml−1
@@ -53,7 +53,6 @@ DashRenderer {
5353 item.fixedArtShapeSize = Qt.binding(function() { return cardTool.artShapeSize; });
5454 item.fixedHeaderHeight = Qt.binding(function() { return cardTool.headerHeight; });
5555 item.cardData = Qt.binding(function() { return model; });
56− item.titleAlignment = Qt.binding(function() { return cardTool.titleAlignment; });
5756 item.scopeStyle = root.scopeStyle;
5857 }
5958 Connections {
qml/Dash/CardTool.qml+2−26
@@ -66,7 +66,7 @@ Item {
6666 return layout;
6767 }
6868
69− property var cardComponent: CardCreatorCache.getCardComponent(cardTool.template, cardTool.components);
69+ property var cardComponent: CardCreatorCache.getCardComponent(cardTool.template, cardTool.components, false);
7070
7171 // FIXME: Saviq
7272 // Only way for the card below to actually be laid out completely.
@@ -139,29 +139,6 @@ Item {
139139 readonly property int headerHeight: cardLoader.item ? cardLoader.item.headerHeight : 0
140140 property size artShapeSize: cardLoader.item ? cardLoader.item.artShapeSize : 0
141141
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−
165142 QtObject {
166143 id: carouselTool
167144
@@ -216,10 +193,9 @@ Item {
216193 "summary": "—\n—\n—\n—\n—",
217194 "attributes": attributesModel.model
218195 }
219− sourceComponent: cardTool.cardComponent
196+ sourceComponent: CardCreatorCache.getCardComponent(cardTool.template, cardTool.components, true);
220197 onLoaded: {
221198 item.objectName = "cardToolCard";
222− item.asynchronous = false;
223199 item.width = Qt.binding(function() { return cardTool.cardWidth !== -1 ? cardTool.cardWidth : item.implicitWidth; });
224200 item.height = Qt.binding(function() { return cardTool.cardHeight !== -1 ? cardTool.cardHeight : item.implicitHeight; });
225201 }
qml/Dash/CardVerticalJournal.qml−1
@@ -72,7 +72,6 @@ DashRenderer {
7272 item.fixedArtShapeSize = Qt.binding(function() { return cardTool.artShapeSize; });
7373 item.fixedHeaderHeight = Qt.binding(function() { return cardTool.headerHeight; });
7474 item.cardData = Qt.binding(function() { return model; });
75− item.titleAlignment = Qt.binding(function() { return cardTool.titleAlignment; });
7675 item.scopeStyle = root.scopeStyle;
7776 }
7877 Connections {
qml/Dash/Dash.qml+11−11
@@ -37,6 +37,7 @@ Showable {
3737 }
3838
3939 property bool windowActive: window.active
40+ property bool showOverlayScope: false
4041
4142 DashCommunicatorService {
4243 objectName: "dashCommunicatorService"
@@ -80,7 +81,7 @@ Showable {
8081 return
8182 }
8283
83− closeOverlayScope();
84+ dash.showOverlayScope = false;
8485
8586 dashContent.closePreview();
8687
@@ -93,12 +94,6 @@ Showable {
9394 dashContent.setCurrentScopeAtIndex(scopeIndex, animate, reset)
9495 }
9596
96− function closeOverlayScope() {
97− if (dashContent.x != 0) {
98− dashContent.x = 0;
99− }
100− }
101−
10297 Scopes {
10398 id: scopes
10499 }
@@ -133,12 +128,13 @@ Showable {
133128 height: dash.height
134129 scopes: scopes
135130 visible: x != -width
131+ x: dash.showOverlayScope ? -width : 0
136132 onGotoScope: {
137133 dash.setCurrentScope(scopeId, true, false);
138134 }
139135 onOpenScope: {
140136 scopeItem.scope = scope;
141− x = -width;
137+ dash.showOverlayScope = true;
142138 }
143139 Behavior on x {
144140 UbuntuNumberAnimation {
@@ -207,7 +203,7 @@ Showable {
207203 bottomEdgeController.enableAnimation = true;
208204 bottomEdgeController.progress = 0;
209205 scopeItem.scope = scope;
210− dashContent.x = -dashContent.width;
206+ dash.showOverlayScope = true;
211207 }
212208 onGotoScope: {
213209 bottomEdgeController.enableAnimation = true;
@@ -226,7 +222,7 @@ Showable {
226222 id: scopeItem
227223 objectName: "dashTempScopeItem"
228224
229− x: dashContent.x + width
225+ x: dash.showOverlayScope ? 0 : width
230226 y: dashContent.y
231227 width: parent.width
232228 height: parent.height
@@ -234,7 +230,7 @@ Showable {
234230 hasBackAction: true
235231 isCurrent: visible
236232 onBackClicked: {
237− closeOverlayScope();
233+ dash.showOverlayScope = false;
238234 closePreview();
239235 }
240236
@@ -250,6 +246,10 @@ Showable {
250246 scopes.closeScope(oldScope);
251247 }
252248 }
249+
250+ Behavior on x {
251+ UbuntuNumberAnimation { }
252+ }
253253 }
254254
255255 Rectangle {
qml/Dash/GenericScopeView.qml+2
@@ -370,6 +370,8 @@ FocusScope {
370370 } else {
371371 cardTool.cardWidth = units.gu(10);
372372 }
373+ } else {
374+ cardTool.cardWidth = units.gu(12);
373375 }
374376 item.minimumHorizontalSpacing = item.defaultMinimumHorizontalSpacing;
375377 }
qml/Dash/Previews/PreviewHeader.qml+37−9
@@ -25,6 +25,7 @@ import "../"
2525 * The mascot fall back image comes in widgetData["fallback"]
2626 * The subtitle comes in widgetData["subtitle"]
2727 * The attributes comes in widgetData["attributes"]
28+ * The emblem comes in widgetData["emblem"]
2829 */
2930
3031PreviewWidget {
@@ -40,6 +41,7 @@ PreviewWidget {
4041 readonly property string title: root.widgetData["title"] || ""
4142 readonly property string subtitle: root.widgetData["subtitle"] || ""
4243 readonly property var attributes: root.widgetData["attributes"] || null
44+ readonly property url emblem: root.widgetData["emblem"] || ""
4345 readonly property color fontColor: root.scopeStyle ? root.scopeStyle.foreground : theme.palette.normal.baseText
4446
4547 // Rewire the source since we may have unwired it on onStatusChanged
@@ -98,16 +100,42 @@ PreviewWidget {
98100 spacing: units.dp(2)
99101 anchors.verticalCenter: parent.verticalCenter
100102
101− Label {
102− id: titleLabel
103− objectName: "titleLabel"
103+ Item {
104104 anchors { left: parent.left; right: parent.right }
105− elide: Text.ElideRight
106− font.weight: Font.Normal
107− fontSize: "large"
108− wrapMode: Text.Wrap
109− color: headerRoot.fontColor
110− text: headerRoot.title
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+ }
115+ elide: Text.ElideRight
116+ font.weight: Font.Normal
117+ fontSize: "large"
118+ wrapMode: Text.Wrap
119+ color: headerRoot.fontColor
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+ }
111139 }
112140
113141 Loader {
qml/Dash/Previews/PreviewOverlay.qml+1−1
@@ -158,7 +158,7 @@ Rectangle {
158158 anchors.centerIn: parent
159159 width: units.gu(2.5)
160160 height: width
161− color: theme.palette.normal.foregroundText
161+ color: "white"
162162 name: "close"
163163 }
164164 }
qml/Dash/Previews/PreviewSharing.qml+17−7
@@ -44,6 +44,22 @@ Item {
4444 }
4545 }
4646
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+
4763 Component {
4864 id: exportItemComponent
4965 ContentItem {
@@ -76,13 +92,7 @@ Item {
7692 onPeerSelected: {
7793 var transfer = peer.request();
7894 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);
8696 transfer.state = ContentTransfer.Charged;
8797 }
8898 peerPicker.visible = false;
qml/Dash/ScopesListCategoryItem.qml+8−4
@@ -18,7 +18,7 @@ import QtQuick 2.4
1818import QtQuick.Layouts 1.1
1919import Ubuntu.Components 1.3
2020
21−MouseArea {
21+AbstractButton {
2222 id: root
2323
2424 signal requestFavorite(string scopeId, bool favorite)
@@ -84,15 +84,19 @@ MouseArea {
8484 visible: text != ""
8585 }
8686 }
87− MouseArea {
87+ AbstractButton {
8888 id: starArea
8989 objectName: "starArea"
9090 height: parent.height
9191 width: height
9292 anchors.right: parent.right
9393 onClicked: if (!editMode) root.requestFavorite(model.scopeId, !isFavorite);
94− onPressed: if (editMode) root.handlePressed(starArea);
95− onReleased: if (editMode) root.handleReleased(starArea);
94+ onPressedChanged: {
95+ if (editMode) {
96+ if (pressed) root.handlePressed(starArea.__mouseArea);
97+ else root.handleReleased(starArea.__mouseArea);
98+ }
99+ }
96100 visible: editMode || showStar
97101 Icon {
98102 id: star
qml/DeviceConfiguration.qml+54−16
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2015 Canonical, Ltd.
2+ * Copyright (C) 2015-2016 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -15,6 +15,7 @@
1515 */
1616
1717import QtQuick 2.4
18+import Utils 0.1
1819
1920QtObject {
2021 id: root
@@ -34,57 +35,83 @@ QtObject {
3435
3536 readonly property alias category: priv.category
3637
38+ readonly property var deviceConfigParser: DeviceConfigParser {
39+ name: root.name
40+ }
41+
3742 readonly property var priv: StateGroup {
3843 id: priv
3944
40− property int primaryOrientation: root.useNativeOrientation
45+ property int primaryOrientation: deviceConfigParser.primaryOrientation == Qt.PrimaryOrientation ?
46+ root.useNativeOrientation : deviceConfigParser.primaryOrientation
4147
42− property int supportedOrientations: Qt.PortraitOrientation
43− | Qt.InvertedPortraitOrientation
44− | Qt.LandscapeOrientation
45− | Qt.InvertedLandscapeOrientation
48+ property int supportedOrientations: deviceConfigParser.supportedOrientations
4649
47− property int landscapeOrientation: Qt.LandscapeOrientation
48− property int invertedLandscapeOrientation: Qt.InvertedLandscapeOrientation
49− property int portraitOrientation: Qt.PortraitOrientation
50− property int invertedPortraitOrientation: Qt.InvertedPortraitOrientation
51−
52− // Supported values so far:
53− // "phone", "tablet" or "desktop"
54− property string category: "phone"
50+ property int landscapeOrientation: deviceConfigParser.landscapeOrientation
51+ property int invertedLandscapeOrientation: deviceConfigParser.invertedLandscapeOrientation
52+ property int portraitOrientation: deviceConfigParser.portraitOrientation
53+ property int invertedPortraitOrientation: deviceConfigParser.invertedPortraitOrientation
54+ property string category: deviceConfigParser.category
5555
5656 states: [
5757 State {
5858 name: "mako"
5959 PropertyChanges {
6060 target: priv
61+ primaryOrientation: root.useNativeOrientation
6162 supportedOrientations: Qt.PortraitOrientation
6263 | Qt.LandscapeOrientation
6364 | Qt.InvertedLandscapeOrientation
65+ landscapeOrientation: Qt.LandscapeOrientation
66+ invertedLandscapeOrientation: Qt.InvertedLandscapeOrientation
67+ portraitOrientation: Qt.PortraitOrientation
68+ invertedPortraitOrientation: Qt.InvertedPortraitOrientation
69+ category: "phone"
6470 }
6571 },
6672 State {
6773 name: "krillin"
6874 PropertyChanges {
6975 target: priv
76+ primaryOrientation: root.useNativeOrientation
7077 supportedOrientations: Qt.PortraitOrientation
7178 | Qt.LandscapeOrientation
7279 | Qt.InvertedLandscapeOrientation
80+ landscapeOrientation: Qt.LandscapeOrientation
81+ invertedLandscapeOrientation: Qt.InvertedLandscapeOrientation
82+ portraitOrientation: Qt.PortraitOrientation
83+ invertedPortraitOrientation: Qt.InvertedPortraitOrientation
84+ category: "phone"
7385 }
7486 },
7587 State {
7688 name: "arale"
7789 PropertyChanges {
7890 target: priv
91+ primaryOrientation: root.useNativeOrientation
7992 supportedOrientations: Qt.PortraitOrientation
8093 | Qt.LandscapeOrientation
8194 | Qt.InvertedLandscapeOrientation
95+ landscapeOrientation: Qt.LandscapeOrientation
96+ invertedLandscapeOrientation: Qt.InvertedLandscapeOrientation
97+ portraitOrientation: Qt.PortraitOrientation
98+ invertedPortraitOrientation: Qt.InvertedPortraitOrientation
99+ category: "phone"
82100 }
83101 },
84102 State {
85103 name: "manta"
86104 PropertyChanges {
87105 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
88115 category: "tablet"
89116 }
90117 },
@@ -92,9 +119,15 @@ QtObject {
92119 name: "flo"
93120 PropertyChanges {
94121 target: priv
122+ primaryOrientation: Qt.InvertedLandscapeOrientation
123+ supportedOrientations: Qt.PortraitOrientation
124+ | Qt.InvertedPortraitOrientation
125+ | Qt.LandscapeOrientation
126+ | Qt.InvertedLandscapeOrientation
95127 landscapeOrientation: Qt.InvertedLandscapeOrientation
96128 invertedLandscapeOrientation: Qt.LandscapeOrientation
97− primaryOrientation: Qt.InvertedLandscapeOrientation
129+ portraitOrientation: Qt.PortraitOrientation
130+ invertedPortraitOrientation: Qt.InvertedPortraitOrientation
98131 category: "tablet"
99132 }
100133 },
@@ -102,8 +135,13 @@ QtObject {
102135 name: "desktop"
103136 PropertyChanges {
104137 target: priv
105− category: "desktop"
138+ primaryOrientation: root.useNativeOrientation
106139 supportedOrientations: root.useNativeOrientation
140+ landscapeOrientation: Qt.LandscapeOrientation
141+ invertedLandscapeOrientation: Qt.InvertedLandscapeOrientation
142+ portraitOrientation: Qt.PortraitOrientation
143+ invertedPortraitOrientation: Qt.InvertedPortraitOrientation
144+ category: "desktop"
107145 }
108146 }
109147 ]
qml/DisabledScreenNotice.qml+44−20
@@ -17,6 +17,7 @@
1717import QtQuick 2.4
1818import QtQuick.Layouts 1.1
1919import Ubuntu.Components 1.3
20+import Unity.Session 0.1
2021import QtQuick.Window 2.2
2122import "Components"
2223
@@ -25,19 +26,36 @@ Item {
2526
2627 property bool infoNoteDisplayed: true
2728
28− WallpaperResolver {
29− width: root.width
30− id: wallpaperResolver
29+ // For testing
30+ property var screen: Screen
31+ property var orientationLock: OrientationLock
32+
33+ DeviceConfiguration {
34+ id: deviceConfiguration
35+ name: applicationArguments.deviceName
3136 }
3237
3338 Item {
3439 id: contentContainer
40+ objectName: "contentContainer"
3541 anchors.centerIn: parent
3642 height: rotation == 90 || rotation == 270 ? parent.width : parent.height
3743 width: rotation == 90 || rotation == 270 ? parent.height : parent.width
3844
45+ property int savedOrientation: deviceConfiguration.primaryOrientation == deviceConfiguration.useNativeOrientation
46+ ? (root.width > root.height ? Qt.LandscapeOrientation : Qt.PortraitOrientation)
47+ : deviceConfiguration.primaryOrientation
48+
3949 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) {
4159 case Qt.PortraitOrientation:
4260 return 0;
4361 case Qt.LandscapeOrientation:
@@ -47,6 +65,8 @@ Item {
4765 case Qt.InvertedLandscapeOrientation:
4866 return 90;
4967 }
68+
69+ return 0;
5070 }
5171 transformOrigin: Item.Center
5272
@@ -60,9 +80,9 @@ Item {
6080 }
6181 }
6282
63− Image {
83+ Rectangle {
6484 anchors.fill: parent
65− source: wallpaperResolver.background
85+ color: "#3b3b3b"
6686 }
6787
6888 Item {
@@ -74,22 +94,26 @@ Item {
7494 UbuntuNumberAnimation { }
7595 }
7696
77− Rectangle {
78− anchors.fill: parent
79− color: "black"
80− opacity: 0.4
81− }
82−
83− Label {
84− id: text
97+ Column {
8598 anchors.centerIn: parent
8699 width: parent.width - units.gu(8)
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.")
88− color: "white"
89− horizontalAlignment: Text.AlignHCenter
90− verticalAlignment: Text.AlignVCenter
91− fontSize: "x-large"
92− wrapMode: Text.Wrap
100+ spacing: units.gu(4)
101+
102+ Label {
103+ id: text
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.")
105+ color: "white"
106+ width: parent.width
107+ fontSize: "large"
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+ }
93117 }
94118 }
95119
qml/Greeter/CoverPage.qml+13−2
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013,2014,2015 Canonical, Ltd.
2+ * Copyright (C) 2013-2016 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -34,6 +34,7 @@ Showable {
3434 readonly property real showProgress: MathUtils.clamp((width - Math.abs(x)) / width, 0, 1)
3535
3636 signal tease()
37+ signal clicked()
3738
3839 function hideRight() {
3940 d.forceRightOnNextHideAnimation = true;
@@ -55,7 +56,17 @@ Showable {
5556 // instead, we can get a little extra horizontal push by using transforms.
5657 transform: Translate { id: translation; x: root.draggable ? launcherOffset : 0 }
5758
58− MouseArea { anchors.fill: parent; }
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 {
66+ anchors.fill: parent
67+ mouseEnabled: false
68+ }
69+ }
5970
6071 Rectangle {
6172 // In case background fails to load
qml/Greeter/Infographics.qml+2−1
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013-2016 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -402,6 +402,7 @@ Item {
402402
403403 MouseArea {
404404 anchors.fill: dataCircle
405+ enabled: notification.text != ""
405406
406407 onDoubleClicked: {
407408 if (!d.animating) {
qml/Greeter/NarrowView.qml+14−1
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2015 Canonical, Ltd.
2+ * Copyright (C) 2015-2016 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -125,6 +125,18 @@ FocusScope {
125125 onCancel: coverPage.show()
126126 onEmergencyCall: root.emergencyCall()
127127
128+ onEnabledChanged: {
129+ if (enabled) {
130+ lockscreen.forceActiveFocus();
131+ }
132+ }
133+
134+ onVisibleChanged: {
135+ if (visible) {
136+ lockscreen.forceActiveFocus();
137+ }
138+ }
139+
128140 function maybeShow() {
129141 if (root.locked && !shown) {
130142 showNow();
@@ -145,6 +157,7 @@ FocusScope {
145157 width: parent.width
146158 background: root.background
147159 onTease: root.tease()
160+ onClicked: hide()
148161
149162 onShowProgressChanged: {
150163 if (showProgress === 1) {
qml/Launcher/Launcher.qml+144−11
@@ -19,21 +19,26 @@ import "../Components"
1919import Ubuntu.Components 1.3
2020import Ubuntu.Gestures 0.1
2121import Unity.Launcher 0.1
22+import GlobalShortcut 1.0
2223
23−Item {
24+FocusScope {
2425 id: root
2526
2627 property bool autohideEnabled: false
28+ property bool lockedVisible: false
2729 property bool available: true // can be used to disable all interactions
2830 property alias inverted: panel.inverted
2931 property bool shadeBackground: true // can be used to disable background shade when launcher is visible
3032
31− property int panelWidth: units.gu(8)
33+ property int panelWidth: units.gu(10)
3234 property int dragAreaWidth: units.gu(1)
3335 property int minimizeDistance: units.gu(26)
3436 property real progress: dragArea.dragging && dragArea.touchX > panelWidth ?
3537 (width * (dragArea.touchX-panelWidth) / (width - panelWidth)) : 0
3638
39+ property bool superPressed: false
40+ property bool superTabPressed: false
41+
3742 readonly property bool dragging: dragArea.dragging
3843 readonly property real dragDistance: dragArea.dragging ? dragArea.touchX : 0
3944 readonly property real visibleWidth: panel.width + panel.x
@@ -57,12 +62,55 @@ Item {
5762 }
5863 }
5964
65+ onSuperPressedChanged: {
66+ if (superPressed) {
67+ superPressTimer.start();
68+ superLongPressTimer.start();
69+ } else {
70+ superPressTimer.stop();
71+ superLongPressTimer.stop();
72+ launcher.switchToNextState("");
73+ panel.shortcutHintsShown = false;
74+ }
75+ }
76+
77+ onSuperTabPressedChanged: {
78+ if (superTabPressed) {
79+ switchToNextState("visible")
80+ panel.highlightIndex = -1;
81+ root.focus = true;
82+ superPressTimer.stop();
83+ superLongPressTimer.stop();
84+ } else {
85+ if (panel.highlightIndex == -1) {
86+ showDashHome();
87+ } else if (panel.highlightIndex >= 0){
88+ launcherApplicationSelected(LauncherModel.get(panel.highlightIndex).appId);
89+ }
90+ panel.highlightIndex = -2;
91+ switchToNextState("");
92+ root.focus = false;
93+ }
94+ }
95+
96+ onLockedVisibleChanged: {
97+ if (lockedVisible && state == "") {
98+ panel.dismissTimer.stop();
99+ fadeOutAnimation.stop();
100+ switchToNextState("visible")
101+ } else if (!lockedVisible && state == "visible") {
102+ hide();
103+ }
104+ }
105+
60106 function hide() {
61107 switchToNextState("")
62108 }
63109
64110 function fadeOut() {
65− fadeOutAnimation.start();
111+ if (!root.lockedVisible) {
112+ fadeOutAnimation.start();
113+ }
66114 }
67115
68116 function switchToNextState(state) {
@@ -90,6 +138,77 @@ Item {
90138 }
91139 }
92140
141+ function openForKeyboardNavigation() {
142+ panel.highlightIndex = -1; // The BFB
143+ root.focus = true;
144+ switchToNextState("visible")
145+ }
146+
147+ Keys.onPressed: {
148+ switch (event.key) {
149+ case Qt.Key_Backtab:
150+ panel.highlightPrevious();
151+ event.accepted = true;
152+ break;
153+ case Qt.Key_Up:
154+ if (root.inverted) {
155+ panel.highlightNext()
156+ } else {
157+ panel.highlightPrevious();
158+ }
159+ event.accepted = true;
160+ break;
161+ case Qt.Key_Tab:
162+ panel.highlightNext();
163+ event.accepted = true;
164+ break;
165+ case Qt.Key_Down:
166+ if (root.inverted) {
167+ panel.highlightPrevious();
168+ } else {
169+ panel.highlightNext();
170+ }
171+ event.accepted = true;
172+ break;
173+ case Qt.Key_Right:
174+ panel.openQuicklist(panel.highlightIndex)
175+ event.accepted = true;
176+ break;
177+ case Qt.Key_Escape:
178+ panel.highlightIndex = -2;
179+ // Falling through intentionally
180+ case Qt.Key_Enter:
181+ case Qt.Key_Return:
182+ case Qt.Key_Space:
183+ if (panel.highlightIndex == -1) {
184+ showDashHome();
185+ } else if (panel.highlightIndex >= 0) {
186+ launcherApplicationSelected(LauncherModel.get(panel.highlightIndex).appId);
187+ }
188+ root.hide();
189+ panel.highlightIndex = -2
190+ event.accepted = true;
191+ root.focus = false;
192+ }
193+ }
194+
195+ Timer {
196+ id: superPressTimer
197+ interval: 200
198+ onTriggered: {
199+ switchToNextState("visible")
200+ }
201+ }
202+
203+ Timer {
204+ id: superLongPressTimer
205+ interval: 1000
206+ onTriggered: {
207+ switchToNextState("visible")
208+ panel.shortcutHintsShown = true;
209+ }
210+ }
211+
93212 Timer {
94213 id: teaseTimer
95214 interval: mode == "teasing" ? 200 : 300
@@ -106,6 +225,13 @@ Item {
106225 interval: 1
107226 property string nextState: ""
108227 onTriggered: {
228+ if (root.lockedVisible && nextState == "") {
229+ // Due to binding updates when switching between modes
230+ // it could happen that our request to show will be overwritten
231+ // with a hide request. Rewrite it when we know hiding is not allowed.
232+ nextState = "visible"
233+ }
234+
109235 // switching to an intermediate state here to make sure all the
110236 // values are restored, even if we were already in the target state
111237 root.state = "tmp"
@@ -151,7 +277,7 @@ Item {
151277
152278 MouseArea {
153279 id: launcherDragArea
154− enabled: root.available && (root.state == "visible" || root.state == "visibleTemporary")
280+ enabled: root.available && (root.state == "visible" || root.state == "visibleTemporary") && !root.lockedVisible
155281 anchors.fill: panel
156282 anchors.rightMargin: -units.gu(2)
157283 drag {
@@ -172,9 +298,10 @@ Item {
172298 InverseMouseArea {
173299 id: closeMouseArea
174300 anchors.fill: panel
175− enabled: root.shadeBackground && root.state == "visible"
301+ enabled: root.shadeBackground && root.state == "visible" && (!root.lockedVisible || panel.highlightIndex >= -1)
176302 visible: enabled
177303 onPressed: {
304+ panel.highlightIndex = -2
178305 root.hide();
179306 }
180307 }
@@ -183,7 +310,7 @@ Item {
183310 id: backgroundShade
184311 anchors.fill: parent
185312 color: "black"
186− opacity: root.shadeBackground && root.state == "visible" ? 0.6 : 0
313+ opacity: root.shadeBackground && root.state == "visible" && !root.lockedVisible ? 0.6 : 0
187314
188315 Behavior on opacity { NumberAnimation { duration: UbuntuAnimation.BriskDuration } }
189316 }
@@ -202,8 +329,8 @@ Item {
202329 rotation: -90
203330 anchors.centerIn: parent
204331 gradient: Gradient {
205− GradientStop { position: 0.0; color: panel.color}
206− GradientStop { position: 1.0; color: Qt.rgba(panel.r,panel.g,panel.b,0)}
332+ GradientStop { position: 0.0; color: Qt.rgba(panel.color.r, panel.color.g, panel.color.b, .5)}
333+ GradientStop { position: 1.0; color: Qt.rgba(panel.color.r,panel.color.g,panel.color.b,0)}
207334 }
208335 }
209336 }
@@ -227,7 +354,7 @@ Item {
227354 Connections {
228355 target: panel.dismissTimer
229356 onTriggered: {
230− if (root.autohideEnabled) {
357+ if (root.autohideEnabled && !root.lockedVisible) {
231358 if (!panel.preventHiding) {
232359 root.state = ""
233360 } else {
@@ -240,11 +367,11 @@ Item {
240367 property bool animate: true
241368
242369 onApplicationSelected: {
243− root.state = ""
370+ root.hide();
244371 launcherApplicationSelected(appId)
245372 }
246373 onShowDashHome: {
247− root.state = ""
374+ root.hide();
248375 root.showDashHome();
249376 }
250377
@@ -254,6 +381,12 @@ Item {
254381 }
255382 }
256383
384+ onKbdNavigationCancelled: {
385+ panel.highlightIndex = -2;
386+ root.hide();
387+ root.focus = false;
388+ }
389+
257390 Behavior on x {
258391 enabled: !dragArea.dragging && !launcherDragArea.drag.active && panel.animate;
259392 NumberAnimation {
qml/Launcher/LauncherDelegate.qml+55−20
@@ -20,6 +20,7 @@ import Ubuntu.Components 1.3
2020Item {
2121 id: root
2222
23+ property int itemIndex: 0
2324 property string iconName
2425 property int count: 0
2526 property bool countVisible: false
@@ -29,10 +30,12 @@ Item {
2930 property real maxAngle: 0
3031 property bool inverted: false
3132 property bool alerting: false
32− readonly property alias wiggling: wiggleAnim.running
33+ property bool highlighted: false
34+ property bool shortcutHintShown: false
3335
3436 readonly property int effectiveHeight: Math.cos(angle * Math.PI / 180) * itemHeight
3537 readonly property real foldedHeight: Math.cos(maxAngle * Math.PI / 180) * itemHeight
38+ readonly property alias wiggling: wiggleAnim.running
3639
3740 property int itemWidth
3841 property int itemHeight
@@ -121,20 +124,32 @@ Item {
121124
122125 Item {
123126 id: iconItem
124− width: parent.itemWidth + units.gu(1)
127+ width: root.width
125128 height: parent.itemHeight + units.gu(1)
126129 anchors.centerIn: parent
127130
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+
128142 ProportionalShape {
129143 id: iconShape
130144 anchors.centerIn: parent
131− width: parent.width - units.gu(2)
145+ width: root.itemWidth
132146 aspect: UbuntuShape.DropShadow
133147 source: Image {
134148 id: iconImage
135149 sourceSize.width: iconShape.width
136150 sourceSize.height: iconShape.height
137151 source: root.iconName
152+ cache: false // see lpbug#1543290 why no cache
138153 }
139154 }
140155
@@ -144,7 +159,8 @@ Item {
144159 anchors {
145160 right: parent.right
146161 bottom: parent.bottom
147− margins: units.dp(3)
162+ rightMargin: (iconItem.width - root.itemWidth) / 2 - units.dp(2)
163+ margins: units.dp(5)
148164 }
149165 width: Math.min(root.itemWidth, Math.max(units.gu(2), countLabel.implicitWidth + units.gu(1)))
150166 height: units.gu(2)
@@ -172,16 +188,11 @@ Item {
172188 id: progressOverlay
173189 objectName: "progressOverlay"
174190
175− anchors {
176− left: iconItem.left
177− right: iconItem.right
178− verticalCenter: parent.verticalCenter
179− leftMargin: units.gu(1.5)
180− rightMargin: units.gu(1.5)
181− }
191+ anchors.centerIn: parent
192+ width: root.itemWidth * .8
182193 height: units.gu(1)
183194 visible: root.progress > -1
184− color: UbuntuColors.darkGrey
195+ backgroundColor: UbuntuColors.darkGrey
185196 borderSource: "none"
186197
187198 Item {
@@ -199,32 +210,56 @@ Item {
199210 top: parent.top
200211 bottom: parent.bottom
201212 }
202− color: "white"
213+ backgroundColor: "white"
203214 borderSource: "none"
204215 width: progressOverlay.width
205216 }
206217 }
207218 }
208219
209− Image {
210− objectName: "runningHighlight"
220+ Column {
211221 anchors {
212222 left: parent.left
213223 verticalCenter: parent.verticalCenter
214224 }
215− visible: root.itemRunning
216− rotation: 180
217− source: "graphics/focused_app_arrow.png"
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"
233+ visible: root.itemRunning
234+ }
235+ }
218236 }
219237
220− Image {
238+ Rectangle {
221239 objectName: "focusedHighlight"
222240 anchors {
223241 right: parent.right
224242 verticalCenter: parent.verticalCenter
225243 }
244+ width: units.gu(0.25)
245+ height: units.gu(.5)
246+ color: "white"
226247 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+ }
228263 }
229264 }
230265
qml/Launcher/LauncherPanel.qml+122−51
@@ -19,12 +19,13 @@ import Ubuntu.Components 1.3
1919import Ubuntu.Components.ListItems 1.3 as ListItems
2020import Unity.Launcher 0.1
2121import Ubuntu.Components.Popups 1.3
22+import GlobalShortcut 1.0
2223import "../Components/ListItems"
2324import "../Components/"
2425
2526Rectangle {
2627 id: root
27− color: "#B2000000"
28+ color: "#E0292929"
2829
2930 rotation: inverted ? 180 : 0
3031
@@ -33,11 +34,13 @@ Rectangle {
3334 property bool dragging: false
3435 property bool moving: launcherListView.moving || launcherListView.flicking
3536 property bool preventHiding: moving || dndArea.draggedIndex >= 0 || quickList.state === "open" || dndArea.pressed
36− || mouseEventEater.containsMouse || dashItem.hovered
37− property int highlightIndex: -1
37+ || mouseEventEater.containsMouse || dashItem.hovered
38+ property int highlightIndex: -2
39+ property bool shortcutHintsShown: false
3840
3941 signal applicationSelected(string appId)
4042 signal showDashHome()
43+ signal kbdNavigationCancelled()
4144
4245 onXChanged: {
4346 if (quickList.state == "open") {
@@ -45,6 +48,26 @@ Rectangle {
4548 }
4649 }
4750
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+
4871 MouseArea {
4972 id: mouseEventEater
5073 anchors.fill: parent
@@ -57,24 +80,16 @@ Rectangle {
5780 fill: parent
5881 }
5982
60− Item {
83+ Rectangle {
6184 objectName: "buttonShowDashHome"
6285 width: parent.width
63− height: units.gu(7)
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
73− }
86+ height: width * .9
87+ color: UbuntuColors.orange
88+ readonly property bool highlighted: root.highlightIndex == -1;
7489
7590 Image {
7691 objectName: "dashItem"
77− width: units.gu(5)
92+ width: parent.width * .6
7893 height: width
7994 anchors.centerIn: parent
8095 source: "graphics/home.png"
@@ -85,6 +100,14 @@ Rectangle {
85100 anchors.fill: parent
86101 onClicked: root.showDashHome()
87102 }
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+ }
88111 }
89112
90113 Item {
@@ -102,10 +125,8 @@ Rectangle {
102125 objectName: "launcherListView"
103126 anchors {
104127 fill: parent
105− topMargin: -extensionSize + units.gu(0.5)
106− bottomMargin: -extensionSize + units.gu(1)
107− leftMargin: units.gu(0.5)
108− rightMargin: units.gu(0.5)
128+ topMargin: -extensionSize + width * .15
129+ bottomMargin: -extensionSize + width * .15
109130 }
110131 topMargin: extensionSize
111132 bottomMargin: extensionSize
@@ -140,11 +161,11 @@ Rectangle {
140161 }
141162
142163 // The height of the area where icons start getting folded
143− property int foldingStartHeight: units.gu(6.5)
164+ property int foldingStartHeight: itemHeight
144165 // The height of the area where the items reach the final folding angle
145166 property int foldingStopHeight: foldingStartHeight - itemHeight - spacing
146− property int itemWidth: units.gu(7)
147− property int itemHeight: units.gu(6.5)
167+ property int itemWidth: width * .75
168+ property int itemHeight: itemWidth * 15 / 16 + units.gu(1)
148169 property int clickFlickSpeed: units.gu(60)
149170 property int draggedIndex: dndArea.draggedIndex
150171 property real realContentY: contentY - originY + topMargin
@@ -172,12 +193,24 @@ Rectangle {
172193
173194 UbuntuNumberAnimation {
174195 id: moveAnimation
196+ objectName: "moveAnimation"
175197 target: launcherListView
176198 property: "contentY"
177199 function moveTo(contentY) {
178200 from = launcherListView.contentY;
179201 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);
181214 }
182215 }
183216
@@ -192,9 +225,10 @@ Rectangle {
192225 // the right app when running autopilot tests for
193226 // multiple apps.
194227 readonly property string appId: model.appId
228+ itemIndex: index
195229 itemHeight: launcherListView.itemHeight
196230 itemWidth: launcherListView.itemWidth
197− width: itemWidth
231+ width: parent.width
198232 height: itemHeight
199233 iconName: model.icon
200234 count: model.count
@@ -204,6 +238,8 @@ Rectangle {
204238 itemFocused: model.focused
205239 inverted: root.inverted
206240 alerting: model.alerting
241+ highlighted: root.highlightIndex == index
242+ shortcutHintShown: root.shortcutHintsShown && index <= 9
207243 z: -Math.abs(offset)
208244 maxAngle: 55
209245 property bool dragging: false
@@ -241,14 +277,7 @@ Rectangle {
241277 onAlertingChanged: {
242278 if(alerting) {
243279 if (!dragging && (launcherListView.peekingIndex === -1 || launcher.visibleWidth > 0)) {
244− var itemPosition = index * launcherListView.itemHeight;
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− }
280+ launcherListView.moveToIndex(index)
252281 if (!dragging && launcher.state !== "visible") {
253282 peekingAnimation.start()
254283 }
@@ -402,10 +431,7 @@ Rectangle {
402431
403432 if (mouse.button & Qt.RightButton) { // context menu
404433 // Opening QuickList
405− quickList.item = clickedItem;
406− quickList.model = launcherListView.model.get(index).quickList;
407− quickList.appId = launcherListView.model.get(index).appId;
408− quickList.state = "open";
434+ quickList.open(index);
409435 return;
410436 }
411437
@@ -413,10 +439,8 @@ Rectangle {
413439
414440 // First/last item do the scrolling at more than 12 degrees
415441 if (index == 0 || index == launcherListView.count - 1) {
416− if (clickedItem.angle > 12) {
417− launcherListView.flick(0, -launcherListView.clickFlickSpeed);
418− } else if (clickedItem.angle < -12) {
419− launcherListView.flick(0, launcherListView.clickFlickSpeed);
442+ if (clickedItem.angle > 12 || clickedItem.angle < -12) {
443+ launcherListView.moveToIndex(index);
420444 } else {
421445 root.applicationSelected(LauncherModel.get(index).appId);
422446 }
@@ -424,10 +448,8 @@ Rectangle {
424448 }
425449
426450 // the rest launches apps up to an angle of 30 degrees
427− if (clickedItem.angle > 30) {
428− launcherListView.flick(0, -launcherListView.clickFlickSpeed);
429− } else if (clickedItem.angle < -30) {
430− launcherListView.flick(0, launcherListView.clickFlickSpeed);
451+ if (clickedItem.angle > 30 || clickedItem.angle < -30) {
452+ launcherListView.moveToIndex(index);
431453 } else {
432454 root.applicationSelected(LauncherModel.get(index).appId);
433455 }
@@ -481,11 +503,7 @@ Rectangle {
481503
482504 draggedIndex = Math.floor((mouse.y + launcherListView.realContentY) / launcherListView.realItemHeight);
483505
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)
489507
490508 launcherListView.interactive = false
491509
@@ -644,7 +662,9 @@ Rectangle {
644662 enabled: quickList.state == "open" || pressed
645663
646664 onClicked: {
647− quickList.state = ""
665+ quickList.state = "";
666+ quickList.focus = false;
667+ root.kbdNavigationCancelled();
648668 }
649669
650670 // Forward for dragging to work when quickList is open
@@ -693,12 +713,60 @@ Rectangle {
693713 property var model
694714 property string appId
695715 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+ }
696755
697756 // internal
698757 property int itemCenter: item ? root.mapFromItem(quickList.item).y + (item.height / 2) + quickList.item.offset : units.gu(1)
699758 property int offset: itemCenter + (height/2) + units.gu(1) > parent.height ? -itemCenter - (height/2) - units.gu(1) + parent.height :
700759 itemCenter - (height/2) < units.gu(1) ? (height/2) - itemCenter + units.gu(1) : 0
701760
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+
702770 Column {
703771 id: quickListColumn
704772 width: parent.width
@@ -712,6 +780,7 @@ Rectangle {
712780 objectName: "quickListEntry" + index
713781 text: (model.clickable ? "" : "<b>") + model.label + (model.clickable ? "" : "</b>")
714782 highlightWhenPressed: model.clickable
783+ selected: index === quickList.selectedIndex
715784
716785 // FIXME: This is a workaround for the theme not being context sensitive. I.e. the
717786 // ListItems don't know that they are sitting in a themed Popover where the color
@@ -727,6 +796,8 @@ Rectangle {
727796 // Unsetting model to prevent showing changing entries during fading out
728797 // that may happen because of triggering an action.
729798 LauncherModel.quickListActionInvoked(quickList.appId, index);
799+ quickList.focus = false;
800+ root.kbdNavigationCancelled();
730801 quickList.model = undefined;
731802 }
732803 }
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
2323import Utils 0.1
2424import "../Components"
2525
26−Item {
26+StyledItem {
2727 id: notification
2828
2929 property alias iconSource: icon.fileSource
@@ -58,6 +58,10 @@ Item {
5858 color: (type === Notification.Confirmation && notificationList.useModal && !greeter.shown) || darkOnBright ? sdLightGrey : Qt.rgba(0.132, 0.117, 0.109, 0.97)
5959 opacity: 1 - (x / notification.width) // FIXME: non-zero initially because of LP: #1354406 workaround, we want this to start at 0 upon creation eventually
6060
61+ theme: ThemeSettings {
62+ name: "Ubuntu.Components.Themes.Ambiance"
63+ }
64+
6165 state: {
6266 var result = "";
6367
@@ -297,7 +301,7 @@ Item {
297301 visible: body != "" && type !== Notification.Confirmation
298302 fontSize: "small"
299303 color: darkOnBright ? sdFontColor : theme.palette.normal.backgroundText
300− wrapMode: Text.WordWrap
304+ wrapMode: Text.Wrap
301305 maximumLineCount: type == Notification.SnapDecision ? 12 : 2
302306 elide: Text.ElideRight
303307 textFormat: Text.PlainText
qml/OrientedShell.qml+26−1
@@ -19,6 +19,7 @@ import QtQuick.Window 2.2
1919import Unity.InputInfo 0.1
2020import Unity.Session 0.1
2121import Unity.Screens 0.1
22+import Utils 0.1
2223import GSettings 1.0
2324import "Components"
2425import "Rotation"
@@ -77,6 +78,8 @@ Rectangle {
7778 InputDeviceModel {
7879 id: keyboardsModel
7980 deviceFilter: InputInfo.Keyboard
81+ onDeviceAdded: forceOSKEnabled = autopilotDevicePresent();
82+ onDeviceRemoved: forceOSKEnabled = autopilotDevicePresent();
8083 }
8184
8285 InputDeviceModel {
@@ -84,6 +87,27 @@ Rectangle {
8487 deviceFilter: InputInfo.TouchScreen
8588 }
8689
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+
87111 Screens {
88112 id: screens
89113 }
@@ -195,7 +219,8 @@ Rectangle {
195219 // have multiple keyboards around. For now we only enable one keyboard at a time
196220 // thus hiding it here if there is a physical one around or if we have a second
197221 // 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
199224
200225 // TODO: Factor in the connected input devices (eg: physical keyboard, mouse, touchscreen),
201226 // 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
1919
2020Rectangle {
2121 id: handle
22− color: UbuntuColors.slate
22+ color: theme.palette.normal.foreground
2323 height: units.gu(2)
2424 property bool active: false
2525
@@ -35,7 +35,7 @@ Rectangle {
3535 id: dot
3636 width: units.dp(3)
3737 height: width
38− color: handle.active ? UbuntuColors.orange : UbuntuColors.ash
38+ color: handle.active ? theme.palette.highlighted.foreground : theme.palette.normal.raised
3939 radius: units.dp(1)
4040 }
4141 }
qml/Panel/Indicators/MenuItemFactory.qml−1
@@ -228,7 +228,6 @@ Item {
228228 text: menuData && menuData.label || ""
229229 iconSource: menuData && menuData.icon || ""
230230 value : menuData && menuData.actionState || 0.0
231− enabled: menuData && menuData.sensitive || false
232231 highlightWhenPressed: false
233232 }
234233 }
qml/Panel/IndicatorsMenu.qml+1−1
@@ -38,7 +38,7 @@ Showable {
3838 property bool enableHint: true
3939 property bool contentEnabled: true
4040 property bool showOnClick: true
41− property color panelColor: theme.palette.normal.background
41+ property color panelColor: UbuntuColors.jet
4242
4343 signal showTapped(point position)
4444
qml/Panel/Panel.qml+5−1
@@ -89,7 +89,7 @@ Item {
8989
9090 Rectangle {
9191 id: indicatorAreaBackground
92− color: callHint.visible ? UbuntuColors.green : theme.palette.normal.background
92+ color: callHint.visible ? UbuntuColors.green : indicators.panelColor
9393 anchors {
9494 top: parent.top
9595 left: parent.left
@@ -169,8 +169,10 @@ Item {
169169 objectName: "windowDecorationTitle"
170170 anchors {
171171 left: parent.left
172+ right: __indicators.left
172173 top: parent.top
173174 leftMargin: units.gu(1)
175+ rightMargin: units.gu(1)
174176 topMargin: units.gu(0.5)
175177 bottomMargin: units.gu(0.5)
176178 }
@@ -181,6 +183,8 @@ Item {
181183 fontSize: "medium"
182184 font.weight: Font.Normal
183185 text: PanelState.title
186+ elide: Text.ElideRight
187+ maximumLineCount: 1
184188 }
185189
186190 // TODO here would the Locally integrated menus come
qml/ScopeTool.qml−8
@@ -52,14 +52,6 @@ Rectangle {
5252 color: "#FCFCFC"
5353 }
5454
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−
6355 DashContent {
6456 id: dashContent
6557
qml/Shell.qml+51−1
@@ -25,6 +25,7 @@ import Ubuntu.Telephony 0.1 as Telephony
2525import Unity.Connectivity 0.1
2626import Unity.Launcher 0.1
2727import GlobalShortcut 1.0 // has to be before Utils, because of WindowKeysFilter
28+import GSettings 1.0
2829import Utils 0.1
2930import Powerd 0.1
3031import SessionBroadcast 0.1
@@ -187,6 +188,11 @@ Item {
187188 }
188189 }
189190
191+ GSettings {
192+ id: settings
193+ schema.id: "com.canonical.Unity8"
194+ }
195+
190196 Item {
191197 id: stages
192198 objectName: "stages"
@@ -343,6 +349,11 @@ Item {
343349 property: "altTabPressed"
344350 value: physicalKeysMapper.altTabPressed
345351 }
352+ Binding {
353+ target: applicationsDisplayLoader.item
354+ property: "leftMargin"
355+ value: shell.usageScenario == "desktop" && !settings.autohideLauncher ? launcher.panelWidth: 0
356+ }
346357 }
347358
348359 Tutorial {
@@ -373,7 +384,11 @@ Item {
373384 InputMethod {
374385 id: inputMethod
375386 objectName: "inputMethod"
376− anchors { fill: parent; topMargin: panel.panelHeight }
387+ anchors {
388+ fill: parent
389+ topMargin: panel.panelHeight
390+ leftMargin: launcher.lockedVisible ? launcher.panelWidth : 0
391+ }
377392 z: notifications.useModal || panel.indicators.shown || wizard.active ? overlay.z + 1 : overlay.z - 1
378393 }
379394
@@ -557,6 +572,10 @@ Item {
557572 && !greeter.hasLockedApp
558573 inverted: shell.usageScenario !== "desktop"
559574 shadeBackground: !tutorial.running
575+ superPressed: physicalKeysMapper.superPressed
576+ superTabPressed: physicalKeysMapper.superTabPressed
577+ panelWidth: units.gu(settings.launcherWidth)
578+ lockedVisible: shell.usageScenario == "desktop" && !settings.autohideLauncher && !panel.fullscreenMode
560579
561580 onShowDashHome: showHome()
562581 onDash: showDash()
@@ -576,6 +595,37 @@ Item {
576595 panel.indicators.hide()
577596 }
578597 }
598+ onFocusChanged: {
599+ if (!focus) {
600+ applicationsDisplayLoader.focus = true;
601+ }
602+ }
603+
604+ GlobalShortcut {
605+ shortcut: Qt.AltModifier | Qt.Key_F1
606+ onTriggered: {
607+ launcher.openForKeyboardNavigation();
608+ }
609+ }
610+ GlobalShortcut {
611+ shortcut: Qt.MetaModifier | Qt.Key_0
612+ onTriggered: {
613+ if (LauncherModel.get(9)) {
614+ activateApplication(LauncherModel.get(9).appId);
615+ }
616+ }
617+ }
618+ Repeater {
619+ model: 9
620+ GlobalShortcut {
621+ shortcut: Qt.MetaModifier | (Qt.Key_1 + index)
622+ onTriggered: {
623+ if (LauncherModel.get(index)) {
624+ activateApplication(LauncherModel.get(index).appId);
625+ }
626+ }
627+ }
628+ }
579629 }
580630
581631 Wizard {
qml/Stages/AbstractStage.qml+3−1
@@ -21,7 +21,7 @@ import GSettings 1.0
2121Rectangle {
2222 id: root
2323
24− color: "#111111"
24+ color: "#060606"
2525
2626 // Controls to be set from outside
2727 property bool altTabPressed
@@ -39,6 +39,8 @@ Rectangle {
3939 property int shellOrientationAngle
4040 property bool spreadEnabled: true // If false, animations and right edge will be disabled
4141 property bool suspended
42+ // A Stage should paint a wallpaper etc over its full size but not use the margins for window placement
43+ property int leftMargin: 0
4244
4345 // To be read from outside
4446 property var mainApp: null
qml/Stages/DesktopSpread.qml+64−17
@@ -19,6 +19,7 @@ import QtQuick.Layouts 1.1
1919import Ubuntu.Components 1.3
2020import Ubuntu.Gestures 0.1
2121import Unity.Application 0.1
22+import "../Components"
2223
2324FocusScope {
2425 id: root
@@ -26,8 +27,11 @@ FocusScope {
2627 property bool altTabPressed: false
2728 property Item workspace: null
2829
30+ readonly property alias ready: blurLayer.ready
2931 readonly property alias highlightedIndex: spreadRepeater.highlightedIndex
3032
33+ signal playFocusAnimation(int index)
34+
3135 function show() {
3236 spreadContainer.animateIn = true;
3337 root.state = "altTab";
@@ -91,6 +95,9 @@ FocusScope {
9195
9296 function focusSelected() {
9397 if (spreadRepeater.highlightedIndex != -1) {
98+ if (spreadContainer.visible) {
99+ root.playFocusAnimation(spreadRepeater.highlightedIndex)
100+ }
94101 var application = ApplicationManager.get(spreadRepeater.highlightedIndex);
95102 ApplicationManager.requestFocusApplication(application.appId);
96103 }
@@ -101,6 +108,31 @@ FocusScope {
101108 state = ""
102109 }
103110
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+
104136 Item {
105137 id: spreadContainer
106138 objectName: "spreadContainer"
@@ -226,13 +258,20 @@ FocusScope {
226258 Transition {
227259 from: ""
228260 to: "altTab"
229− PropertyAction { target: spreadDelegate; properties: "y,height,width,angle,z,itemScale,itemScaleOriginY,visible" }
230− PropertyAction { target: clippedSpreadDelegate; properties: "anchors.topMargin" }
231− PropertyAnimation {
232− target: spreadDelegate; properties: "x"
233− from: root.width
234− duration: spreadContainer.animateIn ? UbuntuAnimation.FastDuration :0
235− easing: UbuntuAnimation.StandardEasing
261+ SequentialAnimation {
262+ ParallelAnimation {
263+ PropertyAction { target: spreadDelegate; properties: "y,height,width,angle,z,itemScale,itemScaleOriginY,visible" }
264+ PropertyAction { target: clippedSpreadDelegate; properties: "anchors.topMargin" }
265+ PropertyAnimation {
266+ target: spreadDelegate; properties: "x"
267+ from: root.width
268+ duration: spreadContainer.animateIn ? UbuntuAnimation.FastDuration :0
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" }
236275 }
237276 }
238277 ]
@@ -240,7 +279,11 @@ FocusScope {
240279 MouseArea {
241280 id: tileInfo
242281 objectName: "tileInfo"
243− anchors { left: parent.left; top: clippedSpreadDelegate.bottom; topMargin: units.gu(5) }
282+ anchors {
283+ left: parent.left
284+ top: clippedSpreadDelegate.bottom
285+ topMargin: ((spreadMaths.sceneHeight - spreadDelegate.y) - clippedSpreadDelegate.height) * 0.2
286+ }
244287 property int nextItemX: spreadRepeater.count > index + 1 ? spreadRepeater.itemAt(index + 1).x : spreadDelegate.x + units.gu(30)
245288 width: Math.min(units.gu(30), nextItemX - spreadDelegate.x)
246289 height: titleInfoColumn.height
@@ -262,12 +305,20 @@ FocusScope {
262305 anchors { left: parent.left; top: parent.top; right: parent.right }
263306 spacing: units.gu(1)
264307
265− UbuntuShape {
308+ UbuntuShapeForItem {
266309 Layout.preferredHeight: Math.min(units.gu(6), root.height * .05)
267310 Layout.preferredWidth: height * 8 / 7.6
268311 image: Image {
269312 anchors.fill: parent
270313 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+ }
271322 }
272323 }
273324 Label {
@@ -420,7 +471,7 @@ FocusScope {
420471
421472 property var source: ShaderEffectSource {
422473 id: shaderEffectSource
423− sourceItem: appContainer
474+ sourceItem: root.workspace
424475 }
425476
426477 fragmentShader: "
@@ -494,20 +545,17 @@ FocusScope {
494545 from: "*"
495546 to: "altTab"
496547 SequentialAnimation {
497− PropertyAction { target: hoverMouseArea; property: "progressiveScrollingEnabled"; value: false }
498548 PropertyAction { target: spreadRepeater; property: "highlightedIndex"; value: Math.min(ApplicationManager.count - 1, 1) }
499− PauseAnimation { duration: 140 }
549+ PauseAnimation { duration: spreadContainer.animateIn ? 0 : 140 }
500550 PropertyAction { target: workspaceSelector; property: "visible" }
501551 PropertyAction { target: spreadContainer; property: "visible" }
502552 ParallelAnimation {
503− UbuntuNumberAnimation {
504− target: blurLayer; properties: "saturation,blurRadius";
505− duration: spreadContainer.animateIn ? UbuntuAnimation.FastDuration : 0
506− }
553+ UbuntuNumberAnimation { target: blurLayer; properties: "saturation,blurRadius"; duration: UbuntuAnimation.SnapDuration }
507554 PropertyAction { target: spreadFlickable; property: "visible" }
508555 PropertyAction { targets: [currentSelectedLabel,spreadBackground]; property: "visible" }
509556 PropertyAction { target: spreadFlickable; property: "contentX"; value: 0 }
510557 }
558+ PropertyAction { target: hoverMouseArea; properties: "enabled,progressiveScrollingEnabled"; value: false }
511559 }
512560 },
513561 Transition {
@@ -518,6 +566,5 @@ FocusScope {
518566 PropertyAction { target: spreadRepeater; property: "highlightedIndex"; value: -1 }
519567 PropertyAction { target: spreadContainer; property: "animateIn"; value: false }
520568 }
521−
522569 ]
523570}
qml/Stages/DesktopSpreadDelegate.qml+15−1
@@ -56,6 +56,11 @@ Item {
5656 }
5757 ]
5858
59+ scale: highlightShown ? 1.025 : 1
60+ Behavior on scale {
61+ UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration }
62+ }
63+
5964 BorderImage {
6065 anchors {
6166 fill: root
@@ -70,7 +75,7 @@ Item {
7075 anchors.fill: parent
7176 anchors.margins: -units.gu(1)
7277 color: "white"
73− opacity: highlightShown ? 0.15 : 0
78+ opacity: highlightShown ? 0.55 : 0
7479 antialiasing: true
7580 }
7681
@@ -108,4 +113,13 @@ Item {
108113 ]
109114 }
110115 }
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+ }
111125}
qml/Stages/DesktopStage.qml+34−34
@@ -224,6 +224,7 @@ AbstractStage {
224224 PanelState.dropShadow = false;
225225 }
226226
227+
227228 FocusScope {
228229 id: appContainer
229230 objectName: "appContainer"
@@ -274,6 +275,7 @@ AbstractStage {
274275 readonly property alias maximizedLeft: appDelegatePrivate.maximizedLeft
275276 readonly property alias maximizedRight: appDelegatePrivate.maximizedRight
276277 readonly property alias minimized: appDelegatePrivate.minimized
278+ readonly property alias fullscreen: decoratedWindow.fullscreen
277279
278280 readonly property string appId: model.appId
279281 property bool animationsEnabled: true
@@ -293,6 +295,7 @@ AbstractStage {
293295 visible: !visuallyMinimized &&
294296 !greeter.fullyShown &&
295297 (priv.foregroundMaximizedAppZ === -1 || priv.foregroundMaximizedAppZ <= z) ||
298+ decoratedWindow.fullscreen ||
296299 (spread.state == "altTab" && index === spread.highlightedIndex)
297300
298301 Binding {
@@ -348,13 +351,26 @@ AbstractStage {
348351 ApplicationManager.focusApplication(appId);
349352 }
350353
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+
351367 states: [
352368 State {
353369 name: "fullscreen"; when: decoratedWindow.fullscreen
354− extend: "maximized"
355370 PropertyChanges {
356371 target: appDelegate;
357− y: -PanelState.panelHeight
372+ x: 0; y: -PanelState.panelHeight
373+ requestedWidth: appContainer.width; requestedHeight: appContainer.height;
358374 }
359375 },
360376 State {
@@ -371,21 +387,21 @@ AbstractStage {
371387 name: "maximized"; when: appDelegate.maximized && !appDelegate.minimized
372388 PropertyChanges {
373389 target: appDelegate;
374− x: 0; y: 0;
375− requestedWidth: root.width; requestedHeight: root.height;
390+ x: root.leftMargin; y: 0;
391+ requestedWidth: appContainer.width - root.leftMargin; requestedHeight: appContainer.height;
376392 visuallyMinimized: false;
377393 visuallyMaximized: true
378394 }
379395 },
380396 State {
381397 name: "maximizedLeft"; when: appDelegate.maximizedLeft && !appDelegate.minimized
382− PropertyChanges { target: appDelegate; x: 0; y: PanelState.panelHeight;
383− requestedWidth: root.width/2; requestedHeight: root.height - PanelState.panelHeight }
398+ PropertyChanges { target: appDelegate; x: root.leftMargin; y: PanelState.panelHeight;
399+ requestedWidth: (appContainer.width - root.leftMargin)/2; requestedHeight: appContainer.height - PanelState.panelHeight }
384400 },
385401 State {
386402 name: "maximizedRight"; when: appDelegate.maximizedRight && !appDelegate.minimized
387− PropertyChanges { target: appDelegate; x: root.width/2; y: PanelState.panelHeight;
388− requestedWidth: root.width/2; requestedHeight: root.height - PanelState.panelHeight }
403+ PropertyChanges { target: appDelegate; x: (appContainer.width + root.leftMargin)/2; y: PanelState.panelHeight;
404+ requestedWidth: (appContainer.width - root.leftMargin)/2; requestedHeight: appContainer.height - PanelState.panelHeight }
389405 },
390406 State {
391407 name: "minimized"; when: appDelegate.minimized
@@ -438,7 +454,7 @@ AbstractStage {
438454 target: appDelegate
439455 property: "z"
440456 value: ApplicationManager.count + 1
441− when: index == spread.highlightedIndex && blurLayer.ready
457+ when: index == spread.highlightedIndex && spread.ready
442458 }
443459
444460 WindowResizeArea {
@@ -448,8 +464,9 @@ AbstractStage {
448464 minHeight: units.gu(10)
449465 borderThickness: units.gu(2)
450466 windowId: model.appId // FIXME: Change this to point to windowId once we have such a thing
451− screenWidth: root.width
452− screenHeight: root.height
467+ screenWidth: appContainer.width
468+ screenHeight: appContainer.height
469+ leftMargin: root.leftMargin
453470
454471 onPressed: { ApplicationManager.focusApplication(model.appId) }
455472 }
@@ -473,27 +490,6 @@ AbstractStage {
473490 }
474491 }
475492
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−
497493 EdgeBarrier {
498494 id: edgeBarrier
499495
@@ -509,7 +505,7 @@ AbstractStage {
509505 rotation: 90
510506 anchors.centerIn: parent
511507 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)}
513509 GradientStop { position: 1.0; color: Qt.rgba(0.16,0.16,0.16,0)}
514510 }
515511 }
@@ -527,9 +523,13 @@ AbstractStage {
527523 DesktopSpread {
528524 id: spread
529525 objectName: "spread"
530− anchors.fill: parent
526+ anchors.fill: appContainer
531527 workspace: appContainer
532528 focus: state == "altTab"
533529 altTabPressed: root.altTabPressed
530+
531+ onPlayFocusAnimation: {
532+ appRepeater.itemAt(index).playFocusAnimation();
533+ }
534534 }
535535}
qml/Stages/PhoneStage.qml+77−3
@@ -44,6 +44,33 @@ AbstractStage {
4444 }
4545 }
4646
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+
4774 // Functions to be called from outside
4875 function updateFocusedAppOrientation() {
4976 if (spreadRepeater.count > 0) {
@@ -74,6 +101,12 @@ AbstractStage {
74101 }
75102 }
76103
104+ function pushRightEdge(amount) {
105+ if (spreadView.contentX == -spreadView.shift) {
106+ edgeBarrier.push(amount);
107+ }
108+ }
109+
77110 mainApp: applicationManager.focusedApplicationId
78111 ? applicationManager.findApplication(applicationManager.focusedApplicationId)
79112 : null
@@ -180,6 +213,7 @@ AbstractStage {
180213
181214 property real oldInverseProgress: 0
182215 property bool animateX: false
216+ property int highlightIndex: 0
183217
184218 onFocusedAppDelegateChanged: {
185219 if (focusedAppDelegate) {
@@ -214,6 +248,10 @@ AbstractStage {
214248 spreadView.phase = 0;
215249 spreadView.contentX = -spreadView.shift;
216250 }
251+
252+ onHighlightIndexChanged: {
253+ spreadView.contentX = highlightIndex * spreadView.contentWidth / (spreadRepeater.count + 2)
254+ }
217255 }
218256 Timer {
219257 id: fullyShowingFocusedAppUpdateTimer
@@ -281,6 +319,11 @@ AbstractStage {
281319 }
282320 }
283321
322+ Behavior on contentX {
323+ enabled: root.altTabPressed
324+ UbuntuNumberAnimation {}
325+ }
326+
284327 onShiftedContentXChanged: {
285328 if (root.beingResized) {
286329 // Flickabe.contentX wiggles during resizes. Don't react to it.
@@ -319,12 +362,17 @@ AbstractStage {
319362 } else if (shiftedContentX < positionMarker3 * width) {
320363 snapTo(1);
321364 } else if (phase < 2){
322− // Add 1 pixel to make sure we definitely hit positionMarker4 even with rounding errors of the animation.
323− snapAnimation.targetContentX = width * positionMarker4 + 1 - shift;
324− snapAnimation.start();
365+ snapToSpread();
325366 root.opened();
326367 }
327368 }
369+
370+ function snapToSpread() {
371+ // Add 1 pixel to make sure we definitely hit positionMarker4 even with rounding errors of the animation.
372+ snapAnimation.targetContentX = (root.width * spreadView.positionMarker4) + 1 - spreadView.shift;
373+ snapAnimation.start();
374+ }
375+
328376 function snapTo(index) {
329377 if (!root.altTabEnabled) {
330378 // Reset to start instead
@@ -427,6 +475,7 @@ AbstractStage {
427475 maximizedAppTopMargin: root.maximizedAppTopMargin
428476 dropShadow: spreadView.active || priv.focusedAppDelegateIsDislocated
429477 focusFirstApp: root.focusFirstApp
478+ highlightShown: root.altTabPressed && index === priv.highlightIndex
430479
431480 readonly property bool isDash: model.appId == "unity8-dash"
432481
@@ -651,4 +700,29 @@ AbstractStage {
651700 }
652701 }
653702 }
703+
704+ EdgeBarrier {
705+ id: edgeBarrier
706+
707+ // NB: it does its own positioning according to the specified edge
708+ edge: Qt.RightEdge
709+
710+ onPassed: {
711+ spreadView.snapToSpread();
712+ }
713+ material: Component {
714+ Item {
715+ Rectangle {
716+ width: parent.height
717+ height: parent.width
718+ rotation: 90
719+ anchors.centerIn: parent
720+ gradient: Gradient {
721+ GradientStop { position: 0.0; color: Qt.rgba(0.16,0.16,0.16,0.7)}
722+ GradientStop { position: 1.0; color: Qt.rgba(0.16,0.16,0.16,0)}
723+ }
724+ }
725+ }
726+ }
727+ }
654728}
qml/Stages/SpreadDelegate.qml+20
@@ -43,6 +43,7 @@ FocusScope {
4343 property int shellOrientationAngle
4444 property int shellOrientation
4545 property QtObject orientations
46+ property bool highlightShown: false
4647
4748 function matchShellOrientation() {
4849 if (!root.application)
@@ -261,6 +262,25 @@ FocusScope {
261262 Behavior on opacity { UbuntuNumberAnimation {} }
262263 }
263264
265+ Rectangle {
266+ id: selectionHighlight
267+ objectName: "selectionHighlight"
268+ anchors.fill: appWindow
269+ anchors.margins: -units.gu(1)
270+ color: "white"
271+ opacity: root.highlightShown ? 0.15 : 0
272+ antialiasing: true
273+ visible: opacity > 0
274+ }
275+
276+ Rectangle {
277+ anchors { left: selectionHighlight.left; right: selectionHighlight.right; bottom: selectionHighlight.bottom; }
278+ height: units.dp(2)
279+ color: UbuntuColors.orange
280+ visible: root.highlightShown
281+ antialiasing: true
282+ }
283+
264284 ApplicationWindow {
265285 id: appWindow
266286 objectName: application ? "appWindow_" + application.appId : "appWindow_null"
qml/Stages/TabletStage.qml+83−3
@@ -70,6 +70,12 @@ AbstractStage {
7070 }
7171 }
7272
73+ function pushRightEdge(amount) {
74+ if (spreadView.contentX == -spreadView.shift) {
75+ edgeBarrier.push(amount);
76+ }
77+ }
78+
7379 orientationChangesEnabled: priv.mainAppOrientationChangesEnabled
7480
7581 supportedOrientations: mainApp ? mainApp.supportedOrientations
@@ -102,6 +108,38 @@ AbstractStage {
102108 priv.oldInverseProgress = inverseProgress;
103109 }
104110
111+ onAltTabPressedChanged: {
112+ if (!spreadEnabled) {
113+ return;
114+ }
115+ if (altTabPressed) {
116+ priv.highlightIndex = Math.min(spreadRepeater.count - 1, 1);
117+ spreadView.snapToSpread();
118+ } else {
119+ for (var i = 0; i < spreadRepeater.count; i++) {
120+ if (spreadRepeater.itemAt(i).zIndex === priv.highlightIndex) {
121+ spreadView.snapTo(i);
122+ return;
123+ }
124+ }
125+ }
126+ }
127+
128+ FocusScope {
129+ focus: root.altTabPressed
130+
131+ Keys.onPressed: {
132+ switch (event.key) {
133+ case Qt.Key_Tab:
134+ priv.highlightIndex = (priv.highlightIndex + 1) % spreadRepeater.count
135+ break;
136+ case Qt.Key_Backtab:
137+ priv.highlightIndex = (priv.highlightIndex + spreadRepeater.count - 1) % spreadRepeater.count
138+ break;
139+ }
140+ }
141+ }
142+
105143 QtObject {
106144 id: priv
107145 objectName: "stagesPriv"
@@ -130,6 +168,8 @@ AbstractStage {
130168
131169 property int oldInverseProgress: 0
132170
171+ property int highlightIndex: 0
172+
133173 onFocusedAppIdChanged: {
134174 if (priv.focusedAppId.length > 0) {
135175 var focusedApp = ApplicationManager.findApplication(focusedAppId);
@@ -185,6 +225,10 @@ AbstractStage {
185225 }
186226 return oneWayFlick;
187227 }
228+
229+ onHighlightIndexChanged: {
230+ spreadView.contentX = highlightIndex * spreadView.contentWidth / (spreadRepeater.count + 2)
231+ }
188232 }
189233
190234 Connections {
@@ -391,11 +435,16 @@ AbstractStage {
391435 } else if (shiftedContentX < phase1Width) {
392436 snapTo(1);
393437 } else {
394− // Add 1 pixel to make sure we definitely hit positionMarker4 even with rounding errors of the animation.
395− snapAnimation.targetContentX = spreadView.width * spreadView.positionMarker4 + 1 - shift;
396− snapAnimation.start();
438+ snapToSpread();
397439 }
398440 }
441+
442+ function snapToSpread() {
443+ // Add 1 pixel to make sure we definitely hit positionMarker4 even with rounding errors of the animation.
444+ snapAnimation.targetContentX = (spreadView.width * spreadView.positionMarker4) + 1 - shift;
445+ snapAnimation.start();
446+ }
447+
399448 function snapTo(index) {
400449 spreadView.selectedIndex = index;
401450 snapAnimation.targetContentX = -shift;
@@ -481,6 +530,11 @@ AbstractStage {
481530 }
482531 }
483532
533+ Behavior on contentX {
534+ enabled: root.altTabPressed
535+ UbuntuNumberAnimation {}
536+ }
537+
484538 MouseArea {
485539 id: spreadRow
486540 x: spreadView.contentX
@@ -611,6 +665,7 @@ AbstractStage {
611665 dragOffset: !isDash && model.appId == priv.mainStageAppId && root.inverseProgress > 0 && spreadView.phase === 0 ? root.inverseProgress : 0
612666 application: ApplicationManager.get(index)
613667 closeable: !isDash
668+ highlightShown: root.altTabPressed && priv.highlightIndex == zIndex
614669
615670 readonly property bool wantsMainStage: model.stage == ApplicationInfoInterface.MainStage
616671
@@ -805,4 +860,29 @@ AbstractStage {
805860 }
806861 }
807862 }
863+
864+ EdgeBarrier {
865+ id: edgeBarrier
866+
867+ // NB: it does its own positioning according to the specified edge
868+ edge: Qt.RightEdge
869+
870+ onPassed: {
871+ spreadView.snapToSpread();
872+ }
873+ material: Component {
874+ Item {
875+ Rectangle {
876+ width: parent.height
877+ height: parent.width
878+ rotation: 90
879+ anchors.centerIn: parent
880+ gradient: Gradient {
881+ GradientStop { position: 0.0; color: Qt.rgba(0.16,0.16,0.16,0.7)}
882+ GradientStop { position: 1.0; color: Qt.rgba(0.16,0.16,0.16,0)}
883+ }
884+ }
885+ }
886+ }
887+ }
808888}
qml/Stages/WindowResizeArea.qml+8−5
@@ -40,6 +40,7 @@ MouseArea {
4040 property int defaultHeight: units.gu(50)
4141 property int screenWidth: 0
4242 property int screenHeight: 0
43+ property int leftMargin: 0
4344
4445 QtObject {
4546 id: priv
@@ -72,10 +73,12 @@ MouseArea {
7273 var windowGeometry = windowStateStorage.getGeometry(root.windowId,
7374 Qt.rect(target.x, target.y, defaultWidth, defaultHeight));
7475
75− target.requestedWidth = Math.min(Math.max(windowGeometry.width, d.minimumWidth), screenWidth);
76− target.requestedHeight = Math.min(Math.max(windowGeometry.height, d.minimumHeight), root.screenHeight - PanelState.panelHeight);
77− target.x = Math.max(Math.min(windowGeometry.x, root.screenWidth - target.requestedWidth), 0)
78− target.y = Math.max(Math.min(windowGeometry.y, root.screenHeight - target.requestedHeight), PanelState.panelHeight)
76+ target.requestedWidth = Qt.binding(function() { return Math.min(Math.max(windowGeometry.width, d.minimumWidth), screenWidth - root.leftMargin); });
77+ target.requestedHeight = Qt.binding(function() { return Math.min(Math.max(windowGeometry.height, d.minimumHeight),
78+ root.screenHeight - (target.fullscreen ? 0 : PanelState.panelHeight)); });
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)); });
81+ target.y = Qt.binding(function() { return Math.max(Math.min(windowGeometry.y, root.screenHeight - target.requestedHeight), PanelState.panelHeight); });
7982
8083 var windowState = windowStateStorage.getState(root.windowId, WindowStateStorage.WindowStateNormal)
8184 if (windowState === WindowStateStorage.WindowStateMaximized) {
@@ -298,7 +301,7 @@ MouseArea {
298301 }
299302
300303 if (d.topBorder) {
301− var newTargetY = d.startY + deltaY;
304+ var newTargetY = Math.max(d.startY + deltaY, PanelState.panelHeight); // disallow resizing up past Panel
302305 var bottomBorderY = target.y + target.height;
303306 if (bottomBorderY > newTargetY + d.minimumHeight) {
304307 if (bottomBorderY < newTargetY + d.maximumHeight) {
src/CMakeLists.txt+1−1
@@ -33,7 +33,7 @@ endif()
3333
3434pkg_check_modules(ANDROID_PROPERTIES REQUIRED libandroid-properties)
3535add_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)
3737
3838if (NOT "${ANDROID_PROPERTIES_INCLUDE_DIRS}" STREQUAL "")
3939 set_target_properties(${SHELL_APP} PROPERTIES INCLUDE_DIRECTORIES ${ANDROID_PROPERTIES_INCLUDE_DIRS})
src/CachingNetworkManagerFactory.cpp+2−2
@@ -24,12 +24,12 @@
2424CachingNetworkAccessManager::CachingNetworkAccessManager(QObject *parent)
2525 : QNetworkAccessManager(parent)
2626{
27− m_networkingStatus = new ubuntu::connectivity::NetworkingStatus(this);
27+ m_networkingStatus = new connectivityqt::Connectivity(QDBusConnection::sessionBus(), this);
2828}
2929
3030QNetworkReply* CachingNetworkAccessManager::createRequest(Operation op, const QNetworkRequest &request, QIODevice *outgoingData)
3131{
32− if (m_networkingStatus->status() != ubuntu::connectivity::NetworkingStatus::Status::Online) {
32+ if (m_networkingStatus->status() != connectivityqt::Connectivity::Status::Online) {
3333 qDebug() << "Not connected to the internet. Request for" << request.url().toString() << "will be served only from the cache.";
3434 QNetworkRequest req(request);
3535 req.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysCache);
src/CachingNetworkManagerFactory.h+3−2
@@ -21,10 +21,11 @@
2121#include <QQmlNetworkAccessManagerFactory>
2222#include <QNetworkAccessManager>
2323
24−#include <ubuntu/connectivity/networking-status.h>
24+#include <connectivityqt/connectivity.h>
2525
2626class CachingNetworkAccessManager : public QNetworkAccessManager
2727{
28+Q_OBJECT
2829public:
2930 CachingNetworkAccessManager(QObject *parent = 0);
3031
@@ -32,7 +33,7 @@ protected:
3233 QNetworkReply* createRequest(Operation op, const QNetworkRequest &req, QIODevice *outgoingData = 0) override;
3334
3435private:
35− ubuntu::connectivity::NetworkingStatus* m_networkingStatus;
36+ connectivityqt::Connectivity* m_networkingStatus;
3637};
3738
3839class CachingNetworkManagerFactory : public QQmlNetworkAccessManagerFactory
src/Dash/CMakeLists.txt+1−1
@@ -9,7 +9,7 @@ endif()
99
1010add_executable(unity8-dash ${DASH_SRCS})
1111
12−qt5_use_modules(unity8-dash Gui Qml Quick Test)
12+qt5_use_modules(unity8-dash DBus Gui Qml Quick Test)
1313
1414# For it to find libUbuntuGestures.so, needed by Ubuntu.Gestures QML module.
1515set_target_properties(unity8-dash PROPERTIES INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${SHELL_PRIVATE_LIBDIR}")
tests/autopilot/unity8/dash.py+1−2
@@ -211,8 +211,7 @@ class GenericScopeView(ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase):
211211 self.get_root_instance().select_single(
212212 objectName='processingIndicator').visible.wait_for(False)
213213 return preview_list.select_single(
214− Preview, objectName='preview{}'.format(
215− preview_list.initialIndex))
214+ Preview, objectName='preview')
216215
217216 @autopilot_logging.log_action(logger.debug)
218217 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):
117117 def test_open_preview(self):
118118 preview = self.generic_scope.open_preview('0', 'Title.0.0')
119119 self.assertIsInstance(preview, dash_helpers.Preview)
120− self.assertTrue(preview.isCurrent)
121120
122121 def test_open_preview_of_non_visible_item(self):
123122 """Open an item that requires swiping to make it visible."""
124123 preview = self.generic_scope.open_preview('2', 'Title.2.0')
125124 self.assertIsInstance(preview, dash_helpers.Preview)
126− self.assertTrue(preview.isCurrent)
127125
128126
129127class DashAppsHelperTestCase(tests.DashBaseTestCase):
tests/mocks/GSettings.1.0/fake_gsettings.cpp+64
@@ -22,6 +22,8 @@ GSettingsControllerQml* GSettingsControllerQml::s_controllerInstance = 0;
2222
2323GSettingsControllerQml::GSettingsControllerQml()
2424 : m_usageMode("Staged")
25+ , m_autohideLauncher(false)
26+ , m_launcherWidth(8)
2527{
2628}
2729
@@ -88,6 +90,32 @@ void GSettingsControllerQml::setLifecycleExemptAppids(const QStringList &appIds)
8890 }
8991}
9092
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+
91119GSettingsSchemaQml::GSettingsSchemaQml(QObject *parent): QObject(parent) {
92120}
93121
@@ -129,6 +157,10 @@ GSettingsQml::GSettingsQml(QObject *parent)
129157 this, &GSettingsQml::lockedOutTimeChanged);
130158 connect(GSettingsControllerQml::instance(), &GSettingsControllerQml::lifecycleExemptAppidsChanged,
131159 this, &GSettingsQml::lifecycleExemptAppidsChanged);
160+ connect(GSettingsControllerQml::instance(), &GSettingsControllerQml::autohideLauncherChanged,
161+ this, &GSettingsQml::autohideLauncherChanged);
162+ connect(GSettingsControllerQml::instance(), &GSettingsControllerQml::launcherWidthChanged,
163+ this, &GSettingsQml::launcherWidthChanged);
132164}
133165
134166GSettingsSchemaQml * GSettingsQml::schema() const {
@@ -192,9 +224,41 @@ QStringList GSettingsQml::lifecycleExemptAppids() const
192224 }
193225}
194226
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+
195245void GSettingsQml::setLifecycleExemptAppids(const QStringList &appIds)
196246{
197247 if (m_schema->id() == "com.canonical.qtmir") {
198248 GSettingsControllerQml::instance()->setLifecycleExemptAppids(appIds);
199249 }
200250}
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
5050 Q_PROPERTY(QString usageMode READ usageMode WRITE setUsageMode NOTIFY usageModeChanged)
5151 Q_PROPERTY(qint64 lockedOutTime READ lockedOutTime WRITE setLockedOutTime NOTIFY lockedOutTimeChanged)
5252 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)
5355
5456public:
5557 GSettingsQml(QObject *parent = nullptr);
@@ -59,11 +61,15 @@ public:
5961 QString usageMode() const;
6062 qint64 lockedOutTime() const;
6163 QStringList lifecycleExemptAppids() const;
64+ bool autohideLauncher() const;
65+ int launcherWidth() const;
6266
6367 void setPictureUri(const QString &str);
6468 void setUsageMode(const QString &usageMode);
6569 void setLockedOutTime(qint64 timestamp);
6670 void setLifecycleExemptAppids(const QStringList &appIds);
71+ void setAutohideLauncher(bool autohideLauncher);
72+ void setLauncherWidth(int launcherWidth);
6773
6874Q_SIGNALS:
6975 void schemaChanged();
@@ -71,6 +77,8 @@ Q_SIGNALS:
7177 void usageModeChanged(const QString&);
7278 void lockedOutTimeChanged(qint64);
7379 void lifecycleExemptAppidsChanged(const QStringList &);
80+ void autohideLauncherChanged(bool);
81+ void launcherWidthChanged(int launcherWidth);
7482
7583private:
7684 GSettingsSchemaQml* m_schema;
@@ -98,11 +106,19 @@ public:
98106 QStringList lifecycleExemptAppids() const;
99107 Q_INVOKABLE void setLifecycleExemptAppids(const QStringList &appIds);
100108
109+ bool autohideLauncher() const;
110+ Q_INVOKABLE void setAutohideLauncher(bool autohideLauncher);
111+
112+ int launcherWidth() const;
113+ Q_INVOKABLE void setLauncherWidth(int launcherWidth);
114+
101115Q_SIGNALS:
102116 void pictureUriChanged(const QString&);
103117 void usageModeChanged(const QString&);
104118 void lockedOutTimeChanged(qint64 timestamp);
105119 void lifecycleExemptAppidsChanged(const QStringList&);
120+ void autohideLauncherChanged(bool autohideLauncher);
121+ void launcherWidthChanged(int launcherWidth);
106122
107123private:
108124 GSettingsControllerQml();
@@ -111,6 +127,8 @@ private:
111127 QString m_usageMode;
112128 qint64 m_lockedOutTime;
113129 QStringList m_lifecycleExemptAppids;
130+ bool m_autohideLauncher;
131+ int m_launcherWidth;
114132
115133 static GSettingsControllerQml* s_controllerInstance;
116134 QList<GSettingsQml *> m_registeredGSettings;
tests/mocks/IntegratedLightDM/liblightdm/UsersModel.cpp−14
@@ -47,20 +47,6 @@ UsersModel::UsersModel(QObject *parent) :
4747 roles[HasMessagesRole] = "hasMessages";
4848 roles[ImagePathRole] = "imagePath";
4949 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− }
6450}
6551
6652UsersModel::~UsersModel()
tests/mocks/Unity/Application/MirSurface.cpp+6−2
@@ -17,13 +17,15 @@
1717#include "MirSurface.h"
1818
1919#include <QDebug>
20+#include <QQmlEngine>
2021
2122MirSurface::MirSurface(const QString& name,
2223 Mir::Type type,
2324 Mir::State state,
2425 const QUrl& screenshot,
25− const QUrl &qmlFilePath)
26− : unity::shell::application::MirSurfaceInterface(nullptr)
26+ const QUrl &qmlFilePath,
27+ QObject *parent)
28+ : unity::shell::application::MirSurfaceInterface(parent)
2729 , m_name(name)
2830 , m_type(type)
2931 , m_state(state)
@@ -38,6 +40,8 @@ MirSurface::MirSurface(const QString& name,
3840 , m_slowToResize(false)
3941{
4042// qDebug() << "MirSurface::MirSurface() " << name;
43+ QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership);
44+
4145 m_delayedResizeTimer.setInterval(600);
4246 m_delayedResizeTimer.setSingleShot(true);
4347 connect(&m_delayedResizeTimer, &QTimer::timeout, this, &MirSurface::applyDelayedResize);
tests/mocks/Unity/Application/MirSurface.h+2−1
@@ -41,7 +41,8 @@ public:
4141 Mir::Type type,
4242 Mir::State state,
4343 const QUrl& screenshot,
44− const QUrl &qmlFilePath = QUrl());
44+ const QUrl &qmlFilePath = QUrl(),
45+ QObject *parent = nullptr);
4546 virtual ~MirSurface();
4647
4748 ////
tests/mocks/Unity/Application/SurfaceManager.cpp+8−9
@@ -34,6 +34,13 @@ SurfaceManager::SurfaceManager(QObject *parent) :
3434 QObject(parent)
3535 , m_virtualKeyboard(nullptr)
3636{
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+ });
3744}
3845
3946MirSurface *SurfaceManager::createSurface(const QString& name,
@@ -58,16 +65,8 @@ MirSurface *SurfaceManager::createSurface(const QString& name,
5865 return surface;
5966}
6067
61−MirSurface *SurfaceManager::inputMethodSurface()
68+MirSurface *SurfaceManager::inputMethodSurface() const
6269{
63− if (!m_virtualKeyboard) {
64− m_virtualKeyboard = new VirtualKeyboard;
65− connect(m_virtualKeyboard, &QObject::destroyed, this, [this](QObject *obj) {
66− MirSurface* surface = qobject_cast<MirSurface*>(obj);
67− Q_EMIT surfaceDestroyed(surface);
68− });
69− Q_EMIT surfaceCreated(m_virtualKeyboard);
70− }
7170 return m_virtualKeyboard;
7271}
7372
tests/mocks/Unity/Application/SurfaceManager.h+3−3
@@ -25,7 +25,7 @@
2525class SurfaceManager : public QObject
2626{
2727 Q_OBJECT
28−
28+ Q_PROPERTY(MirSurface* inputMethodSurface READ inputMethodSurface NOTIFY inputMethodSurfaceChanged)
2929 Q_PROPERTY(int newSurfaceMinimumWidth READ newSurfaceMinimumWidth WRITE setNewSurfaceMinimumWidth NOTIFY newSurfaceMinimumWidthChanged)
3030 Q_PROPERTY(int newSurfaceMaximumWidth READ newSurfaceMaximumWidth WRITE setNewSurfaceMaximumWidth NOTIFY newSurfaceMaximumWidthChanged)
3131 Q_PROPERTY(int newSurfaceMinimumHeight READ newSurfaceMinimumHeight WRITE setNewSurfaceMinimumHeight NOTIFY newSurfaceMinimumHeightChanged)
@@ -43,8 +43,7 @@ public:
4343 Mir::State state,
4444 const QUrl& screenshot);
4545
46− // To be used in the tests
47− Q_INVOKABLE MirSurface* inputMethodSurface();
46+ MirSurface* inputMethodSurface() const;
4847
4948 int newSurfaceMinimumWidth() const { return m_newSurfaceMinimumWidth; }
5049 void setNewSurfaceMinimumWidth(int value);
@@ -65,6 +64,7 @@ public:
6564 void setNewSurfaceHeightIncrement(int);
6665
6766Q_SIGNALS:
67+ void inputMethodSurfaceChanged();
6868 void countChanged();
6969 void surfaceCreated(MirSurface *surface);
7070 void surfaceDestroyed(MirSurface*surface);
tests/mocks/Unity/Application/VirtualKeyboard.cpp+3−2
@@ -22,12 +22,13 @@
2222
2323#include <QDebug>
2424
25−VirtualKeyboard::VirtualKeyboard()
25+VirtualKeyboard::VirtualKeyboard(QObject *parent)
2626 : MirSurface("input-method",
2727 Mir::InputMethodType,
2828 Mir::MinimizedState,
2929 QUrl("qrc:///Unity/Application/vkb_portrait.png"),
30− QUrl("qrc:///Unity/Application/VirtualKeyboard.qml"))
30+ QUrl("qrc:///Unity/Application/VirtualKeyboard.qml"),
31+ parent)
3132{
3233}
3334
tests/mocks/Unity/Application/VirtualKeyboard.h+1−1
@@ -25,7 +25,7 @@ class VirtualKeyboard : public MirSurface
2525{
2626 Q_OBJECT
2727public:
28− explicit VirtualKeyboard();
28+ explicit VirtualKeyboard(QObject *parent = nullptr);
2929 ~VirtualKeyboard();
3030};
3131
tests/mocks/Unity/Application/plugin.cpp+2−1
@@ -78,8 +78,9 @@ void FakeUnityApplicationQmlPlugin::registerTypes(const char *uri)
7878 qmlRegisterUncreatableType<unity::shell::application::ApplicationInfoInterface>(uri, 0, 1, "ApplicationInfoInterface", "Abstract interface. Cannot be created in QML");
7979 qmlRegisterUncreatableType<Session>(uri, 0, 1, "Session", "Session can't be instantiated from QML");
8080 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");
8183 qmlRegisterType<MirSurfaceItem>(uri, 0, 1, "MirSurfaceItem");
82−
8384 qmlRegisterType<ApplicationInfo>(uri, 0, 1, "ApplicationInfo");
8485
8586 qmlRegisterSingletonType<ApplicationManager>(uri, 0, 1, "ApplicationManager", applicationManagerSingleton);
tests/mocks/Unity/Launcher/MockLauncherModel.cpp+2
@@ -25,6 +25,7 @@ MockLauncherModel::MockLauncherModel(QObject* parent): LauncherModelInterface(pa
2525 MockLauncherItem *item = new MockLauncherItem("dialer-app", "/usr/share/applications/dialer-app.desktop", "Dialer", "dialer-app", this);
2626 item->setProgress(0);
2727 item->setPinned(true);
28+ item->setRunning(true);
2829 item->setFocused(true);
2930 m_list.append(item);
3031 item = new MockLauncherItem("camera-app", "/usr/share/applications/camera-app.desktop", "Camera", "camera", this);
@@ -34,6 +35,7 @@ MockLauncherModel::MockLauncherModel(QObject* parent): LauncherModelInterface(pa
3435 item = new MockLauncherItem("gallery-app", "/usr/share/applications/gallery-app.desktop", "Gallery", "gallery", this);
3536 item->setProgress(50);
3637 item->setCountVisible(true);
38+ item->setRunning(true);
3739 item->setAlerting(false);
3840 m_list.append(item);
3941 item = new MockLauncherItem("music-app", "/usr/share/applications/music-app.desktop", "Music", "soundcloud", this);
tests/mocks/Utils/CMakeLists.txt+1
@@ -19,6 +19,7 @@ set(QMLPLUGIN_SRC
1919 ${CMAKE_SOURCE_DIR}/plugins/Utils/timezoneFormatter.cpp
2020 ${CMAKE_SOURCE_DIR}/plugins/Utils/applicationsfiltermodel.cpp
2121 ${CMAKE_SOURCE_DIR}/plugins/Utils/inputeventgenerator.cpp
22+ ${CMAKE_SOURCE_DIR}/plugins/Utils/deviceconfigparser.cpp
2223 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationManagerInterface.h
2324 constants.cpp
2425 plugin.cpp
tests/mocks/Utils/plugin.cpp+2
@@ -38,6 +38,7 @@
3838#include <timezoneFormatter.h>
3939#include <applicationsfiltermodel.h>
4040#include <inputeventgenerator.h>
41+#include <deviceconfigparser.h>
4142
4243static QObject *createWindowStateStorage(QQmlEngine *engine, QJSEngine *scriptEngine)
4344{
@@ -70,6 +71,7 @@ void FakeUtilsPlugin::registerTypes(const char *uri)
7071 qmlRegisterType<ActiveFocusLogger>(uri, 0, 1, "ActiveFocusLogger");
7172 qmlRegisterType<ApplicationsFilterModel>(uri, 0, 1, "ApplicationsFilterModel");
7273 qmlRegisterType<InputEventGenerator>(uri, 0, 1, "InputEventGenerator");
74+ qmlRegisterType<DeviceConfigParser>(uri, 0, 1, "DeviceConfigParser");
7375}
7476
7577void FakeUtilsPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
tests/plugins/AccountsService/CMakeLists.txt+5
@@ -10,11 +10,13 @@ macro(make_dbus_class NAME INTERFACE)
1010endmacro(make_dbus_class)
1111
1212make_dbus_class(Accounts org.freedesktop.Accounts)
13+make_dbus_class(Input com.ubuntu.AccountsService.Input)
1314make_dbus_class(Properties org.freedesktop.DBus.Properties)
1415make_dbus_class(SecurityPrivacy com.ubuntu.touch.AccountsService.SecurityPrivacy)
1516make_dbus_class(Location com.ubuntu.location.providers.here.AccountsService)
1617make_dbus_class(AccountsUser org.freedesktop.Accounts.User)
1718make_dbus_class(AccountsPrivate com.canonical.unity.AccountsService.Private)
19+make_dbus_class(UscInput com.canonical.Unity.Input)
1820
1921include_directories(
2022 ${CMAKE_CURRENT_BINARY_DIR}
@@ -27,12 +29,15 @@ add_executable(mock-server
2729 ${CMAKE_CURRENT_BINARY_DIR}/AccountsAdaptor.cpp
2830 ${CMAKE_CURRENT_BINARY_DIR}/AccountsPrivateAdaptor.cpp
2931 ${CMAKE_CURRENT_BINARY_DIR}/AccountsUserAdaptor.cpp
32+ ${CMAKE_CURRENT_BINARY_DIR}/InputAdaptor.cpp
3033 ${CMAKE_CURRENT_BINARY_DIR}/PropertiesAdaptor.cpp
3134 ${CMAKE_CURRENT_BINARY_DIR}/LocationAdaptor.cpp
3235 ${CMAKE_CURRENT_BINARY_DIR}/SecurityPrivacyAdaptor.cpp
36+ ${CMAKE_CURRENT_BINARY_DIR}/UscInputAdaptor.cpp
3337 server.cpp
3438 AccountsServer.cpp
3539 PropertiesServer.cpp
40+ UscServer.cpp
3641 )
3742qt5_use_modules(mock-server Core DBus)
3843
tests/plugins/AccountsService/PropertiesServer.cpp+16−4
@@ -32,9 +32,9 @@ PropertiesServer::PropertiesServer(QObject *parent)
3232 Reset();
3333}
3434
35−QDBusVariant PropertiesServer::Get(const QString &interface, const QString &property)
35+QDBusVariant PropertiesServer::Get(const QString &interface, const QString &property) const
3636{
37− if (m_properties[interface].contains(property)) {
37+ if (m_properties.contains(interface) && m_properties[interface].contains(property)) {
3838 return QDBusVariant(m_properties[interface][property]);
3939 } else {
4040 sendErrorReply(QDBusError::InvalidArgs, "Bad interface or property");
@@ -42,6 +42,16 @@ QDBusVariant PropertiesServer::Get(const QString &interface, const QString &prop
4242 }
4343}
4444
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+
4555void PropertiesServer::Set(const QString &interface, const QString &property, const QDBusVariant &variant)
4656{
4757 QVariant newValue = variant.variant();
@@ -57,8 +67,8 @@ void PropertiesServer::Set(const QString &interface, const QString &property, co
5767
5868 oldValue = newValue;
5969
60− // Special case for Background file.
61− if (interface == "org.freedesktop.Accounts.User" && property == "BackgroundFile") {
70+ // Special case for user properties.
71+ if (interface == "org.freedesktop.Accounts.User") {
6272 Q_EMIT Changed();
6373 } else {
6474 QVariantMap propertyChanges;
@@ -82,10 +92,12 @@ void PropertiesServer::Reset()
8292 m_properties["com.canonical.unity.AccountsService"]["LauncherItems"] = QVariant::fromValue(QList<QVariantMap>());
8393 m_properties["com.canonical.unity.AccountsService.Private"]["FailedLogins"] = 0;
8494 m_properties["com.ubuntu.touch.AccountsService.SecurityPrivacy"]["StatsWelcomeScreen"] = true;
95+ m_properties["com.ubuntu.AccountsService.Input"]["MousePrimaryButton"] = "right";
8596 m_properties["com.ubuntu.AccountsService.SecurityPrivacy"]["EnableLauncherWhileLocked"] = true;
8697 m_properties["com.ubuntu.AccountsService.SecurityPrivacy"]["EnableIndicatorsWhileLocked"] = true;
8798 m_properties["com.ubuntu.AccountsService.SecurityPrivacy"]["PasswordDisplayHint"] = AccountsService::Keyboard;
8899 m_properties["com.ubuntu.location.providers.here.AccountsService"]["LicenseAccepted"] = false;
89100 m_properties["com.ubuntu.location.providers.here.AccountsService"]["LicenseBasePath"] = "";
90101 m_properties["org.freedesktop.Accounts.User"]["BackgroundFile"] = "";
102+ m_properties["org.freedesktop.Accounts.User"]["RealName"] = "";
91103}
tests/plugins/AccountsService/PropertiesServer.h+2−1
@@ -34,7 +34,8 @@ public:
3434 explicit PropertiesServer(QObject *parent = 0);
3535
3636public 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;
3839 void Set(const QString &interface, const QString &property, const QDBusVariant &variant);
3940
4041 // 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:
4242 : QObject(parent)
4343 , m_userInterface(nullptr)
4444 , m_spy(this, &AccountsServiceTest::propertiesChanged)
45+ , m_mousePrimaryButtonSpy(this, &AccountsServiceTest::setMousePrimaryButtonCalled)
4546 {
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)));
4655 }
4756
4857private Q_SLOTS:
@@ -71,6 +80,7 @@ private Q_SLOTS:
7180
7281 delete m_userInterface;
7382 m_spy.clear();
83+ m_mousePrimaryButtonSpy.clear();
7484 }
7585
7686 void testInvalids()
@@ -107,7 +117,7 @@ private Q_SLOTS:
107117 QCOMPARE(session.hereEnabled(), true);
108118 }
109119
110− void testAsynchornousChangeForDemoEdges()
120+ void testAsynchronousChangeForDemoEdges()
111121 {
112122 AccountsService session(this, QTest::currentTestFunction());
113123
@@ -119,7 +129,7 @@ private Q_SLOTS:
119129 QTRY_COMPARE(session.demoEdges(), true);
120130 }
121131
122− void testAsynchornousChangeForFailedLogins()
132+ void testAsynchronousChangeForFailedLogins()
123133 {
124134 AccountsService session(this, QTest::currentTestFunction());
125135
@@ -131,7 +141,7 @@ private Q_SLOTS:
131141 QTRY_COMPARE(session.failedLogins(), (uint)5);
132142 }
133143
134− void testAsynchornousChangeForStatsWelcomeScreen()
144+ void testAsynchronousChangeForStatsWelcomeScreen()
135145 {
136146 AccountsService session(this, QTest::currentTestFunction());
137147
@@ -143,7 +153,7 @@ private Q_SLOTS:
143153 QTRY_COMPARE(session.statsWelcomeScreen(), false);
144154 }
145155
146− void testAsynchornousChangeForStatsEnableLauncherWhileLocked()
156+ void testAsynchronousChangeForEnableLauncherWhileLocked()
147157 {
148158 AccountsService session(this, QTest::currentTestFunction());
149159
@@ -155,7 +165,7 @@ private Q_SLOTS:
155165 QTRY_COMPARE(session.enableLauncherWhileLocked(), false);
156166 }
157167
158− void testAsynchornousChangeForStatsEnableIndicatorsWhileLocked()
168+ void testAsynchronousChangeForEnableIndicatorsWhileLocked()
159169 {
160170 AccountsService session(this, QTest::currentTestFunction());
161171
@@ -167,7 +177,7 @@ private Q_SLOTS:
167177 QTRY_COMPARE(session.enableIndicatorsWhileLocked(), false);
168178 }
169179
170− void testAsynchornousChangeForStatsPasswordDisplayHint()
180+ void testAsynchronousChangeForPasswordDisplayHint()
171181 {
172182 AccountsService session(this, QTest::currentTestFunction());
173183
@@ -179,7 +189,7 @@ private Q_SLOTS:
179189 QTRY_COMPARE(session.passwordDisplayHint(), AccountsService::Numeric);
180190 }
181191
182− void testAsynchornousChangeForStatsLicenseAccepted()
192+ void testAsynchronousChangeForLicenseAccepted()
183193 {
184194 AccountsService session(this, QTest::currentTestFunction());
185195
@@ -191,7 +201,7 @@ private Q_SLOTS:
191201 QTRY_COMPARE(session.hereEnabled(), true);
192202 }
193203
194− void testAsynchornousChangeForLicenseBasePath()
204+ void testAsynchronousChangeForLicenseBasePath()
195205 {
196206 AccountsService session(this, QTest::currentTestFunction());
197207
@@ -203,7 +213,7 @@ private Q_SLOTS:
203213 QTRY_COMPARE(session.hereLicensePath(), QString("/"));
204214 }
205215
206− void testAsynchornousChangeForStatsBackgroundFile()
216+ void testAsynchronousChangeForBackgroundFile()
207217 {
208218 AccountsService session(this, QTest::currentTestFunction());
209219
@@ -215,12 +225,56 @@ private Q_SLOTS:
215225 QTRY_COMPARE(session.backgroundFile(), QString("/test/BackgroundFile"));
216226 }
217227
228+ void testProxyOnStartup()
229+ {
230+ AccountsService session(this, QTest::currentTestFunction());
231+
232+ QTRY_COMPARE(m_mousePrimaryButtonSpy.count(), 1);
233+ QList<QVariant> arguments = m_mousePrimaryButtonSpy.takeFirst();
234+ QCOMPARE(arguments.at(0).toInt(), 1);
235+ }
236+
237+ void testProxyOnChange()
238+ {
239+ AccountsService session(this, QTest::currentTestFunction());
240+ QTRY_COMPARE(m_mousePrimaryButtonSpy.count(), 1);
241+ m_mousePrimaryButtonSpy.clear();
242+
243+ ASSERT_DBUS_CALL(m_userInterface->asyncCall("Set",
244+ "com.ubuntu.AccountsService.Input",
245+ "MousePrimaryButton",
246+ dbusVariant("left")));
247+
248+ QTRY_COMPARE(m_mousePrimaryButtonSpy.count(), 1);
249+ QList<QVariant> arguments = m_mousePrimaryButtonSpy.takeFirst();
250+ QCOMPARE(arguments.at(0).toInt(), 0);
251+ }
252+
253+ void testInvalidPrimaryButton()
254+ {
255+ AccountsService session(this, QTest::currentTestFunction());
256+ QTRY_COMPARE(m_mousePrimaryButtonSpy.count(), 1);
257+ m_mousePrimaryButtonSpy.clear();
258+
259+ ASSERT_DBUS_CALL(m_userInterface->asyncCall("Set",
260+ "com.ubuntu.AccountsService.Input",
261+ "MousePrimaryButton",
262+ dbusVariant("NOPE")));
263+
264+ QTRY_COMPARE(m_mousePrimaryButtonSpy.count(), 1);
265+ QList<QVariant> arguments = m_mousePrimaryButtonSpy.takeFirst();
266+ QCOMPARE(arguments.at(0).toInt(), 0);
267+ }
268+
218269Q_SIGNALS:
219270 void propertiesChanged(const QString &interface, const QVariantMap &changed, const QStringList &invalid);
271+ void setMousePrimaryButtonCalled(int button);
220272
221273private:
274+ QDBusInterface* m_uscInputInterface;
222275 QDBusInterface* m_userInterface;
223276 QSignalSpy m_spy;
277+ QSignalSpy m_mousePrimaryButtonSpy;
224278};
225279
226280QTEST_MAIN(AccountsServiceTest)
tests/plugins/AccountsService/interfaces.xml+16
@@ -5,6 +5,11 @@
55 <arg name="property" type="s" direction="in" />
66 <arg name="value" type="v" direction="out" />
77 </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>
813 <method name="Set">
914 <arg name="interface" type="s" direction="in" />
1015 <arg name="property" type="s" direction="in" />
@@ -32,10 +37,21 @@
3237 <arg name="success" type="b" direction="out" />
3338 </method>
3439 </interface>
40+ <interface name="com.ubuntu.AccountsService.Input" />
3541 <interface name="com.ubuntu.touch.AccountsService.SecurityPrivacy" />
3642 <interface name="com.ubuntu.location.providers.here.AccountsService" />
3743 <interface name="org.freedesktop.Accounts.User">
3844 <signal name="Changed" />
3945 </interface>
4046 <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>
4157</node>
tests/plugins/AccountsService/server.cpp+12
@@ -19,10 +19,13 @@
1919
2020#include "AccountsAdaptor.h"
2121#include "AccountsServer.h"
22+#include "InputAdaptor.h"
2223#include "PropertiesServer.h"
2324#include "SecurityPrivacyAdaptor.h"
2425#include "LocationAdaptor.h"
2526#include "AccountsPrivateAdaptor.h"
27+#include "UscInputAdaptor.h"
28+#include "UscServer.h"
2629#include <QCoreApplication>
2730
2831int main(int argc, char *argv[])
@@ -31,11 +34,20 @@ int main(int argc, char *argv[])
3134
3235 auto accounts = new AccountsServer(&a);
3336 new AccountsAdaptor(accounts);
37+ new InputAdaptor(accounts);
3438 new SecurityPrivacyAdaptor(accounts);
3539 new LocationAdaptor(accounts);
3640 new AccountsPrivateAdaptor(accounts);
41+
42+ auto usc = new UscServer(&a);
43+ new UscInputAdaptor(usc);
44+
3745 // We use the session bus for testing. The real plugin uses the system bus
3846 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;
3951 if (!connection.registerObject("/org/freedesktop/Accounts", accounts))
4052 return 1;
4153 if (!connection.registerService("org.freedesktop.Accounts"))
tests/plugins/Dash/cardcreator/1.res+4−6
@@ -5,12 +5,10 @@ AbstractButton {
55 property string backgroundShapeStyle: "inset";
66 property real fontScale: 1.0;
77 property var scopeStyle: null;
8− property int titleAlignment: Text.AlignLeft;
98 property int fixedHeaderHeight: -1;
109 property size fixedArtShapeSize: Qt.size(-1, -1);
1110 readonly property string title: cardData && cardData["title"] || "";
12− property bool asynchronous: true;
13− property bool showHeader: true;
11+ property bool showHeader: true;
1412 implicitWidth: childrenRect.width;
1513 enabled: true;
1614
@@ -25,7 +23,7 @@ Item {
2523 objectName: "artShapeLoader";
2624 readonly property string cardArt: cardData && cardData["art"] || "";
2725 active: cardArt != "";
28− asynchronous: root.asynchronous;
26+ asynchronous: true;
2927 visible: status == Loader.Ready;
3028 sourceComponent: Item {
3129 id: artShape;
@@ -82,7 +80,7 @@ Item {
8280 id: artImage;
8381 objectName: "artImage";
8482 source: artShapeLoader.cardArt;
85− asynchronous: root.asynchronous;
83+ asynchronous: true;
8684 width: root.width;
8785 height: width / artShape.aspect;
8886 }
@@ -108,7 +106,7 @@ Label {
108106 width: undefined;
109107 text: root.title;
110108 font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
111− horizontalAlignment: root.titleAlignment;
109+ horizontalAlignment: Text.AlignHCenter;
112110 }
113111UbuntuShape {
114112 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 {
55 property string backgroundShapeStyle: "inset";
66 property real fontScale: 1.0;
77 property var scopeStyle: null;
8− property int titleAlignment: Text.AlignLeft;
98 property int fixedHeaderHeight: -1;
109 property size fixedArtShapeSize: Qt.size(-1, -1);
1110 readonly property string title: cardData && cardData["title"] || "";
12− property bool asynchronous: true;
1311 property bool showHeader: true;
1412 implicitWidth: childrenRect.width;
1513 enabled: true;
@@ -18,7 +16,7 @@ Loader {
1816 id: backgroundLoader;
1917 objectName: "backgroundLoader";
2018 anchors.fill: parent;
21− asynchronous: root.asynchronous;
19+ asynchronous: true;
2220 visible: status == Loader.Ready;
2321 sourceComponent: UbuntuShape {
2422 objectName: "background";
@@ -59,7 +57,7 @@ Row {
5957 objectName: "outerRow";
6058 property real margins: units.gu(1);
6159 spacing: margins;
62− height: root.fixedHeaderHeight != -1 ? root.fixedHeaderHeight : implicitHeight;
60+ height: root.fixedHeaderHeight;
6361 anchors { top: parent.top;
6462 topMargin: units.gu(1);
6563left: parent.left;
@@ -103,7 +101,7 @@ left: parent.left;
103101 width: undefined;
104102 text: root.title;
105103 font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
106− horizontalAlignment: root.titleAlignment;
104+ horizontalAlignment: Text.AlignLeft;
107105 }
108106,Label {
109107 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 {
55 property string backgroundShapeStyle: "inset";
66 property real fontScale: 1.0;
77 property var scopeStyle: null;
8− property int titleAlignment: Text.AlignLeft;
98 property int fixedHeaderHeight: -1;
109 property size fixedArtShapeSize: Qt.size(-1, -1);
1110 readonly property string title: cardData && cardData["title"] || "";
12− property bool asynchronous: true;
1311 property bool showHeader: true;
1412 implicitWidth: childrenRect.width;
1513 enabled: true;
@@ -18,7 +16,7 @@ Loader {
1816 id: backgroundLoader;
1917 objectName: "backgroundLoader";
2018 anchors.fill: parent;
21− asynchronous: root.asynchronous;
19+ asynchronous: true;
2220 visible: status == Loader.Ready;
2321 sourceComponent: UbuntuShape {
2422 objectName: "background";
@@ -63,7 +61,7 @@ Item {
6361 objectName: "artShapeLoader";
6462 readonly property string cardArt: cardData && cardData["art"] || decodeURI("%5C");
6563 active: cardArt != "";
66− asynchronous: root.asynchronous;
64+ asynchronous: true;
6765 visible: status == Loader.Ready;
6866 sourceComponent: Item {
6967 id: artShape;
@@ -120,7 +118,7 @@ Item {
120118 id: artImage;
121119 objectName: "artImage";
122120 source: artShapeLoader.cardArt;
123− asynchronous: root.asynchronous;
121+ asynchronous: true;
124122 width: root.width;
125123 height: width / artShape.aspect;
126124 onStatusChanged: if (status === Image.Error) source = decodeURI("%5C");
@@ -134,7 +132,7 @@ Row {
134132 objectName: "outerRow";
135133 property real margins: units.gu(1);
136134 spacing: margins;
137− height: root.fixedHeaderHeight != -1 ? root.fixedHeaderHeight : implicitHeight;
135+ height: root.fixedHeaderHeight;
138136 anchors { top: artShapeHolder.bottom;
139137 topMargin: units.gu(1);
140138left: parent.left;
@@ -178,7 +176,7 @@ left: parent.left;
178176 width: undefined;
179177 text: root.title;
180178 font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
181− horizontalAlignment: root.titleAlignment;
179+ horizontalAlignment: Text.AlignLeft;
182180 }
183181,Label {
184182 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 {
55 property string backgroundShapeStyle: "inset";
66 property real fontScale: 1.0;
77 property var scopeStyle: null;
8− property int titleAlignment: Text.AlignLeft;
98 property int fixedHeaderHeight: -1;
109 property size fixedArtShapeSize: Qt.size(-1, -1);
1110 readonly property string title: cardData && cardData["title"] || "";
12− property bool asynchronous: true;
1311 property bool showHeader: true;
1412 implicitWidth: childrenRect.width;
1513 enabled: true;
@@ -18,7 +16,7 @@ Loader {
1816 id: backgroundLoader;
1917 objectName: "backgroundLoader";
2018 anchors.fill: parent;
21− asynchronous: root.asynchronous;
19+ asynchronous: true;
2220 visible: status == Loader.Ready;
2321 sourceComponent: UbuntuShape {
2422 objectName: "background";
@@ -59,7 +57,7 @@ Row {
5957 objectName: "outerRow";
6058 property real margins: units.gu(1);
6159 spacing: margins;
62− height: root.fixedHeaderHeight != -1 ? root.fixedHeaderHeight : implicitHeight;
60+ height: root.fixedHeaderHeight;
6361 anchors { top: parent.top;
6462 topMargin: units.gu(1);
6563 left: parent.left;
@@ -102,7 +100,7 @@ CroppedImageMinimumSourceSize {
102100 width: undefined;
103101 text: root.title;
104102 font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
105− horizontalAlignment: root.titleAlignment;
103+ horizontalAlignment: Text.AlignLeft;
106104 }
107105 ,Label {
108106 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 {
55 property string backgroundShapeStyle: "inset";
66 property real fontScale: 1.0;
77 property var scopeStyle: null;
8− property int titleAlignment: Text.AlignLeft;
98 property int fixedHeaderHeight: -1;
109 property size fixedArtShapeSize: Qt.size(-1, -1);
1110 readonly property string title: cardData && cardData["title"] || "";
12− property bool asynchronous: true;
1311 property bool showHeader: true;
1412 implicitWidth: childrenRect.width;
1513 enabled: true;
@@ -25,7 +23,7 @@ Item {
2523 objectName: "artShapeLoader";
2624 readonly property string cardArt: cardData && cardData["art"] || decodeURI("IHAVE%5C%22ESCAPED%5C%22QUOTES%5C%22");
2725 active: cardArt != "";
28− asynchronous: root.asynchronous;
26+ asynchronous: true;
2927 visible: status == Loader.Ready;
3028 sourceComponent: Item {
3129 id: artShape;
@@ -82,7 +80,7 @@ Item {
8280 id: artImage;
8381 objectName: "artImage";
8482 source: artShapeLoader.cardArt;
85− asynchronous: root.asynchronous;
83+ asynchronous: true;
8684 width: root.width;
8785 height: width / artShape.aspect;
8886 onStatusChanged: if (status === Image.Error) source = decodeURI("IHAVE%5C%22ESCAPED%5C%22QUOTES%5C%22");
@@ -109,7 +107,7 @@ Label {
109107 width: undefined;
110108 text: root.title;
111109 font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
112− horizontalAlignment: root.titleAlignment;
110+ horizontalAlignment: Text.AlignLeft;
113111 }
114112Label {
115113 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 {
55 property string backgroundShapeStyle: "inset";
66 property real fontScale: 1.0;
77 property var scopeStyle: null;
8− property int titleAlignment: Text.AlignLeft;
98 property int fixedHeaderHeight: -1;
109 property size fixedArtShapeSize: Qt.size(-1, -1);
1110 readonly property string title: cardData && cardData["title"] || "";
12− property bool asynchronous: true;
1311 property bool showHeader: true;
1412 implicitWidth: childrenRect.width;
1513 enabled: true;
@@ -21,7 +19,7 @@ Row {
2119 objectName: "outerRow";
2220 property real margins: units.gu(1);
2321 spacing: margins;
24− height: root.fixedHeaderHeight != -1 ? root.fixedHeaderHeight : implicitHeight;
22+ height: root.fixedHeaderHeight;
2523 anchors { top: parent.top;
2624 topMargin: units.gu(1);
2725 left: parent.left;
@@ -33,7 +31,7 @@ data: [
3331Loader {
3432 id: mascotShapeLoader;
3533 objectName: "mascotShapeLoader";
36− asynchronous: root.asynchronous;
34+ asynchronous: true;
3735 active: mascotImage.status === Image.Ready;
3836 visible: showHeader && active && status == Loader.Ready;
3937 width: units.gu(6);
@@ -77,7 +75,7 @@ Label {
7775 width: undefined;
7876 text: root.title;
7977 font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
80− horizontalAlignment: root.titleAlignment;
78+ horizontalAlignment: Text.AlignLeft;
8179 }
8280,Label {
8381 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 {
55 property string backgroundShapeStyle: "inset";
66 property real fontScale: 1.0;
77 property var scopeStyle: null;
8− property int titleAlignment: Text.AlignLeft;
98 property int fixedHeaderHeight: -1;
109 property size fixedArtShapeSize: Qt.size(-1, -1);
1110 readonly property string title: cardData && cardData["title"] || "";
12− property bool asynchronous: true;
1311 property bool showHeader: true;
1412 implicitWidth: childrenRect.width;
1513 enabled: false;
@@ -25,7 +23,7 @@ Item {
2523 objectName: "artShapeLoader";
2624 readonly property string cardArt: cardData && cardData["art"] || "";
2725 active: cardArt != "";
28− asynchronous: root.asynchronous;
26+ asynchronous: true;
2927 visible: status == Loader.Ready;
3028 sourceComponent: Item {
3129 id: artShape;
@@ -82,7 +80,7 @@ Item {
8280 id: artImage;
8381 objectName: "artImage";
8482 source: artShapeLoader.cardArt;
85− asynchronous: root.asynchronous;
83+ asynchronous: true;
8684 width: root.width;
8785 height: width / artShape.aspect;
8886 }
@@ -91,10 +89,10 @@ Item {
9189 }
9290Loader {
9391 id: overlayLoader;
94− readonly property real overlayHeight: (fixedHeaderHeight > 0 ? fixedHeaderHeight : headerHeight) + units.gu(2);
92+ readonly property real overlayHeight: root.fixedHeaderHeight + units.gu(2);
9593 anchors.fill: artShapeHolder;
9694 active: artShapeLoader.active && artShapeLoader.item && artShapeLoader.item.image.status === Image.Ready || false;
97− asynchronous: root.asynchronous;
95+ asynchronous: true;
9896 visible: showHeader && status == Loader.Ready;
9997 sourceComponent: UbuntuShapeOverlay {
10098 id: overlay;
@@ -126,7 +124,7 @@ Label {
126124 width: undefined;
127125 text: root.title;
128126 font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
129− horizontalAlignment: root.titleAlignment;
127+ horizontalAlignment: Text.AlignLeft;
130128 }
131129Label {
132130 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 {
55 property string backgroundShapeStyle: "inset";
66 property real fontScale: 1.0;
77 property var scopeStyle: null;
8− property int titleAlignment: Text.AlignLeft;
98 property int fixedHeaderHeight: -1;
109 property size fixedArtShapeSize: Qt.size(-1, -1);
1110 readonly property string title: cardData && cardData["title"] || "";
12− property bool asynchronous: true;
1311 property bool showHeader: true;
1412 implicitWidth: childrenRect.width;
1513 enabled: true;
@@ -18,7 +16,7 @@ Loader {
1816 id: backgroundLoader;
1917 objectName: "backgroundLoader";
2018 anchors.fill: parent;
21− asynchronous: root.asynchronous;
19+ asynchronous: true;
2220 visible: status == Loader.Ready;
2321 sourceComponent: UbuntuShape {
2422 objectName: "background";
@@ -81,7 +79,7 @@ Item {
8179 width: undefined;
8280 text: root.title;
8381 font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
84− horizontalAlignment: root.titleAlignment;
82+ horizontalAlignment: Text.AlignLeft;
8583 }
8684,Label {
8785 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 {
55 property string backgroundShapeStyle: "inset";
66 property real fontScale: 1.0;
77 property var scopeStyle: null;
8− property int titleAlignment: Text.AlignLeft;
98 property int fixedHeaderHeight: -1;
109 property size fixedArtShapeSize: Qt.size(-1, -1);
1110 readonly property string title: cardData && cardData["title"] || "";
12− property bool asynchronous: true;
1311 property bool showHeader: true;
1412 implicitWidth: childrenRect.width;
1513 enabled: true;
@@ -18,7 +16,7 @@ Loader {
1816 id: backgroundLoader;
1917 objectName: "backgroundLoader";
2018 anchors.fill: parent;
21− asynchronous: root.asynchronous;
19+ asynchronous: true;
2220 visible: status == Loader.Ready;
2321 sourceComponent: UbuntuShape {
2422 objectName: "background";
@@ -59,7 +57,7 @@ Row {
5957 objectName: "outerRow";
6058 property real margins: units.gu(1);
6159 spacing: margins;
62− height: root.fixedHeaderHeight != -1 ? root.fixedHeaderHeight : implicitHeight;
60+ height: root.fixedHeaderHeight;
6361 anchors { top: parent.top;
6462 topMargin: units.gu(1);
6563 left: parent.left;
@@ -103,7 +101,7 @@ CroppedImageMinimumSourceSize {
103101 width: undefined;
104102 text: root.title;
105103 font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
106− horizontalAlignment: root.titleAlignment;
104+ horizontalAlignment: Text.AlignLeft;
107105 }
108106,Label {
109107 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 {
55 property string backgroundShapeStyle: "inset";
66 property real fontScale: 1.0;
77 property var scopeStyle: null;
8− property int titleAlignment: Text.AlignLeft;
98 property int fixedHeaderHeight: -1;
109 property size fixedArtShapeSize: Qt.size(-1, -1);
1110 readonly property string title: cardData && cardData["title"] || "";
12− property bool asynchronous: true;
1311 property bool showHeader: true;
1412 implicitWidth: childrenRect.width;
1513 enabled: true;
@@ -18,7 +16,7 @@ Loader {
1816 id: backgroundLoader;
1917 objectName: "backgroundLoader";
2018 anchors.fill: parent;
21− asynchronous: root.asynchronous;
19+ asynchronous: true;
2220 visible: status == Loader.Ready;
2321 sourceComponent: UbuntuShape {
2422 objectName: "background";
@@ -59,7 +57,7 @@ Row {
5957 objectName: "outerRow";
6058 property real margins: units.gu(1);
6159 spacing: margins;
62− height: root.fixedHeaderHeight != -1 ? root.fixedHeaderHeight : implicitHeight;
60+ height: root.fixedHeaderHeight;
6361 anchors { top: parent.top;
6462 topMargin: units.gu(1);
6563left: parent.left;
@@ -94,7 +92,7 @@ left: parent.left;
9492 width: parent.width - x;
9593 text: root.title;
9694 font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
97− horizontalAlignment: root.titleAlignment;
95+ horizontalAlignment: Text.AlignLeft;
9896 }
9997
10098 ]
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 {
55 property string backgroundShapeStyle: "inset";
66 property real fontScale: 1.0;
77 property var scopeStyle: null;
8− property int titleAlignment: Text.AlignLeft;
98 property int fixedHeaderHeight: -1;
109 property size fixedArtShapeSize: Qt.size(-1, -1);
1110 readonly property string title: cardData && cardData["title"] || "";
12− property bool asynchronous: true;
1311 property bool showHeader: true;
1412 implicitWidth: childrenRect.width;
1513 enabled: true;
@@ -36,7 +34,7 @@ top: parent.top;
3634 width: undefined;
3735 text: root.title;
3836 font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
39− horizontalAlignment: root.titleAlignment;
37+ horizontalAlignment: Text.AlignLeft;
4038 }
4139Label {
4240 id: subtitleLabel;
@@ -72,7 +70,7 @@ CardAudioProgress {
7270 id: audioButton;
7371 anchors.fill: undefined;
7472 width: height;
75− height: (root.fixedHeaderHeight > 0 ? root.fixedHeaderHeight : headerHeight) + 2 * units.gu(1);
73+ height: root.fixedHeaderHeight + 2 * units.gu(1);
7674 readonly property url source: (cardData["quickPreviewData"] && cardData["quickPreviewData"]["uri"]) || "";
7775 UbuntuShape {
7876 anchors.fill: parent;
@@ -93,7 +91,8 @@ CardAudioProgress {
9391 opacity: 0.9;
9492 name: DashAudioPlayer.playing && AudioUrlComparer.compare(parent.source, DashAudioPlayer.currentSource) ? "media-playback-pause" : "media-playback-start";
9593 color: "white";
96− }
94+ asynchronous: true;
95+ }
9796 onClicked: {
9897 if (AudioUrlComparer.compare(source, DashAudioPlayer.currentSource)) {
9998 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:
4545 delete view;
4646 }
4747
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+
4872 void testKnownCases()
4973 {
5074 const QString templateString("template: ");
@@ -68,33 +92,19 @@ private Q_SLOTS:
6892 const QString templateJSON = lines[0].mid(templateString.length());
6993 const QString componentsJSON = lines[1].mid(componentsString.length());
7094 const QString resultFileName = lines[2].mid(resultString.length());
71−
7295 QVariant cardStringResult;
73− QMetaObject::invokeMethod(view->rootObject(), "cardString", Q_RETURN_ARG(QVariant, cardStringResult), Q_ARG(QVariant, templateJSON), Q_ARG(QVariant, componentsJSON));
74−
75− QFile testResultFile(testDirPath + resultFileName);
76− QVERIFY(testResultFile.open(QIODevice::ReadOnly));
77− QTextStream ts2(&testResultFile);
96+ QVariant createCardComponentResult;
7897
79− // Record failed results to /tmp
80− const QString executedResult = cardStringResult.toString();
81− QTemporaryFile tmpFile(QDir::tempPath() + QDir::separator() + "testCardCreatorFailedResultXXXXXX");
82− tmpFile.open();
83− tmpFile.setAutoRemove(false);
84− tmpFile.write(executedResult.toUtf8().constData());
98+ QMetaObject::invokeMethod(view->rootObject(), "cardString", Q_RETURN_ARG(QVariant, cardStringResult), Q_ARG(QVariant, templateJSON), Q_ARG(QVariant, componentsJSON), Q_ARG(QVariant, false));
99+ compareFileContents(testDirPath + resultFileName, cardStringResult.toString());
85100
86− // Line by line comparison
87− const QStringList expectedLines = ts2.readAll().trimmed().replace(QRegExp("\n\\s*\n"),"\n").split("\n");
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− }
101+ QMetaObject::invokeMethod(view->rootObject(), "cardString", Q_RETURN_ARG(QVariant, cardStringResult), Q_ARG(QVariant, templateJSON), Q_ARG(QVariant, componentsJSON), Q_ARG(QVariant, true));
102+ compareFileContents(testDirPath + resultFileName + ".cardcreator", cardStringResult.toString());
92103
93− // Remove the result if it passed
94− tmpFile.setAutoRemove(true);
104+ QMetaObject::invokeMethod(view->rootObject(), "createCardComponent", Q_RETURN_ARG(QVariant, createCardComponentResult), Q_ARG(QVariant, templateJSON), Q_ARG(QVariant, componentsJSON), Q_ARG(QVariant, false));
105+ QVERIFY(createCardComponentResult.toBool());
95106
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));
98108 QVERIFY(createCardComponentResult.toBool());
99109 }
100110 }
tests/plugins/Dash/cardcreatortest.qml+4−4
@@ -19,11 +19,11 @@ import "../../../plugins/Dash/CardCreator.js" as CardCreator
1919
2020Item {
2121 id: root
22− function cardString(template, components) {
23− return CardCreator.cardString(JSON.parse(template), JSON.parse(components));
22+ function cardString(template, components, isCardCreator) {
23+ return CardCreator.cardString(JSON.parse(template), JSON.parse(components), isCardCreator);
2424 }
2525
26− function createCardComponent(template, components) {
27− return CardCreator.createCardComponent(root, JSON.parse(template), JSON.parse(components)) !== null;
26+ function createCardComponent(template, components, isCardCreator) {
27+ return CardCreator.createCardComponent(root, JSON.parse(template), JSON.parse(components), isCardCreator) !== null;
2828 }
2929}
tests/plugins/IntegratedLightDM/CMakeLists.txt+25
@@ -65,3 +65,28 @@ add_unity8_uitest(GreeterUsers GreeterUsersModelTestExec
6565 DEPENDS MockLightDM
6666 LIGHTDM
6767 )
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/Utils/CMakeLists.txt+7−1
@@ -4,15 +4,21 @@ include_directories(
44 ${CMAKE_CURRENT_BINARY_DIR}
55 )
66
7+add_definitions(-DCONFIG_TESTING)
8+
79foreach(util_test
810 QLimitProxyModel
911 UnitySortFilterProxyModel
1012 WindowInputMonitor
13+ DeviceConfigParser
1114)
1215 add_executable(${util_test}TestExec ${util_test}Test.cpp ModelTest.cpp)
1316 qt5_use_modules(${util_test}TestExec Test Core Qml)
1417 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+
1622endforeach()
1723
1824# 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)
55add_unity8_qmltest(. DisabledScreenNotice)
66add_unity8_qmltest(. Shell LIGHTDM)
77add_unity8_qmltest(. ShellWithPin LIGHTDM)
8+add_unity8_qmltest(. DeviceConfiguration)
89add_unity8_qmltest(Components Background)
910add_unity8_qmltest(Components Carousel)
1011add_unity8_qmltest(Components Dialogs LIGHTDM)
tests/qmltests/Components/tst_Lockscreen.qml+14−7
@@ -235,11 +235,14 @@ Rectangle {
235235
236236 function test_unlock_data() {
237237 return [
238− {tag: "numeric", alphanumeric: false, password: "1234", minPinLength: 4, maxPinLength: 4},
239− {tag: "alphanumeric", alphanumeric: true, password: "password", minPinLength: -1, maxPinLength: -1},
240− {tag: "numeric (wrong)", alphanumeric: false, password: "4321", minPinLength: 4, maxPinLength: 4},
241− {tag: "alphanumeric (wrong)", alphanumeric: true, password: "drowssap", minPinLength: -1, maxPinLength: -1},
242− {tag: "flexible length", alphanumeric: false, password: "1234", minPinLength: -1, maxPinLength: -1},
238+ {tag: "numeric", alphanumeric: false, password: "1234", minPinLength: 4, maxPinLength: 4, keyboard: false},
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, keyboard: false},
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, 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}
243246 ]
244247 }
245248
@@ -259,8 +262,12 @@ Rectangle {
259262 } else {
260263 for (var i = 0; i < data.password.length; ++i) {
261264 var character = data.password.charAt(i)
262− var button = findChild(lockscreen, "pinPadButton" + character)
263− mouseClick(button, units.gu(1), units.gu(1))
265+ if (data.keyboard) {
266+ typeString(character)
267+ } else {
268+ var button = findChild(lockscreen, "pinPadButton" + character)
269+ mouseClick(button, units.gu(1), units.gu(1))
270+ }
264271 }
265272 var confirmButton = findChild(lockscreen, "confirmButton");
266273 mouseClick(confirmButton, units.gu(1), units.gu(1));
tests/qmltests/Dash/Previews/tst_PreviewHeader.qml+18−1
@@ -38,6 +38,14 @@ Rectangle {
3838 "attributes": [{"value":"text1","icon":"image://theme/ok"},{"value":"text2","icon":"image://theme/cancel"}]
3939 }
4040
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+
4149 property var brokenheaderjson: {
4250 "title": "THE TITLE",
4351 "subtitle": "Something catchy",
@@ -64,7 +72,7 @@ Rectangle {
6472
6573 PreviewHeader {
6674 id: previewHeader
67− widgetData: headerjson
75+ widgetData: emblemheaderjson
6876 width: units.gu(30)
6977
7078 Rectangle {
@@ -136,6 +144,15 @@ Rectangle {
136144 }
137145 }
138146
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+
139156 function test_json() {
140157 headerjson = origHeaderjson;
141158 previewHeader.widgetData = headerjson;
tests/qmltests/Dash/Previews/tst_PreviewSharing.qml+17−3
@@ -28,13 +28,18 @@ Rectangle {
2828
2929 property var shareData: {
3030 "uri": [
31− "Text here",
32− "text here 2",
33− "text here 3"
31+ "file:///this/is/an/url",
32+ "file:///this/is/an/url/2",
33+ "file:///this/is/an/url/3"
3434 ],
3535 "content-type": "text"
3636 }
3737
38+ property var shareDataString: {
39+ "uri": "file:///this/is/an/url",
40+ "content-type": "text"
41+ }
42+
3843 PreviewSharing {
3944 id: previewSharing
4045 anchors { left: parent.left; bottom: parent.bottom; }
@@ -55,5 +60,14 @@ Rectangle {
5560 mouseClick(previewSharing);
5661 compare(peerPicker.visible, true);
5762 }
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+ }
5872 }
5973}
tests/qmltests/Dash/tst_CardTool.qml+4−4
@@ -339,22 +339,22 @@ Rectangle {
339339 function test_card_title_alignment(data) {
340340 selector.selectedIndex = data.index;
341341
342− tryCompare(cardTool, "titleAlignment", Text.AlignLeft);
342+ tryCompareFunction(function() { return findChild(internalCard, "titleLabel").horizontalAlignment == Text.AlignLeft; }, true);
343343
344344 cardTool.components['title'] = { "field": "title", "align": "center" };
345345 cardTool.componentsChanged();
346346
347− tryCompare(cardTool, "titleAlignment", data.value);
347+ tryCompareFunction(function() { return findChild(internalCard, "titleLabel").horizontalAlignment == data.value; }, true);
348348
349349 cardTool.components['title'] = { "field": "title" };
350350 cardTool.componentsChanged();
351351
352− tryCompare(cardTool, "titleAlignment", data.value);
352+ tryCompareFunction(function() { return findChild(internalCard, "titleLabel").horizontalAlignment == data.value; }, true);
353353
354354 cardTool.components['title'] = { "field": "title", "align": "left" };
355355 cardTool.componentsChanged();
356356
357− tryCompare(cardTool, "titleAlignment", Text.AlignLeft);
357+ tryCompareFunction(function() { return findChild(internalCard, "titleLabel").horizontalAlignment == Text.AlignLeft; }, true);
358358 }
359359
360360 function test_categoryLayout_data() {
tests/qmltests/Dash/tst_Dash.qml+89
@@ -70,6 +70,42 @@ Item {
7070 waitForRendering(findChild(dash, "scopeLoader0").item);
7171 }
7272
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+
73109 function test_manage_dash_clickscope_unfavoritable() {
74110 // Show the manage dash
75111 touchFlick(dash, dash.width / 2, dash.height - 1, dash.width / 2, units.gu(2));
@@ -565,5 +601,58 @@ Item {
565601 tryCompare(dashTempScopeItem, "x", dashTempScopeItem.width);
566602 tryCompare(dashTempScopeItem, "visible", false);
567603 }
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+ }
568657 }
569658}
tests/qmltests/Greeter/tst_NarrowView.qml+9
@@ -518,5 +518,14 @@ Item {
518518 tryCompare(coverPage, "shown", false);
519519 tryCompare(coverPage, "showProgress", 0);
520520 }
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+ }
521530 }
522531}
tests/qmltests/Launcher/tst_Launcher.qml+254−18
@@ -28,12 +28,13 @@ import Utils 0.1 // For EdgeBarrierSettings
2828 launcher. */
2929Item {
3030 id: root
31− width: units.gu(50)
32− height: units.gu(55)
31+ width: units.gu(140)
32+ height: units.gu(70)
3333
3434 Loader {
3535 id: launcherLoader
3636 anchors.fill: parent
37+ focus: true
3738 property bool itemDestroyed: false
3839 sourceComponent: Component {
3940 Launcher {
@@ -68,6 +69,7 @@ Item {
6869
6970 Component.onCompleted: {
7071 launcherLoader.itemDestroyed = false;
72+ launcherLoader.focus = true
7173 edgeBarrierControls.target = testCase.findChild(this, "edgeBarrierController");
7274 }
7375 Component.onDestruction: {
@@ -77,11 +79,40 @@ Item {
7779 }
7880 }
7981
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+
8093 ColumnLayout {
8194 anchors { bottom: parent.bottom; right: parent.right; margins: units.gu(1) }
8295 spacing: units.gu(1)
8396 width: childrenRect.width
8497
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+
85116 MouseTouchEmulationCheckbox {}
86117
87118 EdgeBarrierControls {
@@ -102,6 +133,15 @@ Item {
102133 Layout.fillWidth: true
103134 }
104135
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+
105145 Row {
106146 spacing: units.gu(1)
107147
@@ -206,10 +246,6 @@ Item {
206246 // growing while populating it with icons etc.
207247 tryCompare(listView, "flicking", false);
208248
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−
213249 compare(listView.contentY, -listView.topMargin, "Launcher did not start up with first item unfolded");
214250
215251 // Now do check that snapping is in fact enabled
@@ -266,13 +302,39 @@ Item {
266302
267303 function positionLauncherListAtBeginning() {
268304 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);
270311 }
271312 function positionLauncherListAtEnd() {
272313 var listView = testCase.findChild(launcherLoader.item, "launcherListView");
273− if ((listView.contentHeight + listView.topMargin + listView.bottomMargin) > listView.height) {
274− listView.contentY = listView.topMargin + listView.contentHeight
275− - listView.height;
314+ var moveAnimation = findInvisibleChild(listView, "moveAnimation")
315+
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+ }
276338 }
277339 }
278340
@@ -290,10 +352,10 @@ Item {
290352 dragLauncherIntoView()
291353
292354 // 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)
294356
295357 // should eventually get fully retracted (hidden)
296− tryCompare(panel, "x", -launcher.panelWidth, 1000)
358+ tryCompare(panel, "x", -launcher.panelWidth, 2000)
297359 }
298360
299361 /* If I click on the icon of an application on the launcher
@@ -377,6 +439,7 @@ Item {
377439 wait(100)
378440 compare(launcher.maxPanelX, -launcher.panelWidth, "Launcher moved even if it shouldn't")
379441 }
442+
380443 waitUntilLauncherDisappears();
381444 launcher.available = true;
382445 }
@@ -400,6 +463,8 @@ Item {
400463 dragLauncherIntoView();
401464 var launcherListView = findChild(launcher, "launcherListView");
402465 for (var i = 0; i < launcherListView.count; ++i) {
466+ launcherListView.moveToIndex(i);
467+ waitForRendering(launcherListView);
403468 var delegate = findChild(launcherListView, "launcherDelegate" + i)
404469 compare(findChild(delegate, "countEmblem").visible, LauncherModel.get(i).countVisible)
405470 // Intentionally allow type coercion (string/number)
@@ -421,7 +486,7 @@ Item {
421486 var launcherListView = findChild(launcher, "launcherListView");
422487 for (var i = 0; i < launcherListView.count; ++i) {
423488 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)
425490 }
426491 }
427492
@@ -460,6 +525,7 @@ Item {
460525 launcher.lastSelectedApplication = "";
461526 dragLauncherIntoView();
462527 var listView = findChild(launcher, "launcherListView");
528+ var moveAnimation = findInvisibleChild(listView, "moveAnimation")
463529
464530 // flicking is unreliable. sometimes it works, sometimes the
465531 // list view moves just a tiny bit or not at all, making tests fail.
@@ -470,12 +536,14 @@ Item {
470536 } else {
471537 positionLauncherListAtEnd();
472538 }
473− tryCompare(listView, "flicking", false);
474−
475539 var oldY = listView.contentY;
476540
477541 mouseClick(listView, listView.width / 2, data.clickY);
478− tryCompare(listView, "flicking", false);
542+
543+ if (data.expectFlick) {
544+ tryCompare(moveAnimation, "running", true);
545+ }
546+ tryCompare(moveAnimation, "running", false);
479547
480548 if (data.expectFlick) {
481549 verify(listView.contentY != oldY);
@@ -764,14 +832,15 @@ Item {
764832 function test_launcher_dismiss() {
765833 dragLauncherIntoView();
766834 verify(launcher.state == "visible");
767− mouseClick(root);
835+
836+ mouseClick(root, root.width / 2, units.gu(1));
768837 waitUntilLauncherDisappears();
769838 verify(launcher.state == "");
770839
771840 // and repeat, as a test for regression in lpbug#1531339
772841 dragLauncherIntoView();
773842 verify(launcher.state == "visible");
774− mouseClick(root);
843+ mouseClick(root, root.width / 2, units.gu(1));
775844 waitUntilLauncherDisappears();
776845 verify(launcher.state == "");
777846 }
@@ -1044,5 +1113,172 @@ Item {
10441113 LauncherModel.setCountVisible(LauncherModel.get(1).appId, 0)
10451114 LauncherModel.setCount(LauncherModel.get(1).appId, oldCount)
10461115 }
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+ }
10471283 }
10481284}
tests/qmltests/Panel/Indicators/tst_MenuItemFactory.qml+2−4
@@ -274,15 +274,14 @@ Item {
274274
275275 function test_create_progressMenu_data() {
276276 return [
277− {label: "testLabel1", enabled: true, value: 10 },
278− {label: "testLabel2", enabled: false, value: 55 },
277+ {label: "testLabel1", value: 10 },
278+ {label: "testLabel2", value: 55 },
279279 ];
280280 }
281281
282282 function test_create_progressMenu(data) {
283283 menuData.type = "com.canonical.indicator.progress"
284284 menuData.label = data.label;
285− menuData.sensitive = data.enabled;
286285 menuData.actionState = data.value;
287286
288287 loader.data = menuData;
@@ -292,7 +291,6 @@ Item {
292291
293292 compare(loader.item.text, data.label, "Label does not match data");
294293 compare(loader.item.value, data.value, "Value does not match data");
295− compare(loader.item.enabled, data.enabled, "Enabled does not match data");
296294 }
297295
298296 function test_create_standardMenu_data() {
tests/qmltests/Stages/tst_DesktopStage.qml+2−1
@@ -65,8 +65,9 @@ Item {
6565 property bool itemDestroyed: false
6666 sourceComponent: Component {
6767 DesktopStage {
68− color: "darkblue"
68+ color: "white"
6969 anchors.fill: parent
70+ background: "../../../qml/graphics/tablet_background.jpg"
7071
7172 Component.onCompleted: {
7273 edgeBarrierControls.target = testCase.findChild(this, "edgeBarrierController");
tests/qmltests/Stages/tst_PhoneStage.qml+19−6
@@ -40,12 +40,6 @@ Item {
4040 orientations: Orientations {}
4141 }
4242
43− Binding {
44− target: ApplicationManager
45− property: "rightMargin"
46− value: phoneStage.anchors.rightMargin
47− }
48−
4943 Rectangle {
5044 anchors { fill: parent; leftMargin: phoneStage.width }
5145
@@ -77,6 +71,15 @@ Item {
7771 ApplicationManager.get(appList.selectedAppIndex).setState(ApplicationInfoInterface.Stopped);
7872 }
7973 }
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+ }
8083 }
8184 ListView {
8285 id: appList
@@ -463,5 +466,15 @@ Item {
463466
464467 tryCompare(delegate.application, "requestedState", ApplicationInfoInterface.RequestedRunning);
465468 }
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+ }
466479 }
467480}
tests/qmltests/Stages/tst_SpreadDelegate.qml+7
@@ -308,5 +308,12 @@ Rectangle {
308308 tryCompare(closeAnimation, "running", false);
309309 }
310310
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+ }
311318 }
312319}
tests/qmltests/Stages/tst_TabletStage.qml+19−1
@@ -21,6 +21,7 @@ import Ubuntu.Components.ListItems 1.3 as ListItem
2121import Unity.Application 0.1
2222import Unity.Test 0.1
2323
24+import ".."
2425import "../../../qml/Stages"
2526import "../../../qml/Components"
2627
@@ -77,6 +78,15 @@ Rectangle {
7778 Column {
7879 anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
7980 spacing: units.gu(1)
81+ EdgeBarrierControls {
82+ id: edgeBarrierControls
83+ text: "Drag here to pull out spread"
84+ backgroundColor: "blue"
85+ onDragged: { tabletStageLoader.item.pushRightEdge(amount); }
86+ Component.onCompleted: {
87+ edgeBarrierControls.target = testCase.findChild(tabletStageLoader, "edgeBarrierController");
88+ }
89+ }
8090 ApplicationCheckBox {
8191 id: webbrowserCheckBox
8292 appId: "webbrowser-app"
@@ -365,6 +375,14 @@ Rectangle {
365375 tryCompare(webbrowserApp, "requestedState", ApplicationInfoInterface.RequestedRunning);
366376 tryCompare(dialerApp, "requestedState", ApplicationInfoInterface.RequestedRunning);
367377 }
368− }
369378
379+ function test_mouseEdgePush() {
380+ var spreadView = findChild(tabletStageLoader, "spreadView")
381+ mouseMove(tabletStageLoader, tabletStageLoader.width - 1, units.gu(10));
382+ for (var i = 0; i < units.gu(10); i++) {
383+ tabletStageLoader.item.pushRightEdge(1);
384+ }
385+ tryCompare(spreadView, "phase", 2);
386+ }
387+ }
370388}
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 @@
1515 */
1616
1717import QtQuick 2.4
18+import QtQuick.Layouts 1.1
1819import QtTest 1.0
1920import Unity.Test 0.1
21+import Ubuntu.Components 1.3
22+import Ubuntu.Components.ListItems 1.3
2023import "../../qml"
2124
2225Item {
2326 id: root
24− width: units.gu(40)
27+ width: units.gu(80)
2528 height: units.gu(70)
2629
2730 DisabledScreenNotice {
2831 id: touchScreenPad
2932 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+ }
3074 }
3175
3276 UnityTestCase {
@@ -36,10 +80,40 @@ Item {
3680
3781 function test_mouseAreaHidesOnFirstTap() {
3882 var noticeArea = findChild(touchScreenPad, "infoNoticeArea")
39− print("noticeArea", noticeArea.visible)
4083 compare(noticeArea.visible, true)
4184 tap(root)
4285 tryCompare(noticeArea, "visible", false)
4386 }
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+ }
44118 }
45119}
tests/qmltests/tst_OrientedShell.qml+1−1
@@ -314,7 +314,7 @@ Rectangle {
314314 checked: false
315315 activeFocusOnPress: false
316316 onCheckedChanged: {
317− var surface = SurfaceManager.inputMethodSurface();
317+ var surface = SurfaceManager.inputMethodSurface;
318318 if (checked) {
319319 surface.setState(Mir.RestoredState);
320320 } else {
tests/qmltests/tst_Shell.qml+241−59
@@ -26,6 +26,7 @@ import Unity.Application 0.1
2626import Unity.Connectivity 0.1
2727import Unity.Indicators 0.1
2828import Unity.Notifications 1.0
29+import Unity.Launcher 0.1
2930import Unity.Test 0.1
3031import Powerd 0.1
3132import Wizard 0.1 as Wizard
@@ -49,11 +50,6 @@ Rectangle {
4950 shellLoader.active = true;
5051 }
5152
52− MouseArea {
53− id: clickThroughCatcher
54− anchors.fill: shellContainer
55− }
56−
5753 Item {
5854 id: shellContainer
5955 anchors.left: root.left
@@ -121,10 +117,6 @@ Rectangle {
121117 Component.onDestruction: {
122118 shellLoader.itemDestroyed = true;
123119 }
124− Component.onCompleted: {
125− var keyMapper = testCase.findChild(__shell, "physicalKeysMapper");
126− keyMapper.controlInsteadOfAlt = true;
127− }
128120 }
129121 }
130122 }
@@ -213,6 +205,31 @@ Rectangle {
213205 checked: true
214206 color: "white"
215207 }
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+ }
216233
217234 Label { text: "Applications"; font.bold: true }
218235
@@ -296,12 +313,6 @@ Rectangle {
296313 signalName: "applicationRemoved"
297314 }
298315
299− SignalSpy {
300− id: clickThroughSpy
301− target: clickThroughCatcher
302− signalName: "clicked"
303− }
304−
305316 Telephony.CallEntry {
306317 id: phoneCall
307318 phoneNumber: "+447812221111"
@@ -670,6 +681,7 @@ Rectangle {
670681 function swipeAwayGreeter() {
671682 var greeter = findChild(shell, "greeter");
672683 tryCompare(greeter, "fullyShown", true);
684+ waitForGreeterToStabilize();
673685 removeTimeConstraintsFromDirectionalDragAreas(greeter);
674686
675687 var touchX = shell.width - (shell.edgeSize / 2);
@@ -773,7 +785,7 @@ Rectangle {
773785 loadShell("phone");
774786 swipeAwayGreeter();
775787 var item = findChild(shell, "inputMethod");
776− var surface = SurfaceManager.inputMethodSurface();
788+ var surface = SurfaceManager.inputMethodSurface;
777789
778790 surface.setState(Mir.MinimizedState);
779791 tryCompare(item, "visible", false);
@@ -844,7 +856,6 @@ Rectangle {
844856 function test_launchedAppHasActiveFocus(data) {
845857 loadShell(data.formFactor);
846858 shell.usageScenario = data.usageScenario;
847− waitForGreeterToStabilize();
848859 swipeAwayGreeter();
849860
850861 var webApp = ApplicationManager.startApplication("webbrowser-app");
@@ -912,6 +923,7 @@ Rectangle {
912923 function dragLauncherIntoView() {
913924 var launcher = findChild(shell, "launcher");
914925 var launcherPanel = findChild(launcher, "launcherPanel");
926+ waitForRendering(launcher);
915927 verify(launcherPanel.x = - launcherPanel.width);
916928
917929 var touchStartX = 2;
@@ -1424,9 +1436,17 @@ Rectangle {
14241436 tryCompare(galleryApp, "requestedState", ApplicationInfoInterface.RequestedRunning);
14251437 }
14261438
1427− function test_altTabSwitchesFocus() {
1428− loadShell("desktop");
1429− shell.usageScenario = "desktop"
1439+ function test_altTabSwitchesFocus_data() {
1440+ return [
1441+ { tag: "windowed", shellType: "desktop" },
1442+ { tag: "staged", shellType: "phone" },
1443+ { tag: "sidestaged", shellType: "tablet" }
1444+ ];
1445+ }
1446+
1447+ function test_altTabSwitchesFocus(data) {
1448+ loadShell(data.shellType);
1449+ shell.usageScenario = data.shellType;
14301450 waitForRendering(root)
14311451
14321452 var desktopStage = findChild(shell, "stage");
@@ -1441,21 +1461,13 @@ Rectangle {
14411461
14421462 // Do a quick alt-tab and see if focus changes
14431463 tryCompare(app3.session.lastSurface, "activeFocus", true)
1444− keyClick(Qt.Key_Tab, Qt.ControlModifier)
1464+ keyClick(Qt.Key_Tab, Qt.AltModifier)
14451465 tryCompare(app2.session.lastSurface, "activeFocus", true)
14461466
1447− var desktopSpread = findChild(shell, "spread")
1448−
1449− tryCompare(desktopSpread, "state", "")
1450−
1451− // Just press Alt, make sure the spread comes up
1452− keyPress(Qt.Key_Control);
1467+ // Press Alt+Tab
1468+ keyPress(Qt.Key_Alt);
14531469 keyClick(Qt.Key_Tab);
1454− tryCompare(desktopSpread, "state", "altTab")
1455−
1456− // Release control, check if spread disappears
1457− keyRelease(Qt.Key_Control)
1458− tryCompare(desktopSpread, "state", "")
1470+ keyRelease(Qt.Key_Alt)
14591471
14601472 // Focus should have switched back now
14611473 tryCompare(app3.session.lastSurface, "activeFocus", true)
@@ -1482,7 +1494,7 @@ Rectangle {
14821494 tryCompare(desktopSpread, "state", "")
14831495
14841496 // Just press Alt, make sure the spread comes up
1485− keyPress(Qt.Key_Control);
1497+ keyPress(Qt.Key_Alt);
14861498 keyClick(Qt.Key_Tab);
14871499 tryCompare(desktopSpread, "state", "altTab")
14881500 tryCompare(spreadRepeater, "highlightedIndex", 1)
@@ -1503,7 +1515,7 @@ Rectangle {
15031515 tryCompare(spreadRepeater, "highlightedIndex", 0)
15041516
15051517 // Release control, check if spread disappears
1506− keyRelease(Qt.Key_Control)
1518+ keyRelease(Qt.Key_Alt)
15071519 tryCompare(desktopSpread, "state", "")
15081520
15091521 // Make sure that after wrapping around once, we have the same one focused as at the beginning
@@ -1516,7 +1528,7 @@ Rectangle {
15161528 var spreadRepeater = findInvisibleChild(shell, "spreadRepeater");
15171529 verify(spreadRepeater !== null);
15181530
1519− keyPress(Qt.Key_Control)
1531+ keyPress(Qt.Key_Alt)
15201532 keyClick(Qt.Key_Tab);
15211533 tryCompare(spreadRepeater, "highlightedIndex", 1);
15221534
@@ -1538,7 +1550,7 @@ Rectangle {
15381550 keyClick(Qt.Key_Backtab);
15391551 tryCompare(spreadRepeater, "highlightedIndex", 1);
15401552
1541− keyRelease(Qt.Key_Control);
1553+ keyRelease(Qt.Key_Alt);
15421554 }
15431555
15441556 function test_highlightFollowsMouse() {
@@ -1547,7 +1559,7 @@ Rectangle {
15471559 var spreadRepeater = findInvisibleChild(shell, "spreadRepeater");
15481560 verify(spreadRepeater !== null);
15491561
1550− keyPress(Qt.Key_Control)
1562+ keyPress(Qt.Key_Alt)
15511563 keyClick(Qt.Key_Tab);
15521564
15531565 tryCompare(spreadRepeater, "highlightedIndex", 1);
@@ -1566,7 +1578,7 @@ Rectangle {
15661578
15671579 verify(y < 4000);
15681580
1569− keyRelease(Qt.Key_Control);
1581+ keyRelease(Qt.Key_Alt);
15701582 }
15711583
15721584 function test_closeFromSpread() {
@@ -1575,7 +1587,7 @@ Rectangle {
15751587 var spreadRepeater = findInvisibleChild(shell, "spreadRepeater");
15761588 verify(spreadRepeater !== null);
15771589
1578− keyPress(Qt.Key_Control)
1590+ keyPress(Qt.Key_Alt)
15791591 keyClick(Qt.Key_Tab);
15801592
15811593 appRemovedSpy.clear();
@@ -1602,7 +1614,7 @@ Rectangle {
16021614 tryCompare(appRemovedSpy, "count", 1)
16031615 compare(appRemovedSpy.signalArguments[0][0], closedAppId);
16041616
1605− keyRelease(Qt.Key_Control);
1617+ keyRelease(Qt.Key_Alt);
16061618 }
16071619
16081620 function test_selectFromSpreadWithMouse_data() {
@@ -1622,7 +1634,7 @@ Rectangle {
16221634 var spreadRepeater = findInvisibleChild(shell, "spreadRepeater");
16231635 verify(spreadRepeater !== null);
16241636
1625− keyPress(Qt.Key_Control)
1637+ keyPress(Qt.Key_Alt)
16261638 keyClick(Qt.Key_Tab);
16271639
16281640 var focusAppId = ApplicationManager.get(2).appId;
@@ -1649,7 +1661,7 @@ Rectangle {
16491661 tryCompare(stage, "state", "");
16501662 tryCompare(ApplicationManager, "focusedApplicationId", focusAppId);
16511663
1652− keyRelease(Qt.Key_Control);
1664+ keyRelease(Qt.Key_Alt);
16531665 }
16541666
16551667 function test_progressiveAutoScrolling() {
@@ -1658,7 +1670,7 @@ Rectangle {
16581670 var appRepeater = findInvisibleChild(shell, "appRepeater");
16591671 verify(appRepeater !== null);
16601672
1661− keyPress(Qt.Key_Control)
1673+ keyPress(Qt.Key_Alt)
16621674 keyClick(Qt.Key_Tab);
16631675
16641676 var spreadFlickable = findChild(shell, "spreadFlickable")
@@ -1669,7 +1681,7 @@ Rectangle {
16691681 var x = 0;
16701682 var y = shell.height * .5
16711683 mouseMove(shell, x, y)
1672− while (x <= spreadFlickable.width) {
1684+ while (x <= shell.width) {
16731685 x+=10;
16741686 mouseMove(shell, x, y)
16751687 wait(0); // spin the loop so bindings get evaluated
@@ -1684,7 +1696,7 @@ Rectangle {
16841696 }
16851697 tryCompare(spreadFlickable, "contentX", 0);
16861698
1687− keyRelease(Qt.Key_Control);
1699+ keyRelease(Qt.Key_Alt);
16881700 }
16891701
16901702 // This makes sure the hoverMouseArea is set to invisible AND disabled
@@ -1696,13 +1708,13 @@ Rectangle {
16961708 tryCompare(hoverMouseArea, "enabled", false)
16971709 tryCompare(hoverMouseArea, "visible", false)
16981710
1699− keyPress(Qt.Key_Control)
1711+ keyPress(Qt.Key_Alt)
17001712 keyClick(Qt.Key_Tab);
17011713
17021714 tryCompare(hoverMouseArea, "enabled", true)
17031715 tryCompare(hoverMouseArea, "visible", true)
17041716
1705− keyRelease(Qt.Key_Control)
1717+ keyRelease(Qt.Key_Alt)
17061718
17071719 tryCompare(hoverMouseArea, "enabled", false)
17081720 tryCompare(hoverMouseArea, "visible", false)
@@ -1719,7 +1731,7 @@ Rectangle {
17191731 var appRepeater = findInvisibleChild(shell, "appRepeater");
17201732 verify(appRepeater !== null);
17211733
1722− keyPress(Qt.Key_Control)
1734+ keyPress(Qt.Key_Alt)
17231735 keyClick(Qt.Key_Tab);
17241736
17251737 tryCompare(spreadRepeater, "highlightedIndex", 1);
@@ -1740,33 +1752,46 @@ Rectangle {
17401752
17411753 verify(y < 4000);
17421754
1743− keyRelease(Qt.Key_Control);
1755+ keyRelease(Qt.Key_Alt);
17441756 }
17451757
1746− function test_focusAppFromLauncherExitsSpread() {
1747− loadDesktopShellWithApps()
1758+ function test_focusAppFromLauncherExitsSpread_data() {
1759+ return [
1760+ {tag: "autohide launcher", launcherLocked: false },
1761+ {tag: "locked launcher", launcherLocked: true }
1762+ ]
1763+ }
17481764
1749− var desktopSpread = findChild(shell, "spread");
1765+ function test_focusAppFromLauncherExitsSpread(data) {
1766+ loadDesktopShellWithApps()
17501767 var launcher = findChild(shell, "launcher");
1768+ var desktopSpread = findChild(shell, "spread");
17511769 var bfb = findChild(launcher, "buttonShowDashHome");
17521770
1753− keyPress(Qt.Key_Control)
1771+ GSettingsController.setAutohideLauncher(!data.launcherLocked);
1772+ waitForRendering(shell);
1773+
1774+ keyPress(Qt.Key_Alt)
17541775 keyClick(Qt.Key_Tab);
17551776
17561777 tryCompare(desktopSpread, "state", "altTab")
17571778
1758− revealLauncherByEdgePushWithMouse();
1759− tryCompare(launcher, "x", 0);
1760− mouseMove(bfb, bfb.width / 2, bfb.height / 2)
1761− waitForRendering(shell)
1779+ if (!data.launcherLocked) {
1780+ revealLauncherByEdgePushWithMouse();
1781+ tryCompare(launcher, "x", 0);
1782+ mouseMove(bfb, bfb.width / 2, bfb.height / 2)
1783+ waitForRendering(shell)
1784+ }
17621785
17631786 mouseClick(bfb, bfb.width / 2, bfb.height / 2)
1764− tryCompare(launcher, "state", "")
1787+ if (!data.launcherLocked) {
1788+ tryCompare(launcher, "state", "")
1789+ }
17651790 tryCompare(desktopSpread, "state", "")
17661791
17671792 tryCompare(ApplicationManager, "focusedApplicationId", "unity8-dash")
17681793
1769− keyRelease(Qt.Key_Control);
1794+ keyRelease(Qt.Key_Alt);
17701795 }
17711796
17721797 // regression test for http://pad.lv/1443319
@@ -1854,6 +1879,31 @@ Rectangle {
18541879 compare(appDelegate.y >= PanelState.panelHeight, true);
18551880 }
18561881
1882+ function test_cantResizeWindowUnderPanel() {
1883+ loadShell("desktop");
1884+ shell.usageScenario = "desktop";
1885+ waitForRendering(shell);
1886+
1887+ var app = ApplicationManager.startApplication("dialer-app")
1888+ waitUntilAppWindowIsFullyLoaded(app);
1889+
1890+ var appContainer = findChild(shell, "appContainer");
1891+ var appDelegate = findChild(appContainer, "appDelegate_dialer-app");
1892+ var decoration = findChild(appDelegate, "appWindowDecoration_dialer-app");
1893+ verify(decoration);
1894+
1895+ // move it away from launcher and panel
1896+ appDelegate.x = units.gu(10)
1897+ appDelegate.y = units.gu(10)
1898+
1899+ // drag-resize the area up
1900+ mousePress(decoration, decoration.width/2, -units.gu(1));
1901+ mouseMove(decoration, decoration.width/2, -units.gu(100));
1902+
1903+ // verify we don't go past the panel
1904+ compare(appDelegate.y >= PanelState.panelHeight, true);
1905+ }
1906+
18571907 function test_restoreWindowStateFixesIfUnderPanel() {
18581908 loadDesktopShellWithApps();
18591909 var appRepeater = findChild(shell, "appRepeater")
@@ -1989,6 +2039,138 @@ Rectangle {
19892039 }
19902040 }
19912041
2042+ function test_superTabToCycleLauncher_data() {
2043+ return [
2044+ {tag: "autohide launcher", launcherLocked: false},
2045+ {tag: "locked launcher", launcherLocked: true}
2046+ ]
2047+ }
2048+
2049+ function test_superTabToCycleLauncher(data) {
2050+ loadShell("desktop");
2051+ shell.usageScenario = "desktop";
2052+ GSettingsController.setAutohideLauncher(!data.launcherLocked);
2053+ waitForRendering(shell);
2054+
2055+ var launcher = findChild(shell, "launcher");
2056+ var launcherPanel = findChild(launcher, "launcherPanel");
2057+ var firstAppInLauncher = LauncherModel.get(0).appId;
2058+
2059+ compare(launcher.state, data.launcherLocked ? "visible": "");
2060+ compare(launcherPanel.highlightIndex, -2);
2061+ compare(ApplicationManager.focusedApplicationId, "unity8-dash");
2062+
2063+ // Use Super + Tab Tab to cycle to the first entry in the launcher
2064+ keyPress(Qt.Key_Super_L, Qt.MetaModifier);
2065+ keyClick(Qt.Key_Tab);
2066+ tryCompare(launcher, "state", "visible");
2067+ tryCompare(launcherPanel, "highlightIndex", -1);
2068+ keyClick(Qt.Key_Tab);
2069+ tryCompare(launcherPanel, "highlightIndex", 0);
2070+ keyRelease(Qt.Key_Super_L, Qt.MetaModifier);
2071+ tryCompare(launcher, "state", data.launcherLocked ? "visible" : "");
2072+ tryCompare(launcherPanel, "highlightIndex", -2);
2073+ tryCompare(ApplicationManager, "focusedApplicationId", firstAppInLauncher);
2074+
2075+ // Now go back to the dash
2076+ keyPress(Qt.Key_Super_L, Qt.MetaModifier);
2077+ keyClick(Qt.Key_Tab);
2078+ tryCompare(launcher, "state", "visible");
2079+ tryCompare(launcherPanel, "highlightIndex", -1);
2080+ keyRelease(Qt.Key_Super_L, Qt.MetaModifier);
2081+ tryCompare(launcher, "state", data.launcherLocked ? "visible" : "");
2082+ tryCompare(launcherPanel, "highlightIndex", -2);
2083+ tryCompare(ApplicationManager, "focusedApplicationId", "unity8-dash");
2084+ }
2085+
2086+ function test_longpressSuperOpensLauncher() {
2087+ loadShell("desktop");
2088+ var launcher = findChild(shell, "launcher");
2089+ var shortcutHint = findChild(findChild(launcher, "launcherDelegate0"), "shortcutHint")
2090+
2091+ compare(launcher.state, "");
2092+ keyPress(Qt.Key_Super_L, Qt.MetaModifier);
2093+ tryCompare(launcher, "state", "visible");
2094+ tryCompare(shortcutHint, "visible", true);
2095+
2096+ keyRelease(Qt.Key_Super_L, Qt.MetaModifier);
2097+ tryCompare(launcher, "state", "");
2098+ tryCompare(shortcutHint, "visible", false);
2099+ }
2100+
2101+ function test_metaNumberLaunchesFromLauncher_data() {
2102+ return [
2103+ {tag: "Meta+1", key: Qt.Key_1, index: 0},
2104+ {tag: "Meta+2", key: Qt.Key_2, index: 1},
2105+ {tag: "Meta+4", key: Qt.Key_5, index: 4},
2106+ {tag: "Meta+0", key: Qt.Key_0, index: 9},
2107+ ]
2108+ }
2109+
2110+ function test_metaNumberLaunchesFromLauncher(data) {
2111+ loadShell("desktop");
2112+ var launcher = findChild(shell, "launcher");
2113+ var appId = LauncherModel.get(data.index).appId;
2114+ waitForRendering(shell);
2115+
2116+ keyClick(data.key, Qt.MetaModifier);
2117+ tryCompare(ApplicationManager, "focusedApplicationId", appId);
2118+ }
2119+
2120+ function test_altF1OpensLauncherForKeyboardNavigation() {
2121+ loadShell("desktop");
2122+ waitForRendering(shell);
2123+ var launcher = findChild(shell, "launcher");
2124+
2125+ keyClick(Qt.Key_F1, Qt.AltModifier);
2126+ tryCompare(launcher, "state", "visible");
2127+ tryCompare(launcher, "focus", true)
2128+ }
2129+
2130+ function test_lockedOutLauncherAddsMarginsToMaximized() {
2131+ loadShell("desktop");
2132+ shell.usageScenario = "desktop";
2133+ waitForRendering(shell);
2134+ var appContainer = findChild(shell, "appContainer");
2135+ var launcher = findChild(shell, "launcher");
2136+
2137+ var app = ApplicationManager.startApplication("music-app");
2138+ waitUntilAppWindowIsFullyLoaded(app);
2139+ var appDelegate = findChild(appContainer, "appDelegate_music-app");
2140+ appDelegate.maximize();
2141+ tryCompare(appDelegate, "visuallyMaximized", true);
2142+ waitForRendering(shell);
2143+
2144+ GSettingsController.setAutohideLauncher(true);
2145+ waitForRendering(shell)
2146+ var hiddenSize = appDelegate.width;
2147+
2148+ GSettingsController.setAutohideLauncher(false);
2149+ waitForRendering(shell)
2150+ var shownSize = appDelegate.width;
2151+
2152+ compare(shownSize + launcher.panelWidth, hiddenSize);
2153+ }
2154+
2155+ function test_fullscreenAppHidesLockedOutLauncher() {
2156+ loadShell("desktop");
2157+ shell.usageScenario = "desktop";
2158+
2159+ var launcher = findChild(shell, "launcher");
2160+ var launcherPanel = findChild(launcher, "launcherPanel");
2161+
2162+ GSettingsController.setAutohideLauncher(false);
2163+ waitForRendering(shell)
2164+
2165+ tryCompare(launcher, "lockedVisible", true);
2166+
2167+ var cameraApp = ApplicationManager.startApplication("camera-app");
2168+ waitUntilAppWindowIsFullyLoaded(cameraApp);
2169+
2170+ tryCompare(launcher, "lockedVisible", false);
2171+ }
2172+
2173+
19922174 function test_inputEventsOnEdgesEndUpInAppSurface_data() {
19932175 return [
19942176 { tag: "phone", repeaterName: "spreadRepeater" },