on xenial help sign in
merge silo 78. some things are still slightly broken
commit914e4c1afbfeb9291bb9a991571f4f3a80c1f43c authorMichael Zanetti <michael.zanetti@canonical.com> dateMon 12 Sep 2016, 19:05 (10 years ago) parent0cf7d15f5ec171
240 files changed+6109−1282
CMakeLists.txt+1−1 data/unity8-greeter-wrapper+11−3 debian/changelog+79 debian/control+8−4 debian/rules+3−2 debian/unity8-private.install+1 plugins/CMakeLists.txt+1 plugins/Cursor/CMakeLists.txt+2−2 plugins/Cursor/MousePointer.cpp+90−13 plugins/Cursor/MousePointer.h+23 plugins/Dash/CardCreator.js+12−10 plugins/Dash/listviewwithpageheader.cpp+5−4 plugins/ImageCache/CMakeLists.txt+8 plugins/ImageCache/ImageCache.cpp+135 plugins/ImageCache/ImageCache.h+53 plugins/ImageCache/plugin.cpp+39 plugins/ImageCache/plugin.h+31 plugins/ImageCache/qmldir+2 plugins/LightDM/IntegratedLightDM/liblightdm/Greeter.cpp−5 plugins/LightDM/IntegratedLightDM/liblightdm/Greeter.h−2 plugins/LightDM/SessionsModel.cpp+22−16 plugins/LightDM/plugin.cpp+1−1 plugins/SessionBroadcast/SessionBroadcast.cpp+35−9 plugins/SessionBroadcast/SessionBroadcast.h+10−3 plugins/Ubuntu/DownloadDaemonListener/CMakeLists.txt+3−1 plugins/Ubuntu/DownloadDaemonListener/DownloadTracker.cpp+2−1 plugins/Ubuntu/DownloadDaemonListener/DownloadTracker.h+2−1 plugins/Ubuntu/DownloadDaemonListener/interface/downloadtrackeradaptor.cpp+2−2 plugins/Ubuntu/DownloadDaemonListener/interface/downloadtrackeradaptor.h+71−4 plugins/Ubuntu/DownloadDaemonListener/interface/metatypes.h+3−6 plugins/Ubuntu/Gestures/TouchGestureArea.h+2−2 plugins/Utils/CMakeLists.txt+1 plugins/Utils/URLDispatcher.cpp+77 plugins/Utils/URLDispatcher.h+47 plugins/Utils/plugin.cpp+2 plugins/Utils/windowstatestorage.cpp+5−4 plugins/Utils/windowstatestorage.h+8−3 po/unity8.pot+85−131 qml/Components/Carousel.qml+1−2 qml/Components/Flickable.qml+24 qml/Components/FloatingFlickable.qml+1 qml/Components/GridView.qml+24 qml/Components/ListView.qml+24 qml/Components/Lockscreen.qml+1−28 qml/Components/PanelState/PanelState.qml−1 qml/Components/PassphraseLockscreen.qml−4 qml/Components/PinLockscreen.qml−4 qml/Components/ResponsiveGridView.qml+1 qml/Components/VolumeControl.qml+2−3 qml/Components/Wallpaper.qml+44 qml/Components/WallpaperResolver.qml+24−38 qml/Components/WindowControlButtons.qml+7−5 qml/Components/ZoomableImage.qml+1−1 qml/Components/flickableUtils.js+28 qml/Dash/CardTool.qml+14−2 qml/Dash/DashBackground.qml+2−2 qml/Dash/DashContent.qml−2 qml/Dash/DashNavigation.qml+1 qml/Dash/DashPageHeader.qml+2 qml/Dash/FiltersPopover.qml+1 qml/Dash/GenericScopeView.qml+16 qml/Dash/PageHeaderExtraPanel.qml+2 qml/Dash/Previews/PreviewCommentInput.qml+15 qml/Dash/Previews/PreviewInlineVideo.qml+47 qml/Dash/Previews/PreviewProgress.qml+5−1 qml/Dash/Previews/PreviewRatingInput.qml+27−1 qml/Dash/Previews/PreviewSingleton/PreviewSingleton.qml+22 qml/Dash/Previews/PreviewSingleton/qmldir+1 qml/Dash/PullToRefreshScopeStyle.qml+1 qml/Dash/ScopeListView.qml+4−2 qml/Dash/ScopesList.qml+2 qml/Dash/ScopesListCategory.qml+1 qml/Greeter/Circle.qml+57 qml/Greeter/Clock.qml−2 qml/Greeter/CoverPage.qml+6−6 qml/Components/DelayedLockscreen.qml → qml/Greeter/DelayedLockscreen.qml+6−15 qml/Greeter/FullLightDMImpl.qml+2 qml/Greeter/Greeter.qml+60−24 qml/Greeter/GreeterPrompt.qml+21−7 qml/Greeter/Infographics.qml+19−20 qml/Greeter/IntegratedLightDMImpl.qml+2−1 qml/Greeter/LightDMService.qml+2 qml/Greeter/LoginAreaContainer.qml+38 qml/Greeter/LoginList.qml+96−22 qml/Greeter/NarrowView.qml+147−61 qml/Greeter/SessionIcon.qml+47 qml/Greeter/SessionsList.qml+182 qml/Greeter/WideView.qml+109−38 qml/Greeter/graphics/infographic_circle_back.png qml/Greeter/graphics/session_icons/gnome_badge.png qml/Greeter/graphics/session_icons/kde_badge.png qml/Greeter/graphics/session_icons/recovery_console_badge.png qml/Greeter/graphics/session_icons/ubuntu_badge.png qml/Greeter/graphics/session_icons/unknown_badge.png qml/Launcher/LauncherPanel.qml+1−1 qml/Panel/IndicatorPage.qml+1 qml/Panel/Indicators/client/IndicatorRepresentation.qml+27−23 qml/Panel/Indicators/client/IndicatorsClient.qml+1−1 qml/Panel/Indicators/client/IndicatorsList.qml+19−24 qml/Panel/Indicators/client/IndicatorsTree.qml+5−8 qml/Panel/Panel.qml+11−15 qml/Shell.qml+104−12 qml/Stage/AbstractStage.qml+3 qml/Stage/DecoratedWindow.qml+50 qml/Stage/FakeMaximizeDelegate.qml+235 qml/Stage/MoveHandler.qml+179 qml/Stage/Splash.qml+3−6 qml/Stage/Stage.qml+161−77 qml/Stage/TabletStage.qml+7−5 qml/Stage/TopLevelSurfaceRepeater.qml+7−1 qml/Stage/WindowControlsOverlay.qml+80−38 qml/Stage/WindowDecoration.qml+12−43 qml/Stage/WindowResizeArea.qml+15−26 qml/Wizard/Pages/10-welcome.qml+1 qml/Wizard/Pages/20-keyboard.qml+1 qml/Wizard/Pages/30-wifi.qml+1 qml/Wizard/Pages/50-timezone.qml+1 qml/Wizard/Pages/70-passwd-type.qml+1 qml/Wizard/Pages/passcode-confirm.qml+2−2 qml/Wizard/Pages/passcode-desktop.qml+1 qml/Wizard/Pages/passcode-set.qml+2−2 qml/Wizard/Pages/password-set.qml+1 qml/Wizard/WizardItemSelector.qml+1 qml/graphics/phone_background.jpg qml/graphics/tablet_background.jpg tests/CMakeLists.txt+8−13 tests/imports/check_imports.py+58 tests/mocks/AccountsService/AccountsService.cpp+19−3 tests/mocks/AccountsService/AccountsService.h+3 tests/mocks/AccountsService/CMakeLists.txt+14 tests/mocks/CMakeLists.txt+1 tests/mocks/Cursor/CMakeLists.txt+1−1 tests/mocks/Cursor/Cursor.qml+10−1 tests/mocks/LightDM/IntegratedLightDM/MockSessionsModel.cpp+29 tests/mocks/LightDM/IntegratedLightDM/MockSessionsModel.h+12−2 tests/mocks/LightDM/IntegratedLightDM/MockUsersModel.cpp+5 tests/mocks/LightDM/IntegratedLightDM/MockUsersModel.h+2 tests/mocks/LightDM/IntegratedLightDM/liblightdm/Greeter.cpp−5 tests/mocks/LightDM/IntegratedLightDM/liblightdm/Greeter.h−2 tests/mocks/LightDM/IntegratedLightDM/liblightdm/SessionsModel.cpp+22 tests/mocks/LightDM/IntegratedLightDM/liblightdm/SessionsModel.h+3−3 tests/mocks/LightDM/IntegratedLightDM/liblightdm/SessionsModelPrivate.cpp+27−17 tests/mocks/LightDM/IntegratedLightDM/liblightdm/SessionsModelPrivate.h+5−1 tests/mocks/LightDM/IntegratedLightDM/liblightdm/UsersModelPrivate.cpp+15−1 tests/mocks/LightDM/IntegratedLightDM/plugin.cpp+1−1 tests/mocks/QtMultimedia/mediaplayer.cpp+1 tests/mocks/QtMultimedia/mediaplayer.h+1 tests/mocks/SessionBroadcast/CMakeLists.txt+8 tests/mocks/SessionBroadcast/SessionBroadcast.cpp+34 tests/mocks/SessionBroadcast/SessionBroadcast.h+39 tests/mocks/SessionBroadcast/plugin.cpp+31 tests/mocks/SessionBroadcast/plugin.h+32 tests/mocks/SessionBroadcast/qmldir+2 tests/mocks/Ubuntu/DownloadDaemonListener/MockDownloadTracker.cpp+3−1 tests/mocks/Ubuntu/DownloadDaemonListener/MockDownloadTracker.h+2−1 tests/mocks/Ubuntu/Telephony/MockTelepathyHelper.cpp+27 tests/mocks/Ubuntu/Telephony/MockTelepathyHelper.h+22 tests/mocks/Unity/Application/ApplicationInfo.cpp+1−1 tests/mocks/Unity/Application/MirSurface.h+2 tests/mocks/Unity/Indicators/CMakeLists.txt+1 tests/mocks/Unity/Indicators/IndicatorsModel.qml+32−7 tests/mocks/Unity/Indicators/fakeindicatorsmodeldata.js+1106 tests/mocks/Unity/Indicators/fakeplugin.cpp+2 tests/mocks/Utils/CMakeLists.txt+1 tests/mocks/Utils/URLDispatcher.cpp+38 tests/mocks/Utils/URLDispatcher.h+47 tests/mocks/Utils/plugin.cpp+2 tests/mocks/Utils/windowstatestorage.cpp+2−2 tests/mocks/Utils/windowstatestorage.h+8−3 tests/plugins/CMakeLists.txt+2 tests/plugins/Dash/cardcreator/1.res+8−7 tests/plugins/Dash/cardcreator/1.res.cardcreator−8 tests/plugins/Dash/cardcreator/10.res−1 tests/plugins/Dash/cardcreator/11.res+10−9 tests/plugins/Dash/cardcreator/11.res.cardcreator−8 tests/plugins/Dash/cardcreator/12.res+8−7 tests/plugins/Dash/cardcreator/12.res.cardcreator−8 tests/plugins/Dash/cardcreator/2.res−1 tests/plugins/Dash/cardcreator/3.res−1 tests/plugins/Dash/cardcreator/4.res+8−7 tests/plugins/Dash/cardcreator/4.res.cardcreator−8 tests/plugins/Dash/cardcreator/5.res−1 tests/plugins/Dash/cardcreator/6.res+8−7 tests/plugins/Dash/cardcreator/6.res.cardcreator−8 tests/plugins/Dash/cardcreator/7.res−1 tests/plugins/Dash/cardcreator/8.res+8−7 tests/plugins/Dash/cardcreator/8.res.cardcreator−8 tests/plugins/Dash/cardcreator/9.res+10−9 tests/plugins/Dash/cardcreator/9.res.cardcreator+1−9 tests/plugins/Dash/listviewwithpageheadertest.cpp+30 tests/plugins/ImageCache/CMakeLists.txt+20 tests/plugins/ImageCache/graphics/wide.jpg tests/plugins/ImageCache/test.cpp+214 qml/Components/Circle.qml → tests/plugins/ImageCache/test.qml+6−5 tests/plugins/LightDM/IntegratedLightDM/CMakeLists.txt−3 tests/plugins/LightDM/IntegratedLightDM/sessionsmodel.cpp+32 tests/plugins/LightDM/IntegratedLightDM/usersmodel.cpp+2−2 tests/plugins/SessionBroadcast/BroadcastServer.cpp+33 tests/plugins/SessionBroadcast/BroadcastServer.h+40 tests/plugins/SessionBroadcast/CMakeLists.txt+45 tests/plugins/SessionBroadcast/interfaces.xml+18 tests/plugins/SessionBroadcast/server.cpp+37 tests/plugins/SessionBroadcast/sessionbroadcasttest.cpp+111 tests/plugins/Utils/CMakeLists.txt+9 tests/plugins/Utils/URLDispatcherTest.cpp+78 tests/qmltests/CMakeLists.txt+12−12 tests/qmltests/Components/tst_Carousel.qml−1 tests/qmltests/Components/tst_Lockscreen.qml+2−41 tests/qmltests/Components/tst_WallpaperResolver.qml+21−35 tests/qmltests/Components/tst_ZoomableImage.qml+1−1 tests/qmltests/Dash/Previews/tst_Preview.qml−2 tests/qmltests/Dash/Previews/tst_PreviewActions.qml+2−1 tests/qmltests/Dash/Previews/tst_PreviewCommentInput.qml+28 tests/qmltests/Dash/Previews/tst_PreviewExpandable.qml+3−3 tests/qmltests/Dash/Previews/tst_PreviewHeader.qml−4 tests/qmltests/Dash/Previews/tst_PreviewImageGallery.qml+6−6 tests/qmltests/Dash/Previews/tst_PreviewInlineVideo.qml+42−1 tests/qmltests/Dash/Previews/tst_PreviewProgress.qml+14−1 tests/qmltests/Dash/Previews/tst_PreviewRatingDisplayCreationRanges.qml+1 tests/qmltests/Dash/Previews/tst_PreviewRatingInput.qml+32 tests/qmltests/Dash/Previews/tst_PreviewZoomableImage.qml+7−7 tests/qmltests/Dash/tst_Card.qml+7−2 tests/qmltests/Dash/tst_CardTool.qml+4−4 tests/qmltests/Dash/tst_Dash.qml+25−15 tests/qmltests/Dash/tst_DashContent.qml+5−7 tests/qmltests/Dash/tst_GenericScopeView.qml+58−61 tests/qmltests/Greeter/TestView.qml+4−3 tests/qmltests/Greeter/tst_Greeter.qml+8−1 tests/qmltests/Greeter/tst_NarrowView.qml+47−12 tests/qmltests/Greeter/tst_WideView.qml+154−7 tests/qmltests/Panel/Indicators/tst_MenuItemFactory.qml+1−1 tests/qmltests/Panel/tst_IndicatorItem.qml+4−3 tests/qmltests/Panel/tst_IndicatorPage.qml−2 tests/qmltests/Panel/tst_Panel.qml+19−2 tests/qmltests/Stage/tst_DesktopStage.qml+3−3 tests/qmltests/Stage/tst_Splash.qml+16−2 tests/qmltests/Stage/tst_WindowResizeArea.qml+9−1 tests/qmltests/tst_OrientedShell.qml+1 tests/qmltests/tst_Shell.qml+158−5 tests/qmltests/tst_ShellWithPin.qml+44−40
CMakeLists.txt+1−1
@@ -57,7 +57,7 @@ find_package(Qt5DBus 5.4 REQUIRED)
5757find_package(Qt5Concurrent 5.4 REQUIRED)
5858find_package(Qt5Sql 5.4 REQUIRED)
5959
60−pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=21)
60+pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=22)
6161pkg_check_modules(GEONAMES REQUIRED geonames>=0.2)
6262pkg_check_modules(GIO REQUIRED gio-2.0>=2.32)
6363pkg_check_modules(GLIB REQUIRED glib-2.0>=2.32)
data/unity8-greeter-wrapper+11−3
@@ -1,8 +1,7 @@
11#!/bin/sh
22# -*- Mode: sh; indent-tabs-mode: nil; tab-width: 4 -*-
33#
4−# Copyright (C) 2011,2013 Canonical Ltd
5−# Author: Michael Terry <michael.terry@canonical.com>
4+# Copyright (C) 2011,2013,2016 Canonical Ltd
65#
76# This program is free software: you can redistribute it and/or modify it under
87# the terms of the GNU General Public License as published by the Free Software
@@ -42,6 +41,8 @@ set_greeter_var()
4241SUB_SOCKET=$XDG_RUNTIME_DIR/mir_socket
4342rm -f $SUB_SOCKET # clear socket in case we were hard shut down
4443
44+echo "DBUS_SESSION_BUS_ADDRESS=${DBUS_SESSION_BUS_ADDRESS}" >"$XDG_RUNTIME_DIR/dbus-session"
45+
4546# If touch session script (which sets up grid units and mir variables) is available, use it
4647TOUCH_WRAPPER=
4748if [ -x /usr/bin/ubuntu-touch-session ]; then
@@ -65,7 +66,14 @@ set_greeter_var PA_DISABLED 1
6566MIR_SOCKET=$SUB_SOCKET $TOUCH_WRAPPER /sbin/upstart --user --no-startup-event &
6667INIT_PID=$!
6768while [ ! -e "$XDG_RUNTIME_DIR/upstart/sessions/$INIT_PID.session" ]; do sleep 0.1; done
68−export UPSTART_SESSION=$(/sbin/initctl list-sessions | grep "^$INIT_PID " | cut -d' ' -f2)
69+. "$XDG_RUNTIME_DIR/upstart/sessions/$INIT_PID.session"
70+export UPSTART_SESSION
71+
72+# Connect upstart to system, but do first command in a loop to avoid any
73+# race condition between the session file above being written and upstart being
74+# ready to handle requests.
75+while ! /sbin/initctl notify-dbus-address "$DBUS_SESSION_BUS_ADDRESS"; do sleep 0.1; done
76+/sbin/initctl start notify-cgmanager
6977
7078# Start any pre-greeter tasks if needed (like wizard)
7179/sbin/initctl emit unity8-greeter-starting
debian/changelog+79
@@ -1,3 +1,82 @@
1+unity8 (8.14+16.10.20160909.4-0ubuntu1) yakkety; urgency=medium
2+
3+ [ Albert Astals Cid ]
4+ * LVWPH: update clipItem height when list height changes (LP:
5+ #1606935)
6+ * Put the touchdown shape inside a loader
7+ * No need for the touchdown in the card for the cardtool
8+ * Add bottom gradient to the Show Less floating button
9+ * Remove artShapeSize from non cardtool cards
10+ * LVWPH: Fix case in which header was shown incorrectly
11+ * LVWPH: Fix items cut on top in the dash (or at least some instances
12+ of it)
13+ * Reduce calls to CardCreatorCache.getCardComponent while the
14+ component is being created (LP: #1615675)
15+ * Dash::test_cardIconStyle change compare into tryCompareFunction
16+ * Make PreviewActionsTest::test_comboButton more stable
17+ * Adapt onShiftedContentXChanged to work when the content changes very
18+ abruptly
19+ * Make rryGenericScopeView show the scope correctly
20+ * Make sure the spinner does not get too close to the title text (LP:
21+ #1597392)
22+ * Show "Pull to refresh" in white when overlaid in low luminance
23+ colors (LP: #1596849)
24+ * Make test_Shell non ultra slow again (LP: #1597366)
25+ * Improve findChild calls inside tryCompareFunction.
26+
27+ [ Andrea Cimitan ]
28+ * override some Qt Components definitions so we can have sane default
29+ values for flick speeds
30+ * Elide the label text inside recent searches panel (LP: #1611796)
31+ * Add a PreviewSingleton module to store some data for previews (LP:
32+ #1595235)
33+ * Use PreviewSingleton for PreviewRatingInput and PreviewCommentInput
34+ (LP: #1595235)
35+ * rework GenericScopeView PullToRefresh test
36+ * use mouseFlick instead touchFlick for manage_dash_move_current
37+ * add a couple of waiting tricks for a flaky dash test
38+
39+ [ Daniel d'Andrada ]
40+ * Set progress bar indeterminate when processing signal received. (LP:
41+ #1249349)
42+ * Implement cursor confinement (LP: #1590099)
43+
44+ [ Josh Arenson ]
45+ * Add a frontend to the sessions model and enable a session chooser in
46+ the greeter. (LP: #1512470)
47+
48+ [ Lukáš Tinkl ]
49+ * On the PC platform (as opposed to running on $devices), use the
50+ "mute" action instead of silent mode
51+ * Respect Fitt's law wrt the window control buttons in panel (LP:
52+ #1611959)
53+ * Fix 2 failing color-related tests
54+ * Implement edge maximizing (aka window snapping) (LP: #1602628,
55+ #1608465, #1611859)
56+ * Implement moving windows by Alt + left mouse button
57+
58+ [ Marco Trevisan (Treviño) ]
59+ * Indicators, mocks: add fake indicators menuitem to populate mocks
60+ with different menu types
61+ * IndicatorsClient: use PageHeader and ListItemLayout's
62+
63+ [ Michael Terry ]
64+ * Support launching apps inside a unity8 session from the greeter and
65+ support emergency dialing inside the greeter.
66+ * Fix tryShell to actually show appropriate backgrounds for each mock
67+ user.
68+ * Add an indicator to the greeter when a user is logged in (only shown
69+ if we have more than one user)
70+ * Hide the greeter OSK if indicators are open and don't show the edge
71+ tutorial during an emergency call.
72+ * Switch from lockscreen PIN pad to a passcode entry box that uses the
73+ OSK.
74+ * Use the default system wallpaper instead of our custom one.
75+ * Update look of infographic a bit
76+ * Make infographic bubbles white even on the default wallpaper.
77+
78+ -- Michał Sawicz <michal.sawicz@canonical.com> Fri, 09 Sep 2016 14:56:03 +0000
79+
180unity8 (8.14+16.10.20160831.3-0ubuntu1) yakkety; urgency=medium
281
382 [ Daniel d'Andrada ]
debian/control+8−4
@@ -11,9 +11,9 @@ Build-Depends: cmake,
1111# to g++ so we don't try to run arm g++
1212# on an x86 CPU for example, when cross-compiling.
1313 g++:native,
14− libandroid-properties-dev,
1514 graphviz,
1615 gsettings-ubuntu-schemas (>= 0.0.2+14.10.20140815),
16+ libandroid-properties-dev,
1717 libconnectivity-qt1-dev (>= 0.7.1),
1818 libevdev-dev,
1919 libgeonames-dev (>= 0.2),
@@ -32,10 +32,12 @@ Build-Depends: cmake,
3232 libqt5svg5-dev,
3333 libqt5xmlpatterns5-dev,
3434 libsystemsettings-dev,
35+ libubuntu-download-manager-common-dev,
3536 libubuntugestures5-dev (>= 1.3.2030),
3637 libubuntugestures5-private-dev (>= 1.3.2030),
3738 libudev-dev,
38− libunity-api-dev (>= 7.118),
39+ libudm-common-dev,
40+ libunity-api-dev (>= 7.119),
3941 libusermetricsoutput1-dev,
4042# Need those X11 libs touch emulation from mouse events in manual QML tests on a X11 desktop
4143 libx11-dev[!arm64 !armhf],
@@ -133,6 +135,7 @@ Depends: dmz-cursor-theme,
133135 ${shlibs:Depends},
134136Recommends: indicator-keyboard,
135137 indicator-session,
138+ unity-greeter-session-broadcast,
136139 unity-scope-click,
137140 unity-scope-mediascanner2,
138141Breaks: indicator-network (<< 0.5.1+14.10.20141014),
@@ -153,7 +156,8 @@ Depends: qml-module-qtquick-layouts,
153156 qtdeclarative5-ubuntu-settings-components (>= 0.7),
154157 qtdeclarative5-unity-notifications-plugin (>= 0.1.2) | unity-notifications-impl,
155158 ubuntu-thumbnailer-impl-0,
156− unity-application-impl-21,
159+ ubuntu-wallpapers,
160+ unity-application-impl-22,
157161 unity-notifications-impl-3,
158162 unity-plugin-scopes | unity-scopes-impl,
159163 unity-scopes-impl-12,
@@ -199,7 +203,7 @@ Pre-Depends: ${misc:Pre-Depends},
199203Depends: ${misc:Depends},
200204 ${shlibs:Depends},
201205Provides: unity-application-impl,
202− unity-application-impl-21,
206+ unity-application-impl-22,
203207Replaces: unity8-autopilot (<< 8.02+15.04.20150422-0ubuntu1)
204208Description: Fake environment for running Unity 8 shell
205209 Provides fake implementations of some QML modules used by Unity 8 shell
debian/rules+3−2
@@ -37,6 +37,7 @@ override_dh_installdeb:
3737override_dh_makeshlibs:
3838 dh_makeshlibs -Nunity8-private -Nunity8-fake-env
3939
40−# libMockLightDM-qml.so links against liblightdm-qt5-3.so which doesn't exist
4140override_dh_shlibdeps:
42− dh_shlibdeps -XlibMockLightDM-qml.so
41+ # Some mock libraries link against liblightdm-qt5-3.so which we want to
42+ # avoid, since we only really link against our mock one, not the system one.
43+ dh_shlibdeps -XlibMockLightDM-qml.so -XlibMockAccountsService-qml.so
debian/unity8-private.install+1
@@ -4,6 +4,7 @@ usr/lib/*/unity8/qml/Cursor
44usr/lib/*/unity8/qml/Dash
55usr/lib/*/unity8/qml/GlobalShortcut
66usr/lib/*/unity8/qml/Greeter
7+usr/lib/*/unity8/qml/ImageCache
78usr/lib/*/unity8/qml/LightDM
89usr/lib/*/unity8/qml/Lights
910usr/lib/*/unity8/qml/Powerd
plugins/CMakeLists.txt+1
@@ -15,6 +15,7 @@ add_subdirectory(AccountsService)
1515add_subdirectory(Cursor)
1616add_subdirectory(GlobalShortcut)
1717add_subdirectory(Greeter)
18+add_subdirectory(ImageCache)
1819add_subdirectory(LightDM)
1920add_subdirectory(Lights)
2021add_subdirectory(Dash)
plugins/Cursor/CMakeLists.txt+2−2
@@ -28,6 +28,6 @@ target_link_libraries(Cursor-qml
2828 ${QT5PLATFORM_SUPPORT_LDFLAGS}
2929)
3030
31−qt5_use_modules(Cursor-qml Qml Quick DBus Network Gui Sql Concurrent Svg)
31+qt5_use_modules(Cursor-qml Qml Quick Svg)
3232
33−add_unity8_plugin(Cursor 1.0 Cursor TARGETS Cursor-qml)
33+add_unity8_plugin(Cursor 1.1 Cursor TARGETS Cursor-qml)
plugins/Cursor/MousePointer.cpp+90−13
@@ -22,6 +22,7 @@
2222
2323#include <QQuickWindow>
2424#include <QGuiApplication>
25+#include <QtMath>
2526
2627#include <qpa/qwindowsysteminterface.h>
2728
@@ -49,28 +50,77 @@ void MousePointer::handleMouseEvent(ulong timestamp, QPointF movement, Qt::Mouse
4950 m_accumulatedMovement -= appliedMovement;
5051
5152 qreal newX = x() + appliedMovement.x();
52− if (newX < 0) {
53+ qreal newY = y() + appliedMovement.y();
54+ const qreal sceneWidth = parentItem()->width();
55+ const qreal sceneHeight = parentItem()->height();
56+
57+ if (newX <= 0 && newY < m_topBoundaryOffset) { // top left corner
58+ const auto distance = qSqrt(qPow(newX, 2) + qPow(newY-m_topBoundaryOffset, 2));
59+ Q_EMIT pushedTopLeftCorner(qAbs(distance), buttons);
60+ m_pushing = true;
61+ } else if (newX >= sceneWidth-1 && newY < m_topBoundaryOffset) { // top right corner
62+ const auto distance = qSqrt(qPow(newX-sceneWidth, 2) + qPow(newY-m_topBoundaryOffset, 2));
63+ Q_EMIT pushedTopRightCorner(qAbs(distance), buttons);
64+ m_pushing = true;
65+ } else if (newX < 0 && newY >= sceneHeight-1) { // bottom left corner
66+ const auto distance = qSqrt(qPow(newX, 2) + qPow(newY-sceneHeight, 2));
67+ Q_EMIT pushedBottomLeftCorner(qAbs(distance), buttons);
68+ m_pushing = true;
69+ } else if (newX >= sceneWidth-1 && newY >= sceneHeight-1) { // bottom right corner
70+ const auto distance = qSqrt(qPow(newX-sceneWidth, 2) + qPow(newY-sceneHeight, 2));
71+ Q_EMIT pushedBottomRightCorner(qAbs(distance), buttons);
72+ m_pushing = true;
73+ } else if (newX < 0) { // left edge
5374 Q_EMIT pushedLeftBoundary(qAbs(newX), buttons);
54− newX = 0;
55− } else if (newX >= parentItem()->width()) {
56− Q_EMIT pushedRightBoundary(newX - (parentItem()->width() - 1), buttons);
57− newX = parentItem()->width() - 1;
75+ m_pushing = true;
76+ } else if (newX >= sceneWidth) { // right edge
77+ Q_EMIT pushedRightBoundary(newX - (sceneWidth - 1), buttons);
78+ m_pushing = true;
79+ } else if (newY < m_topBoundaryOffset) { // top edge
80+ Q_EMIT pushedTopBoundary(qAbs(newY - m_topBoundaryOffset), buttons);
81+ m_pushing = true;
82+ } else if (Q_LIKELY(newX > 0 && newX < sceneWidth-1 && newY > 0 && newY < sceneHeight-1)) { // normal pos, not pushing
83+ if (m_pushing) {
84+ Q_EMIT pushStopped();
85+ m_pushing = false;
86+ }
5887 }
59− setX(newX);
6088
61− qreal newY = y() + appliedMovement.y();
62− if (newY < 0) {
63− newY = 0;
64− } else if (newY >= parentItem()->height()) {
65− newY = parentItem()->height() - 1;
66− }
67− setY(newY);
89+ applyItemConfinement(newX, newY);
90+
91+ setX(qBound(0.0, newX, sceneWidth - 1));
92+ setY(qBound(0.0, newY, sceneHeight - 1));
6893
6994 QPointF scenePosition = mapToItem(nullptr, QPointF(0, 0));
7095 QWindowSystemInterface::handleMouseEvent(window(), timestamp, scenePosition /*local*/, scenePosition /*global*/,
7196 buttons, modifiers);
7297}
7398
99+void MousePointer::applyItemConfinement(qreal &newX, qreal &newY)
100+{
101+ Q_ASSERT(parentItem() != nullptr);
102+
103+ if (m_confiningItem.isNull()) {
104+ return;
105+ }
106+
107+ QRectF confiningItemGeometry(0, 0, m_confiningItem->width(), m_confiningItem->height());
108+
109+ QRectF confiningRect = m_confiningItem->mapRectToItem(parentItem(), confiningItemGeometry);
110+
111+ if (newX < confiningRect.x()) {
112+ newX = confiningRect.x();
113+ } else if (newX > confiningRect.right()) {
114+ newX = confiningRect.right();
115+ }
116+
117+ if (newY < confiningRect.y()) {
118+ newY = confiningRect.y();
119+ } else if (newY > confiningRect.bottom()) {
120+ newY = confiningRect.bottom();
121+ }
122+}
123+
74124void MousePointer::handleWheelEvent(ulong timestamp, QPoint angleDelta, Qt::KeyboardModifiers modifiers)
75125{
76126 if (!parentItem()) {
@@ -82,6 +132,20 @@ void MousePointer::handleWheelEvent(ulong timestamp, QPoint angleDelta, Qt::Keyb
82132 QPoint() /* pixelDelta */, angleDelta, modifiers, Qt::ScrollUpdate);
83133}
84134
135+int MousePointer::topBoundaryOffset() const
136+{
137+ return m_topBoundaryOffset;
138+}
139+
140+void MousePointer::setTopBoundaryOffset(int topBoundaryOffset)
141+{
142+ if (m_topBoundaryOffset == topBoundaryOffset)
143+ return;
144+
145+ m_topBoundaryOffset = topBoundaryOffset;
146+ Q_EMIT topBoundaryOffsetChanged(topBoundaryOffset);
147+}
148+
85149void MousePointer::itemChange(ItemChange change, const ItemChangeData &value)
86150{
87151 if (change == ItemSceneChange) {
@@ -156,3 +220,16 @@ void MousePointer::setCustomCursor(const QCursor &customCursor)
156220{
157221 CursorImageProvider::instance()->setCustomCursor(customCursor);
158222}
223+
224+QQuickItem* MousePointer::confiningItem() const
225+{
226+ return m_confiningItem.data();
227+}
228+
229+void MousePointer::setConfiningItem(QQuickItem *item)
230+{
231+ if (item != m_confiningItem) {
232+ m_confiningItem = item;
233+ Q_EMIT confiningItemChanged();
234+ }
235+}
plugins/Cursor/MousePointer.h+23
@@ -27,6 +27,8 @@
2727
2828class MousePointer : public MirMousePointerInterface {
2929 Q_OBJECT
30+ Q_PROPERTY(QQuickItem* confiningItem READ confiningItem WRITE setConfiningItem NOTIFY confiningItemChanged)
31+ Q_PROPERTY(int topBoundaryOffset READ topBoundaryOffset WRITE setTopBoundaryOffset NOTIFY topBoundaryOffsetChanged)
3032public:
3133 MousePointer(QQuickItem *parent = nullptr);
3234
@@ -38,6 +40,12 @@ public:
3840
3941 void setCustomCursor(const QCursor &) override;
4042
43+ QQuickItem* confiningItem() const;
44+ void setConfiningItem(QQuickItem*);
45+
46+ int topBoundaryOffset() const;
47+ void setTopBoundaryOffset(int topBoundaryOffset);
48+
4149public Q_SLOTS:
4250 void handleMouseEvent(ulong timestamp, QPointF movement, Qt::MouseButtons buttons,
4351 Qt::KeyboardModifiers modifiers) override;
@@ -46,7 +54,16 @@ public Q_SLOTS:
4654Q_SIGNALS:
4755 void pushedLeftBoundary(qreal amount, Qt::MouseButtons buttons);
4856 void pushedRightBoundary(qreal amount, Qt::MouseButtons buttons);
57+ void pushedTopBoundary(qreal amount, Qt::MouseButtons buttons);
58+ void pushedTopLeftCorner(qreal amount, Qt::MouseButtons buttons);
59+ void pushedTopRightCorner(qreal amount, Qt::MouseButtons buttons);
60+ void pushedBottomLeftCorner(qreal amount, Qt::MouseButtons buttons);
61+ void pushedBottomRightCorner(qreal amount, Qt::MouseButtons buttons);
62+ void pushStopped();
4963 void mouseMoved();
64+ void confiningItemChanged();
65+
66+ void topBoundaryOffsetChanged(int topBoundaryOffset);
5067
5168protected:
5269 void itemChange(ItemChange change, const ItemChangeData &value) override;
@@ -56,6 +73,7 @@ private Q_SLOTS:
5673
5774private:
5875 void registerWindow(QWindow *window);
76+ void applyItemConfinement(qreal &newX, qreal &newY);
5977
6078 QPointer<QWindow> m_registeredWindow;
6179 QPointer<QScreen> m_registeredScreen;
@@ -64,6 +82,11 @@ private:
6482
6583 // Accumulated, unapplied, mouse movement.
6684 QPointF m_accumulatedMovement;
85+
86+ QPointer<QQuickItem> m_confiningItem;
87+
88+ int m_topBoundaryOffset{0};
89+ bool m_pushing{false};
6790};
6891
6992#endif // MOUSEPOINTER_H
plugins/Dash/CardCreator.js+12−10
@@ -331,13 +331,15 @@ var kEmblemIconCode = 'Icon { \n\
331331 }\n';
332332
333333// %1 is used as anchors of touchdown effect
334−var kTouchdownCode = 'UbuntuShape { \n\
335− id: touchdown; \n\
336− objectName: "touchdown"; \n\
334+var kTouchdownCode = 'Loader { \n\
335+ active: root.pressed; \n\
337336 anchors { %1 } \n\
338− visible: root.pressed; \n\
339− radius: "medium"; \n\
340− borderSource: "radius_pressed.sci" \n\
337+ sourceComponent: UbuntuShape { \n\
338+ objectName: "touchdown"; \n\
339+ anchors.fill: parent; \n\
340+ radius: "medium"; \n\
341+ borderSource: "radius_pressed.sci" \n\
342+ } \n\
341343 }\n';
342344
343345// %1 is used as anchors of subtitleLabel
@@ -506,10 +508,10 @@ function cardString(template, components, isCardTool, artShapeStyle, categoryLay
506508 }
507509
508510 if (hasArt) {
509− code += 'readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);\n';
510−
511511 var artShapeAspect;
512512 if (isCardTool) {
513+ code += 'readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);\n';
514+
513515 var artShapeAspect = components["art"] && components["art"]["aspect-ratio"] || 1;
514516 if (isNaN(artShapeAspect)) {
515517 artShapeAspect = 1;
@@ -573,7 +575,7 @@ function cardString(template, components, isCardTool, artShapeStyle, categoryLay
573575 .arg(artShapeHolderShapeCode)
574576 .arg(fallbackStatusCode)
575577 .arg(fallbackURICode);
576− } else {
578+ } else if (isCardTool) {
577579 code += 'readonly property size artShapeSize: Qt.size(-1, -1);\n'
578580 }
579581
@@ -923,7 +925,7 @@ function cardString(template, components, isCardTool, artShapeStyle, categoryLay
923925 code += kSocialActionsRowCode.arg(socialAnchors).arg(socialColor);
924926 }
925927
926− if (artShapeStyle != "shadow" && artShapeStyle != "icon") {
928+ if (artShapeStyle != "shadow" && artShapeStyle != "icon" && !isCardTool) {
927929 var touchdownAnchors;
928930 if (hasBackground) {
929931 touchdownAnchors = 'fill: backgroundLoader';
plugins/Dash/listviewwithpageheader.cpp+5−4
@@ -587,6 +587,10 @@ void ListViewWithPageHeader::adjustHeader(qreal diff)
587587 } else {
588588 m_headerItem->setY(-m_minYExtent);
589589 }
590+ } else if (m_headerItemShownHeight == 0 && m_previousContentY > m_headerItem->y() && contentY() < m_headerItem->y()) {
591+ // The header was hidden but now that we've moved up (e.g. because of item removed) it's visible
592+ // make sure it isn't
593+ m_headerItem->setY(-m_minYExtent);
590594 }
591595 Q_EMIT headerItemShownHeightChanged();
592596 } else {
@@ -978,6 +982,7 @@ void ListViewWithPageHeader::onContentWidthChanged()
978982
979983void ListViewWithPageHeader::onHeightChanged()
980984{
985+ m_clipItem->setHeight(height() - m_headerItemShownHeight);
981986 polish();
982987}
983988
@@ -1388,10 +1393,6 @@ void ListViewWithPageHeader::updatePolish()
13881393
13891394 m_contentHeightDirty = false;
13901395 adjustMinYExtent();
1391− if (contentHeight + m_minYExtent < height()) {
1392− // need this since in the previous call to adjustMinYExtent contentHeight is not set yet
1393− m_minYExtent = 0;
1394− }
13951396 m_inContentHeightKeepHeaderShown = m_headerItem && m_headerItem->y() == contentY();
13961397 setContentHeight(contentHeight);
13971398 m_inContentHeightKeepHeaderShown = false;
plugins/ImageCache/CMakeLists.txtnew file+8
@@ -0,0 +1,8 @@
1+add_library(ImageCache-qml MODULE
2+ ImageCache.cpp
3+ plugin.cpp
4+ )
5+
6+qt5_use_modules(ImageCache-qml Gui Qml Quick)
7+
8+add_unity8_plugin(ImageCache 0.1 ImageCache TARGETS ImageCache-qml)
plugins/ImageCache/ImageCache.cppnew file+135
@@ -0,0 +1,135 @@
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 <QDateTime>
18+#include <QDebug>
19+#include <QDir>
20+#include <QUrl>
21+#include <QUrlQuery>
22+
23+#include "ImageCache.h"
24+
25+ImageCache::ImageCache()
26+ : QQuickImageProvider(QQmlImageProviderBase::Image,
27+ QQmlImageProviderBase::ForceAsynchronousImageLoading)
28+{
29+}
30+
31+QString ImageCache::imageCacheRoot()
32+{
33+ QString xdgCache(qgetenv("XDG_CACHE_HOME"));
34+ if (xdgCache.isEmpty()) {
35+ xdgCache = QDir::homePath() + QStringLiteral("/.cache");
36+ }
37+
38+ return QDir::cleanPath(xdgCache) + QStringLiteral("/unity8/imagecache");
39+}
40+
41+QFileInfo ImageCache::imagePath(const QUrl &image)
42+{
43+ QUrlQuery query(image);
44+
45+ auto name = query.queryItemValue(QStringLiteral("name"));
46+ if (name.isEmpty()) {
47+ name = QStringLiteral("/paths") + image.toLocalFile();
48+ } else {
49+ name = QStringLiteral("/names/") + name;
50+ }
51+
52+ return QFileInfo(imageCacheRoot() + name);
53+}
54+
55+bool ImageCache::needsUpdate(const QUrl &image, const QFileInfo &cachePath, const QSize &imageSize, const QSize &requestedSize, QSize &finalSize)
56+{
57+ if (!cachePath.exists())
58+ return true;
59+
60+ QFileInfo imageInfo(image.toLocalFile());
61+ if (imageInfo.lastModified() > cachePath.lastModified())
62+ return true;
63+
64+ QSize cacheSize(QImageReader(cachePath.filePath()).size());
65+ finalSize = calculateSize(imageSize, requestedSize);
66+ if (finalSize.isValid() && cacheSize != finalSize)
67+ return true;
68+
69+ return false;
70+}
71+
72+QSize ImageCache::calculateSize(const QSize &imageSize, const QSize &requestedSize)
73+{
74+ QSize finalSize(requestedSize);
75+
76+ if (finalSize.width() == 0) {
77+ finalSize.setWidth(imageSize.width() * (((double)finalSize.height()) / imageSize.height()));
78+ } else if (finalSize.height() == 0) {
79+ finalSize.setHeight(imageSize.height() * (((double)finalSize.width()) / imageSize.width()));
80+ }
81+
82+ return finalSize;
83+}
84+
85+QImage ImageCache::loadAndCacheImage(QImageReader &reader, const QFileInfo &cachePath, const QSize &finalSize)
86+{
87+ reader.setQuality(100);
88+ reader.setScaledSize(finalSize);
89+ auto format = reader.format(); // can't get this after reading
90+
91+ QImage loadedImage(reader.read());
92+ if (loadedImage.isNull()) {
93+ qWarning() << "ImageCache could not read image" << reader.fileName() << ":" << reader.errorString();
94+ return QImage();
95+ }
96+
97+ cachePath.dir().mkpath(QStringLiteral("."));
98+ loadedImage.save(cachePath.filePath(), format, 100);
99+
100+ return loadedImage;
101+}
102+
103+QImage ImageCache::requestImage(const QString &id, QSize *size, const QSize &requestedSize)
104+{
105+ QUrl image(id);
106+ QImageReader imageReader(image.toLocalFile());
107+ QSize imageSize(imageReader.size());
108+ QImage result;
109+
110+ // Early exit here, with no sourceSize, scaled-up sourceSize, or bad source image
111+ if ((requestedSize.width() <= 0 && requestedSize.height() <= 0) ||
112+ imageSize.isEmpty() ||
113+ requestedSize.height() >= imageSize.height() ||
114+ requestedSize.width() >= imageSize.width()) {
115+ // We're only interested in scaling down, not up.
116+ result = imageReader.read();
117+ *size = result.size();
118+ return result;
119+ }
120+
121+ auto cachePath = imagePath(image);
122+ QSize finalSize;
123+
124+ if (needsUpdate(image, cachePath, imageSize, requestedSize, finalSize)) {
125+ if (finalSize.isEmpty()) {
126+ finalSize = calculateSize(imageSize, requestedSize);
127+ }
128+ result = loadAndCacheImage(imageReader, cachePath, finalSize);
129+ } else {
130+ result = QImage(cachePath.filePath());
131+ }
132+
133+ *size = result.size();
134+ return result;
135+}
plugins/ImageCache/ImageCache.hnew file+53
@@ -0,0 +1,53 @@
1+/*
2+ * Copyright (C) 2016 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#pragma once
18+
19+#include <QFileInfo>
20+#include <QImageReader>
21+#include <QQuickImageProvider>
22+#include <QSize>
23+
24+/**
25+ * This class accepts an id formulated like a URL. So you'd use something like:
26+ *
27+ * Image {
28+ * source: "image://unity8imagecache/file:///usr/share/..."
29+ * }
30+ *
31+ * Right now, we only support file:/// URLs. We do accept some flags though:
32+ *
33+ * ?name=NAME
34+ * - This will use NAME as the cache lookup key instead of the provided URL.
35+ *
36+ * We don't do any cleaning of old cached files yet. So you may want to always
37+ * provide a name to avoid leaving lots of files around.
38+ */
39+
40+class ImageCache: public QQuickImageProvider
41+{
42+public:
43+ ImageCache();
44+
45+ QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize) override;
46+
47+private:
48+ static QString imageCacheRoot();
49+ static QFileInfo imagePath(const QUrl &image);
50+ static bool needsUpdate(const QUrl &image, const QFileInfo &cachePath, const QSize &imageSize, const QSize &requestedSize, QSize &finalSize);
51+ static QSize calculateSize(const QSize &imageSize, const QSize &requestedSize);
52+ static QImage loadAndCacheImage(QImageReader &reader, const QFileInfo &cachePath, const QSize &finalSize);
53+};
plugins/ImageCache/plugin.cppnew file+39
@@ -0,0 +1,39 @@
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 <QQmlEngine>
18+
19+#include "ImageCache.h"
20+#include "plugin.h"
21+
22+#include <QtQml>
23+
24+void ImageCachePlugin::registerTypes(const char *uri)
25+{
26+ Q_ASSERT(uri == QLatin1String("ImageCache"));
27+
28+ qmlRegisterTypeNotAvailable(uri, 0, 1, "__ImageCacheIgnoreMe",
29+ QStringLiteral("Ignore this: QML plugins must contain at least one type"));
30+}
31+
32+void ImageCachePlugin::initializeEngine(QQmlEngine* engine, const char* uri)
33+{
34+ Q_ASSERT(uri == QLatin1String("ImageCache"));
35+
36+ QQmlExtensionPlugin::initializeEngine(engine, uri);
37+
38+ engine->addImageProvider("unity8imagecache", new ImageCache);
39+}
plugins/ImageCache/plugin.hnew file+31
@@ -0,0 +1,31 @@
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+#pragma once
18+
19+#include <QQmlExtensionPlugin>
20+
21+class QQmlEngine;
22+
23+class ImageCachePlugin : public QQmlExtensionPlugin
24+{
25+ Q_OBJECT
26+ Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
27+
28+public:
29+ void registerTypes(const char* uri) override;
30+ void initializeEngine(QQmlEngine* engine, const char* uri) override;
31+};
plugins/ImageCache/qmldirnew file+2
@@ -0,0 +1,2 @@
1+module ImageCache
2+plugin ImageCache-qml
plugins/LightDM/IntegratedLightDM/liblightdm/Greeter.cpp−5
@@ -78,11 +78,6 @@ bool Greeter::showRemoteLoginHint() const
7878 return true;
7979}
8080
81−bool Greeter::lockHint () const
82−{
83− return false;
84−}
85−
8681QString Greeter::selectUserHint() const
8782{
8883 return QLatin1String("");
plugins/LightDM/IntegratedLightDM/liblightdm/Greeter.h−2
@@ -45,7 +45,6 @@ class Q_DECL_EXPORT Greeter : public QObject
4545
4646 Q_PROPERTY(QString hostname READ hostname CONSTANT)
4747 Q_PROPERTY(bool hasGuestAccount READ hasGuestAccountHint CONSTANT)
48− Q_PROPERTY(bool locked READ lockHint CONSTANT)
4948
5049 Q_ENUMS(PromptType MessageType)
5150
@@ -68,7 +67,6 @@ public:
6867 bool hideUsersHint() const;
6968 bool showManualLoginHint() const;
7069 bool showRemoteLoginHint() const;
71− bool lockHint () const;
7270 bool hasGuestAccountHint() const;
7371 QString selectUserHint() const;
7472 bool selectGuestHint() const;
plugins/LightDM/SessionsModel.cpp+22−16
@@ -47,15 +47,15 @@ void SessionsModel::setIconSearchDirectories(const QList<QUrl> searchDirectories
4747 Q_EMIT iconSearchDirectoriesChanged();
4848}
4949
50−QUrl SessionsModel::iconUrl(const QString sessionName) const
50+QUrl SessionsModel::iconUrl(const QString sessionKey) const
5151{
5252 Q_FOREACH(const QUrl& searchDirectory, m_iconSearchDirectories)
5353 {
5454 // This is an established icon naming convention
5555 QString customIconUrl = searchDirectory.toString(QUrl::StripTrailingSlash) +
56− "/custom_" + sessionName + "_badge.png";
56+ "/custom_" + sessionKey + "_badge.png";
5757 QString iconUrl = searchDirectory.toString(QUrl::StripTrailingSlash) +
58− "/" + sessionName + "_badge.png";
58+ "/" + sessionKey + "_badge.png";
5959
6060 QFile customIconFile(customIconUrl);
6161 QFile iconFile(iconUrl);
@@ -63,29 +63,35 @@ QUrl SessionsModel::iconUrl(const QString sessionName) const
6363 return QUrl(customIconUrl);
6464 } else if (iconFile.exists()) {
6565 return QUrl(iconUrl);
66− } else{
66+ } else {
6767 // Search the legacy way
6868 QString path = searchDirectory.toString(QUrl::StripTrailingSlash) + "/";
69− if (sessionName == "ubuntu" || sessionName == "ubuntu-2d") {
69+ bool iconFound = false;
70+ if (sessionKey == "ubuntu" || sessionKey == "ubuntu-2d") {
7071 path += "ubuntu_badge.png";
72+ iconFound = true;
7173 } else if(
72− sessionName == "gnome-classic" ||
73− sessionName == "gnome-flashback-compiz" ||
74− sessionName == "gnome-flashback-metacity" ||
75− sessionName == "gnome-shell" ||
76− sessionName == "gnome-wayland" ||
77− sessionName == "gnome"
74+ sessionKey == "gnome-classic" ||
75+ sessionKey == "gnome-flashback-compiz" ||
76+ sessionKey == "gnome-flashback-metacity" ||
77+ sessionKey == "gnome-shell" ||
78+ sessionKey == "gnome-wayland" ||
79+ sessionKey == "gnome"
7880 ){
7981 path += "gnome_badge.png";
80− } else if (sessionName == "plasma") {
82+ iconFound = true;
83+ } else if (sessionKey == "plasma") {
8184 path += "kde_badge.png";
82− } else if (sessionName == "xterm") {
85+ iconFound = true;
86+ } else if (sessionKey == "xterm") {
8387 path += "recovery_console_badge.png";
84− } else if (sessionName == "remote-login") {
88+ iconFound = true;
89+ } else if (sessionKey == "remote-login") {
8590 path += "remote_login_help.png";
91+ iconFound = true;
8692 }
8793
88− if (QFile(path).exists()) {
94+ if (QFile(path).exists() && iconFound) {
8995 return path;
9096 }
9197 }
@@ -99,7 +105,7 @@ QVariant SessionsModel::data(const QModelIndex& index, int role) const
99105{
100106 switch (role) {
101107 case SessionsModel::IconRole:
102− return iconUrl(m_model->data(index, Qt::DisplayRole).toString());
108+ return iconUrl(m_model->data(index, QLightDM::SessionsModel::KeyRole).toString());
103109 default:
104110 return m_model->data(index, role);
105111 }
plugins/LightDM/plugin.cpp+1−1
@@ -82,7 +82,7 @@ void PLUGIN_CLASSNAME::registerTypes(const char *uri)
8282 qRegisterMetaType<QLightDM::Greeter::PromptType>("QLightDM::Greeter::PromptType");
8383
8484 qmlRegisterSingletonType<SessionsModel>(uri, 0, 1, "Sessions", sessions_provider);
85− qmlRegisterUncreatableType<SessionsModel>(uri, 0, 1, "SessionRoles", QStringLiteral("Type is not instantiable"));
85+ qmlRegisterUncreatableType<QLightDM::SessionsModel>(uri, 0, 1, "SessionRoles", QStringLiteral("Type is not instantiable"));
8686
8787 qmlRegisterSingletonType<UsersModel>(uri, 0, 1, "Users", users_provider);
8888 qmlRegisterUncreatableType<QLightDM::UsersModel>(uri, 0, 1, "UserRoles", QStringLiteral("Type is not instantiable"));
plugins/SessionBroadcast/SessionBroadcast.cpp+35−9
@@ -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,12 +12,12 @@
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 "SessionBroadcast.h"
2018#include <QDBusConnection>
19+#include <QDBusConnectionInterface>
20+#include <QDBusInterface>
2121
2222#include <glib.h>
2323
@@ -27,13 +27,39 @@ SessionBroadcast::SessionBroadcast(QObject* parent)
2727 m_username = QString::fromUtf8(g_get_user_name());
2828
2929 auto connection = QDBusConnection::SM_BUSNAME();
30+ auto interface = connection.interface();
31+ interface->startService(QStringLiteral("com.canonical.Unity.Greeter.Broadcast"));
32+
33+ m_broadcaster = new QDBusInterface(QStringLiteral("com.canonical.Unity.Greeter.Broadcast"),
34+ QStringLiteral("/com/canonical/Unity/Greeter/Broadcast"),
35+ QStringLiteral("com.canonical.Unity.Greeter.Broadcast"),
36+ connection, this);
37+
38+ connect(m_broadcaster, SIGNAL(StartUrl(const QString &, const QString &)),
39+ this, SLOT(onStartUrl(const QString &, const QString &)));
40+
41+ connect(m_broadcaster, SIGNAL(ShowHome(const QString &)),
42+ this, SLOT(onShowHome(const QString &)));
43+}
44+
45+void SessionBroadcast::requestUrlStart(const QString &username, const QString &url)
46+{
47+ m_broadcaster->asyncCall(QStringLiteral("RequestUrlStart"), username, url);
48+}
49+
50+void SessionBroadcast::requestHomeShown(const QString &username)
51+{
52+ m_broadcaster->asyncCall(QStringLiteral("RequestHomeShown"), username);
53+}
3054
31− connection.connect(QStringLiteral("com.canonical.Unity.Greeter.Broadcast"),
32− QStringLiteral("/com/canonical/Unity/Greeter/Broadcast"),
33− QStringLiteral("com.canonical.Unity.Greeter.Broadcast"),
34− QStringLiteral("ShowHome"),
35− this,
36− SLOT(onShowHome(const QString &)));
55+void SessionBroadcast::onStartUrl(const QString &username, const QString &url)
56+{
57+ // Since this signal is just used for testing, we don't *really* care if
58+ // username matches, but just in case we do eventually use the signal, we
59+ // should only listen to our own requests.
60+ if (username == m_username) {
61+ Q_EMIT startUrl(url);
62+ }
3763}
3864
3965void SessionBroadcast::onShowHome(const QString &username)
plugins/SessionBroadcast/SessionBroadcast.h+10−3
@@ -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_SESSIONBROADCAST_H
@@ -31,14 +29,23 @@ class SessionBroadcast: public QObject
3129public:
3230 explicit SessionBroadcast(QObject *parent = 0);
3331
32+ Q_INVOKABLE void requestUrlStart(const QString &username, const QString &url);
33+ Q_INVOKABLE void requestHomeShown(const QString &username);
34+
3435Q_SIGNALS:
36+ // This signal isn't actually used by the shell
37+ // (unity-greeter-session-broadcast handles launching an app for us), but
38+ // it's useful for testing the plugin.
39+ void startUrl(const QString &url);
3540 void showHome();
3641
3742private Q_SLOTS:
43+ void onStartUrl(const QString &username, const QString &url);
3844 void onShowHome(const QString &username);
3945
4046private:
4147 QString m_username;
48+ QDBusInterface *m_broadcaster;
4249};
4350
4451#endif
plugins/Ubuntu/DownloadDaemonListener/CMakeLists.txt+3−1
@@ -12,5 +12,7 @@ set(DOWNLOADDAEMONLISTENER_SOURCES
1212add_library(DownloadDaemonListener MODULE ${DOWNLOADDAEMONLISTENER_SOURCES})
1313
1414qt5_use_modules(DownloadDaemonListener Qml Quick DBus Core)
15−
15+pkg_check_modules(DDLADAPTER REQUIRED udm-common)
16+add_definitions(${DDLADAPTER_CFLAGS} ${DDLADAPTER_CFLAGS_OTHER})
17+target_link_libraries(DownloadDaemonListener ${DDLADAPTER_LIBRARIES})
1618add_unity8_plugin(Ubuntu.DownloadDaemonListener 0.1 Ubuntu/DownloadDaemonListener TARGETS DownloadDaemonListener)
plugins/Ubuntu/DownloadDaemonListener/DownloadTracker.cpp+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 it
55 * under the terms of the GNU Lesser General Public License, as
@@ -78,6 +78,7 @@ void DownloadTracker::startService()
7878 connect(m_adaptor, static_cast<void (DownloadTrackerAdaptor::*)(qulonglong, qulonglong)>(&DownloadTrackerAdaptor::progress), this, &DownloadTracker::progress);
7979 connect(m_adaptor, &DownloadTrackerAdaptor::resumed, this, &DownloadTracker::resumed);
8080 connect(m_adaptor, &DownloadTrackerAdaptor::started, this, &DownloadTracker::started);
81+ connect(m_adaptor, &DownloadTrackerAdaptor::processing, this, &DownloadTracker::processing);
8182 }
8283 // FIXME find a better way of determining if the service is ready
8384 Q_EMIT serviceReadyChanged(m_adaptor && m_adaptor->isValid());
plugins/Ubuntu/DownloadDaemonListener/DownloadTracker.h+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 it
55 * under the terms of the GNU Lesser General Public License, as
@@ -54,6 +54,7 @@ Q_SIGNALS:
5454 void error(const QString &error);
5555 void finished(const QString &path);
5656 void paused(bool success);
57+ void processing(const QString &path);
5758 void progress(qulonglong received, qulonglong total);
5859 void resumed(bool success);
5960 void started(bool success);
plugins/Ubuntu/DownloadDaemonListener/interface/downloadtrackeradaptor.cpp+2−2
@@ -1,8 +1,8 @@
11/*
22 * This file was generated by qdbusxml2cpp version 0.8
3− * Command line was: qdbusxml2cpp -v -c DownloadTrackerAdaptor -p downloadtrackeradaptor.h:downloadtrackeradaptor.cpp -i metatypes.h com.canonical.applications.download.xml
3+ * Command line was: qdbusxml2cpp -v -c DownloadTrackerAdaptor -p downloadtrackeradaptor.h:downloadtrackeradaptor.cpp -i metatypes.h ../../../../../../ubuntu-download-manager/trunk/docs/dbus/com.canonical.applications.download.xml
44 *
5− * qdbusxml2cpp is Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
5+ * qdbusxml2cpp is Copyright (C) 2015 The Qt Company Ltd.
66 *
77 * This is an auto-generated file.
88 * This file may have been hand-edited. Look for HAND-EDIT comments
plugins/Ubuntu/DownloadDaemonListener/interface/downloadtrackeradaptor.h+71−4
@@ -1,15 +1,15 @@
11/*
22 * This file was generated by qdbusxml2cpp version 0.8
3− * Command line was: qdbusxml2cpp -v -c DownloadTrackerAdaptor -p downloadtrackeradaptor.h:downloadtrackeradaptor.cpp -i metatypes.h com.canonical.applications.download.xml
3+ * Command line was: qdbusxml2cpp -v -c DownloadTrackerAdaptor -p downloadtrackeradaptor.h:downloadtrackeradaptor.cpp -i metatypes.h ../../../../../../ubuntu-download-manager/trunk/docs/dbus/com.canonical.applications.download.xml
44 *
5− * qdbusxml2cpp is Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
5+ * qdbusxml2cpp is Copyright (C) 2015 The Qt Company Ltd.
66 *
77 * This is an auto-generated file.
88 * Do not edit! All changes made to it will be lost.
99 */
1010
11−#ifndef DOWNLOADTRACKERADAPTOR_H_1374434371
12−#define DOWNLOADTRACKERADAPTOR_H_1374434371
11+#ifndef DOWNLOADTRACKERADAPTOR_H
12+#define DOWNLOADTRACKERADAPTOR_H
1313
1414#include <QtCore/QObject>
1515#include <QtCore/QByteArray>
@@ -36,6 +36,22 @@ public:
3636
3737 ~DownloadTrackerAdaptor();
3838
39+ Q_PROPERTY(QString ClickPackage READ clickPackage)
40+ inline QString clickPackage() const
41+ { return qvariant_cast< QString >(property("ClickPackage")); }
42+
43+ Q_PROPERTY(QString DestinationApp READ destinationApp)
44+ inline QString destinationApp() const
45+ { return qvariant_cast< QString >(property("DestinationApp")); }
46+
47+ Q_PROPERTY(bool ShowInIndicator READ showInIndicator)
48+ inline bool showInIndicator() const
49+ { return qvariant_cast< bool >(property("ShowInIndicator")); }
50+
51+ Q_PROPERTY(QString Title READ title)
52+ inline QString title() const
53+ { return qvariant_cast< QString >(property("Title")); }
54+
3955public Q_SLOTS: // METHODS
4056 inline QDBusPendingReply<> allowGSMDownload(bool allowed)
4157 {
@@ -50,6 +66,24 @@ public Q_SLOTS: // METHODS
5066 return asyncCallWithArgumentList(QStringLiteral("cancel"), argumentList);
5167 }
5268
69+ inline QDBusPendingReply<> collected()
70+ {
71+ QList<QVariant> argumentList;
72+ return asyncCallWithArgumentList(QStringLiteral("collected"), argumentList);
73+ }
74+
75+ inline QDBusPendingReply<QString> filePath()
76+ {
77+ QList<QVariant> argumentList;
78+ return asyncCallWithArgumentList(QStringLiteral("filePath"), argumentList);
79+ }
80+
81+ inline QDBusPendingReply<StringMap> headers()
82+ {
83+ QList<QVariant> argumentList;
84+ return asyncCallWithArgumentList(QStringLiteral("headers"), argumentList);
85+ }
86+
5387 inline QDBusPendingReply<bool> isGSMDownloadAllowed()
5488 {
5589 QList<QVariant> argumentList;
@@ -80,6 +114,27 @@ public Q_SLOTS: // METHODS
80114 return asyncCallWithArgumentList(QStringLiteral("resume"), argumentList);
81115 }
82116
117+ inline QDBusPendingReply<> setDestinationDir(const QString &path)
118+ {
119+ QList<QVariant> argumentList;
120+ argumentList << QVariant::fromValue(path);
121+ return asyncCallWithArgumentList(QStringLiteral("setDestinationDir"), argumentList);
122+ }
123+
124+ inline QDBusPendingReply<> setHeaders(StringMap headers)
125+ {
126+ QList<QVariant> argumentList;
127+ argumentList << QVariant::fromValue(headers);
128+ return asyncCallWithArgumentList(QStringLiteral("setHeaders"), argumentList);
129+ }
130+
131+ inline QDBusPendingReply<> setMetadata(const QVariantMap &data)
132+ {
133+ QList<QVariant> argumentList;
134+ argumentList << QVariant::fromValue(data);
135+ return asyncCallWithArgumentList(QStringLiteral("setMetadata"), argumentList);
136+ }
137+
83138 inline QDBusPendingReply<> setThrottle(qulonglong speed)
84139 {
85140 QList<QVariant> argumentList;
@@ -93,6 +148,12 @@ public Q_SLOTS: // METHODS
93148 return asyncCallWithArgumentList(QStringLiteral("start"), argumentList);
94149 }
95150
151+ inline QDBusPendingReply<int> state()
152+ {
153+ QList<QVariant> argumentList;
154+ return asyncCallWithArgumentList(QStringLiteral("state"), argumentList);
155+ }
156+
96157 inline QDBusPendingReply<qulonglong> throttle()
97158 {
98159 QList<QVariant> argumentList;
@@ -106,10 +167,16 @@ public Q_SLOTS: // METHODS
106167 }
107168
108169Q_SIGNALS: // SIGNALS
170+ void authError(AuthErrorStruct error);
109171 void canceled(bool success);
110172 void error(const QString &error);
111173 void finished(const QString &path);
174+ void hashError(HashErrorStruct error);
175+ void httpError(HttpErrorStruct error);
176+ void networkError(NetworkErrorStruct error);
112177 void paused(bool success);
178+ void processError(ProcessErrorStruct error);
179+ void processing(const QString &path);
113180 void progress(qulonglong received, qulonglong total);
114181 void resumed(bool success);
115182 void started(bool success);
plugins/Ubuntu/DownloadDaemonListener/interface/metatypes.h+3−6
@@ -1,5 +1,5 @@
11/*
2− * Copyright 2013 2013 Canonical Ltd.
2+ * Copyright 2013-2016 Canonical Ltd.
33 *
44 * This library is free software; you can redistribute it and/or
55 * modify it under the terms of version 3 of the GNU Lesser General Public
@@ -19,10 +19,7 @@
1919#ifndef DOWNLOADER_LIB_APP_METATYPES_H
2020#define DOWNLOADER_LIB_APP_METATYPES_H
2121
22−#include <QMap>
23−
24−typedef QMap<QString, QString> StringMap;
25−
26−Q_DECLARE_METATYPE(StringMap)
22+// Just include the system file so we're always up to date.
23+#include <ubuntu/download_manager/metatypes.h>
2724
2825#endif // METATYPES_H
plugins/Ubuntu/Gestures/TouchGestureArea.h+2−2
@@ -76,7 +76,7 @@ public:
7676 return *this;
7777 }
7878
79− bool operator=(const GestureTouchPoint& rhs) const {
79+ bool operator==(const GestureTouchPoint& rhs) const {
8080 if (&rhs == this) return true;
8181 return m_id == rhs.m_id &&
8282 m_pressed == rhs.m_pressed &&
@@ -84,7 +84,7 @@ public:
8484 m_y == rhs.m_y &&
8585 m_dragging == rhs.m_dragging;
8686 }
87− bool operator!=(const GestureTouchPoint& rhs) const { return !operator=(rhs); }
87+ bool operator!=(const GestureTouchPoint& rhs) const { return !operator==(rhs); }
8888
8989 void setPos(const QPointF &pos);
9090
plugins/Utils/CMakeLists.txt+1
@@ -29,6 +29,7 @@ set(QMLPLUGIN_SRC
2929 inputeventgenerator.cpp
3030 deviceconfigparser.cpp
3131 globalfunctions.cpp
32+ URLDispatcher.cpp
3233 plugin.cpp
3334 )
3435
plugins/Utils/URLDispatcher.cppnew file+77
@@ -0,0 +1,77 @@
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 "URLDispatcher.h"
18+
19+#include <QDBusConnection>
20+
21+class URLDispatcherInterface : public QObject
22+{
23+ Q_OBJECT
24+ Q_CLASSINFO("D-Bus Interface", "com.canonical.URLDispatcher")
25+
26+public:
27+ explicit URLDispatcherInterface(URLDispatcher *parent);
28+
29+ Q_SCRIPTABLE void DispatchURL(const QString &url, const QString &package);
30+};
31+
32+URLDispatcherInterface::URLDispatcherInterface(URLDispatcher *parent)
33+ : QObject(parent)
34+{
35+}
36+
37+void URLDispatcherInterface::DispatchURL(const QString &url, const QString &package)
38+{
39+ Q_UNUSED(package);
40+ Q_EMIT static_cast<URLDispatcher *>(parent())->urlRequested(url);
41+}
42+
43+URLDispatcher::URLDispatcher(QObject *parent)
44+ : QObject(parent)
45+ , m_dispatcher(nullptr)
46+{
47+}
48+
49+bool URLDispatcher::active() const
50+{
51+ return m_dispatcher != nullptr;
52+}
53+
54+void URLDispatcher::setActive(bool value)
55+{
56+ if (value == active())
57+ return;
58+
59+ QDBusConnection connection = QDBusConnection::sessionBus();
60+
61+ if (value) {
62+ URLDispatcherInterface *dispatcher = new URLDispatcherInterface(this);
63+ connection.registerObject(QStringLiteral("/com/canonical/URLDispatcher"),
64+ dispatcher,
65+ QDBusConnection::ExportScriptableContents);
66+ connection.registerService(QStringLiteral("com.canonical.URLDispatcher"));
67+ m_dispatcher = dispatcher;
68+ } else {
69+ connection.unregisterService(QStringLiteral("com.canonical.URLDispatcher"));
70+ delete m_dispatcher;
71+ m_dispatcher = nullptr;
72+ }
73+
74+ Q_EMIT activeChanged();
75+}
76+
77+#include "URLDispatcher.moc"
plugins/Utils/URLDispatcher.hnew file+47
@@ -0,0 +1,47 @@
1+/*
2+ * Copyright (C) 2016 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef UNITY_URLDISPATCHER_H
18+#define UNITY_URLDISPATCHER_H
19+
20+#include <QObject>
21+#include <QString>
22+
23+// This class manages our url-dispatcher interception. We intercept
24+// url-dispatcher because rather than spawning the handler for the URL
25+// in our own session, we want to notify the user session to do it for us
26+// (and start an unlock in the process).
27+
28+class URLDispatcher : public QObject
29+{
30+ Q_OBJECT
31+ Q_PROPERTY(bool active READ active WRITE setActive NOTIFY activeChanged)
32+
33+public:
34+ explicit URLDispatcher(QObject *parent=0);
35+
36+ bool active() const;
37+ void setActive(bool active);
38+
39+Q_SIGNALS:
40+ void urlRequested(const QString &url);
41+ void activeChanged();
42+
43+private:
44+ QObject *m_dispatcher;
45+};
46+
47+#endif
plugins/Utils/plugin.cpp+2
@@ -38,6 +38,7 @@
3838#include "inputeventgenerator.h"
3939#include "deviceconfigparser.h"
4040#include "globalfunctions.h"
41+#include "URLDispatcher.h"
4142
4243static QObject *createWindowStateStorage(QQmlEngine *engine, QJSEngine *scriptEngine)
4344{
@@ -80,4 +81,5 @@ void UtilsPlugin::registerTypes(const char *uri)
8081 qmlRegisterType<InputEventGenerator>(uri, 0, 1, "InputEventGenerator");
8182 qmlRegisterType<DeviceConfigParser>(uri, 0, 1, "DeviceConfigParser");
8283 qmlRegisterSingletonType<GlobalFunctions>(uri, 0, 1, "Functions", createGlobalFunctions);
84+ qmlRegisterType<URLDispatcher>(uri, 0, 1, "URLDispatcher");
8385}
plugins/Utils/windowstatestorage.cpp+5−4
@@ -76,7 +76,7 @@ WindowStateStorage::WindowState WindowStateStorage::getState(const QString &wind
7676 return (WindowState)query.value(QStringLiteral("state")).toInt();
7777}
7878
79−void WindowStateStorage::saveGeometry(const QString &windowId, const QRect rect)
79+void WindowStateStorage::saveGeometry(const QString &windowId, const QRect &rect)
8080{
8181 const QString queryString = QStringLiteral("INSERT OR REPLACE INTO geometry (windowId, x, y, width, height) values ('%1', '%2', '%3', '%4', '%5');")
8282 .arg(sanitiseString(windowId))
@@ -92,7 +92,7 @@ void WindowStateStorage::saveStage(const QString &appId, int stage)
9292{
9393 const QString queryString = QStringLiteral("INSERT OR REPLACE INTO stage (appId, stage) values ('%1', '%2');")
9494 .arg(sanitiseString(appId))
95− .arg((int)stage);
95+ .arg(stage);
9696
9797 saveValue(queryString);
9898}
@@ -123,7 +123,7 @@ void WindowStateStorage::executeAsyncQuery(const QString &queryString)
123123 }
124124}
125125
126−QRect WindowStateStorage::getGeometry(const QString &windowId, const QRect defaultValue) const
126+QRect WindowStateStorage::getGeometry(const QString &windowId, const QRect &defaultValue) const
127127{
128128 QString queryString = QStringLiteral("SELECT * FROM geometry WHERE windowId = '%1';")
129129 .arg(sanitiseString(windowId));
@@ -133,7 +133,8 @@ QRect WindowStateStorage::getGeometry(const QString &windowId, const QRect defau
133133 if (!query.first()) {
134134 return defaultValue;
135135 }
136− return QRect(query.value(QStringLiteral("x")).toInt(), query.value(QStringLiteral("y")).toInt(), query.value(QStringLiteral("width")).toInt(), query.value(QStringLiteral("height")).toInt());
136+ return QRect(query.value(QStringLiteral("x")).toInt(), query.value(QStringLiteral("y")).toInt(),
137+ query.value(QStringLiteral("width")).toInt(), query.value(QStringLiteral("height")).toInt());
137138}
138139
139140void WindowStateStorage::initdb()
plugins/Utils/windowstatestorage.h+8−3
@@ -32,7 +32,12 @@ public:
3232 WindowStateMaximizedLeft = 1 << 4,
3333 WindowStateMaximizedRight = 1 << 5,
3434 WindowStateMaximizedHorizontally = 1 << 6,
35− WindowStateMaximizedVertically = 1 << 7
35+ WindowStateMaximizedVertically = 1 << 7,
36+ WindowStateMaximizedTopLeft = 1 << 8,
37+ WindowStateMaximizedTopRight = 1 << 9,
38+ WindowStateMaximizedBottomLeft = 1 << 10,
39+ WindowStateMaximizedBottomRight = 1 << 11,
40+ WindowStateRestored = 1 << 12
3641 };
3742 Q_DECLARE_FLAGS(WindowStates, WindowState)
3843#if (QT_VERSION >= QT_VERSION_CHECK(5, 5, 0))
@@ -45,8 +50,8 @@ public:
4550 Q_INVOKABLE void saveState(const QString &windowId, WindowState state);
4651 Q_INVOKABLE WindowState getState(const QString &windowId, WindowState defaultValue) const;
4752
48− Q_INVOKABLE void saveGeometry(const QString &windowId, const QRect rect);
49− Q_INVOKABLE QRect getGeometry(const QString &windowId, const QRect defaultValue) const;
53+ Q_INVOKABLE void saveGeometry(const QString &windowId, const QRect &rect);
54+ Q_INVOKABLE QRect getGeometry(const QString &windowId, const QRect &defaultValue) const;
5055
5156 Q_INVOKABLE void saveStage(const QString &appId, int stage);
5257 Q_INVOKABLE int getStage(const QString &appId, int defaultValue) const;
po/unity8.pot+85−131
@@ -8,7 +8,7 @@ msgid ""
88msgstr ""
99"Project-Id-Version: unity8\n"
1010"Report-Msgid-Bugs-To: \n"
11−"POT-Creation-Date: 2016-08-31 11:56+0000\n"
11+"POT-Creation-Date: 2016-09-09 14:56+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"
@@ -35,25 +35,6 @@ msgstr ""
3535msgid "Unpin shortcut"
3636msgstr ""
3737
38−#: qml/Components/DelayedLockscreen.qml:50
39−msgid "Device Locked"
40−msgstr ""
41−
42−#: qml/Components/DelayedLockscreen.qml:65
43−msgid "You have been locked out due to too many failed passphrase attempts."
44−msgstr ""
45−
46−#: qml/Components/DelayedLockscreen.qml:66
47−msgid "You have been locked out due to too many failed passcode attempts."
48−msgstr ""
49−
50−#: qml/Components/DelayedLockscreen.qml:75
51−#, qt-format
52−msgid "Please wait %1 minute and then try again…"
53−msgid_plural "Please wait %1 minutes and then try again…"
54−msgstr[0] ""
55−msgstr[1] ""
56−
5738#: qml/Components/Dialogs.qml:149
5839msgctxt "Title: Lock/Log out dialog"
5940msgid "Log out"
@@ -74,7 +55,8 @@ msgid "Log Out"
7455msgstr ""
7556
7657#: qml/Components/Dialogs.qml:166 qml/Components/Dialogs.qml:222
77−#: qml/Dash/DashPageHeader.qml:322 qml/Wizard/Pages/passcode-confirm.qml:32
58+#: qml/Dash/DashPageHeader.qml:324 qml/Greeter/NarrowView.qml:232
59+#: qml/Wizard/Pages/passcode-confirm.qml:32
7860#: qml/Wizard/Pages/passcode-set.qml:32
7961msgid "Cancel"
8062msgstr ""
@@ -277,15 +259,15 @@ msgstr ""
277259msgid "Closes the current window."
278260msgstr ""
279261
280−#: qml/Components/Lockscreen.qml:239
262+#: qml/Components/Lockscreen.qml:212 qml/Greeter/NarrowView.qml:252
281263msgid "Return to Call"
282264msgstr ""
283265
284−#: qml/Components/Lockscreen.qml:239
266+#: qml/Components/Lockscreen.qml:212
285267msgid "Emergency Call"
286268msgstr ""
287269
288−#: qml/Components/Lockscreen.qml:271
270+#: qml/Components/Lockscreen.qml:244
289271msgid "OK"
290272msgstr ""
291273
@@ -329,55 +311,55 @@ msgstr ""
329311msgid "Preview Share Item"
330312msgstr ""
331313
332−#: qml/Dash/DashPageHeader.qml:360
314+#: qml/Dash/DashPageHeader.qml:362
333315msgctxt "Button: Open the Ubuntu Store"
334316msgid "Store"
335317msgstr ""
336318
337−#: qml/Dash/DashPageHeader.qml:367
319+#: qml/Dash/DashPageHeader.qml:369
338320msgctxt "Button: Start a search in the current dash scope"
339321msgid "Search"
340322msgstr ""
341323
342−#: qml/Dash/DashPageHeader.qml:377
324+#: qml/Dash/DashPageHeader.qml:379
343325msgctxt "Button: Show the current dash scope settings"
344326msgid "Settings"
345327msgstr ""
346328
347−#: qml/Dash/DashPageHeader.qml:384
329+#: qml/Dash/DashPageHeader.qml:386
348330msgid "Remove from Favorites"
349331msgstr ""
350332
351−#: qml/Dash/DashPageHeader.qml:384
333+#: qml/Dash/DashPageHeader.qml:386
352334msgid "Add to Favorites"
353335msgstr ""
354336
355−#: qml/Dash/FiltersPopover.qml:60
337+#: qml/Dash/FiltersPopover.qml:61
356338msgid "Refine your results"
357339msgstr ""
358340
359−#: qml/Dash/FiltersPopover.qml:69
341+#: qml/Dash/FiltersPopover.qml:70
360342msgid "Reset"
361343msgstr ""
362344
363−#: qml/Dash/GenericScopeView.qml:585 qml/Dash/GenericScopeView.qml:780
345+#: qml/Dash/GenericScopeView.qml:586 qml/Dash/GenericScopeView.qml:782
364346msgid "Show less"
365347msgstr ""
366348
367−#: qml/Dash/GenericScopeView.qml:585
349+#: qml/Dash/GenericScopeView.qml:586
368350msgid "Show all"
369351msgstr ""
370352
371−#: qml/Dash/GenericScopeView.qml:642
353+#: qml/Dash/GenericScopeView.qml:643
372354msgctxt "Label: Hint for dash search line edit"
373355msgid "Search"
374356msgstr ""
375357
376−#: qml/Dash/PageHeaderExtraPanel.qml:56
358+#: qml/Dash/PageHeaderExtraPanel.qml:57
377359msgid "Recent Searches"
378360msgstr ""
379361
380−#: qml/Dash/PageHeaderExtraPanel.qml:67
362+#: qml/Dash/PageHeaderExtraPanel.qml:68
381363msgid "Clear All"
382364msgstr ""
383365
@@ -389,24 +371,24 @@ msgstr ""
389371msgid "Less..."
390372msgstr ""
391373
392−#: qml/Dash/Previews/PreviewCommentInput.qml:68
393−#: qml/Dash/Previews/PreviewRatingInput.qml:175
374+#: qml/Dash/Previews/PreviewCommentInput.qml:83
375+#: qml/Dash/Previews/PreviewRatingInput.qml:201
394376msgid "Send"
395377msgstr ""
396378
397−#: qml/Dash/Previews/PreviewRatingInput.qml:82
379+#: qml/Dash/Previews/PreviewRatingInput.qml:108
398380msgid "Rate this"
399381msgstr ""
400382
401−#: qml/Dash/Previews/PreviewRatingInput.qml:157
383+#: qml/Dash/Previews/PreviewRatingInput.qml:183
402384msgid "Add a review"
403385msgstr ""
404386
405−#: qml/Dash/PullToRefreshScopeStyle.qml:55
387+#: qml/Dash/PullToRefreshScopeStyle.qml:56
406388msgid "Pull to refresh…"
407389msgstr ""
408390
409−#: qml/Dash/PullToRefreshScopeStyle.qml:60
391+#: qml/Dash/PullToRefreshScopeStyle.qml:61
410392msgid "Release to refresh…"
411393msgstr ""
412394
@@ -414,15 +396,15 @@ msgstr ""
414396msgid "Enable location data"
415397msgstr ""
416398
417−#: qml/Dash/ScopesList.qml:55
399+#: qml/Dash/ScopesList.qml:57
418400msgid "Manage"
419401msgstr ""
420402
421−#: qml/Dash/ScopesList.qml:102
403+#: qml/Dash/ScopesList.qml:104
422404msgid "Home"
423405msgstr ""
424406
425−#: qml/Dash/ScopesList.qml:103
407+#: qml/Dash/ScopesList.qml:105
426408msgid "Also installed"
427409msgstr ""
428410
@@ -436,79 +418,51 @@ msgstr ""
436418msgid "Unlock"
437419msgstr ""
438420
439−#: qml/Greeter/Greeter.qml:561
440−msgid "Try again"
441−msgstr ""
442−
443−#: qml/Greeter/LoginList.qml:63
444−msgid "Passphrase"
445−msgstr ""
446−
447−#: qml/Greeter/LoginList.qml:64
448−msgid "Passcode"
449−msgstr ""
450−
451−#: qml/Greeter/LoginList.qml:87
452−msgid "Retry"
453−msgstr ""
454−
455−#: qml/Greeter/LoginList.qml:88
456−msgid "Log In"
457−msgstr ""
458−
459−#: qml/Greeter/NarrowView.qml:55
460−msgid "Sorry, incorrect passphrase."
461−msgstr ""
462−
463−#: qml/Greeter/NarrowView.qml:56
464−msgid "Sorry, incorrect passcode."
465−msgstr ""
466−
467−#: qml/Greeter/NarrowView.qml:57
468−msgid "This will be your last attempt."
421+#: qml/Greeter/DelayedLockscreen.qml:43
422+msgid "Device Locked"
469423msgstr ""
470424
471−#: qml/Greeter/NarrowView.qml:59
472−msgid ""
473−"If passphrase is entered incorrectly, your phone will conduct a factory "
474−"reset and all personal data will be deleted."
425+#: qml/Greeter/DelayedLockscreen.qml:58
426+msgid "You have been locked out due to too many failed passphrase attempts."
475427msgstr ""
476428
477−#: qml/Greeter/NarrowView.qml:60
478−msgid ""
479−"If passcode is entered incorrectly, your phone will conduct a factory reset "
480−"and all personal data will be deleted."
429+#: qml/Greeter/DelayedLockscreen.qml:59
430+msgid "You have been locked out due to too many failed passcode attempts."
481431msgstr ""
482432
483−#: qml/Greeter/NarrowView.qml:129
433+#: qml/Greeter/DelayedLockscreen.qml:68
484434#, qt-format
485−msgid "Enter %1"
435+msgid "Please wait %1 minute and then try again…"
436+msgid_plural "Please wait %1 minutes and then try again…"
437+msgstr[0] ""
438+msgstr[1] ""
439+
440+#: qml/Greeter/Greeter.qml:596
441+msgid "Try again"
486442msgstr ""
487443
488−#: qml/Greeter/NarrowView.qml:130
489−msgid "Enter passphrase"
444+#: qml/Greeter/LoginList.qml:70
445+msgid "Passphrase"
490446msgstr ""
491447
492−#: qml/Greeter/NarrowView.qml:131
493−msgid "Enter passcode"
448+#: qml/Greeter/LoginList.qml:71
449+msgid "Passcode"
494450msgstr ""
495451
496−#: qml/Greeter/NarrowView.qml:135
497−#, qt-format
498−msgid "Sorry, incorrect %1"
452+#: qml/Greeter/LoginList.qml:98
453+msgid "Retry"
499454msgstr ""
500455
501−#: qml/Greeter/NarrowView.qml:136
502−msgid "Sorry, incorrect passphrase"
456+#: qml/Greeter/LoginList.qml:99
457+msgid "Log In"
503458msgstr ""
504459
505−#: qml/Greeter/NarrowView.qml:137
506−msgctxt "passphrase"
507−msgid "Please re-enter"
460+#: qml/Greeter/NarrowView.qml:252
461+msgid "Emergency"
508462msgstr ""
509463
510−#: qml/Greeter/NarrowView.qml:138
511−msgid "Sorry, incorrect passcode"
464+#: qml/Greeter/SessionsList.qml:117
465+msgid "Select desktop environment"
512466msgstr ""
513467
514468#: qml/Notifications/NotificationMenuItemFactory.qml:124
@@ -627,51 +581,51 @@ msgctxt "Button: Go back one page in the Wizard"
627581msgid "Back"
628582msgstr ""
629583
630−#: qml/Wizard/Pages/10-welcome.qml:27
584+#: qml/Wizard/Pages/10-welcome.qml:28
631585msgid "Language"
632586msgstr ""
633587
634−#: qml/Wizard/Pages/10-welcome.qml:172 qml/Wizard/Pages/20-keyboard.qml:151
635−#: qml/Wizard/Pages/30-wifi.qml:206 qml/Wizard/Pages/40-location.qml:271
636−#: qml/Wizard/Pages/50-timezone.qml:270 qml/Wizard/Pages/60-account.qml:65
637−#: qml/Wizard/Pages/70-passwd-type.qml:144
588+#: qml/Wizard/Pages/10-welcome.qml:173 qml/Wizard/Pages/20-keyboard.qml:152
589+#: qml/Wizard/Pages/30-wifi.qml:207 qml/Wizard/Pages/40-location.qml:271
590+#: qml/Wizard/Pages/50-timezone.qml:271 qml/Wizard/Pages/60-account.qml:65
591+#: qml/Wizard/Pages/70-passwd-type.qml:145
638592#: qml/Wizard/Pages/75-report-check.qml:84
639−#: qml/Wizard/Pages/passcode-desktop.qml:141
640−#: qml/Wizard/Pages/password-set.qml:141
593+#: qml/Wizard/Pages/passcode-desktop.qml:142
594+#: qml/Wizard/Pages/password-set.qml:142
641595msgid "Next"
642596msgstr ""
643597
644−#: qml/Wizard/Pages/20-keyboard.qml:30
598+#: qml/Wizard/Pages/20-keyboard.qml:31
645599msgid "Select Keyboard"
646600msgstr ""
647601
648−#: qml/Wizard/Pages/20-keyboard.qml:70
602+#: qml/Wizard/Pages/20-keyboard.qml:71
649603msgid "Keyboard language"
650604msgstr ""
651605
652−#: qml/Wizard/Pages/20-keyboard.qml:92
606+#: qml/Wizard/Pages/20-keyboard.qml:93
653607msgid "Keyboard layout"
654608msgstr ""
655609
656−#: qml/Wizard/Pages/20-keyboard.qml:151 qml/Wizard/Pages/30-wifi.qml:206
610+#: qml/Wizard/Pages/20-keyboard.qml:152 qml/Wizard/Pages/30-wifi.qml:207
657611#: qml/Wizard/Pages/60-account.qml:65
658612#: qml/Wizard/Pages/77-system-update.qml:152 qml/Wizard/Pages/sim.qml:101
659613msgid "Skip"
660614msgstr ""
661615
662−#: qml/Wizard/Pages/30-wifi.qml:30
616+#: qml/Wizard/Pages/30-wifi.qml:31
663617msgid "Connect to Wi‑Fi"
664618msgstr ""
665619
666−#: qml/Wizard/Pages/30-wifi.qml:131
620+#: qml/Wizard/Pages/30-wifi.qml:132
667621msgid "Connected"
668622msgstr ""
669623
670−#: qml/Wizard/Pages/30-wifi.qml:164
624+#: qml/Wizard/Pages/30-wifi.qml:165
671625msgid "Available Wi-Fi networks"
672626msgstr ""
673627
674−#: qml/Wizard/Pages/30-wifi.qml:165
628+#: qml/Wizard/Pages/30-wifi.qml:166
675629msgid "No available Wi-Fi networks"
676630msgstr ""
677631
@@ -707,11 +661,11 @@ msgstr ""
707661msgid "You can change it later in System Settings."
708662msgstr ""
709663
710−#: qml/Wizard/Pages/50-timezone.qml:29
664+#: qml/Wizard/Pages/50-timezone.qml:30
711665msgid "Time Zone"
712666msgstr ""
713667
714−#: qml/Wizard/Pages/50-timezone.qml:181
668+#: qml/Wizard/Pages/50-timezone.qml:182
715669msgid "Enter your city"
716670msgstr ""
717671
@@ -723,21 +677,21 @@ msgstr ""
723677msgid "Preferred Name"
724678msgstr ""
725679
726−#: qml/Wizard/Pages/70-passwd-type.qml:39
680+#: qml/Wizard/Pages/70-passwd-type.qml:40
727681msgid "Lock Screen"
728682msgstr ""
729683
730−#: qml/Wizard/Pages/70-passwd-type.qml:101
684+#: qml/Wizard/Pages/70-passwd-type.qml:102
731685msgctxt "Label: Type of security method"
732686msgid "Create new password"
733687msgstr ""
734688
735−#: qml/Wizard/Pages/70-passwd-type.qml:103
689+#: qml/Wizard/Pages/70-passwd-type.qml:104
736690msgctxt "Label: Type of security method"
737691msgid "Create passcode (numbers only)"
738692msgstr ""
739693
740−#: qml/Wizard/Pages/70-passwd-type.qml:105
694+#: qml/Wizard/Pages/70-passwd-type.qml:106
741695msgctxt "Label: Type of security method"
742696msgid "No lock code"
743697msgstr ""
@@ -830,7 +784,7 @@ msgid "Service Terms"
830784msgstr ""
831785
832786#: qml/Wizard/Pages/passcode-confirm.qml:43
833−#: qml/Wizard/Pages/passcode-desktop.qml:96
787+#: qml/Wizard/Pages/passcode-desktop.qml:97
834788msgid "Confirm passcode"
835789msgstr ""
836790
@@ -843,28 +797,28 @@ msgctxt "Enter the passcode again"
843797msgid "Please re-enter."
844798msgstr ""
845799
846−#: qml/Wizard/Pages/passcode-desktop.qml:30
800+#: qml/Wizard/Pages/passcode-desktop.qml:31
847801msgid "Lock Screen Passcode"
848802msgstr ""
849803
850−#: qml/Wizard/Pages/passcode-desktop.qml:60
804+#: qml/Wizard/Pages/passcode-desktop.qml:61
851805msgid "Enter 4 numbers to setup your passcode"
852806msgstr ""
853807
854−#: qml/Wizard/Pages/passcode-desktop.qml:76
808+#: qml/Wizard/Pages/passcode-desktop.qml:77
855809#: qml/Wizard/Pages/passcode-set.qml:54
856810msgid "Choose passcode"
857811msgstr ""
858812
859−#: qml/Wizard/Pages/passcode-desktop.qml:126
813+#: qml/Wizard/Pages/passcode-desktop.qml:127
860814msgid "Passcode too short"
861815msgstr ""
862816
863−#: qml/Wizard/Pages/passcode-desktop.qml:128
817+#: qml/Wizard/Pages/passcode-desktop.qml:129
864818msgid "Passcodes match"
865819msgstr ""
866820
867−#: qml/Wizard/Pages/passcode-desktop.qml:130
821+#: qml/Wizard/Pages/passcode-desktop.qml:131
868822msgid "Passcodes do not match"
869823msgstr ""
870824
@@ -872,19 +826,19 @@ msgstr ""
872826msgid "Passcode must be 4 characters long"
873827msgstr ""
874828
875−#: qml/Wizard/Pages/password-set.qml:30
829+#: qml/Wizard/Pages/password-set.qml:31
876830msgid "Lock Screen Password"
877831msgstr ""
878832
879−#: qml/Wizard/Pages/password-set.qml:61
833+#: qml/Wizard/Pages/password-set.qml:62
880834msgid "Enter at least 8 characters"
881835msgstr ""
882836
883−#: qml/Wizard/Pages/password-set.qml:73
837+#: qml/Wizard/Pages/password-set.qml:74
884838msgid "Choose password"
885839msgstr ""
886840
887−#: qml/Wizard/Pages/password-set.qml:103
841+#: qml/Wizard/Pages/password-set.qml:104
888842msgid "Confirm password"
889843msgstr ""
890844
qml/Components/Carousel.qml+1−2
@@ -17,6 +17,7 @@
1717import QtQuick 2.4
1818import Ubuntu.Components 1.3
1919import "carousel.js" as CarouselJS
20+import "."
2021
2122/*! The Carousel component presents the items of a model in a carousel view. It's similar to a
2223 cover flow. But it stops at it's boundaries (therefore no PathView is used).
@@ -141,8 +142,6 @@ Item {
141142
142143 boundsBehavior: Flickable.DragOverBounds
143144 cacheBuffer: carousel.cacheBuffer
144− flickDeceleration: Math.max(1500 * Math.pow(realWidth / referenceWidth, 1.5), 1500) // 1500 is platform default
145− maximumFlickVelocity: Math.max(2500 * Math.pow(realWidth / referenceWidth, 1.5), 2500) // 2500 is platform default
146145 orientation: ListView.Horizontal
147146
148147 function getXFromContinuousIndex(index) {
qml/Components/Flickable.qmlnew file+24
@@ -0,0 +1,24 @@
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 as QtQuick
18+import Ubuntu.Components 1.3
19+import "flickableUtils.js" as FlickableUtilsJS
20+
21+QtQuick.Flickable {
22+ flickDeceleration: FlickableUtilsJS.getFlickDeceleration(units.gridUnit)
23+ maximumFlickVelocity: FlickableUtilsJS.getMaximumFlickVelocity(units.gridUnit)
24+}
qml/Components/FloatingFlickable.qml+1
@@ -17,6 +17,7 @@
1717import QtQuick 2.4
1818import Ubuntu.Components 1.3
1919import Ubuntu.Gestures 0.1
20+import "."
2021
2122/*
2223 A Flickable that can be put in front of the item to be flicked and
qml/Components/GridView.qmlnew file+24
@@ -0,0 +1,24 @@
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 as QtQuick
18+import Ubuntu.Components 1.3
19+import "flickableUtils.js" as FlickableUtilsJS
20+
21+QtQuick.GridView {
22+ flickDeceleration: FlickableUtilsJS.getFlickDeceleration(units.gridUnit)
23+ maximumFlickVelocity: FlickableUtilsJS.getMaximumFlickVelocity(units.gridUnit)
24+}
qml/Components/ListView.qmlnew file+24
@@ -0,0 +1,24 @@
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 as QtQuick
18+import Ubuntu.Components 1.3
19+import "flickableUtils.js" as FlickableUtilsJS
20+
21+QtQuick.ListView {
22+ flickDeceleration: FlickableUtilsJS.getFlickDeceleration(units.gridUnit)
23+ maximumFlickVelocity: FlickableUtilsJS.getMaximumFlickVelocity(units.gridUnit)
24+}
qml/Components/Lockscreen.qml+1−28
@@ -41,9 +41,6 @@ Showable {
4141 // The text to be displayed in case the login failed
4242 property string errorText: ""
4343
44− // If > 0, a forced delay is happening
45− property int delayMinutes: 0
46−
4744 // Set those to a value greater 0 to restrict the pin length.
4845 // If both are unset, the Lockscreen will show a confirm button and allow typing any length of pin before
4946 // confirming. If minPinLength is set to a value > 0, the confirm button will only become active when the
@@ -91,14 +88,6 @@ Showable {
9188 popup.z = Number.MAX_VALUE
9289 }
9390
94− function showText(text) {
95− // Programmatically enters the given text into the lockscreen
96− if (pinPadLoader.item) {
97− pinPadLoader.item.showText(text);
98− pinPadLoader.waiting = true;
99− }
100− }
101−
10291 Rectangle {
10392 // In case background fails to load
10493 id: backgroundBackup
@@ -107,17 +96,13 @@ Showable {
10796 visible: root.background.toString() !== ""
10897 }
10998
110− Image {
99+ Wallpaper {
111100 id: backgroundImage
112101 objectName: "lockscreenBackground"
113102 anchors {
114103 fill: parent
115104 }
116− // Limit how much memory we'll reserve for this image
117− sourceSize.height: height
118− sourceSize.width: width
119105 source: root.required ? root.background : ""
120− fillMode: Image.PreserveAspectCrop
121106 }
122107
123108 // This is to
@@ -141,8 +126,6 @@ Showable {
141126 source: {
142127 if (resetting || !root.required) {
143128 return ""
144− } else if (root.delayMinutes > 0) {
145− return "DelayedLockscreen.qml"
146129 } else if (root.alphaNumeric) {
147130 return "PassphraseLockscreen.qml"
148131 } else {
@@ -197,16 +180,6 @@ Showable {
197180 property: "entryEnabled"
198181 value: !pinPadLoader.waiting
199182 }
200− Binding {
201− target: pinPadLoader.item
202− property: "alphaNumeric"
203− value: root.alphaNumeric
204− }
205− Binding {
206− target: pinPadLoader.item
207− property: "delayMinutes"
208− value: root.delayMinutes
209− }
210183 Binding {
211184 target: pinPadLoader.item
212185 property: "showCancelButton"
qml/Components/PanelState/PanelState.qml−1
@@ -30,5 +30,4 @@ QtObject {
3030 signal closeClicked()
3131 signal minimizeClicked()
3232 signal restoreClicked()
33− signal focusMaximizedApp()
3433}
qml/Components/PassphraseLockscreen.qml−4
@@ -43,10 +43,6 @@ FocusScope {
4343 }
4444 }
4545
46− function showText(text) {
47− pinentryField.text = text;
48− }
49−
5046 Column {
5147 id: shakeContainer
5248 anchors.horizontalCenter: parent.horizontalCenter
qml/Components/PinLockscreen.qml−4
@@ -46,10 +46,6 @@ FocusScope {
4646 }
4747 }
4848
49− function showText(text) {
50− pinentryField.text = text;
51− }
52−
5349 Keys.onPressed: {
5450 if (pinentryField.text.length == root.maxPinLength)
5551 return;
qml/Components/ResponsiveGridView.qml+1
@@ -16,6 +16,7 @@
1616
1717import QtQuick 2.4
1818import Ubuntu.Components 1.3
19+import "."
1920
2021/*
2122 Essentially a GridView
qml/Components/VolumeControl.qml+2−3
@@ -17,6 +17,7 @@
1717import QtQuick 2.4
1818import QMenuModel 0.1 as QMenuModel
1919import GlobalShortcut 1.0
20+import Unity.Platform 1.0
2021
2122Item {
2223 id: root
@@ -27,8 +28,6 @@ Item {
2728 readonly property int stepUp: 1
2829 readonly property int stepDown: -1
2930
30− property var indicators // passed from Shell.qml
31−
3231 GlobalShortcut {
3332 id: muteShortcut
3433 shortcut: Qt.Key_VolumeMute
@@ -42,7 +41,7 @@ Item {
4241 objectPath: "/com/canonical/indicator/sound"
4342
4443 property variant actionObject: action("volume")
45− property variant muteActionObject: indicators.indicatorsModel.profile === "desktop" ? action("mute") : action("silent-mode")
44+ property variant muteActionObject: Platform.isPC ? action("mute") : action("silent-mode")
4645 }
4746
4847 function volumeUp() {
qml/Components/Wallpaper.qmlnew file+44
@@ -0,0 +1,44 @@
1+/*
2+ * Copyright (C) 2013-2016 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+import QtQuick.Window 2.2
19+import Ubuntu.Components 1.3
20+
21+Item {
22+ id: root
23+ property url source
24+
25+ CrossFadeImage {
26+ anchors.fill: parent
27+ fillMode: Image.PreserveAspectCrop
28+
29+ // Limit how much memory we reserve and avoid reloading when item size
30+ // changes or is rotated by specifying sourceSize.
31+ //
32+ // FIXME: If the source image has a portrait aspect ratio, we should swap
33+ // sourceSize.width and sourceSize.height to prevent blurriness from double
34+ // scaling. We could easily do that with a tiny image loader to check
35+ // the aspect ratio first, but when we change sourceSize, we lose all
36+ // the benefits of CrossFadeImage. So we need to fix that component
37+ // first to gracefully handle sourceSize changes (LP: #1599203).
38+ readonly property int maxSize: Math.max(Screen.width, Screen.height)
39+ sourceSize.width: 0
40+ sourceSize.height: maxSize
41+
42+ source: root.source
43+ }
44+}
qml/Components/WallpaperResolver.qml+24−38
@@ -15,49 +15,35 @@
1515 */
1616
1717import QtQuick 2.4
18−import AccountsService 0.1
19−import GSettings 1.0
2018import Ubuntu.Components 1.3
2119
22−/*
23− Defines the background URL based on several factors, such as:
24− - default, fallback, background
25− - Background set in AccountSettings, if any
26− - Background set in GSettings, if any
27− */
28−QtObject {
29− // Users should set their UI width here.
30− property real width
31−
32− property url defaultBackground: Qt.resolvedUrl(width >= units.gu(60) ? "../graphics/tablet_background.jpg"
33− : "../graphics/phone_background.jpg")
20+Item {
21+ id: root
3422
35− // That's the property users of this component are going to consume.
36− readonly property url background: asImageTester.status == Image.Ready ? asImageTester.source
37− : gsImageTester.status == Image.Ready ? gsImageTester.source : defaultBackground
38−
39− // This is a dummy image to detect if the custom AS set wallpaper loads successfully.
40− property var _asImageTester: Image {
41− id: asImageTester
42− source: AccountsService.backgroundFile != undefined && AccountsService.backgroundFile.length > 0 ? AccountsService.backgroundFile : ""
43− height: 0
44− width: 0
45− sourceSize.height: 0
46− sourceSize.width: 0
47− }
23+ // Provide a list of wallpapers to resolve here, preferred ones first
24+ property var candidates: []
4825
49− // This is a dummy image to detect if the custom GSettings set wallpaper loads successfully.
50− property var _gsImageTester: Image {
51− id: gsImageTester
52− source: backgroundSettings.pictureUri && backgroundSettings.pictureUri.length > 0 ? backgroundSettings.pictureUri : ""
53− height: 0
54− width: 0
55− sourceSize.height: 0
56− sourceSize.width: 0
26+ readonly property url background: {
27+ for (var i = 0; i < repeater.count; i++) {
28+ if (repeater.itemAt(i).status === Image.Ready)
29+ return candidates[i];
30+ }
31+ if (i > 0) {
32+ return candidates[i - 1]; // last item is last resort
33+ } else {
34+ return "";
35+ }
5736 }
5837
59− property var _gsettings: GSettings {
60− id: backgroundSettings
61− schema.id: "org.gnome.desktop.background"
38+ Repeater {
39+ id: repeater
40+ model: root.candidates
41+ delegate: Image {
42+ source: modelData
43+ height: 0
44+ width: 0
45+ sourceSize.height: 0
46+ sourceSize.width: 0
47+ }
6248 }
6349}
qml/Components/WindowControlButtons.qml+7−5
@@ -19,13 +19,12 @@ import Ubuntu.Components 1.3
1919
2020Row {
2121 id: root
22− spacing: overlayShown ? units.gu(2) : units.gu(1)
22+ spacing: overlayShown ? units.gu(2) : windowIsMaximized ? 0 : units.gu(1)
2323 Behavior on spacing {
2424 UbuntuNumberAnimation {}
2525 }
2626
2727 // to be set from outside
28− property Item target
2928 property bool active: false
3029 property bool windowIsMaximized: false
3130 property bool closeButtonShown: true
@@ -49,13 +48,14 @@ Row {
4948
5049 Rectangle {
5150 anchors.fill: parent
51+ anchors.margins: windowIsMaximized ? units.dp(3) : 0
5252 radius: height / 2
5353 color: theme.palette.normal.negative
5454 visible: parent.containsMouse && !overlayShown
5555 }
5656 Icon {
5757 anchors.fill: parent
58− anchors.margins: units.dp(3)
58+ anchors.margins: windowIsMaximized ? units.dp(6) : units.dp(3)
5959 source: "graphics/window-close.svg"
6060 color: root.active ? "white" : UbuntuColors.slate
6161 }
@@ -71,13 +71,14 @@ Row {
7171
7272 Rectangle {
7373 anchors.fill: parent
74+ anchors.margins: windowIsMaximized ? units.dp(3) : 0
7475 radius: height / 2
7576 color: root.active ? UbuntuColors.graphite : UbuntuColors.ash
7677 visible: parent.containsMouse && !overlayShown
7778 }
7879 Icon {
7980 anchors.fill: parent
80− anchors.margins: units.dp(3)
81+ anchors.margins: windowIsMaximized ? units.dp(6) : units.dp(3)
8182 source: "graphics/window-minimize.svg"
8283 color: root.active ? "white" : UbuntuColors.slate
8384 }
@@ -103,13 +104,14 @@ Row {
103104
104105 Rectangle {
105106 anchors.fill: parent
107+ anchors.margins: windowIsMaximized ? units.dp(3) : 0
106108 radius: height / 2
107109 color: root.active ? UbuntuColors.graphite : UbuntuColors.ash
108110 visible: parent.containsMouse && !overlayShown
109111 }
110112 Icon {
111113 anchors.fill: parent
112− anchors.margins: units.dp(3)
114+ anchors.margins: windowIsMaximized ? units.dp(6) : units.dp(3)
113115 source: root.windowIsMaximized ? "graphics/window-window.svg" : "graphics/window-maximize.svg"
114116 color: root.active ? "white" : UbuntuColors.slate
115117 }
qml/Components/ZoomableImage.qml+1−1
@@ -16,7 +16,7 @@
1616
1717import QtQuick 2.4
1818import Ubuntu.Components 1.3
19−import "../Components"
19+import "."
2020
2121/*! \brief Zoomable for image.
2222
qml/Components/flickableUtils.jsnew file+28
@@ -0,0 +1,28 @@
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+.pragma library
18+
19+/* TODO the "8" is is DEFAULT_GRID_UNIT_PX, we use it to normalize the values with the current grid unit size.
20+ Add a way to fetch this default grid unit px value from UITK. */
21+
22+function getFlickDeceleration(gridUnit) {
23+ return 1500 * gridUnit / 8;
24+}
25+
26+function getMaximumFlickVelocity(gridUnit) {
27+ return 2500 * gridUnit / 8;
28+}
qml/Dash/CardTool.qml+14−2
@@ -71,7 +71,17 @@ Item {
7171 // Not readonly because gets overwritten from GenericScopeView in some cases
7272 property string artShapeStyle: categoryLayout === "carousel" ? "shadow" : "inset"
7373
74− property var cardComponent: CardCreatorCache.getCardComponent(cardTool.template, cardTool.components, false, cardTool.artShapeStyle, cardTool.categoryLayout);
74+ // FIXME ? This seems like it should not be needed, but on Qt 5.4 + phone
75+ // we are doing unneeded calls to getCardComponent with artShapeStyle and categoryLayout being empty
76+ // Check when we move to newer Qts on the phone if we still need this
77+ readonly property bool askForCardComponent: cardTool.template !== undefined &&
78+ cardTool.components !== undefined &&
79+ cardTool.artShapeStyle !== "" &&
80+ cardTool.categoryLayout !== ""
81+
82+ property var cardComponent: askForCardComponent
83+ ? CardCreatorCache.getCardComponent(cardTool.template, cardTool.components, false, cardTool.artShapeStyle, cardTool.categoryLayout)
84+ : undefined
7585
7686 // FIXME: Saviq
7787 // Only way for the card below to actually be laid out completely.
@@ -213,7 +223,9 @@ Item {
213223 "attributes": attributesModel.model,
214224 "socialActions": socialActionsModel.model
215225 }
216− sourceComponent: CardCreatorCache.getCardComponent(cardTool.template, cardTool.components, true, cardTool.artShapeStyle, cardTool.categoryLayout);
226+ sourceComponent: askForCardComponent
227+ ? CardCreatorCache.getCardComponent(cardTool.template, cardTool.components, true, cardTool.artShapeStyle, cardTool.categoryLayout)
228+ : undefined
217229 onLoaded: {
218230 item.objectName = "cardToolCard";
219231 item.width = Qt.binding(function() { return cardTool.cardWidth !== -1 ? cardTool.cardWidth : item.implicitWidth; });
qml/Dash/DashBackground.qml+2−2
@@ -16,10 +16,10 @@
1616
1717import QtQuick 2.4
1818import QtQuick.Window 2.2
19−import Ubuntu.Thumbnailer 0.1
19+import ImageCache 0.1
2020
2121Image {
22− source: "image://thumbnailer/" + Qt.resolvedUrl("graphics/paper.png")
22+ source: "image://unity8imagecache/" + Qt.resolvedUrl("graphics/paper.png")
2323 fillMode: Image.PreserveAspectCrop
2424 horizontalAlignment: Image.AlignRight
2525 verticalAlignment: Image.AlignTop
qml/Dash/DashContent.qml−2
@@ -112,8 +112,6 @@ Item {
112112 anchors.fill: parent
113113 orientation: ListView.Horizontal
114114 boundsBehavior: Flickable.DragAndOvershootBounds
115− flickDeceleration: units.gu(625)
116− maximumFlickVelocity: width * 5
117115 snapMode: ListView.SnapOneItem
118116 highlightMoveDuration: 250
119117 highlightRangeMode: ListView.StrictlyEnforceRange
qml/Dash/DashNavigation.qml+1
@@ -17,6 +17,7 @@
1717import QtQuick 2.4
1818import Ubuntu.Components 1.3
1919import Dash 0.1
20+import "../Components"
2021
2122Item {
2223 id: root
qml/Dash/DashPageHeader.qml+2
@@ -18,6 +18,7 @@ import QtQuick 2.4
1818import Ubuntu.Components 1.3
1919import Ubuntu.Components.Popups 1.3
2020import Ubuntu.Components.ListItems 1.3
21+import Utils 0.1
2122import "../Components"
2223
2324Item {
@@ -25,6 +26,7 @@ Item {
2526 objectName: "pageHeader"
2627 implicitHeight: headerContainer.height + signatureLineHeight
2728 readonly property real signatureLineHeight: showSignatureLine ? units.gu(2) : 0
29+ readonly property real headerDividerLuminance: Style.luminance(bottomBorder.color)
2830
2931 property int activeFiltersCount: 0
3032 property bool scopeHasFilters: false
qml/Dash/FiltersPopover.qml+1
@@ -19,6 +19,7 @@ import Ubuntu.Components 1.3
1919import Ubuntu.Components.Popups 1.3
2020import Ubuntu.Components.ListItems 1.3 as ListItems
2121import "Filters" as Filters
22+import "../Components"
2223
2324Popover {
2425 id: root
qml/Dash/GenericScopeView.qml+16
@@ -23,6 +23,7 @@ import Unity 0.2
2323import Dash 0.1
2424import "../Components"
2525import "../Components/ListItems" as ListItems
26+import "Previews/PreviewSingleton"
2627
2728FocusScope {
2829 id: scopeView
@@ -722,6 +723,7 @@ FocusScope {
722723 target: categoryView
723724
724725 readonly property real contentY: categoryView.contentY - categoryView.originY
726+ readonly property real headerDividerLuminance: categoryView.pageHeader.headerDividerLuminance
725727 y: -contentY - units.gu(5)
726728
727729 onRefresh: {
@@ -787,6 +789,17 @@ FocusScope {
787789 color: scopeStyle ? scopeStyle.foreground : theme.palette.normal.baseText
788790 }
789791
792+ Image {
793+ anchors {
794+ bottom: parent.bottom
795+ left: parent.left
796+ right: parent.right
797+ }
798+ fillMode: Image.Stretch
799+ source: "graphics/dash_divider_top_darkgrad.png"
800+ z: -1
801+ }
802+
790803 Connections {
791804 target: floatingSeeLess.companionTo ? categoryView : null
792805 onContentYChanged: floatingSeeLess.updateVisibility();
@@ -824,6 +837,9 @@ FocusScope {
824837 }
825838
826839 function closeSubPage() {
840+ if (subPage == "preview") {
841+ PreviewSingleton.widgetExtraData = new Object();
842+ }
827843 open = false;
828844 }
829845
qml/Dash/PageHeaderExtraPanel.qml+2
@@ -18,6 +18,7 @@ import QtQuick 2.4
1818import Ubuntu.Components 1.3
1919import Ubuntu.Components.ListItems 1.3 as ListItems
2020import "Filters" as Filters
21+import "../Components"
2122
2223Item {
2324 id: root
@@ -123,6 +124,7 @@ Item {
123124 }
124125 text: query
125126 color: "#888888"
127+ elide: Text.ElideRight
126128 }
127129
128130 divider.visible: index != recentSearchesRepeater.count - 1 || (scope && scope.hasNavigation) || primaryFilter.active
qml/Dash/Previews/PreviewCommentInput.qml+15
@@ -17,6 +17,7 @@
1717import QtQuick 2.4
1818import Ubuntu.Components 1.3
1919import "../../Components"
20+import "PreviewSingleton"
2021
2122/*! \brief Preview widget for commenting.
2223
@@ -35,6 +36,20 @@ PreviewWidget {
3536 triggered(root.widgetId, "commented", data);
3637 }
3738
39+ property alias commentText: commentTextArea.text
40+
41+ onCommentTextChanged: storeCommentState()
42+ onWidgetIdChanged: restoreCommentState()
43+
44+ function storeCommentState() {
45+ PreviewSingleton.widgetExtraData[widgetId] = commentText;
46+ }
47+
48+ function restoreCommentState() {
49+ if (!PreviewSingleton.widgetExtraData[widgetId]) return;
50+ if (PreviewSingleton.widgetExtraData[widgetId] != "") commentText = PreviewSingleton.widgetExtraData[widgetId];
51+ }
52+
3853 TextArea {
3954 id: commentTextArea
4055 objectName: "commentTextArea"
qml/Dash/Previews/PreviewInlineVideo.qml+47
@@ -18,6 +18,7 @@ import QtQuick 2.4
1818import QtMultimedia 5.0
1919import Ubuntu.Components 1.3
2020import Ubuntu.Thumbnailer 0.1
21+import "PreviewSingleton"
2122import "../../Components"
2223import "../../Components/MediaServices"
2324
@@ -36,9 +37,35 @@ PreviewWidget {
3637 orientationLock: services.fullscreen
3738
3839 property alias rootItem: services.rootItem
40+ readonly property string widgetExtraDataKey: services.source.toString()
41+
42+ onWidgetExtraDataKeyChanged: {
43+ root.restorePlaybackState();
44+ }
45+
46+ function seek() {
47+ services.mediaPlayer.seek(services.initialPosition);
48+ services.initialPosition = -1;
49+ }
50+
51+ function storePlaybackState() {
52+ if ((services.mediaPlayer.duration - services.mediaPlayer.position) < 1000) {
53+ // we're at the end of the video
54+ PreviewSingleton.widgetExtraData[widgetExtraDataKey] = 0;
55+ } else {
56+ PreviewSingleton.widgetExtraData[widgetExtraDataKey] = services.mediaPlayer.position;
57+ }
58+ }
59+
60+ function restorePlaybackState() {
61+ if (PreviewSingleton.widgetExtraData[widgetExtraDataKey] > 0) {
62+ services.initialPosition = PreviewSingleton.widgetExtraData[widgetExtraDataKey];
63+ }
64+ }
3965
4066 MediaServices {
4167 id: services
68+ objectName: "services"
4269 width: parent.width
4370
4471 actions: sharingAction
@@ -47,8 +74,28 @@ PreviewWidget {
4774 fullscreen: false
4875 maximumEmbeddedHeight: rootItem.height / 2
4976
77+ property int initialPosition: -1
78+
79+ readonly property var mediaPlayer: footer.mediaPlayer
80+ readonly property url source: mediaPlayer.source
81+ readonly property int position: mediaPlayer.position
82+
5083 onClose: fullscreen = false
5184
85+ onPositionChanged: {
86+ if (mediaPlayer.playbackState === MediaPlayer.StoppedState) return;
87+ root.storePlaybackState();
88+ }
89+
90+ Connections {
91+ target: services.mediaPlayer
92+ ignoreUnknownSignals: true
93+ onPlaying: {
94+ // at the first playback of the file, do a seek()
95+ if (services.initialPosition > 0) root.seek();
96+ }
97+ }
98+
5299 Action {
53100 id: sharingAction
54101 iconName: "share"
qml/Dash/Previews/PreviewProgress.qml+5−1
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2014 Canonical, Ltd.
2+ * Copyright (C) 2014-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
@@ -59,6 +59,10 @@ PreviewWidget {
5959 }
6060 }
6161
62+ onProcessing: {
63+ progressBar.indeterminate = true;
64+ }
65+
6266 onFinished: {
6367 root.triggered(widgetId, "finished", widgetData)
6468 }
qml/Dash/Previews/PreviewRatingInput.qml+27−1
@@ -17,6 +17,7 @@
1717import QtQuick 2.4
1818import Ubuntu.Components 1.3
1919import "../../Components"
20+import "PreviewSingleton"
2021
2122/*! \brief Preview widget for rating.
2223
@@ -53,6 +54,31 @@ PreviewWidget {
5354 property alias ratingValue: rating.value
5455 property alias reviewText: reviewTextArea.text
5556
57+ onRatingValueChanged: storeRatingState()
58+ onReviewTextChanged: storeTextState()
59+ onWidgetIdChanged: restoreReviewState()
60+
61+ function initializeWidgetExtraData() {
62+ if (typeof(PreviewSingleton.widgetExtraData[widgetId]) == "undefined") PreviewSingleton.widgetExtraData[widgetId] = [];
63+ }
64+
65+ function storeRatingState() {
66+ initializeWidgetExtraData();
67+ PreviewSingleton.widgetExtraData[widgetId][0] = ratingValue;
68+ }
69+
70+ function storeTextState() {
71+ initializeWidgetExtraData();
72+ PreviewSingleton.widgetExtraData[widgetId][1] = reviewText;
73+ }
74+
75+ function restoreReviewState() {
76+ if (!PreviewSingleton.widgetExtraData[widgetId]) return;
77+ if (PreviewSingleton.widgetExtraData[widgetId][0] > 0) ratingValue = PreviewSingleton.widgetExtraData[widgetId][0];
78+ if (typeof(PreviewSingleton.widgetExtraData[widgetId][1]) != "undefined" &&
79+ PreviewSingleton.widgetExtraData[widgetId][1] != "") reviewText = PreviewSingleton.widgetExtraData[widgetId][1];
80+ }
81+
5682 function submit() {
5783 // checks rating-input requirements
5884 if (((widgetData["required"] === "both" ||
@@ -89,7 +115,7 @@ PreviewWidget {
89115 size: 5
90116 height: units.gu(4)
91117 onValueChanged: {
92− if (widgetData["visible"] === "rating") root.submit();
118+ if (widgetData && widgetData["visible"] === "rating") root.submit();
93119 }
94120
95121 property var urlIconEmpty: widgetData["rating-icon-empty"] || "image://theme/non-starred"
qml/Dash/Previews/PreviewSingleton/PreviewSingleton.qmlnew file+22
@@ -0,0 +1,22 @@
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+pragma Singleton
18+import QtQuick 2.4
19+
20+QtObject {
21+ property var widgetExtraData: new Object();
22+}
qml/Dash/Previews/PreviewSingleton/qmldirnew file+1
@@ -0,0 +1 @@
1+singleton PreviewSingleton 1.0 PreviewSingleton.qml
qml/Dash/PullToRefreshScopeStyle.qml+1
@@ -48,6 +48,7 @@ PullToRefreshStyle {
4848 anchors.horizontalCenter: parent.horizontalCenter
4949 horizontalAlignment: Text.AlignHCenter
5050 verticalAlignment: Text.AlignVCenter
51+ color: styledItem.headerDividerLuminance < 0.3 ? UbuntuColors.porcelain : UbuntuColors.jet
5152 states: [
5253 State {
5354 name: "pulling"
qml/Dash/ScopeListView.qml+4−2
@@ -15,11 +15,13 @@
1515 */
1616
1717import QtQuick 2.4
18+import Ubuntu.Components 1.3
1819import Dash 0.1
20+import "../Components/flickableUtils.js" as FlickableUtilsJS
1921
2022ListViewWithPageHeader {
21− maximumFlickVelocity: height * 10
22− flickDeceleration: height * 2
23+ flickDeceleration: FlickableUtilsJS.getFlickDeceleration(units.gridUnit)
24+ maximumFlickVelocity: FlickableUtilsJS.getMaximumFlickVelocity(units.gridUnit)
2325 // 1073741823 is s^30 -1. A quite big number so that you have "infinite" cache, but not so
2426 // big so that if you add if with itself you're outside the 2^31 int range
2527 cacheBuffer: 1073741823
qml/Dash/ScopesList.qml+2
@@ -15,7 +15,9 @@
1515 */
1616
1717import QtQuick 2.4
18+import Ubuntu.Components 1.3
1819import Dash 0.1
20+import "../Components"
1921
2022Item {
2123 id: root
qml/Dash/ScopesListCategory.qml+1
@@ -17,6 +17,7 @@
1717import QtQuick 2.4
1818import Ubuntu.Components 1.3
1919import Dash 0.1
20+import "../Components"
2021import "../Components/ListItems" as ListItems
2122
2223Item {
qml/Greeter/Circle.qmlnew file+57
@@ -0,0 +1,57 @@
1+/*
2+ * Copyright (C) 2013,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+
19+Canvas {
20+ id: root
21+ property color color
22+ property real circleScale
23+ property var centerCircle
24+
25+ onColorChanged: requestPaint()
26+ onCircleScaleChanged: requestPaint()
27+ onCenterCircleChanged: requestPaint()
28+
29+ onPaint: {
30+ if (circleScale <= 0) {
31+ return;
32+ }
33+
34+ var ctx = getContext("2d");
35+
36+ // draw big circle
37+ ctx.save();
38+ ctx.fillStyle = color;
39+ ctx.beginPath();
40+ ctx.arc(width / 2, height / 2, circleScale * width / 2, 0, 2 * Math.PI, false);
41+ ctx.fill();
42+ ctx.restore();
43+
44+ // chop out inner infographics circle
45+ if (centerCircle) {
46+ var circleMiddle = mapFromItem(centerCircle,
47+ centerCircle.width / 2,
48+ centerCircle.height / 2);
49+ ctx.save();
50+ ctx.globalCompositeOperation = "destination-out";
51+ ctx.beginPath();
52+ ctx.arc(circleMiddle.x, circleMiddle.y, centerCircle.width / 2, 0, 2 * Math.PI, false);
53+ ctx.fill();
54+ ctx.restore();
55+ }
56+ }
57+}
qml/Greeter/Clock.qml−2
@@ -73,7 +73,6 @@ Item {
7373 anchors.horizontalCenter: parent.horizontalCenter
7474 font.pixelSize: units.gu(7.5)
7575 color: "white"
76− opacity: 0.5
7776 text: Qt.formatTime(clock.currentDate)
7877 font.weight: Font.Light
7978 }
@@ -85,7 +84,6 @@ Item {
8584 anchors.horizontalCenter: parent.horizontalCenter
8685 fontSize: "medium"
8786 color: "white"
88− opacity: 0.5
8987 text: Qt.formatDate(clock.currentDate, Qt.DefaultLocaleLongDate)
9088 font.weight: Font.Light
9189 }
qml/Greeter/CoverPage.qml+6−6
@@ -25,13 +25,14 @@ Showable {
2525 property real dragHandleLeftMargin
2626 property real launcherOffset
2727 property alias background: greeterBackground.source
28+ property alias hasCustomBackground: backgroundShade.visible
2829 property real backgroundTopMargin
2930 property var infographicModel
3031 property bool draggable: true
3132
3233 property alias infographics: infographics
3334
34− readonly property real showProgress: MathUtils.clamp((width - Math.abs(x)) / width, 0, 1)
35+ readonly property real showProgress: MathUtils.clamp((width - Math.abs(x + launcherOffset)) / width, 0, 1)
3536
3637 signal tease()
3738 signal clicked()
@@ -82,24 +83,23 @@ Showable {
8283 color: "black"
8384 }
8485
85− CrossFadeImage {
86+ Wallpaper {
8687 id: greeterBackground
8788 objectName: "greeterBackground"
8889 anchors {
8990 fill: parent
9091 topMargin: root.backgroundTopMargin
9192 }
92− fillMode: Image.PreserveAspectCrop
93− // Limit how much memory we'll reserve for this image
94− sourceSize.height: height
95− sourceSize.width: width
9693 }
9794
9895 // Darkens wallpaper so that we can read text on it and see infographic
9996 Rectangle {
97+ id: backgroundShade
98+ objectName: "backgroundShade"
10099 anchors.fill: parent
101100 color: "black"
102101 opacity: 0.4
102+ visible: false
103103 }
104104
105105 Infographics {
qml/Components/DelayedLockscreen.qml → qml/Greeter/DelayedLockscreen.qmlrenamed+6−15
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013,2014 Canonical, Ltd.
2+ * Copyright (C) 2013-2016 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -16,7 +16,6 @@
1616
1717import QtQuick 2.4
1818import Ubuntu.Components 1.3
19−import "../Components"
2019
2120Item {
2221 id: root
@@ -24,12 +23,6 @@ Item {
2423 property int delayMinutes
2524 property bool alphaNumeric
2625
27− signal entered(string passphrase) // unused
28− signal cancel() // unused
29−
30− function clear(playAnimation) {}
31− function showText(text) {}
32−
3326 Column {
3427 anchors.left: parent.left
3528 anchors.right: parent.right
@@ -46,7 +39,7 @@ Item {
4639 horizontalAlignment: Text.AlignHCenter
4740 wrapMode: Text.Wrap
4841 fontSize: "x-large"
49− color: "#f3f3e7"
42+ color: "white"
5043 text: i18n.tr("Device Locked")
5144 }
5245
@@ -60,7 +53,7 @@ Item {
6053 anchors.right: parent.right
6154 horizontalAlignment: Text.AlignHCenter
6255 wrapMode: Text.Wrap
63− color: "#f3f3e7"
56+ color: "white"
6457 text: alphaNumeric ?
6558 i18n.tr("You have been locked out due to too many failed passphrase attempts.") :
6659 i18n.tr("You have been locked out due to too many failed passcode attempts.")
@@ -71,7 +64,7 @@ Item {
7164 anchors.right: parent.right
7265 horizontalAlignment: Text.AlignHCenter
7366 wrapMode: Text.Wrap
74− color: "#f3f3e7"
67+ color: "white"
7568 text: i18n.tr("Please wait %1 minute and then try again…",
7669 "Please wait %1 minutes and then try again…",
7770 root.delayMinutes).arg(root.delayMinutes)
@@ -83,10 +76,8 @@ Item {
8376 }
8477
8578 Icon {
86− // It would be nice to use a less network-specific name,
87− // but this is the only lock icon we have.
88− name: "network-secure"
89− color: "#f3f3e7"
79+ name: "lock"
80+ color: "white"
9081 height: units.gu(4)
9182 width: units.gu(4)
9283 anchors.horizontalCenter: parent.horizontalCenter
qml/Greeter/FullLightDMImpl.qml+2
@@ -22,6 +22,8 @@ Item{
2222
2323 property var greeter: LightDM.Greeter
2424 property var infographic: LightDM.Infographic
25+ property var sessions: LightDM.Sessions
26+ property var sessionRoles: LightDM.SessionRoles
2527 property var users: LightDM.Users
2628 property var userRoles: LightDM.UserRoles
2729
qml/Greeter/Greeter.qml+60−24
@@ -34,6 +34,7 @@ Showable {
3434 property real dragHandleLeftMargin: 0
3535
3636 property url background
37+ property bool hasCustomBackground
3738
3839 // How far to offset the top greeter layer during a launcher left-drag
3940 property real launcherOffset
@@ -72,9 +73,12 @@ Showable {
7273 }
7374 forcedUnlock = false;
7475 if (required) {
76+ if (loader.item) {
77+ loader.item.reset(true /* forceShow */);
78+ }
7579 // Normally loader.onLoaded will select a user, but if we're
7680 // already shown, do it manually.
77− d.selectUser(d.currentIndex, true);
81+ d.selectUser(d.currentIndex, false);
7882 }
7983
8084 // Even though we may already be shown, we want to call show() for its
@@ -105,8 +109,8 @@ Showable {
105109 }
106110 }
107111
108− // Notify that the user has explicitly requested the given app through unity8 GUI.
109− function notifyUserRequestedApp(appId) {
112+ // Notify that the user has explicitly requested an app
113+ function notifyUserRequestedApp() {
110114 if (!active) {
111115 return;
112116 }
@@ -132,6 +136,22 @@ Showable {
132136 return d.startUnlock(true /* toTheRight */);
133137 }
134138
139+ function sessionToStart() {
140+ for (var i = 0; i < LightDMService.sessions.count; i++) {
141+ var session = LightDMService.sessions.data(i,
142+ LightDMService.sessionRoles.KeyRole);
143+ if (loader.item.sessionToStart === session) {
144+ return session;
145+ }
146+ }
147+
148+ if (loader.item.sessionToStart === LightDMService.greeter.defaultSession) {
149+ return LightDMService.greeter.defaultSession;
150+ } else {
151+ return "ubuntu"; // The default / fallback
152+ }
153+ }
154+
135155 QtObject {
136156 id: d
137157
@@ -178,7 +198,7 @@ Showable {
178198 return;
179199 d.waiting = true;
180200 if (reset) {
181− loader.item.reset();
201+ loader.item.reset(false /* forceShow */);
182202 }
183203 currentIndex = index;
184204 var user = LightDMService.users.data(index, LightDMService.userRoles.NameRole);
@@ -197,7 +217,7 @@ Showable {
197217
198218 function login() {
199219 d.waiting = true;
200− if (LightDMService.greeter.startSessionSync()) {
220+ if (LightDMService.greeter.startSessionSync(root.sessionToStart())) {
201221 sessionStarted();
202222 hideView();
203223 } else if (loader.item) {
@@ -222,6 +242,29 @@ Showable {
222242 }
223243 }
224244 }
245+
246+ function showPromptMessage(text, isError) {
247+ // inefficient, but we only rarely deal with messages
248+ var html = text.replace(/&/g, "&amp;")
249+ .replace(/</g, "&lt;")
250+ .replace(/>/g, "&gt;")
251+ .replace(/\n/g, "<br>");
252+ if (isError) {
253+ html = "<font color=\"#df382c\">" + html + "</font>";
254+ }
255+
256+ if (loader.item) {
257+ loader.item.showMessage(html);
258+ }
259+ }
260+
261+ function showFingerprintMessage(msg) {
262+ if (loader.item) {
263+ loader.item.reset(false /* forceShow */);
264+ loader.item.showErrorMessage(msg);
265+ }
266+ showPromptMessage(msg, true);
267+ }
225268 }
226269
227270 onLauncherOffsetChanged: {
@@ -393,6 +436,12 @@ Showable {
393436 value: root.background
394437 }
395438
439+ Binding {
440+ target: loader.item
441+ property: "hasCustomBackground"
442+ value: root.hasCustomBackground
443+ }
444+
396445 Binding {
397446 target: loader.item
398447 property: "locked"
@@ -436,20 +485,7 @@ Showable {
436485 onShowGreeter: root.forceShow()
437486 onHideGreeter: root.forcedUnlock = true
438487
439− onShowMessage: {
440− // inefficient, but we only rarely deal with messages
441− var html = text.replace(/&/g, "&amp;")
442− .replace(/</g, "&lt;")
443− .replace(/>/g, "&gt;")
444− .replace(/\n/g, "<br>");
445− if (isError) {
446− html = "<font color=\"#df382c\">" + html + "</font>";
447− }
448−
449− if (loader.item) {
450− loader.item.showMessage(html);
451− }
452− }
488+ onShowMessage: d.showPromptMessage(text, isError)
453489
454490 onShowPrompt: {
455491 if (loader.item) {
@@ -557,10 +593,8 @@ Showable {
557593 if (!d.secureFingerprint) {
558594 d.startUnlock(false /* toTheRight */); // use normal login instead
559595 }
560− if (loader.item) {
561− var msg = d.secureFingerprint ? i18n.tr("Try again") : "";
562− loader.item.showErrorMessage(msg);
563− }
596+ var msg = d.secureFingerprint ? i18n.tr("Try again") : "";
597+ d.showFingerprintMessage(msg);
564598 }
565599
566600 Component.onCompleted: restartOperation()
@@ -578,8 +612,10 @@ Showable {
578612 return;
579613 }
580614 console.log("Identified user by fingerprint:", result);
581− if (loader.item)
615+ if (loader.item) {
616+ loader.item.enabled = false;
582617 loader.item.notifyAuthenticationSucceeded(true /* showFakePassword */);
618+ }
583619 if (root.active)
584620 root.forcedUnlock = true;
585621 }
qml/Greeter/GreeterPrompt.qml+21−7
@@ -38,6 +38,16 @@ FocusScope {
3838 d.enabled = true;
3939 }
4040
41+ function showFakePassword() {
42+ // Just a silly hack for looking like 4 pin numbers got entered, if
43+ // a fingerprint was used and we happen to be using a pin. This was
44+ // a request from Design.
45+ if (isSecret && isPrompt && !isAlphanumeric) {
46+ d.enabled = false;
47+ text = "...."; // actual text doesn't matter
48+ }
49+ }
50+
4151 StyledItem {
4252 id: d
4353
@@ -50,6 +60,12 @@ FocusScope {
5060 : theme.palette.disabled.raisedSecondaryText
5161 readonly property color errorColor: passwordInput.enabled ? theme.palette.normal.negative
5262 : theme.palette.disabled.negative
63+
64+ onEnabledChanged: {
65+ if (!enabled) {
66+ fakeLabel.text = passwordInput.displayText;
67+ }
68+ }
5369 }
5470
5571 Rectangle {
@@ -142,15 +158,13 @@ FocusScope {
142158 }
143159 }
144160
145− onAccepted: {
146− if (d.enabled)
147− respond();
148− }
161+ onAccepted: respond()
149162
150163 function respond() {
151− d.enabled = false;
152− fakeLabel.text = displayText;
153− root.responded(text);
164+ if (d.enabled) {
165+ d.enabled = false;
166+ root.responded(text);
167+ }
154168 }
155169
156170 Keys.onEscapePressed: {
qml/Greeter/Infographics.qml+19−20
@@ -14,7 +14,6 @@
1414 * along with this program. If not, see <http://www.gnu.org/licenses/>.
1515 */
1616
17−import "../Components"
1817import "Gradient.js" as Gradient
1918import QtQuick 2.4
2019import Ubuntu.Components 1.3
@@ -36,6 +35,13 @@ Item {
3635 || circleChangeAnimTimer.running
3736 }
3837
38+ QtObject {
39+ id: whiteTheme
40+ property color main: "white"
41+ property color start: "white"
42+ property color end: "white"
43+ }
44+
3945 Connections {
4046 target: model
4147 ignoreUnknownSignals: model === undefined
@@ -142,9 +148,10 @@ Item {
142148 width: dataCircle.width / divisor
143149 height: dataCircle.height / divisor
144150 opacity: 0.0
145− scale: 0.0
151+ circleScale: 0.0
146152 visible: modelData !== undefined
147153 color: "transparent"
154+ centerCircle: dataCircle
148155
149156 SequentialAnimation {
150157 id: pastCircleChangeAnim
@@ -160,7 +167,7 @@ Item {
160167 }
161168 PropertyAnimation {
162169 target: pastCircle
163− property: "scale"
170+ property: "circleScale"
164171 to: modelData
165172 easing.type: Easing.OutCurve
166173 duration: circleChangeAnimTimer.interval * d.circleModifier
@@ -168,7 +175,7 @@ Item {
168175 ColorAnimation {
169176 target: pastCircle
170177 property: "color"
171− to: Gradient.threeColorByIndex(index, count, infographic.model.secondColor)
178+ to: Gradient.threeColorByIndex(index, count, whiteTheme)
172179 easing.type: Easing.OutCurve
173180 duration: circleChangeAnimTimer.interval * d.circleModifier
174181 }
@@ -202,9 +209,10 @@ Item {
202209 width: dataCircle.width / divisor
203210 height: dataCircle.height / divisor
204211 opacity: 0.0
205− scale: 0.0
212+ circleScale: 0.0
206213 visible: modelData !== undefined
207214 color: "transparent"
215+ centerCircle: dataCircle
208216
209217 SequentialAnimation {
210218 id: presentCircleChangeAnim
@@ -221,7 +229,7 @@ Item {
221229 }
222230 PropertyAnimation {
223231 target: presentCircle
224− property: "scale"
232+ property: "circleScale"
225233 to: modelData
226234 easing.type: Easing.OutCurve
227235 duration: circleChangeAnimTimer.interval * d.circleModifier
@@ -229,7 +237,7 @@ Item {
229237 ColorAnimation {
230238 target: presentCircle
231239 property: "color"
232− to: Gradient.threeColorByIndex(index, infographic.model.currentDay, infographic.model.firstColor)
240+ to: Gradient.threeColorByIndex(index, infographic.model.currentDay, whiteTheme)
233241 easing.type: Easing.OutCurve
234242 duration: circleChangeAnimTimer.interval * d.circleModifier
235243 }
@@ -239,15 +247,6 @@ Item {
239247 }
240248 }
241249
242− Image {
243− id: backgroundCircle
244− objectName: "backgroundCircle"
245−
246− anchors.fill: parent
247−
248− source: "graphics/infographic_circle_back.png"
249− }
250−
251250 Timer {
252251 id: dotShowAnimTimer
253252
@@ -318,7 +317,7 @@ Item {
318317
319318 index: model.index
320319 count: dots.count
321− radius: backgroundCircle.width / 2
320+ radius: dataCircle.width / 2
322321 halfSize: dot.width / 2
323322 posOffset: radius / dot.width / 3
324323 state: dot.state
@@ -327,7 +326,7 @@ Item {
327326 id: dot
328327 objectName: "dot" + index
329328
330− property real baseOpacity: 0.4
329+ property real baseOpacity: 1
331330
332331 width: units.dp(5) * parent.radius / 200
333332 height: units.dp(5) * parent.radius / 200
@@ -363,10 +362,10 @@ Item {
363362 property alias hideAnim: decreaseOpacity
364363 property alias showAnim: increaseOpacity
365364
366− property real baseOpacity: 0.6
365+ property real baseOpacity: 1
367366 property real duration: dotShowAnimTimer.interval * 5
368367
369− height: 0.7 * backgroundCircle.width
368+ height: 0.7 * dataCircle.width
370369 width: notification.height
371370 anchors.centerIn: parent
372371
qml/Greeter/IntegratedLightDMImpl.qml+2−1
@@ -22,7 +22,8 @@ Item{
2222
2323 property var greeter: LightDM.Greeter
2424 property var infographic: LightDM.Infographic
25+ property var sessions: LightDM.Sessions
26+ property var sessionRoles: LightDM.SessionRoles
2527 property var users: LightDM.Users
2628 property var userRoles: LightDM.UserRoles
27−
2829}
qml/Greeter/LightDMService.qml+2
@@ -27,6 +27,8 @@ Loader {
2727
2828 property var greeter: d.valid ? loader.item.greeter : null
2929 property var infographic: d.valid ? loader.item.infographic : null
30+ property var sessions: d.valid ? loader.item.sessions : null
31+ property var sessionRoles: d.valid ? loader.item.sessionRoles : null
3032 property var users: d.valid ? loader.item.users : null
3133 property var userRoles: d.valid ? loader.item.userRoles : null
3234
qml/Greeter/LoginAreaContainer.qmlnew file+38
@@ -0,0 +1,38 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+import Ubuntu.Components 1.3
19+
20+Item {
21+ BorderImage {
22+ anchors {
23+ fill: parent
24+ topMargin: -units.gu(1)
25+ leftMargin: -units.gu(1.5)
26+ rightMargin: -units.gu(1.5)
27+ bottomMargin: -units.gu(1.5)
28+ }
29+ source: "../Stages/graphics/dropshadow2gu.sci"
30+ opacity: 0.35
31+ }
32+
33+ UbuntuShape {
34+ anchors.fill: parent
35+ aspect: UbuntuShape.Flat
36+ backgroundColor: theme.palette.normal.raised
37+ }
38+}
qml/Greeter/LoginList.qml+96−22
@@ -18,6 +18,7 @@ import QtQuick 2.4
1818import QtGraphicalEffects 1.0
1919import Ubuntu.Components 1.3
2020import "../Components"
21+import "." 0.1
2122
2223StyledItem {
2324 id: root
@@ -28,16 +29,22 @@ StyledItem {
2829 property int currentIndex
2930 property bool locked
3031 property bool waiting
32+ property alias boxVerticalOffset: highlightItem.y
3133
34+ readonly property alias passwordInput: passwordInput
3235 readonly property int numAboveBelow: 4
3336 readonly property int cellHeight: units.gu(5)
3437 readonly property int highlightedHeight: units.gu(15)
35− readonly property int moveDuration: 200
38+ readonly property int moveDuration: UbuntuAnimation.FastDuration
39+ property string selectedSession
40+ property string currentSession
3641 readonly property string currentUser: userList.currentItem.username
3742 property bool wasPrompted: false
3843
39− signal selected(int index)
44+ signal loginListSessionChanged(string session)
4045 signal responded(string response)
46+ signal selected(int index)
47+ signal sessionChooserButtonClicked()
4148
4249 function tryToUnlock() {
4350 if (wasPrompted) {
@@ -78,6 +85,10 @@ StyledItem {
7885 root.resetAuthentication();
7986 }
8087
88+ function showFakePassword() {
89+ passwordInput.showFakePassword();
90+ }
91+
8192 QtObject {
8293 id: d
8394
@@ -97,6 +108,7 @@ StyledItem {
97108 name: "Ubuntu.Components.Themes.Ambiance"
98109 }
99110
111+ KeyNavigation.tab: sessionChooser
100112 Keys.onUpPressed: {
101113 selected(currentIndex - 1);
102114 event.accepted = true;
@@ -114,30 +126,17 @@ StyledItem {
114126 userList.currentIndex = currentIndex;
115127 }
116128
117− BorderImage {
118− anchors {
119− fill: highlightItem
120− topMargin: -units.gu(1)
121− leftMargin: -units.gu(1.5)
122− rightMargin: -units.gu(1.5)
123− bottomMargin: -units.gu(1.5)
124− }
125− source: "../Stages/graphics/dropshadow2gu.sci"
126− opacity: 0.35
127− }
128−
129− UbuntuShape {
129+ LoginAreaContainer {
130130 id: highlightItem
131+ objectName: "highlightItem"
131132 anchors {
132133 left: parent.left
133134 leftMargin: units.gu(2)
134135 right: parent.right
135136 rightMargin: units.gu(2)
136− verticalCenter: parent.verticalCenter
137137 }
138+
138139 height: root.highlightedHeight
139− aspect: UbuntuShape.Flat
140− backgroundColor: theme.palette.normal.raised
141140 }
142141
143142 ListView {
@@ -148,11 +147,10 @@ StyledItem {
148147 anchors.leftMargin: units.gu(2)
149148 anchors.rightMargin: units.gu(2)
150149
151− preferredHighlightBegin: userList.height / 2 - root.highlightedHeight / 2
152− preferredHighlightEnd: userList.height / 2 - root.highlightedHeight / 2
150+ preferredHighlightBegin: highlightItem.y
151+ preferredHighlightEnd: highlightItem.y
153152 highlightRangeMode: ListView.StrictlyEnforceRange
154153 highlightMoveDuration: root.moveDuration
155− flickDeceleration: 10000
156154 interactive: count > 1
157155
158156 readonly property bool movingInternally: moveTimer.running || userList.moving
@@ -173,6 +171,7 @@ StyledItem {
173171
174172 readonly property bool belowHighlight: (userList.currentIndex < 0 && index > 0) || (userList.currentIndex >= 0 && index > userList.currentIndex)
175173 readonly property int belowOffset: root.highlightedHeight - root.cellHeight
174+ readonly property string userSession: session
176175 readonly property string username: name
177176
178177 opacity: {
@@ -206,9 +205,22 @@ StyledItem {
206205 bottomMargin: -(units.gu(4) + (parent.belowHighlight ? parent.belowOffset : 0))
207206 }
208207 text: realName
209− color: userList.currentIndex !== index ? theme.palette.normal.raised : theme.palette.normal.raisedText
208+ color: userList.currentIndex !== index ? theme.palette.normal.raised
209+ : theme.palette.normal.raisedText
210210
211211 Behavior on anchors.topMargin { NumberAnimation { duration: root.moveDuration; easing.type: Easing.InOutQuad; } }
212+
213+ Rectangle {
214+ id: activeIndicator
215+ anchors.horizontalCenter: parent.left
216+ anchors.horizontalCenterOffset: -units.gu(1)
217+ anchors.verticalCenter: parent.verticalCenter
218+ color: userList.currentIndex !== index ? theme.palette.normal.raised
219+ : theme.palette.normal.focus
220+ visible: userList.count > 1 && loggedIn
221+ height: units.gu(0.5)
222+ width: height
223+ }
212224 }
213225
214226 MouseArea {
@@ -237,6 +249,67 @@ StyledItem {
237249 }
238250 }
239251
252+ // Use an AbstractButton due to icon limitations with Button
253+ AbstractButton {
254+ id: sessionChooser
255+ objectName: "sessionChooserButton"
256+
257+ readonly property alias icon: badge.source
258+
259+ visible: LightDMService.sessions.count > 1 &&
260+ !LightDMService.users.data(userList.currentIndex, LightDMService.userRoles.LoggedInRole)
261+
262+ height: units.gu(3.5)
263+ width: units.gu(3.5)
264+
265+ anchors {
266+ right: highlightItem.right
267+ rightMargin: units.gu(2)
268+
269+ top: highlightItem.top
270+ topMargin: units.gu(1.5)
271+ }
272+
273+ Rectangle {
274+ id: badgeHighlight
275+
276+ anchors.fill: parent
277+ visible: parent.activeFocus
278+ color: "transparent"
279+ border.color: theme.palette.normal.focus
280+ border.width: units.dp(1)
281+ radius: 3
282+ }
283+
284+ Icon {
285+ id: badge
286+ anchors.fill: parent
287+ anchors.margins: units.dp(3)
288+ keyColor: "#ffffff" // icon providers give us white icons
289+ color: theme.palette.normal.raisedSecondaryText
290+ source: LightDMService.sessions.iconUrl(root.currentSession)
291+ }
292+
293+ KeyNavigation.tab: passwordInput
294+ Keys.onReturnPressed: {
295+ sessionChooserButtonClicked();
296+ event.accepted = true;
297+ }
298+
299+ onClicked: {
300+ sessionChooserButtonClicked();
301+ }
302+
303+ // Refresh the icon path if looking at different places at runtime
304+ // this is mainly for testing
305+ Connections {
306+ target: LightDMService.sessions
307+ onIconSearchDirectoriesChanged: {
308+ badge.source = LightDMService.sessions.iconUrl(root.currentSession)
309+ }
310+ }
311+ }
312+
240313 FadingLabel {
241314 id: infoLabel
242315 objectName: "infoLabel"
@@ -283,6 +356,7 @@ StyledItem {
283356
284357 WrongPasswordAnimation {
285358 id: wrongPasswordAnimation
359+ objectName: "wrongPasswordAnimation"
286360 target: passwordInput
287361 }
288362 }
qml/Greeter/NarrowView.qml+147−61
@@ -15,7 +15,9 @@
1515 */
1616
1717import QtQuick 2.4
18+import QtQuick.Window 2.2
1819import Ubuntu.Components 1.3
20+import Ubuntu.Telephony 0.1 as Telephony
1921import "../Components"
2022
2123FocusScope {
@@ -23,34 +25,40 @@ FocusScope {
2325
2426 property alias dragHandleLeftMargin: coverPage.dragHandleLeftMargin
2527 property alias launcherOffset: coverPage.launcherOffset
26− property int currentIndex // unused
27− property alias delayMinutes: lockscreen.delayMinutes
28+ property alias currentIndex: loginList.currentIndex
29+ property alias delayMinutes: delayedLockscreen.delayMinutes
2830 property alias backgroundTopMargin: coverPage.backgroundTopMargin
2931 property url background
32+ property bool hasCustomBackground
3033 property bool locked
31− property bool alphanumeric
32− property var userModel // unused
34+ property alias alphanumeric: loginList.alphanumeric
35+ property alias userModel: loginList.model
3336 property alias infographicModel: coverPage.infographicModel
37+ property string sessionToStart
3438 property bool waiting
3539 readonly property bool fullyShown: coverPage.showProgress === 1 || lockscreen.shown
3640 readonly property bool required: coverPage.required || lockscreen.required
3741 readonly property bool animating: coverPage.showAnimation.running || coverPage.hideAnimation.running
3842
39− signal selected(int index) // unused
43+ // so that it can be replaced in tests with a mock object
44+ property var inputMethod: Qt.inputMethod
45+
46+ signal selected(int index)
4047 signal responded(string response)
4148 signal tease()
4249 signal emergencyCall()
4350
4451 function showMessage(html) {
45− // TODO
52+ loginList.showMessage(html);
4653 }
4754
4855 function showPrompt(text, isSecret, isDefaultPrompt) {
49− lockscreen.promptText = isDefaultPrompt ? "" : text.toLowerCase();
50− lockscreen.maybeShow();
56+ loginList.showPrompt(text, isSecret, isDefaultPrompt);
5157 }
5258
5359 function showLastChance() {
60+ /* TODO: when we finish support for resetting device after too many
61+ failed logins, we should re-add this popup.
5462 var title = lockscreen.alphaNumeric ?
5563 i18n.tr("Sorry, incorrect passphrase.") :
5664 i18n.tr("Sorry, incorrect passcode.");
@@ -59,6 +67,7 @@ FocusScope {
5967 i18n.tr("If passphrase is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted.") :
6068 i18n.tr("If passcode is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted."));
6169 lockscreen.showInfoPopup(title, text);
70+ */
6271 }
6372
6473 function hide() {
@@ -67,27 +76,24 @@ FocusScope {
6776 }
6877
6978 function notifyAuthenticationSucceeded(showFakePassword) {
70− // When using an alternate log in mechanism like fingerprints, the
71− // design is it looks like the user entered a passcode.
72− if (!alphanumeric && showFakePassword) {
73− lockscreen.showText("...."); // actual text doesn't matter, we show bullets
79+ if (showFakePassword) {
80+ loginList.showFakePassword();
7481 }
7582 }
7683
7784 function notifyAuthenticationFailed() {
78− lockscreen.customError = "";
79− lockscreen.clear(true);
85+ loginList.showError();
8086 }
8187
8288 function showErrorMessage(msg) {
8389 coverPage.showErrorMessage(msg);
84− lockscreen.customError = msg ? msg : " "; // avoid default message
85− lockscreen.clear(true);
8690 }
8791
88− function reset() {
89− lockscreen.customError = "";
90− coverPage.show();
92+ function reset(forceShow) {
93+ loginList.reset();
94+ if (forceShow) {
95+ coverPage.show();
96+ }
9197 }
9298
9399 function tryToUnlock(toTheRight) {
@@ -109,48 +115,59 @@ FocusScope {
109115 }
110116 }
111117
112− Lockscreen {
118+ Showable {
113119 id: lockscreen
114120 objectName: "lockscreen"
115−
121+ anchors.fill: parent
116122 shown: false
123+
117124 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
118125 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
119− anchors.fill: parent
120− visible: required
121− enabled: !coverPage.shown
122− background: root.background
123− darkenBackground: 0.4
124− alphaNumeric: root.alphanumeric
125− minPinLength: 4
126− maxPinLength: 4
127−
128− property string promptText
129− infoText: promptText !== "" ? i18n.tr("Enter %1").arg(promptText) :
130− alphaNumeric ? i18n.tr("Enter passphrase") :
131− i18n.tr("Enter passcode")
132−
133− property string customError
134− errorText: customError !== "" ? customError :
135− promptText !== "" ? i18n.tr("Sorry, incorrect %1").arg(promptText) :
136− alphaNumeric ? i18n.tr("Sorry, incorrect passphrase") + "\n" +
137− i18n.ctr("passphrase", "Please re-enter") :
138− i18n.tr("Sorry, incorrect passcode")
139−
140− onEntered: root.responded(passphrase)
141− onCancel: coverPage.show()
142− onEmergencyCall: root.emergencyCall()
143−
144− onEnabledChanged: {
145− if (enabled) {
146− lockscreen.forceActiveFocus();
126+
127+ Wallpaper {
128+ id: lockscreenBackground
129+ objectName: "lockscreenBackground"
130+ anchors {
131+ fill: parent
132+ topMargin: root.backgroundTopMargin
147133 }
134+ source: root.background
148135 }
149136
150− onVisibleChanged: {
151− if (visible) {
152− lockscreen.forceActiveFocus();
137+ // Darken background to match CoverPage
138+ Rectangle {
139+ objectName: "lockscreenShade"
140+ anchors.fill: parent
141+ color: "black"
142+ opacity: root.hasCustomBackground ? 0.4 : 0
143+ }
144+
145+ LoginList {
146+ id: loginList
147+ objectName: "loginList"
148+
149+ anchors {
150+ horizontalCenter: parent.horizontalCenter
151+ top: parent.top
152+ bottom: parent.bottom
153153 }
154+ width: units.gu(40)
155+ boxVerticalOffset: units.gu(14)
156+ enabled: !coverPage.shown && visible
157+ visible: !delayedLockscreen.visible
158+
159+ locked: root.locked
160+
161+ onSelected: if (enabled) root.selected(index)
162+ onResponded: root.responded(response)
163+ }
164+
165+ DelayedLockscreen {
166+ id: delayedLockscreen
167+ objectName: "delayedLockscreen"
168+ anchors.fill: parent
169+ visible: delayMinutes > 0
170+ alphaNumeric: loginList.alphanumeric
154171 }
155172
156173 function maybeShow() {
@@ -172,21 +189,16 @@ FocusScope {
172189 height: parent.height
173190 width: parent.width
174191 background: root.background
192+ hasCustomBackground: root.hasCustomBackground
175193 draggable: !root.waiting
176194 onTease: root.tease()
177195 onClicked: hide()
178196
179197 onShowProgressChanged: {
180198 if (showProgress === 1) {
181− lockscreen.reset();
182− }
183−
184− if (showProgress === 0) {
185− if (root.locked) {
186− lockscreen.clear(false); // to reset focus if necessary
187− } else {
188− root.responded("");
189− }
199+ loginList.reset();
200+ } else if (showProgress === 0) {
201+ loginList.tryToUnlock();
190202 }
191203 }
192204
@@ -198,4 +210,78 @@ FocusScope {
198210 }
199211 }
200212 }
213+
214+ StyledItem {
215+ id: bottomBar
216+ visible: lockscreen.shown
217+ height: units.gu(4)
218+
219+ anchors.left: parent.left
220+ anchors.right: parent.right
221+ anchors.top: parent.bottom
222+ anchors.topMargin: - height * (1 - coverPage.showProgress)
223+ - (inputMethod && inputMethod.visible ?
224+ inputMethod.keyboardRectangle.height : 0)
225+
226+ Rectangle {
227+ color: UbuntuColors.porcelain // matches OSK background
228+ anchors.fill: parent
229+ }
230+
231+ Label {
232+ text: i18n.tr("Cancel")
233+ anchors.left: parent.left
234+ anchors.leftMargin: units.gu(2)
235+ anchors.top: parent.top
236+ anchors.bottom: parent.bottom
237+ verticalAlignment: Text.AlignVCenter
238+ font.weight: Font.Light
239+ fontSize: "small"
240+ color: UbuntuColors.slate
241+
242+ AbstractButton {
243+ anchors.fill: parent
244+ anchors.leftMargin: -units.gu(2)
245+ anchors.rightMargin: -units.gu(2)
246+ onClicked: coverPage.show()
247+ }
248+ }
249+
250+ Label {
251+ objectName: "emergencyCallLabel"
252+ text: callManager.hasCalls ? i18n.tr("Return to Call") : i18n.tr("Emergency")
253+ anchors.right: parent.right
254+ anchors.rightMargin: units.gu(2)
255+ anchors.top: parent.top
256+ anchors.bottom: parent.bottom
257+ verticalAlignment: Text.AlignVCenter
258+ font.weight: Font.Light
259+ fontSize: "small"
260+ color: UbuntuColors.slate
261+ // TODO: uncomment once bug 1616538 is fixed
262+ // visible: telepathyHelper.ready && telepathyHelper.emergencyCallsAvailable
263+ enabled: visible
264+
265+ AbstractButton {
266+ anchors.fill: parent
267+ anchors.leftMargin: -units.gu(2)
268+ anchors.rightMargin: -units.gu(2)
269+ onClicked: root.emergencyCall()
270+ }
271+ }
272+ }
273+
274+ // FIXME: It's difficult to keep something tied closely to the OSK (bug
275+ // 1616163). But as a hack to avoid the background peeking out,
276+ // we add an extra Rectangle that just serves to hide the background
277+ // during OSK animations.
278+ Rectangle {
279+ visible: bottomBar.visible
280+ height: inputMethod && inputMethod.visible ?
281+ inputMethod.keyboardRectangle.height : 0
282+ anchors.bottom: parent.bottom
283+ anchors.left: parent.left
284+ anchors.right: parent.right
285+ color: UbuntuColors.porcelain
286+ }
201287}
qml/Greeter/SessionIcon.qmlnew file+47
@@ -0,0 +1,47 @@
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+
19+Image {
20+ id: root
21+
22+ property color color
23+ readonly property string fragColorShader:
24+ "
25+ varying highp vec2 qt_TexCoord0;
26+ uniform sampler2D source;
27+ uniform lowp vec4 color;
28+ uniform lowp float qt_Opacity;
29+
30+ void main()
31+ {
32+ lowp vec4 sourceColor = texture2D(source, qt_TexCoord0);
33+ gl_FragColor = color * sourceColor.a * qt_Opacity;
34+ }
35+ "
36+
37+ fillMode: Image.PreserveAspectFit
38+ height: sourceSize.height
39+
40+ ShaderEffect {
41+ readonly property color color: root.color
42+ readonly property Image source: parent
43+
44+ height: source.height; width: source.width
45+ fragmentShader: fragColorShader
46+ }
47+}
qml/Greeter/SessionsList.qmlnew file+182
@@ -0,0 +1,182 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+import Ubuntu.Components 1.3
19+import Ubuntu.Components.ListItems 1.3
20+import "." 0.1
21+import "../Components"
22+
23+Item {
24+ id: root
25+ objectName: "sessionsList"
26+
27+ property string initiallySelectedSession
28+ signal sessionSelected(string sessionKey)
29+ signal showLoginList()
30+
31+ onInitiallySelectedSessionChanged: {
32+ sessionsList.currentIndex = getSelectedIndex();
33+ sessionsList.positionViewAtIndex(sessionsList.currentIndex, ListView.Contain);
34+ }
35+
36+ function getSelectedIndex() {
37+ for (var i = 0; i < sessionsList.model.count; i++) {
38+ var key = sessionsList.model.get(i).key
39+ if (key === initiallySelectedSession) {
40+ return i;
41+ }
42+ }
43+ }
44+
45+ function currentKey() {
46+ var session = LightDMService.sessions.data(
47+ sessionsList.currentIndex, LightDMService.sessionRoles.KeyRole)
48+ return session;
49+ }
50+
51+ Keys.onEnterPressed: {
52+ showLoginList(); // Session is already selected
53+ event.accepted = true;
54+ }
55+
56+ Keys.onReturnPressed: {
57+ showLoginList();
58+ event.accepted = true;
59+ }
60+
61+ Keys.onDownPressed: {
62+ if (sessionsList.currentIndex < sessionsList.model.count - 1)
63+ sessionsList.currentIndex++;
64+ sessionSelected(currentKey());
65+ event.accepted = true;
66+ }
67+
68+ Keys.onUpPressed: {
69+ if (sessionsList.currentIndex > 0)
70+ sessionsList.currentIndex--;
71+ sessionSelected(currentKey());
72+ event.accepted = true;
73+ }
74+
75+ LoginAreaContainer {
76+ readonly property real margins: sessionsList.anchors.margins
77+ readonly property real prefferedHeight: {
78+ if (sessionsList.currentItem) {
79+ return ((sessionsList.currentItem.height + margins) *
80+ sessionsList.model.count) + sessionsList.headerItem.height + 2 * margins
81+ } else {
82+ return sessionsList.headerItem.height + 2 * margins
83+ }
84+ }
85+
86+ height: prefferedHeight < parent.height ? prefferedHeight : parent.height - units.gu(4)
87+ width: parent.width
88+
89+ anchors {
90+ left: parent.left
91+ right: parent.right
92+ verticalCenter: parent.verticalCenter
93+ }
94+
95+ UbuntuListView {
96+ id: sessionsList
97+
98+ anchors {
99+ top: parent.top
100+ left: parent.left
101+ right: parent.right
102+ margins: units.gu(2)
103+ }
104+
105+ height: parent.height - headerItem.height
106+
107+ boundsBehavior: Flickable.StopAtBounds
108+
109+ model: LightDMService.sessions
110+ header: ListItemLayout {
111+ id: header
112+
113+ padding.leading: 0 // handled by parent's margins
114+
115+ title.color: theme.palette.normal.raisedText
116+ title.font.pixelSize: units.gu(2.1)
117+ title.text: i18n.tr("Select desktop environment")
118+
119+ Icon {
120+ id: icon
121+ width: units.gu(3)
122+ SlotsLayout.position: SlotsLayout.Leading
123+ name: "go-previous"
124+
125+ MouseArea {
126+ anchors.fill: parent
127+ onClicked: showLoginList()
128+ }
129+ }
130+ }
131+
132+ headerPositioning: ListView.OverlayHeader
133+ highlightFollowsCurrentItem: false
134+
135+ delegate: ListItem {
136+ id: delegate
137+ objectName: "sessionDelegate" + index
138+
139+ divider.visible: false
140+
141+ visible: y > sessionsList.headerItem.y
142+ + sessionsList.headerItem.height
143+ - sessionsList.anchors.margins
144+
145+ MouseArea {
146+ anchors.fill: parent
147+ onClicked: {
148+ sessionsList.currentIndex = index
149+ sessionSelected(key)
150+ showLoginList()
151+ }
152+ }
153+
154+ Rectangle {
155+ height: parent.height
156+ width: parent.width
157+ color: "transparent"
158+ border {
159+ color: theme.palette.normal.positionText
160+ width: units.gu(0.2)
161+ }
162+
163+ visible: index === sessionsList.currentIndex
164+ }
165+
166+ ListItemLayout {
167+ id: layout
168+
169+ SessionIcon {
170+ id: sessionIcon
171+ source: icon_url
172+ SlotsLayout.position: SlotsLayout.Leading
173+ color: theme.palette.normal.raisedSecondaryText
174+ }
175+
176+ title.text: display
177+ title.color: theme.palette.normal.raisedText
178+ }
179+ }
180+ }
181+ }
182+}
qml/Greeter/WideView.qml+109−38
@@ -16,25 +16,29 @@
1616
1717import QtQuick 2.4
1818import Ubuntu.Components 1.3
19+import "." 0.1
1920
2021FocusScope {
2122 id: root
2223 focus: true
2324
25+ property alias background: coverPage.background
26+ property alias backgroundTopMargin: coverPage.backgroundTopMargin
27+ property alias hasCustomBackground: coverPage.hasCustomBackground
2428 property alias dragHandleLeftMargin: coverPage.dragHandleLeftMargin
29+ property alias infographicModel: coverPage.infographicModel
2530 property alias launcherOffset: coverPage.launcherOffset
2631 property alias currentIndex: loginList.currentIndex
2732 property int delayMinutes // TODO
28− property alias backgroundTopMargin: coverPage.backgroundTopMargin
29− property alias background: coverPage.background
30− property bool locked
3133 property alias alphanumeric: loginList.alphanumeric
32− property alias userModel: loginList.model
33− property alias infographicModel: coverPage.infographicModel
34− property bool waiting
34+ property alias locked: loginList.locked
35+ property alias sessionToStart: loginList.currentSession
36+ property alias waiting: loginList.waiting
37+ property var userModel // Set from outside
38+
39+ readonly property bool animating: coverPage.showAnimation.running || coverPage.hideAnimation.running
3540 readonly property bool fullyShown: coverPage.showProgress === 1
3641 readonly property bool required: coverPage.required
37− readonly property bool animating: coverPage.showAnimation.running || coverPage.hideAnimation.running
3842
3943 // so that it can be replaced in tests with a mock object
4044 property var inputMethod: Qt.inputMethod
@@ -44,36 +48,20 @@ FocusScope {
4448 signal tease()
4549 signal emergencyCall() // unused
4650
47− function showMessage(html) {
48− loginList.showMessage(html);
49− }
50−
51− function showPrompt(text, isSecret, isDefaultPrompt) {
52− loginList.showPrompt(text, isSecret, isDefaultPrompt);
53− }
54−
55− function showLastChance() {
56− // TODO
57− }
58−
59− function hide() {
60− coverPage.hide();
61− }
62−
63− function notifyAuthenticationSucceeded(showFakePassword) {
64− // Nothing needed
65− }
66−
6751 function notifyAuthenticationFailed() {
6852 loginList.showError();
6953 }
7054
71− function showErrorMessage(msg) {
72− coverPage.showErrorMessage(msg);
55+ function reset(forceShow) {
56+ loginList.reset();
7357 }
7458
75− function reset() {
76− loginList.reset();
59+ function showMessage(html) {
60+ loginList.showMessage(html);
61+ }
62+
63+ function showPrompt(text, isSecret, isDefaultPrompt) {
64+ loginList.showPrompt(text, isSecret, isDefaultPrompt);
7765 }
7866
7967 function tryToUnlock(toTheRight) {
@@ -92,6 +80,20 @@ FocusScope {
9280 }
9381 }
9482
83+ function hide() {
84+ coverPage.hide();
85+ }
86+
87+ function notifyAuthenticationSucceeded(showFakePassword) {
88+ if (showFakePassword) {
89+ loginList.showFakePassword();
90+ }
91+ }
92+
93+ function showLastChance() {
94+ // TODO
95+ }
96+
9597 Rectangle {
9698 anchors.fill: parent
9799 color: "black"
@@ -104,6 +106,7 @@ FocusScope {
104106 height: parent.height
105107 width: parent.width
106108 draggable: !root.locked && !root.waiting
109+ state: "LoginList"
107110
108111 infographics {
109112 height: 0.75 * parent.height
@@ -122,21 +125,89 @@ FocusScope {
122125 id: loginList
123126 objectName: "loginList"
124127
128+ width: units.gu(40)
125129 anchors {
126130 left: parent.left
127131 leftMargin: Math.min(parent.width * 0.16, units.gu(20))
128132 top: parent.top
133+ bottom: parent.bottom
129134 }
130− width: units.gu(40)
131− height: inputMethod && inputMethod.visible ? parent.height - inputMethod.keyboardRectangle.height
132− : parent.height
133− Behavior on height { UbuntuNumberAnimation {} }
134135
135− locked: root.locked
136− waiting: root.waiting
136+ boxVerticalOffset: (height - highlightedHeight -
137+ (inputMethod && inputMethod.visible ?
138+ inputMethod.keyboardRectangle.height : 0)) / 2
139+ Behavior on boxVerticalOffset { UbuntuNumberAnimation {} }
137140
138− onSelected: root.selected(index)
141+ model: root.userModel
142+ currentSession: LightDMService.greeter.defaultSession
139143 onResponded: root.responded(response)
144+ onSelected: root.selected(index)
145+ onSessionChooserButtonClicked: parent.state = "SessionsList"
146+
147+ Keys.forwardTo: [sessionChooserLoader.item]
140148 }
149+
150+ Loader {
151+ id: sessionChooserLoader
152+
153+ height: loginList.height
154+ width: loginList.width
155+ anchors {
156+ left: parent.left
157+ leftMargin: Math.min(parent.width * 0.16, units.gu(20))
158+ top: parent.top
159+ }
160+
161+ active: false
162+
163+ onLoaded: sessionChooserLoader.item.forceActiveFocus();
164+ Binding {
165+ target: sessionChooserLoader.item
166+ property: "initiallySelectedSession"
167+ value: loginList.currentSession
168+ }
169+
170+ Connections {
171+ target: sessionChooserLoader.item
172+ onSessionSelected: loginList.currentSession = sessionKey
173+ onShowLoginList: {
174+ coverPage.state = "LoginList"
175+ loginList.passwordInput.forceActiveFocus();
176+ }
177+ ignoreUnknownSignals: true
178+ }
179+ }
180+
181+ states: [
182+ State {
183+ name: "SessionsList"
184+ PropertyChanges { target: loginList; opacity: 0 }
185+ PropertyChanges { target: sessionChooserLoader;
186+ active: true;
187+ opacity: 1
188+ source: "SessionsList.qml"
189+ }
190+ },
191+
192+ State {
193+ name: "LoginList"
194+ PropertyChanges { target: loginList; opacity: 1 }
195+ PropertyChanges { target: sessionChooserLoader;
196+ active: false;
197+ opacity: 0
198+ source: "";
199+ }
200+ }
201+ ]
202+
203+ transitions: [
204+ Transition {
205+ from: "*"
206+ to: "*"
207+ UbuntuNumberAnimation {
208+ property: "opacity";
209+ }
210+ }
211+ ]
141212 }
142213}
qml/Greeter/graphics/infographic_circle_back.pngdeleted

Binary file not shown.

qml/Greeter/graphics/session_icons/gnome_badge.pngnew file

Binary file not shown.

qml/Greeter/graphics/session_icons/kde_badge.pngnew file

Binary file not shown.

qml/Greeter/graphics/session_icons/recovery_console_badge.pngnew file

Binary file not shown.

qml/Greeter/graphics/session_icons/ubuntu_badge.pngnew file

Binary file not shown.

qml/Greeter/graphics/session_icons/unknown_badge.pngnew file

Binary file not shown.

qml/Launcher/LauncherPanel.qml+1−1
@@ -19,7 +19,7 @@ import Ubuntu.Components 1.3
1919import Unity.Launcher 0.1
2020import Ubuntu.Components.Popups 1.3
2121import "../Components/ListItems"
22−import "../Components/"
22+import "../Components"
2323
2424Rectangle {
2525 id: root
qml/Panel/IndicatorPage.qml+1
@@ -17,6 +17,7 @@
1717import QtQuick 2.4
1818import Ubuntu.Components 1.3 as Components
1919import Unity.Indicators 0.1 as Indicators
20+import "../Components"
2021import "Indicators"
2122
2223IndicatorBase {
qml/Panel/Indicators/client/IndicatorRepresentation.qml+27−23
@@ -20,25 +20,42 @@
2020
2121import QtQuick 2.4
2222import Ubuntu.Components 1.3
23−import Ubuntu.Components.ListItems 1.3 as ListItem
2423import "../.."
2524
2625Page {
2726 id: root
28−
29− title: indicatorProperties && indicatorProperties.title ? indicatorProperties.title :
30− indicatorProperties && indicatorProperties.accessibleName ? indicatorProperties.accessibleName
31− : ""
3227 property variant indicatorProperties
3328
29+ header: PageHeader {
30+ title: indicatorProperties && indicatorProperties.title ? indicatorProperties.title :
31+ indicatorProperties && indicatorProperties.accessibleName ? indicatorProperties.accessibleName
32+ : identifier
33+ }
34+
3435 anchors.fill: parent
3536
36− ListItem.Standard {
37+ ListItem {
38+ color: theme.palette.highlighted.background
3739 id: visualCheckItem
38− text: "Enable Visual Representation"
39− control: Switch {
40− id: visualCheck
41− checked: true
40+
41+ anchors {
42+ top: header.bottom
43+ left: parent.left
44+ right: parent.right
45+ }
46+
47+ height: selectorLayout.height
48+ onClicked: visualCheck.checked = !visualCheck.checked
49+
50+ ListItemLayout {
51+ id: selectorLayout
52+ title.text: "Enable Visual Representation"
53+
54+ Switch {
55+ id: visualCheck
56+ checked: true
57+ SlotsLayout.position: SlotsLayout.Trailing
58+ }
4259 }
4360 }
4461
@@ -48,11 +65,6 @@ Page {
4865 clip:true
4966 asynchronous: true
5067
51− Rectangle {
52− anchors.fill: pageLoader
53− color: "#221e1c" // FIXME not in palette yet
54− }
55−
5668 anchors {
5769 top: visualCheckItem.bottom
5870 left: parent.left
@@ -92,13 +104,5 @@ Page {
92104 margins: units.gu(1)
93105 }
94106 height: childrenRect.height
95−
96− Button {
97− anchors {
98− left: parent.left
99− }
100− text: "Back"
101− onClicked: root.pageStack.reset()
102− }
103107 }
104108}
qml/Panel/Indicators/client/IndicatorsClient.qml+1−1
@@ -21,8 +21,8 @@ import QtQuick 2.4
2121import Ubuntu.Components 1.3
2222
2323Rectangle {
24− color: theme.palette.normal.background
2524 id: root
25+ color: theme.palette.normal.background
2626
2727 Component.onCompleted: {
2828 theme.name = "Ubuntu.Components.Themes.SuruDark"
qml/Panel/Indicators/client/IndicatorsList.qml+19−24
@@ -1,5 +1,5 @@
11/*
2− * Copyright 2013 Canonical Ltd.
2+ * Copyright 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 Lesser General Public License as published by
@@ -15,19 +15,22 @@
1515 *
1616 * Authors:
1717 * Renato Araujo Oliveira Filho <renato@canonical.com>
18+ * Marco Trevisan <marco.trevisan@canonical.com>
1819 */
1920
2021import QtQuick 2.4
2122import Ubuntu.Components 1.3
22−import Ubuntu.Components.ListItems 1.3 as ListItem
2323import Unity.Indicators 0.1 as Indicators
2424import "../.."
25+import "../../../Components"
2526
2627Page {
2728 id: page
2829 anchors.fill: parent
2930 property string profile: ""
30− title: "Plugin list"
31+ header: PageHeader {
32+ title: "Indicators List"
33+ }
3134
3235 Indicators.IndicatorsModel {
3336 id: indicatorsModel
@@ -41,32 +44,24 @@ Page {
4144 anchors.fill: parent
4245 model: indicatorsModel
4346
44− delegate: Rectangle {
45− color: "#221e1c"
46−
47− anchors.left: parent.left
48− anchors.right: parent.right
49− height: menuItem.height
47+ delegate: ListItem {
48+ objectName: identifier
49+ height: menuItem.height + (divider.visible ? divider.height : 0)
5050
51− ListItem.Standard {
51+ ListItemLayout {
5252 id: menuItem
53− anchors.left: parent.left
54− anchors.right: parent.right
55− objectName: identifier
56−
57− text: identifier
58−
59− onClicked: {
60− var new_page = Qt.createComponent("IndicatorRepresentation.qml");
61− if (new_page.status !== Component.Ready) {
62− if (new_page.status === Component.Error)
63− console.error("Error: " + new_page.errorString());
53+ title.text: identifier
54+ }
6455
65− return;
66− }
56+ onClicked: {
57+ var new_page = Qt.createComponent("IndicatorRepresentation.qml");
58+ if (new_page.status !== Component.Ready) {
59+ if (new_page.status === Component.Error)
60+ console.error("Error: " + new_page.errorString());
6761
68− page.pageStack.push(new_page.createObject(pages), {"indicatorProperties" : model.indicatorProperties });
62+ return;
6963 }
64+ page.pageStack.push(new_page.createObject(pages), {"indicatorProperties" : model.indicatorProperties });
7065 }
7166 }
7267 }
qml/Panel/Indicators/client/IndicatorsTree.qml+5−8
@@ -21,6 +21,7 @@ import QtQuick 2.4
2121import Ubuntu.Components 1.3
2222import Unity.Indicators 0.1 as Indicators
2323import ".."
24+import "../../../Components"
2425
2526IndicatorBase {
2627 id: root
@@ -30,14 +31,10 @@ IndicatorBase {
3031 model: root.menuModel
3132 }
3233
33− Flickable {
34+ TextArea {
3435 anchors.fill: parent
35− contentHeight: all_data.height
36− clip:true
37− Text {
38− id: all_data
39− color: "white"
40− text: printer.text
41− }
36+ anchors.margins: units.gu(0.5)
37+ id: all_data
38+ text: printer.text
4239 }
4340}
qml/Panel/Panel.qml+11−15
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013-2015 Canonical, Ltd.
2+ * Copyright (C) 2013-2016 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -100,15 +100,12 @@ Item {
100100 }
101101 height: indicators.minimizedPanelHeight
102102 hoverEnabled: true
103− onClicked: callHint.visible ? callHint.showLiveCall() : PanelState.focusMaximizedApp()
104− onDoubleClicked: PanelState.restoreClicked()
105−
106− property bool mouseWasPressed: false
107− onPressed: mouseWasPressed = containsPress
108− onMouseYChanged: {
109− if (mouseWasPressed && mouseY > panelHeight) {
110− PanelState.restoreClicked(); // restore the window when "dragging" the panel down
111− mouseWasPressed = false;
103+ onClicked: if (callHint.visible) { callHint.showLiveCall(); }
104+
105+ onPressed: {
106+ if (!callHint.visible) {
107+ // let it fall through to the window decoration of the maximized window behind, if any
108+ mouse.accepted = false;
112109 }
113110 }
114111
@@ -120,11 +117,8 @@ Item {
120117 anchors {
121118 left: parent.left
122119 top: parent.top
123− leftMargin: units.gu(1)
124− topMargin: units.gu(0.5)
125− bottomMargin: units.gu(0.5)
126120 }
127− height: indicators.minimizedPanelHeight - anchors.topMargin - anchors.bottomMargin
121+ height: indicators.minimizedPanelHeight
128122
129123 visible: ((PanelState.buttonsVisible && parent.containsMouse) || PanelState.buttonsAlwaysVisible)
130124 && !root.locked && !callHint.visible
@@ -183,13 +177,15 @@ Item {
183177 }
184178 color: "white"
185179 height: indicators.minimizedPanelHeight - anchors.topMargin - anchors.bottomMargin
186− visible: !windowControlButtons.visible && !root.locked && !callHint.visible
180+ opacity: !windowControlButtons.visible && !root.locked && !callHint.visible ? 1 : 0
181+ visible: opacity != 0
187182 verticalAlignment: Text.AlignVCenter
188183 fontSize: "medium"
189184 font.weight: PanelState.buttonsVisible ? Font.Light : Font.Medium
190185 text: PanelState.title
191186 elide: Text.ElideRight
192187 maximumLineCount: 1
188+ Behavior on opacity { UbuntuNumberAnimation {} }
193189 }
194190
195191 // TODO here would the Locally integrated menus come
qml/Shell.qml+104−12
@@ -26,6 +26,7 @@ import Unity.Connectivity 0.1
2626import Unity.Launcher 0.1
2727import GlobalShortcut 1.0 // has to be before Utils, because of WindowInputFilter
2828import GSettings 1.0
29+import ImageCache 0.1
2930import Utils 0.1
3031import Powerd 0.1
3132import SessionBroadcast 0.1
@@ -138,12 +139,49 @@ StyledItem {
138139
139140 WallpaperResolver {
140141 id: wallpaperResolver
141− width: shell.width
142+ objectName: "wallpaperResolver"
143+
144+ readonly property url defaultBackground: "file:///usr/share/backgrounds/warty-final-ubuntu.png"
145+ readonly property bool hasCustomBackground: background != defaultBackground
146+
147+ // Use a cached version of the scaled-down wallpaper (as sometimes the
148+ // image can be quite big compared to the device size, including for
149+ // our default wallpaper). We use a name=wallpaper argument here to
150+ // make sure we don't litter our cache with lots of scaled images. We
151+ // only need to bother caching one at a time.
152+ readonly property url cachedBackground: background.toString().indexOf("file:///") === 0 ? "image://unity8imagecache/" + background + "?name=wallpaper" : background
153+
154+ GSettings {
155+ id: backgroundSettings
156+ schema.id: "org.gnome.desktop.background"
157+ }
158+
159+ candidates: [
160+ AccountsService.backgroundFile,
161+ backgroundSettings.pictureUri,
162+ defaultBackground
163+ ]
142164 }
143165
144166 readonly property alias greeter: greeterLoader.item
145167
146168 function activateApplication(appId) {
169+ // Either open the app in our own session, or -- if we're acting as a
170+ // greeter -- ask the user's session to open it for us.
171+ if (shell.mode === "greeter") {
172+ activateURL("application:///" + appId + ".desktop");
173+ } else {
174+ startApp(appId);
175+ }
176+ }
177+
178+ function activateURL(url) {
179+ SessionBroadcast.requestUrlStart(AccountsService.user, url);
180+ greeter.notifyUserRequestedApp();
181+ panel.indicators.hide();
182+ }
183+
184+ function startApp(appId) {
147185 if (ApplicationManager.findApplication(appId)) {
148186 ApplicationManager.requestFocusApplication(appId);
149187 } else {
@@ -155,7 +193,7 @@ StyledItem {
155193 if (greeter.locked) {
156194 greeter.lockedApp = app;
157195 }
158− shell.activateApplication(app);
196+ startApp(app); // locked apps are always in our same session
159197 }
160198
161199 Binding {
@@ -170,7 +208,6 @@ StyledItem {
170208
171209 VolumeControl {
172210 id: volumeControl
173− indicators: panel.indicators
174211 }
175212
176213 DashCommunicator {
@@ -313,11 +350,13 @@ StyledItem {
313350 id: integratedGreeter
314351 Greeter {
315352
353+ enabled: panel.indicators.fullyClosed // hides OSK when panel is open
316354 hides: [launcher, panel.indicators]
317355 tabletMode: shell.usageScenario != "phone"
318356 launcherOffset: launcher.progress
319357 forcedUnlock: wizard.active || shell.mode === "full-shell"
320− background: wallpaperResolver.background
358+ background: wallpaperResolver.cachedBackground
359+ hasCustomBackground: wallpaperResolver.hasCustomBackground
321360 allowFingerprint: !dialogs.hasActiveDialog &&
322361 !notifications.topmostIsFullscreen &&
323362 !panel.indicators.shown
@@ -392,11 +431,15 @@ StyledItem {
392431 }
393432
394433 function showHome() {
395− greeter.notifyUserRequestedApp("unity8-dash");
434+ greeter.notifyUserRequestedApp();
396435
397− var animate = !LightDMService.greeter.active && !stages.shown
398− dash.setCurrentScope(0, animate, false)
399− ApplicationManager.requestFocusApplication("unity8-dash")
436+ if (shell.mode === "greeter") {
437+ SessionBroadcast.requestHomeShown(AccountsService.user);
438+ } else {
439+ var animate = !LightDMService.greeter.active && !stages.shown;
440+ dash.setCurrentScope(0, animate, false);
441+ ApplicationManager.requestFocusApplication("unity8-dash");
442+ }
400443 }
401444
402445 function showDash() {
@@ -492,7 +535,7 @@ StyledItem {
492535 }
493536 }
494537 onLauncherApplicationSelected: {
495− greeter.notifyUserRequestedApp(appId);
538+ greeter.notifyUserRequestedApp();
496539 shell.activateApplication(appId);
497540 }
498541 onShownChanged: {
@@ -553,7 +596,7 @@ StyledItem {
553596 objectName: "tutorial"
554597 anchors.fill: parent
555598
556− paused: callManager.hasCalls || !greeter || greeter.shown ||
599+ paused: callManager.hasCalls || !greeter || greeter.active ||
557600 wizard.active
558601 delayed: dialogs.hasActiveDialog || notifications.hasNotification ||
559602 inputMethod.visible ||
@@ -594,7 +637,7 @@ StyledItem {
594637 model: NotificationBackend.Model
595638 margin: units.gu(1)
596639 hasMouse: shell.hasMouse
597− background: wallpaperResolver.background
640+ background: wallpaperResolver.cachedBackground
598641
599642 y: topmostIsFullscreen ? 0 : panel.panelHeight
600643 height: parent.height - (topmostIsFullscreen ? 0 : panel.panelHeight)
@@ -638,7 +681,14 @@ StyledItem {
638681
639682 Connections {
640683 target: SessionBroadcast
641− onShowHome: showHome()
684+ onShowHome: if (shell.mode !== "greeter") showHome()
685+ }
686+
687+ URLDispatcher {
688+ id: urlDispatcher
689+ objectName: "urlDispatcher"
690+ active: shell.mode === "greeter"
691+ onUrlRequested: shell.activateURL(url)
642692 }
643693
644694 ItemGrabber {
@@ -657,6 +707,9 @@ StyledItem {
657707 id: cursor
658708 visible: shell.hasMouse
659709 z: itemGrabber.z + 1
710+ topBoundaryOffset: panel.panelHeight
711+
712+ confiningItem: stage.itemConfiningMouseCursor
660713
661714 property bool mouseNeverMoved: true
662715 Binding {
@@ -670,15 +723,54 @@ StyledItem {
670723
671724 height: units.gu(3)
672725
726+ readonly property var previewRectangle: stage.previewRectangle.target &&
727+ stage.previewRectangle.target.dragging ?
728+ stage.previewRectangle : null
729+
673730 onPushedLeftBoundary: {
674731 if (buttons === Qt.NoButton) {
675732 launcher.pushEdge(amount);
733+ } else if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximizedLeftRight) {
734+ previewRectangle.maximizeLeft(amount);
676735 }
677736 }
678737
679738 onPushedRightBoundary: {
680739 if (buttons === Qt.NoButton) {
681740 stage.pushRightEdge(amount);
741+ } else if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximizedLeftRight) {
742+ previewRectangle.maximizeRight(amount);
743+ }
744+ }
745+
746+ onPushedTopBoundary: {
747+ if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeMaximized) {
748+ previewRectangle.maximize(amount);
749+ }
750+ }
751+ onPushedTopLeftCorner: {
752+ if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
753+ previewRectangle.maximizeTopLeft(amount);
754+ }
755+ }
756+ onPushedTopRightCorner: {
757+ if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
758+ previewRectangle.maximizeTopRight(amount);
759+ }
760+ }
761+ onPushedBottomLeftCorner: {
762+ if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
763+ previewRectangle.maximizeBottomLeft(amount);
764+ }
765+ }
766+ onPushedBottomRightCorner: {
767+ if (buttons === Qt.LeftButton && previewRectangle && previewRectangle.target.canBeCornerMaximized) {
768+ previewRectangle.maximizeBottomRight(amount);
769+ }
770+ }
771+ onPushStopped: {
772+ if (previewRectangle) {
773+ previewRectangle.stop();
682774 }
683775 }
684776
qml/Stage/AbstractStage.qml+3
@@ -55,6 +55,9 @@ FocusScope {
5555 | Qt.InvertedPortraitOrientation
5656 | Qt.InvertedLandscapeOrientation
5757
58+ property Item itemConfiningMouseCursor: null
59+
60+
5861 signal stageAboutToBeUnloaded
5962 signal itemSnapshotRequested(Item item)
6063
qml/Stage/DecoratedWindow.qml+50
@@ -63,6 +63,11 @@ FocusScope {
6363 readonly property int heightIncrement: !counterRotate ? applicationWindow.heightIncrement : applicationWindow.widthIncrement
6464
6565 property alias overlayShown: decoration.overlayShown
66+ property alias stageWidth: moveHandler.stageWidth
67+ property alias stageHeight: moveHandler.stageHeight
68+ readonly property alias dragging: moveHandler.dragging
69+
70+ readonly property Item clientAreaItem: applicationWindow
6671
6772 signal closeClicked()
6873 signal maximizeClicked()
@@ -70,6 +75,7 @@ FocusScope {
7075 signal maximizeVerticallyClicked()
7176 signal minimizeClicked()
7277 signal decorationPressed()
78+ signal decorationReleased()
7379
7480 QtObject {
7581 id: d
@@ -145,6 +151,20 @@ FocusScope {
145151 onMaximizeVerticallyClicked: { root.decorationPressed(); root.maximizeVerticallyClicked(); }
146152 onMinimizeClicked: root.minimizeClicked();
147153 onPressed: root.decorationPressed();
154+
155+ onPressedChanged: moveHandler.handlePressedChanged(pressed, pressedButtons, mouseX, mouseY)
156+ onPositionChanged: moveHandler.handlePositionChanged(mouse)
157+ onReleased: {
158+ root.decorationReleased();
159+ moveHandler.handleReleased();
160+ }
161+ }
162+
163+ MoveHandler {
164+ id: moveHandler
165+ objectName: "moveHandler"
166+ target: root.parent
167+ buttonsWidth: decoration.buttonsWidth
148168 }
149169
150170 ApplicationWindow {
@@ -200,4 +220,34 @@ FocusScope {
200220 opacity: root.darkening && !root.showHighlight ? 0.05 : 0
201221 Behavior on opacity { UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration } }
202222 }
223+
224+ MouseArea {
225+ anchors.fill: applicationWindow
226+ acceptedButtons: Qt.LeftButton
227+ hoverEnabled: true
228+ property bool dragging: false
229+ onPressed: {
230+ if (mouse.button == Qt.LeftButton && mouse.modifiers == Qt.AltModifier) {
231+ root.decorationPressed(); // to raise it
232+ moveHandler.handlePressedChanged(true, Qt.LeftButton, mouse.x, mouse.y);
233+ dragging = true;
234+ mouse.accepted = true;
235+ } else {
236+ mouse.accepted = false;
237+ }
238+ }
239+ onPositionChanged: {
240+ if (dragging) {
241+ moveHandler.handlePositionChanged(mouse);
242+ }
243+ }
244+ onReleased: {
245+ if (dragging) {
246+ moveHandler.handlePressedChanged(false, Qt.LeftButton);
247+ root.decorationReleased(); // commits the fake preview max rectangle
248+ moveHandler.handleReleased();
249+ dragging = false;
250+ }
251+ }
252+ }
203253}
qml/Stage/FakeMaximizeDelegate.qmlnew file+235
@@ -0,0 +1,235 @@
1+/*
2+ * Copyright (C) 2016 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+import Ubuntu.Components 1.3
19+import Utils 0.1 // For EdgeBarrierSettings
20+import "../Components/PanelState"
21+
22+Rectangle {
23+ id: fakeRectangle
24+ visible: opacity > 0 && target && !target.anyMaximized // we go from 0.2 to 0.5
25+ enabled: visible
26+ color: "#ffffff"
27+ border.width: units.dp(2)
28+ border.color: "#99ffffff"
29+
30+ scale: progress > 0 && progress <= hintThreshold ? MathUtils.projectValue(progress, 0.0, 1.0, 1, 2) : 1
31+ opacity: progress > 0 ? MathUtils.projectValue(progress, 0.0, 1.0, 0.2, 0.5) : 0
32+
33+ property int edge: -1 // Item.TransformOrigin
34+ property var target // appDelegate
35+ property int leftMargin
36+ property real appContainerWidth
37+ property real appContainerHeight
38+
39+ readonly property real hintThreshold: 0.1
40+
41+ onEdgeChanged: print("***********+ edge changed", edge)
42+ // Edge push progress
43+ // Value range is [0.0, 1.0]
44+ readonly property real progress: priv.directProgress != -1 ? priv.directProgress : priv.accumulatedProgress
45+
46+ signal passed(int origin)
47+
48+ QtObject {
49+ id: priv
50+
51+ readonly property real accumulatedProgress: MathUtils.clamp(accumulatedPush / EdgeBarrierSettings.pushThreshold, 0.0, 1.0)
52+ property real directProgress: -1
53+ property real accumulatedPush: 0
54+
55+ function push(amount) {
56+ if (accumulatedPush === EdgeBarrierSettings.pushThreshold) {
57+ // NO-OP
58+ return;
59+ }
60+
61+ if (accumulatedPush + amount > EdgeBarrierSettings.pushThreshold) {
62+ accumulatedPush = EdgeBarrierSettings.pushThreshold;
63+ } else {
64+ accumulatedPush += amount;
65+ }
66+
67+ if (accumulatedPush === EdgeBarrierSettings.pushThreshold) {
68+ passed(edge);
69+ // commit(); // NB: uncomment to have automatic maximization on 100% progress
70+ }
71+ }
72+
73+ function setup(edge) {
74+ if (edge !== fakeRectangle.edge) {
75+ stop(); // a different edge, start anew
76+ }
77+ fakeRectangle.x = target.x;
78+ fakeRectangle.y = target.y;
79+ fakeRectangle.width = target.width;
80+ fakeRectangle.height = target.height;
81+ fakeRectangle.edge = edge;
82+ fakeRectangle.transformOrigin = edge;
83+ }
84+
85+ function processAnimation(amount, animation, isProgress) {
86+ if (isProgress) {
87+ priv.directProgress = amount;
88+ } else {
89+ priv.directProgress = -1;
90+ priv.push(amount);
91+ }
92+
93+ if (progress > hintThreshold) { // above 10% we start the full preview animation
94+ animation.start();
95+ }
96+ }
97+ }
98+
99+ function commit() {
100+ print("comitting!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
101+ if (progress > hintThreshold && edge != -1) {
102+ if (edge == Item.Top) {
103+ target.maximize();
104+ } else if (edge == Item.Left) {
105+ target.maximizeLeft();
106+ } else if (edge == Item.Right) {
107+ target.maximizeRight();
108+ } else if (edge == Item.TopLeft) {
109+ target.maximizeTopLeft();
110+ } else if (edge == Item.TopRight) {
111+ target.maximizeTopRight();
112+ } else if (edge == Item.BottomLeft) {
113+ target.maximizeBottomLeft();
114+ } else if (edge == Item.BottomRight) {
115+ target.maximizeBottomRight();
116+ }
117+ } else {
118+ stop();
119+ }
120+ }
121+
122+ function stop() {
123+ priv.accumulatedPush = 0;
124+ priv.directProgress = -1;
125+ edge = -1;
126+ }
127+
128+ function maximize(amount, isProgress) {
129+ if (fakeRectangle.edge != Item.Top) {
130+ priv.setup(Item.Top);
131+ }
132+ priv.processAnimation(amount, fakeMaximizeAnimation, isProgress);
133+ }
134+
135+ function maximizeLeft(amount, isProgress) {
136+ if (fakeRectangle.edge != Item.Left) {
137+ priv.setup(Item.Left);
138+ }
139+ priv.processAnimation(amount, fakeMaximizeLeftAnimation, isProgress);
140+ }
141+
142+ function maximizeRight(amount, isProgress) {
143+ if (fakeRectangle.edge != Item.Right) {
144+ priv.setup(Item.Right);
145+ }
146+ priv.processAnimation(amount, fakeMaximizeRightAnimation, isProgress);
147+ }
148+
149+ function maximizeTopLeft(amount, isProgress) {
150+ if (fakeRectangle.edge != Item.TopLeft) {
151+ priv.setup(Item.TopLeft);
152+ }
153+ priv.processAnimation(amount, fakeMaximizeTopLeftAnimation, isProgress);
154+ }
155+
156+ function maximizeTopRight(amount, isProgress) {
157+ if (fakeRectangle.edge != Item.TopRight) {
158+ priv.setup(Item.TopRight);
159+ }
160+ priv.processAnimation(amount, fakeMaximizeTopRightAnimation, isProgress);
161+ }
162+
163+ function maximizeBottomLeft(amount, isProgress) {
164+ if (fakeRectangle.edge != Item.BottomLeft) {
165+ priv.setup(Item.BottomLeft);
166+ }
167+ priv.processAnimation(amount, fakeMaximizeBottomLeftAnimation, isProgress);
168+ }
169+
170+ function maximizeBottomRight(amount, isProgress) {
171+ if (fakeRectangle.edge != Item.BottomRight) {
172+ priv.setup(Item.BottomRight);
173+ }
174+ priv.processAnimation(amount, fakeMaximizeBottomRightAnimation, isProgress);
175+ }
176+
177+ Behavior on opacity { UbuntuNumberAnimation { duration: UbuntuAnimation.BriskDuration } }
178+ Behavior on scale { UbuntuNumberAnimation { duration: UbuntuAnimation.BriskDuration } }
179+
180+ ParallelAnimation {
181+ id: fakeMaximizeAnimation
182+ UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: leftMargin }
183+ UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: PanelState.panelHeight }
184+ UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: appContainerWidth - leftMargin }
185+ UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: appContainerHeight }
186+ }
187+
188+ ParallelAnimation {
189+ id: fakeMaximizeLeftAnimation
190+ UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: leftMargin }
191+ UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: PanelState.panelHeight }
192+ UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
193+ UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: appContainerHeight - PanelState.panelHeight }
194+ }
195+
196+ ParallelAnimation {
197+ id: fakeMaximizeRightAnimation
198+ UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth + leftMargin)/2 }
199+ UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: PanelState.panelHeight }
200+ UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
201+ UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: appContainerHeight - PanelState.panelHeight }
202+ }
203+
204+ ParallelAnimation {
205+ id: fakeMaximizeTopLeftAnimation
206+ UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: leftMargin }
207+ UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: PanelState.panelHeight }
208+ UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
209+ UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: (appContainerHeight - PanelState.panelHeight)/2 }
210+ }
211+
212+ ParallelAnimation {
213+ id: fakeMaximizeTopRightAnimation
214+ UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth + leftMargin)/2 }
215+ UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: PanelState.panelHeight }
216+ UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
217+ UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: (appContainerHeight - PanelState.panelHeight)/2 }
218+ }
219+
220+ ParallelAnimation {
221+ id: fakeMaximizeBottomLeftAnimation
222+ UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: leftMargin }
223+ UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: (appContainerHeight + PanelState.panelHeight)/2 }
224+ UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
225+ UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: appContainerHeight/2 }
226+ }
227+
228+ ParallelAnimation {
229+ id: fakeMaximizeBottomRightAnimation
230+ UbuntuNumberAnimation { target: fakeRectangle; properties: "x"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth + leftMargin)/2 }
231+ UbuntuNumberAnimation { target: fakeRectangle; properties: "y"; duration: UbuntuAnimation.BriskDuration; to: (appContainerHeight + PanelState.panelHeight)/2 }
232+ UbuntuNumberAnimation { target: fakeRectangle; properties: "width"; duration: UbuntuAnimation.BriskDuration; to: (appContainerWidth - leftMargin)/2 }
233+ UbuntuNumberAnimation { target: fakeRectangle; properties: "height"; duration: UbuntuAnimation.BriskDuration; to: appContainerHeight/2 }
234+ }
235+}
qml/Stage/MoveHandler.qmlnew file+179
@@ -0,0 +1,179 @@
1+/*
2+ * Copyright (C) 2014-2016 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+import Unity.Application 0.1 // For Mir singleton
19+import Ubuntu.Components 1.3
20+import Utils 0.1
21+import "../Components"
22+import "../Components/PanelState"
23+
24+QtObject {
25+ id: root
26+
27+ property Item target // appDelegate
28+ property int stageWidth
29+ property int stageHeight
30+ property real buttonsWidth: 0
31+
32+ readonly property bool dragging: priv.dragging
33+
34+ signal fakeMaximizeAnimationRequested(real amount)
35+ signal fakeMaximizeLeftAnimationRequested(real amount)
36+ signal fakeMaximizeRightAnimationRequested(real amount)
37+ signal fakeMaximizeTopLeftAnimationRequested(real amount)
38+ signal fakeMaximizeTopRightAnimationRequested(real amount)
39+ signal fakeMaximizeBottomLeftAnimationRequested(real amount)
40+ signal fakeMaximizeBottomRightAnimationRequested(real amount)
41+ signal stopFakeAnimation()
42+
43+ property QtObject priv: QtObject {
44+ property real distanceX
45+ property real distanceY
46+ property bool dragging
47+
48+ readonly property int triggerArea: units.gu(8)
49+ property bool nearLeftEdge: target.maximizedLeft
50+ property bool nearTopEdge: target.maximized
51+ property bool nearRightEdge: target.maximizedRight
52+ property bool nearTopLeftCorner: target.maximizedTopLeft
53+ property bool nearTopRightCorner: target.maximizedTopRight
54+ property bool nearBottomLeftCorner: target.maximizedBottomLeft
55+ property bool nearBottomRightCorner: target.maximizedBottomRight
56+
57+ function resetEdges() {
58+ nearLeftEdge = false;
59+ nearRightEdge = false;
60+ nearTopEdge = false;
61+ nearTopLeftCorner = false;
62+ nearTopRightCorner = false;
63+ nearBottomLeftCorner = false;
64+ nearBottomRightCorner = false;
65+ }
66+
67+ // return the progress of mouse pointer movement from 0 to 1 within a corner square of the screen
68+ // 0 -> before the mouse enters the square
69+ // 1 -> mouse is in the outer corner
70+ // a is the corner, b is the mouse pos
71+ function progressInCorner(ax, ay, bx, by) {
72+ // distance of two points, a and b, in pixels
73+ var distance = Math.sqrt(Math.pow(bx-ax, 2) + Math.pow(by-ay, 2));
74+ // length of the triggerArea square diagonal
75+ var diagLength = Math.sqrt(2 * priv.triggerArea * priv.triggerArea);
76+ var ratio = 1 - (distance / diagLength);
77+ return bx > 0 && bx <= stageWidth && by > 0 && by <= stageHeight ? ratio : 1; // everything "outside" of our square from the center is 1
78+ }
79+ property real progress: 0
80+ }
81+
82+ function handlePressedChanged(pressed, pressedButtons, mouseX, mouseY) {
83+ if (pressed && pressedButtons === Qt.LeftButton) {
84+ var pos = mapToItem(target, mouseX, mouseY);
85+ if (target.anyMaximized) {
86+ // keep distanceX relative to the normal window width minus the window control buttons (+spacing)
87+ // so that dragging it back doesn't make the window jump around to weird positions, away from the mouse pointer
88+ priv.distanceX = MathUtils.clampAndProject(pos.x, 0, target.width, buttonsWidth, target.resizeArea.normalWidth);
89+ priv.distanceY = MathUtils.clampAndProject(pos.y, 0, target.height, 0, target.resizeArea.normalHeight);
90+ } else {
91+ priv.distanceX = pos.x;
92+ priv.distanceY = pos.y;
93+ }
94+
95+ priv.dragging = true;
96+ } else {
97+ priv.dragging = false;
98+ Mir.cursorName = "";
99+ }
100+ }
101+
102+ function handlePositionChanged(mouse, sensingPoints) {
103+ if (priv.dragging) {
104+ Mir.cursorName = "grabbing";
105+
106+ if (target.anyMaximized) { // restore from maximized when dragging away from edges/corners
107+ priv.progress = 0;
108+ target.restore(false, WindowStateStorage.WindowStateNormal);
109+ }
110+
111+ var pos = mapToItem(target.parent, mouse.x, mouse.y);
112+ // Use integer coordinate values to ensure that target is left in a pixel-aligned
113+ // position. Mouse movement could have subpixel precision, yielding a fractional
114+ // mouse position.
115+ target.requestedX = Math.round(pos.x - priv.distanceX);
116+ target.requestedY = Math.round(Math.max(pos.y - priv.distanceY, PanelState.panelHeight));
117+
118+ if (sensingPoints) { // edge/corner detection when dragging via the touch overlay
119+ if (sensingPoints.topLeft.x < priv.triggerArea && sensingPoints.topLeft.y < PanelState.panelHeight + priv.triggerArea
120+ && target.canBeCornerMaximized) { // top left
121+ priv.progress = priv.progressInCorner(0, PanelState.panelHeight, sensingPoints.topLeft.x, sensingPoints.topLeft.y);
122+ priv.resetEdges();
123+ priv.nearTopLeftCorner = true;
124+ root.fakeMaximizeTopLeftAnimationRequested(priv.progress);
125+ } else if (sensingPoints.topRight.x > stageWidth - priv.triggerArea && sensingPoints.topRight.y < PanelState.panelHeight + priv.triggerArea
126+ && target.canBeCornerMaximized) { // top right
127+ priv.progress = priv.progressInCorner(stageWidth, PanelState.panelHeight, sensingPoints.topRight.x, sensingPoints.topRight.y);
128+ priv.resetEdges();
129+ priv.nearTopRightCorner = true;
130+ root.fakeMaximizeTopRightAnimationRequested(priv.progress);
131+ } else if (sensingPoints.bottomLeft.x < priv.triggerArea && sensingPoints.bottomLeft.y > stageHeight - priv.triggerArea
132+ && target.canBeCornerMaximized) { // bottom left
133+ priv.progress = priv.progressInCorner(0, stageHeight, sensingPoints.bottomLeft.x, sensingPoints.bottomLeft.y);
134+ priv.resetEdges();
135+ priv.nearBottomLeftCorner = true;
136+ root.fakeMaximizeBottomLeftAnimationRequested(priv.progress);
137+ } else if (sensingPoints.bottomRight.x > stageWidth - priv.triggerArea && sensingPoints.bottomRight.y > stageHeight - priv.triggerArea
138+ && target.canBeCornerMaximized) { // bottom right
139+ priv.progress = priv.progressInCorner(stageWidth, stageHeight, sensingPoints.bottomRight.x, sensingPoints.bottomRight.y);
140+ priv.resetEdges();
141+ priv.nearBottomRightCorner = true;
142+ root.fakeMaximizeBottomRightAnimationRequested(priv.progress);
143+ } else if (sensingPoints.left.x < priv.triggerArea && target.canBeMaximizedLeftRight) { // left
144+ priv.progress = MathUtils.clampAndProject(sensingPoints.left.x, priv.triggerArea, 0, 0, 1);
145+ priv.resetEdges();
146+ priv.nearLeftEdge = true;
147+ root.fakeMaximizeLeftAnimationRequested(priv.progress);
148+ } else if (sensingPoints.right.x > stageWidth - priv.triggerArea && target.canBeMaximizedLeftRight) { // right
149+ priv.progress = MathUtils.clampAndProject(sensingPoints.right.x, stageWidth - priv.triggerArea, stageWidth, 0, 1);
150+ priv.resetEdges();
151+ priv.nearRightEdge = true;
152+ root.fakeMaximizeRightAnimationRequested(priv.progress);
153+ } else if (sensingPoints.top.y < PanelState.panelHeight + priv.triggerArea && target.canBeMaximized) { // top
154+ priv.progress = MathUtils.clampAndProject(sensingPoints.top.y, PanelState.panelHeight + priv.triggerArea, 0, 0, 1);
155+ priv.resetEdges();
156+ priv.nearTopEdge = true;
157+ root.fakeMaximizeAnimationRequested(priv.progress);
158+ } else if (priv.nearLeftEdge || priv.nearRightEdge || priv.nearTopEdge || priv.nearTopLeftCorner || priv.nearTopRightCorner ||
159+ priv.nearBottomLeftCorner || priv.nearBottomRightCorner) {
160+ priv.progress = 0;
161+ priv.resetEdges();
162+ root.stopFakeAnimation();
163+ }
164+ }
165+ }
166+ }
167+
168+ function handleReleased(touchMode) {
169+ if (touchMode) {
170+ priv.progress = 0;
171+ priv.resetEdges();
172+ }
173+ if ((target.state == "normal" || target.state == "restored") && priv.progress == 0) {
174+ // save the x/y to restore to
175+ target.restoredX = target.x;
176+ target.restoredY = target.y;
177+ }
178+ }
179+}
qml/Stage/Splash.qml+3−6
@@ -119,6 +119,7 @@ Item {
119119 }
120120
121121 Label {
122+ id: titleLabel
122123 text: root.title
123124 anchors.horizontalCenter: parent.horizontalCenter
124125 anchors.top: iconShape.bottom
@@ -137,12 +138,8 @@ Item {
137138
138139 ActivityIndicator {
139140 id: spinner
140− anchors.centerIn: header.visible ? parent : undefined
141− anchors.verticalCenterOffset: header.visible ? header.height / 2 : 0
142−
143− anchors.horizontalCenter: header.visible ? undefined : parent.horizontalCenter
144− anchors.bottom: header.visible ? undefined : parent.bottom
145− anchors.bottomMargin: header.visible ? 0 : units.gu(12)
141+ anchors.centerIn: parent
142+ anchors.verticalCenterOffset: header.visible ? header.height / 2 : Math.max(titleLabel.y / 2, units.gu(8))
146143 }
147144
148145 MouseArea {
qml/Stage/Stage.qml+161−77
@@ -49,6 +49,9 @@ AbstractStage {
4949 // Used by TutorialRight
5050 property bool spreadShown: state == "spread"
5151
52+ // used by the snap windows (edge maximize) feature
53+ readonly property alias previewRectangle: fakeRectangle
54+
5255 mainApp: priv.focusedAppDelegate ? priv.focusedAppDelegate.application : null
5356
5457 // application windows never rotate independently
@@ -82,6 +85,10 @@ AbstractStage {
8285
8386 onAltTabPressedChanged: priv.goneToSpread = altTabPressed
8487
88+ itemConfiningMouseCursor: !spreadShown && priv.focusedAppDelegate && priv.focusedAppDelegate.surface &&
89+ priv.focusedAppDelegate.surface.confinesMousePointer ?
90+ priv.focusedAppDelegate.clientAreaItem : null;
91+
8592 GlobalShortcut {
8693 id: showSpreadShortcut
8794 shortcut: Qt.MetaModifier|Qt.Key_W
@@ -98,28 +105,27 @@ AbstractStage {
98105 id: maximizeWindowShortcut
99106 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Up
100107 onTriggered: priv.focusedAppDelegate.maximize()
101− active: priv.focusedAppDelegate !== null
108+ active: priv.focusedAppDelegate !== null && priv.focusedAppDelegate.canBeMaximized
102109 }
103110
104111 GlobalShortcut {
105112 id: maximizeWindowLeftShortcut
106113 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Left
107114 onTriggered: priv.focusedAppDelegate.maximizeLeft()
108− active: priv.focusedAppDelegate !== null
115+ active: priv.focusedAppDelegate !== null && priv.focusedAppDelegate.canBeMaximizedLeftRight
109116 }
110117
111118 GlobalShortcut {
112119 id: maximizeWindowRightShortcut
113120 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Right
114121 onTriggered: priv.focusedAppDelegate.maximizeRight()
115− active: priv.focusedAppDelegate !== null
122+ active: priv.focusedAppDelegate !== null && priv.focusedAppDelegate.canBeMaximizedLeftRight
116123 }
117124
118125 GlobalShortcut {
119126 id: minimizeRestoreShortcut
120127 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Down
121− onTriggered: priv.focusedAppDelegate.maximized || priv.focusedAppDelegate.maximizedLeft || priv.focusedAppDelegate.maximizedRight ||
122− priv.focusedAppDelegate.maximizedHorizontally || priv.focusedAppDelegate.maximizedVertically
128+ onTriggered: priv.focusedAppDelegate.anyMaximized
123129 ? priv.focusedAppDelegate.restoreFromMaximized() : priv.focusedAppDelegate.minimize()
124130 active: priv.focusedAppDelegate !== null
125131 }
@@ -274,7 +280,6 @@ AbstractStage {
274280 onRestoreClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.restoreFromMaximized(); } }
275281 onFocusMaximizedApp: {
276282 if (priv.foregroundMaximizedAppDelegate) {
277− print("***** focusing because of Panel request", model.application.appId)
278283 priv.foregroundMaximizedAppDelegate.focus = true;
279284 }
280285 }
@@ -436,12 +441,10 @@ AbstractStage {
436441 anchors.fill: parent
437442 focus: root.state !== "spread"
438443
439− CrossFadeImage {
444+ Wallpaper {
440445 id: wallpaper
441446 anchors.fill: parent
442447 source: root.background
443− sourceSize { height: root.height; width: root.width }
444− fillMode: Image.PreserveAspectCrop
445448 }
446449
447450 Spread {
@@ -602,7 +605,7 @@ AbstractStage {
602605 value: appDelegate.requestedY -
603606 Math.min(appDelegate.requestedY - PanelState.panelHeight,
604607 Math.max(0, priv.virtualKeyboardHeight - (appContainer.height - (appDelegate.requestedY + appDelegate.height))))
605− when: root.oskEnabled && appDelegate.focus && appDelegate.state == "normal"
608+ when: root.oskEnabled && appDelegate.focus && (appDelegate.state == "normal" || appDelegate.state == "restored")
606609 && SurfaceManager.inputMethodSurface
607610 && SurfaceManager.inputMethodSurface.state != Mir.HiddenState
608611 && SurfaceManager.inputMethodSurface.state != Mir.MinimizedState
@@ -616,7 +619,7 @@ AbstractStage {
616619 onShellOrientationAngleChanged: {
617620 // at this point decoratedWindow.surfaceOrientationAngle is the old shellOrientationAngle
618621 if (application && application.rotatesWindowContents) {
619− if (state == "normal") {
622+ if (state == "normal" || state == "restored") {
620623 var angleDiff = decoratedWindow.surfaceOrientationAngle - shellOrientationAngle;
621624 angleDiff = (360 + angleDiff) % 360;
622625 if (angleDiff === 90 || angleDiff === 270) {
@@ -644,14 +647,29 @@ AbstractStage {
644647 readonly property alias widthIncrement: decoratedWindow.widthIncrement
645648 readonly property alias heightIncrement: decoratedWindow.heightIncrement
646649
647− readonly property bool maximized: windowState & WindowStateStorage.WindowStateMaximized
648− readonly property bool maximizedLeft: windowState & WindowStateStorage.WindowStateMaximizedLeft
649− readonly property bool maximizedRight: windowState & WindowStateStorage.WindowStateMaximizedRight
650− readonly property bool maximizedHorizontally: windowState & WindowStateStorage.WindowStateMaximizedHorizontally
651− readonly property bool maximizedVertically: windowState & WindowStateStorage.WindowStateMaximizedVertically
650+ readonly property bool maximized: windowState === WindowStateStorage.WindowStateMaximized
651+ readonly property bool maximizedLeft: windowState === WindowStateStorage.WindowStateMaximizedLeft
652+ readonly property bool maximizedRight: windowState === WindowStateStorage.WindowStateMaximizedRight
653+ readonly property bool maximizedHorizontally: windowState === WindowStateStorage.WindowStateMaximizedHorizontally
654+ readonly property bool maximizedVertically: windowState === WindowStateStorage.WindowStateMaximizedVertically
655+ readonly property bool maximizedTopLeft: windowState === WindowStateStorage.WindowStateMaximizedTopLeft
656+ readonly property bool maximizedTopRight: windowState === WindowStateStorage.WindowStateMaximizedTopRight
657+ readonly property bool maximizedBottomLeft: windowState === WindowStateStorage.WindowStateMaximizedBottomLeft
658+ readonly property bool maximizedBottomRight: windowState === WindowStateStorage.WindowStateMaximizedBottomRight
659+ readonly property bool anyMaximized: maximized || maximizedLeft || maximizedRight || maximizedHorizontally || maximizedVertically ||
660+ maximizedTopLeft || maximizedTopRight || maximizedBottomLeft || maximizedBottomRight
661+
652662 readonly property bool minimized: windowState & WindowStateStorage.WindowStateMinimized
653663 readonly property bool fullscreen: surface && surface.state === Mir.FullscreenState;
654664
665+ readonly property bool canBeMaximized: canBeMaximizedHorizontally && canBeMaximizedVertically
666+ readonly property bool canBeMaximizedLeftRight: (maximumWidth == 0 || maximumWidth >= appContainer.width/2) &&
667+ (maximumHeight == 0 || maximumHeight >= appContainer.height)
668+ readonly property bool canBeCornerMaximized: (maximumWidth == 0 || maximumWidth >= appContainer.width/2) &&
669+ (maximumHeight == 0 || maximumHeight >= appContainer.height/2)
670+ readonly property bool canBeMaximizedHorizontally: maximumWidth == 0 || maximumWidth >= appContainer.width
671+ readonly property bool canBeMaximizedVertically: maximumHeight == 0 || maximumHeight >= appContainer.height
672+
655673 property int windowState: WindowStateStorage.WindowStateNormal
656674 property bool animationsEnabled: true
657675 property alias title: decoratedWindow.title
@@ -669,12 +687,17 @@ AbstractStage {
669687 readonly property var surface: model.surface
670688 readonly property alias resizeArea: resizeArea
671689 readonly property alias focusedSurface: decoratedWindow.focusedSurface
690+ readonly property bool dragging: touchControls.overlayShown ? touchControls.dragging : decoratedWindow.dragging
672691
673692 readonly property string appId: model.application.appId
674693 readonly property bool isDash: model.application.appId == "unity8-dash"
694+ readonly property alias clientAreaItem: decoratedWindow.clientAreaItem
675695
676696 function claimFocus() {
677697 print("(claimfocus) focusing app", priv.sideStageDelegate, appDelegate, sideStage.shown)
698+ if (root.state == "spread") {
699+ priv.goneToSpread = false;
700+ }
678701 if (root.mode == "windowed" && minimized) {
679702 restore(true)
680703 }
@@ -682,6 +705,7 @@ AbstractStage {
682705 sideStage.show();
683706 }
684707 appDelegate.focus = true;
708+ appDelegate.restore(true /* animated */, appDelegate.windowState);
685709 }
686710 Connections {
687711 target: model.surface
@@ -803,28 +827,34 @@ AbstractStage {
803827 animationsEnabled = (animated === undefined) || animated;
804828 windowState = WindowStateStorage.WindowStateMaximizedVertically;
805829 }
830+ function maximizeTopLeft(animated) {
831+ animationsEnabled = (animated === undefined) || animated;
832+ windowState = WindowStateStorage.WindowStateMaximizedTopLeft;
833+ }
834+ function maximizeTopRight(animated) {
835+ animationsEnabled = (animated === undefined) || animated;
836+ windowState = WindowStateStorage.WindowStateMaximizedTopRight;
837+ }
838+ function maximizeBottomLeft(animated) {
839+ animationsEnabled = (animated === undefined) || animated;
840+ windowState = WindowStateStorage.WindowStateMaximizedBottomLeft;
841+ }
842+ function maximizeBottomRight(animated) {
843+ animationsEnabled = (animated === undefined) || animated;
844+ windowState = WindowStateStorage.WindowStateMaximizedBottomRight;
845+ }
806846 function minimize(animated) {
807847 animationsEnabled = (animated === undefined) || animated;
808848 windowState |= WindowStateStorage.WindowStateMinimized; // add the minimized bit
809849 }
810850 function restoreFromMaximized(animated) {
811851 animationsEnabled = (animated === undefined) || animated;
812− windowState = WindowStateStorage.WindowStateNormal;
852+ windowState = WindowStateStorage.WindowStateRestored;
813853 }
814− function restore(animated) {
854+ function restore(animated,state) {
815855 animationsEnabled = (animated === undefined) || animated;
856+ windowState = state || WindowStateStorage.WindowStateRestored;
816857 windowState &= ~WindowStateStorage.WindowStateMinimized; // clear the minimized bit
817− if (maximized)
818− maximize();
819− else if (maximizedLeft)
820− maximizeLeft();
821− else if (maximizedRight)
822− maximizeRight();
823− else if (maximizedHorizontally)
824− maximizeHorizontally();
825− else if (maximizedVertically)
826− maximizeVertically();
827−
828858 print("***** focusing because of window restore", model.application.appId)
829859 focus = true;
830860 }
@@ -961,6 +991,11 @@ AbstractStage {
961991 breakPoint: spreadItem.rightEdgeBreakPoint
962992 }
963993
994+ // unlike requestedX/Y, this is the last known grab position before being pushed against edges/corners
995+ // when restoring, the window should return to these, not to the place where it was dropped near the edge
996+ property real restoredX
997+ property real restoredY
998+
964999 states: [
9651000 State {
9661001 name: "spread"; when: root.state == "spread"
@@ -1111,8 +1146,8 @@ AbstractStage {
11111146 name: "fullscreen"; when: surface ? surface.state === Mir.FullscreenState : application.fullscreen && !appDelegate.minimized
11121147 PropertyChanges {
11131148 target: appDelegate;
1114− requestedX: rotation == 0 ? 0 : (parent.width - width) / 2 + (shellOrientationAngle == 90 ? 0 : PanelState.panelHeight)
1115− requestedY: rotation == 0 ? 0 : (parent.height - height) / 2
1149+ requestedX: rotation == 0 ? 0 : (parent.width - width) / 2 + (shellOrientationAngle == 90 ? -PanelState.panelHeight : PanelState.panelHeight)
1150+ requestedY: rotation == 0 ? -PanelState.panelHeight : (parent.height - height) / 2
11161151 requestedWidth: appContainer.width;
11171152 requestedHeight: appContainer.height;
11181153 }
@@ -1134,8 +1169,24 @@ AbstractStage {
11341169 PropertyChanges { target: resizeArea; enabled: true }
11351170 PropertyChanges { target: decoratedWindow; shadowOpacity: .3}
11361171 },
1172+ State {
1173+ name: "restored";
1174+ when: appDelegate.windowState == WindowStateStorage.WindowStateRestored
1175+ extend: "normal"
1176+ PropertyChanges {
1177+ target: appDelegate;
1178+ requestedX: restoredX;
1179+ requestedY: restoredY;
1180+ }
1181+ },
1182+ State {
1183+ name: "semiMaximized"
1184+ PropertyChanges { target: touchControls; enabled: true }
1185+ PropertyChanges { target: decoratedWindow; shadowOpacity: .3 }
1186+ },
11371187 State {
11381188 name: "maximizedLeft"; when: appDelegate.maximizedLeft && !appDelegate.minimized
1189+ extend: "semiMaximized"
11391190 PropertyChanges {
11401191 target: appDelegate
11411192 requestedX: root.leftMargin
@@ -1143,27 +1194,56 @@ AbstractStage {
11431194 requestedWidth: (appContainer.width - root.leftMargin)/2
11441195 requestedHeight: appContainer.height - PanelState.panelHeight
11451196 }
1146− PropertyChanges {
1147− target: decoratedWindow
1148− shadowOpacity: .3
1149− }
11501197 },
11511198 State {
11521199 name: "maximizedRight"; when: appDelegate.maximizedRight && !appDelegate.minimized
1200+ extend: "maximizedLeft"
11531201 PropertyChanges {
11541202 target: appDelegate;
11551203 requestedX: (appContainer.width + root.leftMargin)/2
1204+ }
1205+ },
1206+ State {
1207+ name: "maximizedTopLeft"; when: appDelegate.maximizedTopLeft && !appDelegate.minimized
1208+ extend: "semiMaximized"
1209+ PropertyChanges {
1210+ target: appDelegate
1211+ requestedX: root.leftMargin
11561212 requestedY: PanelState.panelHeight
11571213 requestedWidth: (appContainer.width - root.leftMargin)/2
1158− requestedHeight: appContainer.height - PanelState.panelHeight
1214+ requestedHeight: (appContainer.height - PanelState.panelHeight)/2
11591215 }
1216+ },
1217+ State {
1218+ name: "maximizedTopRight"; when: appDelegate.maximizedTopRight && !appDelegate.minimized
1219+ extend: "maximizedTopLeft"
11601220 PropertyChanges {
1161− target: decoratedWindow
1162− shadowOpacity: .3
1221+ target: appDelegate
1222+ requestedX: (appContainer.width + root.leftMargin)/2
1223+ }
1224+ },
1225+ State {
1226+ name: "maximizedBottomLeft"; when: appDelegate.maximizedBottomLeft && !appDelegate.minimized
1227+ extend: "semiMaximized"
1228+ PropertyChanges {
1229+ target: appDelegate
1230+ requestedX: root.leftMargin
1231+ requestedY: (appContainer.height + PanelState.panelHeight)/2
1232+ requestedWidth: (appContainer.width - root.leftMargin)/2
1233+ requestedHeight: appContainer.height/2
1234+ }
1235+ },
1236+ State {
1237+ name: "maximizedBottomRight"; when: appDelegate.maximizedBottomRight && !appDelegate.minimized
1238+ extend: "maximizedBottomLeft"
1239+ PropertyChanges {
1240+ target: appDelegate
1241+ requestedX: (appContainer.width + root.leftMargin)/2
11631242 }
11641243 },
11651244 State {
11661245 name: "maximizedHorizontally"; when: appDelegate.maximizedHorizontally && !appDelegate.minimized
1246+ extend: "semiMaximized"
11671247 PropertyChanges {
11681248 target: appDelegate;
11691249 requestedX: root.leftMargin
@@ -1171,10 +1251,7 @@ AbstractStage {
11711251 requestedWidth: appContainer.width - root.leftMargin
11721252 requestedHeight: appDelegate.windowedHeight
11731253 }
1174− PropertyChanges {
1175− target: decoratedWindow;
1176− shadowOpacity: .3
1177− }
1254+ PropertyChanges { target: appDelegate; requestedX: root.leftMargin; requestedY: requestedY; requestedWidth: appContainer.width - root.leftMargin }
11781255 },
11791256 State {
11801257 name: "maximizedVertically"; when: appDelegate.maximizedVertically && !appDelegate.minimized
@@ -1185,10 +1262,7 @@ AbstractStage {
11851262 requestedWidth: appDelegate.windowedWidth
11861263 requestedHeight: appContainer.height - PanelState.panelHeight
11871264 }
1188− PropertyChanges {
1189− target: decoratedWindow;
1190− shadowOpacity: .3
1191− }
1265+ PropertyChanges { target: appDelegate; requestedX: requestedX; requestedY: PanelState.panelHeight; requestedHeight: appContainer.height - PanelState.panelHeight }
11921266 },
11931267 State {
11941268 name: "minimized"; when: appDelegate.minimized
@@ -1199,6 +1273,7 @@ AbstractStage {
11991273 x: -appDelegate.width / 2;
12001274 y: requestedY // TODO: Should be where the icon is in the launcher. Lets just move it horizontally to the left for now.
12011275 scale: units.gu(5) / appDelegate.width
1276+ requestedX: -appDelegate.width / 2
12021277 opacity: 0;
12031278 visuallyMinimized: true
12041279 visuallyMaximized: false
@@ -1210,11 +1285,11 @@ AbstractStage {
12101285 from: "staged,stagedWithSideStage"; to: "normal"
12111286 enabled: appDelegate.animationsEnabled
12121287 PropertyAction { target: appDelegate; properties: "visuallyMinimized,visuallyMaximized" }
1213− UbuntuNumberAnimation { target: appDelegate; properties: "x,y,opacity,requestedWidth,requestedHeight,scale"; duration: priv.animationDuration }
1288+ UbuntuNumberAnimation { target: appDelegate; properties: "x,y,requestedX,requestedY,opacity,requestedWidth,requestedHeight,scale"; duration: priv.animationDuration }
12141289 },
12151290 Transition {
1216− from: "normal,maximized,maximizedHorizontally,maximizedVertically,maximizedLeft,maximizedRight"; to: "staged,stagedWithSideStage"
1217− UbuntuNumberAnimation { target: appDelegate; properties: "x,y,requestedWidth,requestedHeight"; duration: priv.animationDuration}
1291+ from: "normal,maximized,maximizedHorizontally,maximizedVertically,maximizedLeft,maximizedRight,maximizedTopLeft,maximizedBottomLeft,maximizedTopRight,maximizedBottomRight"; to: "staged,stagedWithSideStage"
1292+ UbuntuNumberAnimation { target: appDelegate; properties: "x,y,requestedX,requestedY,requestedWidth,requestedHeight"; duration: priv.animationDuration}
12181293 },
12191294 Transition {
12201295 from: "maximized,maximizedHorizontally,maximizedVertically,maximizedLeft,maximizedRight,minimized"; to: "normal"
@@ -1222,12 +1297,6 @@ AbstractStage {
12221297 PropertyAction { target: appDelegate; properties: "visuallyMinimized,visuallyMaximized" }
12231298 UbuntuNumberAnimation { target: appDelegate; properties: "requestedX,requestedY,requestedWidth,requestedHeight,scale"; duration: priv.animationDuration }
12241299 },
1225− Transition {
1226− from: "normal"; to: "maximized,maximizedHorizontally,maximizedVertically,maximizedLeft,maximizedRight"
1227− enabled: appDelegate.animationsEnabled
1228− PropertyAction { target: appDelegate; properties: "visuallyMinimized,visuallyMaximized" }
1229− UbuntuNumberAnimation { target: appDelegate; properties: "requestedX,requestedY,requestedWidth,requestedHeight"; duration: priv.animationDuration }
1230− },
12311300 Transition {
12321301 to: "minimized"
12331302 enabled: appDelegate.animationsEnabled
@@ -1248,20 +1317,9 @@ AbstractStage {
12481317 }
12491318 }
12501319 },
1251− Transition {
1252− to: "maximized,fullscreen"
1253− enabled: appDelegate.animationsEnabled
1254− SequentialAnimation {
1255− PropertyAction { target: appDelegate; property: "visuallyMinimized" }
1256− ParallelAnimation {
1257− UbuntuNumberAnimation { target: appDelegate; properties: "requestedX,requestedY,opacity,scale,requestedWidth,requestedHeight"; duration: UbuntuAnimation.FastDuration }
1258− }
1259− PropertyAction { target: appDelegate; property: "visuallyMaximized" }
1260− }
1261− },
12621320 Transition {
12631321 to: "spread"
1264− // DecoratedWindow wants the sceleToPreviewSize set before enabling scaleToPreview
1322+ // DecoratedWindow wants the scaleToPreviewSize set before enabling scaleToPreview
12651323 PropertyAction { target: appDelegate; property: "z" }
12661324 PropertyAction { target: decoratedWindow; property: "scaleToPreviewSize" }
12671325 UbuntuNumberAnimation { target: appDelegate; properties: "x,y,height"; duration: priv.animationDuration }
@@ -1308,6 +1366,16 @@ AbstractStage {
13081366 // We need to release scaleToPreviewSize at last
13091367 PropertyAction { target: decoratedWindow; property: "scaleToPreviewSize" }
13101368 }
1369+ },
1370+ Transition {
1371+ to: "*" // "catch-all for maximized,fullscreen..."
1372+ enabled: appDelegate.animationsEnabled
1373+ SequentialAnimation {
1374+ PropertyAction { target: appDelegate; property: "visuallyMinimized" }
1375+ UbuntuNumberAnimation { target: appDelegate; properties: "requestedX,requestedY,opacity,scale,requestedWidth,requestedHeight"; duration: priv.animationDuration }
1376+ PropertyAction { target: appDelegate; property: "visuallyMaximized" }
1377+ ScriptAction { script: { fakeRectangle.stop(); } }
1378+ }
13111379 }
13121380 ]
13131381
@@ -1363,12 +1431,13 @@ AbstractStage {
13631431 focus: true
13641432 interactive: root.interactive
13651433 showDecoration: 1
1366− maximizeButtonShown: (maximumWidth == 0 || maximumWidth >= appContainer.width) &&
1367− (maximumHeight == 0 || maximumHeight >= appContainer.height)
1434+ maximizeButtonShown: appDelegate.canBeMaximized
13681435 overlayShown: touchControls.overlayShown
13691436 width: implicitWidth
13701437 height: implicitHeight
13711438 highlightSize: windowInfoItem.iconMargin / 2
1439+ stageWidth: appContainer.width
1440+ stageHeight: appContainer.height
13721441
13731442 requestedWidth: appDelegate.requestedWidth
13741443 requestedHeight: appDelegate.requestedHeight
@@ -1380,16 +1449,12 @@ AbstractStage {
13801449 onRequestedHeightChanged: oldRequestedHeight = requestedHeight
13811450
13821451 onCloseClicked: { appDelegate.close(); }
1383− onMaximizeClicked: appDelegate.maximized || appDelegate.maximizedLeft || appDelegate.maximizedRight
1384− || appDelegate.maximizedHorizontally || appDelegate.maximizedVertically
1385− ? appDelegate.restoreFromMaximized() : appDelegate.maximize()
1452+ onMaximizeClicked: appDelegate.anyMaximized ? appDelegate.restoreFromMaximized() : appDelegate.maximize();
13861453 onMaximizeHorizontallyClicked: appDelegate.maximizedHorizontally ? appDelegate.restoreFromMaximized() : appDelegate.maximizeHorizontally()
13871454 onMaximizeVerticallyClicked: appDelegate.maximizedVertically ? appDelegate.restoreFromMaximized() : appDelegate.maximizeVertically()
13881455 onMinimizeClicked: appDelegate.minimize()
1389− onDecorationPressed: {
1390− print("focusing because of decoration press");
1391− appDelegate.focus = true;
1392− }
1456+ onDecorationPressed: { appDelegate.focus = true; }
1457+ onDecorationReleased: fakeRectangle.commit();
13931458
13941459 property real angle: 0
13951460 property real itemScale: 1
@@ -1415,10 +1480,21 @@ AbstractStage {
14151480
14161481 WindowControlsOverlay {
14171482 id: touchControls
1418− anchors.fill: appDelegate
14191483 target: appDelegate
14201484 enabled: false
14211485 visible: enabled
1486+ stageWidth: appContainer.width
1487+ stageHeight: appContainer.height
1488+
1489+ onFakeMaximizeAnimationRequested: if (!appDelegate.maximized) fakeRectangle.maximize(amount, true)
1490+ onFakeMaximizeLeftAnimationRequested: if (!appDelegate.maximizedLeft) fakeRectangle.maximizeLeft(amount, true)
1491+ onFakeMaximizeRightAnimationRequested: if (!appDelegate.maximizedRight) fakeRectangle.maximizeRight(amount, true)
1492+ onFakeMaximizeTopLeftAnimationRequested: if (!appDelegate.maximizedTopLeft) fakeRectangle.maximizeTopLeft(amount, true);
1493+ onFakeMaximizeTopRightAnimationRequested: if (!appDelegate.maximizedTopRight) fakeRectangle.maximizeTopRight(amount, true);
1494+ onFakeMaximizeBottomLeftAnimationRequested: if (!appDelegate.maximizedBottomLeft) fakeRectangle.maximizeBottomLeft(amount, true);
1495+ onFakeMaximizeBottomRightAnimationRequested: if (!appDelegate.maximizedBottomRight) fakeRectangle.maximizeBottomRight(amount, true);
1496+ onStopFakeAnimation: fakeRectangle.stop();
1497+ onDragReleased: fakeRectangle.commit();
14221498 }
14231499
14241500 WindowedFullscreenPolicy {
@@ -1502,6 +1578,14 @@ AbstractStage {
15021578 }
15031579 }
15041580
1581+ FakeMaximizeDelegate {
1582+ id: fakeRectangle
1583+ target: priv.focusedAppDelegate
1584+ leftMargin: root.leftMargin
1585+ appContainerWidth: appContainer.width
1586+ appContainerHeight: appContainer.height
1587+ }
1588+
15051589 EdgeBarrier {
15061590 id: edgeBarrier
15071591
qml/Stage/TabletStage.qml+7−5
@@ -485,8 +485,7 @@ AbstractStage {
485485 return;
486486 }
487487
488− switch (phase) {
489− case 0:
488+ if (phase === 0) {
490489 // the "spreadEnabled" part is because when code does "phase = 0; contentX = -shift" to
491490 // dismiss the spread because spreadEnabled went to false, for some reason, during tests,
492491 // Flickable might jump in and change contentX value back, causing the code below to do
@@ -496,14 +495,17 @@ AbstractStage {
496495 if (root.spreadEnabled && shiftedContentX > width * positionMarker2) {
497496 phase = 1;
498497 }
499− break;
500− case 1:
498+ }
499+
500+ // Do not turn to else if
501+ // Sometimes the animation of shiftedContentX is very fast and we need to jump from phase 0 to 1 to 2
502+ // in the same onShiftedContentXChanged
503+ if (phase === 1) {
501504 if (shiftedContentX < width * positionMarker2) {
502505 phase = 0;
503506 } else if (shiftedContentX >= width * positionMarker4 && !spreadDragArea.dragging) {
504507 phase = 2;
505508 }
506− break;
507509 }
508510 }
509511
qml/Stage/TopLevelSurfaceRepeater.qml+7−1
@@ -27,7 +27,13 @@ Repeater {
2727 if (!startingUp) {
2828 // the top-most surface must be the focused one.
2929 var topmostDelegate = itemAt(0);
30− topmostDelegate.focus = true;
30+ if (topmostDelegate.focus) {
31+ // Delegate got focused while we were still starting up. Not good.
32+ // Force signal handler to run again
33+ topmostDelegate.onFocusChanged(true);
34+ } else {
35+ topmostDelegate.focus = true;
36+ }
3137 }
3238 }
3339
qml/Stage/WindowControlsOverlay.qml+80−38
@@ -23,12 +23,26 @@ import "../Components/PanelState"
2323Item {
2424 id: root
2525 enabled: target && !target.fullscreen
26+ anchors.fill: target
2627
2728 // to be set from outside
2829 property Item target // appDelegate
30+ property alias stageWidth: moveHandler.stageWidth
31+ property alias stageHeight: moveHandler.stageHeight
2932
3033 // to be read from outside
3134 readonly property alias overlayShown: overlay.visible
35+ readonly property alias dragging: priv.dragging
36+
37+ signal fakeMaximizeAnimationRequested(real amount)
38+ signal fakeMaximizeLeftAnimationRequested(real amount)
39+ signal fakeMaximizeRightAnimationRequested(real amount)
40+ signal fakeMaximizeTopLeftAnimationRequested(real amount)
41+ signal fakeMaximizeTopRightAnimationRequested(real amount)
42+ signal fakeMaximizeBottomLeftAnimationRequested(real amount)
43+ signal fakeMaximizeBottomRightAnimationRequested(real amount)
44+ signal stopFakeAnimation()
45+ signal dragReleased()
3246
3347 TouchGestureArea {
3448 id: gestureArea
@@ -51,16 +65,18 @@ Item {
5165 readonly property bool recognizedDrag: recognizedPress && dragging
5266 onRecognizedDragChanged: {
5367 if (recognizedDrag) {
54− priv.handlePressedChanged(true, tp.x, tp.y);
55− } else if (!moveHandler.containsPress) { // prevent interfering with the central piece drag/move
56− priv.dragging = false;
68+ moveHandler.handlePressedChanged(true, Qt.LeftButton, tp.x, tp.y);
69+ } else if (!mouseArea.containsPress) { // prevent interfering with the central piece drag/move
70+ moveHandler.handlePressedChanged(false, Qt.LeftButton);
71+ root.dragReleased();
72+ moveHandler.handleReleased(true);
5773 }
5874 }
5975
6076 readonly property point tp: recognizedPress ? Qt.point(touchPoints[0].x, touchPoints[0].y) : Qt.point(-1, -1)
6177 onUpdated: {
6278 if (recognizedDrag) {
63− priv.handlePositionChanged(tp.x, tp.y);
79+ moveHandler.handlePositionChanged(tp, priv.getSensingPoints());
6480 }
6581 }
6682 }
@@ -69,33 +85,41 @@ Item {
6985 Timer {
7086 id: overlayTimer
7187 interval: 2000
72− repeat: priv.dragging || (priv.resizeArea && priv.resizeArea.dragging)
88+ repeat: priv.dragging
89+ onTriggered: {
90+ print("timer triggered")
91+ }
92+ onRunningChanged: print("timer running changed", running)
7393 }
7494
7595 QtObject {
7696 id: priv
77− property real distanceX
78− property real distanceY
79− property bool dragging
80−
8197 readonly property var resizeArea: root.target && root.target.resizeArea ? root.target.resizeArea : null
98+ readonly property bool ensureWindow: root.target.state == "normal" || root.target.state == "restored"
99+ readonly property bool dragging: moveHandler.dragging || (resizeArea && resizeArea.dragging)
82100
83− function handlePressedChanged(pressed, mouseX, mouseY) {
84− if (pressed) {
85− var pos = mapToItem(root.target, mouseX, mouseY);
86− priv.distanceX = pos.x;
87− priv.distanceY = pos.y;
88− priv.dragging = true;
89− } else {
90− priv.dragging = false;
101+ function getSensingPoints() {
102+ var xPoints = [];
103+ var yPoints = [];
104+ for (var i = 0; i < gestureArea.touchPoints.length; i++) {
105+ var pt = gestureArea.touchPoints[i];
106+ xPoints.push(pt.x);
107+ yPoints.push(pt.y);
91108 }
92− }
93109
94− function handlePositionChanged(mouseX, mouseY) {
95− if (priv.dragging) {
96− var pos = mapToItem(root.target.parent, mouseX, mouseY);
97− root.target.windowedX = Math.round(pos.x - priv.distanceX);
98− root.target.windowedY = Math.round(Math.max(pos.y - priv.distanceY, PanelState.panelHeight));
110+ var leftmost = Math.min.apply(Math, xPoints);
111+ var rightmost = Math.max.apply(Math, xPoints);
112+ var topmost = Math.min.apply(Math, yPoints);
113+ var bottommost = Math.max.apply(Math, yPoints);
114+
115+ return {
116+ left: mapToItem(target.parent, leftmost, (topmost+bottommost)/2),
117+ top: mapToItem(target.parent, (leftmost+rightmost)/2, topmost),
118+ right: mapToItem(target.parent, rightmost, (topmost+bottommost)/2),
119+ topLeft: mapToItem(target.parent, leftmost, topmost),
120+ topRight: mapToItem(target.parent, rightmost, topmost),
121+ bottomLeft: mapToItem(target.parent, leftmost, bottommost),
122+ bottomRight: mapToItem(target.parent, rightmost, bottommost)
99123 }
100124 }
101125 }
@@ -113,8 +137,6 @@ Item {
113137 UbuntuNumberAnimation {}
114138 }
115139
116− readonly property bool anyMaximized: target && (target.maximized || target.maximizedLeft || target.maximizedRight)
117−
118140 Image {
119141 source: "graphics/arrows-centre.png"
120142 width: units.gu(10)
@@ -125,15 +147,33 @@ Item {
125147
126148 // move handler
127149 MouseArea {
128− id: moveHandler
150+ id: mouseArea
129151 anchors.fill: parent
130152 visible: overlay.visible
131153 enabled: visible
132154 hoverEnabled: true
133− cursorShape: priv.dragging ? Qt.ClosedHandCursor : (overlay.visible ? Qt.OpenHandCursor : Qt.ArrowCursor)
134155
135− onPressedChanged: priv.handlePressedChanged(pressed, mouseX, mouseY)
136− onPositionChanged: priv.handlePositionChanged(mouseX, mouseY)
156+ onPressedChanged: moveHandler.handlePressedChanged(pressed, pressedButtons, mouseX, mouseY)
157+ onPositionChanged: moveHandler.handlePositionChanged(mouse)
158+ onReleased: {
159+ root.dragReleased();
160+ moveHandler.handleReleased();
161+ }
162+ }
163+
164+ MoveHandler {
165+ id: moveHandler
166+ objectName: "moveHandler"
167+ target: root.target
168+
169+ onFakeMaximizeAnimationRequested: root.fakeMaximizeAnimationRequested(amount)
170+ onFakeMaximizeLeftAnimationRequested: root.fakeMaximizeLeftAnimationRequested(amount)
171+ onFakeMaximizeRightAnimationRequested: root.fakeMaximizeRightAnimationRequested(amount)
172+ onFakeMaximizeTopLeftAnimationRequested: root.fakeMaximizeTopLeftAnimationRequested(amount)
173+ onFakeMaximizeTopRightAnimationRequested: root.fakeMaximizeTopRightAnimationRequested(amount)
174+ onFakeMaximizeBottomLeftAnimationRequested: root.fakeMaximizeBottomLeftAnimationRequested(amount)
175+ onFakeMaximizeBottomRightAnimationRequested: root.fakeMaximizeBottomRightAnimationRequested(amount)
176+ onStopFakeAnimation: root.stopFakeAnimation()
137177 }
138178
139179 // dismiss area
@@ -148,7 +188,7 @@ Item {
148188 }
149189
150190 overlayTimer.stop();
151− mouse.accepted = root.contains(Qt.point(mouse.x, mouse.y));
191+ mouse.accepted = root.contains(mapToItem(root.target, mouse.x, mouse.y));
152192 }
153193 propagateComposedEvents: true
154194 }
@@ -157,7 +197,7 @@ Item {
157197 ResizeGrip { // top left
158198 anchors.horizontalCenter: parent.left
159199 anchors.verticalCenter: parent.top
160− visible: target && !overlay.anyMaximized && !target.maximizedHorizontally && !target.maximizedVertically
200+ visible: priv.ensureWindow || target.maximizedBottomRight
161201 resizeTarget: priv.resizeArea
162202 }
163203
@@ -165,7 +205,7 @@ Item {
165205 anchors.horizontalCenter: parent.horizontalCenter
166206 anchors.verticalCenter: parent.top
167207 rotation: 45
168− visible: target && !overlay.anyMaximized && !target.maximizedVertically
208+ visible: priv.ensureWindow || target.maximizedHorizontally || target.maximizedBottomLeft || target.maximizedBottomRight
169209 resizeTarget: priv.resizeArea
170210 }
171211
@@ -173,7 +213,7 @@ Item {
173213 anchors.horizontalCenter: parent.right
174214 anchors.verticalCenter: parent.top
175215 rotation: 90
176− visible: target && !overlay.anyMaximized && !target.maximizedHorizontally && !target.maximizedVertically
216+ visible: priv.ensureWindow || target.maximizedBottomLeft
177217 resizeTarget: priv.resizeArea
178218 }
179219
@@ -181,14 +221,15 @@ Item {
181221 anchors.horizontalCenter: parent.right
182222 anchors.verticalCenter: parent.verticalCenter
183223 rotation: 135
184− visible: target && !target.maximizedRight && !target.maximized && !target.maximizedHorizontally
224+ visible: priv.ensureWindow || target.maximizedVertically || target.maximizedLeft ||
225+ target.maximizedTopLeft || target.maximizedBottomLeft
185226 resizeTarget: priv.resizeArea
186227 }
187228
188229 ResizeGrip { // bottom right
189230 anchors.horizontalCenter: parent.right
190231 anchors.verticalCenter: parent.bottom
191− visible: target && !overlay.anyMaximized && !target.maximizedHorizontally && !target.maximizedVertically
232+ visible: priv.ensureWindow || target.maximizedTopLeft
192233 resizeTarget: priv.resizeArea
193234 }
194235
@@ -196,7 +237,7 @@ Item {
196237 anchors.horizontalCenter: parent.horizontalCenter
197238 anchors.verticalCenter: parent.bottom
198239 rotation: 45
199− visible: target && !overlay.anyMaximized && !target.maximizedVertically
240+ visible: priv.ensureWindow || target.maximizedHorizontally || target.maximizedTopLeft || target.maximizedTopRight
200241 resizeTarget: priv.resizeArea
201242 }
202243
@@ -204,7 +245,7 @@ Item {
204245 anchors.horizontalCenter: parent.left
205246 anchors.verticalCenter: parent.bottom
206247 rotation: 90
207− visible: target && !overlay.anyMaximized && !target.maximizedHorizontally && !target.maximizedVertically
248+ visible: priv.ensureWindow || target.maximizedTopRight
208249 resizeTarget: priv.resizeArea
209250 }
210251
@@ -212,7 +253,8 @@ Item {
212253 anchors.horizontalCenter: parent.left
213254 anchors.verticalCenter: parent.verticalCenter
214255 rotation: 135
215− visible: target && !target.maximizedLeft && !target.maximized && !target.maximizedHorizontally
256+ visible: priv.ensureWindow || target.maximizedVertically || target.maximizedRight ||
257+ target.maximizedTopRight || target.maximizedBottomRight
216258 resizeTarget: priv.resizeArea
217259 }
218260 }
qml/Stage/WindowDecoration.qml+12−43
@@ -15,22 +15,24 @@
1515 */
1616
1717import QtQuick 2.4
18−import Unity.Application 0.1 // For Mir singleton
1918import Ubuntu.Components 1.3
2019import "../Components"
21−import "../Components/PanelState"
2220
2321MouseArea {
2422 id: root
2523 clip: true
2624
27− property Item target
25+ property Item target // appDelegate
2826 property alias title: titleLabel.text
2927 property alias maximizeButtonShown: buttons.maximizeButtonShown
3028 property bool active: false
31− acceptedButtons: Qt.AllButtons // prevent leaking unhandled mouse events
3229 property alias overlayShown: buttons.overlayShown
3330
31+ readonly property real buttonsWidth: buttons.width + row.spacing
32+
33+ acceptedButtons: Qt.AllButtons // prevent leaking unhandled mouse events
34+ hoverEnabled: true
35+
3436 signal closeClicked()
3537 signal minimizeClicked()
3638 signal maximizeClicked()
@@ -38,42 +40,11 @@ MouseArea {
3840 signal maximizeVerticallyClicked()
3941
4042 onDoubleClicked: {
41− if (maximizeButtonShown && mouse.button == Qt.LeftButton) {
43+ if (target.canBeMaximized && mouse.button == Qt.LeftButton) {
4244 root.maximizeClicked();
4345 }
4446 }
4547
46− QtObject {
47− id: priv
48− property real distanceX
49− property real distanceY
50− property bool dragging
51− }
52−
53− onPressedChanged: {
54− if (pressed && pressedButtons == Qt.LeftButton) {
55− var pos = mapToItem(root.target, mouseX, mouseY);
56− priv.distanceX = pos.x;
57− priv.distanceY = pos.y;
58− priv.dragging = true;
59− } else {
60− priv.dragging = false;
61− Mir.cursorName = "";
62− }
63− }
64−
65− onPositionChanged: {
66− if (priv.dragging) {
67− Mir.cursorName = "grabbing";
68− var pos = mapToItem(root.target.parent, mouseX, mouseY);
69− // Use integer coordinate values to ensure that target is left in a pixel-aligned
70− // position. Mouse movement could have subpixel precision, yielding a fractional
71− // mouse position.
72− root.target.windowedX = Math.round(pos.x - priv.distanceX);
73− root.target.windowedY = Math.round(Math.max(pos.y - priv.distanceY, PanelState.panelHeight));
74− }
75− }
76−
7748 // do not let unhandled wheel event pass thru the decoration
7849 onWheel: wheel.accepted = true;
7950
@@ -85,6 +56,7 @@ MouseArea {
8556 }
8657
8758 Row {
59+ id: row
8860 anchors {
8961 fill: parent
9062 leftMargin: overlayShown ? units.gu(5) : units.gu(1)
@@ -105,10 +77,9 @@ MouseArea {
10577 onCloseClicked: root.closeClicked();
10678 onMinimizeClicked: root.minimizeClicked();
10779 onMaximizeClicked: root.maximizeClicked();
108− onMaximizeHorizontallyClicked: root.maximizeHorizontallyClicked();
109− onMaximizeVerticallyClicked: root.maximizeVerticallyClicked();
80+ onMaximizeHorizontallyClicked: if (root.target.canBeMaximizedHorizontally) root.maximizeHorizontallyClicked();
81+ onMaximizeVerticallyClicked: if (root.target.canBeMaximizedVertically) root.maximizeVerticallyClicked();
11082 closeButtonShown: root.target.application.appId !== "unity8-dash"
111− target: root.target
11283 }
11384
11485 Label {
@@ -122,10 +93,8 @@ MouseArea {
12293 font.weight: root.active ? Font.Light : Font.Medium
12394 elide: Text.ElideRight
12495 opacity: overlayShown ? 0 : 1
125− visible: opacity == 1
126− Behavior on opacity {
127− OpacityAnimator { duration: UbuntuAnimation.FastDuration; easing: UbuntuAnimation.StandardEasing }
128− }
96+ visible: opacity != 0
97+ Behavior on opacity { UbuntuNumberAnimation {} }
12998 }
13099 }
131100}
qml/Stage/WindowResizeArea.qml+15−26
@@ -30,6 +30,8 @@ MouseArea {
3030
3131 property var windowStateStorage: WindowStateStorage
3232 readonly property alias dragging: d.dragging
33+ readonly property alias normalWidth: priv.normalWidth
34+ readonly property alias normalHeight: priv.normalHeight
3335
3436 // The target item managed by this. Must be a parent or a sibling
3537 // The area will anchor to it and manage resize events
@@ -54,9 +56,9 @@ MouseArea {
5456 property int normalHeight: 0
5557
5658 function updateNormalGeometry() {
57− if (root.target.state == "normal") {
58− normalX = root.target.windowedX
59− normalY = root.target.windowedY
59+ if (root.target.state == "normal" || root.target.state == "restored") {
60+ normalX = root.target.requestedX
61+ normalY = root.target.requestedY
6062 normalWidth = root.target.width
6163 normalHeight = root.target.height
6264 }
@@ -85,34 +87,21 @@ MouseArea {
8587 target.windowedY = Qt.binding(function() { return Math.max(Math.min(windowGeometry.y, root.screenHeight - target.windowedHeight), PanelState.panelHeight); });
8688
8789 var windowState = windowStateStorage.getState(root.windowId, WindowStateStorage.WindowStateNormal)
88− switch (windowState) {
89− case WindowStateStorage.WindowStateNormal:
90− break;
91− case WindowStateStorage.WindowStateMaximized:
92− target.maximize(false);
93− break;
94− case WindowStateStorage.WindowStateMaximizedLeft:
95− target.maximizeLeft(false);
96− break;
97− case WindowStateStorage.WindowStateMaximizedRight:
98− target.maximizeRight(false);
99− break;
100− case WindowStateStorage.WindowStateMaximizedHorizontally:
101− target.maximizeHorizontally(false);
102− break;
103− case WindowStateStorage.WindowStateMaximizedVertically:
104− target.maximizeVertically(false);
105− break;
106− default:
107− console.warn("Unsupported window state");
108− break;
109− }
90+ target.restore(false /* animated */, windowState);
11091
11192 priv.updateNormalGeometry();
93+
94+ // initialize the x/y to restore to
95+ target.restoredX = priv.normalX;
96+ target.restoredY = priv.normalY;
11297 }
11398
11499 function saveWindowState() {
115− windowStateStorage.saveState(root.windowId, target.windowState & ~WindowStateStorage.WindowStateMinimized); // clear the minimized bit when saving
100+ var state = target.windowState;
101+ if (state === WindowStateStorage.WindowStateRestored) {
102+ state = WindowStateStorage.WindowStateNormal;
103+ }
104+ windowStateStorage.saveState(root.windowId, state & ~WindowStateStorage.WindowStateMinimized); // clear the minimized bit when saving
116105 windowStateStorage.saveGeometry(root.windowId, Qt.rect(priv.normalX, priv.normalY, priv.normalWidth, priv.normalHeight));
117106 }
118107
qml/Wizard/Pages/10-welcome.qml+1
@@ -20,6 +20,7 @@ import Ubuntu.Components.ListItems 1.3
2020import Ubuntu.SystemSettings.LanguagePlugin 1.0
2121import Wizard 0.1
2222import ".." as LocalComponents
23+import "../../Components"
2324
2425LocalComponents.Page {
2526 objectName: "languagePage"
qml/Wizard/Pages/20-keyboard.qml+1
@@ -23,6 +23,7 @@ import AccountsService 0.1
2323import Unity.InputInfo 0.1
2424import Unity.Application 0.1
2525import ".." as LocalComponents
26+import "../../Components"
2627
2728LocalComponents.Page {
2829 objectName: "keyboardPage"
qml/Wizard/Pages/30-wifi.qml+1
@@ -22,6 +22,7 @@ import Wizard 0.1
2222import Ubuntu.Connectivity 1.0
2323import Ubuntu.SystemImage 0.1
2424import ".." as LocalComponents
25+import "../../Components"
2526
2627LocalComponents.Page {
2728 id: wifiPage
qml/Wizard/Pages/50-timezone.qml+1
@@ -21,6 +21,7 @@ import Wizard 0.1
2121import Ubuntu.SystemSettings.TimeDate 1.1
2222import Utils 0.1 as Utils
2323import ".." as LocalComponents
24+import "../../Components"
2425
2526LocalComponents.Page {
2627 id: tzPage
qml/Wizard/Pages/70-passwd-type.qml+1
@@ -19,6 +19,7 @@ import Ubuntu.Components 1.3
1919import Ubuntu.Components.ListItems 1.3
2020import Ubuntu.SystemSettings.SecurityPrivacy 1.0
2121import ".." as LocalComponents
22+import "../../Components"
2223
2324/**
2425 * One quirk with this page: we don't actually set the password. We avoid
qml/Wizard/Pages/passcode-confirm.qml+2−2
@@ -18,7 +18,7 @@ import QtQuick 2.4
1818import Ubuntu.Components 1.3
1919import Ubuntu.SystemSettings.SecurityPrivacy 1.0
2020import ".." as LocalComponents
21−import "../../Components" as UnityComponents
21+import "../../Components"
2222
2323/**
2424 * See the main passwd-type page for an explanation of why we don't actually
@@ -34,7 +34,7 @@ LocalComponents.Page {
3434 // If we are entering this page, clear any saved password and get focus
3535 onEnabledChanged: if (enabled) lockscreen.clear(false)
3636
37− UnityComponents.Lockscreen {
37+ Lockscreen {
3838 id: lockscreen
3939 anchors {
4040 fill: content
qml/Wizard/Pages/passcode-desktop.qml+1
@@ -18,6 +18,7 @@ import QtQuick 2.4
1818import QtQuick.Layouts 1.1
1919import Ubuntu.Components 1.3
2020import ".." as LocalComponents
21+import "../../Components"
2122
2223/**
2324 * See the main passwd-type page for an explanation of why we don't actually
qml/Wizard/Pages/passcode-set.qml+2−2
@@ -18,7 +18,7 @@ import QtQuick 2.4
1818import Ubuntu.Components 1.3
1919import Ubuntu.SystemSettings.SecurityPrivacy 1.0
2020import ".." as LocalComponents
21−import "../../Components" as UnityComponents
21+import "../../Components"
2222
2323/**
2424 * See the main passwd-type page for an explanation of why we don't actually
@@ -45,7 +45,7 @@ LocalComponents.Page {
4545 onTriggered: pageStack.load(Qt.resolvedUrl("passcode-confirm.qml"));
4646 }
4747
48− UnityComponents.Lockscreen {
48+ Lockscreen {
4949 id: lockscreen
5050 anchors {
5151 fill: content
qml/Wizard/Pages/password-set.qml+1
@@ -18,6 +18,7 @@ import QtQuick 2.4
1818import QtQuick.Layouts 1.1
1919import Ubuntu.Components 1.3
2020import ".." as LocalComponents
21+import "../../Components"
2122
2223/**
2324 * See the main passwd-type page for an explanation of why we don't actually
qml/Wizard/WizardItemSelector.qml+1
@@ -17,6 +17,7 @@
1717import QtQuick 2.4
1818import Ubuntu.Components 1.3
1919import Ubuntu.Components.ListItems 1.3 as ListItem
20+import "../Components"
2021
2122Rectangle {
2223 id: optionToggle
qml/graphics/phone_background.jpgdeleted

Binary file not shown.

qml/graphics/tablet_background.jpgdeleted

Binary file not shown.

tests/CMakeLists.txt+8−13
@@ -37,27 +37,22 @@ set(UNITY_IMPORT_PATHS ${UNITY_IMPORT_PATHS} CACHE INTERNAL "")
3737
3838macro(unity8_parse_arguments)
3939 cmake_parse_arguments(U8TEST
40− "${QMLTEST_OPTIONS};LIGHTDM"
40+ "${QMLTEST_OPTIONS}"
4141 "${QMLTEST_SINGLE}"
4242 "${QMLTEST_MULTI}"
4343 ${ARGN}
4444 )
4545
4646 set(ld_paths)
47− if(U8TEST_LIGHTDM)
48− list(APPEND ld_paths
49− ${UNITY_MOCKPATH}/libusermetrics
50− ${UNITY_MOCKPATH}/LightDM/IntegratedLightDM/liblightdm
51− )
52− endif()
53−
54− if(ld_paths)
55− string(REPLACE ";" ":" ld_library_path "${ld_paths}")
56− set(ld_library_path LD_LIBRARY_PATH=${ld_library_path})
57− endif()
47+ list(APPEND ld_paths
48+ ${UNITY_MOCKPATH}/libusermetrics
49+ ${UNITY_MOCKPATH}/LightDM/IntegratedLightDM/liblightdm
50+ )
51+
52+ string(REPLACE ";" ":" ld_library_path "${ld_paths}")
53+ set(ld_library_path LD_LIBRARY_PATH=${ld_library_path})
5854
5955 set(U8TEST_ARGN "${ARGN}")
60− list(REMOVE_ITEM U8TEST_ARGN LIGHTDM)
6156
6257 set(environment
6358 UNITY_TESTING=1
tests/imports/check_imports.py+58
@@ -81,6 +81,53 @@ def scan_for_bad_import(file_path, all_pat, good_pats):
8181 print("%s: bad import version in multiple lines" % file_path)
8282 return bool(errors)
8383
84+# Flickable matches
85+flickable_pat = re.compile(r'.*\s*Flickable\s*{')
86+listview_pat = re.compile(r'.*\s*ListView\s*{')
87+gridview_pat = re.compile(r'.*\s*GridView\s*{')
88+flickable_pats = [flickable_pat, listview_pat, gridview_pat]
89+unity_components_pat = re.compile(r'.*import ".*Components"')
90+components_import_pat = re.compile(r'.*import "."')
91+components_path = re.compile(r'.*qml/Components.*')
92+skip_components_flickable_path = re.compile(r'.*qml/Components/Flickable.qml')
93+skip_components_listview_path = re.compile(r'.*qml/Components/ListView.qml')
94+skip_components_gridview_path = re.compile(r'.*qml/Components/GridView.qml')
95+skip_mocks_path = re.compile(r'.*tests/mocks.*')
96+
97+def scan_for_flickable_imports(file_path, component_pats, qtquick_pat, unitycomponents_pat):
98+ errors = []
99+ with open(file_path, 'rt', encoding='utf-8') as ifile, open(file_path, 'rt', encoding='utf-8') as i2file:
100+ flickable_found = False
101+ for lino, line in enumerate(ifile, start=1):
102+ for component_pat in component_pats:
103+ if component_pat.match(line):
104+ flickable_found = True
105+ if flickable_found:
106+ qtquick_found = False
107+ unitycomponents_found = False
108+ for lino, line in enumerate(i2file, start=1):
109+ if not qtquick_found and qtquick_pat.match(line):
110+ qtquick_found = True
111+ if unitycomponents_pat.match(line):
112+ unitycomponents_found = True
113+ if not qtquick_found:
114+ errors.append(lino)
115+ else:
116+ return
117+ if not unitycomponents_found:
118+ errors.append(lino)
119+ if 0 < len(errors) <= 10:
120+ if len(errors) > 1:
121+ plural = 's'
122+ else:
123+ plural = ''
124+ print(
125+ "%s: missing/wrong order of Components import in line%s %s" % (
126+ file_path, plural, ", ".join((str(i) for i in errors))))
127+ elif errors:
128+ print("%s: missing/wrong order of Components imports in multiple lines" % file_path)
129+ return bool(errors)
130+
84131# Parse args
85132
86133parser = argparse.ArgumentParser(
@@ -116,6 +163,17 @@ try:
116163 found_bad_import = True
117164 if scan_for_bad_import(path, ubuntu_components_pat, [ubuntu_good_components_pat]):
118165 found_bad_import = True
166+ if skip_mocks_path.match(path) or \
167+ skip_components_flickable_path.match(path) or \
168+ skip_components_listview_path.match(path) or \
169+ skip_components_gridview_path.match(path):
170+ break
171+ if not components_path.match(path):
172+ if scan_for_flickable_imports(path, flickable_pats, quick_good_pat, unity_components_pat):
173+ found_bad_import = True
174+ else:
175+ if scan_for_flickable_imports(path, flickable_pats, quick_good_pat, components_import_pat):
176+ found_bad_import = True
119177
120178except OSError as e:
121179 error("cannot create file list for \"" + dir + "\": " + e.strerror)
tests/mocks/AccountsService/AccountsService.cpp+19−3
@@ -15,7 +15,9 @@
1515 */
1616
1717#include "AccountsService.h"
18+#include "MockUsersModel.h"
1819
20+#include <QLightDM/UsersModel>
1921#include <paths.h>
2022
2123AccountsService::AccountsService(QObject* parent)
@@ -23,15 +25,17 @@ AccountsService::AccountsService(QObject* parent)
2325 m_enableFingerprintIdentification(true),
2426 m_enableLauncherWhileLocked(true),
2527 m_enableIndicatorsWhileLocked(true),
26− m_backgroundFile(qmlDirectory() + "/graphics/phone_background.jpg"),
28+ m_backgroundFile(),
2729 m_statsWelcomeScreen(true),
2830 m_failedLogins(0),
2931 m_failedFingerprintLogins(0),
3032 m_demoEdges(false),
3133 m_demoEdgesCompleted(),
3234 m_hereEnabled(false),
33− m_hereLicensePath("")
35+ m_hereLicensePath(""),
36+ m_usersModel(new MockUsersModel(this))
3437{
38+ m_usersModel->setMockMode("full");
3539}
3640
3741QString AccountsService::user() const
@@ -44,6 +48,7 @@ void AccountsService::setUser(const QString &user)
4448 m_user = user;
4549 Q_EMIT userChanged();
4650 Q_EMIT passwordDisplayHintChanged();
51+ Q_EMIT backgroundFileChanged();
4752}
4853
4954bool AccountsService::demoEdges() const
@@ -111,7 +116,18 @@ void AccountsService::setEnableIndicatorsWhileLocked(bool enableIndicatorsWhileL
111116
112117QString AccountsService::backgroundFile() const
113118{
114− return m_backgroundFile;
119+ if (!m_backgroundFile.isEmpty()) {
120+ return m_backgroundFile;
121+ }
122+
123+ // Check if our mock user has a background set in liblightdm
124+ for (int i = 0; i < m_usersModel->count(); i++) {
125+ if (m_usersModel->data(i, QLightDM::UsersModel::NameRole) == m_user) {
126+ return m_usersModel->data(i, QLightDM::UsersModel::BackgroundPathRole).toString();
127+ }
128+ }
129+
130+ return QString();
115131}
116132
117133void AccountsService::setBackgroundFile(const QString &backgroundFile)
tests/mocks/AccountsService/AccountsService.h+3
@@ -22,6 +22,8 @@
2222#include <QStringList>
2323#include <QVariant>
2424
25+class MockUsersModel;
26+
2527class AccountsService: public QObject
2628{
2729 Q_OBJECT
@@ -163,6 +165,7 @@ private:
163165 QString m_realName;
164166 QStringList m_kbdMap;
165167 QString m_email;
168+ MockUsersModel *m_usersModel;
166169};
167170
168171#endif
tests/mocks/AccountsService/CMakeLists.txt+14
@@ -1,10 +1,24 @@
1+include_directories(
2+ ${CMAKE_CURRENT_BINARY_DIR}
3+ ${CMAKE_SOURCE_DIR}/plugins/LightDM
4+ ${CMAKE_SOURCE_DIR}/plugins/Utils
5+ ${CMAKE_SOURCE_DIR}/tests/mocks/LightDM/IntegratedLightDM
6+ )
7+
18add_library(MockAccountsService-qml MODULE
9+ ${CMAKE_SOURCE_DIR}/plugins/LightDM/UsersModel.cpp
10+ ${CMAKE_SOURCE_DIR}/plugins/Utils/unitysortfilterproxymodelqml.cpp
11+ ${CMAKE_SOURCE_DIR}/tests/mocks/LightDM/IntegratedLightDM/MockUsersModel.cpp
212 AccountsService.cpp
313 plugin.cpp
414 )
515
616qt5_use_modules(MockAccountsService-qml DBus Qml)
717
18+target_link_libraries(MockAccountsService-qml
19+ MockLightDM
20+ )
21+
822add_unity8_mock(AccountsService 0.1 AccountsService
923 PREFIX mocks
1024 TARGETS MockAccountsService-qml
tests/mocks/CMakeLists.txt+1
@@ -39,6 +39,7 @@ add_subdirectory(Lights)
3939add_subdirectory(MeeGo)
4040add_subdirectory(Powerd)
4141add_subdirectory(QMenuModel)
42+add_subdirectory(SessionBroadcast)
4243add_subdirectory(Ubuntu)
4344add_subdirectory(Unity)
4445add_subdirectory(QtMultimedia)
tests/mocks/Cursor/CMakeLists.txt+1−1
@@ -1 +1 @@
1−add_unity8_mock(Cursor 1.0 Cursor PREFIX mocks)
1+add_unity8_mock(Cursor 1.1 Cursor PREFIX mocks)
tests/mocks/Cursor/Cursor.qml+10−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
@@ -17,7 +17,16 @@
1717import QtQuick 2.4
1818
1919Item {
20+ property int topBoundaryOffset // effectively panel height
21+ property Item confiningItem
22+
2023 signal pushedLeftBoundary(real amount, int buttons)
2124 signal pushedRightBoundary(real amount, int buttons)
25+ signal pushedTopBoundary(real amount, int buttons)
26+ signal pushedTopLeftCorner(real amount, int buttons)
27+ signal pushedTopRightCorner(real amount, int buttons)
28+ signal pushedBottomLeftCorner(real amount, int buttons)
29+ signal pushedBottomRightCorner(real amount, int buttons)
30+ signal pushStopped()
2231 signal mouseMoved()
2332}
tests/mocks/LightDM/IntegratedLightDM/MockSessionsModel.cpp+29
@@ -18,6 +18,23 @@
1818#include "MockSessionsModel.h"
1919#include <QLightDM/SessionsModel>
2020
21+
22+int MockSessionsModel::numSessions() const
23+{
24+ QLightDM::SessionsModel* qSessionsModel =
25+ static_cast<QLightDM::SessionsModel*>(sourceModel());
26+
27+ return qSessionsModel->numSessions();
28+}
29+
30+int MockSessionsModel::numAvailableSessions() const
31+{
32+ QLightDM::SessionsModel* qSessionsModel =
33+ static_cast<QLightDM::SessionsModel*>(sourceModel());
34+
35+ return qSessionsModel->numAvailableSessions();
36+}
37+
2138QString MockSessionsModel::testScenario() const
2239{
2340 QLightDM::SessionsModel* qSessionsModel =
@@ -26,6 +43,17 @@ QString MockSessionsModel::testScenario() const
2643 return qSessionsModel->testScenario();
2744}
2845
46+void MockSessionsModel::setNumSessions(const int numSessions)
47+{
48+ QLightDM::SessionsModel* qSessionsModel =
49+ static_cast<QLightDM::SessionsModel*>(sourceModel());
50+
51+ if (qSessionsModel->numSessions() != numSessions) {
52+ qSessionsModel->setNumSessions(numSessions);
53+ Q_EMIT numSessionsChanged();
54+ }
55+}
56+
2957void MockSessionsModel::setTestScenario(const QString testScenario)
3058{
3159 QLightDM::SessionsModel* qSessionsModel =
@@ -33,5 +61,6 @@ void MockSessionsModel::setTestScenario(const QString testScenario)
3361
3462 if (qSessionsModel->testScenario() != testScenario) {
3563 qSessionsModel->setTestScenario(testScenario);
64+ Q_EMIT testScenarioChanged();
3665 }
3766}
tests/mocks/LightDM/IntegratedLightDM/MockSessionsModel.h+12−2
@@ -24,11 +24,21 @@ class MockSessionsModel : public SessionsModel
2424{
2525 Q_OBJECT
2626
27− Q_PROPERTY(QString testScenario READ testScenario WRITE setTestScenario)
27+ Q_PROPERTY(int numAvailableSessions READ numAvailableSessions CONSTANT)
28+ Q_PROPERTY(int numSessions READ numSessions WRITE setNumSessions NOTIFY numSessionsChanged)
29+ Q_PROPERTY(QString testScenario READ testScenario WRITE setTestScenario NOTIFY testScenarioChanged)
2830
2931public:
32+ int numAvailableSessions() const;
33+ int numSessions() const;
3034 QString testScenario() const;
31− void setTestScenario(QString testScenario);
35+ void setNumSessions(const int numSessions);
36+ void setTestScenario(const QString testScenario);
37+
38+Q_SIGNALS:
39+ void numSessionsChanged();
40+ void testScenarioChanged();
41+
3242};
3343
3444#endif // MOCK_UNITY_SESSIONSMODEL_H
tests/mocks/LightDM/IntegratedLightDM/MockUsersModel.cpp+5
@@ -19,6 +19,11 @@
1919#include <QLightDM/UsersModel>
2020#include <QSortFilterProxyModel>
2121
22+MockUsersModel::MockUsersModel(QObject* parent)
23+ : UsersModel(parent)
24+{
25+}
26+
2227QString MockUsersModel::mockMode() const
2328{
2429 QLightDM::UsersModel* qUsersModel =
tests/mocks/LightDM/IntegratedLightDM/MockUsersModel.h+2
@@ -27,6 +27,8 @@ class MockUsersModel : public UsersModel
2727 Q_PROPERTY(QString mockMode READ mockMode WRITE setMockMode NOTIFY mockModeChanged)
2828
2929public:
30+ explicit MockUsersModel(QObject* parent=0);
31+
3032 QString mockMode() const;
3133 void setMockMode(QString mockMode);
3234
tests/mocks/LightDM/IntegratedLightDM/liblightdm/Greeter.cpp−5
@@ -69,11 +69,6 @@ bool Greeter::showRemoteLoginHint() const
6969 return true;
7070}
7171
72−bool Greeter::lockHint () const
73−{
74− return false;
75−}
76−
7772QString Greeter::selectUserHint() const
7873{
7974 Q_D(const Greeter);
tests/mocks/LightDM/IntegratedLightDM/liblightdm/Greeter.h−2
@@ -41,7 +41,6 @@ class Q_DECL_EXPORT Greeter : public QObject
4141
4242 Q_PROPERTY(QString hostname READ hostname CONSTANT)
4343 Q_PROPERTY(bool hasGuestAccount READ hasGuestAccountHint CONSTANT)
44− Q_PROPERTY(bool locked READ lockHint CONSTANT)
4544
4645 Q_ENUMS(PromptType MessageType)
4746
@@ -67,7 +66,6 @@ public:
6766 bool hideUsersHint() const;
6867 bool showManualLoginHint() const;
6968 bool showRemoteLoginHint() const;
70− bool lockHint () const;
7169 bool hasGuestAccountHint() const;
7270 QString selectUserHint() const;
7371 void setSelectUserHint(const QString &selectUserHint); // only in mock
tests/mocks/LightDM/IntegratedLightDM/liblightdm/SessionsModel.cpp+22
@@ -80,12 +80,34 @@ int SessionsModel::rowCount(const QModelIndex& parent) const
8080 }
8181}
8282
83+int SessionsModel::numSessions() const
84+{
85+ Q_D(const SessionsModel);
86+ return d->numSessions;
87+}
88+
89+int SessionsModel::numAvailableSessions() const
90+{
91+ Q_D(const SessionsModel);
92+ return d->numAvailableSessions();
93+}
94+
8395QString SessionsModel::testScenario() const
8496{
8597 Q_D(const SessionsModel);
8698 return d->testScenario;
8799}
88100
101+void SessionsModel::setNumSessions(int numSessions)
102+{
103+ Q_D(SessionsModel);
104+
105+ if (d->numSessions != numSessions) {
106+ d->numSessions = numSessions;
107+ d->resetEntries();
108+ }
109+}
110+
89111void SessionsModel::setTestScenario(QString testScenario)
90112{
91113 Q_D(SessionsModel);
tests/mocks/LightDM/IntegratedLightDM/liblightdm/SessionsModel.h+3−3
@@ -31,9 +31,6 @@ class Q_DECL_EXPORT SessionsModel : public QAbstractListModel
3131
3232 Q_ENUMS(SessionModelRoles SessionType)
3333
34− // Mock-only API for testing purposes
35− Q_PROPERTY(QString testScenario READ testScenario WRITE setTestScenario)
36−
3734 public:
3835
3936 enum SessionModelRoles {
@@ -57,7 +54,10 @@ class Q_DECL_EXPORT SessionsModel : public QAbstractListModel
5754 int rowCount(const QModelIndex& parent) const override;
5855 QVariant data(const QModelIndex& index, int role) const override;
5956
57+ int numSessions() const;
58+ int numAvailableSessions() const;
6059 QString testScenario() const;
60+ void setNumSessions(int numSessions);
6161 void setTestScenario(QString testScenario);
6262
6363 protected:
tests/mocks/LightDM/IntegratedLightDM/liblightdm/SessionsModelPrivate.cpp+27−17
@@ -16,24 +16,44 @@
1616
1717#include "SessionsModel.h"
1818#include "SessionsModelPrivate.h"
19−
19+#include <QDebug>
2020namespace QLightDM
2121{
2222
2323SessionsModelPrivate::SessionsModelPrivate(SessionsModel* parent)
2424 : testScenario("singleSession")
25+ , m_availableSessions(
26+ {
27+ {"ubuntu", "", "Ubuntu", ""},
28+ {"ubuntu-2d", "", "Ubuntu 2D", ""},
29+ {"gnome", "", "GNOME", ""},
30+ {"gnome-classic", "", "GNOME Classic", ""},
31+ {"gnome-flashback-compiz", "", "GNOME Flashback (Compiz)", ""},
32+ {"gnome-flashback-metacity", "", "GNOME Flashback (Metacity)", ""},
33+ {"gnome-wayland", "", "GNOME on Wayland", ""},
34+ {"plasma", "", "Plasma", ""},
35+ {"kde", "", "KDE" , ""},
36+ {"xterm", "", "Recovery Console", ""},
37+ {"", "", "Unknown?", ""}
38+ })
2539 , q_ptr(parent)
2640{
41+ numSessions = numAvailableSessions();
2742 resetEntries();
2843}
2944
45+int SessionsModelPrivate::numAvailableSessions() const
46+{
47+ return m_availableSessions.length();
48+}
49+
3050void SessionsModelPrivate::resetEntries()
3151{
3252 Q_Q(SessionsModel);
3353
3454 q->beginResetModel();
3555 if (testScenario == "multipleSessions") {
36− resetEntries_multipleSessions();
56+ resetEntries_multipleSessions(numSessions);
3757 } else if (testScenario == "noSessions") {
3858 resetEntries_noSessions();
3959 } else {
@@ -42,22 +62,12 @@ void SessionsModelPrivate::resetEntries()
4262 q->endResetModel();
4363}
4464
45−void SessionsModelPrivate::resetEntries_multipleSessions()
65+void SessionsModelPrivate::resetEntries_multipleSessions(int numSessions)
4666{
47− sessionItems =
48− {
49− {"ubuntu", "", "Ubuntu", ""},
50− {"ubuntu-2d", "", "Ubuntu 2D", ""},
51− {"gnome", "", "GNOME", ""},
52− {"gnome-classic", "", "GNOME Classic", ""},
53− {"gnome-flashback-compiz", "", "GNOME Flashback (Compiz)", ""},
54− {"gnome-flashback-metacity", "", "GNOME Flashback (Metacity)", ""},
55− {"gnome-wayland", "", "GNOME on Wayland", ""},
56− {"plasma", "", "Plasma", ""},
57− {"kde", "", "KDE" , ""},
58− {"xterm", "", "Recovery Console", ""},
59− {"", "", "Unknown?", ""}
60− };
67+ sessionItems.clear();
68+ for (int i = 0; i < numSessions; i++) {
69+ sessionItems.append(m_availableSessions.value(i));
70+ }
6171}
6272
6373void SessionsModelPrivate::resetEntries_noSessions()
tests/mocks/LightDM/IntegratedLightDM/liblightdm/SessionsModelPrivate.h+5−1
@@ -40,7 +40,10 @@ public:
4040 explicit SessionsModelPrivate(SessionsModel* parent=0);
4141 virtual ~SessionsModelPrivate() = default;
4242
43+ int numAvailableSessions() const;
44+
4345 QList<SessionItem> sessionItems;
46+ int numSessions;
4447 QString testScenario;
4548
4649 void resetEntries();
@@ -48,7 +51,8 @@ protected:
4851 SessionsModel* const q_ptr;
4952
5053private:
51− void resetEntries_multipleSessions();
54+ const QList<SessionItem> m_availableSessions;
55+ void resetEntries_multipleSessions(int numSessions);
5256 void resetEntries_noSessions();
5357 void resetEntries_singleSession();
5458 Q_DECLARE_PUBLIC(SessionsModel)
tests/mocks/LightDM/IntegratedLightDM/liblightdm/UsersModelPrivate.cpp+15−1
@@ -17,6 +17,8 @@
1717#include "UsersModelPrivate.h"
1818#include "UsersModel.h"
1919
20+#include <QDir>
21+
2022namespace QLightDM
2123{
2224
@@ -53,6 +55,17 @@ void UsersModelPrivate::resetEntries()
5355 entries[i].uid = i + 1;
5456 }
5557
58+ // Assign backgrounds
59+ QDir backgroundDir("/usr/share/backgrounds");
60+ QStringList backgrounds = backgroundDir.entryList(QDir::Files);
61+ if (!backgrounds.empty()) {
62+ for (int i = 0; i < entries.size(); i++) {
63+ if (entries[i].background.isNull()) {
64+ entries[i].background = backgroundDir.filePath(backgrounds[i % backgrounds.size()]);
65+ }
66+ }
67+ }
68+
5669 q->endResetModel();
5770}
5871
@@ -96,7 +109,7 @@ void UsersModelPrivate::resetEntries_full()
96109 { "wide-info-prompt", "Wide Info Prompt", 0, 0, false, false, "ubuntu", 0 },
97110 { "multi-info-prompt", "Multi Info Prompt", 0, 0, false, false, "ubuntu", 0 },
98111 { "long-name", "Long name (far far too long to fit, seriously this would never fit on the screen, you will never see this part of the name)", 0, 0, false, false, "ubuntu", 0 },
99− { "color-background", "Color Background", "#dd4814", 0, false, false, "ubuntu", 0 },
112+ { "color-background", "Color Background", "#E95420", 0, false, false, "ubuntu", 0 },
100113 // white and black are a bit redundant, but useful for manually testing if UI is still readable
101114 { "white-background", "White Background", "#ffffff", 0, false, false, "ubuntu", 0 },
102115 { "black-background", "Black Background", "#000000", 0, false, false, "ubuntu", 0 },
@@ -104,6 +117,7 @@ void UsersModelPrivate::resetEntries_full()
104117 { "unicode", "가나다라마", 0, 0, false, false, "ubuntu", 0 },
105118 { "no-response", "No Response", 0, 0, false, false, "ubuntu", 0 },
106119 { "empty-name", "", 0, 0, false, false, "ubuntu", 0 },
120+ { "active", "Active Account", 0, 0, true, false, "ubuntu", 0 },
107121 };
108122}
109123
tests/mocks/LightDM/IntegratedLightDM/plugin.cpp+1−1
@@ -72,7 +72,7 @@ void LightDMPlugin::registerTypes(const char *uri)
7272 qRegisterMetaType<QLightDM::Greeter::PromptType>("QLightDM::Greeter::PromptType");
7373 qmlRegisterSingletonType<MockGreeter>(uri, 0, 1, "Greeter", greeter_provider);
7474 qmlRegisterSingletonType<MockSessionsModel>(uri, 0, 1, "Sessions", sessions_provider);
75− qmlRegisterUncreatableType<SessionsModel>(uri, 0, 1, "SessionRoles", "Type is not instantiable");
75+ qmlRegisterUncreatableType<QLightDM::SessionsModel>(uri, 0, 1, "SessionRoles", "Type is not instantiable");
7676 qmlRegisterSingletonType<MockUsersModel>(uri, 0, 1, "Users", users_provider);
7777 qmlRegisterUncreatableType<QLightDM::UsersModel>(uri, 0, 1, "UserRoles", "Type is not instantiable");
7878 qmlRegisterSingletonType<UserMetricsOutput::UserMetrics>(uri, 0, 1, "Infographic", infographic_provider);
tests/mocks/QtMultimedia/mediaplayer.cpp+1
@@ -161,6 +161,7 @@ void MediaPlayer::play()
161161 if (m_playbackState != PlayingState) {
162162 m_playbackState = PlayingState;
163163 Q_EMIT playbackStateChanged(m_playbackState);
164+ Q_EMIT playing();
164165
165166 m_timer.start();
166167 }
tests/mocks/QtMultimedia/mediaplayer.h+1
@@ -131,6 +131,7 @@ public Q_SLOTS:
131131
132132Q_SIGNALS:
133133 void playlistChanged();
134+ void playing();
134135 void sourceChanged(const QUrl &source);
135136 void playbackStateChanged(PlaybackState playbackState);
136137 void positionChanged(int position);
tests/mocks/SessionBroadcast/CMakeLists.txtnew file+8
@@ -0,0 +1,8 @@
1+add_library(MockSessionBroadcast-qml MODULE
2+ plugin.cpp
3+ SessionBroadcast.cpp
4+ )
5+
6+qt5_use_modules(MockSessionBroadcast-qml Qml)
7+
8+add_unity8_mock(SessionBroadcast 0.1 SessionBroadcast TARGETS MockSessionBroadcast-qml)
tests/mocks/SessionBroadcast/SessionBroadcast.cppnew file+34
@@ -0,0 +1,34 @@
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 "SessionBroadcast.h"
18+
19+SessionBroadcast::SessionBroadcast(QObject* parent)
20+ : QObject(parent)
21+{
22+}
23+
24+void SessionBroadcast::requestUrlStart(const QString &, const QString &url)
25+{
26+ // No user name guards, we won't worry about that aspect of the plugin here
27+ Q_EMIT startUrl(url);
28+}
29+
30+void SessionBroadcast::requestHomeShown(const QString &)
31+{
32+ // No user name guards, we won't worry about that aspect of the plugin here
33+ Q_EMIT showHome();
34+}
tests/mocks/SessionBroadcast/SessionBroadcast.hnew file+39
@@ -0,0 +1,39 @@
1+/*
2+ * Copyright (C) 2012,2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ *
16+ * Authors: Michael Terry <michael.terry@canonical.com>
17+ */
18+
19+#ifndef UNITY_MOCK_SESSIONBROADCAST_H
20+#define UNITY_MOCK_SESSIONBROADCAST_H
21+
22+#include <QObject>
23+
24+class SessionBroadcast: public QObject
25+{
26+ Q_OBJECT
27+
28+public:
29+ explicit SessionBroadcast(QObject *parent = 0);
30+
31+ Q_INVOKABLE void requestUrlStart(const QString &username, const QString &url);
32+ Q_INVOKABLE void requestHomeShown(const QString &username);
33+
34+Q_SIGNALS:
35+ void startUrl(const QString &url);
36+ void showHome();
37+};
38+
39+#endif
tests/mocks/SessionBroadcast/plugin.cppnew file+31
@@ -0,0 +1,31 @@
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 "plugin.h"
18+#include "SessionBroadcast.h"
19+
20+#include <QtQml/qqml.h>
21+
22+static QObject *broadcast_provider(QQmlEngine *, QJSEngine *)
23+{
24+ return new SessionBroadcast();
25+}
26+
27+void PowerdPlugin::registerTypes(const char *uri)
28+{
29+ Q_ASSERT(uri == QLatin1String("SessionBroadcast"));
30+ qmlRegisterSingletonType<SessionBroadcast>(uri, 0, 1, "SessionBroadcast", broadcast_provider);
31+}
tests/mocks/SessionBroadcast/plugin.hnew file+32
@@ -0,0 +1,32 @@
1+/*
2+ * Copyright (C) 2016 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef MOCK_SESSIONBROADCAST_PLUGIN_H
18+#define MOCK_SESSIONBROADCAST_PLUGIN_H
19+
20+#include <QtQml/QQmlEngine>
21+#include <QtQml/QQmlExtensionPlugin>
22+
23+class PowerdPlugin : public QQmlExtensionPlugin
24+{
25+ Q_OBJECT
26+ Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
27+
28+public:
29+ void registerTypes(const char *uri) override;
30+};
31+
32+#endif
tests/mocks/SessionBroadcast/qmldirnew file+2
@@ -0,0 +1,2 @@
1+module SessionBroadcast
2+plugin MockSessionBroadcast-qml
tests/mocks/Ubuntu/DownloadDaemonListener/MockDownloadTracker.cpp+3−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 it
55 * under the terms of the GNU Lesser General Public License, as
@@ -67,6 +67,8 @@ void MockDownloadTracker::startService()
6767 Q_EMIT finished("downloadComplete");
6868 }else if(m_dbusPath == "error") {
6969 Q_EMIT error("DOWNLOAD ERROR");
70+ }else if(m_dbusPath == "processing") {
71+ Q_EMIT processing(m_dbusPath);
7072 }
7173 }
7274}
tests/mocks/Ubuntu/DownloadDaemonListener/MockDownloadTracker.h+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 it
55 * under the terms of the GNU Lesser General Public License, as
@@ -51,6 +51,7 @@ Q_SIGNALS:
5151 void error(const QString &error);
5252 void finished(const QString &path);
5353 void paused(bool success);
54+ void processing(const QString &path);
5455 void progress(qulonglong received, qulonglong total);
5556 void resumed(bool success);
5657 void started(bool success);
tests/mocks/Ubuntu/Telephony/MockTelepathyHelper.cpp+27
@@ -20,6 +20,8 @@
2020
2121MockTelepathyHelper::MockTelepathyHelper(QObject *parent)
2222 : QObject(parent)
23+ , m_ready(true)
24+ , m_emergencyCallsAvailable(true)
2325{
2426}
2527
@@ -33,3 +35,28 @@ void MockTelepathyHelper::registerChannelObserver(const QString& name)
3335{
3436 Q_UNUSED(name);
3537}
38+
39+bool MockTelepathyHelper::ready() const
40+{
41+ return m_ready;
42+}
43+
44+void MockTelepathyHelper::setReady(bool value)
45+{
46+ if (m_ready != value) {
47+ m_ready = value;
48+ Q_EMIT readyChanged();
49+ }
50+}
51+bool MockTelepathyHelper::emergencyCallsAvailable() const
52+{
53+ return m_emergencyCallsAvailable;
54+}
55+
56+void MockTelepathyHelper::setEmergencyCallsAvailable(bool value)
57+{
58+ if (m_emergencyCallsAvailable != value) {
59+ m_emergencyCallsAvailable = value;
60+ Q_EMIT emergencyCallsAvailableChanged();
61+ }
62+}
tests/mocks/Ubuntu/Telephony/MockTelepathyHelper.h+22
@@ -24,6 +24,14 @@
2424class MockTelepathyHelper : public QObject
2525{
2626 Q_OBJECT
27+ Q_PROPERTY(bool ready
28+ READ ready
29+ WRITE setReady // only in mock
30+ NOTIFY readyChanged)
31+ Q_PROPERTY(bool emergencyCallsAvailable
32+ READ emergencyCallsAvailable
33+ WRITE setEmergencyCallsAvailable // only in mock
34+ NOTIFY emergencyCallsAvailableChanged)
2735 Q_DISABLE_COPY(MockTelepathyHelper)
2836public:
2937 MockTelepathyHelper(QObject *parent = 0);
@@ -31,6 +39,20 @@ public:
3139 static MockTelepathyHelper *instance();
3240
3341 Q_INVOKABLE void registerChannelObserver(const QString& name);
42+
43+ bool ready() const;
44+ void setReady(bool value);
45+
46+ bool emergencyCallsAvailable() const;
47+ void setEmergencyCallsAvailable(bool value);
48+
49+Q_SIGNALS:
50+ void readyChanged();
51+ void emergencyCallsAvailableChanged();
52+
53+private:
54+ bool m_ready;
55+ bool m_emergencyCallsAvailable;
3456};
3557
3658#endif // MOCKCONTACTWATCHER_H
tests/mocks/Unity/Application/ApplicationInfo.cpp+1−1
@@ -138,7 +138,7 @@ void ApplicationInfo::createSurface()
138138
139139void ApplicationInfo::setIconId(const QString &iconId)
140140{
141− setIcon(QString("../../tests/graphics/applicationIcons/%2@18.png")
141+ setIcon(QString(sourceDirectory() + "/tests/graphics/applicationIcons/%2@18.png")
142142 .arg(iconId));
143143}
144144
tests/mocks/Unity/Application/MirSurface.h+2
@@ -106,6 +106,8 @@ public:
106106 bool focused() const override;
107107 QRect inputBounds() const override;
108108
109+ bool confinesMousePointer() const override { return false; }
110+
109111 Q_INVOKABLE void requestFocus() override;
110112
111113 Q_INVOKABLE void close() override;
tests/mocks/Unity/Indicators/CMakeLists.txt+1
@@ -16,6 +16,7 @@ set(IndicatorsFakeQml_SOURCES
1616 fakeunitymenumodelcache.cpp
1717 ${CMAKE_SOURCE_DIR}/plugins/Unity/Indicators/indicators.h
1818 ${CMAKE_SOURCE_DIR}/plugins/Unity/Indicators/menucontentactivator.cpp
19+ ${CMAKE_SOURCE_DIR}/plugins/Unity/Indicators/modelprinter.cpp
1920 ${CMAKE_SOURCE_DIR}/plugins/Unity/Indicators/sharedunitymenumodel.cpp
2021 ${CMAKE_SOURCE_DIR}/plugins/Unity/Indicators/unitymenumodelcache.cpp
2122 ${CMAKE_SOURCE_DIR}/plugins/Unity/Indicators/unitymenumodelstack.cpp
tests/mocks/Unity/Indicators/IndicatorsModel.qml+32−7
@@ -18,6 +18,7 @@ import QtQuick 2.4
1818import Unity.Indicators 0.1 as Indicators
1919import Unity.InputInfo 0.1
2020import AccountsService 0.1
21+import "fakeindicatorsmodeldata.js" as FakeIndicators
2122
2223Indicators.FakeIndicatorsModel {
2324 id: root
@@ -60,7 +61,7 @@ Indicators.FakeIndicatorsModel {
6061 }
6162 },
6263 {
63− "identifier": "fake-indicator-sound",
64+ "identifier": "fake-indicator-files",
6465 "indicatorProperties": {
6566 "enabled": true,
6667 "busName": "com.canonical.indicators.fake4",
@@ -69,7 +70,7 @@ Indicators.FakeIndicatorsModel {
6970 }
7071 },
7172 {
72− "identifier": "fake-indicator-power",
73+ "identifier": "fake-indicator-sound",
7374 "indicatorProperties": {
7475 "enabled": true,
7576 "busName": "com.canonical.indicators.fake5",
@@ -78,7 +79,7 @@ Indicators.FakeIndicatorsModel {
7879 }
7980 },
8081 {
81− "identifier": "fake-indicator-datetime",
82+ "identifier": "fake-indicator-power",
8283 "indicatorProperties": {
8384 "enabled": true,
8485 "busName": "com.canonical.indicators.fake6",
@@ -87,13 +88,22 @@ Indicators.FakeIndicatorsModel {
8788 }
8889 },
8990 {
90− "identifier": "fake-indicator-session",
91+ "identifier": "fake-indicator-datetime",
9192 "indicatorProperties": {
9293 "enabled": true,
9394 "busName": "com.canonical.indicators.fake7",
9495 "menuObjectPath": "/com/canonical/indicators/fake7",
9596 "actionsObjectPath": "/com/canonical/indicators/fake7"
9697 }
98+ },
99+ {
100+ "identifier": "fake-indicator-session",
101+ "indicatorProperties": {
102+ "enabled": true,
103+ "busName": "com.canonical.indicators.fake8",
104+ "menuObjectPath": "/com/canonical/indicators/fake8",
105+ "actionsObjectPath": "/com/canonical/indicators/fake8"
106+ }
97107 }
98108 ]
99109
@@ -133,21 +143,26 @@ Indicators.FakeIndicatorsModel {
133143 "",
134144 [ "image://theme/messages-new" ]));
135145 Indicators.UnityMenuModelCache.setCachedModelData("/com/canonical/indicators/fake4",
146+ getUnityMenuModelData("fake-indicator-files",
147+ "Files (F)",
148+ "",
149+ [ "image://theme/transfer-progress" ]));
150+ Indicators.UnityMenuModelCache.setCachedModelData("/com/canonical/indicators/fake5",
136151 getUnityMenuModelData("fake-indicator-sound",
137152 "Sound (F)",
138153 "",
139154 [ "image://theme/audio-volume-high" ]));
140− Indicators.UnityMenuModelCache.setCachedModelData("/com/canonical/indicators/fake5",
155+ Indicators.UnityMenuModelCache.setCachedModelData("/com/canonical/indicators/fake6",
141156 getUnityMenuModelData("fake-indicator-power",
142157 "Battery (F)",
143158 "",
144159 [ "image://theme/battery-020" ]));
145− Indicators.UnityMenuModelCache.setCachedModelData("/com/canonical/indicators/fake6",
160+ Indicators.UnityMenuModelCache.setCachedModelData("/com/canonical/indicators/fake7",
146161 getUnityMenuModelData("fake-indicator-datetime",
147162 "Upcoming Events (F)",
148163 "12:04",
149164 []));
150− Indicators.UnityMenuModelCache.setCachedModelData("/com/canonical/indicators/fake7",
165+ Indicators.UnityMenuModelCache.setCachedModelData("/com/canonical/indicators/fake8",
151166 getUnityMenuModelData("fake-indicator-session",
152167 "System (F)",
153168 "",
@@ -155,6 +170,16 @@ Indicators.FakeIndicatorsModel {
155170 }
156171
157172 function getUnityMenuModelData(identifier, title, label, icons) {
173+ var menudata = FakeIndicators.fakeMenuData[identifier];
174+
175+ if (menudata !== undefined) {
176+ var rootState = menudata[0]["rowData"].actionState;
177+ rootState.title = title;
178+ rootState.label = label;
179+ rootState.icons = icons;
180+ return menudata;
181+ }
182+
158183 var root = [{
159184 "rowData": { // 1
160185 "label": "",
tests/mocks/Unity/Indicators/fakeindicatorsmodeldata.jsnew file+1106
@@ -0,0 +1,1106 @@
1+var usageScenario = (typeof shell != 'undefined') ? shell.usageScenario : "desktop";
2+
3+var fakeMenuData = {
4+ "fake-indicator-messages": [
5+ {
6+ "rowData": {
7+ "action": "indicator._header",
8+ "actionState": {
9+ "icons": [ "image://theme/messages-new" ],
10+ "label": "",
11+ "title": "Messages (F)",
12+ "visible": true
13+ },
14+ "icon": "",
15+ "isCheck": false,
16+ "isRadio": false,
17+ "isSeparator": false,
18+ "isToggled": false,
19+ "label": "",
20+ "sensitive": true,
21+ "type": "com.canonical.indicator.root"
22+ },
23+ "submenu": [
24+ {
25+ "rowData": {
26+ "label": "There's an updated system image.",
27+ "sensitive": true,
28+ "isSeparator": false,
29+ "icon": "file:///usr/share/ubuntu/settings/system/icons/settings-system-update.svg",
30+ "type": "com.canonical.indicator.messages.messageitem",
31+ "ext": {
32+ "icon": "file:///usr/share/ubuntu/settings/system/icons/settings-system-update.svg",
33+ "xCanonicalAppIcon": "file:///usr/share/ubuntu/settings/system/system-settings.png",
34+ "xCanonicalMessageActions": null,
35+ "xCanonicalText": "Tap to open the system updater.",
36+ "xCanonicalTime": ((new Date).getTime() - 350 * 1000) * 1000,
37+ },
38+ "action": "indicator.ubuntu-system-settings",
39+ "actionState": {},
40+ "isCheck": false,
41+ "isRadio": false,
42+ "isToggled": false,
43+ }
44+ },
45+ {
46+ "rowData": {
47+ "action": "indicator.telephony-service-sms.msg.MSG_ID",
48+ "actionState": {},
49+ "ext": {},
50+ "icon": "file:///usr/share/telephony-service/assets/avatar-default@18.png",
51+ "type": "com.canonical.indicator.messages.messageitem",
52+ "ext": {
53+ "icon": "file:///usr/share/telephony-service/assets/avatar-default@18.png",
54+ "xCanonicalAppIcon": "image://theme/telephony-service-message",
55+ "xCanonicalMessageActions": "",
56+ "xCanonicalText": "This is a message sent to an awesome Ubuntu phone.",
57+ "xCanonicalTime": ((new Date).getTime() - 35 * 1000) * 1000
58+ },
59+ "isCheck": false,
60+ "isRadio": false,
61+ "isSeparator": false,
62+ "isToggled": false,
63+ "label": "+44391234567 - [SIM 1]",
64+ "sensitive": true
65+ }
66+ },
67+ {
68+ "rowData": {
69+ "action": "indicator.remove-all",
70+ "actionState": {},
71+ "ext": {},
72+ "icon": "",
73+ "isCheck": false,
74+ "isRadio": false,
75+ "isSeparator": false,
76+ "isToggled": false,
77+ "label": "Clear All",
78+ "sensitive": true,
79+ "type": "com.canonical.indicator.button"
80+ }
81+ },
82+ ]
83+ }
84+ ],
85+
86+ "fake-indicator-session": [
87+ {
88+ "rowData": {
89+ "action": "indicator._header",
90+ "actionState": {
91+ "icons": [],
92+ "label": "",
93+ "title": "Session (F)",
94+ "visible": usageScenario == "desktop"
95+ },
96+ "icon": "",
97+ "isCheck": false,
98+ "isRadio": false,
99+ "isSeparator": false,
100+ "isToggled": false,
101+ "label": "",
102+ "sensitive": true,
103+ "type": "com.canonical.indicator.root"
104+ },
105+ "submenu": [
106+ {
107+ "rowData": {
108+ "action": "indicator.about",
109+ "actionState": {},
110+ "ext": {},
111+ "icon": "",
112+ "isCheck": false,
113+ "isRadio": false,
114+ "isSeparator": false,
115+ "isToggled": false,
116+ "label": "About this " + usageScenario,
117+ "sensitive": true,
118+ "type": ""
119+ }
120+ },
121+ {
122+ "rowData": {
123+ "action": "indicator.help",
124+ "actionState": {},
125+ "ext": {},
126+ "icon": "",
127+ "isCheck": false,
128+ "isRadio": false,
129+ "isSeparator": false,
130+ "isToggled": false,
131+ "label": "Ubuntu Help",
132+ "sensitive": true,
133+ "type": ""
134+ }
135+ },
136+ {
137+ "rowData": {
138+ "action": "",
139+ "actionState": {},
140+ "ext": {},
141+ "icon": "",
142+ "isCheck": false,
143+ "isRadio": false,
144+ "isSeparator": true,
145+ "isToggled": false,
146+ "label": "",
147+ "sensitive": true,
148+ "type": ""
149+ }
150+ },
151+ {
152+ "rowData": {
153+ "action": "indicator.settings",
154+ "actionState": {},
155+ "ext": {},
156+ "icon": "",
157+ "isCheck": false,
158+ "isRadio": false,
159+ "isSeparator": false,
160+ "isToggled": false,
161+ "label": "System Settings...",
162+ "sensitive": true,
163+ "type": ""
164+ }
165+ },
166+ {
167+ "rowData": {
168+ "action": "",
169+ "actionState": {},
170+ "ext": {},
171+ "icon": "",
172+ "isCheck": false,
173+ "isRadio": false,
174+ "isSeparator": true,
175+ "isToggled": false,
176+ "label": "",
177+ "sensitive": true,
178+ "type": ""
179+ }
180+ },
181+ {
182+ "rowData": {
183+ "action": "indicator.switch-to-screensaver",
184+ "actionState": {},
185+ "ext": {},
186+ "icon": "",
187+ "isCheck": false,
188+ "isRadio": false,
189+ "isSeparator": false,
190+ "isToggled": false,
191+ "label": "Lock",
192+ "sensitive": true,
193+ "type": ""
194+ }
195+ },
196+ {
197+ "rowData": {
198+ "action": "indicator.switch-to-guest",
199+ "actionState": {},
200+ "ext": {},
201+ "icon": "",
202+ "isCheck": false,
203+ "isRadio": false,
204+ "isSeparator": false,
205+ "isToggled": false,
206+ "label": "Guest Session",
207+ "sensitive": true,
208+ "type": "indicator.guest-menu-item"
209+ }
210+ },
211+ {
212+ "rowData": {
213+ "action": "indicator.switch-to-user",
214+ "actionState": {},
215+ "ext": {},
216+ "icon": "",
217+ "isCheck": false,
218+ "isRadio": true,
219+ "isSeparator": false,
220+ "isToggled": false,
221+ "label": "Marco",
222+ "sensitive": true,
223+ "type": "indicator.user-menu-item"
224+ }
225+ },
226+ {
227+ "rowData": {
228+ "action": "indicator.switch-to-user",
229+ "actionState": {},
230+ "ext": {},
231+ "icon": "",
232+ "isCheck": false,
233+ "isRadio": true,
234+ "isSeparator": false,
235+ "isToggled": false,
236+ "label": "Unity8",
237+ "sensitive": true,
238+ "type": "indicator.user-menu-item"
239+ }
240+ },
241+ {
242+ "rowData": {
243+ "action": "",
244+ "actionState": {},
245+ "ext": {},
246+ "icon": "",
247+ "isCheck": false,
248+ "isRadio": false,
249+ "isSeparator": true,
250+ "isToggled": false,
251+ "label": "",
252+ "sensitive": true,
253+ "type": ""
254+ }
255+ },
256+ {
257+ "rowData": {
258+ "action": "indicator.logout",
259+ "actionState": {},
260+ "ext": {},
261+ "icon": "",
262+ "isCheck": false,
263+ "isRadio": false,
264+ "isSeparator": false,
265+ "isToggled": false,
266+ "label": "Logout...",
267+ "sensitive": true,
268+ "type": ""
269+ }
270+ },
271+ {
272+ "rowData": {
273+ "action": "",
274+ "actionState": {},
275+ "ext": {},
276+ "icon": "",
277+ "isCheck": false,
278+ "isRadio": false,
279+ "isSeparator": true,
280+ "isToggled": false,
281+ "label": "",
282+ "sensitive": true,
283+ "type": ""
284+ }
285+ },
286+ {
287+ "rowData": {
288+ "action": "indicator.suspend",
289+ "actionState": {},
290+ "ext": {},
291+ "icon": "",
292+ "isCheck": false,
293+ "isRadio": false,
294+ "isSeparator": false,
295+ "isToggled": false,
296+ "label": "Suspend",
297+ "sensitive": true,
298+ "type": ""
299+ }
300+ },
301+ {
302+ "rowData": {
303+ "action": "indicator.power-off",
304+ "actionState": {},
305+ "ext": {},
306+ "icon": "",
307+ "isCheck": false,
308+ "isRadio": false,
309+ "isSeparator": false,
310+ "isToggled": false,
311+ "label": "Power off...",
312+ "sensitive": true,
313+ "type": ""
314+ }
315+ }
316+ ]
317+ }
318+ ],
319+
320+ "fake-indicator-power": [
321+ {
322+ "rowData": {
323+ "action": "indicator._header",
324+ "actionState": {
325+ "icons": [ "image://theme/battery-020" ],
326+ "label": "",
327+ "title": "Battery (F)",
328+ "visible": true
329+ },
330+ "icon": "",
331+ "isCheck": false,
332+ "isRadio": false,
333+ "isSeparator": false,
334+ "isToggled": false,
335+ "label": "",
336+ "sensitive": true,
337+ "type": "com.canonical.indicator.root"
338+ },
339+ "submenu": [
340+ {
341+ "rowData": {
342+ "action": "indicator.activate-statistics",
343+ "actionState": {},
344+ "ext": {},
345+ "icon": "image://theme/battery-full-charged-symbolic",
346+ "isCheck": false,
347+ "isRadio": false,
348+ "isSeparator": false,
349+ "isToggled": false,
350+ "label": "Battery (full charged)",
351+ "sensitive": true,
352+ "type": "com.canonical.indicator.basic"
353+ }
354+ },
355+ {
356+ "rowData": {
357+ "action": "indicator.activate-statistics",
358+ "actionState": {},
359+ "ext": {},
360+ "icon": "image://theme/battery-100",
361+ "isCheck": false,
362+ "isRadio": false,
363+ "isSeparator": false,
364+ "isToggled": false,
365+ "label": "Battery",
366+ "sensitive": true,
367+ "type": "com.canonical.indicator.basic"
368+ }
369+ },
370+ {
371+ "rowData": {
372+ "action": "",
373+ "actionState": {},
374+ "ext": {},
375+ "icon": "",
376+ "isCheck": false,
377+ "isRadio": false,
378+ "isSeparator": true,
379+ "isToggled": false,
380+ "label": " ",
381+ "sensitive": true,
382+ "type": ""
383+ }
384+ },
385+ {
386+ "rowData": {
387+ "action": "indicator.show-time",
388+ "actionState": false,
389+ "ext": {},
390+ "icon": "",
391+ "isCheck": true,
392+ "isRadio": false,
393+ "isSeparator": false,
394+ "isToggled": false,
395+ "label": "Show time on menubar",
396+ "sensitive": true,
397+ "type": ""
398+ }
399+ },
400+ {
401+ "rowData": {
402+ "action": "indicator.show-percentage",
403+ "actionState": false,
404+ "ext": {},
405+ "icon": "",
406+ "isCheck": true,
407+ "isRadio": false,
408+ "isSeparator": false,
409+ "isToggled": false,
410+ "label": "Show percentage on menubar",
411+ "sensitive": true,
412+ "type": ""
413+ }
414+ },
415+ {
416+ "rowData": {
417+ "action": "indicator.activate-settings",
418+ "actionState": {},
419+ "ext": {},
420+ "icon": "",
421+ "isCheck": false,
422+ "isRadio": false,
423+ "isSeparator": false,
424+ "isToggled": false,
425+ "label": "Power Settings...",
426+ "sensitive": true,
427+ "type": ""
428+ }
429+ }
430+ ]
431+ }
432+ ],
433+
434+ "fake-indicator-sound": [
435+ {
436+ "rowData": {
437+ "action": "indicator._header",
438+ "actionState": {
439+ "icons": [ "image://theme/audio-volume-high" ],
440+ "label": "",
441+ "title": "Sound (F)",
442+ "visible": true
443+ },
444+ "icon": "",
445+ "isCheck": false,
446+ "isRadio": false,
447+ "isSeparator": false,
448+ "isToggled": false,
449+ "label": "",
450+ "sensitive": true,
451+ "type": "com.canonical.indicator.root"
452+ },
453+ "submenu": [
454+ {
455+ "rowData": {
456+ "action": "indicator.silent-mode",
457+ "actionState": true,
458+ "ext": {},
459+ "icon": "",
460+ "isCheck": true,
461+ "isRadio": false,
462+ "isSeparator": false,
463+ "isToggled": false,
464+ "label": "Silent Mode",
465+ "sensitive": true,
466+ "type": "com.canonical.indicator.switch"
467+ }
468+ },
469+ {
470+ "rowData": {
471+ "action": "indicator.volume",
472+ "actionState": 0.35,
473+ "ext": {
474+ "maxIcon": "image://theme/audio-volume-high-panel",
475+ "maxValue": 1,
476+ "minIcon": "image://theme/audio-volume-low-zero-panel",
477+ "minValue": 0,
478+ "xCanonicalSyncAction": "indicator.volume-sync"
479+ },
480+ "icon": "",
481+ "isCheck": false,
482+ "isRadio": true,
483+ "isSeparator": false,
484+ "isToggled": false,
485+ "label": "Volume",
486+ "sensitive": true,
487+ "type": "com.canonical.unity.slider"
488+ }
489+ },
490+ {
491+ "rowData": {
492+ "action": "",
493+ "actionState": {},
494+ "ext": {},
495+ "icon": "",
496+ "isCheck": false,
497+ "isRadio": false,
498+ "isSeparator": true,
499+ "isToggled": false,
500+ "label": "",
501+ "sensitive": true,
502+ "type": ""
503+ }
504+ },
505+ {
506+ "rowData": {
507+ "action": "indicator.mediaplayer-app.desktop",
508+ "actionState": {
509+ "running": true,
510+ "state": "Stopped"
511+ },
512+ "ext": {},
513+ "icon": "file:///usr/share/mediaplayer-app/mediaplayer-app.png",
514+ "isCheck": false,
515+ "isRadio": false,
516+ "isSeparator": false,
517+ "isToggled": false,
518+ "label": "Media Player",
519+ "sensitive": false,
520+ "type": "com.canonical.unity.media-player"
521+ }
522+ },
523+ {
524+ "rowData": {
525+ "actionState": {},
526+ "ext": {
527+ "action": ""
528+ },
529+ "icon": "",
530+ "isCheck": false,
531+ "isRadio": false,
532+ "isSeparator": false,
533+ "isToggled": false,
534+ "label": "",
535+ "sensitive": true,
536+ "type": "com.canonical.unity.playback-item"
537+ }
538+ },
539+ {
540+ "rowData": {
541+ "action": "indicator.phone-settings",
542+ "actionState": {},
543+ "ext": {},
544+ "icon": "",
545+ "isCheck": false,
546+ "isRadio": false,
547+ "isSeparator": false,
548+ "isToggled": false,
549+ "label": "Sound Settings…",
550+ "sensitive": true,
551+ "type": ""
552+ }
553+ }
554+ ]
555+ }
556+ ],
557+
558+
559+ "fake-indicator-datetime": [
560+ {
561+ "rowData": {
562+ "action": "indicator._header",
563+ "actionState": {
564+ "icons": [],
565+ "label": "16.10",
566+ "title": "Upcoming Events (F)",
567+ "visible": true
568+ },
569+ "icon": "",
570+ "isCheck": false,
571+ "isRadio": false,
572+ "isSeparator": false,
573+ "isToggled": false,
574+ "label": "",
575+ "sensitive": true,
576+ "type": "com.canonical.indicator.root"
577+ },
578+ "submenu": [
579+ {
580+ "rowData": {
581+ "action": "indicator.phone.open-calendar-app",
582+ "actionState": {},
583+ "ext": {},
584+ "icon": "image://theme/calendar",
585+ "isCheck": false,
586+ "isRadio": false,
587+ "isSeparator": false,
588+ "isToggled": false,
589+ "label": "Saturday, 31 December 2016",
590+ "sensitive": true,
591+ "type": ""
592+ }
593+ },
594+ {
595+ "rowData": {
596+ "action": "",
597+ "actionState": {},
598+ "ext": {},
599+ "icon": "fake-indicator-datetime",
600+ "isCheck": false,
601+ "isRadio": false,
602+ "isSeparator": true,
603+ "isToggled": false,
604+ "label": "",
605+ "sensitive": true,
606+ "type": ""
607+ }
608+ },
609+ {
610+ "rowData": {
611+ "action": "indicator.phone.open-alarm-app",
612+ "actionState": {},
613+ "ext": {},
614+ "icon": "image://theme/clock",
615+ "isCheck": false,
616+ "isRadio": false,
617+ "isSeparator": false,
618+ "isToggled": false,
619+ "label": "Clock",
620+ "sensitive": true,
621+ "type": ""
622+ }
623+ },
624+ {
625+ "rowData": {
626+ "action": "indicator.phone.open-appointment",
627+ "actionState": {},
628+ "ext": {
629+ "xCanonicalColor": Qt.rgba(32,74,135,1),
630+ "xCanonicalTime": (new Date).getTime() / 1000 + 35000
631+ },
632+ "icon": "",
633+ "isCheck": false,
634+ "isRadio": false,
635+ "isSeparator": false,
636+ "isToggled": false,
637+ "label": "Finish Indicator mocks",
638+ "sensitive": true,
639+ "type": "com.canonical.indicator.appointment"
640+ }
641+ },
642+ {
643+ "rowData": {
644+ "action": "indicator.phone.open-appointment",
645+ "actionState": {},
646+ "ext": {
647+ "xCanonicalColor": Qt.rgba(32,74,135,1),
648+ "xCanonicalTime": (new Date).getTime() / 1000 + 50474
649+ },
650+ "icon": "",
651+ "isCheck": false,
652+ "isRadio": false,
653+ "isSeparator": false,
654+ "isToggled": false,
655+ "label": "Implement Desktop version",
656+ "sensitive": true,
657+ "type": "com.canonical.indicator.appointment"
658+ }
659+ },
660+ {
661+ "rowData": {
662+ "action": "indicator.phone.open-appointment",
663+ "actionState": {},
664+ "ext": {
665+ "xCanonicalColor": Qt.rgba(32,74,135,1),
666+ "xCanonicalTime": (new Date).getTime() / 1000 + 385000
667+ },
668+ "icon": "",
669+ "isCheck": false,
670+ "isRadio": false,
671+ "isSeparator": false,
672+ "isToggled": false,
673+ "label": "Ubuntu drinks event",
674+ "sensitive": true,
675+ "type": "com.canonical.indicator.appointment"
676+ }
677+ },
678+ {
679+ "rowData": {
680+ "action": "indicator.phone.open-appointment",
681+ "actionState": {},
682+ "ext": {
683+ "xCanonicalColor": Qt.rgba(32,74,135,1),
684+ "xCanonicalTime": (new Date).getTime() / 1000 + 3500050
685+ },
686+ "icon": "",
687+ "isCheck": false,
688+ "isRadio": false,
689+ "isSeparator": false,
690+ "isToggled": false,
691+ "label": "Land unity8",
692+ "sensitive": true,
693+ "type": "com.canonical.indicator.appointment"
694+ }
695+ },
696+ {
697+ "rowData": {
698+ "action": "",
699+ "actionState": {},
700+ "ext": {},
701+ "icon": "",
702+ "isCheck": false,
703+ "isRadio": false,
704+ "isSeparator": true,
705+ "isToggled": false,
706+ "label": "",
707+ "sensitive": true,
708+ "type": ""
709+ }
710+ },
711+ {
712+ "rowData": {
713+ "action": "indicator.phone.open-settings-app",
714+ "actionState": {},
715+ "ext": {},
716+ "icon": "",
717+ "isCheck": false,
718+ "isRadio": false,
719+ "isSeparator": false,
720+ "isToggled": false,
721+ "label": "Date and Time settings…",
722+ "sensitive": true,
723+ "type": ""
724+ }
725+ }
726+ ]
727+ }
728+ ],
729+
730+
731+ "fake-indicator-files": [
732+ {
733+ "rowData": {
734+ "action": "indicator._header",
735+ "actionState": {
736+ "icons": [],
737+ "label": "Files",
738+ "title": "Files",
739+ "visible": true
740+ },
741+ "icon": "",
742+ "isCheck": false,
743+ "isRadio": false,
744+ "isSeparator": false,
745+ "isToggled": false,
746+ "label": "",
747+ "sensitive": true,
748+ "type": "com.canonical.indicator.root"
749+ },
750+ "submenu": [
751+ {
752+ "rowData": {
753+ "action": "indicator.pause-all",
754+ "actionState": {},
755+ "ext": {
756+ "xCanonicalExtraLabel": "Pause all"
757+ },
758+ "icon": "",
759+ "isCheck": false,
760+ "isRadio": false,
761+ "isSeparator": false,
762+ "isToggled": false,
763+ "label": "",
764+ "sensitive": true,
765+ "type": "com.canonical.indicator.button-section"
766+ }
767+ },
768+ {
769+ "rowData": {
770+ "action": "indicator.activate-transfer",
771+ "actionState": {},
772+ "ext": {
773+ "xCanonicalUid": 1003
774+ },
775+ "icon": "file:///usr/share/webbrowser-app/webbrowser-app.png",
776+ "isCheck": false,
777+ "isRadio": false,
778+ "isSeparator": false,
779+ "isToggled": false,
780+ "label": "Ubuntu daily.iso",
781+ "sensitive": true,
782+ "type": "com.canonical.indicator.transfer"
783+ }
784+ },
785+ {
786+ "rowData": {
787+ "action": "",
788+ "actionState": {},
789+ "ext": {},
790+ "icon": "",
791+ "isCheck": false,
792+ "isRadio": false,
793+ "isSeparator": true,
794+ "isToggled": false,
795+ "label": "",
796+ "sensitive": true,
797+ "type": ""
798+ }
799+ },
800+ {
801+ "rowData": {
802+ "action": "indicator.clear-all",
803+ "actionState": {},
804+ "ext": {
805+ "xCanonicalExtraLabel": "Clear all"
806+ },
807+ "icon": "",
808+ "isCheck": false,
809+ "isRadio": false,
810+ "isSeparator": false,
811+ "isToggled": false,
812+ "label": "Successful Transfers",
813+ "sensitive": true,
814+ "type": "com.canonical.indicator.button-section"
815+ }
816+ },
817+ {
818+ "rowData": {
819+ "action": "indicator.activate-transfer",
820+ "actionState": {},
821+ "ext": {
822+ "xCanonicalUid": 1002
823+ },
824+ "icon": "file:///usr/share/webbrowser-app/webbrowser-app.png",
825+ "isCheck": false,
826+ "isRadio": false,
827+ "isSeparator": false,
828+ "isToggled": false,
829+ "label": "Ubuntu Guide.pdf",
830+ "sensitive": true,
831+ "type": "com.canonical.indicator.transfer"
832+ }
833+ },
834+ {
835+ "rowData": {
836+ "action": "indicator.activate-transfer",
837+ "actionState": {},
838+ "ext": {
839+ "xCanonicalUid": 1001
840+ },
841+ "icon": "file:///usr/share/webbrowser-app/webbrowser-app.png",
842+ "isCheck": false,
843+ "isRadio": false,
844+ "isSeparator": false,
845+ "isToggled": false,
846+ "label": "Unity8 Hacking.pdf",
847+ "sensitive": true,
848+ "type": "com.canonical.indicator.transfer"
849+ }
850+ }
851+ ]
852+ }
853+ ],
854+
855+ "fake-indicator-network": [
856+ {
857+ "rowData": {
858+ "action": "indicator._header",
859+ "actionState": {
860+ "icons": [],
861+ "label": "",
862+ "title": "Network",
863+ "visible": true
864+ },
865+ "icon": "",
866+ "isCheck": false,
867+ "isRadio": false,
868+ "isSeparator": false,
869+ "isToggled": false,
870+ "label": "",
871+ "sensitive": true,
872+ "type": "com.canonical.indicator.root"
873+ },
874+ "submenu": [
875+ {
876+ "rowData": {
877+ "action": "indicator.airplane.enabled",
878+ "actionState": false,
879+ "ext": {},
880+ "icon": "",
881+ "isCheck": true,
882+ "isRadio": false,
883+ "isSeparator": false,
884+ "isToggled": false,
885+ "label": "Flight Mode",
886+ "sensitive": true,
887+ "type": "com.canonical.indicator.switch"
888+ }
889+ },
890+ /* Anything to see here, since no fake modem actions are implemented
891+ {
892+ "rowData": {
893+ "actionState": false,
894+ "ext": {},
895+ "icon": "",
896+ "isCheck": false,
897+ "isRadio": false,
898+ "isSeparator": true,
899+ "isToggled": false,
900+ "label": "",
901+ "sensitive": true,
902+ "type": ""
903+ }
904+ },
905+ {
906+ "rowData": {
907+ "actionState": false,
908+ "ext": {
909+ "xCanonicalModemConnectivityIconAction": "indicator.modem.1::connectivity-icon",
910+ "xCanonicalModemLockedAction": "indicator.modem.1::locked",
911+ "xCanonicalModemRoamingAction": "indicator.modem.1::roaming",
912+ "xCanonicalModemSimIdentifierLabelAction": "indicator.modem.1::sim-identifier-label",
913+ "xCanonicalModemStatusIconAction": "indicator.modem.1::status-icon",
914+ "xCanonicalModemStatusLabelAction": "indicator.modem.1::status-label"
915+ },
916+ "icon": "",
917+ "isCheck": false,
918+ "isRadio": false,
919+ "isSeparator": false,
920+ "isToggled": false,
921+ "label": "",
922+ "sensitive": true,
923+ "type": "com.canonical.indicator.network.modeminfoitem",
924+ "visible": false
925+ }
926+ },
927+ */
928+ {
929+ "rowData": {
930+ "action": "indicator.wifi.enable",
931+ "actionState": true,
932+ "ext": {},
933+ "icon": "",
934+ "isCheck": true,
935+ "isRadio": false,
936+ "isSeparator": false,
937+ "isToggled": true,
938+ "label": "Wi-Fi",
939+ "sensitive": true,
940+ "type": "com.canonical.indicator.switch"
941+ }
942+ },
943+ {
944+ "rowData": {
945+ "action": "",
946+ "actionState": {},
947+ "ext": {},
948+ "icon": "",
949+ "isCheck": false,
950+ "isRadio": false,
951+ "isSeparator": true,
952+ "isToggled": false,
953+ "label": "",
954+ "sensitive": true,
955+ "type": ""
956+ }
957+ },
958+ {
959+ "rowData": {
960+ "action": "indicator.accesspoint.1",
961+ "actionState": true,
962+ "ext": {
963+ "xCanonicalWifiApIsAdhoc": 0,
964+ "xCanonicalWifiApIsSecure": 1,
965+ "xCanonicalWifiApStrengthAction": "indicator.accesspoint.1::strength"
966+ },
967+ "icon": "",
968+ "isCheck": true,
969+ "isRadio": false,
970+ "isSeparator": false,
971+ "isToggled": true,
972+ "label": "Canonical",
973+ "sensitive": true,
974+ "type": "unity.widgets.systemsettings.tablet.accesspoint"
975+ }
976+ },
977+ {
978+ "rowData": {
979+ "action": "indicator.accesspoint.2",
980+ "actionState": false,
981+ "ext": {
982+ "xCanonicalWifiApIsAdhoc": 0,
983+ "xCanonicalWifiApIsSecure": 1,
984+ "xCanonicalWifiApStrengthAction": "indicator.accesspoint.2::strength"
985+ },
986+ "icon": "",
987+ "isCheck": true,
988+ "isRadio": false,
989+ "isSeparator": false,
990+ "isToggled": false,
991+ "label": "Ubuntu",
992+ "sensitive": true,
993+ "type": "unity.widgets.systemsettings.tablet.accesspoint"
994+ }
995+ },
996+ {
997+ "rowData": {
998+ "action": "indicator.accesspoint.3",
999+ "actionState": false,
1000+ "ext": {
1001+ "xCanonicalWifiApIsAdhoc": 0,
1002+ "xCanonicalWifiApIsSecure": 0,
1003+ "xCanonicalWifiApStrengthAction": "indicator.accesspoint.3::strength"
1004+ },
1005+ "icon": "",
1006+ "isCheck": true,
1007+ "isRadio": false,
1008+ "isSeparator": false,
1009+ "isToggled": false,
1010+ "label": "Linux",
1011+ "sensitive": true,
1012+ "type": "unity.widgets.systemsettings.tablet.accesspoint"
1013+ }
1014+ },
1015+ {
1016+ "rowData": {
1017+ "action": "indicator.accesspoint.4",
1018+ "actionState": false,
1019+ "ext": {
1020+ "xCanonicalWifiApIsAdhoc": 0,
1021+ "xCanonicalWifiApIsSecure": 0,
1022+ "xCanonicalWifiApStrengthAction": "indicator.accesspoint.4::strength"
1023+ },
1024+ "icon": "",
1025+ "isCheck": true,
1026+ "isRadio": false,
1027+ "isSeparator": false,
1028+ "isToggled": false,
1029+ "label": "for Human Beings",
1030+ "sensitive": true,
1031+ "type": "unity.widgets.systemsettings.tablet.accesspoint"
1032+ }
1033+ },
1034+ {
1035+ "rowData": {
1036+ "action": "indicator.wifi.settings",
1037+ "actionState": {},
1038+ "ext": {},
1039+ "icon": "",
1040+ "isCheck": false,
1041+ "isRadio": false,
1042+ "isSeparator": false,
1043+ "isToggled": false,
1044+ "label": "Wi-Fi settings…",
1045+ "sensitive": true,
1046+ "type": ""
1047+ }
1048+ }
1049+ ]
1050+ }
1051+ ],
1052+
1053+ "fake-indicator-bluetooth": [
1054+ {
1055+ "rowData": {
1056+ "action": "indicator._header",
1057+ "actionState": {
1058+ "icons": [],
1059+ "label": "",
1060+ "title": "Network",
1061+ "visible": true
1062+ },
1063+ "icon": "",
1064+ "isCheck": false,
1065+ "isRadio": false,
1066+ "isSeparator": false,
1067+ "isToggled": false,
1068+ "label": "",
1069+ "sensitive": true,
1070+ "type": "com.canonical.indicator.root"
1071+ },
1072+ "submenu": [
1073+ {
1074+ "rowData": {
1075+ "action": "indicator.bluetooth.enable",
1076+ "actionState": true,
1077+ "ext": {},
1078+ "icon": "",
1079+ "isCheck": true,
1080+ "isRadio": false,
1081+ "isSeparator": false,
1082+ "isToggled": true,
1083+ "label": "Bluetooth",
1084+ "sensitive": true,
1085+ "type": "com.canonical.indicator.switch"
1086+ }
1087+ },
1088+ {
1089+ "rowData": {
1090+ "action": "indicator.bluetooth.settings",
1091+ "actionState": {},
1092+ "ext": {},
1093+ "icon": "",
1094+ "isCheck": false,
1095+ "isRadio": false,
1096+ "isSeparator": false,
1097+ "isToggled": false,
1098+ "label": "Bluetooth settings…",
1099+ "sensitive": true,
1100+ "type": ""
1101+ }
1102+ }
1103+ ]
1104+ }
1105+ ]
1106+}
tests/mocks/Unity/Indicators/fakeplugin.cpp+2
@@ -29,6 +29,7 @@
2929#include "sharedunitymenumodel.h"
3030#include "fakeunitymenumodelcache.h"
3131#include "unitymenumodelstack.h"
32+#include "modelprinter.h"
3233
3334#include <unitymenumodel.h>
3435
@@ -49,6 +50,7 @@ void IndicatorsFakePlugin::registerTypes(const char * uri)
4950 qmlRegisterType<MenuContentActivator>(uri, 0, 1, "MenuContentActivator");
5051 qmlRegisterType<UnityMenuModelStack>(uri, 0, 1, "UnityMenuModelStack");
5152 qmlRegisterType<SharedUnityMenuModel>(uri, 0, 1, "SharedUnityMenuModel");
53+ qmlRegisterType<ModelPrinter>(uri, 0, 1, "ModelPrinter");
5254
5355 qmlRegisterSingletonType<FakeUnityMenuModelCache>(uri, 0, 1, "UnityMenuModelCache", menuModelCacheSingleton);
5456
tests/mocks/Utils/CMakeLists.txt+1
@@ -32,6 +32,7 @@ set(QMLPLUGIN_SRC
3232 constants.cpp
3333 plugin.cpp
3434 windowstatestorage.cpp
35+ URLDispatcher.cpp
3536 )
3637
3738add_library(FakeUtils-qml SHARED
tests/mocks/Utils/URLDispatcher.cppnew file+38
@@ -0,0 +1,38 @@
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 "URLDispatcher.h"
18+
19+#include <QDBusConnection>
20+
21+URLDispatcher::URLDispatcher(QObject *parent)
22+ : QObject(parent)
23+ , m_active(false)
24+{
25+}
26+
27+bool URLDispatcher::active() const
28+{
29+ return m_active;
30+}
31+
32+void URLDispatcher::setActive(bool value)
33+{
34+ if (m_active != value) {
35+ m_active = value;
36+ Q_EMIT activeChanged();
37+ }
38+}
tests/mocks/Utils/URLDispatcher.hnew file+47
@@ -0,0 +1,47 @@
1+/*
2+ * Copyright (C) 2016 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef UNITY_URLDISPATCHER_H
18+#define UNITY_URLDISPATCHER_H
19+
20+#include <QObject>
21+#include <QString>
22+
23+// This class manages our url-dispatcher interception. We intercept
24+// url-dispatcher because rather than spawning the handler for the URL
25+// in our own session, we want to notify the user session to do it for us
26+// (and start an unlock in the process).
27+
28+class URLDispatcher : public QObject
29+{
30+ Q_OBJECT
31+ Q_PROPERTY(bool active READ active WRITE setActive NOTIFY activeChanged)
32+
33+public:
34+ explicit URLDispatcher(QObject *parent=0);
35+
36+ bool active() const;
37+ void setActive(bool active);
38+
39+Q_SIGNALS:
40+ void urlRequested(const QString &url);
41+ void activeChanged();
42+
43+private:
44+ bool m_active;
45+};
46+
47+#endif
tests/mocks/Utils/plugin.cpp+2
@@ -25,6 +25,7 @@
2525#include "plugin.h"
2626#include "windowstatestorage.h"
2727#include "constants.h"
28+#include "URLDispatcher.h"
2829
2930// plugin
3031#include <activefocuslogger.h>
@@ -80,4 +81,5 @@ void FakeUtilsPlugin::registerTypes(const char *uri)
8081 qmlRegisterType<InputEventGenerator>(uri, 0, 1, "InputEventGenerator");
8182 qmlRegisterType<DeviceConfigParser>(uri, 0, 1, "DeviceConfigParser");
8283 qmlRegisterSingletonType<GlobalFunctions>(uri, 0, 1, "Functions", createGlobalFunctions);
84+ qmlRegisterType<URLDispatcher>(uri, 0, 1, "URLDispatcher");
8385}
tests/mocks/Utils/windowstatestorage.cpp+2−2
@@ -41,7 +41,7 @@ void WindowStateStorage::saveGeometry(const QString &windowId, const QRect &rect
4141 m_geometry[windowId] = rect;
4242}
4343
44−QRect WindowStateStorage::getGeometry(const QString &windowId, const QRect &defaultValue)
44+QRect WindowStateStorage::getGeometry(const QString &windowId, const QRect &defaultValue) const
4545{
4646 if (!m_geometry.contains(windowId)) return defaultValue;
4747 return m_geometry.value(windowId).toRect();
@@ -70,7 +70,7 @@ void WindowStateStorage::saveState(const QString &windowId, WindowState state)
7070 m_state[windowId] = state;
7171}
7272
73−WindowStateStorage::WindowState WindowStateStorage::getState(const QString &windowId, WindowStateStorage::WindowState defaultValue)
73+WindowStateStorage::WindowState WindowStateStorage::getState(const QString &windowId, WindowStateStorage::WindowState defaultValue) const
7474{
7575 if (!m_state.contains(windowId)) return defaultValue;
7676 return m_state.value(windowId);
tests/mocks/Utils/windowstatestorage.h+8−3
@@ -32,7 +32,12 @@ public:
3232 WindowStateMaximizedLeft = 1 << 4,
3333 WindowStateMaximizedRight = 1 << 5,
3434 WindowStateMaximizedHorizontally = 1 << 6,
35− WindowStateMaximizedVertically = 1 << 7
35+ WindowStateMaximizedVertically = 1 << 7,
36+ WindowStateMaximizedTopLeft = 1 << 8,
37+ WindowStateMaximizedTopRight = 1 << 9,
38+ WindowStateMaximizedBottomLeft = 1 << 10,
39+ WindowStateMaximizedBottomRight = 1 << 11,
40+ WindowStateRestored = 1 << 12
3641 };
3742 Q_DECLARE_FLAGS(WindowStates, WindowState)
3843#if (QT_VERSION >= QT_VERSION_CHECK(5, 5, 0))
@@ -42,10 +47,10 @@ public:
4247 WindowStateStorage(QObject *parent = 0);
4348
4449 Q_INVOKABLE void saveState(const QString &windowId, WindowState state);
45− Q_INVOKABLE WindowState getState(const QString &windowId, WindowState defaultValue);
50+ Q_INVOKABLE WindowState getState(const QString &windowId, WindowState defaultValue) const;
4651
4752 Q_INVOKABLE void saveGeometry(const QString &windowId, const QRect &rect);
48− Q_INVOKABLE QRect getGeometry(const QString &windowId, const QRect &defaultValue);
53+ Q_INVOKABLE QRect getGeometry(const QString &windowId, const QRect &defaultValue) const;
4954
5055 Q_INVOKABLE void saveStage(const QString &appId, int stage);
5156 Q_INVOKABLE int getStage(const QString &appId, int defaultValue) const;
tests/plugins/CMakeLists.txt+2
@@ -3,7 +3,9 @@ add_subdirectory(Cursor)
33add_subdirectory(Dash)
44add_subdirectory(GlobalShortcut)
55add_subdirectory(Greeter)
6+add_subdirectory(ImageCache)
67add_subdirectory(LightDM)
8+add_subdirectory(SessionBroadcast)
79add_subdirectory(Ubuntu)
810add_subdirectory(Unity)
911add_subdirectory(Utils)
tests/plugins/Dash/cardcreator/1.res+8−7
@@ -11,7 +11,6 @@ AbstractButton {
1111 property int fixedHeaderHeight: -1;
1212 property size fixedArtShapeSize: Qt.size(-1, -1);
1313signal action(var actionId);
14−readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);
1514Loader {
1615 id: artShapeLoader;
1716 height: root.fixedArtShapeSize.height;
@@ -69,13 +68,15 @@ Label {
6968 font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
7069 horizontalAlignment: Text.AlignHCenter;
7170 }
72−UbuntuShape {
73− id: touchdown;
74− objectName: "touchdown";
71+Loader {
72+ active: root.pressed;
7573 anchors { fill: artShapeLoader }
76− visible: root.pressed;
77− radius: "medium";
78− borderSource: "radius_pressed.sci"
74+ sourceComponent: UbuntuShape {
75+ objectName: "touchdown";
76+ anchors.fill: parent;
77+ radius: "medium";
78+ borderSource: "radius_pressed.sci"
79+ }
7980}
8081implicitHeight: titleLabel.y + titleLabel.height + units.gu(1);
8182}
tests/plugins/Dash/cardcreator/1.res.cardcreator−8
@@ -65,13 +65,5 @@ Label {
6565 font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
6666 horizontalAlignment: Text.AlignHCenter;
6767 }
68−UbuntuShape {
69− id: touchdown;
70− objectName: "touchdown";
71− anchors { fill: artShapeLoader }
72− visible: root.pressed;
73− radius: "medium";
74− borderSource: "radius_pressed.sci"
75−}
7668implicitHeight: titleLabel.y + titleLabel.height + units.gu(1);
7769}
tests/plugins/Dash/cardcreator/10.res−1
@@ -49,7 +49,6 @@ Loader {
4949 }
5050 }
5151 }
52−readonly property size artShapeSize: Qt.size(-1, -1);
5352readonly property int headerHeight: row.height;
5453Row {
5554 id: row;
tests/plugins/Dash/cardcreator/11.res+10−9
@@ -49,7 +49,6 @@ Loader {
4949 }
5050 }
5151 }
52−readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);
5352Loader {
5453 id: artShapeLoader;
5554 height: root.fixedArtShapeSize.height;
@@ -163,13 +162,15 @@ top: titleLabel.bottom;
163162
164163 ]
165164 }
166−UbuntuShape {
167− id: touchdown;
168− objectName: "touchdown";
169− anchors { fill: backgroundLoader }
170− visible: root.pressed;
171− radius: "medium";
172− borderSource: "radius_pressed.sci"
173− }
165+Loader {
166+ active: root.pressed;
167+ anchors { fill: backgroundLoader }
168+ sourceComponent: UbuntuShape {
169+ objectName: "touchdown";
170+ anchors.fill: parent;
171+ radius: "medium";
172+ borderSource: "radius_pressed.sci"
173+ }
174+}
174175implicitHeight: row.y + row.height + units.gu(1);
175176}
tests/plugins/Dash/cardcreator/11.res.cardcreator−8
@@ -156,13 +156,5 @@ top: titleLabel.bottom;
156156
157157 ]
158158 }
159−UbuntuShape {
160− id: touchdown;
161− objectName: "touchdown";
162− anchors { fill: backgroundLoader }
163− visible: root.pressed;
164− radius: "medium";
165− borderSource: "radius_pressed.sci"
166− }
167159implicitHeight: row.y + row.height + units.gu(1);
168160}
tests/plugins/Dash/cardcreator/12.res+8−7
@@ -11,7 +11,6 @@ AbstractButton {
1111 property int fixedHeaderHeight: -1;
1212 property size fixedArtShapeSize: Qt.size(-1, -1);
1313signal action(var actionId);
14−readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);
1514Loader {
1615 id: artShapeLoader;
1716 height: root.fixedArtShapeSize.height;
@@ -95,13 +94,15 @@ CardSocialActions {
9594 model: cardData && cardData["socialActions"];
9695 onClicked: root.action(actionId);
9796}
98−UbuntuShape {
99− id: touchdown;
100− objectName: "touchdown";
97+Loader {
98+ active: root.pressed;
10199 anchors { fill: artShapeLoader }
102− visible: root.pressed;
103− radius: "medium";
104− borderSource: "radius_pressed.sci"
100+ sourceComponent: UbuntuShape {
101+ objectName: "touchdown";
102+ anchors.fill: parent;
103+ radius: "medium";
104+ borderSource: "radius_pressed.sci"
105+ }
105106}
106107implicitHeight: socialActionsRow.y + socialActionsRow.height + units.gu(1);
107108}
tests/plugins/Dash/cardcreator/12.res.cardcreator−8
@@ -91,13 +91,5 @@ CardSocialActions {
9191 model: cardData && cardData["socialActions"];
9292 onClicked: root.action(actionId);
9393}
94−UbuntuShape {
95− id: touchdown;
96− objectName: "touchdown";
97− anchors { fill: artShapeLoader }
98− visible: root.pressed;
99− radius: "medium";
100− borderSource: "radius_pressed.sci"
101−}
10294implicitHeight: socialActionsRow.y + socialActionsRow.height + units.gu(1);
10395}
tests/plugins/Dash/cardcreator/2.res−1
@@ -49,7 +49,6 @@ Loader {
4949 }
5050 }
5151 }
52−readonly property size artShapeSize: Qt.size(-1, -1);
5352readonly property int headerHeight: row.height;
5453Row {
5554 id: row;
tests/plugins/Dash/cardcreator/3.res−1
@@ -11,7 +11,6 @@ AbstractButton {
1111 property int fixedHeaderHeight: -1;
1212 property size fixedArtShapeSize: Qt.size(-1, -1);
1313signal action(var actionId);
14−readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);
1514Loader {
1615 id: artShapeLoader;
1716 height: root.fixedArtShapeSize.height;
tests/plugins/Dash/cardcreator/4.res+8−7
@@ -11,7 +11,6 @@ AbstractButton {
1111 property int fixedHeaderHeight: -1;
1212 property size fixedArtShapeSize: Qt.size(-1, -1);
1313signal action(var actionId);
14−readonly property size artShapeSize: Qt.size(-1, -1);
1514readonly property int headerHeight: row.height;
1615Row {
1716 id: row;
@@ -98,13 +97,15 @@ Label {
9897}
9998]
10099}
101−UbuntuShape {
102− id: touchdown;
103− objectName: "touchdown";
100+Loader {
101+ active: root.pressed;
104102 anchors { fill: root }
105− visible: root.pressed;
106− radius: "medium";
107− borderSource: "radius_pressed.sci"
103+ sourceComponent: UbuntuShape {
104+ objectName: "touchdown";
105+ anchors.fill: parent;
106+ radius: "medium";
107+ borderSource: "radius_pressed.sci"
108+ }
108109}
109110implicitHeight: row.y + row.height + units.gu(1);
110111}
tests/plugins/Dash/cardcreator/4.res.cardcreator−8
@@ -95,13 +95,5 @@ Label {
9595}
9696]
9797}
98−UbuntuShape {
99− id: touchdown;
100− objectName: "touchdown";
101− anchors { fill: root }
102− visible: root.pressed;
103− radius: "medium";
104− borderSource: "radius_pressed.sci"
105−}
10698implicitHeight: row.y + row.height + units.gu(1);
10799}
tests/plugins/Dash/cardcreator/5.res−1
@@ -11,7 +11,6 @@ AbstractButton {
1111 property int fixedHeaderHeight: -1;
1212 property size fixedArtShapeSize: Qt.size(-1, -1);
1313signal action(var actionId);
14−readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);
1514Loader {
1615 id: artShapeLoader;
1716 height: root.fixedArtShapeSize.height;
tests/plugins/Dash/cardcreator/6.res+8−7
@@ -49,7 +49,6 @@ Loader {
4949 }
5050 }
5151 }
52−readonly property size artShapeSize: Qt.size(-1, -1);
5352readonly property int headerHeight: titleLabel.height + subtitleLabel.height + subtitleLabel.anchors.topMargin;
5453Item {
5554 id: headerTitleContainer;
@@ -114,13 +113,15 @@ Item {
114113
115114 ]
116115 }
117−UbuntuShape {
118− id: touchdown;
119− objectName: "touchdown";
116+Loader {
117+ active: root.pressed;
120118 anchors { fill: backgroundLoader }
121− visible: root.pressed;
122− radius: "medium";
123− borderSource: "radius_pressed.sci"
119+ sourceComponent: UbuntuShape {
120+ objectName: "touchdown";
121+ anchors.fill: parent;
122+ radius: "medium";
123+ borderSource: "radius_pressed.sci"
124+ }
124125}
125126implicitHeight: headerTitleContainer.y + headerTitleContainer.height + units.gu(1);
126127}
tests/plugins/Dash/cardcreator/6.res.cardcreator−8
@@ -112,13 +112,5 @@ Item {
112112
113113 ]
114114 }
115−UbuntuShape {
116− id: touchdown;
117− objectName: "touchdown";
118− anchors { fill: backgroundLoader }
119− visible: root.pressed;
120− radius: "medium";
121− borderSource: "radius_pressed.sci"
122−}
123115implicitHeight: headerTitleContainer.y + headerTitleContainer.height + units.gu(1);
124116}
tests/plugins/Dash/cardcreator/7.res−1
@@ -49,7 +49,6 @@ Loader {
4949 }
5050 }
5151 }
52−readonly property size artShapeSize: Qt.size(-1, -1);
5352readonly property int headerHeight: row.height;
5453Row {
5554 id: row;
tests/plugins/Dash/cardcreator/8.res+8−7
@@ -49,7 +49,6 @@ Loader {
4949 }
5050 }
5151 }
52−readonly property size artShapeSize: Qt.size(-1, -1);
5352readonly property int headerHeight: row.height;
5453Row {
5554 id: row;
@@ -96,13 +95,15 @@ left: parent.left;
9695
9796 ]
9897 }
99−UbuntuShape {
100− id: touchdown;
101− objectName: "touchdown";
98+Loader {
99+ active: root.pressed;
102100 anchors { fill: backgroundLoader }
103− visible: root.pressed;
104− radius: "medium";
105− borderSource: "radius_pressed.sci"
101+ sourceComponent: UbuntuShape {
102+ objectName: "touchdown";
103+ anchors.fill: parent;
104+ radius: "medium";
105+ borderSource: "radius_pressed.sci"
106+ }
106107}
107108implicitHeight: row.y + row.height + units.gu(1);
108109}
tests/plugins/Dash/cardcreator/8.res.cardcreator−8
@@ -93,13 +93,5 @@ left: parent.left;
9393
9494 ]
9595 }
96−UbuntuShape {
97− id: touchdown;
98− objectName: "touchdown";
99− anchors { fill: backgroundLoader }
100− visible: root.pressed;
101− radius: "medium";
102− borderSource: "radius_pressed.sci"
103−}
10496implicitHeight: row.y + row.height + units.gu(1);
10597}
tests/plugins/Dash/cardcreator/9.res+10−9
@@ -11,7 +11,6 @@ AbstractButton {
1111 property int fixedHeaderHeight: -1;
1212 property size fixedArtShapeSize: Qt.size(-1, -1);
1313signal action(var actionId);
14−readonly property size artShapeSize: Qt.size(-1, -1);
1514readonly property int headerHeight: titleLabel.height + subtitleLabel.height + subtitleLabel.anchors.topMargin + audioProgressBar.height + audioProgressBar.anchors.topMargin;
1615Label {
1716 id: titleLabel;
@@ -107,13 +106,15 @@ CardAudioProgress {
107106 onPressAndHold: {
108107 root.pressAndHold();
109108 }
110− }UbuntuShape {
111− id: touchdown;
112− objectName: "touchdown";
113− anchors { fill: root }
114− visible: root.pressed;
115− radius: "medium";
116− borderSource: "radius_pressed.sci"
117− }
109+ }Loader {
110+ active: root.pressed;
111+ anchors { fill: root }
112+ sourceComponent: UbuntuShape {
113+ objectName: "touchdown";
114+ anchors.fill: parent;
115+ radius: "medium";
116+ borderSource: "radius_pressed.sci"
117+ }
118+}
118119implicitHeight: audioButton.height;
119120}
tests/plugins/Dash/cardcreator/9.res.cardcreator+1−9
@@ -105,13 +105,5 @@ CardAudioProgress {
105105 onPressAndHold: {
106106 root.pressAndHold();
107107 }
108− }UbuntuShape {
109− id: touchdown;
110− objectName: "touchdown";
111− anchors { fill: root }
112− visible: root.pressed;
113− radius: "medium";
114− borderSource: "radius_pressed.sci"
115− }
116−implicitHeight: audioButton.height;
108+ }implicitHeight: audioButton.height;
117109}
tests/plugins/Dash/listviewwithpageheadertest.cpp+30
@@ -1994,6 +1994,36 @@ private Q_SLOTS:
19941994 verifyItem(2, 400., 350., false);
19951995 }
19961996
1997+ void testRemoveItemNotShowHeader()
1998+ {
1999+ QMetaObject::invokeMethod(model, "removeItems", Q_ARG(QVariant, 0), Q_ARG(QVariant, 6));
2000+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 90));
2001+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 1), Q_ARG(QVariant, 600));
2002+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 2);
2003+ scrollToBottom();
2004+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 1), Q_ARG(QVariant, 2000));
2005+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 3);
2006+ QMetaObject::invokeMethod(model, "removeItems", Q_ARG(QVariant, 2), Q_ARG(QVariant, 1));
2007+ QTRY_COMPARE(lvwph->m_headerItem->y(), -610.);
2008+ QTRY_COMPARE(lvwph->m_minYExtent, 610.);
2009+ }
2010+
2011+ void testRemoveItemCutContents()
2012+ {
2013+ view->rootObject()->setHeight(921);
2014+ QMetaObject::invokeMethod(model, "removeItems", Q_ARG(QVariant, 0), Q_ARG(QVariant, 6));
2015+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 1000));
2016+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 1);
2017+ QTRY_COMPARE(lvwph->contentHeight(), 1050.);
2018+ scrollToBottom();
2019+ QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 530));
2020+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 2);
2021+ QMetaObject::invokeMethod(model, "removeItems", Q_ARG(QVariant, 1), Q_ARG(QVariant, 1));
2022+ QTRY_COMPARE(lvwph->m_visibleItems.count(), 1);
2023+ verifyItem(0, 50., 530., false);
2024+ QTRY_COMPARE(lvwph->m_minYExtent, 530.);
2025+ }
2026+
19972027private:
19982028 QQuickView *view;
19992029 ListViewWithPageHeader *lvwph;
tests/plugins/ImageCache/CMakeLists.txtnew file+20
@@ -0,0 +1,20 @@
1+include_directories(
2+ ${CMAKE_CURRENT_BINARY_DIR}
3+ )
4+
5+include_directories(
6+ SYSTEM
7+ ${Qt5Quick_PRIVATE_INCLUDE_DIRS}
8+ )
9+
10+add_definitions(-DCURRENT_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}")
11+remove_definitions(-DQT_NO_KEYWORDS)
12+
13+add_executable(ImageCacheTestExec
14+ test.cpp
15+ )
16+qt5_use_modules(ImageCacheTestExec Core Quick Test)
17+add_unity8_uitest(ImageCache ImageCacheTestExec
18+ DEPENDS ImageCache-qml
19+ IMPORT_PATHS ${UNITY_PLUGINPATH}
20+ )
tests/plugins/ImageCache/graphics/wide.jpgnew file

Binary file not shown.

tests/plugins/ImageCache/test.cppnew file+214
@@ -0,0 +1,214 @@
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 <QCoreApplication>
18+#include <QFileInfo>
19+#include <QImageReader>
20+#include <QQuickView>
21+#include <QTemporaryDir>
22+#include <QtTestGui>
23+#include <private/qquickimage_p.h>
24+#include <time.h>
25+#include <utime.h>
26+
27+Q_DECLARE_METATYPE(QQuickImage::Status)
28+
29+class ImageCacheTest : public QObject
30+{
31+ Q_OBJECT
32+
33+private:
34+
35+ void setUpImage(const QString &name, const QSize &sourceSize = QSize())
36+ {
37+ QString url = "image://unity8imagecache/file://" + sourceFile(name);
38+ image->setProperty("sourceSize", sourceSize);
39+ image->setProperty("source", url);
40+ }
41+
42+ void waitForImage(QQuickImage::Status status = QQuickImage::Ready)
43+ {
44+ QTRY_COMPARE(image->property("status"), QVariant((int)status));
45+ }
46+
47+ QSize cachedImageSize(const QString &filename)
48+ {
49+ QImageReader reader(filename);
50+ return reader.size();
51+ }
52+
53+ QString sourceFile(const QString &name)
54+ {
55+ return CURRENT_SOURCE_DIR "/graphics/" + name;
56+ }
57+
58+ QString cachedFile(bool isPath, const QString &name)
59+ {
60+ if (isPath)
61+ return home->path() + "/.cache/unity8/imagecache/paths" + sourceFile(name);
62+ else
63+ return home->path() + "/.cache/unity8/imagecache/names/" + name;
64+ }
65+
66+ void createCachedImage(const QString &name, const QString &cachePath, const QSize &cacheSize, time_t mtime_in = 0)
67+ {
68+ QString origPath = sourceFile(name);
69+ QImageReader origImage(origPath);
70+ origImage.setScaledSize(cacheSize);
71+ auto format = origImage.format(); // can't get this after reading
72+
73+ QImage cacheImage(origImage.read());
74+ QVERIFY(!cacheImage.isNull());
75+
76+ QFileInfo(cachePath).dir().mkpath(QStringLiteral("."));
77+ QVERIFY(cacheImage.save(cachePath, format, 100));
78+
79+ // Make file a few seconds old, so we can more easily tell if it gets updated
80+ auto mtime = mtime_in == 0 ? QFileInfo(cachePath).lastModified().toTime_t() - 5 : mtime_in;
81+ struct utimbuf timebuffer;
82+ timebuffer.modtime = mtime;
83+ timebuffer.actime = timebuffer.modtime;
84+ QCOMPARE(utime(cachePath.toUtf8().data(), &timebuffer), 0);
85+ }
86+
87+private Q_SLOTS:
88+
89+ void init()
90+ {
91+ home = new QTemporaryDir();
92+ QVERIFY(home->isValid());
93+ qputenv("HOME", home->path().toUtf8());
94+
95+ view = new QQuickView();
96+ view->setSource(QUrl::fromLocalFile(CURRENT_SOURCE_DIR "/test.qml"));
97+ image = view->rootObject();
98+
99+ view->show();
100+ QTest::qWaitForWindowExposed(view);
101+
102+ waitForImage(QQuickImage::Null);
103+ }
104+
105+ void cleanup()
106+ {
107+ delete view;
108+ delete home;
109+ }
110+
111+ void testFileNotFound()
112+ {
113+ setUpImage("NOTHERE");
114+ waitForImage(QQuickImage::Error);
115+ QVERIFY(!QFile::exists(cachedFile(true, "NOTHERE")));
116+ }
117+
118+ void testNoSourceSize()
119+ {
120+ setUpImage("wide.jpg");
121+ waitForImage();
122+ QVERIFY(!QFile::exists(cachedFile(true, "wide.jpg")));
123+ }
124+
125+ void testNoScalingUp()
126+ {
127+ setUpImage("wide.jpg", QSize(1000, 1000));
128+ waitForImage();
129+ QVERIFY(!QFile::exists(cachedFile(true, "wide.jpg")));
130+ }
131+
132+ void testFullSourceSize()
133+ {
134+ setUpImage("wide.jpg", QSize(100, 100));
135+ waitForImage();
136+ QCOMPARE(cachedImageSize(cachedFile(true, "wide.jpg")), QSize(100, 100));
137+ }
138+
139+ void testWidthSourceSize()
140+ {
141+ setUpImage("wide.jpg", QSize(100, 0));
142+ waitForImage();
143+ // wide.jpg is 500x200
144+ QCOMPARE(cachedImageSize(cachedFile(true, "wide.jpg")), QSize(100, 40));
145+ }
146+
147+ void testHeightSourceSize()
148+ {
149+ setUpImage("wide.jpg", QSize(0, 100));
150+ waitForImage();
151+ // wide.jpg is 500x200
152+ QCOMPARE(cachedImageSize(cachedFile(true, "wide.jpg")), QSize(250, 100));
153+ }
154+
155+ void testNameArg()
156+ {
157+ setUpImage("wide.jpg?name=foo", QSize(0, 100));
158+ waitForImage();
159+ QVERIFY(!QFile::exists(cachedFile(true, ""))); // check for dir itself
160+ QVERIFY(QFile::exists(cachedFile(false, "foo")));
161+ }
162+
163+ void testLoadCache()
164+ {
165+ auto cacheName = cachedFile(false, "foo");
166+ createCachedImage("wide.jpg", cacheName, QSize(250, 100));
167+ auto mtime = QFileInfo(cacheName).lastModified();
168+
169+ auto now = time(NULL);
170+ QVERIFY(QFileInfo(cacheName).lastModified().toTime_t() < now); // sanity check
171+
172+ setUpImage("wide.jpg?name=foo", QSize(0, 100));
173+ waitForImage();
174+ QCOMPARE(QFileInfo(cacheName).lastModified(), mtime); // wasn't recreated
175+ }
176+
177+ void testDifferentSize()
178+ {
179+ auto cacheName = cachedFile(false, "foo");
180+ createCachedImage("wide.jpg", cacheName, QSize(250, 100));
181+
182+ auto now = time(NULL);
183+ QVERIFY(QFileInfo(cacheName).lastModified().toTime_t() < now); // sanity check
184+
185+ setUpImage("wide.jpg?name=foo", QSize(100, 0));
186+ waitForImage();
187+ QVERIFY(QFileInfo(cacheName).lastModified().toTime_t() >= now); // was recreated
188+ }
189+
190+ void testStaleCache()
191+ {
192+ auto sourceName = sourceFile("wide.jpg");
193+ auto mtime = QFileInfo(sourceName).lastModified().toTime_t() - 5;
194+
195+ auto cacheName = cachedFile(false, "foo");
196+ createCachedImage("wide.jpg", cacheName, QSize(250, 100), mtime);
197+
198+ auto now = time(NULL);
199+ QVERIFY(QFileInfo(cacheName).lastModified().toTime_t() < now); // sanity check
200+
201+ setUpImage("wide.jpg?name=foo", QSize(0, 100));
202+ waitForImage();
203+ QVERIFY(QFileInfo(cacheName).lastModified().toTime_t() >= now); // was recreated
204+ }
205+
206+private:
207+ QQuickView *view;
208+ QObject *image;
209+ QTemporaryDir *home;
210+};
211+
212+QTEST_MAIN(ImageCacheTest)
213+
214+#include "test.moc"
qml/Components/Circle.qml → tests/plugins/ImageCache/test.qmlrenamed+6−5
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright 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,8 +15,9 @@
1515 */
1616
1717import QtQuick 2.4
18+import ImageCache 0.1
1819
19−Rectangle {
20− antialiasing: true
21− radius: width / 2
22− }
20+Image {
21+ width: 400
22+ height: 400
23+}
tests/plugins/LightDM/IntegratedLightDM/CMakeLists.txt−3
@@ -26,7 +26,6 @@ add_unity8_uitest(GreeterDBus dbus-test-runner
2626 ARGS --task $<TARGET_FILE:GreeterDBusTestExec>
2727 DEPENDS MockLightDM
2828 IMPORT_PATHS ${UNITY_MOCKPATH}
29− LIGHTDM
3029 )
3130
3231add_executable(GreeterPamTestExec
@@ -63,7 +62,6 @@ target_include_directories(GreeterSessionsModelTestExec PUBLIC
6362add_unity8_uitest(GreeterSessions GreeterSessionsModelTestExec
6463 ENVIRONMENT LIBLIGHTDM_MOCK_MODE=full
6564 DEPENDS MockLightDM
66− LIGHTDM
6765 )
6866
6967# UsersModelTest
@@ -87,7 +85,6 @@ target_include_directories(GreeterUsersModelTestExec PUBLIC
8785add_unity8_uitest(GreeterUsers GreeterUsersModelTestExec
8886 ENVIRONMENT LIBLIGHTDM_MOCK_MODE=full
8987 DEPENDS MockLightDM
90− LIGHTDM
9188 )
9289
9390add_executable(GreeterIntegratedTestExec
tests/plugins/LightDM/IntegratedLightDM/sessionsmodel.cpp+32
@@ -59,6 +59,38 @@ private Q_SLOTS:
5959 }
6060 }
6161
62+ void testIconLookupLogic()
63+ {
64+ const QString data[][2] = {
65+ //{SESSION KEY, ICON NAME}
66+ {"gnome-classic", "gnome_badge.png"},
67+ {"gnome-flashback-compiz", "gnome_badge.png"},
68+ {"gnome-flashback-metacity", "gnome_badge.png"},
69+ {"gnome-shell", "gnome_badge.png"},
70+ {"gnome-wayland", "gnome_badge.png"},
71+ {"gnome", "gnome_badge.png"},
72+ {"kde", "kde_badge.png"},
73+ {"plasma", "kde_badge.png"},
74+ {"recovery_console", "recovery_console_badge.png"},
75+ {"ubuntu","ubuntu_badge.png"},
76+ {"ubuntu-2d", "ubuntu_badge.png"},
77+ {"xterm", "recovery_console_badge.png"},
78+ {"made up session", "unknown_badge.png"},
79+ {"", "unknown_badge.png"}
80+ };
81+
82+ const short length = sizeof(data) / sizeof(data[0]);
83+ for (int i = 0; i < length; i++) {
84+ std::string key = data[i][0].toStdString();
85+ std::string icon = data[i][1].toStdString();
86+ std::string url = model->iconUrl(data[i][0]).toString().toStdString();
87+ std::string errorMessage = "Session icon url for " + key +
88+ " should contain " + icon + " but " + url + " was returned";
89+ QVERIFY2(model->iconUrl(data[i][0]).toString().contains(data[i][1]),
90+ errorMessage.c_str());
91+ }
92+ }
93+
6294 void testMultipleSessionsCountIsCorrect()
6395 {
6496 sourceModel->setTestScenario("multipleSessions");
tests/plugins/LightDM/IntegratedLightDM/usersmodel.cpp+2−2
@@ -59,10 +59,10 @@ private Q_SLOTS:
5959 void testMangleColor()
6060 {
6161 QString background = getStringValue(sourceModel, "color-background", QLightDM::UsersModel::BackgroundPathRole);
62− QVERIFY(background == "#dd4814");
62+ QVERIFY(background == "#E95420");
6363
6464 background = getStringValue(model, "color-background", QLightDM::UsersModel::BackgroundPathRole);
65− QVERIFY(background == "data:image/svg+xml,<svg><rect width='100%' height='100%' fill='#dd4814'/></svg>");
65+ QVERIFY(background == "data:image/svg+xml,<svg><rect width='100%' height='100%' fill='#E95420'/></svg>");
6666 }
6767
6868 void testMangleEmptyName()
tests/plugins/SessionBroadcast/BroadcastServer.cppnew file+33
@@ -0,0 +1,33 @@
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 "BroadcastServer.h"
19+
20+BroadcastServer::BroadcastServer(QObject *parent)
21+ : QObject(parent)
22+{
23+}
24+
25+void BroadcastServer::RequestUrlStart(const QString &username, const QString &url)
26+{
27+ Q_EMIT StartUrl(username, url);
28+}
29+
30+void BroadcastServer::RequestHomeShown(const QString &username)
31+{
32+ Q_EMIT ShowHome(username);
33+}
tests/plugins/SessionBroadcast/BroadcastServer.hnew file+40
@@ -0,0 +1,40 @@
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_BROADCASTSERVER_H
19+#define UNITY_BROADCASTSERVER_H
20+
21+#include <QDBusContext>
22+#include <QObject>
23+
24+class BroadcastServer: public QObject, protected QDBusContext
25+{
26+ Q_OBJECT
27+
28+public:
29+ explicit BroadcastServer(QObject *parent = nullptr);
30+
31+public Q_SLOTS:
32+ void RequestUrlStart(const QString &username, const QString &url);
33+ void RequestHomeShown(const QString &username);
34+
35+Q_SIGNALS:
36+ void StartUrl(const QString &username, const QString &url);
37+ void ShowHome(const QString &username);
38+};
39+
40+#endif
tests/plugins/SessionBroadcast/CMakeLists.txtnew file+45
@@ -0,0 +1,45 @@
1+macro(make_dbus_class NAME INTERFACE)
2+ if(${CMAKE_CURRENT_SOURCE_DIR}/interfaces.xml IS_NEWER_THAN ${CMAKE_CURRENT_BINARY_DIR}/${NAME}Adaptor.h)
3+ execute_process(COMMAND ${QDBUSXML2CPP_EXECUTABLE} -c ${NAME}Adaptor -a ${CMAKE_CURRENT_BINARY_DIR}/${NAME}Adaptor ${CMAKE_CURRENT_SOURCE_DIR}/interfaces.xml ${INTERFACE})
4+ endif()
5+endmacro(make_dbus_class)
6+
7+make_dbus_class(Broadcast com.canonical.Unity.Greeter.Broadcast)
8+
9+add_definitions(-DSM_BUSNAME=sessionBus)
10+
11+include_directories(
12+ ${GLIB_INCLUDE_DIRS}
13+ ${CMAKE_CURRENT_BINARY_DIR}
14+ ${CMAKE_CURRENT_SOURCE_DIR}
15+ ${CMAKE_SOURCE_DIR}/plugins/SessionBroadcast
16+)
17+
18+link_libraries(
19+ ${GLIB_LIBRARIES}
20+)
21+
22+add_executable(MockBroadcastServer
23+ ${CMAKE_CURRENT_BINARY_DIR}/BroadcastAdaptor.cpp
24+ server.cpp
25+ BroadcastServer.cpp
26+)
27+qt5_use_modules(MockBroadcastServer Core DBus)
28+
29+### SessionBroadcastTest
30+add_executable(sessionbroadcasttestExec
31+ ${CMAKE_SOURCE_DIR}/plugins/SessionBroadcast/SessionBroadcast.cpp
32+ sessionbroadcasttest.cpp
33+)
34+qt5_use_modules(sessionbroadcasttestExec Test Core Qml DBus)
35+
36+add_unity8_unittest(SessionBroadcast dbus-test-runner
37+ ARG_PREFIX "--parameter"
38+ ARGS
39+ --task $<TARGET_FILE:MockBroadcastServer>
40+ --task-name server
41+ --ignore-return
42+ --task $<TARGET_FILE:sessionbroadcasttestExec>
43+ --task-name client
44+ --wait-for com.canonical.Unity.Greeter.Broadcast
45+)
tests/plugins/SessionBroadcast/interfaces.xmlnew file+18
@@ -0,0 +1,18 @@
1+<node>
2+ <interface name="com.canonical.Unity.Greeter.Broadcast">
3+ <method name="RequestUrlStart">
4+ <arg name="username" type="s" direction="in" />
5+ <arg name="url" type="s" direction="in" />
6+ </method>
7+ <method name="RequestHomeShown">
8+ <arg name="username" type="s" direction="in" />
9+ </method>
10+ <signal name="StartUrl">
11+ <arg name="username" type="s" direction="out" />
12+ <arg name="url" type="s" direction="out" />
13+ </signal>
14+ <signal name="ShowHome">
15+ <arg name="username" type="s" direction="out" />
16+ </signal>
17+ </interface>
18+</node>
tests/plugins/SessionBroadcast/server.cppnew file+37
@@ -0,0 +1,37 @@
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 "BroadcastAdaptor.h"
19+#include "BroadcastServer.h"
20+#include <QCoreApplication>
21+
22+int main(int argc, char *argv[])
23+{
24+ QCoreApplication a(argc, argv);
25+
26+ auto broadcast = new BroadcastServer(&a);
27+ new BroadcastAdaptor(broadcast);
28+
29+ // We use the session bus for testing. The real plugin uses the system bus
30+ auto connection = QDBusConnection::sessionBus();
31+ if (!connection.registerObject("/com/canonical/Unity/Greeter/Broadcast", broadcast))
32+ return 1;
33+ if (!connection.registerService("com.canonical.Unity.Greeter.Broadcast"))
34+ return 1;
35+
36+ return a.exec();
37+}
tests/plugins/SessionBroadcast/sessionbroadcasttest.cppnew file+111
@@ -0,0 +1,111 @@
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 <QtTest>
18+#include <QDBusInterface>
19+#include <QSignalSpy>
20+
21+#include <glib.h>
22+
23+#include "SessionBroadcast.h"
24+
25+class SessionBroadcastTest : public QObject
26+{
27+ Q_OBJECT
28+
29+private Q_SLOTS:
30+
31+ void initTestCase() {
32+ broadcastServer = new QDBusInterface("com.canonical.Unity.Greeter.Broadcast",
33+ "/com/canonical/Unity/Greeter/Broadcast",
34+ "com.canonical.Unity.Greeter.Broadcast",
35+ QDBusConnection::sessionBus(), this);
36+ serverStartUrlSpy = new QSignalSpy(broadcastServer,
37+ SIGNAL(StartUrl(const QString &, const QString &)));
38+ serverShowHomeSpy = new QSignalSpy(broadcastServer,
39+ SIGNAL(ShowHome(const QString &)));
40+ user = QString(g_get_user_name());
41+ otherUser = user + "2";
42+ }
43+
44+ void init()
45+ {
46+ serverStartUrlSpy->clear();
47+ serverShowHomeSpy->clear();
48+
49+ broadcastPlugin = new SessionBroadcast(this);
50+ pluginStartUrlSpy = new QSignalSpy(broadcastPlugin,
51+ SIGNAL(startUrl(const QString &)));
52+ pluginShowHomeSpy = new QSignalSpy(broadcastPlugin,
53+ SIGNAL(showHome()));
54+ }
55+
56+ void cleanup()
57+ {
58+ delete broadcastPlugin;
59+ }
60+
61+ void testReceiveUrlStart() {
62+ broadcastServer->call("RequestUrlStart", user, "test:");
63+ QTRY_COMPARE(serverStartUrlSpy->count(), 1);
64+ QCOMPARE(pluginStartUrlSpy->count(), 1);
65+ QCOMPARE((*pluginStartUrlSpy)[0][0], QVariant("test:"));
66+ }
67+
68+ void testReceiveUrlStartOtherUser() {
69+ broadcastServer->call("RequestUrlStart", otherUser, "test:");
70+ QTRY_COMPARE(serverStartUrlSpy->count(), 1);
71+ QCOMPARE(pluginStartUrlSpy->count(), 0);
72+ }
73+
74+ void testReceiveShowHome() {
75+ broadcastServer->call("RequestHomeShown", user);
76+ QTRY_COMPARE(serverShowHomeSpy->count(), 1);
77+ QCOMPARE(pluginShowHomeSpy->count(), 1);
78+ }
79+
80+ void testReceiveShowHomeOtherUser() {
81+ broadcastServer->call("RequestHomeShown", otherUser);
82+ QTRY_COMPARE(serverShowHomeSpy->count(), 1);
83+ QCOMPARE(pluginShowHomeSpy->count(), 0);
84+ }
85+
86+ void testSendUrlStart() {
87+ broadcastPlugin->requestUrlStart(otherUser, "test:");
88+ QTRY_COMPARE(serverStartUrlSpy->count(), 1);
89+ QCOMPARE((*serverStartUrlSpy)[0][0], QVariant(otherUser));
90+ QCOMPARE((*serverStartUrlSpy)[0][1], QVariant("test:"));
91+ }
92+
93+ void testSendShowHome() {
94+ broadcastPlugin->requestHomeShown(otherUser);
95+ QTRY_COMPARE(serverShowHomeSpy->count(), 1);
96+ QCOMPARE((*serverShowHomeSpy)[0][0], QVariant(otherUser));
97+ }
98+
99+private:
100+ QDBusInterface *broadcastServer;
101+ SessionBroadcast *broadcastPlugin;
102+ QSignalSpy *serverStartUrlSpy;
103+ QSignalSpy *serverShowHomeSpy;
104+ QSignalSpy *pluginStartUrlSpy;
105+ QSignalSpy *pluginShowHomeSpy;
106+ QString user;
107+ QString otherUser;
108+};
109+
110+QTEST_GUILESS_MAIN(SessionBroadcastTest)
111+#include "sessionbroadcasttest.moc"
tests/plugins/Utils/CMakeLists.txt+9
@@ -23,3 +23,12 @@ endforeach()
2323
2424# plain qml test
2525add_unity8_qmlunittest(. UtilsStyle)
26+
27+# URLDispatcher test needs dbus-test-runner because it claims a dbus name
28+add_executable(URLDispatcherTestExec URLDispatcherTest.cpp)
29+qt5_use_modules(URLDispatcherTestExec Test Core Qml)
30+target_link_libraries(URLDispatcherTestExec Utils-qml)
31+add_unity8_unittest(URLDispatcher dbus-test-runner
32+ ARG_PREFIX "--parameter"
33+ ARGS --task $<TARGET_FILE:URLDispatcherTestExec>
34+)
tests/plugins/Utils/URLDispatcherTest.cppnew file+78
@@ -0,0 +1,78 @@
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 "URLDispatcher.h"
18+
19+#include <QDBusInterface>
20+#include <QSignalSpy>
21+#include <QTest>
22+
23+class URLDispatcherTest : public QObject
24+{
25+ Q_OBJECT
26+
27+private Q_SLOTS:
28+
29+ void initTestCase() {
30+ dispatchServer = new QDBusInterface("com.canonical.URLDispatcher",
31+ "/com/canonical/URLDispatcher",
32+ "com.canonical.URLDispatcher",
33+ QDBusConnection::sessionBus(), this);
34+ }
35+
36+ void init()
37+ {
38+ dispatcher = new URLDispatcher(this);
39+ dispatchSpy = new QSignalSpy(dispatcher,
40+ SIGNAL(urlRequested(const QString &)));
41+ }
42+
43+ void cleanup()
44+ {
45+ delete dispatcher;
46+ }
47+
48+ void testInactiveByDefault() {
49+ QVERIFY(!dispatcher->active());
50+ }
51+
52+ void testActiveRequest() {
53+ dispatcher->setActive(true);
54+ dispatchServer->call("DispatchURL", "test:", "package");
55+ QCOMPARE(dispatchSpy->count(), 1);
56+ QCOMPARE((*dispatchSpy)[0][0], QVariant("test:"));
57+ }
58+
59+ void testInactiveRequest() {
60+ dispatchServer->call("DispatchURL", "test:", "package");
61+ QCOMPARE(dispatchSpy->count(), 0);
62+ }
63+
64+ void testInactiveAfterActiveRequest() {
65+ dispatcher->setActive(true);
66+ dispatcher->setActive(false);
67+ dispatchServer->call("DispatchURL", "test:", "package");
68+ QCOMPARE(dispatchSpy->count(), 0);
69+ }
70+
71+private:
72+ QDBusInterface *dispatchServer;
73+ URLDispatcher *dispatcher;
74+ QSignalSpy *dispatchSpy;
75+};
76+
77+QTEST_GUILESS_MAIN(URLDispatcherTest)
78+#include "URLDispatcherTest.moc"
tests/qmltests/CMakeLists.txt+12−12
@@ -1,14 +1,14 @@
11
22add_subdirectory(Components)
33
4−add_unity8_qmltest(. OrientedShell LIGHTDM)
4+add_unity8_qmltest(. OrientedShell)
55add_unity8_qmltest(. DisabledScreenNotice)
6−add_unity8_qmltest(. Shell LIGHTDM)
7−add_unity8_qmltest(. ShellWithPin LIGHTDM)
6+add_unity8_qmltest(. Shell)
7+add_unity8_qmltest(. ShellWithPin)
88add_unity8_qmltest(. DeviceConfiguration)
99add_unity8_qmltest(Components Background)
1010add_unity8_qmltest(Components Carousel)
11−add_unity8_qmltest(Components Dialogs LIGHTDM)
11+add_unity8_qmltest(Components Dialogs)
1212add_unity8_qmltest(Components DraggingArea)
1313add_unity8_qmltest(Components LazyImage)
1414add_unity8_qmltest(Components Lockscreen)
@@ -25,7 +25,7 @@ add_unity8_qmltest(Components WallpaperResolver)
2525add_unity8_qmltest(Components ZoomableImage)
2626add_unity8_qmltest(Dash Dash)
2727add_unity8_qmltest(Dash DashContent)
28−add_unity8_qmltest(Dash DashShell LIGHTDM)
28+add_unity8_qmltest(Dash DashShell)
2929add_unity8_qmltest(Dash Card)
3030add_unity8_qmltest(Dash CardCarousel)
3131add_unity8_qmlbenchmark(Dash CardBenchmark 1000)
@@ -66,10 +66,10 @@ add_unity8_qmltest(Dash/ScopeSettings ScopeSettingNumber)
6666add_unity8_qmltest(Dash/ScopeSettings ScopeSettingString)
6767add_unity8_qmltest(Dash/ScopeSettings ScopeSettingsWidgetFactory)
6868add_unity8_qmltest(Greeter Clock)
69−add_unity8_qmltest(Greeter Greeter LIGHTDM)
70−add_unity8_qmltest(Greeter Infographics LIGHTDM)
71−add_unity8_qmltest(Greeter NarrowView LIGHTDM)
72−add_unity8_qmltest(Greeter WideView LIGHTDM)
69+add_unity8_qmltest(Greeter Greeter)
70+add_unity8_qmltest(Greeter Infographics)
71+add_unity8_qmltest(Greeter NarrowView)
72+add_unity8_qmltest(Greeter WideView)
7373add_unity8_qmltest(Launcher Launcher)
7474add_unity8_qmltest(Notifications Notifications)
7575add_unity8_qmltest(Notifications VisualSnapDecisionsQueue)
@@ -93,8 +93,8 @@ add_unity8_qmltest(Stages SpreadDelegate)
9393add_unity8_qmltest(Stages SurfaceContainer)
9494add_unity8_qmltest(Stages TabletStage)
9595add_unity8_qmltest(Stages WindowResizeArea)
96−add_unity8_qmltest(Stages Splash)
97−add_unity8_qmltest(Stages DecoratedWindow)
98−add_unity8_qmltest(Tutorial Tutorial LIGHTDM)
96+add_unity8_qmltest(Stage Splash)
97+add_unity8_qmltest(Stage DecoratedWindow)
98+add_unity8_qmltest(Tutorial Tutorial)
9999add_unity8_qmltest(Wizard Wizard ENVIRONMENT "OXIDE_NO_SANDBOX=1")
100100add_unity8_qmltest(utils/Unity/Test UnityTest)
tests/qmltests/Components/tst_Carousel.qml−1
@@ -268,7 +268,6 @@ Item {
268268
269269 function test_activate() {
270270 var carouselList = findChild(carousel, "listView");
271− tryCompareFunction(function() { return findChild(carouselList, "carouselDelegate3") ? true : false; }, true);
272271 var carouselItem = findChild(carousel, "carouselDelegate3")
273272 verify(carouselItem, "Could not find delegate");
274273
tests/qmltests/Components/tst_Lockscreen.qml+2−41
@@ -37,8 +37,7 @@ Rectangle {
3737 alphaNumeric: pinPadCheckBox.checked
3838 minPinLength: minPinLengthTextField.text
3939 maxPinLength: maxPinLengthTextField.text
40− delayMinutes: delayMinutesTextField.text
41− background: "../../../qml/graphics/phone_background.jpg"
40+ background: "/usr/share/backgrounds/warty-final-ubuntu.png"
4241 }
4342
4443 Connections {
@@ -109,16 +108,6 @@ Rectangle {
109108 text: "Retries left"
110109 }
111110 }
112− Row {
113− TextField {
114− id: delayMinutesTextField
115− width: units.gu(7)
116− text: "0"
117− }
118− Label {
119− text: "Delay Minutes"
120− }
121− }
122111 Label {
123112 id: pinLabel
124113 anchors.verticalCenter: parent.verticalCenter
@@ -172,7 +161,6 @@ Rectangle {
172161
173162 function cleanup() {
174163 lockscreen.clear(false);
175− delayMinutesTextField.text = "0"
176164 enteredLabel.text = "";
177165
178166 // Reset sizing
@@ -422,7 +410,6 @@ Rectangle {
422410 function test_infoPopup() {
423411 verify(findChild(root, "infoPopup") === null);
424412 lockscreen.showInfoPopup("foo", "bar");
425− tryCompareFunction(function() { return findChild(root, "infoPopup") !== null}, true);
426413
427414 var infoPopup = findChild(root, "infoPopup");
428415 compare(infoPopup.title, "foo");
@@ -434,7 +421,7 @@ Rectangle {
434421 var okButton = findChild(root, "infoPopupOkButton");
435422 mouseClick(okButton);
436423
437− tryCompareFunction(function() { return findChild(root, "infoPopup") === null}, true);
424+ tryCompareFunction(function() { return findChild(root, "infoPopup", 0 /* timeout */) === null}, true);
438425
439426 tryCompare(signalSpy, "count", 1);
440427 }
@@ -455,32 +442,6 @@ Rectangle {
455442 compare(label.text, data.text);
456443 }
457444
458− function test_delayMinutes() {
459− delayMinutesTextField.text = "4"
460− waitForLockscreenReady()
461− var label = findChild(lockscreen, "deviceLockedLabel")
462− compare(label.text, "Device Locked")
463− }
464−
465− function test_showText_data() {
466− return [
467− { tag: "alphanumeric", alphanumeric: true },
468− { tag: "pinPad", alphanumeric: false },
469− ]
470− }
471−
472− function test_showText(data) {
473− pinPadCheckBox.checked = data.alphanumeric;
474− waitForLockscreenReady();
475−
476− lockscreen.showText("test");
477−
478− var pinPadLoader = findChild(lockscreen, "pinPadLoader");
479− verify(pinPadLoader.waiting);
480− compare(enteredLabel.text, ""); // no entered signal should occur
481− compare(lockscreen.passphrase, "test");
482− }
483−
484445 function test_resize_data() {
485446 return [
486447 { tag: "small", width: units.gu(40), height: units.gu(76) },
tests/qmltests/Components/tst_WallpaperResolver.qml+21−35
@@ -18,8 +18,6 @@ import QtQuick 2.4
1818import QtTest 1.0
1919import Ubuntu.Components 1.3
2020import Unity.Test 0.1
21−import AccountsService 0.1
22−import GSettings 1.0
2321
2422import "../../../qml/Components"
2523
@@ -32,7 +30,6 @@ Image {
3230
3331 WallpaperResolver {
3432 id: wallpaperResolver
35− width: units.gu(70)
3633 }
3734
3835 UnityTestCase {
@@ -42,48 +39,37 @@ Image {
4239
4340 function test_background_data() {
4441 return [
45− {tag: "color",
46− accounts: Qt.resolvedUrl("data:image/svg+xml,<svg><rect width='100%' height='100%' fill='#dd4814'/></svg>"),
47− gsettings: "",
48− output: Qt.resolvedUrl("data:image/svg+xml,<svg><rect width='100%' height='100%' fill='#dd4814'/></svg>")},
42+ {tag: "none-valid",
43+ list: ["/first", "/middle", "/last"],
44+ output: "file:///last"},
4945
50− {tag: "empty", accounts: "", gsettings: "", output: "defaultBackground"},
51−
52− {tag: "as-specified",
53− accounts: Qt.resolvedUrl("../../data/unity/backgrounds/blue.png"),
54− gsettings: "",
46+ {tag: "first-valid",
47+ list: [Qt.resolvedUrl("../../data/unity/backgrounds/blue.png"),
48+ "/middle", "/last"],
5549 output: Qt.resolvedUrl("../../data/unity/backgrounds/blue.png")},
5650
57− {tag: "gs-specified",
58− accounts: "",
59− gsettings: Qt.resolvedUrl("../../data/unity/backgrounds/red.png"),
51+ {tag: "middle-valid",
52+ list: ["/first",
53+ Qt.resolvedUrl("../../data/unity/backgrounds/red.png"),
54+ "/last"],
6055 output: Qt.resolvedUrl("../../data/unity/backgrounds/red.png")},
6156
62− {tag: "both-specified",
63− accounts: Qt.resolvedUrl("../../data/unity/backgrounds/blue.png"),
64− gsettings: Qt.resolvedUrl("../../data/unity/backgrounds/red.png"),
65− output: Qt.resolvedUrl("../../data/unity/backgrounds/blue.png")},
66−
67− {tag: "invalid-as",
68− accounts: Qt.resolvedUrl("../../data/unity/backgrounds/nope.png"),
69− gsettings: Qt.resolvedUrl("../../data/unity/backgrounds/red.png"),
57+ {tag: "last-valid",
58+ list: ["/first",
59+ "/middle",
60+ Qt.resolvedUrl("../../data/unity/backgrounds/red.png")],
7061 output: Qt.resolvedUrl("../../data/unity/backgrounds/red.png")},
7162
72− {tag: "invalid-both",
73− accounts: Qt.resolvedUrl("../../data/unity/backgrounds/nope.png"),
74− gsettings: Qt.resolvedUrl("../../data/unity/backgrounds/stillnope.png"),
75− output: "defaultBackground"},
63+ {tag: "multiple-valid",
64+ list: [Qt.resolvedUrl("../../data/unity/backgrounds/blue.png"),
65+ Qt.resolvedUrl("../../data/unity/backgrounds/red.png")],
66+ output: Qt.resolvedUrl("../../data/unity/backgrounds/blue.png")},
7667 ]
7768 }
78− function test_background(data) {
79− AccountsService.backgroundFile = data.accounts;
80− GSettingsController.setPictureUri(data.gsettings);
8169
82− if (data.output === "defaultBackground") {
83− tryCompare(wallpaperResolver, "background", wallpaperResolver.defaultBackground);
84− } else {
85− tryCompare(wallpaperResolver, "background", data.output);
86− }
70+ function test_background(data) {
71+ wallpaperResolver.candidates = data.list;
72+ tryCompare(wallpaperResolver, "background", data.output);
8773 }
8874 }
8975}
tests/qmltests/Components/tst_ZoomableImage.qml+1−1
@@ -31,7 +31,7 @@ Rectangle {
3131 }
3232
3333 property var widgetData1: {
34− "source": "../../../qml/graphics/phone_background.jpg",
34+ "source": "../../../tests/graphics/avatars/amanda@12.png",
3535 "zoomable": false
3636 }
3737
tests/qmltests/Dash/Previews/tst_Preview.qml−2
@@ -78,7 +78,6 @@ Rectangle {
7878
7979 function test_containOnFocus() {
8080 waitForRendering(preview);
81− tryCompareFunction(function () { return findChild(preview, "widget-10") != null }, true);
8281 var widget = findChild(preview, "widget-10");
8382
8483 var bottomLeft = preview.mapFromItem(widget, 0, widget.height);
@@ -94,7 +93,6 @@ Rectangle {
9493
9594 function test_containOnGrow() {
9695 waitForRendering(preview);
97− tryCompareFunction(function () { return findChild(preview, "widget-13") != null }, true);
9896 var widget = findChild(preview, "widget-13");
9997
10098 var bottomLeft = preview.mapFromItem(widget, 0, widget.height);
tests/qmltests/Dash/Previews/tst_PreviewActions.qml+2−1
@@ -151,15 +151,16 @@ Rectangle {
151151 {
152152 var button = findChild(buttonGroup, "moreLessButton");
153153 var buttonColumn = findChild(buttonGroup, "buttonColumn");
154+ var buttonGroupInitialHeight = buttonGroup.height;
154155 verify(button != null);
155156 mouseClick(button);
156157 tryCompare(buttonColumn, "height", buttonColumn.implicitHeight);
158+ tryCompare(buttonGroup, "height", buttonGroupInitialHeight + buttonColumn.height + buttonColumn.anchors.topMargin);
157159 }
158160
159161 function test_comboButton(data) {
160162 var twoActionsY = twoActions.y
161163 pressMoreButton(buttonAndCombo);
162− tryCompareFunction(function () { return twoActions.y <= twoActionsY; }, false);
163164 checkButtonPressSignal(buttonAndCombo, data.id);
164165 mouseClick(findChild(buttonAndCombo, "moreLessButton"));
165166 tryCompare(twoActions, "y", twoActionsY);
tests/qmltests/Dash/Previews/tst_PreviewCommentInput.qml+28
@@ -39,6 +39,21 @@ Rectangle {
3939 widgetId: "previewCommentInput"
4040 }
4141
42+ Loader {
43+ id: previewCommentInputLoader
44+ anchors {
45+ left: parent.left
46+ right: parent.right
47+ top: previewCommentInput.bottom
48+ topMargin: units.gu(2)
49+ }
50+
51+ sourceComponent: PreviewCommentInput {
52+ widgetData: widgetDataNewLabels
53+ widgetId: "previewCommentInputLoader"
54+ }
55+ }
56+
4257 SignalSpy {
4358 id: spy
4459 signalName: "triggered"
@@ -81,5 +96,18 @@ Rectangle {
8196 compare(args[2]["comment"], data.inputText);
8297 }
8398 }
99+
100+ function test_singleton() {
101+ var commentTextAreaLoader = findChild(previewCommentInputLoader, "commentTextArea")
102+
103+ commentTextAreaLoader.text = "singleton test text";
104+
105+ // Simulate widget being destroyed and recreated
106+ previewCommentInputLoader.active = false;
107+ previewCommentInputLoader.active = true;
108+
109+ commentTextAreaLoader = findChild(previewCommentInputLoader, "commentTextArea")
110+ tryCompare(commentTextAreaLoader, "text", "singleton test text");
111+ }
84112 }
85113}
tests/qmltests/Dash/Previews/tst_PreviewExpandable.qml+3−3
@@ -54,8 +54,8 @@ Rectangle {
5454
5555 property var galleryData: {
5656 "sources": [
57− "../../graphics/phone_background.jpg",
58− "../../graphics/tablet_background.jpg",
57+ "../../../tests/graphics/avatars/amanda@12.png",
58+ "../../../tests/graphics/avatars/funky@12.png",
5959 "../../../tests/graphics/clock@18.png",
6060 "../../graphics/borked"
6161 ]
@@ -71,7 +71,7 @@ Rectangle {
7171 }
7272
7373 property var imageData: {
74− "source": "../../graphics/phone_background.jpg",
74+ "source": "../../../tests/graphics/avatars/amanda@12.png",
7575 "zoomable": false
7676 }
7777
tests/qmltests/Dash/Previews/tst_PreviewHeader.qml−4
@@ -116,7 +116,6 @@ Rectangle {
116116
117117 tryCompare(testCase.mascotShapeLoader, "visible", data.loaderVisible);
118118 if (data.loaderVisible) {
119− tryCompareFunction(function() { return findChild(previewHeader, "mascotShape") != null }, true);
120119 var mascot = findChild(previewHeader, "mascotShape")
121120 tryCompare(mascot, "visible", data.visible);
122121 }
@@ -165,20 +164,17 @@ Rectangle {
165164
166165 function test_fallback() {
167166 previewHeader.widgetData = brokenheaderjson;
168− tryCompareFunction(function() { return findChild(previewHeader, "mascotShape") != null }, true);
169167 var mascot = findChild(previewHeader, "mascotShape");
170168 compare(mascot.visible, false);
171169
172170 previewHeader.widgetData = {};
173171 previewHeader.widgetData = fallbackheaderjson;
174− tryCompareFunction(function() { return findChild(previewHeader, "mascotShape") != null }, true);
175172 var mascot = findChild(previewHeader, "mascotShape");
176173 tryCompare(mascot, "visible", true);
177174 tryCompare(mascot.source, "status", Image.Ready);
178175
179176 previewHeader.widgetData = {};
180177 previewHeader.widgetData = emptyfallbackheaderjson;
181− tryCompareFunction(function() { return findChild(previewHeader, "mascotShape") != null }, true);
182178 var mascot = findChild(previewHeader, "mascotShape");
183179 tryCompare(mascot, "visible", true);
184180 tryCompare(mascot.source, "status", Image.Ready);
tests/qmltests/Dash/Previews/tst_PreviewImageGallery.qml+6−6
@@ -31,8 +31,8 @@ Rectangle {
3131
3232 property var sourcesModel1: {
3333 "sources": [
34− "../../graphics/phone_background.jpg",
35− "../../graphics/tablet_background.jpg",
34+ "../../../tests/graphics/avatars/amanda@12.png",
35+ "../../../tests/graphics/avatars/funky@12.png",
3636 "../../../tests/graphics/clock@18.png",
3737 "../../graphics/borked"
3838 ]
@@ -40,8 +40,8 @@ Rectangle {
4040
4141 property var sourcesModel1WithFallback: {
4242 "sources": [
43− "../../graphics/phone_background.jpg",
44− "../../graphics/tablet_background.jpg",
43+ "../../../tests/graphics/avatars/amanda@12.png",
44+ "../../../tests/graphics/avatars/funky@12.png",
4545 "../../../tests/graphics/clock@18.png",
4646 "../../graphics/borked"
4747 ]
@@ -50,8 +50,8 @@ Rectangle {
5050
5151 property var sourcesModelEmptyWithFallback: {
5252 "sources": [
53− "../../graphics/phone_background.jpg",
54− "../../graphics/tablet_background.jpg",
53+ "../../../tests/graphics/avatars/amanda@12.png",
54+ "../../../tests/graphics/avatars/funky@12.png",
5555 "../../../tests/graphics/clock@18.png",
5656 ""
5757 ]
tests/qmltests/Dash/Previews/tst_PreviewInlineVideo.qml+42−1
@@ -76,8 +76,23 @@ Rectangle {
7676
7777 rootItem: inner
7878 }
79− }
8079
80+ Loader {
81+ id: videoPlaybackLoader
82+ anchors {
83+ left: parent.left
84+ right: parent.right
85+ top: videoPlayback.bottom
86+ topMargin: units.gu(2)
87+ }
88+
89+ sourceComponent: PreviewInlineVideo {
90+ widgetData: widgetData0
91+
92+ rootItem: inner
93+ }
94+ }
95+ }
8196 }
8297
8398 Rectangle {
@@ -98,6 +113,11 @@ Rectangle {
98113 }
99114 }
100115
116+ SignalSpy {
117+ id: spy
118+ signalName: "playing"
119+ }
120+
101121 UT.UnityTestCase {
102122 name: "PreviewInlineVideoTest"
103123 when: windowShown
@@ -110,5 +130,26 @@ Rectangle {
110130 var screenshotSource = screenshot.source
111131 compare(screenshotSource.toString(), "file:///test-video2-screenshot");
112132 }
133+
134+ function test_singleton() {
135+ var services = findChild(videoPlaybackLoader, "services");
136+ spy.clear();
137+ spy.target = services.mediaPlayer;
138+ mouseClick(videoPlaybackLoader, videoPlaybackLoader.width / 2, videoPlaybackLoader.height / 2);
139+ tryCompare(spy, "count", 1);
140+ tryCompareFunction(function() { return services.position > 3000; }, true);
141+
142+ // Simulate widget being destroyed and recreated
143+ videoPlaybackLoader.active = false;
144+ videoPlaybackLoader.active = true;
145+
146+ services = findChild(videoPlaybackLoader, "services");
147+ spy.clear();
148+ spy.target = services.mediaPlayer;
149+ compare(services.position, 0);
150+ mouseClick(videoPlaybackLoader, videoPlaybackLoader.width / 2, videoPlaybackLoader.height / 2);
151+ tryCompare(spy, "count", 1);
152+ tryCompareFunction(function() { return services.position > 3000; }, true, 1000);
153+ }
113154 }
114155}
tests/qmltests/Dash/Previews/tst_PreviewProgress.qml+14−1
@@ -1,5 +1,5 @@
11/*
2− * Copyright 2014 Canonical Ltd.
2+ * Copyright 2014-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
@@ -40,6 +40,11 @@ Rectangle {
4040 "source": { "dbus-name" : "somename", "dbus-object": "error" }
4141 }
4242
43+ property var progressjsonProcessing: {
44+ "type": "progress",
45+ "source": { "dbus-name" : "somename", "dbus-object": "processing" }
46+ }
47+
4348 SignalSpy {
4449 id: spy
4550 signalName: "triggered"
@@ -82,6 +87,14 @@ Rectangle {
8287 compare(args[0], "previewProgress");
8388 compare(args[1], "failed");
8489 compare(args[2], progressjsonError);
90+
91+ spy.clear();
92+
93+ var progressBar = findChild(previewProgress, "progressBar");
94+ compare(progressBar.indeterminate, false);
95+ previewProgress.widgetData = progressjsonProcessing;
96+ progressBar = findChild(previewProgress, "progressBar");
97+ tryCompare(progressBar, "indeterminate", true);
8598 }
8699 }
87100}
tests/qmltests/Dash/Previews/tst_PreviewRatingDisplayCreationRanges.qml+1
@@ -18,6 +18,7 @@ import QtQuick 2.4
1818import QtTest 1.0
1919import Ubuntu.Components 1.3
2020import "../../../../qml/Dash/Previews"
21+import "../../../../qml/Components"
2122import Unity.Test 0.1 as UT
2223
2324Flickable {
tests/qmltests/Dash/Previews/tst_PreviewRatingInput.qml+32
@@ -53,6 +53,21 @@ Rectangle {
5353 widgetId: "previewRatingInput"
5454 }
5555
56+ Loader {
57+ id: previewRatingInputLoader
58+ anchors {
59+ left: parent.left
60+ right: parent.right
61+ top: previewRatingInput.bottom
62+ topMargin: units.gu(2)
63+ }
64+
65+ sourceComponent: PreviewRatingInput {
66+ widgetData: widgetDataBoth
67+ widgetId: "previewRatingInputLoader"
68+ }
69+ }
70+
5671 SignalSpy {
5772 id: spy
5873 signalName: "triggered"
@@ -210,5 +225,22 @@ Rectangle {
210225 compare(args[2]["review"], data.triggeredData["review"]);
211226 verify(args[2]["author"] !== undefined); // Just verifying it exists now
212227 }
228+
229+ function test_singleton() {
230+ var ratingLoader = findChild(previewRatingInputLoader, "rating");
231+ var reviewTextAreaLoader = findChild(previewRatingInputLoader, "reviewTextArea");
232+
233+ ratingLoader.value = 3;
234+ reviewTextAreaLoader.text = "singleton test text";
235+
236+ // Simulate widget being destroyed and recreated
237+ previewRatingInputLoader.active = false;
238+ previewRatingInputLoader.active = true;
239+
240+ ratingLoader = findChild(previewRatingInputLoader, "rating");
241+ reviewTextAreaLoader = findChild(previewRatingInputLoader, "reviewTextArea");
242+ tryCompare(ratingLoader, "value", 3);
243+ tryCompare(reviewTextAreaLoader, "text", "singleton test text");
244+ }
213245 }
214246}
tests/qmltests/Dash/Previews/tst_PreviewZoomableImage.qml+7−7
@@ -26,7 +26,7 @@ Rectangle {
2626 color: "lightgrey"
2727
2828 property var widgetData0: {
29− "source": "../../graphics/phone_background.jpg",
29+ "source": "../../../tests/graphics/avatars/amanda@12.png",
3030 "zoomable": false
3131 }
3232
@@ -36,17 +36,17 @@ Rectangle {
3636
3737 property var widgetData2: {
3838 "source": "fadsasf",
39− "fallback": "../../graphics/phone_background.jpg"
39+ "fallback": "../../../tests/graphics/avatars/amanda@12.png"
4040 }
4141
4242 property var widgetData3: {
4343 "source": "",
44− "fallback": "../../graphics/phone_background.jpg"
44+ "fallback": "../../../tests/graphics/avatars/amanda@12.png"
4545 }
4646
4747 property var widgetData4: {
4848 "source": "fadsasf",
49− "fallback": "../../graphics/phone_background.jpg"
49+ "fallback": "../../../tests/graphics/avatars/amanda@12.png"
5050 }
5151
5252 Loader {
@@ -123,11 +123,11 @@ Rectangle {
123123 waitForRendering(zoomableImage);
124124 waitForRendering(lazyImage);
125125 tryCompare(lazyImage, "state", "ready");
126− verify(lazyImage.sourceImage.source.toString().indexOf("phone_background") > -1);
127− zoomableImage.widgetData["source"] = "../../graphics/tablet_background.jpg"
126+ verify(lazyImage.sourceImage.source.toString().indexOf("amanda") > -1);
127+ zoomableImage.widgetData["source"] = "../../../tests/graphics/avatars/funky@12.png"
128128 zoomableImage.widgetDataChanged();
129129 tryCompare(lazyImage, "state", "ready");
130− verify(lazyImage.sourceImage.source.toString().indexOf("tablet_background") > -1);
130+ verify(lazyImage.sourceImage.source.toString().indexOf("funky") > -1);
131131 }
132132 }
133133}
tests/qmltests/Dash/tst_Card.qml+7−2
@@ -618,12 +618,17 @@ Rectangle {
618618 waitForRendering(card);
619619
620620 var touchdown = findChild(card, "touchdown");
621+ compare(touchdown, null);
621622
622− compare(touchdown.visible, false);
623623 mousePress(card);
624+
625+ touchdown = findChild(card, "touchdown");
624626 compare(touchdown.visible, true);
627+
625628 mouseRelease(card);
626− compare(touchdown.visible, false);
629+
630+ touchdown = findChild(card, "touchdown");
631+ compare(touchdown, null);
627632 }
628633
629634 function test_paddings_data() {
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− tryCompareFunction(function() { return findChild(internalCard, "titleLabel").horizontalAlignment == Text.AlignLeft; }, true);
342+ tryCompareFunction(function() { return findChild(internalCard, "titleLabel", 0 /* timeout */).horizontalAlignment == Text.AlignLeft; }, true);
343343
344344 cardTool.components['title'] = { "field": "title", "align": "center" };
345345 cardTool.componentsChanged();
346346
347− tryCompareFunction(function() { return findChild(internalCard, "titleLabel").horizontalAlignment == data.value; }, true);
347+ tryCompareFunction(function() { return findChild(internalCard, "titleLabel", 0 /* timeout */).horizontalAlignment == data.value; }, true);
348348
349349 cardTool.components['title'] = { "field": "title" };
350350 cardTool.componentsChanged();
351351
352− tryCompareFunction(function() { return findChild(internalCard, "titleLabel").horizontalAlignment == data.value; }, true);
352+ tryCompareFunction(function() { return findChild(internalCard, "titleLabel", 0 /* timeout */).horizontalAlignment == data.value; }, true);
353353
354354 cardTool.components['title'] = { "field": "title", "align": "left" };
355355 cardTool.componentsChanged();
356356
357− tryCompareFunction(function() { return findChild(internalCard, "titleLabel").horizontalAlignment == Text.AlignLeft; }, true);
357+ tryCompareFunction(function() { return findChild(internalCard, "titleLabel", 0 /* timeout */).horizontalAlignment == Text.AlignLeft; }, true);
358358 }
359359
360360 function test_categoryLayout_data() {
tests/qmltests/Dash/tst_Dash.qml+25−15
@@ -76,7 +76,7 @@ Item {
7676 var genericScopeView = dashContentList.currentItem;
7777 var categoryListView = findChild(genericScopeView, "categoryListView");
7878 tryCompareFunction(function() {
79− var category = findChild(genericScopeView, categoryName);
79+ var category = findChild(genericScopeView, categoryName, 0 /* timeout */);
8080 if (category && category.y > 0 && category.y < genericScopeView.height) return true;
8181 touchFlick(genericScopeView, genericScopeView.width/2, units.gu(20),
8282 genericScopeView.width/2, genericScopeView.y)
@@ -84,7 +84,7 @@ Item {
8484 return false;
8585 }, true);
8686
87− tryCompareFunction(function() { return findChild(genericScopeView, "delegate0") !== null; }, true);
87+ tryCompareFunction(function() { return findChild(genericScopeView, "delegate0", 0 /* timeout */) !== null; }, true);
8888 return findChild(genericScopeView, categoryName);
8989 }
9090
@@ -94,9 +94,9 @@ Item {
9494 if (category === undefined) category = 0;
9595 if (delegate === undefined) delegate = 0;
9696 tryCompareFunction(function() {
97− var cardGrid = findChild(genericScopeView, "dashCategory"+category);
97+ var cardGrid = findChild(genericScopeView, "dashCategory"+category, 0 /* timeout */);
9898 if (cardGrid != null) {
99− var tile = findChild(cardGrid, "delegate"+delegate);
99+ var tile = findChild(cardGrid, "delegate"+delegate, 0 /* timeout */);
100100 return tile != null;
101101 }
102102 return false;
@@ -215,6 +215,8 @@ Item {
215215
216216 // Click in first item
217217 var favScopesListCategoryList = findChild(findChild(dash, "scopesListCategoryfavorites"), "scopesListCategoryInnerList");
218+ tryCompareFunction(function() { return favScopesListCategoryList.currentItem !== null }, true);
219+ waitForRendering(favScopesListCategoryList.currentItem);
218220 mouseClick(favScopesListCategoryList.currentItem);
219221
220222 // Make sure animation went back
@@ -409,7 +411,7 @@ Item {
409411 compare(dashContentList.currentItem.scopeId, "clickscope");
410412
411413 // Move to second scope
412− touchFlick(dash, dash.width / 2, units.gu(2), dash.width / 5, units.gu(2));
414+ mouseFlick(dash, dash.width / 2, units.gu(2), dash.width / 5, units.gu(2));
413415 tryCompare(dashContentList, "currentIndex", 1);
414416 compare(dashContentList.currentItem.scopeId, "MockScope1");
415417 }
@@ -473,9 +475,9 @@ Item {
473475 compare(dashContentList.currentItem.scopeId, "MockScope1");
474476
475477 tryCompareFunction(function() {
476− var cardGrid = findChild(dashContentList, "dashCategory0");
478+ var cardGrid = findChild(dashContentList, "dashCategory0", 0 /* timeout */);
477479 if (cardGrid != null) {
478− var tile = findChild(cardGrid, "delegate0");
480+ var tile = findChild(cardGrid, "delegate0", 0 /* timeout */);
479481 return tile != null;
480482 }
481483 return false;
@@ -520,9 +522,9 @@ Item {
520522 var categoryListView = findChild(dashTempScopeItem, "categoryListView");
521523 categoryListView.positionAtBeginning();
522524 tryCompareFunction(function() {
523− var cardGrid = findChild(dashTempScopeItem, "dashCategory0");
525+ var cardGrid = findChild(dashTempScopeItem, "dashCategory0", 0 /* timeout */);
524526 if (cardGrid != null) {
525− var tile = findChild(cardGrid, "delegate0");
527+ var tile = findChild(cardGrid, "delegate0", 0 /* timeout */);
526528 return tile != null;
527529 }
528530 return false;
@@ -631,18 +633,26 @@ Item {
631633 tryCompare(dashContent.currentScope, "id", "clickscope");
632634
633635 scrollToCategory("dashCategorypredefined");
634− var tile = getCategoryDelegate("predefined", 2);
635− var proportionalShape = findChildsByType(tile, "UCProportionalShape");
636− compare(proportionalShape.length, 1);
636+ tryCompareFunction(function() {
637+ var tile = getCategoryDelegate("predefined", 2);
638+ var proportionalShape = findChildsByType(tile, "UCProportionalShape");
639+ return proportionalShape.length === 1;
640+ },
641+ true
642+ );
637643
638644 dash.setCurrentScope("libertine-scope.ubuntu_libertine-scope");
639645 var dashContent = findChild(dash, "dashContent");
640646 tryCompare(dashContent.currentScope, "id", "libertine-scope.ubuntu_libertine-scope");
641647
642648 scrollToCategory("dashCategory2");
643− tile = getCategoryDelegate("2", 2);
644− proportionalShape = findChildsByType(tile, "UCProportionalShape");
645− compare(proportionalShape.length, 1);
649+ tryCompareFunction(function() {
650+ var tile = getCategoryDelegate("2", 2);
651+ var proportionalShape = findChildsByType(tile, "UCProportionalShape");
652+ return proportionalShape.length === 1;
653+ },
654+ true
655+ );
646656 }
647657
648658 function test_tempScopeItemXOnResize()
tests/qmltests/Dash/tst_DashContent.qml+5−7
@@ -261,11 +261,11 @@ Item {
261261
262262 function test_carouselAspectRatio() {
263263 tryCompareFunction(function() {
264− var scope = findChild(dashContent, "scopeLoader0");
264+ var scope = findChild(dashContent, "scopeLoader0", 0 /* timeout */);
265265 if (scope != null) {
266− var dashCategory1 = findChild(scope, "dashCategory1");
266+ var dashCategory1 = findChild(scope, "dashCategory1", 0 /* timeout */);
267267 if (dashCategory1 != null) {
268− var tile = findChild(dashCategory1, "carouselDelegate1");
268+ var tile = findChild(dashCategory1, "carouselDelegate1", 0 /* timeout */);
269269 return tile != null;
270270 }
271271 }
@@ -282,7 +282,6 @@ Item {
282282
283283 function test_mainNavigation() {
284284 var dashContentList = findChild(dashContent, "dashContentList");
285− tryCompareFunction(function() { return findChild(dashContentList.currentItem, "dashNavigation") != null; }, true);
286285 var dashNavigation = findChild(dashContentList.currentItem, "dashNavigation");
287286 var peExtraPanel = findChild(dashContentList.currentItem, "peExtraPanel");
288287 var searchButton = findChild(dashContentList.currentItem, "search_button");
@@ -405,7 +404,6 @@ Item {
405404
406405 function goToSecondLevel() {
407406 var dashContentList = findChild(dashContent, "dashContentList");
408− tryCompareFunction(function() { return findChild(dashContentList.currentItem, "dashNavigation") != null; }, true);
409407 var dashNavigation = findChild(dashContentList.currentItem, "dashNavigation");
410408 var peExtraPanel = findChild(dashContentList.currentItem, "peExtraPanel");
411409 var searchButton = findChild(dashContentList.currentItem, "search_button");
@@ -580,7 +578,7 @@ Item {
580578
581579 function test_primaryFilter() {
582580 var dashContentList = findChild(dashContent, "dashContentList");
583− tryCompareFunction(function() { return findChild(dashContentList.currentItem, "dashNavigation") != null; }, true);
581+ tryCompareFunction(function() { return findChild(dashContentList.currentItem, "dashNavigation", 0 /* timeout */) != null; }, true);
584582 dashContentList.currentItem.item.scope.setHasNavigation(false);
585583 var peExtraPanel = findChild(dashContentList.currentItem, "peExtraPanel");
586584 var searchButton = findChild(dashContentList.currentItem, "search_button");
@@ -589,7 +587,7 @@ Item {
589587 mouseClick(searchButton);
590588 tryCompare(peExtraPanel, "visible", true);
591589
592− tryCompareFunction(function() { return findChild(peExtraPanel, "OSF3") != null; }, true);
590+ tryCompareFunction(function() { return findChild(peExtraPanel, "OSF3", 0 /* timeout */) != null; }, true);
593591 }
594592
595593 function test_searchHint() {
tests/qmltests/Dash/tst_GenericScopeView.qml+58−61
@@ -22,6 +22,7 @@ import "../../../qml/Dash"
2222import "../../../qml/Components"
2323import Ubuntu.Components 1.3
2424import Unity.Test 0.1 as UT
25+import Qt.test.qtestroot 1.0
2526
2627Item {
2728 id: shell
@@ -40,7 +41,12 @@ Item {
4041 Scopes {
4142 id: scopes
4243 // for tryGenericScopeView
43− onLoadedChanged: if (loaded) genericScopeView.scope = scopes.getScope(2);
44+ onLoadedChanged: {
45+ if (loaded && UT.Util.isInstanceOf(QTestRootObject, "DummyTestRootObject")) {
46+ genericScopeView.scope = scopes.getScope(2);
47+ genericScopeView.isCurrent = true;
48+ }
49+ }
4450 }
4551
4652 MockScope {
@@ -92,7 +98,7 @@ Item {
9298 function scrollToCategory(categoryName) {
9399 var categoryListView = findChild(genericScopeView, "categoryListView");
94100 tryCompareFunction(function() {
95− var category = findChild(genericScopeView, categoryName);
101+ var category = findChild(genericScopeView, categoryName, 0 /* timeout */);
96102 if (category && category.y > 0 && category.y < genericScopeView.height) return true;
97103 touchFlick(genericScopeView, genericScopeView.width/2, units.gu(20),
98104 genericScopeView.width/2, genericScopeView.y)
@@ -100,7 +106,7 @@ Item {
100106 return false;
101107 }, true);
102108
103− tryCompareFunction(function() { return findChild(genericScopeView, "delegate0") !== null; }, true);
109+ tryCompareFunction(function() { return findChild(genericScopeView, "delegate0", 0 /* timeout */) !== null; }, true);
104110 return findChild(genericScopeView, categoryName);
105111 }
106112
@@ -163,7 +169,7 @@ Item {
163169 }
164170
165171 function test_expand_collapse() {
166− tryCompareFunction(function() { return findChild(genericScopeView, "dashSectionHeader0") != null; }, true);
172+ tryCompareFunction(function() { return findChild(genericScopeView, "dashSectionHeader0", 0 /* timeout */) != null; }, true);
167173
168174 var category = findChild(genericScopeView, "dashCategory0")
169175 var seeAll = findChild(category, "seeAll")
@@ -184,7 +190,7 @@ Item {
184190
185191 function test_expand_expand_collapse() {
186192 // wait for the item to be there
187− tryCompareFunction(function() { return findChild(genericScopeView, "dashSectionHeader2") != null; }, true);
193+ tryCompareFunction(function() { return findChild(genericScopeView, "dashSectionHeader2", 0 /* timeout */) != null; }, true);
188194
189195 var categoryListView = findChild(genericScopeView, "categoryListView");
190196 categoryListView.contentY = categoryListView.height;
@@ -212,7 +218,6 @@ Item {
212218 }
213219
214220 function test_headerLink() {
215− tryCompareFunction(function() { return findChild(genericScopeView, "dashSectionHeader1") != null; }, true);
216221 var header = findChild(genericScopeView, "dashSectionHeader1");
217222
218223 spy.target = genericScopeView.scope;
@@ -231,7 +236,7 @@ Item {
231236 categoryListView.contentY = categoryListView.height * 2;
232237
233238 // wait for the item to be there
234− tryCompareFunction(function() { return findChild(genericScopeView, "dashSectionHeader4") != null; }, true);
239+ tryCompareFunction(function() { return findChild(genericScopeView, "dashSectionHeader4", 0 /* timeout */) != null; }, true);
235240
236241 var categoryView = findChild(genericScopeView, "dashCategory4");
237242 verify(categoryView, "Can't find the category view.");
@@ -243,7 +248,6 @@ Item {
243248 }
244249
245250 function test_narrow_delegate_ranges_expand() {
246− tryCompareFunction(function() { return findChild(genericScopeView, "dashCategory0") !== null; }, true);
247251 var category = findChild(genericScopeView, "dashCategory0")
248252 tryCompare(category, "expanded", false);
249253
@@ -275,7 +279,6 @@ Item {
275279 function test_single_category_expansion() {
276280 genericScopeView.scope = scopes.getScope(3);
277281
278− tryCompareFunction(function() { return findChild(genericScopeView, "dashCategory0") != undefined; }, true);
279282 var category = findChild(genericScopeView, "dashCategory0")
280283 compare(category.expandable, false, "Only category should not be expandable.");
281284
@@ -289,9 +292,9 @@ Item {
289292 if (category === undefined) category = 0;
290293 if (delegate === undefined) delegate = 0;
291294 tryCompareFunction(function() {
292− var cardGrid = findChild(genericScopeView, "dashCategory"+category);
295+ var cardGrid = findChild(genericScopeView, "dashCategory"+category, 0 /* timeout */);
293296 if (cardGrid != null) {
294− var tile = findChild(cardGrid, "delegate"+delegate);
297+ var tile = findChild(cardGrid, "delegate"+delegate, 0 /* timeout */);
295298 return tile != null;
296299 }
297300 return false;
@@ -332,9 +335,9 @@ Item {
332335 genericScopeView.scope = scopes.getScope("NullPreviewScope");
333336
334337 tryCompareFunction(function() {
335− var cardGrid = findChild(genericScopeView, 0);
338+ var cardGrid = findChild(genericScopeView, 0, 0 /* timeout */);
336339 if (cardGrid != null) {
337− var tile = findChild(cardGrid, 0);
340+ var tile = findChild(cardGrid, 0, 0 /* timeout */);
338341 return tile != null;
339342 }
340343 return false;
@@ -385,7 +388,6 @@ Item {
385388
386389 tryCompare(testCase.subPageLoader, "open", false);
387390
388− tryCompareFunction(function() { return findChild(category, "delegate1") != null; }, true);
389391 var tile = findChild(category, "delegate1");
390392
391393 mouseClick(tile);
@@ -487,7 +489,6 @@ Item {
487489
488490 scrollToEnd();
489491
490− tryCompareFunction(function() { return findChild(genericScopeView, "dashCategory1") !== null; }, true);
491492 var category1 = findChild(genericScopeView, "dashCategory1")
492493 var seeAll1 = findChild(category1, "seeAll")
493494 verify(category1.expandable);
@@ -570,53 +571,40 @@ Item {
570571 genericScopeView.scope = !genericScopeView.scope;
571572 }
572573
573− function test_pullToRefresh() {
574− waitForRendering(genericScopeView)
575−
576− mouseFlick(genericScopeView,
577− genericScopeView.width/2, units.gu(10),
578− genericScopeView.width/2, units.gu(80),
579− true, false)
580−
581− var pullToRefresh = findChild(genericScopeView, "pullToRefresh")
582− tryCompare(pullToRefresh, "releaseToRefresh", true)
583−
584− spy.target = genericScopeView.scope
585− spy.signalName = "refreshed"
586−
587− mouseRelease(genericScopeView)
588− tryCompare(pullToRefresh, "releaseToRefresh", false)
574+ function test_pullToRefresh_data() {
575+ return [
576+ { tag: "Tall window long flick", shellHeight: units.gu(100), flickLength: units.gu(70), refresh: true },
577+ { tag: "Tall window short flick", shellHeight: units.gu(100), flickLength: units.gu(25), refresh: false },
578+ { tag: "Short window", shellHeight: units.gu(30), flickLength: units.gu(25), refresh: true }
579+ ];
580+ }
589581
590− spy.wait()
591− compare(spy.count, 1)
582+ function test_pullToRefresh(data) {
583+ var initialHeight = shell.height;
584+ shell.height = data.shellHeight;
585+ waitForRendering(shell);
592586
593− // test short swipe doesn't refresh on tall window
594587 mouseFlick(genericScopeView,
595− genericScopeView.width/2, units.gu(10),
596− genericScopeView.width/2, units.gu(20),
597− true, false)
598− mouseRelease(genericScopeView)
599− compare(spy.count, 1)
600−
601− // resize window, repeat the test
602− var initialHeight = shell.height
603− shell.height = units.gu(30)
604− waitForRendering(shell)
605− mouseFlick(genericScopeView,
606− genericScopeView.width/2, units.gu(10),
607− genericScopeView.width/2, units.gu(20),
608− true, false)
588+ genericScopeView.width/2, units.gu(6),
589+ genericScopeView.width/2, units.gu(6) + data.flickLength,
590+ true, false);
591+
592+ var pullToRefresh = findChild(genericScopeView, "pullToRefresh");
593+ tryCompare(pullToRefresh, "releaseToRefresh", data.refresh);
609594
610− tryCompare(pullToRefresh, "releaseToRefresh", true)
595+ spy.target = genericScopeView.scope;
596+ spy.signalName = "refreshed";
611597
612− mouseRelease(genericScopeView)
613− tryCompare(pullToRefresh, "releaseToRefresh", false)
598+ mouseRelease(genericScopeView);
599+ tryCompare(pullToRefresh, "releaseToRefresh", false);
614600
615− spy.wait()
616− compare(spy.count, 2)
601+ if (data.refresh) {
602+ spy.wait();
603+ compare(spy.count, 1);
604+ }
617605
618− shell.height = initialHeight
619− waitForRendering(shell)
606+ shell.height = initialHeight;
607+ waitForRendering(shell);
620608 }
621609
622610 function test_item_noninteractive() {
@@ -642,11 +630,15 @@ Item {
642630 var item0 = findChild(cardGrid, "delegate0");
643631 waitForRendering(item0);
644632 compare(item0.enabled, false);
633+
645634 var touchdown = findChild(item0, "touchdown");
635+ compare(touchdown, null);
646636
647− compare(touchdown.visible, false);
648637 mousePress(item0);
649− compare(touchdown.visible, false);
638+
639+ touchdown = findChild(item0, "touchdown");
640+ compare(touchdown, null);
641+
650642 mouseRelease(item0);
651643
652644 cardTool.template["non-interactive"] = false;
@@ -656,20 +648,25 @@ Item {
656648 waitForRendering(category0);
657649 item0 = findChild(cardGrid, "delegate0");
658650 compare(item0.enabled, true);
659− var touchdown = findChild(item0, "touchdown");
660651
661− compare(touchdown.visible, false);
652+ touchdown = findChild(item0, "touchdown");
653+ compare(touchdown, null);
654+
662655 mousePress(item0);
656+
657+ touchdown = findChild(item0, "touchdown");
663658 compare(touchdown.visible, true);
659+
664660 mouseRelease(item0);
665− compare(touchdown.visible, false);
661+
662+ touchdown = findChild(item0, "touchdown");
663+ compare(touchdown, null);
666664 closePreview();
667665 }
668666
669667 function test_carousel_borderSource() {
670668 var category = scrollToCategory("dashCategory1");
671669 var tile = findChild(category, "carouselDelegate0");
672− tryCompareFunction(function() { return findChild(tile, "artShapeLoader") !== null; }, true);
673670 var artShapeLoader = findChild(tile, "artShapeLoader");
674671 var shape = findChildsByType(artShapeLoader, "UCUbuntuShape");
675672 compare(shape.borderSource, undefined);
tests/qmltests/Greeter/TestView.qml+4−3
@@ -27,6 +27,7 @@ Item {
2727 property int delayMinutes
2828 property real backgroundTopMargin
2929 property url background
30+ property bool hasCustomBackground
3031 property bool locked
3132 property bool alphanumeric
3233 property var userModel
@@ -51,7 +52,7 @@ Item {
5152 signal _notifyAuthenticationSucceededCalled(bool showFakePassword)
5253 signal _notifyAuthenticationFailedCalled()
5354 signal _showErrorMessageCalled(string msg)
54− signal _resetCalled()
55+ signal _resetCalled(bool forceShow)
5556 signal _tryToUnlockCalled(bool toTheRight)
5657
5758 function showMessage(html) {
@@ -84,8 +85,8 @@ Item {
8485 _showErrorMessageCalled(msg);
8586 }
8687
87− function reset() {
88− _resetCalled();
88+ function reset(forceShow) {
89+ _resetCalled(forceShow);
8990 }
9091
9192 function tryToUnlock(toTheRight) {
tests/qmltests/Greeter/tst_Greeter.qml+8−1
@@ -29,7 +29,7 @@ Item {
2929 width: units.gu(120)
3030 height: units.gu(80)
3131
32− property url defaultBackground: Qt.resolvedUrl("../../../qml/graphics/tablet_background.jpg")
32+ property url defaultBackground: "/usr/share/backgrounds/warty-final-ubuntu.png"
3333
3434 Component.onCompleted: {
3535 // set the mock mode before loading
@@ -368,6 +368,12 @@ Item {
368368 compare(view.background, Qt.resolvedUrl("testing"));
369369 }
370370
371+ function test_hasCustomBackground() {
372+ verify(!view.hasCustomBackground);
373+ greeter.hasCustomBackground = true;
374+ verify(view.hasCustomBackground);
375+ }
376+
371377 function test_notifyAboutToFocusApp() {
372378 greeter.notifyUserRequestedApp("fake-app");
373379 compare(viewTryToUnlockSpy.count, 1);
@@ -559,6 +565,7 @@ Item {
559565
560566 LightDM.Greeter.showGreeter();
561567 compare(viewResetSpy.count, 1);
568+ compare(viewResetSpy.signalArguments[0][0], true);
562569 tryCompare(viewShowPromptSpy, "count", 1);
563570 }
564571
tests/qmltests/Greeter/tst_NarrowView.qml+47−12
@@ -20,6 +20,7 @@ import ".."
2020import "../../../qml/Greeter"
2121import LightDM.IntegratedLightDM 0.1 as LightDM
2222import Ubuntu.Components 1.3
23+import Ubuntu.Telephony 0.1 as Telephony
2324import Unity.Test 0.1 as UT
2425
2526Item {
@@ -39,7 +40,7 @@ Item {
3940 property bool itemDestroyed: false
4041 sourceComponent: Component {
4142 NarrowView {
42− background: Qt.resolvedUrl("../../../qml/graphics/phone_background.jpg")
43+ background: "/usr/share/backgrounds/warty-final-ubuntu.png"
4344 userModel: LightDM.Users
4445 infographicModel: LightDM.Infographic
4546
@@ -293,6 +294,8 @@ Item {
293294 function init() {
294295 view.currentIndex = 0; // break binding with text field
295296
297+ telepathyHelper.ready = true;
298+ telepathyHelper.emergencyCallsAvailable = true;
296299 selectedSpy.clear();
297300 respondedSpy.clear();
298301 teaseSpy.clear();
@@ -328,14 +331,6 @@ Item {
328331 waitForRendering(view);
329332 }
330333
331− function enterPin(pin) {
332− for (var i = 0; i < pin.length; ++i) {
333− var character = pin.charAt(i);
334− var button = findChild(view, "pinPadButton" + character);
335− tap(button);
336− }
337− }
338−
339334 function test_tease_data() {
340335 return [
341336 {tag: "left", x: 0, offset: 0, count: 1},
@@ -350,10 +345,23 @@ Item {
350345 compare(teaseSpy.count, data.count);
351346 }
352347
348+ function test_customBackground() {
349+ var lockscreenShade = findChild(view, "lockscreenShade");
350+ var backgroundShade = findChild(view, "backgroundShade");
351+
352+ compare(lockscreenShade.opacity, 0);
353+ verify(!backgroundShade.visible);
354+
355+ view.hasCustomBackground = true;
356+ compare(lockscreenShade.opacity, 0.4);
357+ verify(backgroundShade.visible);
358+ }
359+
353360 function test_respondedWithPin() {
354361 view.locked = true;
362+ view.showPrompt("", true, true);
355363 swipeAwayCover();
356− enterPin("1234");
364+ typeString("1234");
357365 compare(respondedSpy.count, 1);
358366 compare(respondedSpy.signalArguments[0][0], "1234");
359367 }
@@ -361,6 +369,7 @@ Item {
361369 function test_respondedWithPassphrase() {
362370 view.locked = true;
363371 view.alphanumeric = true;
372+ view.showPrompt("", true, true);
364373 swipeAwayCover();
365374 typeString("test");
366375 keyClick(Qt.Key_Enter);
@@ -382,12 +391,38 @@ Item {
382391 compare(respondedSpy.signalArguments[0][0], "");
383392 }
384393
385− function test_emergencyCall() {
394+ function test_emergencyCall_data() {
395+ return [
396+ {tag: "phone", available: true},
397+ {tag: "desktop", available: false},
398+ ];
399+ }
400+ function test_emergencyCall(data) {
401+ telepathyHelper.emergencyCallsAvailable = data.available;
386402 view.locked = true;
387403 swipeAwayCover();
388404 var emergencyCallLabel = findChild(view, "emergencyCallLabel");
389405 tap(emergencyCallLabel);
390− compare(emergencySpy.count, 1);
406+ if (!data.available) {
407+ expectFail("", "Bug 1616538 prevents us supporting conditional emergency button support");
408+ }
409+ compare(emergencyCallLabel.visible, data.available ? true : false);
410+ compare(emergencySpy.count, data.available ? 1 : 0);
411+ }
412+
413+ function test_emergencyCallAvailability() {
414+ view.locked = true;
415+ var emergencyCallLabel = findChild(view, "emergencyCallLabel");
416+ verify(emergencyCallLabel.visible);
417+
418+ telepathyHelper.emergencyCallsAvailable = false;
419+ telepathyHelper.ready = true;
420+ expectFail("", "Bug 1616538 prevents us supporting conditional emergency button support");
421+ verify(!emergencyCallLabel.visible);
422+
423+ telepathyHelper.emergencyCallsAvailable = true;
424+ telepathyHelper.ready = false;
425+ verify(!emergencyCallLabel.visible);
391426 }
392427
393428 function test_fullyShown() {
tests/qmltests/Greeter/tst_WideView.qml+154−7
@@ -24,6 +24,7 @@ import Unity.Test 0.1 as UT
2424
2525StyledItem {
2626 id: root
27+
2728 width: units.gu(120)
2829 height: units.gu(80)
2930 focus: true
@@ -49,7 +50,7 @@ StyledItem {
4950 WideView {
5051 id: view
5152
52− background: Qt.resolvedUrl("../../../qml/graphics/tablet_background.jpg")
53+ background: "/usr/share/backgrounds/warty-final-ubuntu.png"
5354 userModel: LightDM.Users
5455 infographicModel: LightDM.Infographic
5556
@@ -111,11 +112,9 @@ StyledItem {
111112 color: theme.palette.normal.background
112113 width: units.gu(40)
113114 height: parent.height
114−
115115 Column {
116116 anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
117117 spacing: units.gu(1)
118−
119118 Row {
120119 Button {
121120 text: "Show Last Chance"
@@ -266,6 +265,51 @@ StyledItem {
266265 text: "emergency: " + emergencySpy.count
267266 }
268267 }
268+ Row {
269+ id: multipleSessions
270+ CheckBox {
271+ id: multipleSessionsCheckbox
272+ onClicked: {
273+ if (checked) {
274+ LightDM.Sessions.testScenario = "multipleSessions"
275+ } else {
276+ LightDM.Sessions.testScenario = "singleSession"
277+ }
278+ }
279+ Connections {
280+ target: LightDM.Sessions
281+ onTestScenarioChanged: {
282+ if (LightDM.Sessions.testScenario === "multipleSessions") {
283+ multipleSessionsCheckbox.checked = true;
284+ } else {
285+ multipleSessionsCheckbox.checked = false;
286+ }
287+ }
288+ }
289+ }
290+ Label {
291+ text: "Multiple Sessions"
292+ }
293+ }
294+ Row {
295+ Slider {
296+ id: numSessionsSlider
297+
298+ width: units.gu(10)
299+ minimumValue: 0
300+ maximumValue: LightDM.Sessions.numAvailableSessions
301+ value: LightDM.Sessions.numSessions
302+ visible: LightDM.Sessions.testScenario === "multipleSessions"
303+ Binding {
304+ target: LightDM.Sessions
305+ property: "numSessions"
306+ value: numSessionsSlider.value
307+ }
308+ }
309+ Label {
310+ text: "Available Sessions"
311+ }
312+ }
269313 Row {
270314 Button {
271315 text: "Reload View"
@@ -316,6 +360,7 @@ StyledItem {
316360 when: windowShown
317361
318362 property Item view: loader.status === Loader.Ready ? loader.item : null
363+ property url testIconDirectory: Qt.resolvedUrl("../../../qml/Greeter/graphics/session_icons")
319364
320365 function init() {
321366 selectIndex(0); // break binding with text field
@@ -323,6 +368,7 @@ StyledItem {
323368 respondedSpy.clear();
324369 teaseSpy.clear();
325370 emergencySpy.clear();
371+ LightDM.Sessions.testScenario = "multipleSessions"
326372 }
327373
328374 function cleanup() {
@@ -378,6 +424,92 @@ StyledItem {
378424 {tag: "right", x: view.width, offset: 0, count: 1, locked: false},
379425 ]
380426 }
427+
428+ function test_sessionIconsAreValid() {
429+ LightDM.Sessions.testScenario = "multipleSessions"
430+ var originalDirectories = LightDM.Sessions.iconSearchDirectories
431+ LightDM.Sessions.iconSearchDirectories = [testIconDirectory]
432+
433+ selectUser("has-password");
434+
435+ // Test the login list icon is valid
436+ var sessionChooserButton = findChild(view, "sessionChooserButton");
437+ compare(sessionChooserButton.visible, true);
438+
439+ var session = String(view.sessionToStart).toLowerCase();
440+ var icon = String(sessionChooserButton.icon);
441+ compare(icon.indexOf(session) > -1, true);
442+
443+ // Test the session list icons are valid
444+ var coverPage = findChild(view, "coverPage");
445+ coverPage.state = "SessionsList"
446+ var sessionsList = findChild(view, "sessionsList");
447+ tryCompare(sessionsList, "visible", true);
448+ }
449+
450+ function test_choosingNewSessionChangesLoginListIcon() {
451+ // Ensure the default session is selected (Ubuntu)
452+ loader.active = false;
453+ loader.active = true;
454+
455+ selectUser("has-password");
456+
457+ LightDM.Sessions.testScenario = "multipleSessions";
458+ var sessionChooserButton = findChild(view, "sessionChooserButton");
459+ var icon = String(sessionChooserButton.icon);
460+ compare(icon.indexOf("ubuntu") > -1, true);
461+
462+ tap(sessionChooserButton)
463+ for(var i = 0; i < LightDM.Sessions.count; i++) {
464+ var delegateName = "sessionDelegate" + String(i);
465+ var currentDelegate = findChild(view, delegateName);
466+ var sessionKey = LightDM.Sessions.data(i,LightDM.SessionRoles.KeyRole);
467+ if (sessionKey === "gnome-classic") {
468+ tap(currentDelegate);
469+ var sessionChooserButton = findChild(view, "sessionChooserButton");
470+ waitForRendering(sessionChooserButton);
471+ var icon = String(sessionChooserButton.icon);
472+ break;
473+ }
474+ }
475+
476+ compare(icon.indexOf("gnome") > -1, true,
477+ "Expected icon to contain gnome but it was " + icon);
478+ }
479+
480+ function test_noSessionsDoesntBreakView() {
481+ LightDM.Sessions.testScenario = "noSessions"
482+ compare(LightDM.Sessions.count, 0)
483+ }
484+
485+ function test_sessionIconNotShownWithOneSession() {
486+ LightDM.Sessions.testScenario = "singleSession"
487+ compare(LightDM.Sessions.count, 1);
488+
489+ var sessionChooserButton = findChild(view, "sessionChooserButton");
490+ tryCompare(sessionChooserButton, "visible", false);
491+ }
492+
493+ function test_sessionIconNotShownWithActiveUser() {
494+ LightDM.Sessions.testScenario = "multipleSessions";
495+ compare(LightDM.Sessions.count > 1, true);
496+
497+ selectUser("active");
498+
499+ var sessionChooserButton = findChild(view, "sessionChooserButton");
500+ tryCompare(sessionChooserButton, "visible", false);
501+ }
502+
503+ function test_sessionIconShownWithMultipleSessions() {
504+ LightDM.Sessions.testScenario = "multipleSessions"
505+ compare(LightDM.Sessions.count > 1, true);
506+
507+ selectUser("has-password");
508+
509+ var sessionChooserButton = findChild(view, "sessionChooserButton");
510+ tryCompare(sessionChooserButton, "visible", true);
511+ }
512+
381513 function test_tease(data) {
382514 view.locked = data.locked;
383515 view.dragHandleLeftMargin = data.offset;
@@ -393,6 +525,17 @@ StyledItem {
393525 compare(view.currentIndex, 0); // confirm we didn't change
394526 }
395527
528+ function test_customBackground() {
529+ var coverPage = findChild(view, "coverPage");
530+ var backgroundShade = findChild(coverPage, "backgroundShade");
531+
532+ verify(!view.hasCustomBackground);
533+ verify(!backgroundShade.visible);
534+
535+ view.hasCustomBackground = true;
536+ verify(backgroundShade.visible);
537+ }
538+
396539 function test_respondedWithPassword() {
397540 view.locked = true;
398541 view.showPrompt("Prompt", true, false);
@@ -509,17 +652,21 @@ StyledItem {
509652
510653 keyboardVisibleCheckBox.checked = true;
511654
512− tryCompare(loginList, "height", view.height - view.inputMethod.keyboardRectangle.height);
655+ var halfway = (view.height - loginList.highlightedHeight) / 2;
656+ var halfwayWithOsk = halfway - view.inputMethod.keyboardRectangle.height / 2;
657+ tryCompare(loginList, "boxVerticalOffset", halfwayWithOsk);
658+
659+ var highlightItem = findChild(loginList, "highlightItem");
513660 tryCompareFunction( function() {
514− var loginListRect = loginList.mapToItem(view, 0, 0, loginList.width, loginList.height);
515− return loginListRect.y + loginListRect.height <= view.inputMethod.keyboardRectangle.y;
661+ var highlightRect = highlightItem.mapToItem(view, 0, 0, highlightItem.width, highlightItem.height);
662+ return highlightRect.y + highlightRect.height <= view.inputMethod.keyboardRectangle.y;
516663 }, true);
517664
518665 // once the vkb goes away, loginList goes back to its full height
519666
520667 keyboardVisibleCheckBox.checked = false;
521668
522− tryCompare(loginList, "height", view.height);
669+ tryCompare(loginList, "boxVerticalOffset", halfway);
523670 }
524671
525672 function test_passphrase() {
tests/qmltests/Panel/Indicators/tst_MenuItemFactory.qml+1−1
@@ -409,7 +409,7 @@ Item {
409409 menuData.sensitive = data.enabled;
410410 menuData.icon = data.icon;
411411 menuData.ext = {
412− 'xCanonicalColor': data.colour,
412+ 'xCanonicalColor': data.color,
413413 'xCanonicalTime': data.time.getTime() / 1000 // expected in seconds
414414 };
415415
tests/qmltests/Panel/tst_IndicatorItem.qml+4−3
@@ -190,9 +190,10 @@ Rectangle {
190190
191191 indicatorItem.expanded = true;
192192 tryCompare(findChild(indicatorItem, "icon0"), "color", theme.palette.disabled.backgroundText);
193− tryCompare(findChild(indicatorItem, "leftLabel"), "color", theme.palette.disabled.backgroundText);
194− tryCompare(findChild(indicatorItem, "rightLabel"), "color", theme.palette.disabled.backgroundText);
195− tryCompare(findChild(indicatorItem, "indicatorName"), "color", theme.palette.disabled.backgroundText);
193+ // WORKAROUND for a Qt bug in comparing colors: https://bugs.launchpad.net/ubuntu/+source/qtdeclarative-opensource-src/+bug/1619281
194+ fuzzyCompare(findChild(indicatorItem, "leftLabel").color, theme.palette.disabled.backgroundText, 0.001);
195+ fuzzyCompare(findChild(indicatorItem, "rightLabel").color, theme.palette.disabled.backgroundText, 0.001);
196+ fuzzyCompare(findChild(indicatorItem, "indicatorName").color, theme.palette.disabled.backgroundText, 0.001);
196197
197198 indicatorItem.selected = true;
198199 tryCompare(findChild(indicatorItem, "icon0"), "color", theme.palette.normal.backgroundText);
tests/qmltests/Panel/tst_IndicatorPage.qml−2
@@ -177,8 +177,6 @@ Item {
177177 initializeMenuData(fullMenuData);
178178
179179 var menuId = "menu"+data.remove
180−
181− tryCompareFunction(function() { return findChild(page, menuId) !== null;}, true);
182180 var menu = findChild(page, menuId);
183181
184182 menu.menuSelected();
tests/qmltests/Panel/tst_Panel.qml+19−2
@@ -182,6 +182,12 @@ IndicatorTest {
182182 signalName: "pressedChanged"
183183 }
184184
185+ SignalSpy {
186+ id: windowControlButtonsSpy
187+ target: PanelState
188+ signalName: "closeClicked"
189+ }
190+
185191 function init() {
186192 panel.fullscreenMode = false;
187193 callManager.foregroundCall = null;
@@ -197,6 +203,8 @@ IndicatorTest {
197203
198204 backgroundPressedSpy.clear();
199205 compare(backgroundPressedSpy.valid, true);
206+ windowControlButtonsSpy.clear();
207+ compare(windowControlButtonsSpy.valid, true);
200208 }
201209
202210 function get_indicator_item(index) {
@@ -380,8 +388,6 @@ IndicatorTest {
380388 tap(panel, stepLength * i, tapY);
381389 tryCompare(panel.indicators, "fullyClosed", true);
382390 }
383−
384− compare(backgroundPressedSpy.count, 0);
385391 }
386392
387393 function test_darkenedAreaEatsAllEvents() {
@@ -494,5 +500,16 @@ IndicatorTest {
494500 compare(panel.indicators.shown, false);
495501 tryCompare(panel.indicators, "fullyClosed", true);
496502 }
503+
504+ // https://bugs.launchpad.net/ubuntu/+source/unity8/+bug/1611959
505+ function test_windowControlButtonsFittsLaw() {
506+ var buttonsVisible = PanelState.buttonsVisible;
507+ PanelState.buttonsVisible = true;
508+ // click in very topleft corner and verify the close button got clicked too
509+ mouseMove(panel, 0, 0);
510+ mouseClick(panel, 0, 0, undefined /*button*/, undefined /*modifiers*/, 100 /*short delay*/);
511+ compare(windowControlButtonsSpy.count, 1);
512+ PanelState.buttonsVisible = buttonsVisible;
513+ }
497514 }
498515}
tests/qmltests/Stage/tst_DesktopStage.qml+3−3
@@ -75,7 +75,7 @@ Item {
7575 sourceComponent: Component {
7676 Stage {
7777 anchors.fill: parent
78− background: "../../../qml/graphics/tablet_background.jpg"
78+ background: "/usr/share/backgrounds/warty-final-ubuntu.png"
7979 focus: true
8080
8181 Component.onCompleted: {
@@ -487,7 +487,7 @@ Item {
487487
488488 // click again to restore
489489 mouseClick(dialerMaximizeButton);
490− tryCompare(dialerDelegate, "windowState", WindowStateStorage.WindowStateNormal);
490+ tryCompare(dialerDelegate, "windowState", WindowStateStorage.WindowStateRestored);
491491 }
492492
493493 function test_windowMaximizeVertically() {
@@ -502,7 +502,7 @@ Item {
502502
503503 // click again to restore
504504 mouseClick(dialerMaximizeButton);
505− tryCompare(dialerDelegate, "windowState", WindowStateStorage.WindowStateNormal);
505+ tryCompare(dialerDelegate, "windowState", WindowStateStorage.WindowStateRestored);
506506 }
507507
508508 function test_smashCursorKeys() {
tests/qmltests/Stage/tst_Splash.qml+16−2
@@ -18,7 +18,7 @@ import QtQuick 2.4
1818import QtTest 1.0
1919import Unity.Test 0.1 as UT
2020import ".."
21−import "../../../qml/Stages"
21+import "../../../qml/Stage"
2222import Ubuntu.Components 1.3
2323import Unity.Application 0.1
2424
@@ -55,7 +55,7 @@ Rectangle {
5555 bottom: parent.bottom
5656 left: parent.left
5757 }
58− width: units.gu(40)
58+ width: landscapeCheckbox.checked ? units.gu(70) : units.gu(40)
5959 sourceComponent: splashComponent
6060 }
6161
@@ -97,6 +97,20 @@ Rectangle {
9797 CheckBox {id: footerColorCheckbox; checked: false; }
9898 Label { text: "custom footerColor"; anchors.verticalCenter: parent.verticalCenter }
9999 }
100+ Row {
101+ anchors { left: parent.left; right: parent.right }
102+ CheckBox {
103+ id: landscapeCheckbox
104+ checked: false
105+ onCheckedChanged: {
106+ if (checked) {
107+ window.width = Math.max(window.width, units.gu(110));
108+ }
109+ window.height = checked ? units.gu(40) : units.gu(70);
110+ }
111+ }
112+ Label { text: "Landscape"; anchors.verticalCenter: parent.verticalCenter }
113+ }
100114 }
101115 }
102116
tests/qmltests/Stage/tst_WindowResizeArea.qml+9−1
@@ -60,9 +60,12 @@ Item {
6060 property real heightIncrement: 0
6161
6262 property int windowState: WindowStateStorage.WindowStateNormal
63+ property real restoredX
64+ property real restoredY
6365
6466 states: [
6567 State { name: "normal"; when: windowState == WindowStateStorage.WindowStateNormal },
68+ State { name: "restored"; when: windowState == WindowStateStorage.WindowStateRestored },
6669 State { name: "maximized"; when: windowState == WindowStateStorage.WindowStateMaximized }
6770 ]
6871
@@ -71,7 +74,12 @@ Item {
7174 }
7275
7376 function restoreFromMaximized() {
74− windowState = WindowStateStorage.WindowStateNormal
77+ windowState = WindowStateStorage.WindowStateRestored
78+ }
79+
80+ function restore(animated,state) {
81+ windowState = state || WindowStateStorage.WindowStateRestored;
82+ windowState &= ~WindowStateStorage.WindowStateMinimized; // clear the minimized bit
7583 }
7684
7785 WindowResizeArea {
tests/qmltests/tst_OrientedShell.qml+1
@@ -28,6 +28,7 @@ import Unity.InputInfo 0.1
2828import Utils 0.1
2929
3030import "../../qml"
31+import "../../qml/Components"
3132import "Stage"
3233
3334Rectangle {
tests/qmltests/tst_Shell.qml+158−5
@@ -19,6 +19,7 @@ import QtTest 1.0
1919import AccountsService 0.1
2020import GSettings 1.0
2121import LightDM.IntegratedLightDM 0.1 as LightDM
22+import SessionBroadcast 0.1
2223import Ubuntu.Components 1.3
2324import Ubuntu.Components.ListItems 1.3 as ListItem
2425import Ubuntu.Telephony 0.1 as Telephony
@@ -440,6 +441,18 @@ Rectangle {
440441 signalName: "setCurrentScopeCalled"
441442 }
442443
444+ SignalSpy {
445+ id: broadcastUrlSpy
446+ target: SessionBroadcast
447+ signalName: "startUrl"
448+ }
449+
450+ SignalSpy {
451+ id: broadcastHomeSpy
452+ target: SessionBroadcast
453+ signalName: "showHome"
454+ }
455+
443456 SignalSpy {
444457 id: unlockAllModemsSpy
445458 target: Connectivity
@@ -571,6 +584,7 @@ Rectangle {
571584
572585 AccountsService.demoEdges = false;
573586 AccountsService.demoEdgesCompleted = [];
587+ AccountsService.backgroundFile = "";
574588 Wizard.System.wizardEnabled = false;
575589 shellLoader.mode = "full-greeter";
576590
@@ -581,8 +595,11 @@ Rectangle {
581595 LightDM.Greeter.authenticate(""); // reset greeter
582596
583597 sessionSpy.clear();
598+ broadcastUrlSpy.clear();
599+ broadcastHomeSpy.clear();
584600
585601 GSettingsController.setLifecycleExemptAppids([]);
602+ GSettingsController.setPictureUri("");
586603
587604 // there should be only unity8-dash window over there
588605 tryCompare(ApplicationManager, "count", 1);
@@ -707,7 +724,7 @@ Rectangle {
707724 waitUntilDashIsFocused();
708725 tryCompare(greeter, "shown", false);
709726 } else {
710− compare(greeter.fullyShown, data.greeterShown);
727+ tryCompare(greeter, "fullyShown", data.greeterShown);
711728 }
712729
713730 var launcher = findChild(shell, "launcherPanel");
@@ -1354,6 +1371,32 @@ Rectangle {
13541371 tryCompare(tutorial, "paused", false);
13551372 }
13561373
1374+ function test_customBackground() {
1375+ loadShell("desktop");
1376+ shell.usageScenario = "desktop";
1377+ waitForRendering(shell);
1378+
1379+ var wallpaperResolver = findInvisibleChild(shell, "wallpaperResolver");
1380+ var greeter = findChild(shell, "greeter");
1381+ verify(!greeter.hasCustomBackground);
1382+ compare(wallpaperResolver.background, wallpaperResolver.defaultBackground);
1383+
1384+ AccountsService.backgroundFile = Qt.resolvedUrl("../graphics/applicationIcons/dash.png");
1385+ tryCompare(greeter, "hasCustomBackground", true);
1386+ compare(wallpaperResolver.background, AccountsService.backgroundFile);
1387+ }
1388+
1389+ function test_cachedBackground() {
1390+ loadShell("desktop");
1391+ shell.usageScenario = "desktop";
1392+ waitForRendering(shell);
1393+
1394+ var greeter = findChild(shell, "greeter");
1395+ verify(!greeter.hasCustomBackground);
1396+ compare(greeter.background.toString().indexOf("image://unity8imagecache/file:///"), 0);
1397+ verify(greeter.background.toString().indexOf("?name=wallpaper") > 0);
1398+ }
1399+
13571400 function test_tapOnRightEdgeReachesApplicationSurface() {
13581401 loadShell("phone");
13591402 swipeAwayGreeter();
@@ -2408,6 +2451,119 @@ Rectangle {
24082451 compare(topmostSurfaceItem.touchReleaseCount, 2);
24092452 }
24102453
2454+ function test_background_data() {
2455+ return [
2456+ {tag: "color",
2457+ accounts: Qt.resolvedUrl("data:image/svg+xml,<svg><rect width='100%' height='100%' fill='#dd4814'/></svg>"),
2458+ gsettings: "",
2459+ output: Qt.resolvedUrl("data:image/svg+xml,<svg><rect width='100%' height='100%' fill='#dd4814'/></svg>")},
2460+
2461+ {tag: "empty", accounts: "", gsettings: "", output: "defaultBackground"},
2462+
2463+ {tag: "as-specified",
2464+ accounts: Qt.resolvedUrl("../data/unity/backgrounds/blue.png"),
2465+ gsettings: "",
2466+ output: Qt.resolvedUrl("../data/unity/backgrounds/blue.png")},
2467+
2468+ {tag: "gs-specified",
2469+ accounts: "",
2470+ gsettings: Qt.resolvedUrl("../data/unity/backgrounds/red.png"),
2471+ output: Qt.resolvedUrl("../data/unity/backgrounds/red.png")},
2472+
2473+ {tag: "both-specified",
2474+ accounts: Qt.resolvedUrl("../data/unity/backgrounds/blue.png"),
2475+ gsettings: Qt.resolvedUrl("../data/unity/backgrounds/red.png"),
2476+ output: Qt.resolvedUrl("../data/unity/backgrounds/blue.png")},
2477+
2478+ {tag: "invalid-as",
2479+ accounts: Qt.resolvedUrl("../data/unity/backgrounds/nope.png"),
2480+ gsettings: Qt.resolvedUrl("../data/unity/backgrounds/red.png"),
2481+ output: Qt.resolvedUrl("../data/unity/backgrounds/red.png")},
2482+
2483+ {tag: "invalid-both",
2484+ accounts: Qt.resolvedUrl("../data/unity/backgrounds/nope.png"),
2485+ gsettings: Qt.resolvedUrl("../data/unity/backgrounds/stillnope.png"),
2486+ output: "defaultBackground"},
2487+ ]
2488+ }
2489+ function test_background(data) {
2490+ loadShell("phone");
2491+ shell.usageScenario = "phone";
2492+ waitForRendering(shell);
2493+
2494+ AccountsService.backgroundFile = data.accounts;
2495+ GSettingsController.setPictureUri(data.gsettings);
2496+
2497+ var wallpaperResolver = findChild(shell, "wallpaperResolver");
2498+ if (data.output === "defaultBackground") {
2499+ tryCompare(wallpaperResolver, "background", wallpaperResolver.defaultBackground);
2500+ verify(!wallpaperResolver.hasCustomBackground);
2501+ } else {
2502+ tryCompare(wallpaperResolver, "background", data.output);
2503+ verify(wallpaperResolver.hasCustomBackground);
2504+ }
2505+ }
2506+
2507+ function test_greeterModeBroadcastsApp() {
2508+ setLightDMMockMode("single-pin");
2509+ shellLoader.mode = "greeter";
2510+ loadShell("phone");
2511+ shell.usageScenario = "phone";
2512+ waitForRendering(shell);
2513+
2514+ dragLauncherIntoView();
2515+ var appIcon = findChild(shell, "launcherDelegate0")
2516+ tap(appIcon);
2517+
2518+ tryCompare(broadcastUrlSpy, "count", 1);
2519+ compare(broadcastUrlSpy.signalArguments[0][0], "application:///" + appIcon.appId + ".desktop");
2520+ compare(ApplicationManager.count, 1); // confirm only dash is open, we didn't start new app
2521+
2522+ var coverPage = findChild(shell, "coverPage");
2523+ tryCompare(coverPage, "showProgress", 0);
2524+ }
2525+
2526+ function test_greeterModeBroadcastsHome() {
2527+ setLightDMMockMode("single-pin");
2528+ shellLoader.mode = "greeter";
2529+ loadShell("phone");
2530+ shell.usageScenario = "phone";
2531+ waitForRendering(shell);
2532+
2533+ var gallerySurfaceId = topLevelSurfaceList.nextId;
2534+ var galleryApp = ApplicationManager.startApplication("gallery-app");
2535+ waitUntilAppWindowIsFullyLoaded(gallerySurfaceId);
2536+ compare(ApplicationManager.focusedApplicationId, "gallery-app");
2537+
2538+ dragLauncherIntoView();
2539+ tap(findChild(shell, "buttonShowDashHome"));
2540+
2541+ tryCompare(broadcastHomeSpy, "count", 1);
2542+ compare(ApplicationManager.focusedApplicationId, "gallery-app"); // confirm we didn't focus dash
2543+
2544+ var coverPage = findChild(shell, "coverPage");
2545+ tryCompare(coverPage, "showProgress", 0);
2546+ }
2547+
2548+ function test_greeterModeDispatchesURL() {
2549+ setLightDMMockMode("single-pin");
2550+ shellLoader.mode = "greeter";
2551+ loadShell("phone");
2552+ shell.usageScenario = "phone";
2553+ waitForRendering(shell);
2554+
2555+ var urlDispatcher = findInvisibleChild(shell, "urlDispatcher");
2556+ verify(urlDispatcher.active);
2557+ urlDispatcher.urlRequested("test:"); // force signal emission
2558+
2559+ tryCompare(broadcastUrlSpy, "count", 1);
2560+ compare(broadcastUrlSpy.signalArguments[0][0], "test:");
2561+ compare(ApplicationManager.count, 1); // confirm only dash is open, we didn't start new app
2562+
2563+ var coverPage = findChild(shell, "coverPage");
2564+ tryCompare(coverPage, "showProgress", 0);
2565+ }
2566+
24112567 function test_switchKeymap() {
24122568 // start with phone shell
24132569 loadShell("phone");
@@ -2496,10 +2652,7 @@ Rectangle {
24962652 appDelegate.maximize();
24972653 tryCompare(appDelegate, "state", "maximized");
24982654
2499− mousePress(panel);
2500− mouseMove(shell, shell.width/2, shell.height/2);
2501− mouseRelease(shell);
2502−
2655+ mouseDrag(panel, panel.width/3, panel.height/2, 0, shell.height/3, Qt.LeftButton, Qt.NoModifier, 500);
25032656 tryCompare(appDelegate, "state", "normal");
25042657 }
25052658
tests/qmltests/tst_ShellWithPin.qml+44−40
@@ -154,7 +154,6 @@ Item {
154154 var greeter = findChild(shell, "greeter");
155155 sessionSpy.target = greeter;
156156 swipeAwayGreeter(true);
157− waitForLockscreen()
158157 greeter.failedLoginsDelayAttempts = -1;
159158 greeter.maxFailedLogins = -1;
160159
@@ -214,20 +213,16 @@ Item {
214213 }
215214 }
216215
217− function waitForLockscreen() {
218− var lockscreen = findChild(shell, "lockscreen");
219− var pinPadLoader = findChild(lockscreen, "pinPadLoader");
220− tryCompare(pinPadLoader, "status", Loader.Ready)
221− waitForRendering(lockscreen)
216+ function enterPin(pin) {
217+ typeString(pin);
222218 }
223219
224− function enterPin(pin) {
225− for (var i = 0; i < pin.length; ++i) {
226− var character = pin.charAt(i)
227− var button = findChild(shell, "pinPadButton" + character)
228− tryCompare(button, "enabled", true);
229− tap(button)
230− }
220+ function showGreeter() {
221+ LightDM.Greeter.showGreeter();
222+ tryCompare(shell, "waitingOnGreeter", false);
223+ var coverPage = findChild(shell, "coverPage");
224+ tryCompare(coverPage, "showProgress", 1);
225+ removeTimeConstraintsFromSwipeAreas(shell);
231226 }
232227
233228 function confirmLockedApp(app) {
@@ -248,7 +243,7 @@ Item {
248243 LightDM.Greeter.hideGreeter();
249244 tryCompare(panel.indicators.indicatorsModel, "profile", shell.indicatorProfile);
250245
251− LightDM.Greeter.showGreeter();
246+ showGreeter();
252247 tryCompare(panel.indicators.indicatorsModel, "profile", shell.indicatorProfile + "_greeter");
253248
254249 LightDM.Greeter.hideGreeter();
@@ -293,7 +288,7 @@ Item {
293288
294289 // Cancel emergency mode, and go back to normal
295290 waitForRendering(greeter)
296− LightDM.Greeter.showGreeter()
291+ showGreeter()
297292
298293 tryCompare(greeter, "shown", true)
299294 tryCompare(greeter, "lockedApp", "")
@@ -328,6 +323,22 @@ Item {
328323 tryCompare(greeter, "shown", true);
329324 }
330325
326+ function test_emergencyCallPausesTutorial() {
327+ var greeter = findChild(shell, "greeter");
328+ var tutorial = findChild(shell, "tutorial");
329+
330+ AccountsService.demoEdges = true;
331+ enterPin("1234");
332+ tryCompare(tutorial, "paused", false);
333+
334+ showGreeter();
335+ verify(tutorial.paused);
336+
337+ swipeAwayGreeter(true);
338+ tap(findChild(greeter, "emergencyCallLabel"));
339+ verify(tutorial.paused);
340+ }
341+
331342 function test_failedLoginsCount() {
332343 AccountsService.failedLogins = 0
333344
@@ -342,33 +353,33 @@ Item {
342353 var greeter = findChild(shell, "greeter");
343354 greeter.failedLoginsDelayAttempts = 3;
344355
345− var placeHolder = findChild(shell, "wrongNoticeLabel")
346− tryCompare(placeHolder, "text", "")
356+ var wrongPasswordAnimation = findInvisibleChild(shell, "wrongPasswordAnimation");
347357
348358 enterPin("1111")
349− tryCompare(placeHolder, "text", "Sorry, incorrect passcode")
359+ verify(wrongPasswordAnimation.running);
350360
351361 enterPin("1111")
352− tryCompare(placeHolder, "text", "Sorry, incorrect passcode")
362+ verify(wrongPasswordAnimation.running);
353363
354− var lockscreen = findChild(shell, "lockscreen")
355− tryCompare(lockscreen, "delayMinutes", 0)
364+ var delayedLockscreen = findChild(shell, "delayedLockscreen");
365+ tryCompare(delayedLockscreen, "delayMinutes", 0);
356366 enterPin("1111")
357− tryCompare(lockscreen, "delayMinutes", greeter.failedLoginsDelayMinutes);
367+ tryCompare(delayedLockscreen, "delayMinutes", greeter.failedLoginsDelayMinutes);
358368 }
359369
360370 function test_factoryReset() {
371+ skip("Factory reset support is not finished")
372+
361373 maxRetriesTextField.text = "3"
362374 resetSpy.clear()
363375
364376 enterPin("1111")
365377 enterPin("1111")
366− tryCompareFunction(function() {return findChild(root, "infoPopup") !== null}, true)
367378
368379 var dialog = findChild(root, "infoPopup")
369380 var button = findChild(dialog, "infoPopupOkButton")
370381 tap(button)
371− tryCompareFunction(function() {return findChild(root, "infoPopup")}, null)
382+ tryCompareFunction(function() {return findChild(root, "infoPopup", 0 /* timeout */)}, null)
372383
373384 tryCompare(resetSpy, "count", 0)
374385 enterPin("1111")
@@ -388,8 +399,7 @@ Item {
388399 compare(shell.usageScenario, "phone");
389400 compare(applicationsDisplayLoader.usageScenario, "phone");
390401
391− var lockscreen = findChild(shell, "lockscreen")
392− lockscreen.emergencyCall()
402+ tap(findChild(shell, "emergencyCallLabel"));
393403 confirmLockedApp("dialer-app")
394404
395405 // OK, we're in. Now try (but fail) to switch to tablet mode
@@ -416,16 +426,13 @@ Item {
416426 // - Should be back in emergency mode dialer
417427
418428 var greeter = findChild(shell, "greeter");
419− var lockscreen = findChild(shell, "lockscreen");
420− verify(lockscreen);
421429
422− lockscreen.emergencyCall();
430+ tap(findChild(shell, "emergencyCallLabel"));
423431 confirmLockedApp("dialer-app");
424432 callManager.foregroundCall = phoneCall;
425433
426− LightDM.Greeter.showGreeter();
427− lockscreen = findChild(shell, "lockscreen");
428− verify(lockscreen);
434+ showGreeter();
435+ var lockscreen = findChild(shell, "lockscreen");
429436 tryCompare(lockscreen, "shown", true);
430437 tryCompare(greeter, "hasLockedApp", false);
431438
@@ -468,7 +475,7 @@ Item {
468475 }
469476
470477 function test_focusRequestedHidesCoverPage() {
471− LightDM.Greeter.showGreeter();
478+ showGreeter();
472479
473480 var app = ApplicationManager.startApplication("gallery-app");
474481 // wait until the app is fully loaded (ie, real surface replaces splash screen)
@@ -480,8 +487,7 @@ Item {
480487 tryCompare(coverPage, "showProgress", 0);
481488 tryCompare(greeter, "fullyShown", true);
482489
483− LightDM.Greeter.showGreeter();
484− tryCompare(coverPage, "showProgress", 1);
490+ showGreeter();
485491
486492 // Make sure focusing same app triggers same behavior
487493 ApplicationManager.requestFocusApplication("gallery-app");
@@ -555,9 +561,7 @@ Item {
555561 only after its idle timeout is triggered.
556562 */
557563 function test_shortLeftEdgeSwipeMakesLauncherStayVisible() {
558− LightDM.Greeter.showGreeter();
559− var coverPage = testCase.findChild(shell, "coverPage");
560− tryCompare(coverPage, "showProgress", 1);
564+ showGreeter();
561565
562566 var launcher = testCase.findChild(shell, "launcher")
563567 var launcherPanel = testCase.findChild(launcher, "launcherPanel");
@@ -573,6 +577,7 @@ Item {
573577 tryCompare(launcherPanel, "x", 0);
574578
575579 // Greeter should be displaced
580+ var coverPage = findChild(shell, "coverPage");
576581 tryCompareFunction(function() { return coverPage.mapToItem(shell, 0, 0).x > shell.width*0.2; }, true);
577582
578583 touchRelease(shell, toX, shell.height * 0.5);
@@ -595,8 +600,7 @@ Item {
595600 tryCompare(shell, "mainApp", galleryApp);
596601
597602 // Show greeter
598− LightDM.Greeter.showGreeter();
599− tryCompare(coverPage, "showProgress", 1);
603+ showGreeter();
600604
601605 // Swipe cover page away
602606 touchFlick(shell, 2, shell.height / 2, units.gu(30), shell.height / 2);