on xenial help sign in
Merge side-stage-redesign
commit14a5635b10a63c40b25504b91378f8b679e808ae authorMichael Terry <michael.terry@canonical.com> dateFri 11 Mar 2016, 16:16 (11 years ago) parent91e8197b379bab
297 files changed+11911−2726
CMakeLists.txt+3−2 CODING+2 cmake/modules/QmlTest.cmake+1−1 data/com.canonical.Unity8.gschema.xml+11 data/unity8-dash.conf+1 debian/changelog+97 debian/control+13−11 debian/unity8-common.udev+2 debian/unity8-doc.install+1 doc/devices.conf+30 libs/UbuntuGestures/Timer.h+5 libs/UbuntuGestures/TouchRegistry.cpp+15−10 plugins/AccountsService/AccountsService.cpp+212−374 plugins/AccountsService/AccountsService.h+34−25 plugins/AccountsService/AccountsServiceDBusAdaptor.cpp+11−4 plugins/AccountsService/AccountsServiceDBusAdaptor.h+5−6 plugins/Cursor/Cursor.qmltypes+78 plugins/Dash/AudioProgressBar.qml+1 plugins/Dash/CardCreator.js+69−34 plugins/Dash/CardCreatorCache.qml+3−3 plugins/Dash/plugin.cpp+6−6 plugins/GlobalShortcut/GlobalShortcut.qmltypes+31 plugins/GlobalShortcut/globalshortcutregistry.cpp+4−4 plugins/IntegratedLightDM/liblightdm/CMakeLists.txt+16−3 plugins/IntegratedLightDM/liblightdm/UsersModel.cpp+4−18 plugins/IntegratedLightDM/liblightdm/UsersModel.h+1−1 plugins/IntegratedLightDM/liblightdm/UsersModelPrivate.cpp+43−9 plugins/IntegratedLightDM/liblightdm/UsersModelPrivate.h+15−3 plugins/Ubuntu/Gestures/CMakeLists.txt+1 plugins/Ubuntu/Gestures/DirectionalDragArea.cpp+3 plugins/Ubuntu/Gestures/Gestures.qmltypes+86−45 plugins/Ubuntu/Gestures/TouchGestureArea.cpp+875 plugins/Ubuntu/Gestures/TouchGestureArea.h+229 plugins/Ubuntu/Gestures/plugin.cpp+3 plugins/Unity/Indicators/indicatorsmanager.cpp+2−2 plugins/Unity/Indicators/rootstateparser.h+1 plugins/Unity/InputInfo/InputInfo.qmltypes+71 plugins/Unity/Launcher/desktopfilehandler.cpp+2−2 plugins/Unity/Launcher/launcheritem.cpp+1 plugins/Unity/Launcher/launcheritem.h+1−1 plugins/Unity/Launcher/launchermodel.cpp+15−5 plugins/Unity/Launcher/launchermodel.h+1−1 plugins/Unity/Platform/Platform.qmltypes+20 plugins/Utils/CMakeLists.txt+2 plugins/Utils/deviceconfigparser.cpp+150 plugins/Utils/deviceconfigparser.h+62 plugins/Utils/globalfunctions.cpp+56 plugins/Utils/globalfunctions.h+42 plugins/Utils/plugin.cpp+11 plugins/Utils/timezoneFormatter.cpp+10 plugins/Utils/timezoneFormatter.h+1 plugins/Utils/windowstatestorage.cpp+36−4 plugins/Utils/windowstatestorage.h+3 plugins/Wizard/CMakeLists.txt+7−1 plugins/Wizard/LocalePlugin.cpp+291 plugins/Wizard/LocalePlugin.h+58 plugins/Wizard/PageList.cpp+4−4 plugins/Wizard/Status.cpp+143 plugins/Wizard/Status.h+60 plugins/Wizard/System.cpp+5−4 plugins/Wizard/System.h+3−2 plugins/Wizard/plugin.cpp+7−8 plugins/Wizard/plugin.h+1−2 plugins/Wizard/timezonemodel.cpp+239 plugins/Wizard/timezonemodel.h+80 po/unity8.pot+29−27 qml/Components/EdgeBarrier.qml+1−1 qml/Components/InputMethod.qml+1−9 qml/Components/Lockscreen.qml+80−94 qml/Components/ModeSwitchWarningDialog.qml+1−1 qml/Components/PassphraseLockscreen.qml+5−4 qml/Components/PhysicalKeysMapper.qml+24 qml/Components/PinLockscreen.qml+22−1 qml/Components/ShellDialog.qml+8 qml/Components/Showable.qml+10 qml/Dash/CardCarousel.qml−1 qml/Dash/CardGrid.qml−1 qml/Dash/CardHorizontalList.qml−1 qml/Dash/CardTool.qml+2−26 qml/Dash/CardVerticalJournal.qml−1 qml/Dash/Dash.qml+11−11 qml/Dash/GenericScopeView.qml+2 qml/Dash/Previews/PreviewHeader.qml+37−9 qml/Dash/Previews/PreviewOverlay.qml+1−1 qml/Dash/Previews/PreviewSharing.qml+17−7 qml/Dash/ScopesListCategoryItem.qml+8−4 qml/DeviceConfiguration.qml+54−16 qml/DisabledScreenNotice.qml+44−20 qml/Greeter/CoverPage.qml+13−2 qml/Greeter/Infographics.qml+2−1 qml/Greeter/NarrowView.qml+14−1 qml/Launcher/Launcher.qml+144−11 qml/Launcher/LauncherDelegate.qml+55−20 qml/Launcher/LauncherPanel.qml+122−51 qml/Launcher/graphics/launcher-app-focus-ring.svg+12 qml/Notifications/Notification.qml+6−2 qml/OrientedShell.qml+26−1 qml/Panel/Handle.qml+2−2 qml/Panel/Indicators/MenuItemFactory.qml−1 qml/Panel/IndicatorsMenu.qml+1−1 qml/Panel/Panel.qml+5−1 qml/ScopeTool.qml−8 qml/Shell.qml+53−8 qml/Stages/AbstractStage.qml+4−2 qml/Stages/ApplicationWindow.qml+3−1 qml/Stages/DesktopSpread.qml+64−17 qml/Stages/DesktopSpreadDelegate.qml+15−1 qml/Stages/DesktopStage.qml+34−34 qml/Stages/PhoneStage.qml+77−3 qml/Stages/SideStage.qml+118 qml/Stages/SpreadDelegate.qml+30−1 qml/Stages/SurfaceContainer.qml+1 qml/Stages/TabletSideStageTouchGesture.qml+154 qml/Stages/TabletStage.qml+469−173 qml/Stages/TransformedTabletSpreadDelegate.qml+84−56 qml/Stages/WindowResizeArea.qml+8−5 qml/Stages/graphics/sidestage_drag.svg+207 qml/Stages/graphics/sidestage_open.svg+181 qml/Wizard/CheckableSetting.qml+10−2 qml/Wizard/Page.qml+260−44 qml/Wizard/Pages.qml+75−31 qml/Wizard/Pages/10-welcome.qml+121−48 qml/Wizard/Pages/20-sim.qml−77 qml/Wizard/Pages/30-passwd-type.qml−140 qml/Wizard/Pages/30-wifi.qml+210 qml/Wizard/Pages/40-location.qml+291 qml/Wizard/Pages/40-wifi.qml−219 qml/Wizard/Pages/50-location.qml−150 qml/Wizard/Pages/50-timezone.qml+275 qml/Wizard/Pages/60-reporting.qml → qml/Wizard/Pages/60-account.qml+30−20 qml/Wizard/Pages/70-passwd-type.qml+166 qml/Wizard/Pages/75-report-check.qml+91 qml/Wizard/Pages/80-finished.qml+94−16 qml/Wizard/Pages/data/Desktop_header_bkg.png qml/Wizard/Pages/data/Desktop_splash_screen_bkg.png qml/Wizard/Pages/data/Phone_header_bkg.png qml/Wizard/Pages/data/Phone_splash_screen_bkg.png qml/Wizard/Pages/data/Tick@30.png qml/Wizard/Pages/data/meet_ubuntu_simcard@30.png qml/Wizard/Pages/data/timezonemap/map.png qml/Wizard/Pages/data/timezonemap/pin.png qml/Wizard/Pages/data/timezonemap/timezone_-1.png qml/Wizard/Pages/data/timezonemap/timezone_-10.png qml/Wizard/Pages/data/timezonemap/timezone_-11.png qml/Wizard/Pages/data/timezonemap/timezone_-2.png qml/Wizard/Pages/data/timezonemap/timezone_-3.5.png qml/Wizard/Pages/data/timezonemap/timezone_-3.png qml/Wizard/Pages/data/timezonemap/timezone_-4.5.png qml/Wizard/Pages/data/timezonemap/timezone_-4.png qml/Wizard/Pages/data/timezonemap/timezone_-5.png qml/Wizard/Pages/data/timezonemap/timezone_-6.png qml/Wizard/Pages/data/timezonemap/timezone_-7.png qml/Wizard/Pages/data/timezonemap/timezone_-8.png qml/Wizard/Pages/data/timezonemap/timezone_-9.5.png qml/Wizard/Pages/data/timezonemap/timezone_-9.png qml/Wizard/Pages/data/timezonemap/timezone_0.png qml/Wizard/Pages/data/timezonemap/timezone_1.png qml/Wizard/Pages/data/timezonemap/timezone_10.5.png qml/Wizard/Pages/data/timezonemap/timezone_10.png qml/Wizard/Pages/data/timezonemap/timezone_11.png qml/Wizard/Pages/data/timezonemap/timezone_12.75.png qml/Wizard/Pages/data/timezonemap/timezone_12.png qml/Wizard/Pages/data/timezonemap/timezone_13.png qml/Wizard/Pages/data/timezonemap/timezone_2.png qml/Wizard/Pages/data/timezonemap/timezone_3.5.png qml/Wizard/Pages/data/timezonemap/timezone_3.png qml/Wizard/Pages/data/timezonemap/timezone_4.5.png qml/Wizard/Pages/data/timezonemap/timezone_4.png qml/Wizard/Pages/data/timezonemap/timezone_5.5.png qml/Wizard/Pages/data/timezonemap/timezone_5.75.png qml/Wizard/Pages/data/timezonemap/timezone_5.png qml/Wizard/Pages/data/timezonemap/timezone_6.5.png qml/Wizard/Pages/data/timezonemap/timezone_6.png qml/Wizard/Pages/data/timezonemap/timezone_7.png qml/Wizard/Pages/data/timezonemap/timezone_8.5.png qml/Wizard/Pages/data/timezonemap/timezone_8.png qml/Wizard/Pages/data/timezonemap/timezone_9.5.png qml/Wizard/Pages/data/timezonemap/timezone_9.png qml/Wizard/Pages/here-terms.qml+72−65 qml/Wizard/Pages/passwd-confirm.qml → qml/Wizard/Pages/passcode-confirm.qml+12−29 qml/Wizard/Pages/passcode-desktop.qml+132 qml/Wizard/Pages/passwd-set.qml → qml/Wizard/Pages/passcode-set.qml+12−28 qml/Wizard/Pages/password-set.qml+149 qml/Wizard/Pages/sim.qml+108 qml/Wizard/PasswordMeter.qml+106 qml/Wizard/StackButton.qml+6−11 qml/Wizard/Wizard.qml−9 qml/Wizard/WizardTextField.qml+25 src/CMakeLists.txt+1−1 src/CachingNetworkManagerFactory.cpp+2−2 src/CachingNetworkManagerFactory.h+3−2 src/Dash/CMakeLists.txt+1−1 src/MouseTouchAdaptor.cpp+53−9 src/MouseTouchAdaptor.h+5−3 tests/autopilot/unity8/dash.py+1−2 tests/autopilot/unity8/shell/tests/test_helpers.py−2 tests/libs/UbuntuGestures/tst_TouchRegistry.cpp+59 tests/mocks/AccountsService/AccountsService.cpp+23−3 tests/mocks/AccountsService/AccountsService.h+12−5 tests/mocks/GSettings.1.0/fake_gsettings.cpp+64 tests/mocks/GSettings.1.0/fake_gsettings.h+18 tests/mocks/IntegratedLightDM/liblightdm/UsersModel.cpp−14 tests/mocks/MeeGo/QOfono/MockOfonoSimManager.qml+1 tests/mocks/Ubuntu/Connectivity/plugin.cpp+1 tests/mocks/Ubuntu/SystemSettings/CMakeLists.txt+2 tests/mocks/Ubuntu/SystemSettings/Diagnostics/CMakeLists.txt+1 tests/mocks/Ubuntu/SystemSettings/Diagnostics/MockDiagnostics.qml+24 tests/mocks/Ubuntu/SystemSettings/Diagnostics/qmldir+2 tests/mocks/Ubuntu/SystemSettings/LanguagePlugin/MockLanguagePlugin.qml+2−2 tests/mocks/Ubuntu/SystemSettings/SecurityPrivacy/MockSecurityPrivacy.cpp+1−1 tests/mocks/Ubuntu/SystemSettings/TimeDate/CMakeLists.txt+1 tests/mocks/Ubuntu/SystemSettings/TimeDate/MockTimeDate.qml+25 tests/mocks/Ubuntu/SystemSettings/TimeDate/qmldir+2 tests/mocks/Unity/Application/ApplicationManager.cpp+10−25 tests/mocks/Unity/Application/ApplicationManager.h−8 tests/mocks/Unity/Application/MirSurface.cpp+6−2 tests/mocks/Unity/Application/MirSurface.h+2−1 tests/mocks/Unity/Application/MirSurfaceItem.cpp+17 tests/mocks/Unity/Application/MirSurfaceItem.h+1 tests/mocks/Unity/Application/SurfaceManager.cpp+8−10 tests/mocks/Unity/Application/SurfaceManager.h+3−3 tests/mocks/Unity/Application/VirtualKeyboard.cpp+3−2 tests/mocks/Unity/Application/VirtualKeyboard.h+1−1 tests/mocks/Unity/Application/plugin.cpp+2−1 tests/mocks/Unity/Application/resources/MirSurfaceItem.qml+59 tests/mocks/Unity/Launcher/MockLauncherModel.cpp+2 tests/mocks/Utils/CMakeLists.txt+3−1 tests/mocks/Utils/plugin.cpp+14 tests/mocks/Utils/windowstatestorage.cpp+13 tests/mocks/Utils/windowstatestorage.h+4 tests/mocks/Wizard/CMakeLists.txt+6−1 tests/mocks/Wizard/MockSystem.cpp+4−2 tests/mocks/Wizard/MockSystem.h+2−2 tests/mocks/Wizard/mockplugin.cpp+7−8 tests/plugins/AccountsService/CMakeLists.txt+5 tests/plugins/AccountsService/PropertiesServer.cpp+16−4 tests/plugins/AccountsService/PropertiesServer.h+2−1 tests/plugins/AccountsService/UscServer.cpp+28 tests/plugins/AccountsService/UscServer.h+39 tests/plugins/AccountsService/client.cpp+63−9 tests/plugins/AccountsService/interfaces.xml+16 tests/plugins/AccountsService/server.cpp+12 tests/plugins/Dash/cardcreator/1.res+4−6 tests/plugins/Dash/cardcreator/1.res.cardcreator+119 tests/plugins/Dash/cardcreator/10.res+3−5 tests/plugins/Dash/cardcreator/10.res.cardcreator+137 tests/plugins/Dash/cardcreator/11.res+5−7 tests/plugins/Dash/cardcreator/11.res.cardcreator+210 tests/plugins/Dash/cardcreator/2.res+3−5 tests/plugins/Dash/cardcreator/2.res.cardcreator+136 tests/plugins/Dash/cardcreator/3.res+3−5 tests/plugins/Dash/cardcreator/3.res.cardcreator+137 tests/plugins/Dash/cardcreator/4.res+3−5 tests/plugins/Dash/cardcreator/4.res.cardcreator+109 tests/plugins/Dash/cardcreator/5.res+5−7 tests/plugins/Dash/cardcreator/5.res.cardcreator+156 tests/plugins/Dash/cardcreator/6.res+2−4 tests/plugins/Dash/cardcreator/6.res.cardcreator+126 tests/plugins/Dash/cardcreator/7.res+3−5 tests/plugins/Dash/cardcreator/7.res.cardcreator+149 tests/plugins/Dash/cardcreator/8.res+3−5 tests/plugins/Dash/cardcreator/8.res.cardcreator+107 tests/plugins/Dash/cardcreator/9.res+4−5 tests/plugins/Dash/cardcreator/9.res.cardcreator+119 tests/plugins/Dash/cardcreatortest.cpp+32−22 tests/plugins/Dash/cardcreatortest.qml+4−4 tests/plugins/IntegratedLightDM/CMakeLists.txt+25 tests/plugins/IntegratedLightDM/integrated.cpp+91 tests/plugins/Ubuntu/Gestures/CMakeLists.txt+7−1 tests/plugins/Ubuntu/Gestures/GestureTest.cpp+44 tests/plugins/Ubuntu/Gestures/GestureTest.h+13−1 tests/plugins/Ubuntu/Gestures/tst_DirectionalDragArea.cpp−56 tests/plugins/Ubuntu/Gestures/tst_TouchGestureArea.cpp+270 tests/plugins/Ubuntu/Gestures/tst_TouchGestureArea.qml+90 tests/plugins/Unity/Launcher/launchermodeltest.cpp+1 tests/plugins/Utils/CMakeLists.txt+7−1 tests/plugins/Utils/DeviceConfigParserTest.cpp+70 tests/qmltests/CMakeLists.txt+1 tests/qmltests/Components/tst_Lockscreen.qml+14−7 tests/qmltests/Dash/Previews/tst_PreviewHeader.qml+18−1 tests/qmltests/Dash/Previews/tst_PreviewSharing.qml+17−3 tests/qmltests/Dash/tst_CardTool.qml+4−4 tests/qmltests/Dash/tst_Dash.qml+89 tests/qmltests/Greeter/tst_NarrowView.qml+9 tests/qmltests/Launcher/tst_Launcher.qml+254−18 tests/qmltests/Panel/Indicators/tst_MenuItemFactory.qml+2−4 tests/qmltests/Stages/tst_DesktopStage.qml+2−1 tests/qmltests/Stages/tst_PhoneStage.qml+19−6 tests/qmltests/Stages/tst_SpreadDelegate.qml+7 tests/qmltests/Stages/tst_TabletStage.qml+162−1 tests/qmltests/Tutorial/tst_Tutorial.qml+3−4 tests/qmltests/Wizard/tst_Wizard.qml+170−199 tests/qmltests/tst_DeviceConfiguration.qml+49 tests/qmltests/tst_DisabledScreenNotice.qml+76−2 tests/qmltests/tst_OrientedShell.qml+56−12 tests/qmltests/tst_Shell.qml+241−59 tests/utils/modules/Unity/Test/UnityTestCase.qml+67−11
CMakeLists.txt+3−2
@@ -57,7 +57,8 @@ 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=13)
60+pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=14)
61+pkg_check_modules(GEONAMES REQUIRED geonames)
6162pkg_check_modules(GIO REQUIRED gio-2.0>=2.32)
6263pkg_check_modules(GLIB REQUIRED glib-2.0>=2.32)
6364pkg_check_modules(QMENUMODEL REQUIRED qmenumodel)
@@ -131,7 +132,7 @@ if(NOT QUIET_LOGGER)
131132 set(STDOUT_LOGGER "-o" "-,txt")
132133endif()
133134
134−execute_process(COMMAND arch OUTPUT_VARIABLE ARCH)
135+execute_process(COMMAND dpkg-architecture -qDEB_HOST_ARCH OUTPUT_VARIABLE ARCH)
135136if (NOT ARCH MATCHES "arm*")
136137 set(ENABLE_TOUCH_EMULATION true)
137138 add_definitions(-DUNITY8_ENABLE_TOUCH_EMULATION)
CODING+2
@@ -54,6 +54,8 @@ $ ./run.sh -p OR -k
5454Notes
5555-----
5656- to navigate, utilize the mouse left button as you would your finger
57+- a three point drag (for side stage operations) can be performed by using
58+ the Shift+Control+Alt (all together) keyboard modifiers while using the mouse.
5759- to get the translations work, currently you have to do make install to
5860 put the *.mo files into correct structure. We'll look for a better solution
5961 later.
cmake/modules/QmlTest.cmake+1−1
@@ -183,7 +183,7 @@ function(add_manual_test COMPONENT_NAME TARGET)
183183 cmake_parse_arguments(QMLTEST "${QMLTEST_OPTIONS}" "${QMLTEST_SINGLE}" "${QMLTEST_MULTI}" ${ARGN})
184184 mangle_arguments()
185185
186− bake_arguments("${QMLTEST_ARG_PREFIX}" args -qmljsdebugger=port:3768)
186+ bake_arguments("${QMLTEST_ARG_PREFIX}" args -qmljsdebugger=port:3768,3800)
187187
188188 set(qmltry_command
189189 $<TARGET_FILE:${TARGET}>
data/com.canonical.Unity8.gschema.xml+11
@@ -27,6 +27,17 @@
2727 <summary>Maximum push needed to overcome edge barrier</summary>
2828 <description>How much you have to push (in grid units) the mouse against an edge barrier when sensibility is 1.</description>
2929 </key>
30+ <key type="b" name="autohide-launcher">
31+ <default>true</default>
32+ <summary>Autohide the launcher</summary>
33+ <description>This will only be applied in windowed mode. In staged mode, the launcher will always hide.</description>
34+ </key>
35+ <key type="u" name="launcher-width">
36+ <default>8</default>
37+ <range min="6" max="12"/>
38+ <summary>Width of the launcher in grid units.</summary>
39+ <description>Changes the width of the launcher in all usage modes.</description>
40+ </key>
3041 </schema>
3142
3243 <schema path="/com/canonical/unity8/greeter/" id="com.canonical.Unity8.Greeter" gettext-domain="unity8">
data/unity8-dash.conf+1
@@ -14,6 +14,7 @@ kill timeout 120
1414oom score 50
1515
1616respawn
17+respawn limit unlimited
1718
1819env APP_ID=unity8-dash
1920
debian/changelog+97
@@ -1,3 +1,100 @@
1+unity8 (8.13+15.04.20150914-0ubuntu1) UNRELEASED; urgency=medium
2+
3+ * bump version to fix OOBE build
4+
5+ -- Lukas Tinkl <ltinkl@canonical.com> Mon, 14 Sep 2015 19:19:23 +0200
6+
7+unity8 (8.11+16.04.20160310.4-0ubuntu1) xenial; urgency=medium
8+
9+ [ Albert Astals Cid ]
10+ * Add context for Re-dock as asked by translators (LP: #1534608)
11+ * Add emblem to the preview header widget (LP: #1424720)
12+ * Add haptics to ScopesListCategoryItem buttons
13+ * Audio Cards: Make some of the image loading async (LP: #1533432)
14+ * Do not create fallback code for the card tool fake card (LP:
15+ #1545865)
16+ * Fix resizing the dash bringing temp scopes size out of sync (LP:
17+ #1543130)
18+ * Minor fixes for unity-scope-tool
19+ * Resolve title alignment on card creator time instead of on runtime
20+ * Use fixedHeaderHeight only in the non cardtool cards
21+ * Use the new undeprecated connectivityqt::Connectivity
22+ * asynchronous is only false on the fake card in cardtool
23+ * clazy fixes
24+
25+ [ Albert Astals Cid, CI Train Bot ]
26+ * click scope: Add the else branch so we reset the card size in all
27+ situations
28+
29+ [ Andrea Cimitan ]
30+ * PreviewSharing widget now accepts both string and array of
31+ widgetData["share-data"]["uri"] (LP: #1549056)
32+ * Update AP tests for new single preview
33+ * Use Text.Wrap for body notification text (LP: #1544909)
34+
35+ [ Andrea Cimitan, Lukáš Tinkl, Michael Zanetti, Nick Dedekind ]
36+ * some fixes for the new palette (LP: #1554616)
37+
38+ [ CI Train Bot ]
39+ * Resync trunk.
40+ * Update translation template
41+
42+ [ CI Train Bot, Daniel d'Andrada ]
43+ * Ensure the QML engine doesn't delete our mock MirSurfaces on its
44+ own.
45+
46+ [ Daniel d'Andrada ]
47+ * tst_Shell: Remove unused qml items
48+
49+ [ Josh Arenson ]
50+ * Allow the shell to blacklist input devices and force the OSK shown.
51+ (LP: #1542224)
52+
53+ [ Lukáš Tinkl ]
54+ * Disallow resizing windows up, past the Panel (LP: #1544766)
55+ * Elide the window title not to let it overflow into the indicators
56+ area (LP: #1535767)
57+ * Enable the PIN lockscreen to be used with a HW keyboard (LP:
58+ #1550359)
59+ * Fix tiny windows when switching stages
60+ * Provide a range of ports to QML JS Debugger
61+ * Watch for launcher item icon changes (LP: #1543290)
62+
63+ [ Michael Terry ]
64+ * Proxy more mouse and touchpad properties to USC (LP: #1540398) (LP:
65+ #1543344, #1540398)
66+ * Refactor the AccountsService plugin and make it slightly faster.
67+ * To let the user log in if a mouse is connected, hide the greeter
68+ cover page on a mouse click (but NOT a touch click). (LP: #1540497)
69+ * Watch AccountsService for changes to the user's real name. This was
70+ preventing us from noticing when the user set their name in the
71+ welcome wizard.
72+
73+ [ Michael Zanetti ]
74+ * Allow alt+tabbing in staged mode too (LP: #1540502)
75+ * Allow invoking the staged mode spreads by mouse right edge pushes
76+ too (LP: #1540392)
77+ * Allow loading the device configuration from an external file
78+ * Implement Launcher's keyboard navigation and updated pip design
79+ * Make launcher scalable, allow it locking (LP: #1511015)
80+ * Properly parent launcher items in all cases (LP: #1495732)
81+ * Read inputMethod surface from the new property int QtMir (LP:
82+ #1545286)
83+ * Some visual updates and rotation lock for the virtual touchpad (LP:
84+ #1549087)
85+ * Visual updates for the windowed spread (LP: #1488148)
86+ * stabilize swipeAwayGreeter()
87+
88+ [ Michał Sawicz ]
89+ * Add udev rules to make sure we have access to uinput
90+ * Make dash respawn indefinitely (LP: #1550056)
91+ * Use dpkg-architecture, not arch, to disable touch emulation
92+
93+ [ Vesa Rautiainen ]
94+ * Fixing the vertical position of desktop spread item icon.
95+
96+ -- Michał Sawicz <michal.sawicz@canonical.com> Thu, 10 Mar 2016 22:44:16 +0000
97+
198unity8 (8.11+16.04.20160308-0ubuntu1) xenial; urgency=medium
299
3100 [ Albert Astals Cid ]
debian/control+13−11
@@ -8,13 +8,14 @@ Build-Depends: cmake,
88 doxyqml,
99# To allow cross-compiling to work, we also must append :native
1010# to g++ so we don't try to run armhf g++
11−# on an x86 CPU for eaxmple, when cross-compiling.
11+# on an x86 CPU for example, when cross-compiling.
1212 g++:native,
1313 libandroid-properties-dev,
1414 graphviz,
1515 gsettings-ubuntu-schemas (>= 0.0.2+14.10.20140815),
16− libconnectivity-qt1-dev,
16+ libconnectivity-qt1-dev (>= 0.7.1),
1717 libevdev-dev,
18+ libgeonames-dev (>= 0.2),
1819 libgl1-mesa-dev[!armhf] | libgl-dev[!armhf],
1920 libgl1-mesa-dri,
2021 libgles2-mesa-dev[armhf],
@@ -29,7 +30,7 @@ Build-Depends: cmake,
2930 libqt5xmlpatterns5-dev,
3031 libsystemsettings-dev,
3132 libudev-dev,
32− libunity-api-dev (>= 7.107),
33+ libunity-api-dev (>= 7.108),
3334 libusermetricsoutput1-dev,
3435# Need those X11 libs touch emulation from mouse events in manual QML tests on a X11 desktop
3536 libx11-dev[!armhf],
@@ -55,7 +56,7 @@ Build-Depends: cmake,
5556 qtdeclarative5-qtmultimedia-plugin (>= 5.4.1-1ubuntu19~overlay2),
5657 qtdeclarative5-ubuntu-content1,
5758 qtdeclarative5-ubuntu-settings-components (>= 0.7),
58− qtdeclarative5-ubuntu-ui-toolkit-plugin (>= 1.3.1796) | qtdeclarative5-ubuntu-ui-toolkit-plugin-gles (>= 1.3.1796),
59+ qtdeclarative5-ubuntu-ui-toolkit-plugin (>= 1.3.1845) | qtdeclarative5-ubuntu-ui-toolkit-plugin-gles (>= 1.3.1845),
5960 qtdeclarative5-ubuntu-web-plugin,
6061 ttf-ubuntu-font-family,
6162Standards-Version: 3.9.4
@@ -70,7 +71,7 @@ X-Ubuntu-Use-Langpack: yes
7071Package: indicators-client
7172Architecture: amd64 armhf i386
7273Depends: qmenumodel-qml (>= 0.2.9),
73− qtdeclarative5-ubuntu-ui-toolkit-plugin (>= 1.3.1796) | qtdeclarative5-ubuntu-ui-toolkit-plugin-gles (>= 1.3.1796),
74+ qtdeclarative5-ubuntu-ui-toolkit-plugin (>= 1.3.1845) | qtdeclarative5-ubuntu-ui-toolkit-plugin-gles (>= 1.3.1845),
7475 unity8 (= ${binary:Version}),
7576 ${misc:Depends},
7677 ${shlibs:Depends},
@@ -101,8 +102,7 @@ Depends: dmz-cursor-theme,
101102 qml-module-qt-labs-folderlistmodel,
102103 qml-module-qtquick-xmllistmodel,
103104 qml-module-qtsysteminfo,
104− qtdeclarative5-gsettings1.0,
105− qtdeclarative5-qtmir-plugin (>= 0.4.5),
105+ qtdeclarative5-qtmir-plugin (>= 0.4.8),
106106 qtdeclarative5-ubuntu-telephony0.1,
107107 qtdeclarative5-ubuntu-web-plugin,
108108 ubuntu-system-settings,
@@ -128,10 +128,10 @@ Architecture: all
128128Depends: qml-module-qtquick-layouts,
129129 qtdeclarative5-ubuntu-settings-components (>= 0.7),
130130 qtdeclarative5-ubuntu-thumbnailer0.1 | ubuntu-thumbnailer-impl,
131− qtdeclarative5-ubuntu-ui-toolkit-plugin (>= 1.3.1796) | qtdeclarative5-ubuntu-ui-toolkit-plugin-gles (>= 1.3.1796),
131+ qtdeclarative5-ubuntu-ui-toolkit-plugin (>= 1.3.1845) | qtdeclarative5-ubuntu-ui-toolkit-plugin-gles (>= 1.3.1845),
132132 qtdeclarative5-unity-notifications-plugin (>= 0.1.2) | unity-notifications-impl,
133133 ubuntu-thumbnailer-impl-0,
134− unity-application-impl-13,
134+ unity-application-impl-14,
135135 unity-notifications-impl-3,
136136 unity-plugin-scopes | unity-scopes-impl,
137137 unity-scopes-impl-10,
@@ -158,7 +158,7 @@ Depends: autopilot-qt5 (>= 1.4),
158158 python3-fixtures,
159159 python3-gi,
160160 qttestability-autopilot (>= 1.4),
161− ubuntu-ui-toolkit-autopilot (>= 1.3.1796),
161+ ubuntu-ui-toolkit-autopilot (>= 1.3.1845),
162162 unity-scope-click,
163163 unity8 (= ${source:Version}),
164164 unity8-fake-env (= ${source:Version}),
@@ -177,7 +177,7 @@ Pre-Depends: ${misc:Pre-Depends},
177177Depends: ${misc:Depends},
178178 ${shlibs:Depends},
179179Provides: unity-application-impl,
180− unity-application-impl-13,
180+ unity-application-impl-14,
181181Replaces: unity8-autopilot (<< 8.02+15.04.20150422-0ubuntu1)
182182Description: Fake environment for running Unity 8 shell
183183 Provides fake implementations of some QML modules used by Unity 8 shell
@@ -192,6 +192,8 @@ Depends: accountsservice-ubuntu-schemas (>= 0.0.5),
192192 libhardware2,
193193 pay-service,
194194 unity-schemas (>= 7.3.1+14.10.20140915),
195+ qtdeclarative5-gsettings1.0,
196+ qml-module-qtmultimedia,
195197 ${misc:Depends},
196198 ${shlibs:Depends},
197199Provides: unity-launcher-impl,
debian/unity8-common.udevnew file+2
@@ -0,0 +1,2 @@
1+# Make local foreground session able to inject input
2+KERNEL=="uinput", SUBSYSTEM=="misc", TAG+="uaccess"
debian/unity8-doc.install+1
@@ -1 +1,2 @@
1+doc/devices.conf usr/share/doc/unity8/
12usr/share/doc/unity8/*
doc/devices.confnew file+30
@@ -0,0 +1,30 @@
1+# This file can hold multiple device configs. Devices are separated by sections.
2+#
3+# SupportedOrientations holds a list of all enabled orientations. A standard
4+# phone will usually have Portrait,Landcape,InvertedLandscape in order to
5+# disable upside down usage.
6+#
7+# PrimaryOrientation gives the orientation the device will start up with
8+# when there is no orientations sensor input available (yet) or lock to
9+# when an application specifies to be locked to PrimaryOrientation.
10+#
11+# The other Orientation settings can be used to re-map the orientations.
12+# A device might be used with different orientations than how the screen
13+# is physically mounted on the hardware.
14+#
15+# Category can be phone, tablet, or desktop. This option determines
16+# whether the side stage is shown (tablet) or not (phone). Using
17+# desktop will load the shell in windowed mode. Note that the user
18+# can override/change this by connecting input hardware or change
19+# user settings.
20+#
21+# Any options not listed will default to the values of the example below.
22+
23+[devicename]
24+SupportedOrientations=Portrait,InvertedPortrait,Landscape,InvertedLandscape
25+PrimaryOrientation=PrimaryOrienation
26+PortraitOrientation=Portrait
27+InvertedPortraitOrientation=InvertedPortrait
28+LandscapeOrientation=Landscape
29+InvertedLandscapeOrientation=InvertedLandscape
30+Category=phone
libs/UbuntuGestures/Timer.h+5
@@ -35,6 +35,11 @@ public:
3535 virtual int interval() const = 0;
3636 virtual void setInterval(int msecs) = 0;
3737 virtual void start() { m_isRunning = true; }
38+ virtual void start(int msecs)
39+ {
40+ setInterval(msecs);
41+ start();
42+ }
3843 virtual void stop() { m_isRunning = false; }
3944 bool isRunning() const { return m_isRunning; }
4045 virtual bool isSingleShot() const = 0;
libs/UbuntuGestures/TouchRegistry.cpp+15−10
@@ -359,10 +359,6 @@ void TouchRegistry::removeCandidateOwnerForTouchByIndex(Pool<TouchRegistry::Touc
359359
360360void TouchRegistry::requestTouchOwnership(int id, QQuickItem *candidate)
361361{
362− #if TOUCHREGISTRY_DEBUG
363− UG_DEBUG << "requestTouchOwnership id " << id << "candidate" << candidate;
364− #endif
365−
366362 Pool<TouchInfo>::Iterator touchInfo = findTouchInfo(id);
367363 if (!touchInfo) { qFatal("TouchRegistry: Failed to find TouchInfo"); }
368364
@@ -379,6 +375,9 @@ void TouchRegistry::requestTouchOwnership(int id, QQuickItem *candidate)
379375 break;
380376 }
381377 }
378+ #if TOUCHREGISTRY_DEBUG
379+ UG_DEBUG << "requestTouchOwnership id " << id << "candidate" << candidate << "index: " << candidateIndex;
380+ #endif
382381
383382 // add it as a candidate if not present yet
384383 if (candidateIndex < 0) {
@@ -533,16 +532,22 @@ void TouchRegistry::TouchInfo::notifyCandidatesOfOwnershipResolution()
533532 << " gained) to candidate" << candidates[0].item;
534533 #endif
535534
536− TouchOwnershipEvent gainedOwnershipEvent(id, true /*gained*/);
537− QCoreApplication::sendEvent(candidates[0].item, &gainedOwnershipEvent);
535+ // need to take a copy of the item list in case
536+ // we call back in to remove candidate during the lost ownership event.
537+ QList<QPointer<QQuickItem>> items;
538+ Q_FOREACH(const CandidateInfo& info, candidates) {
539+ items << info.item;
540+ }
538541
542+ TouchOwnershipEvent gainedOwnershipEvent(id, true /*gained*/);
543+ QCoreApplication::sendEvent(items[0], &gainedOwnershipEvent);
539544
540545 TouchOwnershipEvent lostOwnershipEvent(id, false /*gained*/);
541− for (int i = 1; i < candidates.count(); ++i) {
546+ for (int i = 1; i < items.count(); ++i) {
542547 #if TOUCHREGISTRY_DEBUG
543− UG_DEBUG << "sending TouchWonershipEvent(id =" << id << " lost) to candidate"
544− << candidates[i].item;
548+ UG_DEBUG << "sending TouchOwnershipEvent(id =" << id << " lost) to candidate"
549+ << items[i];
545550 #endif
546− QCoreApplication::sendEvent(candidates[i].item, &lostOwnershipEvent);
551+ QCoreApplication::sendEvent(items[i], &lostOwnershipEvent);
547552 }
548553}
plugins/AccountsService/AccountsService.cpp+212−374
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013, 2015 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -12,8 +12,6 @@
1212 *
1313 * You should have received a copy of the GNU General Public License
1414 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− *
16− * Author: Michael Terry <michael.terry@canonical.com>
1715 */
1816
1917#include "AccountsService.h"
@@ -24,18 +22,55 @@
2422#include <QStringList>
2523#include <QDebug>
2624
25+#define IFACE_ACCOUNTS_USER QStringLiteral("org.freedesktop.Accounts.User")
26+#define IFACE_LOCATION_HERE QStringLiteral("com.ubuntu.location.providers.here.AccountsService")
27+#define IFACE_UBUNTU_INPUT QStringLiteral("com.ubuntu.AccountsService.Input")
28+#define IFACE_UBUNTU_SECURITY QStringLiteral("com.ubuntu.AccountsService.SecurityPrivacy")
29+#define IFACE_UBUNTU_SECURITY_OLD QStringLiteral("com.ubuntu.touch.AccountsService.SecurityPrivacy")
30+#define IFACE_UNITY QStringLiteral("com.canonical.unity.AccountsService")
31+#define IFACE_UNITY_PRIVATE QStringLiteral("com.canonical.unity.AccountsService.Private")
32+
33+#define PROP_BACKGROUND_FILE QStringLiteral("BackgroundFile")
34+#define PROP_DEMO_EDGES QStringLiteral("demo-edges")
35+#define PROP_DEMO_EDGES_COMPLETED QStringLiteral("DemoEdgesCompleted")
36+#define PROP_EMAIL QStringLiteral("Email")
37+#define PROP_ENABLE_INDICATORS_WHILE_LOCKED QStringLiteral("EnableIndicatorsWhileLocked")
38+#define PROP_ENABLE_LAUNCHER_WHILE_LOCKED QStringLiteral("EnableLauncherWhileLocked")
39+#define PROP_FAILED_LOGINS QStringLiteral("FailedLogins")
40+#define PROP_LICENSE_ACCEPTED QStringLiteral("LicenseAccepted")
41+#define PROP_LICENSE_BASE_PATH QStringLiteral("LicenseBasePath")
42+#define PROP_MOUSE_CURSOR_SPEED QStringLiteral("MouseCursorSpeed")
43+#define PROP_MOUSE_DOUBLE_CLICK_SPEED QStringLiteral("MouseDoubleClickSpeed")
44+#define PROP_MOUSE_PRIMARY_BUTTON QStringLiteral("MousePrimaryButton")
45+#define PROP_MOUSE_SCROLL_SPEED QStringLiteral("MouseScrollSpeed")
46+#define PROP_PASSWORD_DISPLAY_HINT QStringLiteral("PasswordDisplayHint")
47+#define PROP_REAL_NAME QStringLiteral("RealName")
48+#define PROP_STATS_WELCOME_SCREEN QStringLiteral("StatsWelcomeScreen")
49+#define PROP_TOUCHPAD_CURSOR_SPEED QStringLiteral("TouchpadCursorSpeed")
50+#define PROP_TOUCHPAD_DISABLE_WHILE_TYPING QStringLiteral("TouchpadDisableWhileTyping")
51+#define PROP_TOUCHPAD_DISABLE_WITH_MOUSE QStringLiteral("TouchpadDisableWithMouse")
52+#define PROP_TOUCHPAD_DOUBLE_CLICK_SPEED QStringLiteral("TouchpadDoubleClickSpeed")
53+#define PROP_TOUCHPAD_PRIMARY_BUTTON QStringLiteral("TouchpadPrimaryButton")
54+#define PROP_TOUCHPAD_SCROLL_SPEED QStringLiteral("TouchpadScrollSpeed")
55+#define PROP_TOUCHPAD_TAP_TO_CLICK QStringLiteral("TouchpadTapToClick")
56+#define PROP_TOUCHPAD_TWO_FINGER_SCROLL QStringLiteral("TouchpadTwoFingerScroll")
57+
58+
59+QVariant primaryButtonConverter(const QVariant &value)
60+{
61+ QString stringValue = value.toString();
62+ if (stringValue == "left") {
63+ return QVariant::fromValue(0);
64+ } else if (stringValue == "right") {
65+ return QVariant::fromValue(1); // Mir is less clear on this -- any non-zero value is the same
66+ } else {
67+ return QVariant::fromValue(0); // default to left
68+ }
69+}
70+
2771AccountsService::AccountsService(QObject* parent, const QString &user)
28− : QObject(parent),
29− m_service(new AccountsServiceDBusAdaptor(this)),
30− m_demoEdges(false),
31− m_demoEdgesCompleted(),
32− m_enableLauncherWhileLocked(false),
33− m_enableIndicatorsWhileLocked(false),
34− m_statsWelcomeScreen(false),
35− m_passwordDisplayHint(Keyboard),
36− m_failedLogins(0),
37− m_hereEnabled(false),
38− m_hereLicensePath() // null means not set yet
72+ : QObject(parent)
73+ , m_service(new AccountsServiceDBusAdaptor(this))
3974{
4075 m_unityInput = new QDBusInterface(QStringLiteral("com.canonical.Unity.Input"),
4176 QStringLiteral("/com/canonical/Unity/Input"),
@@ -45,6 +80,46 @@ AccountsService::AccountsService(QObject* parent, const QString &user)
4580 connect(m_service, &AccountsServiceDBusAdaptor::propertiesChanged, this, &AccountsService::onPropertiesChanged);
4681 connect(m_service, &AccountsServiceDBusAdaptor::maybeChanged, this, &AccountsService::onMaybeChanged);
4782
83+ registerProperty(IFACE_ACCOUNTS_USER, PROP_BACKGROUND_FILE, QStringLiteral("backgroundFileChanged"));
84+ registerProperty(IFACE_ACCOUNTS_USER, PROP_EMAIL, QStringLiteral("emailChanged"));
85+ registerProperty(IFACE_ACCOUNTS_USER, PROP_REAL_NAME, QStringLiteral("realNameChanged"));
86+ registerProperty(IFACE_LOCATION_HERE, PROP_LICENSE_ACCEPTED, QStringLiteral("hereEnabledChanged"));
87+ registerProperty(IFACE_LOCATION_HERE, PROP_LICENSE_BASE_PATH, QStringLiteral("hereLicensePathChanged"));
88+ registerProperty(IFACE_UBUNTU_SECURITY, PROP_ENABLE_LAUNCHER_WHILE_LOCKED, QStringLiteral("enableLauncherWhileLockedChanged"));
89+ registerProperty(IFACE_UBUNTU_SECURITY, PROP_ENABLE_INDICATORS_WHILE_LOCKED, QStringLiteral("enableIndicatorsWhileLockedChanged"));
90+ registerProperty(IFACE_UBUNTU_SECURITY, PROP_PASSWORD_DISPLAY_HINT, QStringLiteral("passwordDisplayHintChanged"));
91+ registerProperty(IFACE_UBUNTU_SECURITY_OLD, PROP_STATS_WELCOME_SCREEN, QStringLiteral("statsWelcomeScreenChanged"));
92+ registerProperty(IFACE_UNITY, PROP_DEMO_EDGES, QStringLiteral("demoEdgesChanged"));
93+ registerProperty(IFACE_UNITY, PROP_DEMO_EDGES_COMPLETED, QStringLiteral("demoEdgesCompletedChanged"));
94+ registerProperty(IFACE_UNITY_PRIVATE, PROP_FAILED_LOGINS, QStringLiteral("failedLoginsChanged"));
95+
96+ registerProxy(IFACE_UBUNTU_INPUT, PROP_MOUSE_CURSOR_SPEED,
97+ m_unityInput, QStringLiteral("setMouseCursorSpeed"));
98+ registerProxy(IFACE_UBUNTU_INPUT, PROP_MOUSE_DOUBLE_CLICK_SPEED,
99+ m_unityInput, QStringLiteral("setMouseDoubleClickSpeed"));
100+ registerProxy(IFACE_UBUNTU_INPUT, PROP_MOUSE_PRIMARY_BUTTON,
101+ m_unityInput, QStringLiteral("setMousePrimaryButton"),
102+ primaryButtonConverter);
103+ registerProxy(IFACE_UBUNTU_INPUT, PROP_MOUSE_SCROLL_SPEED,
104+ m_unityInput, QStringLiteral("setMouseScrollSpeed"));
105+ registerProxy(IFACE_UBUNTU_INPUT, PROP_TOUCHPAD_CURSOR_SPEED,
106+ m_unityInput, QStringLiteral("setTouchpadCursorSpeed"));
107+ registerProxy(IFACE_UBUNTU_INPUT, PROP_TOUCHPAD_SCROLL_SPEED,
108+ m_unityInput, QStringLiteral("setTouchpadScrollSpeed"));
109+ registerProxy(IFACE_UBUNTU_INPUT, PROP_TOUCHPAD_DISABLE_WHILE_TYPING,
110+ m_unityInput, QStringLiteral("setTouchpadDisableWhileTyping"));
111+ registerProxy(IFACE_UBUNTU_INPUT, PROP_TOUCHPAD_DISABLE_WITH_MOUSE,
112+ m_unityInput, QStringLiteral("setTouchpadDisableWithMouse"));
113+ registerProxy(IFACE_UBUNTU_INPUT, PROP_TOUCHPAD_DOUBLE_CLICK_SPEED,
114+ m_unityInput, QStringLiteral("setTouchpadDoubleClickSpeed"));
115+ registerProxy(IFACE_UBUNTU_INPUT, PROP_TOUCHPAD_PRIMARY_BUTTON,
116+ m_unityInput, QStringLiteral("setTouchpadPrimaryButton"),
117+ primaryButtonConverter);
118+ registerProxy(IFACE_UBUNTU_INPUT, PROP_TOUCHPAD_TAP_TO_CLICK,
119+ m_unityInput, QStringLiteral("setTouchpadTapToClick"));
120+ registerProxy(IFACE_UBUNTU_INPUT, PROP_TOUCHPAD_TWO_FINGER_SCROLL,
121+ m_unityInput, QStringLiteral("setTouchpadTwoFingerScroll"));
122+
48123 setUser(!user.isEmpty() ? user : QString::fromUtf8(qgetenv("USER")));
49124}
50125
@@ -58,455 +133,236 @@ void AccountsService::setUser(const QString &user)
58133 if (user.isEmpty() || m_user == user)
59134 return;
60135
136+ bool wasEmpty = m_user.isEmpty();
137+
61138 m_user = user;
62139 Q_EMIT userChanged();
63140
64− updateDemoEdgesCompleted(false);
65− updateDemoEdges(false);
66− updateEnableLauncherWhileLocked(false);
67− updateEnableIndicatorsWhileLocked(false);
68− updateBackgroundFile(false);
69− updateMouseCursorSpeed();
70− updateTouchpadCursorSpeed();
71− updateStatsWelcomeScreen(false);
72− updatePasswordDisplayHint(false);
73− updateFailedLogins(false);
74− updateHereEnabled(false);
75− updateHereLicensePath(false);
141+ // Do the first update synchronously, as a cheap way to block rendering
142+ // until we have the right values on bootup.
143+ refresh(!wasEmpty);
76144}
77145
78146bool AccountsService::demoEdges() const
79147{
80− return m_demoEdges;
148+ auto value = getProperty(IFACE_UNITY, PROP_DEMO_EDGES);
149+ return value.toBool();
81150}
82151
83152void AccountsService::setDemoEdges(bool demoEdges)
84153{
85− if (m_demoEdges != demoEdges) {
86− m_demoEdges = demoEdges;
87− m_service->setUserPropertyAsync(m_user, QStringLiteral("com.canonical.unity.AccountsService"), QStringLiteral("demo-edges"), demoEdges);
88−
89− Q_EMIT demoEdgesChanged();
90− }
154+ setProperty(IFACE_UNITY, PROP_DEMO_EDGES, demoEdges);
91155}
92156
93157QStringList AccountsService::demoEdgesCompleted() const
94158{
95− return m_demoEdgesCompleted;
159+ auto value = getProperty(IFACE_UNITY, PROP_DEMO_EDGES_COMPLETED);
160+ return value.toStringList();
96161}
97162
98163void AccountsService::markDemoEdgeCompleted(const QString &edge)
99164{
100− if (!m_demoEdgesCompleted.contains(edge)) {
101− m_demoEdgesCompleted << edge;
102− m_service->setUserPropertyAsync(m_user, "com.canonical.unity.AccountsService", "DemoEdgesCompleted", m_demoEdgesCompleted);
103−
104− Q_EMIT demoEdgesCompletedChanged();
165+ auto currentList = demoEdgesCompleted();
166+ if (!currentList.contains(edge)) {
167+ setProperty(IFACE_UNITY, PROP_DEMO_EDGES_COMPLETED, currentList << edge);
105168 }
106169}
107170
108171bool AccountsService::enableLauncherWhileLocked() const
109172{
110− return m_enableLauncherWhileLocked;
173+ auto value = getProperty(IFACE_UBUNTU_SECURITY, PROP_ENABLE_LAUNCHER_WHILE_LOCKED);
174+ return value.toBool();
111175}
112176
113177bool AccountsService::enableIndicatorsWhileLocked() const
114178{
115− return m_enableIndicatorsWhileLocked;
179+ auto value = getProperty(IFACE_UBUNTU_SECURITY, PROP_ENABLE_INDICATORS_WHILE_LOCKED);
180+ return value.toBool();
116181}
117182
118183QString AccountsService::backgroundFile() const
119184{
120− return m_backgroundFile;
185+ auto value = getProperty(IFACE_ACCOUNTS_USER, PROP_BACKGROUND_FILE);
186+ return value.toString();
121187}
122188
123189bool AccountsService::statsWelcomeScreen() const
124190{
125− return m_statsWelcomeScreen;
191+ auto value = getProperty(IFACE_UBUNTU_SECURITY_OLD, PROP_STATS_WELCOME_SCREEN);
192+ return value.toBool();
126193}
127194
128195AccountsService::PasswordDisplayHint AccountsService::passwordDisplayHint() const
129196{
130− return m_passwordDisplayHint;
197+ auto value = getProperty(IFACE_UBUNTU_SECURITY, PROP_PASSWORD_DISPLAY_HINT);
198+ return (PasswordDisplayHint)value.toInt();
131199}
132200
133201bool AccountsService::hereEnabled() const
134202{
135− return m_hereEnabled;
203+ auto value = getProperty(IFACE_LOCATION_HERE, PROP_LICENSE_ACCEPTED);
204+ return value.toBool();
136205}
137206
138207void AccountsService::setHereEnabled(bool enabled)
139208{
140− if (m_hereEnabled != enabled) {
141− m_hereEnabled = enabled;
142− m_service->setUserPropertyAsync(m_user, QStringLiteral("com.ubuntu.location.providers.here.AccountsService"), QStringLiteral("LicenseAccepted"), enabled);
143−
144− Q_EMIT hereEnabledChanged();
145− }
209+ setProperty(IFACE_LOCATION_HERE, PROP_LICENSE_ACCEPTED, enabled);
146210}
147211
148212QString AccountsService::hereLicensePath() const
149213{
150− return m_hereLicensePath;
214+ auto value = getProperty(IFACE_LOCATION_HERE, PROP_LICENSE_BASE_PATH);
215+ QString hereLicensePath = value.toString();
216+ if (hereLicensePath.isEmpty() || !QFile::exists(hereLicensePath))
217+ hereLicensePath = QStringLiteral("");
218+ return hereLicensePath;
151219}
152220
153221bool AccountsService::hereLicensePathValid() const
154222{
155− return !m_hereLicensePath.isNull();
223+ auto value = getProperty(IFACE_LOCATION_HERE, PROP_LICENSE_BASE_PATH);
224+ return !value.toString().isNull();
156225}
157226
158−void AccountsService::updateDemoEdges(bool async)
227+QString AccountsService::realName() const
159228{
160− QDBusPendingCall pendingReply = m_service->getUserPropertyAsync(m_user,
161− QStringLiteral("com.canonical.unity.AccountsService"),
162− QStringLiteral("demo-edges"));
163− QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingReply, this);
164−
165− connect(watcher, &QDBusPendingCallWatcher::finished,
166− this, [this](QDBusPendingCallWatcher* watcher) {
167−
168− QDBusPendingReply<QDBusVariant> reply = *watcher;
169− watcher->deleteLater();
170− if (reply.isError()) {
171− qWarning() << "Failed to get 'demo-edges' property - " << reply.error().message();
172− return;
173− }
174−
175− auto demoEdges = reply.value().variant().toBool();
176− if (m_demoEdges != demoEdges) {
177− m_demoEdges = demoEdges;
178− Q_EMIT demoEdgesChanged();
179− }
180− });
181− if (!async) {
182− watcher->waitForFinished();
183− delete watcher;
184− }
229+ auto value = getProperty(IFACE_ACCOUNTS_USER, PROP_REAL_NAME);
230+ return value.toString();
185231}
186232
187−void AccountsService::updateDemoEdgesCompleted(bool async)
233+void AccountsService::setRealName(const QString &realName)
188234{
189− QDBusPendingCall pendingReply = m_service->getUserPropertyAsync(m_user,
190− "com.canonical.unity.AccountsService",
191− "DemoEdgesCompleted");
192− QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingReply, this);
193−
194− connect(watcher, &QDBusPendingCallWatcher::finished,
195− this, [this](QDBusPendingCallWatcher* watcher) {
196−
197− QDBusPendingReply<QDBusVariant> reply = *watcher;
198− watcher->deleteLater();
199− if (reply.isError()) {
200− qWarning() << "Failed to get 'DemoEdgesCompleted' property - " << reply.error().message();
201− return;
202− }
203−
204− auto demoEdgesCompleted = reply.value().variant().toStringList();
205− if (m_demoEdgesCompleted != demoEdgesCompleted) {
206− m_demoEdgesCompleted = demoEdgesCompleted;
207− Q_EMIT demoEdgesCompletedChanged();
208− }
209− });
210− if (!async) {
211− watcher->waitForFinished();
212− delete watcher;
213− }
235+ setProperty(IFACE_ACCOUNTS_USER, PROP_REAL_NAME, realName);
214236}
215237
216−void AccountsService::updateEnableLauncherWhileLocked(bool async)
238+QString AccountsService::email() const
217239{
218− QDBusPendingCall pendingReply = m_service->getUserPropertyAsync(m_user,
219− QStringLiteral("com.ubuntu.AccountsService.SecurityPrivacy"),
220− QStringLiteral("EnableLauncherWhileLocked"));
221− QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingReply, this);
222−
223− connect(watcher, &QDBusPendingCallWatcher::finished,
224− this, [this](QDBusPendingCallWatcher* watcher) {
225−
226− QDBusPendingReply<QVariant> reply = *watcher;
227− watcher->deleteLater();
228− if (reply.isError()) {
229− qWarning() << "Failed to get 'EnableLauncherWhileLocked' property - " << reply.error().message();
230− return;
231− }
232−
233− const bool enableLauncherWhileLocked = reply.value().toBool();
234− if (m_enableLauncherWhileLocked != enableLauncherWhileLocked) {
235− m_enableLauncherWhileLocked = enableLauncherWhileLocked;
236− Q_EMIT enableLauncherWhileLockedChanged();
237− }
238− });
239− if (!async) {
240− watcher->waitForFinished();
241− delete watcher;
242− }
240+ auto value = getProperty(IFACE_ACCOUNTS_USER, PROP_EMAIL);
241+ return value.toString();
243242}
244243
245−void AccountsService::updateEnableIndicatorsWhileLocked(bool async)
244+void AccountsService::setEmail(const QString &email)
246245{
247− QDBusPendingCall pendingReply = m_service->getUserPropertyAsync(m_user,
248− QStringLiteral("com.ubuntu.AccountsService.SecurityPrivacy"),
249− QStringLiteral("EnableIndicatorsWhileLocked"));
250− QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingReply, this);
251−
252− connect(watcher, &QDBusPendingCallWatcher::finished,
253− this, [this](QDBusPendingCallWatcher* watcher) {
254−
255− QDBusPendingReply<QVariant> reply = *watcher;
256− watcher->deleteLater();
257− if (reply.isError()) {
258− qWarning() << "Failed to get 'EnableIndicatorsWhileLocked' property - " << reply.error().message();
259− return;
260− }
261−
262− const bool enableIndicatorsWhileLocked = reply.value().toBool();
263− if (m_enableIndicatorsWhileLocked != enableIndicatorsWhileLocked) {
264− m_enableIndicatorsWhileLocked = enableIndicatorsWhileLocked;
265− Q_EMIT enableIndicatorsWhileLockedChanged();
266− }
267− });
268− if (!async) {
269− watcher->waitForFinished();
270− delete watcher;
271− }
246+ setProperty(IFACE_ACCOUNTS_USER, PROP_EMAIL, email);
272247}
273248
274−void AccountsService::updateBackgroundFile(bool async)
249+uint AccountsService::failedLogins() const
275250{
276− QDBusPendingCall pendingReply = m_service->getUserPropertyAsync(m_user,
277− QStringLiteral("org.freedesktop.Accounts.User"),
278− QStringLiteral("BackgroundFile"));
279− QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingReply, this);
280−
281− connect(watcher, &QDBusPendingCallWatcher::finished,
282− this, [this](QDBusPendingCallWatcher* watcher) {
283−
284− QDBusPendingReply<QVariant> reply = *watcher;
285− watcher->deleteLater();
286− if (reply.isError()) {
287− qWarning() << "Failed to get 'BackgroundFile' property - " << reply.error().message();
288− return;
289− }
290−
291− const QString backgroundFile = reply.value().toString();
292− if (m_backgroundFile != backgroundFile) {
293− m_backgroundFile = backgroundFile;
294− Q_EMIT backgroundFileChanged();
295− }
296− });
297− if (!async) {
298− watcher->waitForFinished();
299− delete watcher;
300− }
251+ return getProperty(IFACE_UNITY_PRIVATE, PROP_FAILED_LOGINS).toUInt();
301252}
302253
303−void AccountsService::updateMouseCursorSpeed()
254+void AccountsService::setFailedLogins(uint failedLogins)
304255{
305− QDBusPendingCall pendingReply = m_service->getUserPropertyAsync(m_user,
306− QStringLiteral("com.ubuntu.AccountsService.Input"),
307− QStringLiteral("MouseCursorSpeed"));
308− QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingReply, this);
309−
310− connect(watcher, &QDBusPendingCallWatcher::finished,
311− this, [this](QDBusPendingCallWatcher* watcher) {
312−
313− QDBusPendingReply<QVariant> reply = *watcher;
314− watcher->deleteLater();
315− if (reply.isError()) {
316− qWarning() << "Failed to get 'MouseCursorSpeed' property - " << reply.error().message();
317− return;
318− }
319−
320− // Merely proxy this along to USC. We don't care about keeping a copy
321− // or exporting it internally.
322− m_unityInput->asyncCall(QStringLiteral("setMouseCursorSpeed"), reply.value());
323− });
256+ setProperty(IFACE_UNITY_PRIVATE, PROP_FAILED_LOGINS, failedLogins);
324257}
325258
326−void AccountsService::updateTouchpadCursorSpeed()
327−{
328− QDBusPendingCall pendingReply = m_service->getUserPropertyAsync(m_user,
329− QStringLiteral("com.ubuntu.AccountsService.Input"),
330− QStringLiteral("TouchpadCursorSpeed"));
331− QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingReply, this);
332−
333− connect(watcher, &QDBusPendingCallWatcher::finished,
334− this, [this](QDBusPendingCallWatcher* watcher) {
335−
336− QDBusPendingReply<QVariant> reply = *watcher;
337− watcher->deleteLater();
338− if (reply.isError()) {
339− qWarning() << "Failed to get 'TouchpadCursorSpeed' property - " << reply.error().message();
340− return;
341− }
259+// ====================================================
260+// Everything below this line is generic helper methods
261+// ====================================================
342262
343− // Merely proxy this along to USC. We don't care about keeping a copy
344− // or exporting it internally.
345− m_unityInput->asyncCall(QStringLiteral("setTouchpadCursorSpeed"), reply.value());
346− });
263+void AccountsService::emitChangedForProperty(const QString &interface, const QString &property)
264+{
265+ QString signalName = m_properties[interface][property].signal;
266+ QMetaObject::invokeMethod(this, signalName.toUtf8().data());
347267}
348268
349−void AccountsService::updateStatsWelcomeScreen(bool async)
269+QVariant AccountsService::getProperty(const QString &interface, const QString &property) const
350270{
351− QDBusPendingCall pendingReply = m_service->getUserPropertyAsync(m_user,
352− QStringLiteral("com.ubuntu.touch.AccountsService.SecurityPrivacy"),
353− QStringLiteral("StatsWelcomeScreen"));
354− QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingReply, this);
355−
356− connect(watcher, &QDBusPendingCallWatcher::finished,
357− this, [this](QDBusPendingCallWatcher* watcher) {
358−
359− QDBusPendingReply<QVariant> reply = *watcher;
360− watcher->deleteLater();
361− if (reply.isError()) {
362− qWarning() << "Failed to get 'StatsWelcomeScreen' property - " << reply.error().message();
363− return;
364− }
365−
366− const bool statsWelcomeScreen = reply.value().toBool();
367− if (m_statsWelcomeScreen != statsWelcomeScreen) {
368− m_statsWelcomeScreen = statsWelcomeScreen;
369− Q_EMIT statsWelcomeScreenChanged();
370− }
371− });
372− if (!async) {
373− watcher->waitForFinished();
374− delete watcher;
375− }
271+ return m_properties[interface][property].value;
376272}
377273
378−void AccountsService::updatePasswordDisplayHint(bool async)
274+void AccountsService::setProperty(const QString &interface, const QString &property, const QVariant &value)
379275{
380− QDBusPendingCall pendingReply = m_service->getUserPropertyAsync(m_user,
381− QStringLiteral("com.ubuntu.AccountsService.SecurityPrivacy"),
382− QStringLiteral("PasswordDisplayHint"));
383− QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingReply, this);
384−
385− connect(watcher, &QDBusPendingCallWatcher::finished,
386− this, [this](QDBusPendingCallWatcher* watcher) {
387−
388− QDBusPendingReply<QVariant> reply = *watcher;
389− watcher->deleteLater();
390− if (reply.isError()) {
391− qWarning() << "Failed to get 'PasswordDisplayHint' property - " << reply.error().message();
392− return;
393− }
394−
395− const PasswordDisplayHint passwordDisplayHint = (PasswordDisplayHint)reply.value().toInt();
396− if (m_passwordDisplayHint != passwordDisplayHint) {
397− m_passwordDisplayHint = passwordDisplayHint;
398− Q_EMIT passwordDisplayHintChanged();
399− }
400− });
401− if (!async) {
402− watcher->waitForFinished();
403− delete watcher;
276+ if (m_properties[interface][property].value != value) {
277+ m_properties[interface][property].value = value;
278+ m_service->setUserPropertyAsync(m_user, interface, property, value);
279+ emitChangedForProperty(interface, property);
404280 }
405281}
406282
407−void AccountsService::updateFailedLogins(bool async)
283+void AccountsService::updateCache(const QString &interface, const QString &property, const QVariant &value)
408284{
409− QDBusPendingCall pendingReply = m_service->getUserPropertyAsync(m_user,
410− QStringLiteral("com.canonical.unity.AccountsService.Private"),
411− QStringLiteral("FailedLogins"));
412− QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingReply, this);
413−
414− connect(watcher, &QDBusPendingCallWatcher::finished,
415− this, [this](QDBusPendingCallWatcher* watcher) {
285+ PropertyInfo &info = m_properties[interface][property];
416286
417− QDBusPendingReply<QVariant> reply = *watcher;
418− watcher->deleteLater();
419− if (reply.isError()) {
420− qWarning() << "Failed to get 'FailedLogins' property - " << reply.error().message();
421− return;
287+ if (info.proxyInterface) {
288+ QVariant finalValue;
289+ if (info.proxyConverter) {
290+ finalValue = info.proxyConverter(value);
291+ } else {
292+ finalValue = value;
422293 }
294+ info.proxyInterface->asyncCall(info.proxyMethod, finalValue);
295+ return; // don't bother saving a copy
296+ }
423297
424− const uint failedLogins = reply.value().toUInt();
425− if (m_failedLogins != failedLogins) {
426− m_failedLogins = failedLogins;
427− Q_EMIT failedLoginsChanged();
428− }
429− });
430− if (!async) {
431− watcher->waitForFinished();
432− delete watcher;
298+ if (info.value != value) {
299+ info.value = value;
300+ emitChangedForProperty(interface, property);
433301 }
434302}
435303
436−void AccountsService::updateHereEnabled(bool async)
304+void AccountsService::updateProperty(const QString &interface, const QString &property)
437305{
438306 QDBusPendingCall pendingReply = m_service->getUserPropertyAsync(m_user,
439− QStringLiteral("com.ubuntu.location.providers.here.AccountsService"),
440− QStringLiteral("LicenseAccepted"));
307+ interface,
308+ property);
441309 QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingReply, this);
442310
443311 connect(watcher, &QDBusPendingCallWatcher::finished,
444− this, [this](QDBusPendingCallWatcher* watcher) {
312+ this, [this, interface, property](QDBusPendingCallWatcher* watcher) {
445313
446314 QDBusPendingReply<QVariant> reply = *watcher;
447315 watcher->deleteLater();
448316 if (reply.isError()) {
449− qWarning() << "Failed to get 'LicenseAccepted' property - " << reply.error().message();
317+ qWarning() << "Failed to get '" << property << "' property:" << reply.error().message();
450318 return;
451319 }
452320
453− const bool hereEnabled = reply.value().toBool();
454− if (m_hereEnabled != hereEnabled) {
455− m_hereEnabled = hereEnabled;
456− Q_EMIT hereEnabledChanged();
457− }
321+ updateCache(interface, property, reply.value());
458322 });
459− if (!async) {
460− watcher->waitForFinished();
461− delete watcher;
462− }
463323}
464324
465−void AccountsService::updateHereLicensePath(bool async)
325+void AccountsService::updateAllProperties(const QString &interface, bool async)
466326{
467− QDBusPendingCall pendingReply = m_service->getUserPropertyAsync(m_user,
468− QStringLiteral("com.ubuntu.location.providers.here.AccountsService"),
469− QStringLiteral("LicenseBasePath"));
327+ QDBusPendingCall pendingReply = m_service->getAllPropertiesAsync(m_user,
328+ interface);
470329 QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingReply, this);
471330
472331 connect(watcher, &QDBusPendingCallWatcher::finished,
473− this, [this](QDBusPendingCallWatcher* watcher) {
332+ this, [this, interface](QDBusPendingCallWatcher* watcher) {
474333
475− QDBusPendingReply<QVariant> reply = *watcher;
334+ QDBusPendingReply< QHash<QString, QVariant> > reply = *watcher;
476335 watcher->deleteLater();
477336 if (reply.isError()) {
478− qWarning() << "Failed to get 'LicenseBasePath' property - " << reply.error().message();
337+ qWarning() << "Failed to get all properties for" << interface << ":" << reply.error().message();
479338 return;
480339 }
481340
482− QString hereLicensePath = reply.value().toString();
483− if (hereLicensePath.isEmpty() || !QFile::exists(hereLicensePath))
484− hereLicensePath = QLatin1String("");
485−
486− if (m_hereLicensePath.isNull() || m_hereLicensePath != hereLicensePath) {
487− m_hereLicensePath = hereLicensePath;
488− Q_EMIT hereLicensePathChanged();
341+ auto valueHash = reply.value();
342+ auto i = valueHash.constBegin();
343+ while (i != valueHash.constEnd()) {
344+ updateCache(interface, i.key(), i.value());
345+ ++i;
489346 }
490347 });
491348 if (!async) {
492349 watcher->waitForFinished();
493− delete watcher;
494350 }
495351}
496352
497−uint AccountsService::failedLogins() const
353+void AccountsService::registerProxy(const QString &interface, const QString &property, QDBusInterface *iface, const QString &method, ProxyConverter converter)
498354{
499− return m_failedLogins;
355+ registerProperty(interface, property, nullptr);
356+
357+ m_properties[interface][property].proxyInterface = iface;
358+ m_properties[interface][property].proxyMethod = method;
359+ m_properties[interface][property].proxyConverter = converter;
500360}
501361
502−void AccountsService::setFailedLogins(uint failedLogins)
362+void AccountsService::registerProperty(const QString &interface, const QString &property, const QString &signal)
503363{
504− if (m_failedLogins != failedLogins) {
505− m_failedLogins = failedLogins;
506− m_service->setUserPropertyAsync(m_user, QStringLiteral("com.canonical.unity.AccountsService.Private"), QStringLiteral("FailedLogins"), failedLogins);
507−
508− Q_EMIT failedLoginsChanged();
509− }
364+ m_properties[interface][property] = PropertyInfo();
365+ m_properties[interface][property].signal = signal;
510366}
511367
512368void AccountsService::onPropertiesChanged(const QString &user, const QString &interface, const QStringList &changed)
@@ -515,45 +371,13 @@ void AccountsService::onPropertiesChanged(const QString &user, const QString &in
515371 return;
516372 }
517373
518− if (interface == QLatin1String("com.canonical.unity.AccountsService")) {
519− if (changed.contains(QStringLiteral("demo-edges"))) {
520− updateDemoEdges();
521− }
522− if (changed.contains(QStringLiteral("DemoEdgesCompleted"))) {
523− updateDemoEdgesCompleted();
524− }
525− } else if (interface == QLatin1String("com.canonical.unity.AccountsService.Private")) {
526− if (changed.contains(QStringLiteral("FailedLogins"))) {
527− updateFailedLogins();
528− }
529− } else if (interface == QLatin1String("com.ubuntu.AccountsService.Input")) {
530− if (changed.contains(QStringLiteral("MouseCursorSpeed"))) {
531− updateMouseCursorSpeed();
532− }
533− if (changed.contains(QStringLiteral("TouchpadCursorSpeed"))) {
534− updateTouchpadCursorSpeed();
535− }
536− } else if (interface == QLatin1String("com.ubuntu.touch.AccountsService.SecurityPrivacy")) {
537− if (changed.contains(QStringLiteral("StatsWelcomeScreen"))) {
538− updateStatsWelcomeScreen();
539− }
540− } else if (interface == QLatin1String("com.ubuntu.AccountsService.SecurityPrivacy")) {
541− if (changed.contains(QStringLiteral("PasswordDisplayHint"))) {
542− updatePasswordDisplayHint();
543− }
544− if (changed.contains(QStringLiteral("EnableLauncherWhileLocked"))) {
545− updateEnableLauncherWhileLocked();
546− }
547− if (changed.contains(QStringLiteral("EnableIndicatorsWhileLocked"))) {
548− updateEnableIndicatorsWhileLocked();
549− }
550− } else if (interface == QLatin1String("com.ubuntu.location.providers.here.AccountsService")) {
551− if (changed.contains(QStringLiteral("LicenseAccepted"))) {
552− updateHereEnabled();
553− }
554− if (changed.contains(QStringLiteral("LicenseBasePath"))) {
555− updateHereLicensePath();
374+ auto propHash = m_properties.value(interface);
375+ auto i = propHash.constBegin();
376+ while (i != propHash.constEnd()) {
377+ if (changed.contains(i.key())) {
378+ updateProperty(interface, i.key());
556379 }
380+ ++i;
557381 }
558382}
559383
@@ -563,6 +387,20 @@ void AccountsService::onMaybeChanged(const QString &user)
563387 return;
564388 }
565389
566− // Standard properties might have changed
567− updateBackgroundFile();
390+ // Any of the standard properties might have changed!
391+ auto propHash = m_properties.value(IFACE_ACCOUNTS_USER);
392+ auto i = propHash.constBegin();
393+ while (i != propHash.constEnd()) {
394+ updateProperty(IFACE_ACCOUNTS_USER, i.key());
395+ ++i;
396+ }
397+}
398+
399+void AccountsService::refresh(bool async)
400+{
401+ auto i = m_properties.constBegin();
402+ while (i != m_properties.constEnd()) {
403+ updateAllProperties(i.key(), async);
404+ ++i;
405+ }
568406}
plugins/AccountsService/AccountsService.h+34−25
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013, 2015 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -12,16 +12,16 @@
1212 *
1313 * You should have received a copy of the GNU General Public License
1414 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− *
16− * Authors: Michael Terry <michael.terry@canonical.com>
1715 */
1816
1917#ifndef UNITY_ACCOUNTSSERVICE_H
2018#define UNITY_ACCOUNTSSERVICE_H
2119
20+#include <QHash>
2221#include <QObject>
2322#include <QString>
2423#include <QStringList>
24+#include <QVariant>
2525
2626class AccountsServiceDBusAdaptor;
2727class QDBusInterface;
@@ -70,6 +70,8 @@ class AccountsService: public QObject
7070 Q_PROPERTY(bool hereLicensePathValid // qml sees a null string as "", so we use proxy setting for that
7171 READ hereLicensePathValid
7272 NOTIFY hereLicensePathChanged)
73+ Q_PROPERTY(QString realName READ realName WRITE setRealName NOTIFY realNameChanged)
74+ Q_PROPERTY(QString email READ email WRITE setEmail NOTIFY emailChanged)
7375
7476public:
7577 enum PasswordDisplayHint {
@@ -97,6 +99,10 @@ public:
9799 void setHereEnabled(bool enabled);
98100 QString hereLicensePath() const;
99101 bool hereLicensePathValid() const;
102+ QString realName() const;
103+ void setRealName(const QString &realName);
104+ QString email() const;
105+ void setEmail(const QString &email);
100106
101107Q_SIGNALS:
102108 void userChanged();
@@ -110,38 +116,41 @@ Q_SIGNALS:
110116 void failedLoginsChanged();
111117 void hereEnabledChanged();
112118 void hereLicensePathChanged();
119+ void realNameChanged();
120+ void emailChanged();
113121
114122private Q_SLOTS:
115123 void onPropertiesChanged(const QString &user, const QString &interface, const QStringList &changed);
116124 void onMaybeChanged(const QString &user);
117125
118126private:
119− void updateDemoEdges(bool async = true);
120− void updateDemoEdgesCompleted(bool async = true);
121− void updateEnableLauncherWhileLocked(bool async = true);
122− void updateEnableIndicatorsWhileLocked(bool async = true);
123− void updateBackgroundFile(bool async = true);
124− void updateMouseCursorSpeed();
125− void updateTouchpadCursorSpeed();
126− void updateStatsWelcomeScreen(bool async = true);
127− void updatePasswordDisplayHint(bool async = true);
128− void updateFailedLogins(bool async = true);
129− void updateHereEnabled(bool async = true);
130− void updateHereLicensePath(bool async = true);
127+ typedef QVariant (*ProxyConverter)(const QVariant &);
128+
129+ void refresh(bool async);
130+ void registerProperty(const QString &interface, const QString &property, const QString &signal);
131+ void registerProxy(const QString &interface, const QString &property, QDBusInterface *iface, const QString &method, ProxyConverter converter = nullptr);
132+
133+ void updateAllProperties(const QString &interface, bool async);
134+ void updateProperty(const QString &interface, const QString &property);
135+ void updateCache(const QString &interface, const QString &property, const QVariant &value);
131136
137+ void setProperty(const QString &interface, const QString &property, const QVariant &value);
138+ QVariant getProperty(const QString &interface, const QString &property) const;
139+
140+ void emitChangedForProperty(const QString &interface, const QString &property);
141+
142+ struct PropertyInfo {
143+ QVariant value{};
144+ QString signal{};
145+ QDBusInterface *proxyInterface{};
146+ QString proxyMethod{};
147+ ProxyConverter proxyConverter{};
148+ };
149+ typedef QHash< QString, QHash<QString, PropertyInfo> > PropertyHash;
150+ PropertyHash m_properties;
132151 AccountsServiceDBusAdaptor *m_service;
133152 QDBusInterface *m_unityInput;
134153 QString m_user;
135− bool m_demoEdges;
136− QStringList m_demoEdgesCompleted;
137− bool m_enableLauncherWhileLocked;
138− bool m_enableIndicatorsWhileLocked;
139− QString m_backgroundFile;
140− bool m_statsWelcomeScreen;
141− PasswordDisplayHint m_passwordDisplayHint;
142− uint m_failedLogins;
143− bool m_hereEnabled;
144− QString m_hereLicensePath;
145154};
146155
147156#endif
plugins/AccountsService/AccountsServiceDBusAdaptor.cpp+11−4
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013-2016 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -12,8 +12,6 @@
1212 *
1313 * You should have received a copy of the GNU General Public License
1414 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− *
16− * Author: Michael Terry <michael.terry@canonical.com>
1715 */
1816
1917#include "AccountsServiceDBusAdaptor.h"
@@ -37,6 +35,15 @@ AccountsServiceDBusAdaptor::AccountsServiceDBusAdaptor(QObject* parent)
3735 connection, this);
3836}
3937
38+QDBusPendingReply<QVariantMap> AccountsServiceDBusAdaptor::getAllPropertiesAsync(const QString &user, const QString &interface)
39+{
40+ QDBusInterface *iface = getUserInterface(user);
41+ if (iface != nullptr && iface->isValid()) {
42+ return iface->asyncCall(QStringLiteral("GetAll"), interface);
43+ }
44+ return QDBusPendingReply<QVariantMap>(QDBusMessage::createError(QDBusError::Other, QStringLiteral("Invalid Interface")));
45+}
46+
4047QDBusPendingReply<QVariant> AccountsServiceDBusAdaptor::getUserPropertyAsync(const QString &user, const QString &interface, const QString &property)
4148{
4249 QDBusInterface *iface = getUserInterface(user);
@@ -80,7 +87,7 @@ void AccountsServiceDBusAdaptor::maybeChangedSlot()
8087 m_ignoreNextChanged = false;
8188}
8289
83−QString AccountsServiceDBusAdaptor::getUserForPath(const QString &path)
90+QString AccountsServiceDBusAdaptor::getUserForPath(const QString &path) const
8491{
8592 QMap<QString, QDBusInterface *>::const_iterator i;
8693 for (i = m_users.constBegin(); i != m_users.constEnd(); ++i) {
plugins/AccountsService/AccountsServiceDBusAdaptor.h+5−6
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013-2016 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -12,8 +12,6 @@
1212 *
1313 * You should have received a copy of the GNU General Public License
1414 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− *
16− * Authors: Michael Terry <michael.terry@canonical.com>
1715 */
1816
1917#ifndef UNITY_ACCOUNTSSERVICEDBUSADAPTOR_H
@@ -35,8 +33,9 @@ public:
3533 explicit AccountsServiceDBusAdaptor(QObject *parent = 0);
3634 ~AccountsServiceDBusAdaptor() = default;
3735
38− Q_INVOKABLE QDBusPendingReply<QVariant> getUserPropertyAsync(const QString &user, const QString &interface, const QString &property);
39− Q_INVOKABLE QDBusPendingCall setUserPropertyAsync(const QString &user, const QString &interface, const QString &property, const QVariant &value);
36+ QDBusPendingReply<QVariantMap> getAllPropertiesAsync(const QString &user, const QString &interface);
37+ QDBusPendingReply<QVariant> getUserPropertyAsync(const QString &user, const QString &interface, const QString &property);
38+ QDBusPendingCall setUserPropertyAsync(const QString &user, const QString &interface, const QString &property, const QVariant &value);
4039
4140Q_SIGNALS:
4241 void propertiesChanged(const QString &user, const QString &interface, const QStringList &changed);
@@ -48,7 +47,7 @@ private Q_SLOTS:
4847
4948private:
5049 QDBusInterface *getUserInterface(const QString &user);
51− QString getUserForPath(const QString &path);
50+ QString getUserForPath(const QString &path) const;
5251
5352 QDBusInterface *m_accountsManager;
5453 QMap<QString, QDBusInterface *> m_users;
plugins/Cursor/Cursor.qmltypesnew file+78
@@ -0,0 +1,78 @@
1+import QtQuick.tooling 1.1
2+
3+// This file describes the plugin-supplied types contained in the library.
4+// It is used for QML tooling purposes only.
5+//
6+// This file was auto-generated by:
7+// 'qmlplugindump -notrelocatable Cursor 1.0 plugins'
8+
9+Module {
10+ Component {
11+ name: "MirMousePointerInterface"
12+ defaultProperty: "data"
13+ prototype: "QQuickItem"
14+ Property { name: "cursorName"; type: "string"; isReadonly: true }
15+ Property { name: "themeName"; type: "string"; isReadonly: true }
16+ Property { name: "hotspotX"; type: "double"; isReadonly: true }
17+ Property { name: "hotspotY"; type: "double"; isReadonly: true }
18+ Signal {
19+ name: "cursorNameChanged"
20+ Parameter { name: "name"; type: "string" }
21+ }
22+ Signal {
23+ name: "themeNameChanged"
24+ Parameter { name: "name"; type: "string" }
25+ }
26+ Signal {
27+ name: "hotspotXChanged"
28+ Parameter { name: "value"; type: "double" }
29+ }
30+ Signal {
31+ name: "hotspotYChanged"
32+ Parameter { name: "value"; type: "double" }
33+ }
34+ Method {
35+ name: "handleMouseEvent"
36+ Parameter { name: "timestamp"; type: "ulong" }
37+ Parameter { name: "movement"; type: "QPointF" }
38+ Parameter { name: "buttons"; type: "Qt::MouseButtons" }
39+ Parameter { name: "modifiers"; type: "Qt::KeyboardModifiers" }
40+ }
41+ Method {
42+ name: "handleWheelEvent"
43+ Parameter { name: "timestamp"; type: "ulong" }
44+ Parameter { name: "angleDelta"; type: "QPoint" }
45+ Parameter { name: "modifiers"; type: "Qt::KeyboardModifiers" }
46+ }
47+ }
48+ Component {
49+ name: "MousePointer"
50+ defaultProperty: "data"
51+ prototype: "MirMousePointerInterface"
52+ exports: ["Cursor/MousePointer 1.0"]
53+ exportMetaObjectRevisions: [0]
54+ Signal {
55+ name: "pushedLeftBoundary"
56+ Parameter { name: "amount"; type: "double" }
57+ Parameter { name: "buttons"; type: "Qt::MouseButtons" }
58+ }
59+ Signal {
60+ name: "pushedRightBoundary"
61+ Parameter { name: "amount"; type: "double" }
62+ Parameter { name: "buttons"; type: "Qt::MouseButtons" }
63+ }
64+ Method {
65+ name: "handleMouseEvent"
66+ Parameter { name: "timestamp"; type: "ulong" }
67+ Parameter { name: "movement"; type: "QPointF" }
68+ Parameter { name: "buttons"; type: "Qt::MouseButtons" }
69+ Parameter { name: "modifiers"; type: "Qt::KeyboardModifiers" }
70+ }
71+ Method {
72+ name: "handleWheelEvent"
73+ Parameter { name: "timestamp"; type: "ulong" }
74+ Parameter { name: "angleDelta"; type: "QPoint" }
75+ Parameter { name: "modifiers"; type: "Qt::KeyboardModifiers" }
76+ }
77+ }
78+}
plugins/Dash/AudioProgressBar.qml+1
@@ -31,6 +31,7 @@ Item {
3131 anchors { left: parent.left; right: parent.right }
3232 height: units.dp(6)
3333 source: "graphics/music_progress_bg.png"
34+ asynchronous: true
3435 sourceSize.width: width
3536 sourceSize.height: height
3637 }
plugins/Dash/CardCreator.js+69−34
@@ -18,12 +18,13 @@
1818
1919// %1 is the template["card-background"]["elements"][0]
2020// %2 is the template["card-background"]["elements"][1]
21−// %3 is the template["card-background"] string
21+// %3 is whether the loader should be asynchronous or not
22+// %4 is the template["card-background"] string
2223var kBackgroundLoaderCode = 'Loader {\n\
2324 id: backgroundLoader; \n\
2425 objectName: "backgroundLoader"; \n\
2526 anchors.fill: parent; \n\
26− asynchronous: root.asynchronous; \n\
27+ asynchronous: %3; \n\
2728 visible: status == Loader.Ready; \n\
2829 sourceComponent: UbuntuShape { \n\
2930 objectName: "background"; \n\
@@ -46,7 +47,7 @@ var kBackgroundLoaderCode = 'Loader {\n\
4647 objectName: "backgroundImage"; \n\
4748 source: { \n\
4849 if (cardData && typeof cardData["background"] === "string") return cardData["background"]; \n\
49− else return %3; \n\
50+ else return %4; \n\
5051 } \n\
5152 } \n\
5253 function getColor(index) { \n\
@@ -63,8 +64,9 @@ var kBackgroundLoaderCode = 'Loader {\n\
6364// %3 is used as image height
6465// %4 is used for artShapeSource.hideSource and inner Loader visible
6566// %5 is used as aspect ratio fallback
66−// %6 is injected as code to artImage
67−// %7 is used as image fallback
67+// %6 is whether the loader should be asynchronous or not
68+// %7 is injected as code to artImage
69+// %8 is used as image fallback
6870var kArtShapeHolderCode = 'Item { \n\
6971 id: artShapeHolder; \n\
7072 height: root.fixedArtShapeSize.height > 0 ? root.fixedArtShapeSize.height : artShapeLoader.height; \n\
@@ -73,9 +75,9 @@ var kArtShapeHolderCode = 'Item { \n\
7375 Loader { \n\
7476 id: artShapeLoader; \n\
7577 objectName: "artShapeLoader"; \n\
76− readonly property string cardArt: cardData && cardData["art"] || %7; \n\
78+ readonly property string cardArt: cardData && cardData["art"] || %8; \n\
7779 active: cardArt != ""; \n\
78− asynchronous: root.asynchronous; \n\
80+ asynchronous: %6; \n\
7981 visible: status == Loader.Ready; \n\
8082 sourceComponent: Item { \n\
8183 id: artShape; \n\
@@ -132,10 +134,10 @@ var kArtShapeHolderCode = 'Item { \n\
132134 id: artImage; \n\
133135 objectName: "artImage"; \n\
134136 source: artShapeLoader.cardArt; \n\
135− asynchronous: root.asynchronous; \n\
137+ asynchronous: %6; \n\
136138 width: %2; \n\
137139 height: %3; \n\
138− %6 \n\
140+ %7 \n\
139141 } \n\
140142 } \n\
141143 } \n\
@@ -144,6 +146,7 @@ var kArtShapeHolderCode = 'Item { \n\
144146// %1 is anchors.fill
145147// %2 is width
146148// %3 is height
149+// %4 is whether the icon should be asynchronous or not
147150var kAudioButtonCode = 'AbstractButton { \n\
148151 id: audioButton; \n\
149152 anchors.fill: %1; \n\
@@ -169,6 +172,7 @@ var kAudioButtonCode = 'AbstractButton { \n\
169172 opacity: 0.9; \n\
170173 name: DashAudioPlayer.playing && AudioUrlComparer.compare(parent.source, DashAudioPlayer.currentSource) ? "media-playback-pause" : "media-playback-start"; \n\
171174 color: "white"; \n\
175+ asynchronous: %4; \n\
172176 } \n\
173177 onClicked: { \n\
174178 if (AudioUrlComparer.compare(source, DashAudioPlayer.currentSource)) { \n\
@@ -187,12 +191,14 @@ var kAudioButtonCode = 'AbstractButton { \n\
187191 } \n\
188192 }';
189193
194+// %1 is whether the loader should be asynchronous or not
195+// %2 is the header height code
190196var kOverlayLoaderCode = 'Loader { \n\
191197 id: overlayLoader; \n\
192− readonly property real overlayHeight: (fixedHeaderHeight > 0 ? fixedHeaderHeight : headerHeight) + units.gu(2); \n\
198+ readonly property real overlayHeight: %2 + units.gu(2); \n\
193199 anchors.fill: artShapeHolder; \n\
194200 active: artShapeLoader.active && artShapeLoader.item && artShapeLoader.item.image.status === Image.Ready || false; \n\
195− asynchronous: root.asynchronous; \n\
201+ asynchronous: %1; \n\
196202 visible: showHeader && status == Loader.Ready; \n\
197203 sourceComponent: UbuntuShapeOverlay { \n\
198204 id: overlay; \n\
@@ -211,17 +217,19 @@ function kHeaderRowCodeGenerator() {
211217 objectName: "outerRow"; \n\
212218 property real margins: units.gu(1); \n\
213219 spacing: margins; \n\
214− height: root.fixedHeaderHeight != -1 ? root.fixedHeaderHeight : implicitHeight; \n\
220+ %2\
215221 anchors { %1 } \n\
216222 anchors.right: parent.right; \n\
217223 anchors.margins: margins; \n\
218224 anchors.rightMargin: 0; \n\
219225 data: [ \n\
220− %2 \n\
226+ %3 \n\
221227 ] \n\
222228 }\n';
223229 var args = Array.prototype.slice.call(arguments);
224− var code = kHeaderRowCodeTemplate.arg(args.shift()).arg(args.join(',\n'));
230+ var isCardTool = args.shift();
231+ var heightCode = isCardTool ? "" : "height: root.fixedHeaderHeight; \n";
232+ var code = kHeaderRowCodeTemplate.arg(args.shift()).arg(heightCode).arg(args.join(',\n'));
225233 return code;
226234}
227235
@@ -242,10 +250,11 @@ function kHeaderContainerCodeGenerator() {
242250}
243251
244252// %1 is used as anchors of mascotShapeLoader
253+// %2 is whether the loader should be asynchronous or not
245254var kMascotShapeLoaderCode = 'Loader { \n\
246255 id: mascotShapeLoader; \n\
247256 objectName: "mascotShapeLoader"; \n\
248− asynchronous: root.asynchronous; \n\
257+ asynchronous: %2; \n\
249258 active: mascotImage.status === Image.Ready; \n\
250259 visible: showHeader && active && status == Loader.Ready; \n\
251260 width: units.gu(6); \n\
@@ -275,6 +284,7 @@ var kMascotImageCode = 'CroppedImageMinimumSourceSize { \n\
275284// %2 is used as color of titleLabel
276285// %3 is used as extra condition for visible of titleLabel
277286// %4 is used as title width
287+// %5 is used as horizontal alignment
278288var kTitleLabelCode = 'Label { \n\
279289 id: titleLabel; \n\
280290 objectName: "titleLabel"; \n\
@@ -289,7 +299,7 @@ var kTitleLabelCode = 'Label { \n\
289299 width: %4; \n\
290300 text: root.title; \n\
291301 font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal; \n\
292− horizontalAlignment: root.titleAlignment; \n\
302+ horizontalAlignment: %5; \n\
293303 }\n';
294304
295305// %1 is used as extra anchors of emblemIcon
@@ -398,7 +408,7 @@ function sanitizeColor(colorString) {
398408 return colorString;
399409}
400410
401−function cardString(template, components) {
411+function cardString(template, components, isCardTool) {
402412 var code;
403413
404414 var templateInteractive = (template == null ? true : (template["non-interactive"] !== undefined ? !template["non-interactive"] : true)) ? "true" : "false";
@@ -410,16 +420,16 @@ function cardString(template, components) {
410420 property string backgroundShapeStyle: "inset"; \n\
411421 property real fontScale: 1.0; \n\
412422 property var scopeStyle: null; \n\
413− property int titleAlignment: Text.AlignLeft; \n\
414− property int fixedHeaderHeight: -1; \n\
423+ %2\
415424 property size fixedArtShapeSize: Qt.size(-1, -1); \n\
416425 readonly property string title: cardData && cardData["title"] || ""; \n\
417− property bool asynchronous: true; \n\
418426 property bool showHeader: true; \n\
419427 implicitWidth: childrenRect.width; \n\
420428 enabled: %1; \n\
421429 \n'.arg(templateInteractive);
422430
431+ code = code.arg(isCardTool ? "" : "property int fixedHeaderHeight: -1; \n");
432+
423433 var hasArt = components["art"] && components["art"]["field"] || false;
424434 var hasSummary = components["summary"] || false;
425435 var isConciergeMode = components["art"] && components["art"]["conciergeMode"] || false;
@@ -435,6 +445,7 @@ function cardString(template, components) {
435445 var hasHeaderRow = hasMascot && hasTitle;
436446 var hasAttributes = hasTitle && components["attributes"] && components["attributes"]["field"] || false;
437447 var isAudio = template["quick-preview-type"] === "audio";
448+ var asynchronous = isCardTool ? "false" : "true";
438449
439450 if (isAudio) {
440451 // For now we only support audio cards with [optional] art, title, subtitle
@@ -468,7 +479,7 @@ function cardString(template, components) {
468479 backgroundElements1 = '"%1"'.arg(element1);
469480 }
470481 }
471− code += kBackgroundLoaderCode.arg(backgroundElements0).arg(backgroundElements1).arg(templateCardBackground);
482+ code += kBackgroundLoaderCode.arg(backgroundElements0).arg(backgroundElements1).arg(asynchronous).arg(templateCardBackground);
472483 }
473484
474485 if (hasArt) {
@@ -497,23 +508,31 @@ function cardString(template, components) {
497508 if (isNaN(aspectRatio)) {
498509 aspectRatio = 1;
499510 }
500− var fallback = components["art"] && components["art"]["fallback"] || "";
511+ var fallback = !isCardTool && components["art"] && components["art"]["fallback"] || "";
501512 fallback = encodeURI(fallback);
502513 var fallbackStatusCode = "";
503514 var fallbackURICode = '""';
504515 if (fallback !== "") {
505516 // fallbackStatusCode has %6 in it because we want to substitute it for fallbackURICode
506− // which in kArtShapeHolderCode is %7
507− fallbackStatusCode += 'onStatusChanged: if (status === Image.Error) source = %7;';
517+ // which in kArtShapeHolderCode is %8
518+ fallbackStatusCode += 'onStatusChanged: if (status === Image.Error) source = %8;';
508519 fallbackURICode = 'decodeURI("%1")'.arg(fallback);
509520 }
510− code += kArtShapeHolderCode.arg(artAnchors).arg(widthCode).arg(heightCode).arg(isConciergeMode ? "false" : "true").arg(aspectRatio).arg(fallbackStatusCode).arg(fallbackURICode);
521+ code += kArtShapeHolderCode.arg(artAnchors)
522+ .arg(widthCode)
523+ .arg(heightCode)
524+ .arg(isConciergeMode ? "false" : "true")
525+ .arg(aspectRatio)
526+ .arg(asynchronous)
527+ .arg(fallbackStatusCode)
528+ .arg(fallbackURICode);
511529 } else {
512530 code += 'readonly property size artShapeSize: Qt.size(-1, -1);\n'
513531 }
514532
515533 if (headerAsOverlay) {
516− code += kOverlayLoaderCode;
534+ var headerHeightCode = isCardTool ? "headerHeight" : "root.fixedHeaderHeight";
535+ code += kOverlayLoaderCode.arg(asynchronous).arg(headerHeightCode);
517536 }
518537
519538 var headerVerticalAnchors;
@@ -595,11 +614,11 @@ function cardString(template, components) {
595614 }
596615
597616 if (useMascotShape) {
598− mascotShapeCode = kMascotShapeLoaderCode.arg(mascotAnchors);
617+ mascotShapeCode = kMascotShapeLoaderCode.arg(mascotAnchors).arg(asynchronous);
599618 }
600619
601620 var mascotImageVisible = useMascotShape ? 'false' : 'showHeader';
602− var fallback = components["mascot"] && components["mascot"]["fallback"] || "";
621+ var fallback = !isCardTool && components["mascot"] && components["mascot"]["fallback"] || "";
603622 fallback = encodeURI(fallback);
604623 var fallbackStatusCode = "";
605624 var fallbackURICode = '""';
@@ -715,9 +734,24 @@ function cardString(template, components) {
715734 }
716735 }
717736
737+ var titleAlignment = "Text.AlignHCenter";
738+ if (template["card-layout"] === "horizontal"
739+ || typeof components["title"] !== "object"
740+ || components["title"]["align"] === "left") titleAlignment = "Text.AlignLeft";
741+ var keys = ["mascot", "emblem", "subtitle", "attributes", "summary"];
742+ for (var key in keys) {
743+ key = keys[key];
744+ try {
745+ if (typeof components[key] === "string"
746+ || typeof components[key]["field"] === "string") titleAlignment = "Text.AlignLeft";
747+ } catch (e) {
748+ continue;
749+ }
750+ }
751+
718752 // code for different elements
719753 var titleLabelVisibleExtra = (headerAsOverlay ? '&& overlayLoader.active': '');
720− var titleCode = kTitleLabelCode.arg(titleAnchors).arg(titleColor).arg(titleLabelVisibleExtra).arg(titleWidth);
754+ var titleCode = kTitleLabelCode.arg(titleAnchors).arg(titleColor).arg(titleLabelVisibleExtra).arg(titleWidth).arg(titleAlignment);
721755 var subtitleCode;
722756 var attributesCode;
723757
@@ -759,7 +793,7 @@ function cardString(template, components) {
759793 if (mascotShapeCode != '') {
760794 rowCode.unshift(mascotShapeCode);
761795 }
762− code += kHeaderRowCodeGenerator(headerVerticalAnchors + headerLeftAnchor, rowCode)
796+ code += kHeaderRowCodeGenerator(isCardTool, headerVerticalAnchors + headerLeftAnchor, rowCode)
763797 } else {
764798 code += mascotShapeCode + mascotCode + titleSubtitleCode;
765799 }
@@ -783,9 +817,10 @@ function cardString(template, components) {
783817 } else {
784818 audioButtonAnchorsFill = 'undefined';
785819 audioButtonWidth = 'height';
786− audioButtonHeight = '(root.fixedHeaderHeight > 0 ? root.fixedHeaderHeight : headerHeight) + 2 * units.gu(1)';
820+ audioButtonHeight = isCardTool ? 'headerHeight + 2 * units.gu(1)'
821+ : 'root.fixedHeaderHeight + 2 * units.gu(1)';
787822 }
788− code += kAudioButtonCode.arg(audioButtonAnchorsFill).arg(audioButtonWidth).arg(audioButtonHeight);
823+ code += kAudioButtonCode.arg(audioButtonAnchorsFill).arg(audioButtonWidth).arg(audioButtonHeight).arg(asynchronous);
789824 }
790825
791826 if (hasSummary) {
@@ -854,13 +889,13 @@ function cardString(template, components) {
854889 return code;
855890}
856891
857−function createCardComponent(parent, template, components, identifier) {
892+function createCardComponent(parent, template, components, isCardTool, identifier) {
858893 var imports = 'import QtQuick 2.4; \n\
859894 import Ubuntu.Components 1.3; \n\
860895 import Ubuntu.Settings.Components 0.1; \n\
861896 import Dash 0.1;\n\
862897 import Utils 0.1;\n';
863− var card = cardString(template, components);
898+ var card = cardString(template, components, isCardTool);
864899 var code = imports + 'Component {\n' + card + '}\n';
865900
866901 try {
plugins/Dash/CardCreatorCache.qml+3−3
@@ -23,16 +23,16 @@ QtObject {
2323
2424 property var cache: new Object();
2525
26− function getCardComponent(template, components) {
26+ function getCardComponent(template, components, isCardTool) {
2727 if (template === undefined || components === undefined)
2828 return undefined;
2929
3030 var tString = JSON.stringify(template);
3131 var cString = JSON.stringify(components);
32− var allString = tString + cString;
32+ var allString = tString + cString + isCardTool;
3333 var component = cache[allString];
3434 if (component === undefined) {
35− component = CardCreator.createCardComponent(root, template, components, allString);
35+ component = CardCreator.createCardComponent(root, template, components, isCardTool, allString);
3636 cache[allString] = component;
3737 }
3838 return component;
plugins/Dash/plugin.cpp+6−6
@@ -28,12 +28,12 @@
2828static QUrl oauthCleanedUrl(QUrl u)
2929{
3030 QUrlQuery q(u);
31− q.removeQueryItem("oauth_nonce");
32− q.removeQueryItem("oauth_timestamp");
33− q.removeQueryItem("oauth_consumer_key");
34− q.removeQueryItem("oauth_signature_method");
35− q.removeQueryItem("oauth_version");
36− q.removeQueryItem("oauth_signature");
31+ q.removeQueryItem(QStringLiteral("oauth_nonce"));
32+ q.removeQueryItem(QStringLiteral("oauth_timestamp"));
33+ q.removeQueryItem(QStringLiteral("oauth_consumer_key"));
34+ q.removeQueryItem(QStringLiteral("oauth_signature_method"));
35+ q.removeQueryItem(QStringLiteral("oauth_version"));
36+ q.removeQueryItem(QStringLiteral("oauth_signature"));
3737 u.setQuery(q);
3838 return u;
3939}
plugins/GlobalShortcut/GlobalShortcut.qmltypesnew file+31
@@ -0,0 +1,31 @@
1+import QtQuick.tooling 1.1
2+
3+// This file describes the plugin-supplied types contained in the library.
4+// It is used for QML tooling purposes only.
5+//
6+// This file was auto-generated by:
7+// 'qmlplugindump -notrelocatable GlobalShortcut 1.0 plugins'
8+
9+Module {
10+ Component {
11+ name: "GlobalShortcut"
12+ defaultProperty: "data"
13+ prototype: "QQuickItem"
14+ exports: ["GlobalShortcut/GlobalShortcut 1.0"]
15+ exportMetaObjectRevisions: [0]
16+ Property { name: "shortcut"; type: "QVariant" }
17+ Property { name: "active"; type: "bool" }
18+ Signal {
19+ name: "shortcutChanged"
20+ Parameter { name: "shortcut"; type: "QVariant" }
21+ }
22+ Signal {
23+ name: "triggered"
24+ Parameter { name: "shortcut"; type: "string" }
25+ }
26+ Signal {
27+ name: "activeChanged"
28+ Parameter { name: "active"; type: "bool" }
29+ }
30+ }
31+}
plugins/GlobalShortcut/globalshortcutregistry.cpp+4−4
@@ -35,13 +35,13 @@ GlobalShortcutList GlobalShortcutRegistry::shortcuts() const
3535
3636bool GlobalShortcutRegistry::hasShortcut(const QVariant &seq) const
3737{
38− return m_shortcuts.keys().contains(seq);
38+ return m_shortcuts.contains(seq);
3939}
4040
4141void GlobalShortcutRegistry::addShortcut(const QVariant &seq, GlobalShortcut *sc)
4242{
4343 if (sc) {
44− if (!m_shortcuts.keys().contains(seq)) { // create a new entry
44+ if (!m_shortcuts.contains(seq)) { // create a new entry
4545 m_shortcuts.insert(seq, {sc});
4646 } else { // append to an existing one
4747 auto shortcuts = m_shortcuts[seq];
@@ -75,8 +75,8 @@ bool GlobalShortcutRegistry::eventFilter(QObject *obj, QEvent *event)
7575
7676 if (event->type() == QEvent::KeyPress) {
7777 QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
78− QKeySequence seq = QKeySequence(keyEvent->key() + keyEvent->modifiers());
79− if (m_shortcuts.keys().contains(seq)) {
78+ int seq = keyEvent->key() + keyEvent->modifiers();
79+ if (m_shortcuts.contains(seq)) {
8080 const auto shortcuts = m_shortcuts.value(seq);
8181 Q_FOREACH(const auto &shortcut, shortcuts) {
8282 if (shortcut) {
plugins/IntegratedLightDM/liblightdm/CMakeLists.txt+16−3
@@ -5,13 +5,16 @@ set(LibLightDM_SOURCES
55 UsersModel.cpp
66 GreeterPrivate.cpp
77 UsersModelPrivate.cpp
8+ ${CMAKE_SOURCE_DIR}/plugins/AccountsService/AccountsServiceDBusAdaptor.cpp
89 ${CMAKE_SOURCE_DIR}/plugins/Utils/qvariantlistmodel.cpp
910 )
1011
1112add_library(integratedLightDM STATIC ${LibLightDM_SOURCES})
13+add_library(integratedLightDMSession STATIC ${LibLightDM_SOURCES})
1214add_library(MockLightDM-demo-shared SHARED ${LibLightDM_SOURCES})
1315
1416include_directories(
17+ ${CMAKE_SOURCE_DIR}/plugins/AccountsService
1518 ${CMAKE_CURRENT_BINARY_DIR}
1619 ${GLIB_INCLUDE_DIRS}
1720 ${LIBUSERMETRICSOUTPUT_INCLUDE_DIRS}
@@ -22,17 +25,27 @@ target_link_libraries(integratedLightDM
2225 ${LIBUSERMETRICSOUTPUT_LDFLAGS}
2326 -lpam
2427)
28+target_link_libraries(integratedLightDMSession
29+ ${GLIB_LIBRARIES}
30+ ${LIBUSERMETRICSOUTPUT_LDFLAGS}
31+ -lpam
32+)
2533target_link_libraries(MockLightDM-demo-shared
2634 ${GLIB_LIBRARIES}
2735 ${LIBUSERMETRICSOUTPUT_LDFLAGS}
2836 -lpam
2937)
3038
31−qt5_use_modules(integratedLightDM Concurrent Gui)
32−qt5_use_modules(MockLightDM-demo-shared Concurrent Gui)
39+qt5_use_modules(integratedLightDM Concurrent DBus Gui)
40+qt5_use_modules(integratedLightDMSession Concurrent DBus Gui)
41+qt5_use_modules(MockLightDM-demo-shared Concurrent DBus Gui)
3342
34−set_target_properties(integratedLightDM PROPERTIES COMPILE_FLAGS -fPIC)
43+set_target_properties(integratedLightDM PROPERTIES
44+ COMPILE_FLAGS "-DSM_BUSNAME=systemBus -fPIC")
45+set_target_properties(integratedLightDMSession PROPERTIES
46+ COMPILE_FLAGS "-DSM_BUSNAME=sessionBus -fPIC")
3547set_target_properties(MockLightDM-demo-shared PROPERTIES
48+ COMPILE_FLAGS "-DSM_BUSNAME=systemBus"
3649 OUTPUT_NAME lightdm-qt5-2)
3750
3851install(TARGETS MockLightDM-demo-shared
plugins/IntegratedLightDM/liblightdm/UsersModel.cpp+4−18
@@ -54,24 +54,10 @@ UsersModel::UsersModel(QObject *parent) :
5454 roles[ImagePathRole] = "imagePath";
5555 setRoleNames(roles);
5656
57− // Now modify our mock user backgrounds
58− QDir bgdir = QDir(QStringLiteral("/usr/share/demo-assets/shell/backgrounds/"));
59− QStringList backgrounds = bgdir.entryList(QDir::Files | QDir::NoDotAndDotDot);
60−
61− for (int i = 0, j = 0; i < d->entries.size(); i++) {
62− Entry &entry = d->entries[i];
63− if (entry.background.isNull() && !backgrounds.isEmpty()) {
64− entry.background = bgdir.filePath(backgrounds[j++]);
65− if (j >= backgrounds.length()) {
66− j = 0;
67− }
68− }
69− }
70−}
71−
72−UsersModel::~UsersModel()
73−{
74− delete d_ptr;
57+ connect(d_ptr, &UsersModelPrivate::dataChanged, this, [this](int i) {
58+ QModelIndex index = createIndex(i, 0);
59+ Q_EMIT dataChanged(index, index);
60+ });
7561}
7662
7763int UsersModel::rowCount(const QModelIndex &parent) const
plugins/IntegratedLightDM/liblightdm/UsersModel.h+1−1
@@ -41,7 +41,7 @@ class Q_DECL_EXPORT UsersModel : public QAbstractListModel
4141
4242public:
4343 explicit UsersModel(QObject *parent = 0);
44− virtual ~UsersModel();
44+ virtual ~UsersModel() = default;
4545
4646 enum UserModelRoles {NameRole = Qt::UserRole,
4747 RealNameRole,
plugins/IntegratedLightDM/liblightdm/UsersModelPrivate.cpp+43−9
@@ -18,7 +18,11 @@
1818
1919#include "UsersModelPrivate.h"
2020
21+#include "AccountsServiceDBusAdaptor.h"
22+#include "UsersModel.h"
23+
2124#include <glib.h>
25+#include <QDebug>
2226#include <QDir>
2327#include <QSettings>
2428#include <QStringList>
@@ -27,7 +31,9 @@ namespace QLightDM
2731{
2832
2933UsersModelPrivate::UsersModelPrivate(UsersModel* parent)
30− : q_ptr(parent)
34+ : QObject(parent),
35+ q_ptr(parent),
36+ m_service(new AccountsServiceDBusAdaptor(this))
3137{
3238 QFileInfo demoFile(QDir::homePath() + "/.unity8-greeter-demo");
3339 QString currentUser = g_get_user_name();
@@ -43,15 +49,43 @@ UsersModelPrivate::UsersModelPrivate(UsersModel* parent)
4349 entries.append({user, name, 0, 0, false, false, 0, 0});
4450 }
4551 } else {
46− // If we were using the actual liblightdm, we could just ask it
47− // for the user's real name. But we aren't. We *should* ask
48− // AccountsService for the real name, like liblightdm does internally,
49− // but this is close enough since AS and passwd are always in sync.
50− QString realName = QString::fromUtf8(g_get_real_name()); // gets name from passwd entry
51− if (realName == QStringLiteral("Unknown")) { // glib doesn't translate this string
52− realName.clear();
52+ entries.append({currentUser, 0, 0, 0, false, false, 0, 0});
53+
54+ connect(m_service, &AccountsServiceDBusAdaptor::maybeChanged,
55+ this, [this](const QString &user) {
56+ if (user == entries[0].username) {
57+ updateName(true);
58+ }
59+ });
60+ updateName(false);
61+ }
62+}
63+
64+void UsersModelPrivate::updateName(bool async)
65+{
66+ auto pendingReply = m_service->getUserPropertyAsync(entries[0].username,
67+ QStringLiteral("org.freedesktop.Accounts.User"),
68+ QStringLiteral("RealName"));
69+ auto *watcher = new QDBusPendingCallWatcher(pendingReply, this);
70+
71+ connect(watcher, &QDBusPendingCallWatcher::finished,
72+ this, [this](QDBusPendingCallWatcher* watcher) {
73+
74+ QDBusPendingReply<QVariant> reply = *watcher;
75+ watcher->deleteLater();
76+ if (reply.isError()) {
77+ qWarning() << "Failed to get 'RealName' property - " << reply.error().message();
78+ return;
79+ }
80+
81+ const QString realName = reply.value().toString();
82+ if (entries[0].real_name != realName) {
83+ entries[0].real_name = realName;
84+ Q_EMIT dataChanged(0);
5385 }
54− entries.append({currentUser, realName, 0, 0, false, false, 0, 0});
86+ });
87+ if (!async) {
88+ watcher->waitForFinished();
5589 }
5690}
5791
plugins/IntegratedLightDM/liblightdm/UsersModelPrivate.h+15−3
@@ -19,8 +19,11 @@
1919#ifndef UNITY_MOCK_USERSMODEL_PRIVATE_H
2020#define UNITY_MOCK_USERSMODEL_PRIVATE_H
2121
22−#include <QtCore/QList>
23−#include <QtCore/QString>
22+#include <QList>
23+#include <QObject>
24+#include <QString>
25+
26+class AccountsServiceDBusAdaptor;
2427
2528namespace QLightDM
2629{
@@ -39,19 +42,28 @@ public:
3942 QString infographic;
4043};
4144
42−class UsersModelPrivate
45+class UsersModelPrivate : public QObject
4346{
47+ Q_OBJECT
48+
4449public:
4550 explicit UsersModelPrivate(UsersModel *parent = 0);
4651 virtual ~UsersModelPrivate() = default;
4752
4853 QList<Entry> entries;
4954
55+Q_SIGNALS:
56+ void dataChanged(int);
57+
5058protected:
5159 UsersModel * const q_ptr;
5260
5361private:
5462 Q_DECLARE_PUBLIC(UsersModel)
63+
64+ void updateName(bool async);
65+
66+ AccountsServiceDBusAdaptor *m_service;
5567};
5668
5769}
plugins/Ubuntu/Gestures/CMakeLists.txt+1
@@ -11,6 +11,7 @@ set(UbuntuGesturesQml_SOURCES
1111 PressedOutsideNotifier.cpp
1212 TouchDispatcher.cpp
1313 TouchGate.cpp
14+ TouchGestureArea.cpp
1415)
1516
1617add_definitions(-DUBUNTUGESTURESQML_LIBRARY)
plugins/Ubuntu/Gestures/DirectionalDragArea.cpp+3
@@ -640,6 +640,9 @@ void DirectionalDragAreaPrivate::setStatus(Status newStatus)
640640 Q_EMIT q->pressedChanged(true);
641641 break;
642642 case Recognized:
643+ if (oldStatus == WaitingForTouch) { // for immediate recognition
644+ Q_EMIT q->pressedChanged(true);
645+ }
643646 Q_EMIT q->draggingChanged(true);
644647 break;
645648 default:
plugins/Ubuntu/Gestures/Gestures.qmltypes+86−45
@@ -34,7 +34,8 @@ Module {
3434 "Leftwards": 1,
3535 "Downwards": 2,
3636 "Upwards": 3,
37− "Horizontal": 4
37+ "Horizontal": 4,
38+ "Vertical": 5
3839 }
3940 }
4041 Method {
@@ -59,14 +60,6 @@ Module {
5960 prototype: "QQuickItem"
6061 exports: ["Ubuntu.Gestures/DirectionalDragArea 0.1"]
6162 exportMetaObjectRevisions: [0]
62− Enum {
63− name: "Status"
64− values: {
65− "WaitingForTouch": 0,
66− "Undecided": 1,
67− "Recognized": 2
68− }
69− }
7063 Property { name: "direction"; type: "Direction::Type" }
7164 Property { name: "distance"; type: "double"; isReadonly: true }
7265 Property { name: "sceneDistance"; type: "double"; isReadonly: true }
@@ -74,24 +67,19 @@ Module {
7467 Property { name: "touchY"; type: "double"; isReadonly: true }
7568 Property { name: "touchSceneX"; type: "double"; isReadonly: true }
7669 Property { name: "touchSceneY"; type: "double"; isReadonly: true }
77− Property { name: "status"; type: "Status"; isReadonly: true }
7870 Property { name: "dragging"; type: "bool"; isReadonly: true }
79− Property { name: "maxDeviation"; type: "double" }
80− Property { name: "wideningAngle"; type: "double" }
81− Property { name: "distanceThreshold"; type: "double" }
82− Property { name: "minSpeed"; type: "double" }
83− Property { name: "maxSilenceTime"; type: "int" }
84− Property { name: "compositionTime"; type: "int" }
71+ Property { name: "pressed"; type: "bool"; isReadonly: true }
72+ Property { name: "immediateRecognition"; type: "bool" }
8573 Signal {
8674 name: "directionChanged"
8775 Parameter { name: "direction"; type: "Direction::Type" }
8876 }
8977 Signal {
90− name: "statusChanged"
91− Parameter { name: "value"; type: "Status" }
78+ name: "draggingChanged"
79+ Parameter { name: "value"; type: "bool" }
9280 }
9381 Signal {
94− name: "draggingChanged"
82+ name: "pressedChanged"
9583 Parameter { name: "value"; type: "bool" }
9684 }
9785 Signal {
@@ -102,30 +90,6 @@ Module {
10290 name: "sceneDistanceChanged"
10391 Parameter { name: "value"; type: "double" }
10492 }
105− Signal {
106− name: "maxDeviationChanged"
107− Parameter { name: "value"; type: "double" }
108− }
109− Signal {
110− name: "wideningAngleChanged"
111− Parameter { name: "value"; type: "double" }
112− }
113− Signal {
114− name: "distanceThresholdChanged"
115− Parameter { name: "value"; type: "double" }
116− }
117− Signal {
118− name: "minSpeedChanged"
119− Parameter { name: "value"; type: "double" }
120− }
121− Signal {
122− name: "maxSilenceTimeChanged"
123− Parameter { name: "value"; type: "int" }
124− }
125− Signal {
126− name: "compositionTimeChanged"
127− Parameter { name: "value"; type: "int" }
128− }
12993 Signal {
13094 name: "touchXChanged"
13195 Parameter { name: "value"; type: "double" }
@@ -142,7 +106,35 @@ Module {
142106 name: "touchSceneYChanged"
143107 Parameter { name: "value"; type: "double" }
144108 }
145− Signal { name: "tapped" }
109+ Signal {
110+ name: "immediateRecognitionChanged"
111+ Parameter { name: "value"; type: "bool" }
112+ }
113+ Method { name: "removeTimeConstraints" }
114+ }
115+ Component {
116+ name: "FloatingFlickable"
117+ defaultProperty: "data"
118+ prototype: "QQuickItem"
119+ exports: ["Ubuntu.Gestures/FloatingFlickable 0.1"]
120+ exportMetaObjectRevisions: [0]
121+ Property { name: "contentWidth"; type: "double" }
122+ Property { name: "contentHeight"; type: "double" }
123+ Property { name: "contentX"; type: "double" }
124+ Property { name: "contentY"; type: "double" }
125+ Property { name: "direction"; type: "Direction::Type" }
126+ }
127+ Component {
128+ name: "GestureTouchPoint"
129+ prototype: "QObject"
130+ exports: ["Ubuntu.Gestures/GestureTouchPoint 0.1"]
131+ isCreatable: false
132+ exportMetaObjectRevisions: [0]
133+ Property { name: "pointId"; type: "int"; isReadonly: true }
134+ Property { name: "pressed"; type: "bool"; isReadonly: true }
135+ Property { name: "x"; type: "double"; isReadonly: true }
136+ Property { name: "y"; type: "double"; isReadonly: true }
137+ Property { name: "dragging"; type: "bool"; isReadonly: true }
146138 }
147139 Component {
148140 name: "PressedOutsideNotifier"
@@ -163,6 +155,55 @@ Module {
163155 name: "targetItemChanged"
164156 Parameter { name: "item"; type: "QQuickItem"; isPointer: true }
165157 }
166− Signal { name: "pressed" }
158+ }
159+ Component {
160+ name: "TouchGestureArea"
161+ defaultProperty: "data"
162+ prototype: "QQuickItem"
163+ exports: ["Ubuntu.Gestures/TouchGestureArea 0.1"]
164+ exportMetaObjectRevisions: [0]
165+ Enum {
166+ name: "Status"
167+ values: {
168+ "WaitingForTouch": 0,
169+ "Undecided": 1,
170+ "Recognized": 2,
171+ "Rejected": 3
172+ }
173+ }
174+ Property { name: "touchPoints"; type: "GestureTouchPoint"; isList: true; isReadonly: true }
175+ Property { name: "dragging"; type: "bool"; isReadonly: true }
176+ Property { name: "minimumTouchPoints"; type: "int" }
177+ Property { name: "maximumTouchPoints"; type: "int" }
178+ Signal {
179+ name: "statusChanged"
180+ Parameter { name: "status"; type: "Status" }
181+ }
182+ Signal { name: "touchPointsUpdated" }
183+ Signal {
184+ name: "draggingChanged"
185+ Parameter { name: "dragging"; type: "bool" }
186+ }
187+ Signal {
188+ name: "minimumTouchPointsChanged"
189+ Parameter { name: "value"; type: "bool" }
190+ }
191+ Signal {
192+ name: "maximumTouchPointsChanged"
193+ Parameter { name: "value"; type: "bool" }
194+ }
195+ Signal {
196+ name: "pressed"
197+ Parameter { name: "points"; type: "QList<QObject*>" }
198+ }
199+ Signal {
200+ name: "released"
201+ Parameter { name: "points"; type: "QList<QObject*>" }
202+ }
203+ Signal {
204+ name: "updated"
205+ Parameter { name: "points"; type: "QList<QObject*>" }
206+ }
207+ Signal { name: "clicked" }
167208 }
168209}
plugins/Ubuntu/Gestures/TouchGestureArea.cppnew file+875
@@ -0,0 +1,875 @@
1+/*
2+ * Copyright (C) 2016 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include "TouchGestureArea.h"
18+
19+// local
20+#include "TouchOwnershipEvent.h"
21+#include "TouchRegistry.h"
22+#include "UnownedTouchEvent.h"
23+
24+#include <QGuiApplication>
25+#include <QStyleHints>
26+#include <private/qquickwindow_p.h>
27+
28+#define TOUCHGESTUREAREA_DEBUG 0
29+
30+// TODO - understand more about why we lose touch event releases.
31+// Workaround for now is to monitor all the touch points from first touch till
32+// all have been released; no matter if we've rejected the gesture.
33+
34+namespace {
35+
36+struct InternalStatus {
37+ enum Status {
38+ WaitingForTouch,
39+ WaitingForMoreTouches,
40+ WaitingForOwnership, //Recognizing,
41+ Recognized,
42+ WaitingForRejection,
43+ Rejected
44+ };
45+};
46+
47+TouchGestureArea::Status internalStatusToGestureStatus(int internalStatus) {
48+ switch (internalStatus) {
49+ case InternalStatus::WaitingForTouch: return TouchGestureArea::WaitingForTouch;
50+ case InternalStatus::WaitingForMoreTouches: return TouchGestureArea::Undecided;
51+ case InternalStatus::WaitingForOwnership: return TouchGestureArea::Undecided;
52+ case InternalStatus::Recognized: return TouchGestureArea::Recognized;
53+ case InternalStatus::WaitingForRejection: return TouchGestureArea::Recognized;
54+ case InternalStatus::Rejected: return TouchGestureArea::Rejected;
55+ }
56+ return TouchGestureArea::WaitingForTouch;
57+}
58+
59+}
60+
61+#if TOUCHGESTUREAREA_DEBUG
62+#define tgaDebug(params) qDebug().nospace() << "[TGA(" << qPrintable(objectName()) << ")] " << params
63+#include "DebugHelpers.h"
64+
65+namespace {
66+
67+const char *statusToString(int status)
68+{
69+ if (status == InternalStatus::WaitingForTouch) {
70+ return "WaitingForTouch";
71+ } else if (status == InternalStatus::WaitingForMoreTouches) {
72+ return "WaitingForMoreTouches";
73+ } else if (status == InternalStatus::WaitingForOwnership) {
74+ return "WaitingForOwnership";
75+ } else if (status == InternalStatus::Rejected) {
76+ return "Rejected";
77+ } else if (status == InternalStatus::WaitingForRejection) {
78+ return "WaitingForRejection";
79+ } else {
80+ return "Recognized";
81+ }
82+ return "Unknown";
83+}
84+
85+QString touchState(Qt::TouchPointState state) {
86+ switch (state) {
87+ case Qt::TouchPointPressed: return "pressed";
88+ case Qt::TouchPointMoved: return "moved";
89+ case Qt::TouchPointStationary: return "stationary";
90+ case Qt::TouchPointReleased: return "released";
91+ break;
92+ }
93+ return "unknown";
94+}
95+
96+QString touchesString(const QList<QObject*> touches) {
97+ QString str;
98+ Q_FOREACH(QObject* object, touches) {
99+ GestureTouchPoint* touchPoint = qobject_cast<GestureTouchPoint*>(object);
100+ if (touchPoint) {
101+ str += QStringLiteral("[%1 @ (%2, %3)], ").arg(touchPoint->id())
102+ .arg(touchPoint->x())
103+ .arg(touchPoint->y());
104+ }
105+ }
106+ return str;
107+}
108+
109+QString touchEventString(QTouchEvent* event) {
110+ if (!event) return QString();
111+ QString str;
112+ Q_FOREACH(const auto& touchPoint, event->touchPoints()) {
113+ str += QStringLiteral("[%1:%2 @ (%3, %4)], ").arg(touchPoint.id())
114+ .arg(touchState(touchPoint.state()))
115+ .arg(touchPoint.pos().x())
116+ .arg(touchPoint.pos().y());
117+ }
118+ return str;
119+}
120+
121+
122+} // namespace {
123+#else // TOUCHGESTUREAREA_DEBUG
124+#define tgaDebug(params) ((void)0)
125+#endif // TOUCHGESTUREAREA_DEBUG
126+
127+TouchGestureArea::TouchGestureArea(QQuickItem* parent)
128+ : QQuickItem(parent)
129+ , m_status(WaitingForTouch)
130+ , m_recognitionTimer(nullptr)
131+ , m_dragging(false)
132+ , m_minimumTouchPoints(1)
133+ , m_maximumTouchPoints(INT_MAX)
134+ , m_recognitionPeriod(50)
135+ , m_releaseRejectPeriod(100)
136+{
137+ setRecognitionTimer(new UbuntuGestures::Timer(this));
138+ m_recognitionTimer->setInterval(m_recognitionPeriod);
139+ m_recognitionTimer->setSingleShot(true);
140+}
141+
142+TouchGestureArea::~TouchGestureArea()
143+{
144+ clearTouchLists();
145+ qDeleteAll(m_liveTouchPoints);
146+ m_liveTouchPoints.clear();
147+ qDeleteAll(m_cachedTouchPoints);
148+ m_cachedTouchPoints.clear();
149+}
150+
151+bool TouchGestureArea::event(QEvent *event)
152+{
153+ // Process unowned touch events (handles update/release for incomplete gestures)
154+ if (event->type() == TouchOwnershipEvent::touchOwnershipEventType()) {
155+ touchOwnershipEvent(static_cast<TouchOwnershipEvent *>(event));
156+ return true;
157+ } else if (event->type() == UnownedTouchEvent::unownedTouchEventType()) {
158+ unownedTouchEvent(static_cast<UnownedTouchEvent *>(event)->touchEvent());
159+ return true;
160+ }
161+
162+ return QQuickItem::event(event);
163+}
164+
165+void TouchGestureArea::touchOwnershipEvent(TouchOwnershipEvent *event)
166+{
167+ int touchId = event->touchId();
168+ tgaDebug("touchOwnershipEvent - id:" << touchId << ", gained:" << event->gained());
169+
170+ if (event->gained()) {
171+ grabTouchPoints(QVector<int>() << touchId);
172+ m_candidateTouches.remove(touchId);
173+ TouchRegistry::instance()->addTouchWatcher(touchId, this);
174+ m_watchedTouches.insert(touchId);
175+
176+ if (m_watchedTouches.count() >= m_minimumTouchPoints) {
177+ setInternalStatus(InternalStatus::Recognized);
178+ }
179+ } else {
180+ rejectGesture();
181+ }
182+}
183+
184+void TouchGestureArea::touchEvent(QTouchEvent *event)
185+{
186+ if (!isEnabled() || !isVisible()) {
187+ tgaDebug(QString("NOT ENABLED touchEvent(%1) %2").arg(statusToString(m_status)).arg(touchEventString(event)));
188+ QQuickItem::touchEvent(event);
189+ return;
190+ }
191+
192+ tgaDebug(QString("touchEvent(%1) %2").arg(statusToString(m_status)).arg(touchEventString(event)));
193+
194+ switch (m_status) {
195+ case InternalStatus::WaitingForTouch:
196+ touchEvent_waitingForTouch(event);
197+ break;
198+ case InternalStatus::WaitingForMoreTouches:
199+ touchEvent_waitingForMoreTouches(event);
200+ break;
201+ case InternalStatus::WaitingForOwnership:
202+ touchEvent_waitingForOwnership(event);
203+ break;
204+ case InternalStatus::Recognized:
205+ case InternalStatus::WaitingForRejection:
206+ touchEvent_recognized(event);
207+ break;
208+ case InternalStatus::Rejected:
209+ touchEvent_rejected(event);
210+ break;
211+ default: // Recognized:
212+ break;
213+ }
214+
215+ updateTouchPoints(event);
216+}
217+
218+void TouchGestureArea::touchEvent_waitingForTouch(QTouchEvent *event)
219+{
220+ Q_FOREACH(const QTouchEvent::TouchPoint& touchPoint, event->touchPoints()) {
221+ Qt::TouchPointState touchPointState = touchPoint.state();
222+ int touchId = touchPoint.id();
223+
224+ if (touchPointState == Qt::TouchPointPressed) {
225+ if (!m_candidateTouches.contains(touchId)) {
226+ TouchRegistry::instance()->addCandidateOwnerForTouch(touchId, this);
227+ m_candidateTouches.insert(touchId);
228+ }
229+ }
230+ }
231+ event->ignore();
232+
233+ if (m_candidateTouches.count() > m_maximumTouchPoints) {
234+ rejectGesture();
235+ } else if (m_candidateTouches.count() >= m_minimumTouchPoints) {
236+ setInternalStatus(InternalStatus::WaitingForOwnership);
237+
238+ QSet<int> tmpCandidates(m_candidateTouches);
239+ Q_FOREACH(int candidateTouchId, tmpCandidates) {
240+ TouchRegistry::instance()->requestTouchOwnership(candidateTouchId, this);
241+ }
242+ // We accept the gesture; so don't pass to lower items
243+ event->accept();
244+ } else if (m_candidateTouches.count() > 0) {
245+ setInternalStatus(InternalStatus::WaitingForMoreTouches);
246+ }
247+}
248+
249+void TouchGestureArea::touchEvent_waitingForMoreTouches(QTouchEvent *event)
250+{
251+ Q_FOREACH(const QTouchEvent::TouchPoint& touchPoint, event->touchPoints()) {
252+ Qt::TouchPointState touchPointState = touchPoint.state();
253+ int touchId = touchPoint.id();
254+
255+ if (touchPointState == Qt::TouchPointPressed) {
256+ if (!m_candidateTouches.contains(touchId)) {
257+ TouchRegistry::instance()->addCandidateOwnerForTouch(touchId, this);
258+ m_candidateTouches.insert(touchId);
259+ }
260+ }
261+ }
262+ event->ignore();
263+
264+ if (m_candidateTouches.count() > m_maximumTouchPoints) {
265+ rejectGesture();
266+ } else if (m_candidateTouches.count() >= m_minimumTouchPoints) {
267+ setInternalStatus(InternalStatus::WaitingForOwnership);
268+
269+ QSet<int> tmpCandidates(m_candidateTouches);
270+ Q_FOREACH(int candidateTouchId, tmpCandidates) {
271+ TouchRegistry::instance()->requestTouchOwnership(candidateTouchId, this);
272+ }
273+ // We accept the gesture; so don't pass to lower items
274+ event->accept();
275+ }
276+}
277+
278+void TouchGestureArea::touchEvent_waitingForOwnership(QTouchEvent *event)
279+{
280+ Q_FOREACH(const QTouchEvent::TouchPoint& touchPoint, event->touchPoints()) {
281+ Qt::TouchPointState touchPointState = touchPoint.state();
282+ int touchId = touchPoint.id();
283+
284+ if (touchPointState == Qt::TouchPointPressed) {
285+ if (!m_watchedTouches.contains(touchId)) {
286+ TouchRegistry::instance()->addTouchWatcher(touchId, this);
287+ m_watchedTouches.insert(touchId);
288+ }
289+ }
290+ }
291+}
292+
293+void TouchGestureArea::touchEvent_recognized(QTouchEvent *event)
294+{
295+ Q_FOREACH(const QTouchEvent::TouchPoint& touchPoint, event->touchPoints()) {
296+ Qt::TouchPointState touchPointState = touchPoint.state();
297+ int touchId = touchPoint.id();
298+
299+ if (touchPointState == Qt::TouchPointPressed) {
300+ if (!m_watchedTouches.contains(touchId)) {
301+ TouchRegistry::instance()->addTouchWatcher(touchId, this);
302+ m_watchedTouches.insert(touchId);
303+ }
304+ }
305+ }
306+
307+ if (m_watchedTouches.count() > m_maximumTouchPoints) {
308+ rejectGesture();
309+ } else if (m_watchedTouches.count() >= m_minimumTouchPoints &&
310+ m_status==InternalStatus::WaitingForRejection) {
311+ setInternalStatus(InternalStatus::Recognized);
312+ }
313+}
314+
315+void TouchGestureArea::touchEvent_rejected(QTouchEvent *event)
316+{
317+ Q_FOREACH(const QTouchEvent::TouchPoint& touchPoint, event->touchPoints()) {
318+ Qt::TouchPointState touchPointState = touchPoint.state();
319+ int touchId = touchPoint.id();
320+
321+ if (touchPointState == Qt::TouchPointPressed) {
322+ if (!m_watchedTouches.contains(touchId)) {
323+ TouchRegistry::instance()->addTouchWatcher(touchId, this);
324+ m_watchedTouches.insert(touchId);
325+ }
326+ }
327+ }
328+}
329+
330+void TouchGestureArea::unownedTouchEvent(QTouchEvent *unownedTouchEvent)
331+{
332+ tgaDebug(QString("unownedTouchEvent(%1) %2").arg(statusToString(m_status)).arg(touchEventString(unownedTouchEvent)));
333+
334+ // Only monitor unowned touch events for presses/releases
335+ if ((unownedTouchEvent->touchPointStates() & (Qt::TouchPointPressed|Qt::TouchPointReleased)) == 0) {
336+ return;
337+ }
338+
339+ switch (m_status) {
340+ case InternalStatus::WaitingForTouch:
341+ break;
342+ case InternalStatus::WaitingForMoreTouches:
343+ unownedTouchEvent_waitingForMoreTouches(unownedTouchEvent);
344+ // do nothing
345+ break;
346+ case InternalStatus::WaitingForOwnership:
347+ unownedTouchEvent_waitingForOwnership(unownedTouchEvent);
348+ break;
349+ case InternalStatus::Recognized:
350+ case InternalStatus::WaitingForRejection:
351+ unownedTouchEvent_recognised(unownedTouchEvent);
352+ break;
353+ case InternalStatus::Rejected:
354+ unownedTouchEvent_rejected(unownedTouchEvent);
355+ break;
356+ default:
357+ break;
358+ }
359+
360+ updateTouchPoints(unownedTouchEvent);
361+}
362+
363+void TouchGestureArea::unownedTouchEvent_waitingForMoreTouches(QTouchEvent *event)
364+{
365+ Q_FOREACH(const QTouchEvent::TouchPoint& touchPoint, event->touchPoints()) {
366+ Qt::TouchPointState touchPointState = touchPoint.state();
367+ int touchId = touchPoint.id();
368+
369+ if (touchPointState == Qt::TouchPointReleased) {
370+ if (m_candidateTouches.contains(touchId)) {
371+ TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, this);
372+ m_candidateTouches.remove(touchId);
373+ }
374+ }
375+ }
376+
377+ if (m_candidateTouches.count() == 0) {
378+ setInternalStatus(InternalStatus::WaitingForTouch);
379+ }
380+}
381+
382+void TouchGestureArea::unownedTouchEvent_waitingForOwnership(QTouchEvent *event)
383+{
384+ Q_FOREACH(const QTouchEvent::TouchPoint& touchPoint, event->touchPoints()) {
385+ Qt::TouchPointState touchPointState = touchPoint.state();
386+ int touchId = touchPoint.id();
387+
388+ if (touchPointState == Qt::TouchPointReleased) {
389+ if (m_candidateTouches.contains(touchId)) {
390+ TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, this);
391+ m_candidateTouches.remove(touchId);
392+ }
393+ if (m_watchedTouches.contains(touchId)) {
394+ m_watchedTouches.remove(touchId);
395+ }
396+ }
397+ }
398+
399+ if (m_candidateTouches.count() + m_watchedTouches.count() == 0) {
400+ setInternalStatus(InternalStatus::WaitingForTouch);
401+ }
402+}
403+
404+void TouchGestureArea::unownedTouchEvent_recognised(QTouchEvent *event)
405+{
406+ Q_FOREACH(const QTouchEvent::TouchPoint& touchPoint, event->touchPoints()) {
407+ Qt::TouchPointState touchPointState = touchPoint.state();
408+ int touchId = touchPoint.id();
409+
410+ if (touchPointState == Qt::TouchPointReleased) {
411+ if (m_watchedTouches.contains(touchId)) {
412+ m_watchedTouches.remove(touchId);
413+ }
414+ }
415+ }
416+
417+ if (m_watchedTouches.count() < m_minimumTouchPoints && m_status==InternalStatus::Recognized) {
418+ setInternalStatus(InternalStatus::WaitingForRejection);
419+ }
420+}
421+
422+void TouchGestureArea::unownedTouchEvent_rejected(QTouchEvent *event)
423+{
424+ Q_FOREACH(const QTouchEvent::TouchPoint& touchPoint, event->touchPoints()) {
425+ Qt::TouchPointState touchPointState = touchPoint.state();
426+ int touchId = touchPoint.id();
427+
428+ if (touchPointState == Qt::TouchPointPressed) {
429+ if (!m_watchedTouches.contains(touchId)) {
430+ TouchRegistry::instance()->addTouchWatcher(touchId, this);
431+ m_watchedTouches.insert(touchId);
432+ }
433+ }
434+ if (touchPointState == Qt::TouchPointReleased) {
435+ if (m_watchedTouches.contains(touchId)) {
436+ m_watchedTouches.remove(touchId);
437+ }
438+ }
439+ }
440+
441+ if (m_watchedTouches.count() == 0) {
442+ setInternalStatus(InternalStatus::WaitingForTouch);
443+ }
444+}
445+
446+void TouchGestureArea::updateTouchPoints(QTouchEvent *touchEvent)
447+{
448+ bool added = false;
449+ bool ended = false;
450+ bool moved = false;
451+
452+ const int dragThreshold = qApp->styleHints()->startDragDistance();
453+ const int dragVelocity = qApp->styleHints()->startDragVelocity();
454+
455+ clearTouchLists();
456+ bool updateable = m_status != InternalStatus::WaitingForRejection;
457+
458+ Q_FOREACH(const QTouchEvent::TouchPoint& touchPoint, touchEvent->touchPoints()) {
459+ Qt::TouchPointState touchPointState = touchPoint.state();
460+ int touchId = touchPoint.id();
461+
462+ if (touchPointState & Qt::TouchPointReleased) {
463+ GestureTouchPoint* gtp = m_liveTouchPoints.value(touchId);
464+ if (!gtp) continue;
465+
466+ gtp->setPos(touchPoint.pos());
467+ gtp->setPressed(false);
468+ m_releasedTouchPoints.append(gtp);
469+ m_liveTouchPoints.remove(touchId);
470+
471+ if (updateable) {
472+ if (m_cachedTouchPoints.contains(touchId)) {
473+ GestureTouchPoint* cachedPoint = m_cachedTouchPoints.take(touchId);
474+ cachedPoint->deleteLater();
475+ }
476+ }
477+ ended = true;
478+ } else {
479+ GestureTouchPoint* gtp = m_liveTouchPoints.value(touchPoint.id(), nullptr);
480+ if (!gtp) {
481+ gtp = addTouchPoint(&touchPoint);
482+ m_pressedTouchPoints.append(gtp);
483+
484+ if (updateable) {
485+ if (m_cachedTouchPoints.contains(touchId)) {
486+ m_cachedTouchPoints[touchId]->setPos(touchPoint.pos());
487+ } else {
488+ m_cachedTouchPoints[touchId] = new GestureTouchPoint(*gtp);
489+ }
490+ }
491+ added = true;
492+ } else if (touchPointState & Qt::TouchPointMoved) {
493+ gtp->setPos(touchPoint.pos());
494+ m_movedTouchPoints.append(gtp);
495+ moved = true;
496+
497+ const QPointF &currentPos = touchPoint.scenePos();
498+ const QPointF &startPos = touchPoint.startScenePos();
499+
500+ bool overDragThreshold = false;
501+ bool supportsVelocity = (touchEvent->device()->capabilities() & QTouchDevice::Velocity) && dragVelocity;
502+ overDragThreshold |= qAbs(currentPos.x() - startPos.x()) > dragThreshold ||
503+ qAbs(currentPos.y() - startPos.y()) > dragThreshold;
504+ if (supportsVelocity) {
505+ QVector2D velocityVec = touchPoint.velocity();
506+ overDragThreshold |= qAbs(velocityVec.x()) > dragVelocity;
507+ overDragThreshold |= qAbs(velocityVec.y()) > dragVelocity;
508+ }
509+
510+ if (overDragThreshold) {
511+ gtp->setDragging(true);
512+ }
513+
514+ if (updateable) {
515+ if (m_cachedTouchPoints.contains(touchId)) {
516+ m_cachedTouchPoints[touchId]->setPos(touchPoint.pos());
517+ if (overDragThreshold) {
518+ m_cachedTouchPoints[touchId]->setDragging(true);
519+ }
520+ }
521+ }
522+ }
523+ }
524+ }
525+
526+ if (updateable) {
527+ if (!dragging() && m_status == InternalStatus::Recognized) {
528+ bool allWantDrag = !m_liveTouchPoints.isEmpty();
529+ Q_FOREACH(auto point, m_liveTouchPoints) {
530+ allWantDrag &= point->dragging();
531+ }
532+ // only dragging if all points are dragging.
533+ if (allWantDrag) {
534+ setDragging(true);
535+ }
536+ }
537+
538+ if (ended) {
539+ if (m_liveTouchPoints.isEmpty()) {
540+ if (!dragging()) Q_EMIT clicked();
541+ setDragging(false);
542+ }
543+ tgaDebug("Released " << touchesString(m_releasedTouchPoints));
544+ Q_EMIT released(m_releasedTouchPoints);
545+ }
546+ if (added) {
547+ tgaDebug("Pressed " << touchesString(m_pressedTouchPoints));
548+ Q_EMIT pressed(m_pressedTouchPoints);
549+ }
550+ if (moved) {
551+ tgaDebug("Updated " << touchesString(m_movedTouchPoints));
552+ Q_EMIT updated(m_movedTouchPoints);
553+ }
554+ if (added || ended || moved) {
555+ Q_EMIT touchPointsUpdated();
556+ }
557+ }
558+}
559+
560+void TouchGestureArea::clearTouchLists()
561+{
562+ Q_FOREACH (QObject *gtp, m_releasedTouchPoints) {
563+ delete gtp;
564+ }
565+ m_releasedTouchPoints.clear();
566+ m_pressedTouchPoints.clear();
567+ m_movedTouchPoints.clear();
568+}
569+
570+void TouchGestureArea::setInternalStatus(uint newStatus)
571+{
572+ if (newStatus == m_status)
573+ return;
574+
575+ uint oldStatus = m_status;
576+
577+ m_status = newStatus;
578+ Q_EMIT statusChanged(status());
579+
580+ if (oldStatus == InternalStatus::WaitingForMoreTouches || oldStatus == InternalStatus::WaitingForRejection) {
581+ m_recognitionTimer->stop();
582+ }
583+
584+ tgaDebug(statusToString(oldStatus) << " -> " << statusToString(newStatus));
585+
586+ switch (newStatus) {
587+ case InternalStatus::WaitingForTouch:
588+ resyncCachedTouchPoints();
589+ break;
590+ case InternalStatus::WaitingForMoreTouches:
591+ m_recognitionTimer->start(m_recognitionPeriod);
592+ break;
593+ case InternalStatus::Recognized:
594+ resyncCachedTouchPoints();
595+ break;
596+ case InternalStatus::WaitingForRejection:
597+ m_recognitionTimer->start(m_releaseRejectPeriod);
598+ break;
599+ case InternalStatus::Rejected:
600+ resyncCachedTouchPoints();
601+ break;
602+ default:
603+ // no-op
604+ break;
605+ }
606+}
607+
608+void TouchGestureArea::setRecognitionTimer(UbuntuGestures::AbstractTimer *timer)
609+{
610+ int interval = 0;
611+ bool timerWasRunning = false;
612+ bool wasSingleShot = false;
613+
614+ // can be null when called from the constructor
615+ if (m_recognitionTimer) {
616+ interval = m_recognitionTimer->interval();
617+ timerWasRunning = m_recognitionTimer->isRunning();
618+ if (m_recognitionTimer->parent() == this) {
619+ delete m_recognitionTimer;
620+ }
621+ }
622+
623+ m_recognitionTimer = timer;
624+ timer->setInterval(interval);
625+ timer->setSingleShot(wasSingleShot);
626+ connect(timer, SIGNAL(timeout()),
627+ this, SLOT(rejectGesture()));
628+ if (timerWasRunning) {
629+ m_recognitionTimer->start();
630+ }
631+}
632+
633+int TouchGestureArea::status() const
634+{
635+ return internalStatusToGestureStatus(m_status);
636+}
637+
638+bool TouchGestureArea::dragging() const
639+{
640+ return m_dragging;
641+}
642+
643+QQmlListProperty<GestureTouchPoint> TouchGestureArea::touchPoints()
644+{
645+ return QQmlListProperty<GestureTouchPoint>(this,
646+ 0,
647+ nullptr,
648+ TouchGestureArea::touchPoint_count,
649+ TouchGestureArea::touchPoint_at,
650+ 0);
651+}
652+
653+int TouchGestureArea::minimumTouchPoints() const
654+{
655+ return m_minimumTouchPoints;
656+}
657+
658+void TouchGestureArea::setMinimumTouchPoints(int value)
659+{
660+ if (m_minimumTouchPoints != value) {
661+ m_minimumTouchPoints = value;
662+ Q_EMIT minimumTouchPointsChanged(value);
663+ }
664+}
665+
666+int TouchGestureArea::maximumTouchPoints() const
667+{
668+ return m_maximumTouchPoints;
669+}
670+
671+void TouchGestureArea::setMaximumTouchPoints(int value)
672+{
673+ if (m_maximumTouchPoints != value) {
674+ m_maximumTouchPoints = value;
675+ Q_EMIT maximumTouchPointsChanged(value);
676+ }
677+}
678+
679+int TouchGestureArea::recognitionPeriod() const
680+{
681+ return m_recognitionPeriod;
682+}
683+
684+void TouchGestureArea::setRecognitionPeriod(int value)
685+{
686+ if (value != m_recognitionPeriod) {
687+ m_recognitionPeriod = value;
688+ Q_EMIT recognitionPeriodChanged(value);
689+ }
690+}
691+
692+int TouchGestureArea::releaseRejectPeriod() const
693+{
694+ return m_releaseRejectPeriod;
695+}
696+
697+void TouchGestureArea::setReleaseRejectPeriod(int value)
698+{
699+ if (value != m_releaseRejectPeriod) {
700+ m_releaseRejectPeriod = value;
701+ Q_EMIT releaseRejectPeriodChanged(value);
702+ }
703+}
704+
705+void TouchGestureArea::rejectGesture()
706+{
707+ tgaDebug("rejectGesture");
708+ ungrabTouchPoints();
709+
710+ Q_FOREACH(int touchId, m_candidateTouches) {
711+ TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, this);
712+ }
713+
714+ // Monitor the candidates
715+ Q_FOREACH(int touchId, m_candidateTouches) {
716+ TouchRegistry::instance()->addTouchWatcher(touchId, this);
717+ m_watchedTouches.insert(touchId);
718+ }
719+ m_candidateTouches.clear();
720+
721+ if (m_watchedTouches.count() == 0) {
722+ setInternalStatus(InternalStatus::WaitingForTouch);
723+ } else {
724+ setInternalStatus(InternalStatus::Rejected);
725+ }
726+}
727+
728+void TouchGestureArea::resyncCachedTouchPoints()
729+{
730+ clearTouchLists();
731+
732+ bool added = false;
733+ bool ended = false;
734+ bool moved = false;
735+ bool wantsDrag = false;
736+
737+ // list of deletes
738+ QMutableHashIterator<int, GestureTouchPoint*> removeIter(m_cachedTouchPoints);
739+ while(removeIter.hasNext()) {
740+ removeIter.next();
741+ if (!m_liveTouchPoints.contains(removeIter.key())) {
742+ m_releasedTouchPoints.append(removeIter.value());
743+ removeIter.remove();
744+ ended = true;
745+ }
746+ }
747+
748+ // list of adds/moves
749+ Q_FOREACH(GestureTouchPoint* touchPoint, m_liveTouchPoints) {
750+ if (m_cachedTouchPoints.contains(touchPoint->id())) {
751+ GestureTouchPoint* cachedPoint = m_cachedTouchPoints[touchPoint->id()];
752+
753+ if (*cachedPoint != *touchPoint) {
754+ *cachedPoint = *touchPoint;
755+ m_movedTouchPoints.append(touchPoint);
756+ moved = true;
757+ }
758+ } else {
759+ m_cachedTouchPoints.insert(touchPoint->id(), new GestureTouchPoint(*touchPoint));
760+ m_pressedTouchPoints.append(touchPoint);
761+ added = true;
762+ }
763+ }
764+
765+ if (wantsDrag && !dragging()) {
766+ setDragging(true);
767+ }
768+
769+ if (ended) {
770+ if (m_cachedTouchPoints.isEmpty()) {
771+ if (!dragging()) Q_EMIT clicked();
772+ setDragging(false);
773+ }
774+ tgaDebug("Cached Release " << touchesString(m_releasedTouchPoints));
775+ Q_EMIT released(m_releasedTouchPoints);
776+ }
777+ if (added) {
778+ tgaDebug("Cached Press " << touchesString(m_pressedTouchPoints));
779+ Q_EMIT pressed(m_pressedTouchPoints);
780+ }
781+ if (moved) {
782+ tgaDebug("Cached Update " << touchesString(m_movedTouchPoints));
783+ Q_EMIT updated(m_movedTouchPoints);
784+ }
785+ if (added || ended || moved) Q_EMIT touchPointsUpdated();
786+}
787+
788+int TouchGestureArea::touchPoint_count(QQmlListProperty<GestureTouchPoint> *list)
789+{
790+ TouchGestureArea *q = static_cast<TouchGestureArea*>(list->object);
791+ return q->m_cachedTouchPoints.count();
792+}
793+
794+GestureTouchPoint *TouchGestureArea::touchPoint_at(QQmlListProperty<GestureTouchPoint> *list, int index)
795+{
796+ TouchGestureArea *q = static_cast<TouchGestureArea*>(list->object);
797+ return (q->m_cachedTouchPoints.begin()+index).value();
798+}
799+
800+GestureTouchPoint* TouchGestureArea::addTouchPoint(QTouchEvent::TouchPoint const* tp)
801+{
802+ GestureTouchPoint* gtp = new GestureTouchPoint();
803+ gtp->setId(tp->id());
804+ gtp->setPressed(true);
805+ gtp->setPos(tp->pos());
806+ m_liveTouchPoints.insert(tp->id(), gtp);
807+ return gtp;
808+}
809+
810+void TouchGestureArea::itemChange(ItemChange change, const ItemChangeData &value)
811+{
812+ if (change == QQuickItem::ItemSceneChange) {
813+ if (value.window != nullptr) {
814+ value.window->installEventFilter(TouchRegistry::instance());
815+ }
816+ }
817+}
818+
819+void TouchGestureArea::setDragging(bool dragging)
820+{
821+ if (m_dragging == dragging)
822+ return;
823+
824+ tgaDebug("setDragging " << dragging);
825+
826+ m_dragging = dragging;
827+ Q_EMIT draggingChanged(m_dragging);
828+}
829+
830+void GestureTouchPoint::setId(int id)
831+{
832+ if (m_id == id)
833+ return;
834+ m_id = id;
835+ Q_EMIT idChanged();
836+}
837+
838+void GestureTouchPoint::setPressed(bool pressed)
839+{
840+ if (m_pressed == pressed)
841+ return;
842+ m_pressed = pressed;
843+ Q_EMIT pressedChanged();
844+}
845+
846+void GestureTouchPoint::setX(qreal x)
847+{
848+ if (m_x == x)
849+ return;
850+ m_x = x;
851+ Q_EMIT xChanged();
852+}
853+
854+void GestureTouchPoint::setY(qreal y)
855+{
856+ if (m_y == y)
857+ return;
858+ m_y = y;
859+ Q_EMIT yChanged();
860+}
861+
862+void GestureTouchPoint::setDragging(bool dragging)
863+{
864+ if (m_dragging == dragging)
865+ return;
866+
867+ m_dragging = dragging;
868+ Q_EMIT draggingChanged();
869+}
870+
871+void GestureTouchPoint::setPos(const QPointF &pos)
872+{
873+ setX(pos.x());
874+ setY(pos.y());
875+}
plugins/Ubuntu/Gestures/TouchGestureArea.hnew file+229
@@ -0,0 +1,229 @@
1+/*
2+ * Copyright (C) 2016 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef TOUCHGESTUREAREA_H
18+#define TOUCHGESTUREAREA_H
19+
20+#include "UbuntuGesturesQmlGlobal.h"
21+
22+#include <QQuickItem>
23+
24+// lib UbuntuGestures
25+#include <Timer.h>
26+
27+class TouchOwnershipEvent;
28+class UnownedTouchEvent;
29+
30+class GestureTouchPoint : public QObject
31+{
32+ Q_OBJECT
33+ Q_PROPERTY(int id READ id NOTIFY idChanged)
34+ Q_PROPERTY(bool pressed READ pressed NOTIFY pressedChanged)
35+ Q_PROPERTY(qreal x READ x NOTIFY xChanged)
36+ Q_PROPERTY(qreal y READ y NOTIFY yChanged)
37+ Q_PROPERTY(bool dragging READ dragging NOTIFY draggingChanged)
38+public:
39+ GestureTouchPoint()
40+ : m_id(-1)
41+ , m_pressed(false)
42+ , m_x(0)
43+ , m_y(0)
44+ , m_dragging(false)
45+ {
46+ }
47+
48+ GestureTouchPoint(const GestureTouchPoint& other)
49+ : QObject(nullptr)
50+ {
51+ operator=(other);
52+ }
53+
54+ int id() const { return m_id; }
55+ void setId(int id);
56+
57+ bool pressed() const { return m_pressed; }
58+ void setPressed(bool pressed);
59+
60+ qreal x() const { return m_x; }
61+ void setX(qreal x);
62+
63+ qreal y() const { return m_y; }
64+ void setY(qreal y);
65+
66+ bool dragging() const { return m_dragging; }
67+ void setDragging(bool dragging);
68+
69+ GestureTouchPoint& operator=(const GestureTouchPoint& rhs) {
70+ if (&rhs == this) return *this;
71+ m_id = rhs.m_id;
72+ m_pressed = rhs.m_pressed;
73+ m_x = rhs.m_x;
74+ m_y = rhs.m_y;
75+ m_dragging = rhs.m_dragging;
76+ return *this;
77+ }
78+
79+ bool operator=(const GestureTouchPoint& rhs) const {
80+ if (&rhs == this) return true;
81+ return m_id == rhs.m_id &&
82+ m_pressed == rhs.m_pressed &&
83+ m_x == rhs.m_x &&
84+ m_y == rhs.m_y &&
85+ m_dragging == rhs.m_dragging;
86+ }
87+ bool operator!=(const GestureTouchPoint& rhs) const { return !operator=(rhs); }
88+
89+ void setPos(const QPointF &pos);
90+
91+Q_SIGNALS:
92+ void idChanged();
93+ void pressedChanged();
94+ void xChanged();
95+ void yChanged();
96+ void draggingChanged();
97+
98+private:
99+ int m_id;
100+ bool m_pressed;
101+ qreal m_x;
102+ qreal m_y;
103+ bool m_dragging;
104+};
105+
106+/*
107+ An area that detects multi-finger gestures.
108+
109+ We can use this to detect gestures contstrained by a minimim and/or maximum number of touch points.
110+ This components uses the touch registry to apply for ownership of touch points.
111+ This way we can use the component in conjuntion with the directional drag area to compete for ownwership
112+ or gestures; unlike the MultiPointTouchArea.
113+ */
114+class UBUNTUGESTURESQML_EXPORT TouchGestureArea : public QQuickItem
115+{
116+ Q_OBJECT
117+ Q_ENUMS(Status)
118+
119+ Q_PROPERTY(int status READ status NOTIFY statusChanged)
120+ Q_PROPERTY(bool dragging READ dragging NOTIFY draggingChanged)
121+ Q_PROPERTY(QQmlListProperty<GestureTouchPoint> touchPoints READ touchPoints NOTIFY touchPointsUpdated)
122+
123+ Q_PROPERTY(int minimumTouchPoints READ minimumTouchPoints WRITE setMinimumTouchPoints NOTIFY minimumTouchPointsChanged)
124+ Q_PROPERTY(int maximumTouchPoints READ maximumTouchPoints WRITE setMaximumTouchPoints NOTIFY maximumTouchPointsChanged)
125+
126+ // Time(ms) the component will wait for after receiving an initial touch to recognise a gesutre before rejecting it.
127+ Q_PROPERTY(int recognitionPeriod READ recognitionPeriod WRITE setRecognitionPeriod NOTIFY recognitionPeriodChanged)
128+ // Time(ms) the component will allow a recognised gesture to intermitently release a touch point before rejecting the gesture.
129+ // This is so we will not immediately reject a gesture if there are fleeting touch point releases while dragging.
130+ Q_PROPERTY(int releaseRejectPeriod READ releaseRejectPeriod WRITE setReleaseRejectPeriod NOTIFY releaseRejectPeriodChanged)
131+
132+public:
133+ // Describes the state of the touch gesture area.
134+ enum Status {
135+ WaitingForTouch,
136+ Undecided,
137+ Recognized,
138+ Rejected
139+ };
140+ TouchGestureArea(QQuickItem* parent = NULL);
141+ ~TouchGestureArea();
142+
143+ bool event(QEvent *e) override;
144+
145+ void setRecognitionTimer(UbuntuGestures::AbstractTimer *timer);
146+
147+ int status() const;
148+ bool dragging() const;
149+ QQmlListProperty<GestureTouchPoint> touchPoints();
150+
151+ int minimumTouchPoints() const;
152+ void setMinimumTouchPoints(int value);
153+
154+ int maximumTouchPoints() const;
155+ void setMaximumTouchPoints(int value);
156+
157+ int recognitionPeriod() const;
158+ void setRecognitionPeriod(int value);
159+
160+ int releaseRejectPeriod() const;
161+ void setReleaseRejectPeriod(int value);
162+
163+Q_SIGNALS:
164+ void statusChanged(int status);
165+
166+ void touchPointsUpdated();
167+ void draggingChanged(bool dragging);
168+ void minimumTouchPointsChanged(bool value);
169+ void maximumTouchPointsChanged(bool value);
170+ void recognitionPeriodChanged(bool value);
171+ void releaseRejectPeriodChanged(bool value);
172+
173+ void pressed(const QList<QObject*>& points);
174+ void released(const QList<QObject*>& points);
175+ void updated(const QList<QObject*>& points);
176+ void clicked();
177+
178+protected:
179+ void itemChange(ItemChange change, const ItemChangeData &value);
180+
181+private Q_SLOTS:
182+ void rejectGesture();
183+
184+private:
185+ void touchEvent(QTouchEvent *event) override;
186+ void touchEvent_waitingForTouch(QTouchEvent *event);
187+ void touchEvent_waitingForMoreTouches(QTouchEvent *event);
188+ void touchEvent_waitingForOwnership(QTouchEvent *event);
189+ void touchEvent_recognized(QTouchEvent *event);
190+ void touchEvent_rejected(QTouchEvent *event);
191+
192+ void unownedTouchEvent(QTouchEvent *unownedTouchEvent);
193+ void unownedTouchEvent_waitingForMoreTouches(QTouchEvent *unownedTouchEvent);
194+ void unownedTouchEvent_waitingForOwnership(QTouchEvent *unownedTouchEvent);
195+ void unownedTouchEvent_recognised(QTouchEvent *unownedTouchEvent);
196+ void unownedTouchEvent_rejected(QTouchEvent *unownedTouchEvent);
197+
198+ void touchOwnershipEvent(TouchOwnershipEvent *event);
199+ void updateTouchPoints(QTouchEvent *event);
200+
201+ GestureTouchPoint* addTouchPoint(const QTouchEvent::TouchPoint *tp);
202+ void clearTouchLists();
203+ void setDragging(bool dragging);
204+ void setInternalStatus(uint status);
205+ void resyncCachedTouchPoints();
206+
207+ static int touchPoint_count(QQmlListProperty<GestureTouchPoint> *list);
208+ static GestureTouchPoint* touchPoint_at(QQmlListProperty<GestureTouchPoint> *list, int index);
209+
210+ uint m_status;
211+ QSet<int> m_candidateTouches;
212+ QSet<int> m_watchedTouches;
213+ UbuntuGestures::AbstractTimer *m_recognitionTimer;
214+
215+ bool m_dragging;
216+ QHash<int, GestureTouchPoint*> m_liveTouchPoints;
217+ QHash<int, GestureTouchPoint*> m_cachedTouchPoints;
218+ QList<QObject*> m_releasedTouchPoints;
219+ QList<QObject*> m_pressedTouchPoints;
220+ QList<QObject*> m_movedTouchPoints;
221+ int m_minimumTouchPoints;
222+ int m_maximumTouchPoints;
223+ int m_recognitionPeriod;
224+ int m_releaseRejectPeriod;
225+};
226+
227+QML_DECLARE_TYPE(GestureTouchPoint)
228+
229+#endif // TOUCHGESTUREAREA_H
plugins/Ubuntu/Gestures/plugin.cpp+3
@@ -21,6 +21,7 @@
2121#include "FloatingFlickable.h"
2222#include "PressedOutsideNotifier.h"
2323#include "TouchGate.h"
24+#include "TouchGestureArea.h"
2425
2526#include <qqml.h>
2627
@@ -38,4 +39,6 @@ void UbuntuGesturesQmlPlugin::registerTypes(const char *uri)
3839 qmlRegisterType<FloatingFlickable>(uri, 0, 1, "FloatingFlickable");
3940 qmlRegisterType<PressedOutsideNotifier>(uri, 0, 1, "PressedOutsideNotifier");
4041 qmlRegisterType<TouchGate>(uri, 0, 1, "TouchGate");
42+ qmlRegisterType<TouchGestureArea>(uri, 0, 1, "TouchGestureArea");
43+ qmlRegisterUncreatableType<GestureTouchPoint>(uri, 0, 1, "GestureTouchPoint", "Cannot create GestureTouchPoints");
4144}
plugins/Unity/Indicators/indicatorsmanager.cpp+2−2
@@ -116,7 +116,7 @@ void IndicatorsManager::loadFile(const QFileInfo& file_info)
116116 QSettings indicator_settings(file_info.absoluteFilePath(), QSettings::IniFormat, this);
117117 const QString name = indicator_settings.value(QStringLiteral("Indicator Service/Name")).toString();
118118
119− if (m_platform.isPC() && name == "indicator-keyboard") {
119+ if (m_platform.isPC() && name == QLatin1String("indicator-keyboard")) {
120120 return; // convergence: skip this indicator until it works in Mir
121121 }
122122
@@ -293,7 +293,7 @@ Indicator::Ptr IndicatorsManager::indicator(const QString& indicator_name)
293293 // The rest of the indicators respect their default profile (which is "phone", even on desktop PCs)
294294 if ((new_indicator->identifier() == QStringLiteral("indicator-session") && m_platform.isMultiSession())
295295 || (new_indicator->identifier() == QStringLiteral("indicator-power") && m_platform.isPC())) {
296− new_indicator->setProfile("desktop");
296+ new_indicator->setProfile(QStringLiteral("desktop"));
297297 } else {
298298 new_indicator->setProfile(m_profile);
299299 }
plugins/Unity/Indicators/rootstateparser.h+1
@@ -23,6 +23,7 @@
2323
2424class UNITYINDICATORS_EXPORT RootStateParser : public ActionStateParser
2525{
26+Q_OBJECT
2627public:
2728 RootStateParser(QObject* parent = nullptr);
2829 virtual QVariant toQVariant(GVariant* state) const override;
plugins/Unity/InputInfo/InputInfo.qmltypesnew file+71
@@ -0,0 +1,71 @@
1+import QtQuick.tooling 1.1
2+
3+// This file describes the plugin-supplied types contained in the library.
4+// It is used for QML tooling purposes only.
5+//
6+// This file was auto-generated by:
7+// 'qmlplugindump -notrelocatable Unity.InputInfo 0.1 plugins'
8+
9+Module {
10+ Component {
11+ name: "QDeclarativeInputDeviceModel"
12+ prototype: "QAbstractListModel"
13+ exports: ["Unity.InputInfo/InputDeviceModel 0.1"]
14+ exportMetaObjectRevisions: [0]
15+ Property { name: "deviceFilter"; type: "QInputDevice::InputType" }
16+ Property { name: "count"; type: "int"; isReadonly: true }
17+ Signal {
18+ name: "deviceAdded"
19+ Parameter { name: "devicePath"; type: "string" }
20+ }
21+ Signal {
22+ name: "deviceRemoved"
23+ Parameter { name: "devicePath"; type: "string" }
24+ }
25+ Signal {
26+ name: "deviceFilterChanged"
27+ Parameter { name: "filter"; type: "QInputDevice::InputType" }
28+ }
29+ Method { name: "updateDeviceList" }
30+ Method {
31+ name: "indexOf"
32+ type: "int"
33+ Parameter { name: "devicePath"; type: "string" }
34+ }
35+ Method {
36+ name: "get"
37+ type: "QInputDevice*"
38+ Parameter { name: "index"; type: "int" }
39+ }
40+ }
41+ Component {
42+ name: "QInputDevice"
43+ prototype: "QObject"
44+ exports: ["Unity.InputInfo/InputInfo 0.1"]
45+ exportMetaObjectRevisions: [0]
46+ Enum {
47+ name: "InputType"
48+ values: {
49+ "Unknown": 0,
50+ "Button": 1,
51+ "Mouse": 2,
52+ "TouchPad": 4,
53+ "TouchScreen": 8,
54+ "Keyboard": 16,
55+ "Switch": 32
56+ }
57+ }
58+ Enum {
59+ name: "InputTypeFlags"
60+ values: {
61+ "Unknown": 0,
62+ "Button": 1,
63+ "Mouse": 2,
64+ "TouchPad": 4,
65+ "TouchScreen": 8,
66+ "Keyboard": 16,
67+ "Switch": 32
68+ }
69+ }
70+ }
71+}
plugins/Unity/Launcher/desktopfilehandler.cpp+2−2
@@ -146,9 +146,9 @@ QString DesktopFileHandler::icon() const
146146 QString iconString = settings.value(QStringLiteral("Icon")).toString();
147147 QString pathString = settings.value(QStringLiteral("Path")).toString();
148148
149− if (QFileInfo(iconString).exists()) {
149+ if (QFileInfo::exists(iconString)) {
150150 return QFileInfo(iconString).absoluteFilePath();
151− } else if (QFileInfo(pathString + '/' + iconString).exists()) {
151+ } else if (QFileInfo::exists(pathString + '/' + iconString)) {
152152 return pathString + '/' + iconString;
153153 }
154154 return "image://theme/" + iconString;
plugins/Unity/Launcher/launcheritem.cpp+1
@@ -37,6 +37,7 @@ LauncherItem::LauncherItem(const QString &appId, const QString &name, const QStr
3737 m_alerting(false),
3838 m_quickList(new QuickListModel(this))
3939{
40+ Q_ASSERT(parent != nullptr);
4041 QuickListEntry nameAction;
4142 nameAction.setActionId(QStringLiteral("launch_item"));
4243 nameAction.setText(m_name);
plugins/Unity/Launcher/launcheritem.h+1−1
@@ -32,7 +32,7 @@ class LauncherItem: public LauncherItemInterface
3232{
3333 Q_OBJECT
3434public:
35− LauncherItem(const QString &appId, const QString &name, const QString &icon, QObject *parent = 0);
35+ LauncherItem(const QString &appId, const QString &name, const QString &icon, QObject *parent);
3636
3737 QString appId() const override;
3838 QString name() const override;
plugins/Unity/Launcher/launchermodel.cpp+15−5
@@ -103,7 +103,7 @@ void LauncherModel::setAlerting(const QString &appId, bool alerting) {
103103 LauncherItem *item = m_list.at(index);
104104 if (!item->focused()) {
105105 item->setAlerting(alerting);
106− Q_EMIT dataChanged(modelIndex, modelIndex, QVector<int>() << RoleAlerting);
106+ Q_EMIT dataChanged(modelIndex, modelIndex, {RoleAlerting});
107107 }
108108 }
109109}
@@ -389,7 +389,8 @@ void LauncherModel::countVisibleChanged(const QString &appId, bool countVisible)
389389 if (countVisible && desktopFile.isValid()) {
390390 LauncherItem *item = new LauncherItem(appId,
391391 desktopFile.displayName(),
392− desktopFile.icon());
392+ desktopFile.icon(),
393+ this);
393394 item->setCountVisible(true);
394395 beginInsertRows(QModelIndex(), m_list.count(), m_list.count());
395396 m_list.append(item);
@@ -414,10 +415,19 @@ void LauncherModel::refresh()
414415 } else {
415416 int idx = m_list.indexOf(item);
416417 item->setName(desktopFile.displayName());
417− item->setIcon(desktopFile.icon());
418418 item->setPinned(item->pinned()); // update pinned text if needed
419419 item->setRunning(item->running());
420− Q_EMIT dataChanged(index(idx), index(idx), {RoleName, RoleIcon, RoleRunning});
420+ Q_EMIT dataChanged(index(idx), index(idx), {RoleName, RoleRunning});
421+
422+ const QString oldIcon = item->icon();
423+ if (oldIcon == desktopFile.icon()) { // same icon file, perhaps different contents, simulate changing the icon name to force reload
424+ item->setIcon(QString());
425+ Q_EMIT dataChanged(index(idx), index(idx), {RoleIcon});
426+ }
427+
428+ // now set the icon for real
429+ item->setIcon(desktopFile.icon());
430+ Q_EMIT dataChanged(index(idx), index(idx), {RoleIcon});
421431 }
422432 }
423433
@@ -489,7 +499,7 @@ void LauncherModel::alert(const QString &appId)
489499 if (idx >= 0) {
490500 LauncherItem *item = m_list.at(idx);
491501 setAlerting(item->appId(), true);
492− Q_EMIT dataChanged(index(idx), index(idx), QVector<int>() << RoleAlerting);
502+ Q_EMIT dataChanged(index(idx), index(idx), {RoleAlerting});
493503 }
494504}
495505
plugins/Unity/Launcher/launchermodel.h+1−1
@@ -38,7 +38,7 @@ class LauncherModel: public LauncherModelInterface
3838 Q_OBJECT
3939
4040public:
41− LauncherModel(QObject *parent = 0);
41+ LauncherModel(QObject *parent = nullptr);
4242 ~LauncherModel();
4343
4444 int rowCount(const QModelIndex &parent = QModelIndex()) const override;
plugins/Unity/Platform/Platform.qmltypesnew file+20
@@ -0,0 +1,20 @@
1+import QtQuick.tooling 1.1
2+
3+// This file describes the plugin-supplied types contained in the library.
4+// It is used for QML tooling purposes only.
5+//
6+// This file was auto-generated by:
7+// 'qmlplugindump -notrelocatable Unity.Platform 1.0 plugins'
8+
9+Module {
10+ Component {
11+ name: "Platform"
12+ prototype: "QObject"
13+ exports: ["Unity.Platform/Platform 1.0"]
14+ isCreatable: false
15+ isSingleton: true
16+ exportMetaObjectRevisions: [0]
17+ Property { name: "chassis"; type: "string"; isReadonly: true }
18+ Property { name: "isPC"; type: "bool"; isReadonly: true }
19+ }
20+}
plugins/Utils/CMakeLists.txt+2
@@ -23,6 +23,8 @@ set(QMLPLUGIN_SRC
2323 windowstatestorage.cpp
2424 timezoneFormatter.cpp
2525 inputeventgenerator.cpp
26+ deviceconfigparser.cpp
27+ globalfunctions.cpp
2628 plugin.cpp
2729 )
2830
plugins/Utils/deviceconfigparser.cppnew file+150
@@ -0,0 +1,150 @@
1+/*
2+ * Copyright 2016 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU Lesser General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include "deviceconfigparser.h"
18+
19+#include <QSettings>
20+#include <QFileInfo>
21+#include <QDebug>
22+#include <QStandardPaths>
23+
24+DeviceConfigParser::DeviceConfigParser(QObject *parent): QObject(parent)
25+{
26+ QString path;
27+ Q_FOREACH (const QString &standardPath, QStandardPaths::standardLocations(QStandardPaths::GenericConfigLocation)) {
28+ if (QFileInfo(standardPath + "/devices.conf").exists()) {
29+ path = standardPath + "/devices.conf";
30+ break;
31+ }
32+ }
33+
34+ if (path.isEmpty()) {
35+ path = "/etc/ubuntu/devices.conf";
36+ }
37+ qDebug() << "Using" << path << "as device configuration file";
38+ m_config = new QSettings(path, QSettings::IniFormat, this);
39+}
40+
41+QString DeviceConfigParser::name() const
42+{
43+ return m_name;
44+}
45+
46+void DeviceConfigParser::setName(const QString &name)
47+{
48+ if (m_name == name) {
49+ return;
50+ }
51+ m_name = name;
52+ Q_EMIT changed();
53+}
54+
55+Qt::ScreenOrientation DeviceConfigParser::primaryOrientation() const
56+{
57+ return stringToOrientation(readOrientationFromConfig("PrimaryOrientation"), Qt::PrimaryOrientation);
58+}
59+
60+Qt::ScreenOrientations DeviceConfigParser::supportedOrientations() const
61+{
62+ QStringList values = readOrientationsFromConfig("SupportedOrientations");
63+ if (values.isEmpty()) {
64+ return Qt::PortraitOrientation
65+ | Qt::InvertedPortraitOrientation
66+ | Qt::LandscapeOrientation
67+ | Qt::InvertedLandscapeOrientation;
68+ }
69+
70+ Qt::ScreenOrientations ret = Qt::PrimaryOrientation;
71+ Q_FOREACH(const QString &orientationString, values) {
72+ ret |= stringToOrientation(orientationString, Qt::PrimaryOrientation);
73+ }
74+ return ret;
75+}
76+
77+Qt::ScreenOrientation DeviceConfigParser::landscapeOrientation() const
78+{
79+ return stringToOrientation(readOrientationFromConfig("LandscapeOrientation"), Qt::LandscapeOrientation);
80+}
81+
82+Qt::ScreenOrientation DeviceConfigParser::invertedLandscapeOrientation() const
83+{
84+ return stringToOrientation(readOrientationFromConfig("InvertedLandscapeOrientation"), Qt::InvertedLandscapeOrientation);
85+}
86+
87+Qt::ScreenOrientation DeviceConfigParser::portraitOrientation() const
88+{
89+ return stringToOrientation(readOrientationFromConfig("PortraitOrientation"), Qt::PortraitOrientation);
90+}
91+
92+Qt::ScreenOrientation DeviceConfigParser::invertedPortraitOrientation() const
93+{
94+ return stringToOrientation(readOrientationFromConfig("InvertedPortraitOrientation"), Qt::InvertedPortraitOrientation);
95+}
96+
97+QString DeviceConfigParser::category() const
98+{
99+ QStringList supportedValues = {"phone", "tablet", "desktop"};
100+ m_config->beginGroup(m_name);
101+ QString value = m_config->value("Category", "phone").toString();
102+ if (!supportedValues.contains(value)) {
103+ qWarning().nospace().noquote() << "Unknown option \"" << value << "\" in " << m_config->fileName()
104+ << ". Supported options are: " << supportedValues.join(", ") << ".";
105+ return "phone";
106+ }
107+ m_config->endGroup();
108+ return value;
109+}
110+
111+QStringList DeviceConfigParser::readOrientationsFromConfig(const QString &key) const
112+{
113+ m_config->beginGroup(m_name);
114+
115+ QStringList ret;
116+ if (m_config->contains(key)) {
117+ ret = m_config->value(key).toStringList();
118+ }
119+
120+ m_config->endGroup();
121+ return ret;
122+}
123+
124+QString DeviceConfigParser::readOrientationFromConfig(const QString &key) const
125+{
126+ QStringList ret = readOrientationsFromConfig(key);
127+ return ret.count() > 0 ? ret.first() : QString();
128+}
129+
130+Qt::ScreenOrientation DeviceConfigParser::stringToOrientation(const QString &orientationString, Qt::ScreenOrientation defaultValue) const
131+{
132+ if (orientationString == "Landscape") {
133+ return Qt::LandscapeOrientation;
134+ }
135+ if (orientationString == "InvertedLandscape") {
136+ return Qt::InvertedLandscapeOrientation;
137+ }
138+ if (orientationString == "Portrait") {
139+ return Qt::PortraitOrientation;
140+ }
141+ if (orientationString == "InvertedPortrait") {
142+ return Qt::InvertedPortraitOrientation;
143+ }
144+ if (!orientationString.isEmpty()) {
145+ // Some option we don't know. Give some hint on what went wrong.
146+ qWarning().nospace().noquote() << "Unknown option \"" << orientationString << "\" in " << m_config->fileName()
147+ << ". Supported options are: Landscape, InvertedLandscape, Portrait and InvertedPortrait.";
148+ }
149+ return defaultValue;
150+}
plugins/Utils/deviceconfigparser.hnew file+62
@@ -0,0 +1,62 @@
1+/*
2+ * Copyright 2016 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU Lesser General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef DEVICECONFIGPARSER_H
18+#define DEVICECONFIGPARSER_H
19+
20+#include <QObject>
21+#include <QSettings>
22+
23+class DeviceConfigParser: public QObject
24+{
25+ Q_OBJECT
26+ Q_PROPERTY(QString name READ name WRITE setName NOTIFY changed)
27+
28+ Q_PROPERTY(Qt::ScreenOrientation primaryOrientation READ primaryOrientation NOTIFY changed)
29+ Q_PROPERTY(Qt::ScreenOrientations supportedOrientations READ supportedOrientations NOTIFY changed)
30+ Q_PROPERTY(Qt::ScreenOrientation landscapeOrientation READ landscapeOrientation NOTIFY changed)
31+ Q_PROPERTY(Qt::ScreenOrientation invertedLandscapeOrientation READ invertedLandscapeOrientation NOTIFY changed)
32+ Q_PROPERTY(Qt::ScreenOrientation portraitOrientation READ portraitOrientation NOTIFY changed)
33+ Q_PROPERTY(Qt::ScreenOrientation invertedPortraitOrientation READ invertedPortraitOrientation NOTIFY changed)
34+ Q_PROPERTY(QString category READ category NOTIFY changed)
35+
36+public:
37+ DeviceConfigParser(QObject *parent = nullptr);
38+
39+ QString name() const;
40+ void setName(const QString &name);
41+
42+ Qt::ScreenOrientation primaryOrientation() const;
43+ Qt::ScreenOrientations supportedOrientations() const;
44+ Qt::ScreenOrientation landscapeOrientation() const;
45+ Qt::ScreenOrientation invertedLandscapeOrientation() const;
46+ Qt::ScreenOrientation portraitOrientation() const;
47+ Qt::ScreenOrientation invertedPortraitOrientation() const;
48+ QString category() const;
49+
50+Q_SIGNALS:
51+ void changed();
52+
53+private:
54+ QString m_name;
55+ QSettings *m_config;
56+
57+ QStringList readOrientationsFromConfig(const QString &key) const;
58+ QString readOrientationFromConfig(const QString &key) const;
59+ Qt::ScreenOrientation stringToOrientation(const QString &orientationString, Qt::ScreenOrientation defaultValue) const;
60+};
61+
62+#endif
plugins/Utils/globalfunctions.cppnew file+56
@@ -0,0 +1,56 @@
1+/*
2+ * Copyright 2015 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU Lesser General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+*/
16+
17+#include "globalfunctions.h"
18+
19+#pragma GCC diagnostic push
20+#pragma GCC diagnostic ignored "-pedantic"
21+#include <private/qquickitem_p.h>
22+#pragma GCC diagnostic pop
23+#include <QQmlEngine>
24+
25+GlobalFunctions::GlobalFunctions(QObject *parent)
26+ : QObject(parent)
27+{
28+}
29+
30+QQuickItem *GlobalFunctions::itemAt(QQuickItem* parent, int x, int y, QJSValue matcher)
31+{
32+ if (!parent) return nullptr;
33+ QList<QQuickItem *> children = QQuickItemPrivate::get(parent)->paintOrderChildItems();
34+
35+ for (int i = children.count() - 1; i >= 0; --i) {
36+ QQuickItem *child = children.at(i);
37+
38+ // Map coordinates to the child element's coordinate space
39+ QPointF point = parent->mapToItem(child, QPointF(x, y));
40+ if (child->isVisible() && point.x() >= 0
41+ && child->width() >= point.x()
42+ && point.y() >= 0
43+ && child->height() >= point.y()) {
44+ if (!matcher.isCallable()) return child;
45+
46+ QQmlEngine* engine = qmlEngine(child);
47+ if (!engine) return child;
48+
49+ QJSValue newObj = engine->newQObject(child);
50+ if (matcher.call(QJSValueList() << newObj).toBool()) {
51+ return child;
52+ }
53+ }
54+ }
55+ return nullptr;
56+}
plugins/Utils/globalfunctions.hnew file+42
@@ -0,0 +1,42 @@
1+/*
2+ * Copyright 2015 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU Lesser General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+*/
16+
17+#ifndef GLOBALFUNCTIONS_H
18+#define GLOBALFUNCTIONS_H
19+
20+#include <QObject>
21+#include <QJSValue>
22+class QQuickItem;
23+
24+/**
25+ * @brief The GlobalFunctions class
26+ *
27+ * This singleton class exposes utility functions to QML
28+ *
29+ */
30+class GlobalFunctions : public QObject
31+{
32+ Q_OBJECT
33+public:
34+ explicit GlobalFunctions(QObject *parent = 0);
35+
36+ static Q_INVOKABLE QQuickItem* itemAt(QQuickItem* parent,
37+ int x,
38+ int y,
39+ QJSValue matcher);
40+};
41+
42+#endif // GLOBALFUNCTIONS_H
plugins/Utils/plugin.cpp+11
@@ -37,6 +37,8 @@
3737#include "timezoneFormatter.h"
3838#include "applicationsfiltermodel.h"
3939#include "inputeventgenerator.h"
40+#include "deviceconfigparser.h"
41+#include "globalfunctions.h"
4042
4143static QObject *createWindowStateStorage(QQmlEngine *engine, QJSEngine *scriptEngine)
4244{
@@ -52,6 +54,13 @@ static QObject *createConstants(QQmlEngine *engine, QJSEngine *scriptEngine)
5254 return new Constants();
5355}
5456
57+static QObject *createGlobalFunctions(QQmlEngine *engine, QJSEngine *scriptEngine)
58+{
59+ Q_UNUSED(engine)
60+ Q_UNUSED(scriptEngine)
61+ return new GlobalFunctions();
62+}
63+
5564void UtilsPlugin::registerTypes(const char *uri)
5665{
5766 Q_ASSERT(uri == QLatin1String("Utils"));
@@ -70,6 +79,8 @@ void UtilsPlugin::registerTypes(const char *uri)
7079 qmlRegisterType<ActiveFocusLogger>(uri, 0, 1, "ActiveFocusLogger");
7180 qmlRegisterType<ApplicationsFilterModel>(uri, 0, 1, "ApplicationsFilterModel");
7281 qmlRegisterType<InputEventGenerator>(uri, 0, 1, "InputEventGenerator");
82+ qmlRegisterType<DeviceConfigParser>(uri, 0, 1, "DeviceConfigParser");
83+ qmlRegisterSingletonType<GlobalFunctions>(uri, 0, 1, "Functions", createGlobalFunctions);
7384}
7485
7586void UtilsPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
plugins/Utils/timezoneFormatter.cpp+10
@@ -33,3 +33,13 @@ QString TimezoneFormatter::currentTimeInTimezone(const QVariant &tzId) const
3333 }
3434 return QString();
3535}
36+
37+QString TimezoneFormatter::currentTimeInTimezoneWithAbbrev(const QVariant &tzId) const
38+{
39+ QTimeZone tz(tzId.toByteArray());
40+ if (tz.isValid()) {
41+ const QDateTime now = QDateTime::currentDateTime().toTimeZone(tz);
42+ return QStringLiteral("%1 %2").arg(now.time().toString(QStringLiteral("h:mm"))).arg(tz.abbreviation(now));
43+ }
44+ return QString();
45+}
plugins/Utils/timezoneFormatter.h+1
@@ -27,6 +27,7 @@ public:
2727 ~TimezoneFormatter() = default;
2828
2929 Q_INVOKABLE QString currentTimeInTimezone(const QVariant &tzId) const;
30+ Q_INVOKABLE QString currentTimeInTimezoneWithAbbrev(const QVariant &tzId) const;
3031};
3132
3233#endif
plugins/Utils/windowstatestorage.cpp+36−4
@@ -23,9 +23,14 @@
2323#include <QSqlError>
2424#include <QSqlResult>
2525#include <QRect>
26+#include <unity/shell/application/ApplicationInfoInterface.h>
2627
2728QMutex WindowStateStorage::s_mutex;
2829
30+inline QString sanitiseString(QString string) {
31+ return string.remove("\"").remove("'").remove("\\");
32+}
33+
2934WindowStateStorage::WindowStateStorage(QObject *parent):
3035 QObject(parent)
3136{
@@ -50,7 +55,7 @@ WindowStateStorage::~WindowStateStorage()
5055void WindowStateStorage::saveState(const QString &windowId, WindowStateStorage::WindowState state)
5156{
5257 const QString queryString = QStringLiteral("INSERT OR REPLACE INTO state (windowId, state) values ('%1', '%2');")
53− .arg(windowId)
58+ .arg(sanitiseString(windowId))
5459 .arg((int)state);
5560
5661 saveValue(queryString);
@@ -59,7 +64,7 @@ void WindowStateStorage::saveState(const QString &windowId, WindowStateStorage::
5964WindowStateStorage::WindowState WindowStateStorage::getState(const QString &windowId, WindowStateStorage::WindowState defaultValue) const
6065{
6166 const QString queryString = QStringLiteral("SELECT * FROM state WHERE windowId = '%1';")
62− .arg(windowId);
67+ .arg(sanitiseString(windowId));
6368
6469 QSqlQuery query = getValue(queryString);
6570
@@ -72,7 +77,7 @@ WindowStateStorage::WindowState WindowStateStorage::getState(const QString &wind
7277void WindowStateStorage::saveGeometry(const QString &windowId, const QRect rect)
7378{
7479 const QString queryString = QStringLiteral("INSERT OR REPLACE INTO geometry (windowId, x, y, width, height) values ('%1', '%2', '%3', '%4', '%5');")
75− .arg(windowId)
80+ .arg(sanitiseString(windowId))
7681 .arg(rect.x())
7782 .arg(rect.y())
7883 .arg(rect.width())
@@ -81,6 +86,28 @@ void WindowStateStorage::saveGeometry(const QString &windowId, const QRect rect)
8186 saveValue(queryString);
8287}
8388
89+void WindowStateStorage::saveStage(const QString &appId, int stage)
90+{
91+ const QString queryString = QStringLiteral("INSERT OR REPLACE INTO stage (appId, stage) values ('%1', '%2');")
92+ .arg(sanitiseString(appId))
93+ .arg((int)stage);
94+
95+ saveValue(queryString);
96+}
97+
98+int WindowStateStorage::getStage(const QString &appId) const
99+{
100+ const QString queryString = QStringLiteral("SELECT * FROM stage WHERE appId = '%1';")
101+ .arg(sanitiseString(appId));
102+
103+ QSqlQuery query = getValue(queryString);
104+
105+ if (!query.first()) {
106+ return unity::shell::application::ApplicationInfoInterface::MainStage;
107+ }
108+ return query.value("stage").toInt();
109+}
110+
84111void WindowStateStorage::executeAsyncQuery(const QString &queryString)
85112{
86113 QMutexLocker l(&s_mutex);
@@ -97,7 +124,7 @@ void WindowStateStorage::executeAsyncQuery(const QString &queryString)
97124QRect WindowStateStorage::getGeometry(const QString &windowId, const QRect defaultValue) const
98125{
99126 QString queryString = QStringLiteral("SELECT * FROM geometry WHERE windowId = '%1';")
100− .arg(windowId);
127+ .arg(sanitiseString(windowId));
101128
102129 QSqlQuery query = getValue(queryString);
103130
@@ -124,6 +151,11 @@ void WindowStateStorage::initdb()
124151 QSqlQuery query;
125152 query.exec(QStringLiteral("CREATE TABLE state(windowId TEXT UNIQUE, state INTEGER);"));
126153 }
154+
155+ if (!m_db.tables().contains(QStringLiteral("stage"))) {
156+ QSqlQuery query;
157+ query.exec(QStringLiteral("CREATE TABLE stage(appId TEXT UNIQUE, stage INTEGER);"));
158+ }
127159}
128160
129161void WindowStateStorage::saveValue(const QString &queryString)
plugins/Utils/windowstatestorage.h+3
@@ -38,6 +38,9 @@ public:
3838 Q_INVOKABLE void saveGeometry(const QString &windowId, const QRect rect);
3939 Q_INVOKABLE QRect getGeometry(const QString &windowId, const QRect defaultValue) const;
4040
41+ Q_INVOKABLE void saveStage(const QString &appId, int stage);
42+ Q_INVOKABLE int getStage(const QString &appId) const;
43+
4144private:
4245 void initdb();
4346
plugins/Wizard/CMakeLists.txt+7−1
@@ -1,10 +1,16 @@
1+include_directories(${GLIB_INCLUDE_DIRS} ${GEONAMES_INCLUDE_DIRS})
2+
13add_library(Wizard-qml MODULE
24 plugin.cpp
35 PageList.cpp
46 System.cpp
7+ LocalePlugin.cpp
8+ timezonemodel.cpp
9+ Status.cpp
510)
611
7−qt5_use_modules(Wizard-qml DBus Qml)
12+qt5_use_modules(Wizard-qml DBus Qml Concurrent)
13+target_link_libraries(Wizard-qml ${GLIB_LDFLAGS} ${GEONAMES_LDFLAGS})
814add_unity8_plugin(Wizard 0.1 Wizard TARGETS Wizard-qml)
915
1016set(POLKIT_LIB_DIR "${CMAKE_INSTALL_LOCALSTATEDIR}/lib/polkit-1")
plugins/Wizard/LocalePlugin.cppnew file+291
@@ -0,0 +1,291 @@
1+/*
2+ * Copyright (C) 2015 Canonical Ltd.
3+ *
4+ * This program is free software: you can redistribute it and/or modify it
5+ * under the terms of the GNU General Public License version 3, as published
6+ * by the Free Software Foundation.
7+ *
8+ * This program is distributed in the hope that it will be useful, but
9+ * WITHOUT ANY WARRANTY; without even the implied warranties of
10+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11+ * PURPOSE. See the GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License along
14+ * with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include <QLocale>
18+#include <QStringList>
19+
20+#include <libintl.h>
21+
22+#include "LocalePlugin.h"
23+
24+class LocalePrivate {
25+public:
26+ LocalePrivate() {
27+ m_mccCodes = QHash<int, QString>
28+ ({{202,"gr"},
29+ {204,"nl"},
30+ {206,"be"},
31+ {208,"fr"},
32+ {212,"mc"},
33+ {213,"ad"},
34+ {214,"es"},
35+ {216,"hu"},
36+ {218,"ba"},
37+ {219,"hr"},
38+ {220,"rs"},
39+ {222,"it"},
40+ {226,"ro"},
41+ {228,"ch"},
42+ {230,"cz"},
43+ {231,"sk"},
44+ {232,"at"},
45+ {234,"gb"},
46+ {235,"gb"},
47+ {238,"dk"},
48+ {240,"se"},
49+ {242,"no"},
50+ {244,"fi"},
51+ {246,"lt"},
52+ {247,"lv"},
53+ {248,"ee"},
54+ {250,"ru"},
55+ {255,"ua"},
56+ {257,"by"},
57+ {259,"md"},
58+ {260,"pl"},
59+ {262,"de"},
60+ {266,"gi"},
61+ {268,"pt"},
62+ {270,"lu"},
63+ {272,"ie"},
64+ {274,"is"},
65+ {276,"al"},
66+ {278,"mt"},
67+ {280,"cy"},
68+ {282,"ge"},
69+ {283,"am"},
70+ {284,"bg"},
71+ {286,"tr"},
72+ {288,"fo"},
73+ {289,"ge"},
74+ {290,"gl"},
75+ {292,"sm"},
76+ {293,"si"},
77+ {294,"mk"},
78+ {295,"li"},
79+ {297,"me"},
80+ {302,"ca"},
81+ {308,"pm"},
82+ {310,"gu"},
83+ {310,"us"},
84+ {311,"gu"},
85+ {311,"us"},
86+ {312,"us"},
87+ {316,"us"},
88+ {330,"pr"},
89+ {334,"mx"},
90+ {338,"jm"},
91+ {340,"fg"},
92+ {340,"gp"},
93+ {340,"mq"},
94+ {342,"bb"},
95+ {344,"ag"},
96+ {346,"ky"},
97+ {348,"vg"},
98+ {350,"bm"},
99+ {352,"gd"},
100+ {354,"ms"},
101+ {356,"kn"},
102+ {358,"lc"},
103+ {360,"vc"},
104+ {362,"an"},
105+ {362,"cw"},
106+ {363,"aw"},
107+ {364,"bs"},
108+ {365,"ai"},
109+ {366,"dm"},
110+ {368,"cu"},
111+ {370,"do"},
112+ {372,"ht"},
113+ {374,"tt"},
114+ {376,"tc"},
115+ {376,"vi"},
116+ {400,"az"},
117+ {401,"kz"},
118+ {402,"bt"},
119+ {404,"in"},
120+ {405,"in"},
121+ {410,"pk"},
122+ {412,"af"},
123+ {413,"lk"},
124+ {414,"mm"},
125+ {415,"lb"},
126+ {416,"jo"},
127+ {417,"sy"},
128+ {418,"iq"},
129+ {419,"kw"},
130+ {420,"sa"},
131+ {421,"ye"},
132+ {422,"om"},
133+ {424,"ae"},
134+ {425,"il"},
135+ {425,"ps"},
136+ {426,"bh"},
137+ {427,"qa"},
138+ {428,"mn"},
139+ {429,"np"},
140+ {430,"ae"},
141+ {431,"ae"},
142+ {432,"ir"},
143+ {434,"uz"},
144+ {436,"tk"},
145+ {437,"kg"},
146+ {438,"tm"},
147+ {440,"jp"},
148+ {441,"jp"},
149+ {450,"kr"},
150+ {452,"vn"},
151+ {454,"hk"},
152+ {455,"mo"},
153+ {456,"kh"},
154+ {457,"la"},
155+ {460,"cn"},
156+ {466,"tw"},
157+ {467,"kp"},
158+ {470,"bd"},
159+ {472,"mv"},
160+ {502,"my"},
161+ {505,"au"},
162+ {510,"id"},
163+ {514,"tp"},
164+ {515,"ph"},
165+ {520,"th"},
166+ {525,"sg"},
167+ {528,"bn"},
168+ {530,"nz"},
169+ {537,"pg"},
170+ {539,"to"},
171+ {540,"sb"},
172+ {541,"vu"},
173+ {542,"fj"},
174+ {544,"as"},
175+ {545,"ki"},
176+ {546,"nc"},
177+ {547,"pf"},
178+ {548,"ck"},
179+ {549,"ws"},
180+ {550,"fm"},
181+ {552,"pw"},
182+ {553,"tv"},
183+ {555,"nu"},
184+ {602,"eg"},
185+ {603,"dz"},
186+ {604,"ma"},
187+ {605,"tn"},
188+ {606,"ly"},
189+ {607,"gm"},
190+ {608,"sn"},
191+ {609,"mr"},
192+ {610,"ml"},
193+ {611,"gn"},
194+ {612,"ci"},
195+ {613,"bf"},
196+ {614,"ne"},
197+ {615,"tg"},
198+ {616,"bj"},
199+ {617,"mu"},
200+ {618,"lr"},
201+ {619,"sl"},
202+ {620,"gh"},
203+ {621,"ng"},
204+ {622,"td"},
205+ {623,"cf"},
206+ {624,"cm"},
207+ {625,"cv"},
208+ {626,"st"},
209+ {627,"gq"},
210+ {628,"ga"},
211+ {629,"cg"},
212+ {630,"cd"},
213+ {631,"ao"},
214+ {632,"gw"},
215+ {633,"sc"},
216+ {634,"sd"},
217+ {635,"rw"},
218+ {636,"et"},
219+ {637,"so"},
220+ {638,"dj"},
221+ {639,"ke"},
222+ {640,"tz"},
223+ {641,"ug"},
224+ {642,"bi"},
225+ {643,"mz"},
226+ {645,"zm"},
227+ {646,"mg"},
228+ {647,"re"},
229+ {648,"zw"},
230+ {649,"na"},
231+ {650,"mw"},
232+ {651,"ls"},
233+ {652,"bw"},
234+ {653,"sz"},
235+ {654,"km"},
236+ {655,"za"},
237+ {657,"er"},
238+ {659,"ss"},
239+ {702,"bz"},
240+ {704,"gt"},
241+ {706,"sv"},
242+ {708,"hn"},
243+ {710,"ni"},
244+ {712,"cr"},
245+ {714,"pa"},
246+ {716,"pe"},
247+ {722,"ar"},
248+ {724,"br"},
249+ {730,"cl"},
250+ {732,"co"},
251+ {734,"ve"},
252+ {736,"bo"},
253+ {738,"gy"},
254+ {740,"ec"},
255+ {744,"py"},
256+ {746,"sr"},
257+ {748,"uy"},
258+ {750,"fk"}}
259+ );
260+ }
261+
262+ QString mccToCountryCode(int mcc) const {
263+ return m_mccCodes.value(mcc, "us").toUpper();
264+ }
265+
266+private:
267+ // MCC = Mobile Country Code, see https://en.wikipedia.org/wiki/Mobile_country_code
268+ QHash<int,QString> m_mccCodes;
269+};
270+
271+Q_GLOBAL_STATIC(LocalePrivate, d)
272+
273+LocaleAttached::LocaleAttached(QObject* parent)
274+ : QObject(parent)
275+{
276+}
277+
278+QString LocaleAttached::mccToCountryCode(int mcc) const
279+{
280+ return d->mccToCountryCode(mcc);
281+}
282+
283+LocalePlugin::LocalePlugin(QObject* parent)
284+ : QObject(parent)
285+{
286+}
287+
288+LocaleAttached* LocalePlugin::qmlAttachedProperties(QObject* parent)
289+{
290+ return new LocaleAttached(parent);
291+}
plugins/Wizard/LocalePlugin.hnew file+58
@@ -0,0 +1,58 @@
1+/*
2+ * Copyright (C) 2015 Canonical Ltd.
3+ *
4+ * This program is free software: you can redistribute it and/or modify it
5+ * under the terms of the GNU General Public License version 3, as published
6+ * by the Free Software Foundation.
7+ *
8+ * This program is distributed in the hope that it will be useful, but
9+ * WITHOUT ANY WARRANTY; without even the implied warranties of
10+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11+ * PURPOSE. See the GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License along
14+ * with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef LOCALE_PLUGIN_H
18+#define LOCALE_PLUGIN_H
19+
20+#include <QObject>
21+#include <QString>
22+#include <QJsonObject>
23+#include <QtQml>
24+
25+class LocaleAttached: public QObject
26+{
27+ Q_OBJECT
28+protected:
29+ explicit LocaleAttached(QObject *parent = 0);
30+
31+public:
32+ Q_INVOKABLE QString mccToCountryCode(int mcc) const;
33+
34+ friend class LocalePlugin;
35+};
36+
37+/**
38+ * A simplified wrapper around QLocale.
39+ *
40+ * The wrapper is implemented as an attached property, which makes it possible
41+ * to use it as if its methods were static:
42+ *
43+ * @code
44+ * var langs = LocalePlugin.languages();
45+ * @endcode
46+ */
47+class LocalePlugin: public QObject
48+{
49+ Q_OBJECT
50+public:
51+ explicit LocalePlugin(QObject *parent = 0);
52+
53+ static LocaleAttached* qmlAttachedProperties(QObject *parent);
54+};
55+
56+QML_DECLARE_TYPEINFO(LocalePlugin, QML_HAS_ATTACHED_PROPERTIES)
57+
58+#endif // LOCALE_PLUGIN_H
plugins/Wizard/PageList.cpp+4−4
@@ -40,20 +40,20 @@ PageList::PageList(QObject *parent)
4040 m_index(-1),
4141 m_pages()
4242{
43− QString qmlSuffix = QStringLiteral(".qml");
44− QString disabledSuffix = QStringLiteral(".disabled");
43+ const QString qmlSuffix = QStringLiteral(".qml");
44+ const QString disabledSuffix = QStringLiteral(".disabled");
4545 QSet<QString> disabledPages;
4646 QStringList dataDirs;
4747
4848 if (!isRunningInstalled() && getenv("WIZARD_TESTING") == nullptr) {
49− dataDirs = QStringList() << qmlDirectory();
49+ dataDirs << qmlDirectory();
5050 } else {
5151 dataDirs = shellDataDirs();
5252 }
5353
5454 Q_FOREACH(const QString &dataDir, dataDirs) {
5555 QDir dir(dataDir + "/Wizard/Pages");
56− QStringList entries = dir.entryList(QStringList(QStringLiteral("[0-9]*")), QDir::Files | QDir::Readable);
56+ const QStringList entries = dir.entryList(QStringList(QStringLiteral("[0-9]*")), QDir::Files | QDir::Readable);
5757 Q_FOREACH(const QString &entry, entries) {
5858 if (!m_pages.contains(entry) && entry.endsWith(qmlSuffix))
5959 m_pages.insert(entry, dir.absoluteFilePath(entry));
plugins/Wizard/Status.cppnew file+143
@@ -0,0 +1,143 @@
1+/*
2+ * Copyright (C) 2015 Canonical Ltd.
3+ *
4+ * This program is free software: you can redistribute it and/or modify it
5+ * under the terms of the GNU General Public License version 3, as published
6+ * by the Free Software Foundation.
7+ *
8+ * This program is distributed in the hope that it will be useful, but
9+ * WITHOUT ANY WARRANTY; without even the implied warranties of
10+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11+ * PURPOSE. See the GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License along
14+ * with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include <QDebug>
18+#include <QDBusConnection>
19+
20+#include "Status.h"
21+
22+Status::Status()
23+{
24+ initNM();
25+ initUPower();
26+}
27+
28+void Status::initNM()
29+{
30+ m_nmIface = new QDBusInterface("org.freedesktop.NetworkManager", "/org/freedesktop/NetworkManager", "org.freedesktop.NetworkManager",
31+ QDBusConnection::systemBus(), this);
32+
33+ QDBusConnection::systemBus().connect("org.freedesktop.NetworkManager", "/org/freedesktop/NetworkManager", "org.freedesktop.NetworkManager", "PropertiesChanged",
34+ this, SLOT(onNMPropertiesChanged(QVariantMap)));
35+}
36+
37+void Status::onNMPropertiesChanged(const QVariantMap &changedProps)
38+{
39+ if (changedProps.contains("State") || changedProps.contains("Connectivity")) {
40+ Q_EMIT onlineChanged();
41+ Q_EMIT networkIconChanged();
42+ }
43+
44+ if (changedProps.contains("PrimaryConnection") || changedProps.contains("SpecificObject") || changedProps.contains("Strength")) {
45+ Q_EMIT networkIconChanged();
46+ }
47+}
48+
49+bool Status::online() const
50+{
51+ if (!m_nmIface->isValid())
52+ return false;
53+
54+ return m_nmIface->property("State").toUInt() == 70;
55+}
56+
57+QString Status::networkIcon()
58+{
59+ QString iconName = QStringLiteral("nm-no-connection");
60+
61+ if (!online()) {
62+ return iconName;
63+ }
64+
65+ const QString primaryConn = m_nmIface->property("PrimaryConnection").value<QDBusObjectPath>().path();
66+ const QString primaryConnType = m_nmIface->property("PrimaryConnectionType").toString();
67+
68+ if (primaryConn.isEmpty()) {
69+ qWarning() << "Empty primary connection";
70+ return iconName;
71+ }
72+
73+ if (primaryConnType == "802-11-wireless") {
74+ QDBusInterface activeConn("org.freedesktop.NetworkManager", primaryConn, "org.freedesktop.NetworkManager.Connection.Active", QDBusConnection::systemBus());
75+
76+ if (activeConn.isValid()) {
77+ const QString apPath = activeConn.property("SpecificObject").value<QDBusObjectPath>().path();
78+
79+ if (apPath.isEmpty()) {
80+ qWarning() << "No AP path";
81+ return iconName;
82+ }
83+
84+ QDBusConnection::systemBus().connect("org.freedesktop.NetworkManager", primaryConn, "org.freedesktop.NetworkManager.Connection.Active", "PropertiesChanged",
85+ this, SLOT(onNMPropertiesChanged(QVariantMap)));
86+
87+ QDBusInterface ap("org.freedesktop.NetworkManager", apPath, "org.freedesktop.NetworkManager.AccessPoint", QDBusConnection::systemBus());
88+
89+ if (!ap.isValid()) {
90+ qWarning() << "Invalid AP";
91+ return iconName;
92+ }
93+
94+ QDBusConnection::systemBus().connect("org.freedesktop.NetworkManager", apPath, "org.freedesktop.NetworkManager.AccessPoint", "PropertiesChanged",
95+ this, SLOT(onNMPropertiesChanged(QVariantMap)));
96+
97+ const uint strength = ap.property("Strength").toUInt();
98+ const uint flags = ap.property("Flags").toUInt();
99+
100+ if (strength == 0) {
101+ iconName = "nm-signal-00";
102+ } else if (strength <= 25) {
103+ iconName = "nm-signal-25";
104+ } else if (strength <= 50) {
105+ iconName = "nm-signal-50";
106+ } else if (strength <= 75) {
107+ iconName = "nm-signal-75";
108+ } else if (strength <= 100) {
109+ iconName = "nm-signal-100";
110+ }
111+
112+ if (flags >= 1) {
113+ iconName += "-secure";
114+ }
115+ }
116+ }
117+
118+ return iconName;
119+}
120+
121+void Status::initUPower()
122+{
123+ m_upowerIface = new QDBusInterface("org.freedesktop.UPower", "/org/freedesktop/UPower/devices/DisplayDevice", "org.freedesktop.UPower.Device",
124+ QDBusConnection::systemBus(), this);
125+ QDBusConnection::systemBus().connect("org.freedesktop.UPower", "/org/freedesktop/UPower/devices/DisplayDevice", "org.freedesktop.DBus.Properties",
126+ "PropertiesChanged", this, SLOT(onUPowerPropertiesChanged(QString,QVariantMap,QStringList)));
127+}
128+
129+void Status::onUPowerPropertiesChanged(const QString &iface, const QVariantMap &changedProps, const QStringList &invalidatedProps)
130+{
131+ Q_UNUSED(iface)
132+ Q_UNUSED(invalidatedProps)
133+
134+ if (changedProps.contains("IconName")) {
135+ Q_EMIT batteryIconChanged();
136+ }
137+}
138+
139+QString Status::batteryIcon() const
140+{
141+ const QString iconName = m_upowerIface->property("IconName").toString();
142+ return iconName;
143+}
plugins/Wizard/Status.hnew file+60
@@ -0,0 +1,60 @@
1+/*
2+ * Copyright (C) 2015 Canonical Ltd.
3+ *
4+ * This program is free software: you can redistribute it and/or modify it
5+ * under the terms of the GNU General Public License version 3, as published
6+ * by the Free Software Foundation.
7+ *
8+ * This program is distributed in the hope that it will be useful, but
9+ * WITHOUT ANY WARRANTY; without even the implied warranties of
10+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11+ * PURPOSE. See the GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License along
14+ * with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef WIZARD_STATUS_H
18+#define WIZARD_STATUS_H
19+
20+#include <QObject>
21+#include <QString>
22+#include <QDBusInterface>
23+
24+class Status: public QObject
25+{
26+ Q_OBJECT
27+ Q_PROPERTY(bool online READ online NOTIFY onlineChanged)
28+ Q_PROPERTY(QString networkIcon READ networkIcon NOTIFY networkIconChanged)
29+ Q_PROPERTY(QString batteryIcon READ batteryIcon NOTIFY batteryIconChanged)
30+public:
31+ Status();
32+ ~Status() = default;
33+
34+ bool online() const;
35+ QString networkIcon();
36+
37+ QString batteryIcon() const;
38+
39+Q_SIGNALS:
40+ void networkIconChanged();
41+ void onlineChanged();
42+ void batteryIconChanged();
43+
44+private Q_SLOTS:
45+ void onNMPropertiesChanged(const QVariantMap &changedProps);
46+ void onUPowerPropertiesChanged(const QString &iface, const QVariantMap &changedProps, const QStringList &invalidatedProps);
47+
48+private:
49+ Q_DISABLE_COPY(Status)
50+
51+ // network status
52+ void initNM();
53+ QDBusInterface * m_nmIface = nullptr;
54+
55+ // battery status
56+ void initUPower();
57+ QDBusInterface * m_upowerIface = nullptr;
58+};
59+
60+#endif
plugins/Wizard/System.cpp+5−4
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2014 Canonical Ltd.
2+ * Copyright (C) 2014-2015 Canonical Ltd.
33 *
44 * This program is free software: you can redistribute it and/or modify it
55 * under the terms of the GNU General Public License version 3, as published
@@ -25,10 +25,10 @@
2525#include <QLocale>
2626#include <QMap>
2727#include <QProcess>
28+#include <QDebug>
2829
2930System::System()
30− : QObject(),
31− m_fsWatcher()
31+ : QObject()
3232{
3333 // Register the argument needed for UpdateActivationEnvironment below
3434 qDBusRegisterMetaType<QMap<QString,QString>>();
@@ -88,7 +88,7 @@ void System::setSessionVariable(const QString &variable, const QString &value)
8888 QDBusConnection::sessionBus().asyncCall(msg);
8989}
9090
91−void System::updateSessionLanguage(const QString &locale)
91+void System::updateSessionLocale(const QString &locale)
9292{
9393 const QString language = locale.split(QStringLiteral("."))[0];
9494
@@ -106,5 +106,6 @@ void System::updateSessionLanguage(const QString &locale)
106106 initctl stop smart-scopes-proxy; \
107107 initctl emit --no-wait indicator-services-start; \
108108 initctl restart --no-wait maliit-server; \
109+ initctl restart --no-wait indicator-messages; \
109110 initctl restart --no-wait unity8-dash\""));
110111}
plugins/Wizard/System.h+3−2
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2014 Canonical Ltd.
2+ * Copyright (C) 2014-2015 Canonical Ltd.
33 *
44 * This program is free software: you can redistribute it and/or modify it
55 * under the terms of the GNU General Public License version 3, as published
@@ -28,12 +28,13 @@ class System : public QObject
2828
2929public:
3030 System();
31+ ~System() = default;
3132
3233 bool wizardEnabled() const;
3334 void setWizardEnabled(bool enabled);
3435
3536public Q_SLOTS:
36− void updateSessionLanguage(const QString &locale);
37+ void updateSessionLocale(const QString &locale);
3738
3839Q_SIGNALS:
3940 void wizardEnabledChanged();
plugins/Wizard/plugin.cpp+7−8
@@ -17,19 +17,18 @@
1717#include "plugin.h"
1818#include "PageList.h"
1919#include "System.h"
20+#include "timezonemodel.h"
21+#include "LocalePlugin.h"
22+#include "Status.h"
2023
2124#include <QtQml/qqml.h>
2225
23−static QObject *system_provider(QQmlEngine *engine, QJSEngine *scriptEngine)
24−{
25− Q_UNUSED(engine)
26− Q_UNUSED(scriptEngine)
27− return new System();
28−}
29−
3026void WizardPlugin::registerTypes(const char *uri)
3127{
3228 Q_ASSERT(uri == QLatin1String("Wizard"));
3329 qmlRegisterType<PageList>(uri, 0, 1, "PageList");
34− qmlRegisterSingletonType<System>(uri, 0, 1, "System", system_provider);
30+ qmlRegisterSingletonType<System>(uri, 0, 1, "System", [](QQmlEngine*, QJSEngine*) -> QObject* { return new System; });
31+ qmlRegisterSingletonType<Status>(uri, 0, 1, "Status", [](QQmlEngine*, QJSEngine*) -> QObject* { return new Status; });
32+ qmlRegisterType<TimeZoneLocationModel>(uri, 0, 1, "TimeZoneModel");
33+ qmlRegisterType<LocalePlugin>(uri, 0, 1, "LocalePlugin");
3534}
plugins/Wizard/plugin.h+1−2
@@ -17,13 +17,12 @@
1717#ifndef WIZARD_PLUGIN_H
1818#define WIZARD_PLUGIN_H
1919
20−#include <QtQml/QQmlEngine>
2120#include <QtQml/QQmlExtensionPlugin>
2221
2322class WizardPlugin : public QQmlExtensionPlugin
2423{
2524 Q_OBJECT
26− Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
25+ Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid)
2726
2827public:
2928 void registerTypes(const char *uri) override;
plugins/Wizard/timezonemodel.cppnew file+239
@@ -0,0 +1,239 @@
1+/*
2+ * Copyright (C) 2016 Canonical Ltd.
3+ *
4+ * This program is free software: you can redistribute it and/or modify it
5+ * under the terms of the GNU General Public License version 3, as published
6+ * by the Free Software Foundation.
7+ *
8+ * This program is distributed in the hope that it will be useful, but
9+ * WITHOUT ANY WARRANTY; without even the implied warranties of
10+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11+ * PURPOSE. See the GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License along
14+ * with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include <QDebug>
18+
19+#include <glib.h>
20+#include <glib-object.h>
21+
22+#include "LocalePlugin.h"
23+#include "timezonemodel.h"
24+
25+TimeZoneLocationModel::TimeZoneLocationModel(QObject *parent):
26+ QAbstractListModel(parent),
27+ m_listUpdating(false),
28+ m_cancellable(nullptr)
29+{
30+ m_roleNames[Qt::DisplayRole] = "displayName";
31+ m_roleNames[TimeZoneRole] = "timeZone";
32+ m_roleNames[CityRole] = "city";
33+ m_roleNames[CountryRole] = "country";
34+ m_roleNames[OffsetRole] = "offset";
35+ m_roleNames[LatitudeRole] = "latitude";
36+ m_roleNames[LongitudeRole] = "longitude";
37+}
38+
39+int TimeZoneLocationModel::rowCount(const QModelIndex &parent) const
40+{
41+ if (parent.isValid()) {
42+ return 0;
43+ } else if (m_filter.isEmpty()) {
44+ return m_countryLocations.count();
45+ } else {
46+ return m_locations.count();
47+ }
48+}
49+
50+QVariant TimeZoneLocationModel::data(const QModelIndex &index, int role) const
51+{
52+ GeonamesCity *city;
53+ if (m_filter.isEmpty()) {
54+ city = m_countryLocations.value(index.row());
55+ } else {
56+ city = m_locations.value(index.row());
57+ }
58+ if (!city)
59+ return QVariant();
60+
61+ switch (role) {
62+ case Qt::DisplayRole:
63+ return QStringLiteral("%1, %2, %3").arg(geonames_city_get_name(city))
64+ .arg(geonames_city_get_state(city))
65+ .arg(geonames_city_get_country(city));
66+ case SimpleRole:
67+ return QStringLiteral("%1, %2").arg(geonames_city_get_name(city))
68+ .arg(geonames_city_get_country(city));
69+ case TimeZoneRole:
70+ return geonames_city_get_timezone(city);
71+ case CountryRole:
72+ return geonames_city_get_country(city);
73+ case CityRole:
74+ return geonames_city_get_name(city);
75+ case OffsetRole: {
76+ QTimeZone tmp(geonames_city_get_timezone(city));
77+ return static_cast<double>(tmp.standardTimeOffset(QDateTime::currentDateTime())) / 3600;
78+ }
79+ case LatitudeRole:
80+ return geonames_city_get_latitude(city);
81+ case LongitudeRole:
82+ return geonames_city_get_longitude(city);
83+ default:
84+ qWarning() << Q_FUNC_INFO << "Unknown role";
85+ return QVariant();
86+ }
87+}
88+
89+QHash<int, QByteArray> TimeZoneLocationModel::roleNames() const
90+{
91+ return m_roleNames;
92+}
93+
94+void TimeZoneLocationModel::setModel(const QList<GeonamesCity *> &locations)
95+{
96+ beginResetModel();
97+
98+ Q_FOREACH(GeonamesCity *city, m_locations) {
99+ geonames_city_free(city);
100+ }
101+
102+ m_locations = locations;
103+ endResetModel();
104+}
105+
106+void TimeZoneLocationModel::filterFinished(GObject *source_object,
107+ GAsyncResult *res,
108+ gpointer user_data)
109+{
110+ Q_UNUSED(source_object);
111+
112+ g_autofree gint *cities = nullptr;
113+ guint cities_len = 0;
114+ g_autoptr(GError) error = nullptr;
115+
116+ cities = geonames_query_cities_finish(res, &cities_len, &error);
117+ if (error) {
118+ if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
119+ TimeZoneLocationModel *model = static_cast<TimeZoneLocationModel *>(user_data);
120+ g_clear_object(&model->m_cancellable);
121+ model->setListUpdating(false);
122+ qWarning() << "Could not filter timezones:" << error->message;
123+ }
124+ return;
125+ }
126+
127+ QList<GeonamesCity *> locations;
128+
129+ for (guint i = 0; i < cities_len; ++i) {
130+ GeonamesCity *city = geonames_get_city(cities[i]);
131+ if (city) {
132+ locations.append(city);
133+ }
134+ }
135+
136+ TimeZoneLocationModel *model = static_cast<TimeZoneLocationModel *>(user_data);
137+
138+ g_clear_object(&model->m_cancellable);
139+
140+ model->setModel(locations);
141+ model->setListUpdating(false);
142+}
143+
144+bool TimeZoneLocationModel::listUpdating() const
145+{
146+ return m_listUpdating;
147+}
148+
149+void TimeZoneLocationModel::setListUpdating(bool listUpdating)
150+{
151+ if (m_listUpdating != listUpdating) {
152+ m_listUpdating = listUpdating;
153+ Q_EMIT listUpdatingChanged();
154+ }
155+}
156+
157+QString TimeZoneLocationModel::filter() const
158+{
159+ return m_filter;
160+}
161+
162+void TimeZoneLocationModel::setFilter(const QString &filter)
163+{
164+ if (filter != m_filter) {
165+ m_filter = filter;
166+ Q_EMIT filterChanged();
167+ }
168+
169+ setListUpdating(true);
170+
171+ if (m_cancellable) {
172+ g_cancellable_cancel(m_cancellable);
173+ g_clear_object(&m_cancellable);
174+ }
175+
176+ setModel(QList<GeonamesCity *>());
177+
178+ if (filter.isEmpty()) {
179+ setListUpdating(false);
180+ return;
181+ }
182+
183+ m_cancellable = g_cancellable_new();
184+ geonames_query_cities(filter.toUtf8().data(),
185+ GEONAMES_QUERY_DEFAULT,
186+ m_cancellable,
187+ filterFinished,
188+ this);
189+}
190+
191+QString TimeZoneLocationModel::country() const
192+{
193+ return m_country;
194+}
195+
196+static bool citycmp(GeonamesCity *a, GeonamesCity *b)
197+{
198+ return geonames_city_get_population(b) < geonames_city_get_population(a);
199+}
200+
201+void TimeZoneLocationModel::setCountry(const QString &country)
202+{
203+ if (m_country == country)
204+ return;
205+
206+ m_country = country;
207+
208+ Q_FOREACH(GeonamesCity *city, m_countryLocations) {
209+ geonames_city_free(city);
210+ }
211+
212+ gint num_cities = geonames_get_n_cities();
213+ for (gint i = 0; i < num_cities; i++) {
214+ GeonamesCity *city = geonames_get_city(i);
215+ if (city && m_country == geonames_city_get_country_code(city)) {
216+ m_countryLocations.append(city);
217+ }
218+ }
219+
220+ std::sort(m_countryLocations.begin(), m_countryLocations.end(), citycmp);
221+
222+ Q_EMIT countryChanged(country);
223+}
224+
225+TimeZoneLocationModel::~TimeZoneLocationModel()
226+{
227+ if (m_cancellable) {
228+ g_cancellable_cancel(m_cancellable);
229+ g_clear_object(&m_cancellable);
230+ }
231+
232+ Q_FOREACH(GeonamesCity *city, m_countryLocations) {
233+ geonames_city_free(city);
234+ }
235+
236+ Q_FOREACH(GeonamesCity *city, m_locations) {
237+ geonames_city_free(city);
238+ }
239+}
plugins/Wizard/timezonemodel.hnew file+80
@@ -0,0 +1,80 @@
1+/*
2+ * Copyright (C) 2016 Canonical Ltd.
3+ *
4+ * This program is free software: you can redistribute it and/or modify it
5+ * under the terms of the GNU General Public License version 3, as published
6+ * by the Free Software Foundation.
7+ *
8+ * This program is distributed in the hope that it will be useful, but
9+ * WITHOUT ANY WARRANTY; without even the implied warranties of
10+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11+ * PURPOSE. See the GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License along
14+ * with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef TIMEZONEMODEL_H
18+#define TIMEZONEMODEL_H
19+
20+#include <geonames.h>
21+#include <glib.h>
22+#include <QAbstractListModel>
23+
24+class TimeZoneLocationModel: public QAbstractListModel
25+{
26+ Q_OBJECT
27+ Q_PROPERTY(bool listUpdating READ listUpdating NOTIFY listUpdatingChanged)
28+ Q_PROPERTY(QString filter READ filter WRITE setFilter NOTIFY filterChanged)
29+ Q_PROPERTY(QString country READ country WRITE setCountry NOTIFY countryChanged)
30+ Q_ENUMS(Roles)
31+
32+public:
33+ explicit TimeZoneLocationModel(QObject *parent = nullptr);
34+ ~TimeZoneLocationModel();
35+
36+ enum Roles {
37+ TimeZoneRole = Qt::UserRole + 1,
38+ CityRole,
39+ CountryRole,
40+ SimpleRole,
41+ OffsetRole,
42+ LatitudeRole,
43+ LongitudeRole
44+ };
45+
46+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
47+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
48+ QHash<int, QByteArray> roleNames() const override;
49+
50+ bool listUpdating() const;
51+
52+ QString filter() const;
53+ void setFilter(const QString &filter);
54+
55+ QString country() const;
56+ void setCountry(const QString &country);
57+
58+Q_SIGNALS:
59+ void listUpdatingChanged();
60+ void filterChanged();
61+ void countryChanged(const QString &country);
62+
63+private:
64+ void setModel(const QList<GeonamesCity *> &locations);
65+ void setListUpdating(bool listUpdating);
66+ static void filterFinished(GObject *source_object,
67+ GAsyncResult *res,
68+ gpointer user_data);
69+
70+
71+ bool m_listUpdating;
72+ QString m_filter;
73+ QString m_country;
74+ GCancellable *m_cancellable;
75+ QHash<int, QByteArray> m_roleNames;
76+ QList<GeonamesCity *> m_locations;
77+ QList<GeonamesCity *> m_countryLocations;
78+};
79+
80+#endif
po/unity8.pot+29−27
@@ -8,7 +8,7 @@ msgid ""
88msgstr ""
99"Project-Id-Version: unity8\n"
1010"Report-Msgid-Bugs-To: \n"
11−"POT-Creation-Date: 2016-03-08 21:04+0000\n"
11+"POT-Creation-Date: 2016-03-10 22:48+0000\n"
1212"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
1313"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
1414"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -22,16 +22,16 @@ msgstr ""
2222msgid "Password: "
2323msgstr ""
2424
25−#: plugins/Unity/Launcher/launcheritem.cpp:47
26−#: plugins/Unity/Launcher/launcheritem.cpp:106
25+#: plugins/Unity/Launcher/launcheritem.cpp:48
26+#: plugins/Unity/Launcher/launcheritem.cpp:107
2727msgid "Pin shortcut"
2828msgstr ""
2929
30−#: plugins/Unity/Launcher/launcheritem.cpp:52
30+#: plugins/Unity/Launcher/launcheritem.cpp:53
3131msgid "Quit"
3232msgstr ""
3333
34−#: plugins/Unity/Launcher/launcheritem.cpp:106
34+#: plugins/Unity/Launcher/launcheritem.cpp:107
3535msgid "Unpin shortcut"
3636msgstr ""
3737
@@ -135,15 +135,15 @@ msgctxt "Button: Restart the system"
135135msgid "Restart"
136136msgstr ""
137137
138−#: qml/Components/Lockscreen.qml:245
138+#: qml/Components/Lockscreen.qml:231
139139msgid "Return to Call"
140140msgstr ""
141141
142−#: qml/Components/Lockscreen.qml:245
142+#: qml/Components/Lockscreen.qml:231
143143msgid "Emergency Call"
144144msgstr ""
145145
146−#: qml/Components/Lockscreen.qml:277
146+#: qml/Components/Lockscreen.qml:263
147147msgid "OK"
148148msgstr ""
149149
@@ -152,6 +152,8 @@ msgid "Apps may have unsaved data:"
152152msgstr ""
153153
154154#: qml/Components/ModeSwitchWarningDialog.qml:57
155+msgctxt ""
156+"Re-dock means connect the device again to an external screen/mouse/keyboard"
155157msgid "Re-dock, save your work and close these apps to continue."
156158msgstr ""
157159
@@ -194,15 +196,15 @@ msgstr ""
194196msgid "Add to Favorites"
195197msgstr ""
196198
197−#: qml/Dash/GenericScopeView.qml:565 qml/Dash/GenericScopeView.qml:723
199+#: qml/Dash/GenericScopeView.qml:567 qml/Dash/GenericScopeView.qml:725
198200msgid "See less"
199201msgstr ""
200202
201−#: qml/Dash/GenericScopeView.qml:565
203+#: qml/Dash/GenericScopeView.qml:567
202204msgid "See all"
203205msgstr ""
204206
205−#: qml/Dash/GenericScopeView.qml:627
207+#: qml/Dash/GenericScopeView.qml:629
206208msgctxt "Label: Hint for dash search line edit"
207209msgid "Search"
208210msgstr ""
@@ -228,7 +230,7 @@ msgstr ""
228230msgid "Add a review"
229231msgstr ""
230232
231−#: qml/Dash/Previews/PreviewSharing.qml:50
233+#: qml/Dash/Previews/PreviewSharing.qml:66
232234msgid "Preview Share Item"
233235msgstr ""
234236
@@ -256,13 +258,13 @@ msgstr ""
256258msgid "Also installed"
257259msgstr ""
258260
259−#: qml/DisabledScreenNotice.qml:87
261+#: qml/DisabledScreenNotice.qml:104
260262msgid ""
261263"Your device is now connected to an external display. Use this screen as a "
262−"touch pad to interact with the mouse."
264+"touch pad to interact with the pointer."
263265msgstr ""
264266
265−#: qml/Greeter/CoverPage.qml:107
267+#: qml/Greeter/CoverPage.qml:118
266268msgid "Unlock"
267269msgstr ""
268270
@@ -341,62 +343,62 @@ msgstr ""
341343msgid "Conference"
342344msgstr ""
343345
344−#: qml/Panel/Indicators/MenuItemFactory.qml:753
346+#: qml/Panel/Indicators/MenuItemFactory.qml:752
345347msgid "Nothing is playing"
346348msgstr ""
347349
348−#: qml/Panel/Indicators/MenuItemFactory.qml:882
350+#: qml/Panel/Indicators/MenuItemFactory.qml:881
349351#, qt-format
350352msgid "%1 hour"
351353msgid_plural "%1 hours"
352354msgstr[0] ""
353355msgstr[1] ""
354356
355−#: qml/Panel/Indicators/MenuItemFactory.qml:886
357+#: qml/Panel/Indicators/MenuItemFactory.qml:885
356358#, qt-format
357359msgid "%1 minute"
358360msgid_plural "%1 minutes"
359361msgstr[0] ""
360362msgstr[1] ""
361363
362−#: qml/Panel/Indicators/MenuItemFactory.qml:891
364+#: qml/Panel/Indicators/MenuItemFactory.qml:890
363365#, qt-format
364366msgid "%1 second"
365367msgid_plural "%1 seconds"
366368msgstr[0] ""
367369msgstr[1] ""
368370
369−#: qml/Panel/Indicators/MenuItemFactory.qml:894
371+#: qml/Panel/Indicators/MenuItemFactory.qml:893
370372msgid "0 seconds"
371373msgstr ""
372374
373375#. Translators: String like "1 hour, 2 minutes, 3 seconds remaining"
374−#: qml/Panel/Indicators/MenuItemFactory.qml:896
376+#: qml/Panel/Indicators/MenuItemFactory.qml:895
375377#, qt-format
376378msgid "%1 remaining"
377379msgstr ""
378380
379−#: qml/Panel/Indicators/MenuItemFactory.qml:902
381+#: qml/Panel/Indicators/MenuItemFactory.qml:901
380382msgid "In queue…"
381383msgstr ""
382384
383−#: qml/Panel/Indicators/MenuItemFactory.qml:906
385+#: qml/Panel/Indicators/MenuItemFactory.qml:905
384386msgid "Downloading"
385387msgstr ""
386388
387−#: qml/Panel/Indicators/MenuItemFactory.qml:908
389+#: qml/Panel/Indicators/MenuItemFactory.qml:907
388390msgid "Paused, tap to resume"
389391msgstr ""
390392
391−#: qml/Panel/Indicators/MenuItemFactory.qml:910
393+#: qml/Panel/Indicators/MenuItemFactory.qml:909
392394msgid "Canceled"
393395msgstr ""
394396
395−#: qml/Panel/Indicators/MenuItemFactory.qml:912
397+#: qml/Panel/Indicators/MenuItemFactory.qml:911
396398msgid "Finished"
397399msgstr ""
398400
399−#: qml/Panel/Indicators/MenuItemFactory.qml:914
401+#: qml/Panel/Indicators/MenuItemFactory.qml:913
400402msgid "Failed, tap to retry"
401403msgstr ""
402404
qml/Components/EdgeBarrier.qml+1−1
@@ -105,7 +105,7 @@ Item {
105105 },
106106 Transition {
107107 from: "resisting"; to: "passed"
108− UbuntuNumberAnimation { target: materialContainer; property: "opacity" }
108+ UbuntuNumberAnimation { duration: UbuntuAnimation.BriskDuration; target: materialContainer; property: "opacity" }
109109 }
110110 ]
111111}
qml/Components/InputMethod.qml+1−9
@@ -22,15 +22,6 @@ import Ubuntu.Gestures 0.1
2222Item {
2323 id: root
2424
25− Connections {
26− target: SurfaceManager
27− onSurfaceCreated: {
28− if (surface.type == Mir.InputMethodType) {
29− surfaceItem.surface = surface;
30− }
31− }
32− }
33−
3425 property int transitionDuration: UbuntuAnimation.FastDuration
3526
3627 MirSurfaceItem {
@@ -41,6 +32,7 @@ Item {
4132
4233 surfaceWidth: width
4334 surfaceHeight: height
35+ surface: SurfaceManager.inputMethodSurface
4436
4537 onLiveChanged: {
4638 if (surface !== null && !live) {
qml/Components/Lockscreen.qml+80−94
@@ -121,108 +121,94 @@ Showable {
121121 opacity: root.darkenBackground
122122 }
123123
124− MouseArea {
125− anchors.fill: root
126− onClicked: {
127− if (pinPadLoader.item)
128− pinPadLoader.item.forceActiveFocus()
129− }
130− }
131−
132− FocusScope {
133− id: loaderScope
124+ Loader {
125+ id: pinPadLoader
126+ objectName: "pinPadLoader"
134127 anchors.fill: parent
135−
136− Loader {
137− id: pinPadLoader
138− objectName: "pinPadLoader"
139− anchors.fill: parent
140− property bool resetting: false
141− property bool waiting: false
142− property bool showWrongText: false
143−
144− source: {
145− if (resetting || !root.required) {
146− return ""
147− } else if (root.delayMinutes > 0) {
148− return "DelayedLockscreen.qml"
149− } else if (root.alphaNumeric) {
150− return "PassphraseLockscreen.qml"
151− } else {
152− return "PinLockscreen.qml"
153− }
154− }
155− onSourceChanged: {
156− waiting = false
157− showWrongText = false
158− if (loaderScope.activeFocus && pinPadLoader.item)
159− pinPadLoader.item.forceActiveFocus()
128+ property bool resetting: false
129+ property bool waiting: false
130+ property bool showWrongText: false
131+ focus: true
132+
133+ source: {
134+ if (resetting || !root.required) {
135+ return ""
136+ } else if (root.delayMinutes > 0) {
137+ return "DelayedLockscreen.qml"
138+ } else if (root.alphaNumeric) {
139+ return "PassphraseLockscreen.qml"
140+ } else {
141+ return "PinLockscreen.qml"
160142 }
143+ }
144+ onSourceChanged: {
145+ waiting = false
146+ showWrongText = false
147+ }
161148
162− Connections {
163− target: pinPadLoader.item
164−
165− onEntered: {
166− pinPadLoader.waiting = true
167− root.entered(passphrase);
168− }
149+ Connections {
150+ target: pinPadLoader.item
169151
170− onCancel: {
171− root.cancel()
172− }
152+ onEntered: {
153+ pinPadLoader.waiting = true
154+ root.entered(passphrase);
173155 }
174156
175− Binding {
176− target: pinPadLoader.item
177− property: "minPinLength"
178− value: root.minPinLength
179− }
180− Binding {
181− target: pinPadLoader.item
182− property: "maxPinLength"
183− value: root.maxPinLength
184− }
185− Binding {
186− target: pinPadLoader.item
187− property: "infoText"
188− value: root.infoText
189− }
190− Binding {
191− target: pinPadLoader.item
192− property: "retryText"
193− value: root.retryText
194− }
195− Binding {
196− target: pinPadLoader.item
197− property: "errorText"
198− value: pinPadLoader.showWrongText ? root.errorText : ""
199− }
200− Binding {
201− target: pinPadLoader.item
202− property: "entryEnabled"
203− value: !pinPadLoader.waiting
204− }
205− Binding {
206− target: pinPadLoader.item
207− property: "alphaNumeric"
208− value: root.alphaNumeric
209− }
210− Binding {
211− target: pinPadLoader.item
212− property: "delayMinutes"
213− value: root.delayMinutes
214− }
215− Binding {
216− target: pinPadLoader.item
217− property: "showCancelButton"
218− value: root.showCancelButton
219− }
220− Binding {
221− target: pinPadLoader.item
222− property: "foregroundColor"
223− value: root.foregroundColor
157+ onCancel: {
158+ root.cancel()
224159 }
225160 }
161+
162+ Binding {
163+ target: pinPadLoader.item
164+ property: "minPinLength"
165+ value: root.minPinLength
166+ }
167+ Binding {
168+ target: pinPadLoader.item
169+ property: "maxPinLength"
170+ value: root.maxPinLength
171+ }
172+ Binding {
173+ target: pinPadLoader.item
174+ property: "infoText"
175+ value: root.infoText
176+ }
177+ Binding {
178+ target: pinPadLoader.item
179+ property: "retryText"
180+ value: root.retryText
181+ }
182+ Binding {
183+ target: pinPadLoader.item
184+ property: "errorText"
185+ value: pinPadLoader.showWrongText ? root.errorText : ""
186+ }
187+ Binding {
188+ target: pinPadLoader.item
189+ property: "entryEnabled"
190+ value: !pinPadLoader.waiting
191+ }
192+ Binding {
193+ target: pinPadLoader.item
194+ property: "alphaNumeric"
195+ value: root.alphaNumeric
196+ }
197+ Binding {
198+ target: pinPadLoader.item
199+ property: "delayMinutes"
200+ value: root.delayMinutes
201+ }
202+ Binding {
203+ target: pinPadLoader.item
204+ property: "showCancelButton"
205+ value: root.showCancelButton
206+ }
207+ Binding {
208+ target: pinPadLoader.item
209+ property: "foregroundColor"
210+ value: root.foregroundColor
211+ }
226212 }
227213
228214 Item {
qml/Components/ModeSwitchWarningDialog.qml+1−1
@@ -54,7 +54,7 @@ ShellDialog {
5454 }
5555
5656 Label {
57− text: i18n.tr("Re-dock, save your work and close these apps to continue.")
57+ text: i18n.ctr("Re-dock means connect the device again to an external screen/mouse/keyboard", "Re-dock, save your work and close these apps to continue.")
5858 wrapMode: Text.WordWrap
5959 color: "#888888"
6060 }
qml/Components/PassphraseLockscreen.qml+5−4
@@ -18,10 +18,11 @@ import QtQuick 2.4
1818import Ubuntu.Components 1.3
1919import "../Components"
2020
21−Item {
21+FocusScope {
2222 id: root
2323 y: units.gu(4)
2424 height: shakeContainer.height
25+ focus: true
2526
2627 property string infoText
2728 property string errorText
@@ -42,8 +43,6 @@ Item {
4243 }
4344 }
4445
45− onActiveFocusChanged: if (activeFocus) pinentryField.forceActiveFocus()
46−
4746 Column {
4847 id: shakeContainer
4948 anchors.horizontalCenter: parent.horizontalCenter
@@ -59,14 +58,16 @@ Item {
5958 text: root.infoText
6059 }
6160
62− Item {
61+ FocusScope {
6362 id: entryContainer
6463 anchors { left: parent.left; right: parent.right; margins: units.gu(2) }
6564 height: units.gu(4)
65+ focus: true
6666
6767 TextInput {
6868 id: pinentryField
6969 objectName: "pinentryField"
70+ focus: true
7071
7172 property bool incorrectOverride: false
7273
qml/Components/PhysicalKeysMapper.qml+24
@@ -42,12 +42,15 @@ Item {
4242 signal screenshotTriggered;
4343
4444 readonly property bool altTabPressed: d.altTabPressed
45+ readonly property bool superPressed: d.superPressed
46+ readonly property bool superTabPressed: d.superTabPressed
4547
4648 property int powerKeyLongPressTime: 2000
4749
4850 // For testing. If running windowed (e.g. tryShell), Alt+Tab is taken by the
4951 // running desktop, set this to true to use Ctrl+Tab instead.
5052 property bool controlInsteadOfAlt: false
53+ property bool controlInsteadOfSuper: false
5154
5255 QtObject {
5356 id: d
@@ -59,6 +62,9 @@ Item {
5962 property bool altPressed: false
6063 property bool altTabPressed: false
6164
65+ property bool superPressed: false
66+ property bool superTabPressed: false
67+
6268 property var powerButtonPressStart: 0
6369
6470 // We need to eat ALT presses until we know what they're for (Alt+Tab or going to the app?)
@@ -119,11 +125,23 @@ Item {
119125 event.accepted = true;
120126 d.altPressInjected = false;
121127 }
128+
129+ // Adding MetaModifier here because that's what keyboards do. Pressing Super_L actually gives
130+ // Super_L + MetaModifier. This helps to make sure we only invoke superPressed if no other
131+ // Modifier is pressed too.
132+ } else if (((event.key == Qt.Key_Super_L || event.key == Qt.Key_Super_R) && event.modifiers === Qt.MetaModifier)
133+ || (root.controlInsteadOfSuper && event.key == Qt.Key_Control)
134+ ) {
135+ d.superPressed = true;
122136 } else if (event.key == Qt.Key_Tab) {
123137 if (d.altPressed && !d.altTabPressed) {
124138 d.altTabPressed = true;
125139 event.accepted = true;
126140 }
141+ if (d.superPressed && !d.superTabPressed) {
142+ d.superTabPressed = true;
143+ event.accepted = true;
144+ }
127145 }
128146 }
129147
@@ -154,6 +172,12 @@ Item {
154172 if (d.altTabPressed) {
155173 event.accepted = true;
156174 }
175+ } else if (event.key == Qt.Key_Super_L || event.key == Qt.Key_Super_R || (root.controlInsteadOfSuper && event.key == Qt.Key_Control)) {
176+ d.superPressed = false;
177+ if (d.superTabPressed) {
178+ d.superTabPressed = false;
179+ event.accepted = true;
180+ }
157181 }
158182 }
159183}
qml/Components/PinLockscreen.qml+22−1
@@ -19,8 +19,9 @@ import Ubuntu.Components 1.3
1919import Ubuntu.Components.ListItems 1.3
2020import "../Components"
2121
22−Item {
22+FocusScope {
2323 id: root
24+ focus: true
2425
2526 property string infoText
2627 property string retryText
@@ -45,6 +46,24 @@ Item {
4546 }
4647 }
4748
49+ Keys.onPressed: {
50+ if (pinentryField.text.length == root.maxPinLength)
51+ return;
52+
53+ if (event.key === Qt.Key_Backspace) {
54+ pinentryField.backspace();
55+ } else if (event.key === Qt.Key_Delete || event.key === Qt.Key_Escape) {
56+ closeButton.clicked()
57+ } else if (event.key === Qt.Key_Enter || event.key === Qt.Key_Return) {
58+ confirmButton.clicked()
59+ } else {
60+ var digit = parseInt(event.text);
61+ if (!isNaN(digit) && typeof digit == "number") {
62+ pinentryField.appendNumber(digit);
63+ }
64+ }
65+ }
66+
4867 Column {
4968 anchors {
5069 left: parent.left;
@@ -204,6 +223,7 @@ Item {
204223 width: numbersGrid.buttonWidth
205224 }
206225 PinPadButton {
226+ id: closeButton
207227 iconName: "close"
208228 height: units.gu(5) // visual spec has this row a little closer in
209229 width: numbersGrid.buttonWidth
@@ -216,6 +236,7 @@ Item {
216236 width: numbersGrid.buttonWidth
217237 }
218238 PinPadButton {
239+ id: confirmButton
219240 iconName: "tick"
220241 objectName: "confirmButton"
221242 height: units.gu(5)
qml/Components/ShellDialog.qml+8
@@ -37,7 +37,15 @@ Dialog {
3737
3838 focus: true
3939
40+ // FIXME: this is a hack because Dialog subtheming seems broken atm
41+ // https://bugs.launchpad.net/ubuntu/+source/ubuntu-ui-toolkit/+bug/1555548
42+ ThemeSettings {
43+ id: themeHack
44+ name: "Ubuntu.Components.Themes.Ambiance"
45+ }
46+
4047 Component.onCompleted: {
48+ __foreground.theme = themeHack
4149 show();
4250 }
4351}
qml/Components/Showable.qml+10
@@ -31,6 +31,7 @@ Item {
3131 property bool required
3232 property bool __shouldShow: false
3333 property bool __skipShowAnimation: false
34+ property bool __skipHideAnimation: false
3435
3536 property list<QtObject> hides
3637 property var showAnimation
@@ -120,12 +121,21 @@ Item {
120121 if (!hideAnimation.running) {
121122 hideAnimation.restart()
122123 }
124+ if (__skipHideAnimation) {
125+ hideAnimation.complete();
126+ }
123127 } else {
124128 visible = false
125129 required = false
126130 }
127131
128132 shown = false
133+ __skipHideAnimation = false;
134+ }
135+
136+ function hideNow() {
137+ __skipHideAnimation = true;
138+ hide();
129139 }
130140
131141 Connections {
qml/Dash/CardCarousel.qml−1
@@ -66,7 +66,6 @@ DashRenderer {
6666 item.cardData = Qt.binding(function() { return model; });
6767 item.fontScale = Qt.binding(function() { return carousel.fontScale; });
6868 item.showHeader = Qt.binding(function() { return loader.explicitlyScaled; });
69− item.titleAlignment = Qt.binding(function() { return cardTool.titleAlignment; });
7069 item.artShapeStyle = "shadow";
7170 item.scopeStyle = cardCarousel.scopeStyle;
7271 }
qml/Dash/CardGrid.qml−1
@@ -70,7 +70,6 @@ DashRenderer {
7070 item.fixedHeaderHeight = Qt.binding(function() { return cardTool.headerHeight; });
7171 item.fixedArtShapeSize = Qt.binding(function() { return cardTool.artShapeSize; });
7272 item.cardData = Qt.binding(function() { return model; });
73− item.titleAlignment = Qt.binding(function() { return cardTool.titleAlignment; });
7473 item.scopeStyle = root.scopeStyle;
7574 item.artShapeStyle = root.artShapeStyle;
7675 item.backgroundShapeStyle = root.backgroundShapeStyle;
qml/Dash/CardHorizontalList.qml−1
@@ -53,7 +53,6 @@ DashRenderer {
5353 item.fixedArtShapeSize = Qt.binding(function() { return cardTool.artShapeSize; });
5454 item.fixedHeaderHeight = Qt.binding(function() { return cardTool.headerHeight; });
5555 item.cardData = Qt.binding(function() { return model; });
56− item.titleAlignment = Qt.binding(function() { return cardTool.titleAlignment; });
5756 item.scopeStyle = root.scopeStyle;
5857 }
5958 Connections {
qml/Dash/CardTool.qml+2−26
@@ -66,7 +66,7 @@ Item {
6666 return layout;
6767 }
6868
69− property var cardComponent: CardCreatorCache.getCardComponent(cardTool.template, cardTool.components);
69+ property var cardComponent: CardCreatorCache.getCardComponent(cardTool.template, cardTool.components, false);
7070
7171 // FIXME: Saviq
7272 // Only way for the card below to actually be laid out completely.
@@ -139,29 +139,6 @@ Item {
139139 readonly property int headerHeight: cardLoader.item ? cardLoader.item.headerHeight : 0
140140 property size artShapeSize: cardLoader.item ? cardLoader.item.artShapeSize : 0
141141
142− /*!
143− \brief Desired alignment of title
144− */
145− readonly property int titleAlignment: {
146− if (template["card-layout"] === "horizontal"
147− || typeof components["title"] !== "object"
148− || components["title"]["align"] === "left") return Text.AlignLeft;
149−
150− var keys = ["mascot", "emblem", "subtitle", "attributes", "summary"];
151−
152− for (var key in keys) {
153− key = keys[key];
154− try {
155− if (typeof components[key] === "string"
156− || typeof components[key]["field"] === "string") return Text.AlignLeft;
157− } catch (e) {
158− continue;
159− }
160− }
161−
162− return Text.AlignHCenter;
163− }
164−
165142 QtObject {
166143 id: carouselTool
167144
@@ -216,10 +193,9 @@ Item {
216193 "summary": "—\n—\n—\n—\n—",
217194 "attributes": attributesModel.model
218195 }
219− sourceComponent: cardTool.cardComponent
196+ sourceComponent: CardCreatorCache.getCardComponent(cardTool.template, cardTool.components, true);
220197 onLoaded: {
221198 item.objectName = "cardToolCard";
222− item.asynchronous = false;
223199 item.width = Qt.binding(function() { return cardTool.cardWidth !== -1 ? cardTool.cardWidth : item.implicitWidth; });
224200 item.height = Qt.binding(function() { return cardTool.cardHeight !== -1 ? cardTool.cardHeight : item.implicitHeight; });
225201 }
qml/Dash/CardVerticalJournal.qml−1
@@ -72,7 +72,6 @@ DashRenderer {
7272 item.fixedArtShapeSize = Qt.binding(function() { return cardTool.artShapeSize; });
7373 item.fixedHeaderHeight = Qt.binding(function() { return cardTool.headerHeight; });
7474 item.cardData = Qt.binding(function() { return model; });
75− item.titleAlignment = Qt.binding(function() { return cardTool.titleAlignment; });
7675 item.scopeStyle = root.scopeStyle;
7776 }
7877 Connections {
qml/Dash/Dash.qml+11−11
@@ -37,6 +37,7 @@ Showable {
3737 }
3838
3939 property bool windowActive: window.active
40+ property bool showOverlayScope: false
4041
4142 DashCommunicatorService {
4243 objectName: "dashCommunicatorService"
@@ -80,7 +81,7 @@ Showable {
8081 return
8182 }
8283
83− closeOverlayScope();
84+ dash.showOverlayScope = false;
8485
8586 dashContent.closePreview();
8687
@@ -93,12 +94,6 @@ Showable {
9394 dashContent.setCurrentScopeAtIndex(scopeIndex, animate, reset)
9495 }
9596
96− function closeOverlayScope() {
97− if (dashContent.x != 0) {
98− dashContent.x = 0;
99− }
100− }
101−
10297 Scopes {
10398 id: scopes
10499 }
@@ -133,12 +128,13 @@ Showable {
133128 height: dash.height
134129 scopes: scopes
135130 visible: x != -width
131+ x: dash.showOverlayScope ? -width : 0
136132 onGotoScope: {
137133 dash.setCurrentScope(scopeId, true, false);
138134 }
139135 onOpenScope: {
140136 scopeItem.scope = scope;
141− x = -width;
137+ dash.showOverlayScope = true;
142138 }
143139 Behavior on x {
144140 UbuntuNumberAnimation {
@@ -207,7 +203,7 @@ Showable {
207203 bottomEdgeController.enableAnimation = true;
208204 bottomEdgeController.progress = 0;
209205 scopeItem.scope = scope;
210− dashContent.x = -dashContent.width;
206+ dash.showOverlayScope = true;
211207 }
212208 onGotoScope: {
213209 bottomEdgeController.enableAnimation = true;
@@ -226,7 +222,7 @@ Showable {
226222 id: scopeItem
227223 objectName: "dashTempScopeItem"
228224
229− x: dashContent.x + width
225+ x: dash.showOverlayScope ? 0 : width
230226 y: dashContent.y
231227 width: parent.width
232228 height: parent.height
@@ -234,7 +230,7 @@ Showable {
234230 hasBackAction: true
235231 isCurrent: visible
236232 onBackClicked: {
237− closeOverlayScope();
233+ dash.showOverlayScope = false;
238234 closePreview();
239235 }
240236
@@ -250,6 +246,10 @@ Showable {
250246 scopes.closeScope(oldScope);
251247 }
252248 }
249+
250+ Behavior on x {
251+ UbuntuNumberAnimation { }
252+ }
253253 }
254254
255255 Rectangle {
qml/Dash/GenericScopeView.qml+2
@@ -370,6 +370,8 @@ FocusScope {
370370 } else {
371371 cardTool.cardWidth = units.gu(10);
372372 }
373+ } else {
374+ cardTool.cardWidth = units.gu(12);
373375 }
374376 item.minimumHorizontalSpacing = item.defaultMinimumHorizontalSpacing;
375377 }
qml/Dash/Previews/PreviewHeader.qml+37−9
@@ -25,6 +25,7 @@ import "../"
2525 * The mascot fall back image comes in widgetData["fallback"]
2626 * The subtitle comes in widgetData["subtitle"]
2727 * The attributes comes in widgetData["attributes"]
28+ * The emblem comes in widgetData["emblem"]
2829 */
2930
3031PreviewWidget {
@@ -40,6 +41,7 @@ PreviewWidget {
4041 readonly property string title: root.widgetData["title"] || ""
4142 readonly property string subtitle: root.widgetData["subtitle"] || ""
4243 readonly property var attributes: root.widgetData["attributes"] || null
44+ readonly property url emblem: root.widgetData["emblem"] || ""
4345 readonly property color fontColor: root.scopeStyle ? root.scopeStyle.foreground : theme.palette.normal.baseText
4446
4547 // Rewire the source since we may have unwired it on onStatusChanged
@@ -98,16 +100,42 @@ PreviewWidget {
98100 spacing: units.dp(2)
99101 anchors.verticalCenter: parent.verticalCenter
100102
101− Label {
102− id: titleLabel
103− objectName: "titleLabel"
103+ Item {
104104 anchors { left: parent.left; right: parent.right }
105− elide: Text.ElideRight
106− font.weight: Font.Normal
107− fontSize: "large"
108− wrapMode: Text.Wrap
109− color: headerRoot.fontColor
110− text: headerRoot.title
105+ height: titleLabel.height
106+
107+ Label {
108+ id: titleLabel
109+ objectName: "titleLabel"
110+ anchors {
111+ left: parent.left;
112+ right: iconLoader.right
113+ rightMargin: iconLoader.width > 0 ? units.gu(0.5) : 0
114+ }
115+ elide: Text.ElideRight
116+ font.weight: Font.Normal
117+ fontSize: "large"
118+ wrapMode: Text.Wrap
119+ color: headerRoot.fontColor
120+ text: headerRoot.title
121+ }
122+
123+ Loader {
124+ id: iconLoader
125+ active: headerRoot.emblem != ""
126+ anchors {
127+ bottom: titleLabel.baseline
128+ right: parent.right
129+ }
130+ sourceComponent: Icon {
131+ objectName: "emblemIcon"
132+ source: headerRoot.emblem
133+ color: headerRoot.fontColor
134+ height: source != "" ? titleLabel.font.pixelSize : 0
135+ // FIXME Workaround for bug https://bugs.launchpad.net/ubuntu/+source/ubuntu-ui-toolkit/+bug/1421293
136+ width: implicitWidth > 0 && implicitHeight > 0 ? (implicitWidth / implicitHeight * height) : implicitWidth
137+ }
138+ }
111139 }
112140
113141 Loader {
qml/Dash/Previews/PreviewOverlay.qml+1−1
@@ -158,7 +158,7 @@ Rectangle {
158158 anchors.centerIn: parent
159159 width: units.gu(2.5)
160160 height: width
161− color: theme.palette.normal.foregroundText
161+ color: "white"
162162 name: "close"
163163 }
164164 }
qml/Dash/Previews/PreviewSharing.qml+17−7
@@ -44,6 +44,22 @@ Item {
4444 }
4545 }
4646
47+ function createExportedItems(url) {
48+ var items = new Array();
49+ if (typeof url === "string") {
50+ var exportItem = exportItemComponent.createObject();
51+ exportItem.url = url;
52+ items.push(exportItem);
53+ } else {
54+ for (var i = 0; i < url.length; i++) {
55+ var exportItem = exportItemComponent.createObject();
56+ exportItem.url = url[i];
57+ items.push(exportItem);
58+ }
59+ }
60+ return items;
61+ }
62+
4763 Component {
4864 id: exportItemComponent
4965 ContentItem {
@@ -76,13 +92,7 @@ Item {
7692 onPeerSelected: {
7793 var transfer = peer.request();
7894 if (transfer.state === ContentTransfer.InProgress) {
79− var items = new Array();
80− for (var i = 0; i < url.length; i++) {
81− var exportItem = exportItemComponent.createObject();
82− exportItem.url = url[i];
83− items.push(exportItem);
84− }
85− transfer.items = items;
95+ transfer.items = createExportedItems(url);
8696 transfer.state = ContentTransfer.Charged;
8797 }
8898 peerPicker.visible = false;
qml/Dash/ScopesListCategoryItem.qml+8−4
@@ -18,7 +18,7 @@ import QtQuick 2.4
1818import QtQuick.Layouts 1.1
1919import Ubuntu.Components 1.3
2020
21−MouseArea {
21+AbstractButton {
2222 id: root
2323
2424 signal requestFavorite(string scopeId, bool favorite)
@@ -84,15 +84,19 @@ MouseArea {
8484 visible: text != ""
8585 }
8686 }
87− MouseArea {
87+ AbstractButton {
8888 id: starArea
8989 objectName: "starArea"
9090 height: parent.height
9191 width: height
9292 anchors.right: parent.right
9393 onClicked: if (!editMode) root.requestFavorite(model.scopeId, !isFavorite);
94− onPressed: if (editMode) root.handlePressed(starArea);
95− onReleased: if (editMode) root.handleReleased(starArea);
94+ onPressedChanged: {
95+ if (editMode) {
96+ if (pressed) root.handlePressed(starArea.__mouseArea);
97+ else root.handleReleased(starArea.__mouseArea);
98+ }
99+ }
96100 visible: editMode || showStar
97101 Icon {
98102 id: star
qml/DeviceConfiguration.qml+54−16
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2015 Canonical, Ltd.
2+ * Copyright (C) 2015-2016 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -15,6 +15,7 @@
1515 */
1616
1717import QtQuick 2.4
18+import Utils 0.1
1819
1920QtObject {
2021 id: root
@@ -34,57 +35,83 @@ QtObject {
3435
3536 readonly property alias category: priv.category
3637
38+ readonly property var deviceConfigParser: DeviceConfigParser {
39+ name: root.name
40+ }
41+
3742 readonly property var priv: StateGroup {
3843 id: priv
3944
40− property int primaryOrientation: root.useNativeOrientation
45+ property int primaryOrientation: deviceConfigParser.primaryOrientation == Qt.PrimaryOrientation ?
46+ root.useNativeOrientation : deviceConfigParser.primaryOrientation
4147
42− property int supportedOrientations: Qt.PortraitOrientation
43− | Qt.InvertedPortraitOrientation
44− | Qt.LandscapeOrientation
45− | Qt.InvertedLandscapeOrientation
48+ property int supportedOrientations: deviceConfigParser.supportedOrientations
4649
47− property int landscapeOrientation: Qt.LandscapeOrientation
48− property int invertedLandscapeOrientation: Qt.InvertedLandscapeOrientation
49− property int portraitOrientation: Qt.PortraitOrientation
50− property int invertedPortraitOrientation: Qt.InvertedPortraitOrientation
51−
52− // Supported values so far:
53− // "phone", "tablet" or "desktop"
54− property string category: "phone"
50+ property int landscapeOrientation: deviceConfigParser.landscapeOrientation
51+ property int invertedLandscapeOrientation: deviceConfigParser.invertedLandscapeOrientation
52+ property int portraitOrientation: deviceConfigParser.portraitOrientation
53+ property int invertedPortraitOrientation: deviceConfigParser.invertedPortraitOrientation
54+ property string category: deviceConfigParser.category
5555
5656 states: [
5757 State {
5858 name: "mako"
5959 PropertyChanges {
6060 target: priv
61+ primaryOrientation: root.useNativeOrientation
6162 supportedOrientations: Qt.PortraitOrientation
6263 | Qt.LandscapeOrientation
6364 | Qt.InvertedLandscapeOrientation
65+ landscapeOrientation: Qt.LandscapeOrientation
66+ invertedLandscapeOrientation: Qt.InvertedLandscapeOrientation
67+ portraitOrientation: Qt.PortraitOrientation
68+ invertedPortraitOrientation: Qt.InvertedPortraitOrientation
69+ category: "phone"
6470 }
6571 },
6672 State {
6773 name: "krillin"
6874 PropertyChanges {
6975 target: priv
76+ primaryOrientation: root.useNativeOrientation
7077 supportedOrientations: Qt.PortraitOrientation
7178 | Qt.LandscapeOrientation
7279 | Qt.InvertedLandscapeOrientation
80+ landscapeOrientation: Qt.LandscapeOrientation
81+ invertedLandscapeOrientation: Qt.InvertedLandscapeOrientation
82+ portraitOrientation: Qt.PortraitOrientation
83+ invertedPortraitOrientation: Qt.InvertedPortraitOrientation
84+ category: "phone"
7385 }
7486 },
7587 State {
7688 name: "arale"
7789 PropertyChanges {
7890 target: priv
91+ primaryOrientation: root.useNativeOrientation
7992 supportedOrientations: Qt.PortraitOrientation
8093 | Qt.LandscapeOrientation
8194 | Qt.InvertedLandscapeOrientation
95+ landscapeOrientation: Qt.LandscapeOrientation
96+ invertedLandscapeOrientation: Qt.InvertedLandscapeOrientation
97+ portraitOrientation: Qt.PortraitOrientation
98+ invertedPortraitOrientation: Qt.InvertedPortraitOrientation
99+ category: "phone"
82100 }
83101 },
84102 State {
85103 name: "manta"
86104 PropertyChanges {
87105 target: priv
106+ primaryOrientation: root.useNativeOrientation
107+ supportedOrientations: Qt.PortraitOrientation
108+ | Qt.InvertedPortraitOrientation
109+ | Qt.LandscapeOrientation
110+ | Qt.InvertedLandscapeOrientation
111+ landscapeOrientation: Qt.LandscapeOrientation
112+ invertedLandscapeOrientation: Qt.InvertedLandscapeOrientation
113+ portraitOrientation: Qt.PortraitOrientation
114+ invertedPortraitOrientation: Qt.InvertedPortraitOrientation
88115 category: "tablet"
89116 }
90117 },
@@ -92,9 +119,15 @@ QtObject {
92119 name: "flo"
93120 PropertyChanges {
94121 target: priv
122+ primaryOrientation: Qt.InvertedLandscapeOrientation
123+ supportedOrientations: Qt.PortraitOrientation
124+ | Qt.InvertedPortraitOrientation
125+ | Qt.LandscapeOrientation
126+ | Qt.InvertedLandscapeOrientation
95127 landscapeOrientation: Qt.InvertedLandscapeOrientation
96128 invertedLandscapeOrientation: Qt.LandscapeOrientation
97− primaryOrientation: Qt.InvertedLandscapeOrientation
129+ portraitOrientation: Qt.PortraitOrientation
130+ invertedPortraitOrientation: Qt.InvertedPortraitOrientation
98131 category: "tablet"
99132 }
100133 },
@@ -102,8 +135,13 @@ QtObject {
102135 name: "desktop"
103136 PropertyChanges {
104137 target: priv
105− category: "desktop"
138+ primaryOrientation: root.useNativeOrientation
106139 supportedOrientations: root.useNativeOrientation
140+ landscapeOrientation: Qt.LandscapeOrientation
141+ invertedLandscapeOrientation: Qt.InvertedLandscapeOrientation
142+ portraitOrientation: Qt.PortraitOrientation
143+ invertedPortraitOrientation: Qt.InvertedPortraitOrientation
144+ category: "desktop"
107145 }
108146 }
109147 ]
qml/DisabledScreenNotice.qml+44−20
@@ -17,6 +17,7 @@
1717import QtQuick 2.4
1818import QtQuick.Layouts 1.1
1919import Ubuntu.Components 1.3
20+import Unity.Session 0.1
2021import QtQuick.Window 2.2
2122import "Components"
2223
@@ -25,19 +26,36 @@ Item {
2526
2627 property bool infoNoteDisplayed: true
2728
28− WallpaperResolver {
29− width: root.width
30− id: wallpaperResolver
29+ // For testing
30+ property var screen: Screen
31+ property var orientationLock: OrientationLock
32+
33+ DeviceConfiguration {
34+ id: deviceConfiguration
35+ name: applicationArguments.deviceName
3136 }
3237
3338 Item {
3439 id: contentContainer
40+ objectName: "contentContainer"
3541 anchors.centerIn: parent
3642 height: rotation == 90 || rotation == 270 ? parent.width : parent.height
3743 width: rotation == 90 || rotation == 270 ? parent.height : parent.width
3844
45+ property int savedOrientation: deviceConfiguration.primaryOrientation == deviceConfiguration.useNativeOrientation
46+ ? (root.width > root.height ? Qt.LandscapeOrientation : Qt.PortraitOrientation)
47+ : deviceConfiguration.primaryOrientation
48+
3949 rotation: {
40− switch (Screen.orientation) {
50+ var usedOrientation = root.screen.orientation;
51+
52+ if (root.orientationLock.enabled) {
53+ usedOrientation = savedOrientation;
54+ }
55+
56+ savedOrientation = usedOrientation;
57+
58+ switch (usedOrientation) {
4159 case Qt.PortraitOrientation:
4260 return 0;
4361 case Qt.LandscapeOrientation:
@@ -47,6 +65,8 @@ Item {
4765 case Qt.InvertedLandscapeOrientation:
4866 return 90;
4967 }
68+
69+ return 0;
5070 }
5171 transformOrigin: Item.Center
5272
@@ -60,9 +80,9 @@ Item {
6080 }
6181 }
6282
63− Image {
83+ Rectangle {
6484 anchors.fill: parent
65− source: wallpaperResolver.background
85+ color: "#3b3b3b"
6686 }
6787
6888 Item {
@@ -74,22 +94,26 @@ Item {
7494 UbuntuNumberAnimation { }
7595 }
7696
77− Rectangle {
78− anchors.fill: parent
79− color: "black"
80− opacity: 0.4
81− }
82−
83− Label {
84− id: text
97+ Column {
8598 anchors.centerIn: parent
8699 width: parent.width - units.gu(8)
87− text: i18n.tr("Your device is now connected to an external display. Use this screen as a touch pad to interact with the mouse.")
88− color: "white"
89− horizontalAlignment: Text.AlignHCenter
90− verticalAlignment: Text.AlignVCenter
91− fontSize: "x-large"
92− wrapMode: Text.Wrap
100+ spacing: units.gu(4)
101+
102+ Label {
103+ id: text
104+ text: i18n.tr("Your device is now connected to an external display. Use this screen as a touch pad to interact with the pointer.")
105+ color: "white"
106+ width: parent.width
107+ fontSize: "large"
108+ wrapMode: Text.Wrap
109+ }
110+ Icon {
111+ height: units.gu(8)
112+ width: height
113+ name: "input-touchpad-symbolic"
114+ color: "white"
115+ anchors.horizontalCenter: parent.horizontalCenter
116+ }
93117 }
94118 }
95119
qml/Greeter/CoverPage.qml+13−2
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013,2014,2015 Canonical, Ltd.
2+ * Copyright (C) 2013-2016 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -34,6 +34,7 @@ Showable {
3434 readonly property real showProgress: MathUtils.clamp((width - Math.abs(x)) / width, 0, 1)
3535
3636 signal tease()
37+ signal clicked()
3738
3839 function hideRight() {
3940 d.forceRightOnNextHideAnimation = true;
@@ -55,7 +56,17 @@ Showable {
5556 // instead, we can get a little extra horizontal push by using transforms.
5657 transform: Translate { id: translation; x: root.draggable ? launcherOffset : 0 }
5758
58− MouseArea { anchors.fill: parent; }
59+ // Eat events elsewhere on the coverpage, except mouse clicks which we pass
60+ // up (they are used in the NarrowView to hide the cover page)
61+ MouseArea {
62+ anchors.fill: parent
63+ onClicked: root.clicked()
64+
65+ MultiPointTouchArea {
66+ anchors.fill: parent
67+ mouseEnabled: false
68+ }
69+ }
5970
6071 Rectangle {
6172 // In case background fails to load
qml/Greeter/Infographics.qml+2−1
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013-2016 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -402,6 +402,7 @@ Item {
402402
403403 MouseArea {
404404 anchors.fill: dataCircle
405+ enabled: notification.text != ""
405406
406407 onDoubleClicked: {
407408 if (!d.animating) {
qml/Greeter/NarrowView.qml+14−1
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2015 Canonical, Ltd.
2+ * Copyright (C) 2015-2016 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -125,6 +125,18 @@ FocusScope {
125125 onCancel: coverPage.show()
126126 onEmergencyCall: root.emergencyCall()
127127
128+ onEnabledChanged: {
129+ if (enabled) {
130+ lockscreen.forceActiveFocus();
131+ }
132+ }
133+
134+ onVisibleChanged: {
135+ if (visible) {
136+ lockscreen.forceActiveFocus();
137+ }
138+ }
139+
128140 function maybeShow() {
129141 if (root.locked && !shown) {
130142 showNow();
@@ -145,6 +157,7 @@ FocusScope {
145157 width: parent.width
146158 background: root.background
147159 onTease: root.tease()
160+ onClicked: hide()
148161
149162 onShowProgressChanged: {
150163 if (showProgress === 1) {
qml/Launcher/Launcher.qml+144−11
@@ -19,20 +19,25 @@ import "../Components"
1919import Ubuntu.Components 1.3
2020import Ubuntu.Gestures 0.1
2121import Unity.Launcher 0.1
22+import GlobalShortcut 1.0
2223
23−Item {
24+FocusScope {
2425 id: root
2526
2627 property bool autohideEnabled: false
28+ property bool lockedVisible: false
2729 property bool available: true // can be used to disable all interactions
2830 property alias inverted: panel.inverted
2931
30− property int panelWidth: units.gu(8)
32+ property int panelWidth: units.gu(10)
3133 property int dragAreaWidth: units.gu(1)
3234 property int minimizeDistance: units.gu(26)
3335 property real progress: dragArea.dragging && dragArea.touchX > panelWidth ?
3436 (width * (dragArea.touchX-panelWidth) / (width - panelWidth)) : 0
3537
38+ property bool superPressed: false
39+ property bool superTabPressed: false
40+
3641 readonly property bool dragging: dragArea.dragging
3742 readonly property real dragDistance: dragArea.dragging ? dragArea.touchX : 0
3843 readonly property real visibleWidth: panel.width + panel.x
@@ -56,12 +61,55 @@ Item {
5661 }
5762 }
5863
64+ onSuperPressedChanged: {
65+ if (superPressed) {
66+ superPressTimer.start();
67+ superLongPressTimer.start();
68+ } else {
69+ superPressTimer.stop();
70+ superLongPressTimer.stop();
71+ launcher.switchToNextState("");
72+ panel.shortcutHintsShown = false;
73+ }
74+ }
75+
76+ onSuperTabPressedChanged: {
77+ if (superTabPressed) {
78+ switchToNextState("visible")
79+ panel.highlightIndex = -1;
80+ root.focus = true;
81+ superPressTimer.stop();
82+ superLongPressTimer.stop();
83+ } else {
84+ if (panel.highlightIndex == -1) {
85+ showDashHome();
86+ } else if (panel.highlightIndex >= 0){
87+ launcherApplicationSelected(LauncherModel.get(panel.highlightIndex).appId);
88+ }
89+ panel.highlightIndex = -2;
90+ switchToNextState("");
91+ root.focus = false;
92+ }
93+ }
94+
95+ onLockedVisibleChanged: {
96+ if (lockedVisible && state == "") {
97+ panel.dismissTimer.stop();
98+ fadeOutAnimation.stop();
99+ switchToNextState("visible")
100+ } else if (!lockedVisible && state == "visible") {
101+ hide();
102+ }
103+ }
104+
59105 function hide() {
60106 switchToNextState("")
61107 }
62108
63109 function fadeOut() {
64− fadeOutAnimation.start();
110+ if (!root.lockedVisible) {
111+ fadeOutAnimation.start();
112+ }
65113 }
66114
67115 function switchToNextState(state) {
@@ -89,6 +137,77 @@ Item {
89137 }
90138 }
91139
140+ function openForKeyboardNavigation() {
141+ panel.highlightIndex = -1; // The BFB
142+ root.focus = true;
143+ switchToNextState("visible")
144+ }
145+
146+ Keys.onPressed: {
147+ switch (event.key) {
148+ case Qt.Key_Backtab:
149+ panel.highlightPrevious();
150+ event.accepted = true;
151+ break;
152+ case Qt.Key_Up:
153+ if (root.inverted) {
154+ panel.highlightNext()
155+ } else {
156+ panel.highlightPrevious();
157+ }
158+ event.accepted = true;
159+ break;
160+ case Qt.Key_Tab:
161+ panel.highlightNext();
162+ event.accepted = true;
163+ break;
164+ case Qt.Key_Down:
165+ if (root.inverted) {
166+ panel.highlightPrevious();
167+ } else {
168+ panel.highlightNext();
169+ }
170+ event.accepted = true;
171+ break;
172+ case Qt.Key_Right:
173+ panel.openQuicklist(panel.highlightIndex)
174+ event.accepted = true;
175+ break;
176+ case Qt.Key_Escape:
177+ panel.highlightIndex = -2;
178+ // Falling through intentionally
179+ case Qt.Key_Enter:
180+ case Qt.Key_Return:
181+ case Qt.Key_Space:
182+ if (panel.highlightIndex == -1) {
183+ showDashHome();
184+ } else if (panel.highlightIndex >= 0) {
185+ launcherApplicationSelected(LauncherModel.get(panel.highlightIndex).appId);
186+ }
187+ root.hide();
188+ panel.highlightIndex = -2
189+ event.accepted = true;
190+ root.focus = false;
191+ }
192+ }
193+
194+ Timer {
195+ id: superPressTimer
196+ interval: 200
197+ onTriggered: {
198+ switchToNextState("visible")
199+ }
200+ }
201+
202+ Timer {
203+ id: superLongPressTimer
204+ interval: 1000
205+ onTriggered: {
206+ switchToNextState("visible")
207+ panel.shortcutHintsShown = true;
208+ }
209+ }
210+
92211 Timer {
93212 id: teaseTimer
94213 interval: mode == "teasing" ? 200 : 300
@@ -105,6 +224,13 @@ Item {
105224 interval: 1
106225 property string nextState: ""
107226 onTriggered: {
227+ if (root.lockedVisible && nextState == "") {
228+ // Due to binding updates when switching between modes
229+ // it could happen that our request to show will be overwritten
230+ // with a hide request. Rewrite it when we know hiding is not allowed.
231+ nextState = "visible"
232+ }
233+
108234 // switching to an intermediate state here to make sure all the
109235 // values are restored, even if we were already in the target state
110236 root.state = "tmp"
@@ -150,7 +276,7 @@ Item {
150276
151277 MouseArea {
152278 id: launcherDragArea
153− enabled: root.available && (root.state == "visible" || root.state == "visibleTemporary")
279+ enabled: root.available && (root.state == "visible" || root.state == "visibleTemporary") && !root.lockedVisible
154280 anchors.fill: panel
155281 anchors.rightMargin: -units.gu(2)
156282 drag {
@@ -171,9 +297,10 @@ Item {
171297 InverseMouseArea {
172298 id: closeMouseArea
173299 anchors.fill: panel
174− enabled: root.state == "visible"
300+ enabled: root.state == "visible" && (!root.lockedVisible || panel.highlightIndex >= -1)
175301 visible: enabled
176302 onPressed: {
303+ panel.highlightIndex = -2
177304 root.hide();
178305 }
179306 }
@@ -182,7 +309,7 @@ Item {
182309 id: backgroundShade
183310 anchors.fill: parent
184311 color: "black"
185− opacity: root.state == "visible" ? 0.6 : 0
312+ opacity: root.state == "visible" && !root.lockedVisible ? 0.6 : 0
186313
187314 Behavior on opacity { NumberAnimation { duration: UbuntuAnimation.BriskDuration } }
188315 }
@@ -201,8 +328,8 @@ Item {
201328 rotation: -90
202329 anchors.centerIn: parent
203330 gradient: Gradient {
204− GradientStop { position: 0.0; color: panel.color}
205− GradientStop { position: 1.0; color: Qt.rgba(panel.r,panel.g,panel.b,0)}
331+ GradientStop { position: 0.0; color: Qt.rgba(panel.color.r, panel.color.g, panel.color.b, .5)}
332+ GradientStop { position: 1.0; color: Qt.rgba(panel.color.r,panel.color.g,panel.color.b,0)}
206333 }
207334 }
208335 }
@@ -226,7 +353,7 @@ Item {
226353 Connections {
227354 target: panel.dismissTimer
228355 onTriggered: {
229− if (root.autohideEnabled) {
356+ if (root.autohideEnabled && !root.lockedVisible) {
230357 if (!panel.preventHiding) {
231358 root.state = ""
232359 } else {
@@ -239,11 +366,11 @@ Item {
239366 property bool animate: true
240367
241368 onApplicationSelected: {
242− root.state = ""
369+ root.hide();
243370 launcherApplicationSelected(appId)
244371 }
245372 onShowDashHome: {
246− root.state = ""
373+ root.hide();
247374 root.showDashHome();
248375 }
249376
@@ -253,6 +380,12 @@ Item {
253380 }
254381 }
255382
383+ onKbdNavigationCancelled: {
384+ panel.highlightIndex = -2;
385+ root.hide();
386+ root.focus = false;
387+ }
388+
256389 Behavior on x {
257390 enabled: !dragArea.dragging && !launcherDragArea.drag.active && panel.animate;
258391 NumberAnimation {
qml/Launcher/LauncherDelegate.qml+55−20
@@ -20,6 +20,7 @@ import Ubuntu.Components 1.3
2020Item {
2121 id: root
2222
23+ property int itemIndex: 0
2324 property string iconName
2425 property int count: 0
2526 property bool countVisible: false
@@ -29,10 +30,12 @@ Item {
2930 property real maxAngle: 0
3031 property bool inverted: false
3132 property bool alerting: false
32− readonly property alias wiggling: wiggleAnim.running
33+ property bool highlighted: false
34+ property bool shortcutHintShown: false
3335
3436 readonly property int effectiveHeight: Math.cos(angle * Math.PI / 180) * itemHeight
3537 readonly property real foldedHeight: Math.cos(maxAngle * Math.PI / 180) * itemHeight
38+ readonly property alias wiggling: wiggleAnim.running
3639
3740 property int itemWidth
3841 property int itemHeight
@@ -121,20 +124,32 @@ Item {
121124
122125 Item {
123126 id: iconItem
124− width: parent.itemWidth + units.gu(1)
127+ width: root.width
125128 height: parent.itemHeight + units.gu(1)
126129 anchors.centerIn: parent
127130
131+ Image {
132+ objectName: "focusRing"
133+ anchors.centerIn: iconShape
134+ height: width * 15 / 16
135+ width: iconShape.width + units.gu(1)
136+ source: "graphics/launcher-app-focus-ring.svg"
137+ sourceSize.width: width
138+ sourceSize.height: height
139+ visible: root.highlighted
140+ }
141+
128142 ProportionalShape {
129143 id: iconShape
130144 anchors.centerIn: parent
131− width: parent.width - units.gu(2)
145+ width: root.itemWidth
132146 aspect: UbuntuShape.DropShadow
133147 source: Image {
134148 id: iconImage
135149 sourceSize.width: iconShape.width
136150 sourceSize.height: iconShape.height
137151 source: root.iconName
152+ cache: false // see lpbug#1543290 why no cache
138153 }
139154 }
140155
@@ -144,7 +159,8 @@ Item {
144159 anchors {
145160 right: parent.right
146161 bottom: parent.bottom
147− margins: units.dp(3)
162+ rightMargin: (iconItem.width - root.itemWidth) / 2 - units.dp(2)
163+ margins: units.dp(5)
148164 }
149165 width: Math.min(root.itemWidth, Math.max(units.gu(2), countLabel.implicitWidth + units.gu(1)))
150166 height: units.gu(2)
@@ -172,16 +188,11 @@ Item {
172188 id: progressOverlay
173189 objectName: "progressOverlay"
174190
175− anchors {
176− left: iconItem.left
177− right: iconItem.right
178− verticalCenter: parent.verticalCenter
179− leftMargin: units.gu(1.5)
180− rightMargin: units.gu(1.5)
181− }
191+ anchors.centerIn: parent
192+ width: root.itemWidth * .8
182193 height: units.gu(1)
183194 visible: root.progress > -1
184− color: UbuntuColors.darkGrey
195+ backgroundColor: UbuntuColors.darkGrey
185196 borderSource: "none"
186197
187198 Item {
@@ -199,32 +210,56 @@ Item {
199210 top: parent.top
200211 bottom: parent.bottom
201212 }
202− color: "white"
213+ backgroundColor: "white"
203214 borderSource: "none"
204215 width: progressOverlay.width
205216 }
206217 }
207218 }
208219
209− Image {
210− objectName: "runningHighlight"
220+ Column {
211221 anchors {
212222 left: parent.left
213223 verticalCenter: parent.verticalCenter
214224 }
215− visible: root.itemRunning
216− rotation: 180
217− source: "graphics/focused_app_arrow.png"
225+ spacing: units.gu(.5)
226+ Repeater {
227+ model: 1 // TODO: This should be "Math.min(3, app.surfaceCount)" once we have multiple surfaces
228+ Rectangle {
229+ objectName: "runningHighlight" + index
230+ width: units.gu(0.25)
231+ height: units.gu(.5)
232+ color: "white"
233+ visible: root.itemRunning
234+ }
235+ }
218236 }
219237
220− Image {
238+ Rectangle {
221239 objectName: "focusedHighlight"
222240 anchors {
223241 right: parent.right
224242 verticalCenter: parent.verticalCenter
225243 }
244+ width: units.gu(0.25)
245+ height: units.gu(.5)
246+ color: "white"
226247 visible: root.itemFocused
227− source: "graphics/focused_app_arrow.png"
248+ }
249+
250+ Rectangle {
251+ objectName: "shortcutHint"
252+ anchors.centerIn: parent
253+ width: units.gu(3)
254+ height: width
255+ color: "#E0292929"
256+ visible: root.shortcutHintShown
257+ Label {
258+ anchors.centerIn: parent
259+ text: (itemIndex + 1) % 10
260+ color: "white"
261+ font.weight: Font.DemiBold
262+ }
228263 }
229264 }
230265
qml/Launcher/LauncherPanel.qml+122−51
@@ -19,12 +19,13 @@ import Ubuntu.Components 1.3
1919import Ubuntu.Components.ListItems 1.3 as ListItems
2020import Unity.Launcher 0.1
2121import Ubuntu.Components.Popups 1.3
22+import GlobalShortcut 1.0
2223import "../Components/ListItems"
2324import "../Components/"
2425
2526Rectangle {
2627 id: root
27− color: "#B2000000"
28+ color: "#E0292929"
2829
2930 rotation: inverted ? 180 : 0
3031
@@ -33,11 +34,13 @@ Rectangle {
3334 property bool dragging: false
3435 property bool moving: launcherListView.moving || launcherListView.flicking
3536 property bool preventHiding: moving || dndArea.draggedIndex >= 0 || quickList.state === "open" || dndArea.pressed
36− || mouseEventEater.containsMouse || dashItem.hovered
37− property int highlightIndex: -1
37+ || mouseEventEater.containsMouse || dashItem.hovered
38+ property int highlightIndex: -2
39+ property bool shortcutHintsShown: false
3840
3941 signal applicationSelected(string appId)
4042 signal showDashHome()
43+ signal kbdNavigationCancelled()
4144
4245 onXChanged: {
4346 if (quickList.state == "open") {
@@ -45,6 +48,26 @@ Rectangle {
4548 }
4649 }
4750
51+ function highlightNext() {
52+ highlightIndex++;
53+ if (highlightIndex >= launcherListView.count) {
54+ highlightIndex = -1;
55+ }
56+ launcherListView.moveToIndex(Math.max(highlightIndex, 0));
57+ }
58+ function highlightPrevious() {
59+ highlightIndex--;
60+ if (highlightIndex <= -2) {
61+ highlightIndex = launcherListView.count - 1;
62+ }
63+ launcherListView.moveToIndex(Math.max(highlightIndex, 0));
64+ }
65+ function openQuicklist(index) {
66+ quickList.open(index);
67+ quickList.selectedIndex = 0;
68+ quickList.focus = true;
69+ }
70+
4871 MouseArea {
4972 id: mouseEventEater
5073 anchors.fill: parent
@@ -57,24 +80,16 @@ Rectangle {
5780 fill: parent
5881 }
5982
60− Item {
83+ Rectangle {
6184 objectName: "buttonShowDashHome"
6285 width: parent.width
63− height: units.gu(7)
64− clip: true
65−
66− UbuntuShape {
67− anchors {
68− fill: parent
69− topMargin: -units.gu(2)
70− }
71− aspect: UbuntuShape.Flat
72− backgroundColor: UbuntuColors.orange
73− }
86+ height: width * .9
87+ color: UbuntuColors.orange
88+ readonly property bool highlighted: root.highlightIndex == -1;
7489
7590 Image {
7691 objectName: "dashItem"
77− width: units.gu(5)
92+ width: parent.width * .6
7893 height: width
7994 anchors.centerIn: parent
8095 source: "graphics/home.png"
@@ -85,6 +100,14 @@ Rectangle {
85100 anchors.fill: parent
86101 onClicked: root.showDashHome()
87102 }
103+ Rectangle {
104+ objectName: "bfbFocusHighlight"
105+ anchors.fill: parent
106+ border.color: "white"
107+ border.width: units.dp(1)
108+ color: "transparent"
109+ visible: parent.highlighted
110+ }
88111 }
89112
90113 Item {
@@ -102,10 +125,8 @@ Rectangle {
102125 objectName: "launcherListView"
103126 anchors {
104127 fill: parent
105− topMargin: -extensionSize + units.gu(0.5)
106− bottomMargin: -extensionSize + units.gu(1)
107− leftMargin: units.gu(0.5)
108− rightMargin: units.gu(0.5)
128+ topMargin: -extensionSize + width * .15
129+ bottomMargin: -extensionSize + width * .15
109130 }
110131 topMargin: extensionSize
111132 bottomMargin: extensionSize
@@ -140,11 +161,11 @@ Rectangle {
140161 }
141162
142163 // The height of the area where icons start getting folded
143− property int foldingStartHeight: units.gu(6.5)
164+ property int foldingStartHeight: itemHeight
144165 // The height of the area where the items reach the final folding angle
145166 property int foldingStopHeight: foldingStartHeight - itemHeight - spacing
146− property int itemWidth: units.gu(7)
147− property int itemHeight: units.gu(6.5)
167+ property int itemWidth: width * .75
168+ property int itemHeight: itemWidth * 15 / 16 + units.gu(1)
148169 property int clickFlickSpeed: units.gu(60)
149170 property int draggedIndex: dndArea.draggedIndex
150171 property real realContentY: contentY - originY + topMargin
@@ -172,12 +193,24 @@ Rectangle {
172193
173194 UbuntuNumberAnimation {
174195 id: moveAnimation
196+ objectName: "moveAnimation"
175197 target: launcherListView
176198 property: "contentY"
177199 function moveTo(contentY) {
178200 from = launcherListView.contentY;
179201 to = contentY;
180− start();
202+ restart();
203+ }
204+ }
205+ function moveToIndex(index) {
206+ var totalItemHeight = launcherListView.itemHeight + launcherListView.spacing
207+ var itemPosition = index * totalItemHeight;
208+ var height = launcherListView.height - launcherListView.topMargin - launcherListView.bottomMargin
209+ var distanceToEnd = index == 0 || index == launcherListView.count - 1 ? 0 : totalItemHeight
210+ if (itemPosition + totalItemHeight + distanceToEnd > launcherListView.contentY + launcherListView.originY + launcherListView.topMargin + height) {
211+ moveAnimation.moveTo(itemPosition + launcherListView.itemHeight - launcherListView.topMargin - height + distanceToEnd - launcherListView.originY);
212+ } else if (itemPosition - distanceToEnd < launcherListView.contentY - launcherListView.originY + launcherListView.topMargin) {
213+ moveAnimation.moveTo(itemPosition - distanceToEnd - launcherListView.topMargin + launcherListView.originY);
181214 }
182215 }
183216
@@ -192,9 +225,10 @@ Rectangle {
192225 // the right app when running autopilot tests for
193226 // multiple apps.
194227 readonly property string appId: model.appId
228+ itemIndex: index
195229 itemHeight: launcherListView.itemHeight
196230 itemWidth: launcherListView.itemWidth
197− width: itemWidth
231+ width: parent.width
198232 height: itemHeight
199233 iconName: model.icon
200234 count: model.count
@@ -204,6 +238,8 @@ Rectangle {
204238 itemFocused: model.focused
205239 inverted: root.inverted
206240 alerting: model.alerting
241+ highlighted: root.highlightIndex == index
242+ shortcutHintShown: root.shortcutHintsShown && index <= 9
207243 z: -Math.abs(offset)
208244 maxAngle: 55
209245 property bool dragging: false
@@ -241,14 +277,7 @@ Rectangle {
241277 onAlertingChanged: {
242278 if(alerting) {
243279 if (!dragging && (launcherListView.peekingIndex === -1 || launcher.visibleWidth > 0)) {
244− var itemPosition = index * launcherListView.itemHeight;
245− var height = launcherListView.height - launcherListView.topMargin - launcherListView.bottomMargin
246− var distanceToEnd = index == 0 || index == launcherListView.count - 1 ? 0 : launcherListView.itemHeight
247− if (itemPosition + launcherListView.itemHeight + distanceToEnd > launcherListView.contentY + launcherListView.topMargin + height) {
248− moveAnimation.moveTo(itemPosition + launcherListView.itemHeight - launcherListView.topMargin - height + distanceToEnd);
249− } else if (itemPosition - distanceToEnd < launcherListView.contentY + launcherListView.topMargin) {
250− moveAnimation.moveTo(itemPosition - distanceToEnd - launcherListView.topMargin);
251− }
280+ launcherListView.moveToIndex(index)
252281 if (!dragging && launcher.state !== "visible") {
253282 peekingAnimation.start()
254283 }
@@ -402,10 +431,7 @@ Rectangle {
402431
403432 if (mouse.button & Qt.RightButton) { // context menu
404433 // Opening QuickList
405− quickList.item = clickedItem;
406− quickList.model = launcherListView.model.get(index).quickList;
407− quickList.appId = launcherListView.model.get(index).appId;
408− quickList.state = "open";
434+ quickList.open(index);
409435 return;
410436 }
411437
@@ -413,10 +439,8 @@ Rectangle {
413439
414440 // First/last item do the scrolling at more than 12 degrees
415441 if (index == 0 || index == launcherListView.count - 1) {
416− if (clickedItem.angle > 12) {
417− launcherListView.flick(0, -launcherListView.clickFlickSpeed);
418− } else if (clickedItem.angle < -12) {
419− launcherListView.flick(0, launcherListView.clickFlickSpeed);
442+ if (clickedItem.angle > 12 || clickedItem.angle < -12) {
443+ launcherListView.moveToIndex(index);
420444 } else {
421445 root.applicationSelected(LauncherModel.get(index).appId);
422446 }
@@ -424,10 +448,8 @@ Rectangle {
424448 }
425449
426450 // the rest launches apps up to an angle of 30 degrees
427− if (clickedItem.angle > 30) {
428− launcherListView.flick(0, -launcherListView.clickFlickSpeed);
429− } else if (clickedItem.angle < -30) {
430− launcherListView.flick(0, launcherListView.clickFlickSpeed);
451+ if (clickedItem.angle > 30 || clickedItem.angle < -30) {
452+ launcherListView.moveToIndex(index);
431453 } else {
432454 root.applicationSelected(LauncherModel.get(index).appId);
433455 }
@@ -481,11 +503,7 @@ Rectangle {
481503
482504 draggedIndex = Math.floor((mouse.y + launcherListView.realContentY) / launcherListView.realItemHeight);
483505
484− // Opening QuickList
485− quickList.item = selectedItem;
486− quickList.model = launcherListView.model.get(draggedIndex).quickList;
487− quickList.appId = launcherListView.model.get(draggedIndex).appId;
488− quickList.state = "open";
506+ quickList.open(draggedIndex)
489507
490508 launcherListView.interactive = false
491509
@@ -644,7 +662,9 @@ Rectangle {
644662 enabled: quickList.state == "open" || pressed
645663
646664 onClicked: {
647− quickList.state = ""
665+ quickList.state = "";
666+ quickList.focus = false;
667+ root.kbdNavigationCancelled();
648668 }
649669
650670 // Forward for dragging to work when quickList is open
@@ -693,12 +713,60 @@ Rectangle {
693713 property var model
694714 property string appId
695715 property var item
716+ property int selectedIndex: -1
717+
718+ Keys.onPressed: {
719+ switch (event.key) {
720+ case Qt.Key_Down:
721+ selectedIndex++;
722+ if (selectedIndex >= popoverRepeater.count) {
723+ selectedIndex = 0;
724+ }
725+ event.accepted = true;
726+ break;
727+ case Qt.Key_Up:
728+ selectedIndex--;
729+ if (selectedIndex < 0) {
730+ selectedIndex = popoverRepeater.count - 1;
731+ }
732+ event.accepted = true;
733+ break;
734+ case Qt.Key_Left:
735+ case Qt.Key_Escape:
736+ quickList.selectedIndex = -1;
737+ quickList.focus = false;
738+ quickList.state = ""
739+ event.accepted = true;
740+ break;
741+ case Qt.Key_Enter:
742+ case Qt.Key_Return:
743+ case Qt.Key_Space:
744+ if (quickList.selectedIndex >= 0) {
745+ LauncherModel.quickListActionInvoked(quickList.appId, quickList.selectedIndex)
746+ }
747+ quickList.selectedIndex = -1;
748+ quickList.focus = false;
749+ quickList.state = ""
750+ root.kbdNavigationCancelled();
751+ event.accepted = true;
752+ break;
753+ }
754+ }
696755
697756 // internal
698757 property int itemCenter: item ? root.mapFromItem(quickList.item).y + (item.height / 2) + quickList.item.offset : units.gu(1)
699758 property int offset: itemCenter + (height/2) + units.gu(1) > parent.height ? -itemCenter - (height/2) - units.gu(1) + parent.height :
700759 itemCenter - (height/2) < units.gu(1) ? (height/2) - itemCenter + units.gu(1) : 0
701760
761+ function open(index) {
762+ var itemPosition = index * launcherListView.itemHeight;
763+ var height = launcherListView.height - launcherListView.topMargin - launcherListView.bottomMargin
764+ item = launcherListView.itemAt(launcherListView.width / 2, itemPosition + launcherListView.itemHeight / 2);
765+ quickList.model = launcherListView.model.get(index).quickList;
766+ quickList.appId = launcherListView.model.get(index).appId;
767+ quickList.state = "open";
768+ }
769+
702770 Column {
703771 id: quickListColumn
704772 width: parent.width
@@ -712,6 +780,7 @@ Rectangle {
712780 objectName: "quickListEntry" + index
713781 text: (model.clickable ? "" : "<b>") + model.label + (model.clickable ? "" : "</b>")
714782 highlightWhenPressed: model.clickable
783+ selected: index === quickList.selectedIndex
715784
716785 // FIXME: This is a workaround for the theme not being context sensitive. I.e. the
717786 // ListItems don't know that they are sitting in a themed Popover where the color
@@ -727,6 +796,8 @@ Rectangle {
727796 // Unsetting model to prevent showing changing entries during fading out
728797 // that may happen because of triggering an action.
729798 LauncherModel.quickListActionInvoked(quickList.appId, index);
799+ quickList.focus = false;
800+ root.kbdNavigationCancelled();
730801 quickList.model = undefined;
731802 }
732803 }
qml/Launcher/graphics/launcher-app-focus-ring.svgnew file+12
@@ -0,0 +1,12 @@
1+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2+<svg width="172px" height="163px" viewBox="0 0 172 163" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
3+ <!-- Generator: Sketch 3.4.4 (17249) - http://www.bohemiancoding.com/sketch -->
4+ <title>Shape</title>
5+ <desc>Created with Sketch.</desc>
6+ <defs></defs>
7+ <g id="•-Launcher" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
8+ <g id="Artboard-9" sketch:type="MSArtboardGroup" transform="translate(-163.000000, -1436.000000)" fill="#E95420">
9+ <path d="M221.983432,1440 L221.983432,1440 C195.6127,1440 184.708233,1442.4723 177.107949,1450.10734 C169.476819,1457.77336 167,1468.79245 167,1495.3481 L167,1538.9019 C167,1565.45755 169.476819,1576.47664 177.107949,1584.14266 C184.708233,1591.7777 195.6127,1594.25 221.983432,1594.25 L276.016868,1594.25 C302.387595,1594.25 313.291998,1591.77771 320.892221,1584.14264 C328.523252,1576.47663 331,1565.45769 331,1538.9019 L331,1495.3481 C331,1468.79231 328.523252,1457.77337 320.892221,1450.10736 C313.291998,1442.47229 302.387595,1440 276.016868,1440 L221.983432,1440 Z M221.983432,1436 L276.016868,1436 C302.345315,1436 314.848953,1438.36655 323.727108,1447.2854 C332.633306,1456.23243 335,1468.85167 335,1495.3481 L335,1538.9019 C335,1565.39833 332.633306,1578.01757 323.727108,1586.9646 C314.848953,1595.88345 302.345315,1598.25 276.016868,1598.25 L221.983432,1598.25 C195.654985,1598.25 183.151291,1595.88345 174.273077,1586.96463 C165.366772,1578.0176 163,1565.39822 163,1538.9019 L163,1495.3481 C163,1468.85178 165.366772,1456.2324 174.273077,1447.28537 C183.151291,1438.36655 195.654985,1436 221.983432,1436 L221.983432,1436 Z" id="Shape" sketch:type="MSShapeGroup"></path>
10+ </g>
11+ </g>
12+</svg>
qml/Notifications/Notification.qml+6−2
@@ -23,7 +23,7 @@ import QMenuModel 0.1
2323import Utils 0.1
2424import "../Components"
2525
26−Item {
26+StyledItem {
2727 id: notification
2828
2929 property alias iconSource: icon.fileSource
@@ -58,6 +58,10 @@ Item {
5858 color: (type === Notification.Confirmation && notificationList.useModal && !greeter.shown) || darkOnBright ? sdLightGrey : Qt.rgba(0.132, 0.117, 0.109, 0.97)
5959 opacity: 1 - (x / notification.width) // FIXME: non-zero initially because of LP: #1354406 workaround, we want this to start at 0 upon creation eventually
6060
61+ theme: ThemeSettings {
62+ name: "Ubuntu.Components.Themes.Ambiance"
63+ }
64+
6165 state: {
6266 var result = "";
6367
@@ -297,7 +301,7 @@ Item {
297301 visible: body != "" && type !== Notification.Confirmation
298302 fontSize: "small"
299303 color: darkOnBright ? sdFontColor : theme.palette.normal.backgroundText
300− wrapMode: Text.WordWrap
304+ wrapMode: Text.Wrap
301305 maximumLineCount: type == Notification.SnapDecision ? 12 : 2
302306 elide: Text.ElideRight
303307 textFormat: Text.PlainText
qml/OrientedShell.qml+26−1
@@ -19,6 +19,7 @@ import QtQuick.Window 2.2
1919import Unity.InputInfo 0.1
2020import Unity.Session 0.1
2121import Unity.Screens 0.1
22+import Utils 0.1
2223import GSettings 1.0
2324import "Components"
2425import "Rotation"
@@ -77,6 +78,8 @@ Rectangle {
7778 InputDeviceModel {
7879 id: keyboardsModel
7980 deviceFilter: InputInfo.Keyboard
81+ onDeviceAdded: forceOSKEnabled = autopilotDevicePresent();
82+ onDeviceRemoved: forceOSKEnabled = autopilotDevicePresent();
8083 }
8184
8285 InputDeviceModel {
@@ -84,6 +87,27 @@ Rectangle {
8487 deviceFilter: InputInfo.TouchScreen
8588 }
8689
90+ /* FIXME: This exposes the NameRole as a work arround for lp:1542224.
91+ * When QInputInfo exposes NameRole to QML, this should be removed.
92+ */
93+ property bool forceOSKEnabled: false
94+ property var autopilotEmulatedDeviceNames: ["py-evdev-uinput"]
95+ UnitySortFilterProxyModel {
96+ id: autopilotDevices
97+ model: keyboardsModel
98+ }
99+
100+ function autopilotDevicePresent() {
101+ for(var i = 0; i < autopilotDevices.count; i++) {
102+ var device = autopilotDevices.get(i);
103+ if (autopilotEmulatedDeviceNames.indexOf(device.name) != -1) {
104+ console.warn("Forcing the OSK to be enabled as there is an autopilot eumlated device present.")
105+ return true;
106+ }
107+ }
108+ return false;
109+ }
110+
87111 Screens {
88112 id: screens
89113 }
@@ -195,7 +219,8 @@ Rectangle {
195219 // have multiple keyboards around. For now we only enable one keyboard at a time
196220 // thus hiding it here if there is a physical one around or if we have a second
197221 // screen (the virtual touchpad & osk on the phone) attached.
198− oskEnabled: keyboardsModel.count === 0 && screens.count === 1
222+ oskEnabled: (keyboardsModel.count === 0 && screens.count === 1) ||
223+ forceOSKEnabled
199224
200225 // TODO: Factor in the connected input devices (eg: physical keyboard, mouse, touchscreen),
201226 // what's the output device (eg: big TV, desktop monitor, phone display), etc.
qml/Panel/Handle.qml+2−2
@@ -19,7 +19,7 @@ import Ubuntu.Components 1.3
1919
2020Rectangle {
2121 id: handle
22− color: UbuntuColors.slate
22+ color: theme.palette.normal.foreground
2323 height: units.gu(2)
2424 property bool active: false
2525
@@ -35,7 +35,7 @@ Rectangle {
3535 id: dot
3636 width: units.dp(3)
3737 height: width
38− color: handle.active ? UbuntuColors.orange : UbuntuColors.ash
38+ color: handle.active ? theme.palette.highlighted.foreground : theme.palette.normal.raised
3939 radius: units.dp(1)
4040 }
4141 }
qml/Panel/Indicators/MenuItemFactory.qml−1
@@ -228,7 +228,6 @@ Item {
228228 text: menuData && menuData.label || ""
229229 iconSource: menuData && menuData.icon || ""
230230 value : menuData && menuData.actionState || 0.0
231− enabled: menuData && menuData.sensitive || false
232231 highlightWhenPressed: false
233232 }
234233 }
qml/Panel/IndicatorsMenu.qml+1−1
@@ -37,7 +37,7 @@ Showable {
3737 readonly property bool fullyClosed: unitProgress == 0
3838 property bool enableHint: true
3939 property bool showOnClick: true
40− property color panelColor: theme.palette.normal.background
40+ property color panelColor: UbuntuColors.jet
4141
4242 signal showTapped(point position)
4343
qml/Panel/Panel.qml+5−1
@@ -89,7 +89,7 @@ Item {
8989
9090 Rectangle {
9191 id: indicatorAreaBackground
92− color: callHint.visible ? UbuntuColors.green : theme.palette.normal.background
92+ color: callHint.visible ? UbuntuColors.green : indicators.panelColor
9393 anchors {
9494 top: parent.top
9595 left: parent.left
@@ -169,8 +169,10 @@ Item {
169169 objectName: "windowDecorationTitle"
170170 anchors {
171171 left: parent.left
172+ right: __indicators.left
172173 top: parent.top
173174 leftMargin: units.gu(1)
175+ rightMargin: units.gu(1)
174176 topMargin: units.gu(0.5)
175177 bottomMargin: units.gu(0.5)
176178 }
@@ -181,6 +183,8 @@ Item {
181183 fontSize: "medium"
182184 font.weight: Font.Normal
183185 text: PanelState.title
186+ elide: Text.ElideRight
187+ maximumLineCount: 1
184188 }
185189
186190 // TODO here would the Locally integrated menus come
qml/ScopeTool.qml−8
@@ -52,14 +52,6 @@ Rectangle {
5252 color: "#FCFCFC"
5353 }
5454
55− Image {
56− anchors.fill: dashContent
57− source: root.width > root.height ? "Dash/graphics/paper_landscape.png" : "Dash/graphics/paper_portrait.png"
58− fillMode: Image.PreserveAspectCrop
59− horizontalAlignment: Image.AlignRight
60− verticalAlignment: Image.AlignTop
61− }
62−
6355 DashContent {
6456 id: dashContent
6557
qml/Shell.qml+53−8
@@ -25,6 +25,7 @@ import Ubuntu.Telephony 0.1 as Telephony
2525import Unity.Connectivity 0.1
2626import Unity.Launcher 0.1
2727import GlobalShortcut 1.0 // has to be before Utils, because of WindowInputFilter
28+import GSettings 1.0
2829import Utils 0.1
2930import Powerd 0.1
3031import SessionBroadcast 0.1
@@ -119,9 +120,7 @@ Item {
119120 if (ApplicationManager.findApplication(appId)) {
120121 ApplicationManager.requestFocusApplication(appId);
121122 } else {
122− var execFlags = shell.usageScenario === "phone" ? ApplicationManager.ForceMainStage
123− : ApplicationManager.NoFlag;
124− ApplicationManager.startApplication(appId, execFlags);
123+ ApplicationManager.startApplication(appId);
125124 }
126125 }
127126
@@ -188,6 +187,11 @@ Item {
188187 }
189188 }
190189
190+ GSettings {
191+ id: settings
192+ schema.id: "com.canonical.Unity8"
193+ }
194+
191195 Item {
192196 id: stages
193197 objectName: "stages"
@@ -343,13 +347,22 @@ Item {
343347 property: "altTabPressed"
344348 value: physicalKeysMapper.altTabPressed
345349 }
350+ Binding {
351+ target: applicationsDisplayLoader.item
352+ property: "leftMargin"
353+ value: shell.usageScenario == "desktop" && !settings.autohideLauncher ? launcher.panelWidth: 0
354+ }
346355 }
347356 }
348357
349358 InputMethod {
350359 id: inputMethod
351360 objectName: "inputMethod"
352− anchors { fill: parent; topMargin: panel.panelHeight }
361+ anchors {
362+ fill: parent
363+ topMargin: panel.panelHeight
364+ leftMargin: launcher.lockedVisible ? launcher.panelWidth : 0
365+ }
353366 z: notifications.useModal || panel.indicators.shown || wizard.active || tutorial.running ? overlay.z + 1 : overlay.z - 1
354367 }
355368
@@ -503,9 +516,7 @@ Item {
503516 greeterShown: greeter.shown
504517 }
505518
506− readonly property bool topmostApplicationIsFullscreen:
507− ApplicationManager.focusedApplicationId &&
508− ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
519+ readonly property bool topmostApplicationIsFullscreen: mainApp && mainApp.fullscreen
509520
510521 fullscreenMode: (topmostApplicationIsFullscreen && !lightDM.greeter.active && launcher.progress == 0)
511522 || greeter.hasLockedApp
@@ -527,6 +538,10 @@ Item {
527538 && (!greeter.locked || AccountsService.enableLauncherWhileLocked)
528539 && !greeter.hasLockedApp
529540 inverted: shell.usageScenario !== "desktop"
541+ superPressed: physicalKeysMapper.superPressed
542+ superTabPressed: physicalKeysMapper.superTabPressed
543+ panelWidth: units.gu(settings.launcherWidth)
544+ lockedVisible: shell.usageScenario == "desktop" && !settings.autohideLauncher && !panel.fullscreenMode
530545
531546 onShowDashHome: showHome()
532547 onDash: showDash()
@@ -544,6 +559,37 @@ Item {
544559 panel.indicators.hide()
545560 }
546561 }
562+ onFocusChanged: {
563+ if (!focus) {
564+ applicationsDisplayLoader.focus = true;
565+ }
566+ }
567+
568+ GlobalShortcut {
569+ shortcut: Qt.AltModifier | Qt.Key_F1
570+ onTriggered: {
571+ launcher.openForKeyboardNavigation();
572+ }
573+ }
574+ GlobalShortcut {
575+ shortcut: Qt.MetaModifier | Qt.Key_0
576+ onTriggered: {
577+ if (LauncherModel.get(9)) {
578+ activateApplication(LauncherModel.get(9).appId);
579+ }
580+ }
581+ }
582+ Repeater {
583+ model: 9
584+ GlobalShortcut {
585+ shortcut: Qt.MetaModifier | (Qt.Key_1 + index)
586+ onTriggered: {
587+ if (LauncherModel.get(index)) {
588+ activateApplication(LauncherModel.get(index).appId);
589+ }
590+ }
591+ }
592+ }
547593 }
548594
549595 Tutorial {
@@ -564,7 +610,6 @@ Item {
564610 id: wizard
565611 objectName: "wizard"
566612 anchors.fill: parent
567− background: wallpaperResolver.background
568613
569614 function unlockWhenDoneWithWizard() {
570615 if (!active) {
qml/Stages/AbstractStage.qml+4−2
@@ -21,7 +21,7 @@ import GSettings 1.0
2121Rectangle {
2222 id: root
2323
24− color: "#111111"
24+ color: "#060606"
2525
2626 // Controls to be set from outside
2727 property bool altTabPressed
@@ -40,10 +40,12 @@ Rectangle {
4040 property int shellOrientationAngle
4141 property bool spreadEnabled: true // If false, animations and right edge will be disabled
4242 property bool suspended
43+ // A Stage should paint a wallpaper etc over its full size but not use the margins for window placement
44+ property int leftMargin: 0
4345
4446 // To be read from outside
4547 property var mainApp: null
46− property int mainAppWindowOrientationAngle
48+ property int mainAppWindowOrientationAngle: 0
4749 property bool orientationChangesEnabled
4850 property int supportedOrientations: Qt.PortraitOrientation
4951 | Qt.LandscapeOrientation
qml/Stages/ApplicationWindow.qml+3−1
@@ -24,12 +24,14 @@ FocusScope {
2424 implicitHeight: sessionContainer.implicitHeight
2525
2626 // to be read from outside
27− readonly property bool fullscreen: application ? application.fullscreen : false
2827 property alias interactive: sessionContainer.interactive
2928 property bool orientationChangesEnabled: d.supportsSurfaceResize ? d.surfaceOldEnoughToBeResized : true
3029 readonly property string title: sessionContainer.surface && sessionContainer.surface.name !== "" ?
3130 sessionContainer.surface.name : d.name
3231
32+ // overridable from outside
33+ property bool fullscreen: application ? application.fullscreen : false
34+
3335 // to be set from outside
3436 property QtObject application
3537 property int surfaceOrientationAngle
qml/Stages/DesktopSpread.qml+64−17
@@ -19,6 +19,7 @@ import QtQuick.Layouts 1.1
1919import Ubuntu.Components 1.3
2020import Ubuntu.Gestures 0.1
2121import Unity.Application 0.1
22+import "../Components"
2223
2324FocusScope {
2425 id: root
@@ -26,8 +27,11 @@ FocusScope {
2627 property bool altTabPressed: false
2728 property Item workspace: null
2829
30+ readonly property alias ready: blurLayer.ready
2931 readonly property alias highlightedIndex: spreadRepeater.highlightedIndex
3032
33+ signal playFocusAnimation(int index)
34+
3135 function show() {
3236 spreadContainer.animateIn = true;
3337 root.state = "altTab";
@@ -91,6 +95,9 @@ FocusScope {
9195
9296 function focusSelected() {
9397 if (spreadRepeater.highlightedIndex != -1) {
98+ if (spreadContainer.visible) {
99+ root.playFocusAnimation(spreadRepeater.highlightedIndex)
100+ }
94101 var application = ApplicationManager.get(spreadRepeater.highlightedIndex);
95102 ApplicationManager.requestFocusApplication(application.appId);
96103 }
@@ -101,6 +108,31 @@ FocusScope {
101108 state = ""
102109 }
103110
111+ BlurLayer {
112+ id: blurLayer
113+ anchors.fill: parent
114+ source: root.workspace
115+ visible: false
116+ }
117+
118+ Rectangle {
119+ id: spreadBackground
120+ anchors.fill: parent
121+ color: "#B2000000"
122+ visible: false
123+ opacity: visible ? 1 : 0
124+ Behavior on opacity {
125+ UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration }
126+ }
127+ }
128+
129+ MouseArea {
130+ id: eventEater
131+ anchors.fill: parent
132+ visible: spreadBackground.visible
133+ enabled: visible
134+ }
135+
104136 Item {
105137 id: spreadContainer
106138 objectName: "spreadContainer"
@@ -226,13 +258,20 @@ FocusScope {
226258 Transition {
227259 from: ""
228260 to: "altTab"
229− PropertyAction { target: spreadDelegate; properties: "y,height,width,angle,z,itemScale,itemScaleOriginY,visible" }
230− PropertyAction { target: clippedSpreadDelegate; properties: "anchors.topMargin" }
231− PropertyAnimation {
232− target: spreadDelegate; properties: "x"
233− from: root.width
234− duration: spreadContainer.animateIn ? UbuntuAnimation.FastDuration :0
235− easing: UbuntuAnimation.StandardEasing
261+ SequentialAnimation {
262+ ParallelAnimation {
263+ PropertyAction { target: spreadDelegate; properties: "y,height,width,angle,z,itemScale,itemScaleOriginY,visible" }
264+ PropertyAction { target: clippedSpreadDelegate; properties: "anchors.topMargin" }
265+ PropertyAnimation {
266+ target: spreadDelegate; properties: "x"
267+ from: root.width
268+ duration: spreadContainer.animateIn ? UbuntuAnimation.FastDuration :0
269+ easing: UbuntuAnimation.StandardEasing
270+ }
271+ UbuntuNumberAnimation { target: clippedSpreadDelegate; property: "shadowOpacity"; from: 0; to: spreadMaths.shadowOpacity; duration: spreadContainer.animateIn ? UbuntuAnimation.FastDuration : 0 }
272+ UbuntuNumberAnimation { target: tileInfo; property: "opacity"; from: 0; to: spreadMaths.tileInfoOpacity; duration: spreadContainer.animateIn ? UbuntuAnimation.FastDuration : 0 }
273+ }
274+ PropertyAction { target: spreadSelectArea; property: "enabled" }
236275 }
237276 }
238277 ]
@@ -240,7 +279,11 @@ FocusScope {
240279 MouseArea {
241280 id: tileInfo
242281 objectName: "tileInfo"
243− anchors { left: parent.left; top: clippedSpreadDelegate.bottom; topMargin: units.gu(5) }
282+ anchors {
283+ left: parent.left
284+ top: clippedSpreadDelegate.bottom
285+ topMargin: ((spreadMaths.sceneHeight - spreadDelegate.y) - clippedSpreadDelegate.height) * 0.2
286+ }
244287 property int nextItemX: spreadRepeater.count > index + 1 ? spreadRepeater.itemAt(index + 1).x : spreadDelegate.x + units.gu(30)
245288 width: Math.min(units.gu(30), nextItemX - spreadDelegate.x)
246289 height: titleInfoColumn.height
@@ -262,12 +305,20 @@ FocusScope {
262305 anchors { left: parent.left; top: parent.top; right: parent.right }
263306 spacing: units.gu(1)
264307
265− UbuntuShape {
308+ UbuntuShapeForItem {
266309 Layout.preferredHeight: Math.min(units.gu(6), root.height * .05)
267310 Layout.preferredWidth: height * 8 / 7.6
268311 image: Image {
269312 anchors.fill: parent
270313 source: model.icon
314+ Rectangle {
315+ anchors.fill: parent
316+ color: "black"
317+ opacity: clippedSpreadDelegate.highlightShown ? 0 : .1
318+ Behavior on opacity {
319+ UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration }
320+ }
321+ }
271322 }
272323 }
273324 Label {
@@ -420,7 +471,7 @@ FocusScope {
420471
421472 property var source: ShaderEffectSource {
422473 id: shaderEffectSource
423− sourceItem: appContainer
474+ sourceItem: root.workspace
424475 }
425476
426477 fragmentShader: "
@@ -494,20 +545,17 @@ FocusScope {
494545 from: "*"
495546 to: "altTab"
496547 SequentialAnimation {
497− PropertyAction { target: hoverMouseArea; property: "progressiveScrollingEnabled"; value: false }
498548 PropertyAction { target: spreadRepeater; property: "highlightedIndex"; value: Math.min(ApplicationManager.count - 1, 1) }
499− PauseAnimation { duration: 140 }
549+ PauseAnimation { duration: spreadContainer.animateIn ? 0 : 140 }
500550 PropertyAction { target: workspaceSelector; property: "visible" }
501551 PropertyAction { target: spreadContainer; property: "visible" }
502552 ParallelAnimation {
503− UbuntuNumberAnimation {
504− target: blurLayer; properties: "saturation,blurRadius";
505− duration: spreadContainer.animateIn ? UbuntuAnimation.FastDuration : 0
506− }
553+ UbuntuNumberAnimation { target: blurLayer; properties: "saturation,blurRadius"; duration: UbuntuAnimation.SnapDuration }
507554 PropertyAction { target: spreadFlickable; property: "visible" }
508555 PropertyAction { targets: [currentSelectedLabel,spreadBackground]; property: "visible" }
509556 PropertyAction { target: spreadFlickable; property: "contentX"; value: 0 }
510557 }
558+ PropertyAction { target: hoverMouseArea; properties: "enabled,progressiveScrollingEnabled"; value: false }
511559 }
512560 },
513561 Transition {
@@ -518,6 +566,5 @@ FocusScope {
518566 PropertyAction { target: spreadRepeater; property: "highlightedIndex"; value: -1 }
519567 PropertyAction { target: spreadContainer; property: "animateIn"; value: false }
520568 }
521−
522569 ]
523570}
qml/Stages/DesktopSpreadDelegate.qml+15−1
@@ -56,6 +56,11 @@ Item {
5656 }
5757 ]
5858
59+ scale: highlightShown ? 1.025 : 1
60+ Behavior on scale {
61+ UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration }
62+ }
63+
5964 BorderImage {
6065 anchors {
6166 fill: root
@@ -70,7 +75,7 @@ Item {
7075 anchors.fill: parent
7176 anchors.margins: -units.gu(1)
7277 color: "white"
73− opacity: highlightShown ? 0.15 : 0
78+ opacity: highlightShown ? 0.55 : 0
7479 antialiasing: true
7580 }
7681
@@ -108,4 +113,13 @@ Item {
108113 ]
109114 }
110115 }
116+
117+ Rectangle {
118+ anchors.fill: parent
119+ color: "black"
120+ opacity: root.highlightShown ? 0 : .1
121+ Behavior on opacity {
122+ UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration }
123+ }
124+ }
111125}
qml/Stages/DesktopStage.qml+34−34
@@ -224,6 +224,7 @@ AbstractStage {
224224 PanelState.dropShadow = false;
225225 }
226226
227+
227228 FocusScope {
228229 id: appContainer
229230 objectName: "appContainer"
@@ -274,6 +275,7 @@ AbstractStage {
274275 readonly property alias maximizedLeft: appDelegatePrivate.maximizedLeft
275276 readonly property alias maximizedRight: appDelegatePrivate.maximizedRight
276277 readonly property alias minimized: appDelegatePrivate.minimized
278+ readonly property alias fullscreen: decoratedWindow.fullscreen
277279
278280 readonly property string appId: model.appId
279281 property bool animationsEnabled: true
@@ -293,6 +295,7 @@ AbstractStage {
293295 visible: !visuallyMinimized &&
294296 !greeter.fullyShown &&
295297 (priv.foregroundMaximizedAppZ === -1 || priv.foregroundMaximizedAppZ <= z) ||
298+ decoratedWindow.fullscreen ||
296299 (spread.state == "altTab" && index === spread.highlightedIndex)
297300
298301 Binding {
@@ -348,13 +351,26 @@ AbstractStage {
348351 ApplicationManager.focusApplication(appId);
349352 }
350353
354+ function playFocusAnimation() {
355+ focusAnimation.start()
356+ }
357+
358+ UbuntuNumberAnimation {
359+ id: focusAnimation
360+ target: appDelegate
361+ property: "scale"
362+ from: 0.98
363+ to: 1
364+ duration: UbuntuAnimation.SnapDuration
365+ }
366+
351367 states: [
352368 State {
353369 name: "fullscreen"; when: decoratedWindow.fullscreen
354− extend: "maximized"
355370 PropertyChanges {
356371 target: appDelegate;
357− y: -PanelState.panelHeight
372+ x: 0; y: -PanelState.panelHeight
373+ requestedWidth: appContainer.width; requestedHeight: appContainer.height;
358374 }
359375 },
360376 State {
@@ -371,21 +387,21 @@ AbstractStage {
371387 name: "maximized"; when: appDelegate.maximized && !appDelegate.minimized
372388 PropertyChanges {
373389 target: appDelegate;
374− x: 0; y: 0;
375− requestedWidth: root.width; requestedHeight: root.height;
390+ x: root.leftMargin; y: 0;
391+ requestedWidth: appContainer.width - root.leftMargin; requestedHeight: appContainer.height;
376392 visuallyMinimized: false;
377393 visuallyMaximized: true
378394 }
379395 },
380396 State {
381397 name: "maximizedLeft"; when: appDelegate.maximizedLeft && !appDelegate.minimized
382− PropertyChanges { target: appDelegate; x: 0; y: PanelState.panelHeight;
383− requestedWidth: root.width/2; requestedHeight: root.height - PanelState.panelHeight }
398+ PropertyChanges { target: appDelegate; x: root.leftMargin; y: PanelState.panelHeight;
399+ requestedWidth: (appContainer.width - root.leftMargin)/2; requestedHeight: appContainer.height - PanelState.panelHeight }
384400 },
385401 State {
386402 name: "maximizedRight"; when: appDelegate.maximizedRight && !appDelegate.minimized
387− PropertyChanges { target: appDelegate; x: root.width/2; y: PanelState.panelHeight;
388− requestedWidth: root.width/2; requestedHeight: root.height - PanelState.panelHeight }
403+ PropertyChanges { target: appDelegate; x: (appContainer.width + root.leftMargin)/2; y: PanelState.panelHeight;
404+ requestedWidth: (appContainer.width - root.leftMargin)/2; requestedHeight: appContainer.height - PanelState.panelHeight }
389405 },
390406 State {
391407 name: "minimized"; when: appDelegate.minimized
@@ -438,7 +454,7 @@ AbstractStage {
438454 target: appDelegate
439455 property: "z"
440456 value: ApplicationManager.count + 1
441− when: index == spread.highlightedIndex && blurLayer.ready
457+ when: index == spread.highlightedIndex && spread.ready
442458 }
443459
444460 WindowResizeArea {
@@ -448,8 +464,9 @@ AbstractStage {
448464 minHeight: units.gu(10)
449465 borderThickness: units.gu(2)
450466 windowId: model.appId // FIXME: Change this to point to windowId once we have such a thing
451− screenWidth: root.width
452− screenHeight: root.height
467+ screenWidth: appContainer.width
468+ screenHeight: appContainer.height
469+ leftMargin: root.leftMargin
453470
454471 onPressed: { ApplicationManager.focusApplication(model.appId) }
455472 }
@@ -473,27 +490,6 @@ AbstractStage {
473490 }
474491 }
475492
476− BlurLayer {
477− id: blurLayer
478− anchors.fill: parent
479− source: appContainer
480− visible: false
481− }
482−
483− Rectangle {
484− id: spreadBackground
485− anchors.fill: parent
486− color: "#55000000"
487− visible: false
488− }
489−
490− MouseArea {
491− id: eventEater
492− anchors.fill: parent
493− visible: spreadBackground.visible
494− enabled: visible
495− }
496−
497493 EdgeBarrier {
498494 id: edgeBarrier
499495
@@ -509,7 +505,7 @@ AbstractStage {
509505 rotation: 90
510506 anchors.centerIn: parent
511507 gradient: Gradient {
512− GradientStop { position: 0.0; color: Qt.rgba(0.16,0.16,0.16,0.7)}
508+ GradientStop { position: 0.0; color: Qt.rgba(0.16,0.16,0.16,0.5)}
513509 GradientStop { position: 1.0; color: Qt.rgba(0.16,0.16,0.16,0)}
514510 }
515511 }
@@ -527,9 +523,13 @@ AbstractStage {
527523 DesktopSpread {
528524 id: spread
529525 objectName: "spread"
530− anchors.fill: parent
526+ anchors.fill: appContainer
531527 workspace: appContainer
532528 focus: state == "altTab"
533529 altTabPressed: root.altTabPressed
530+
531+ onPlayFocusAnimation: {
532+ appRepeater.itemAt(index).playFocusAnimation();
533+ }
534534 }
535535}
qml/Stages/PhoneStage.qml+77−3
@@ -44,6 +44,33 @@ AbstractStage {
4444 }
4545 }
4646
47+ onAltTabPressedChanged: {
48+ if (!spreadEnabled || !altTabEnabled) {
49+ return;
50+ }
51+ if (altTabPressed) {
52+ spreadView.snapToSpread();
53+ priv.highlightIndex = Math.min(spreadRepeater.count - 1, 1);
54+ } else {
55+ spreadView.snapTo(priv.highlightIndex)
56+ }
57+ }
58+
59+ FocusScope {
60+ focus: root.altTabPressed
61+
62+ Keys.onPressed: {
63+ switch (event.key) {
64+ case Qt.Key_Tab:
65+ priv.highlightIndex = (priv.highlightIndex + 1) % spreadRepeater.count
66+ break;
67+ case Qt.Key_Backtab:
68+ priv.highlightIndex = (priv.highlightIndex + spreadRepeater.count - 1) % spreadRepeater.count
69+ break;
70+ }
71+ }
72+ }
73+
4774 // Functions to be called from outside
4875 function updateFocusedAppOrientation() {
4976 if (spreadRepeater.count > 0) {
@@ -74,6 +101,12 @@ AbstractStage {
74101 }
75102 }
76103
104+ function pushRightEdge(amount) {
105+ if (spreadView.contentX == -spreadView.shift) {
106+ edgeBarrier.push(amount);
107+ }
108+ }
109+
77110 mainApp: applicationManager.focusedApplicationId
78111 ? applicationManager.findApplication(applicationManager.focusedApplicationId)
79112 : null
@@ -177,6 +210,7 @@ AbstractStage {
177210
178211 property real oldInverseProgress: 0
179212 property bool animateX: false
213+ property int highlightIndex: 0
180214
181215 onFocusedAppDelegateChanged: {
182216 if (focusedAppDelegate) {
@@ -211,6 +245,10 @@ AbstractStage {
211245 spreadView.phase = 0;
212246 spreadView.contentX = -spreadView.shift;
213247 }
248+
249+ onHighlightIndexChanged: {
250+ spreadView.contentX = highlightIndex * spreadView.contentWidth / (spreadRepeater.count + 2)
251+ }
214252 }
215253 Timer {
216254 id: fullyShowingFocusedAppUpdateTimer
@@ -278,6 +316,11 @@ AbstractStage {
278316 }
279317 }
280318
319+ Behavior on contentX {
320+ enabled: root.altTabPressed
321+ UbuntuNumberAnimation {}
322+ }
323+
281324 onShiftedContentXChanged: {
282325 if (root.beingResized) {
283326 // Flickabe.contentX wiggles during resizes. Don't react to it.
@@ -316,12 +359,17 @@ AbstractStage {
316359 } else if (shiftedContentX < positionMarker3 * width) {
317360 snapTo(1);
318361 } else if (phase < 2){
319− // Add 1 pixel to make sure we definitely hit positionMarker4 even with rounding errors of the animation.
320− snapAnimation.targetContentX = width * positionMarker4 + 1 - shift;
321− snapAnimation.start();
362+ snapToSpread();
322363 root.opened();
323364 }
324365 }
366+
367+ function snapToSpread() {
368+ // Add 1 pixel to make sure we definitely hit positionMarker4 even with rounding errors of the animation.
369+ snapAnimation.targetContentX = (root.width * spreadView.positionMarker4) + 1 - spreadView.shift;
370+ snapAnimation.start();
371+ }
372+
325373 function snapTo(index) {
326374 if (!root.altTabEnabled) {
327375 // Reset to start instead
@@ -424,6 +472,7 @@ AbstractStage {
424472 maximizedAppTopMargin: root.maximizedAppTopMargin
425473 dropShadow: spreadView.active || priv.focusedAppDelegateIsDislocated
426474 focusFirstApp: root.focusFirstApp
475+ highlightShown: root.altTabPressed && index === priv.highlightIndex
427476
428477 readonly property bool isDash: model.appId == "unity8-dash"
429478
@@ -648,4 +697,29 @@ AbstractStage {
648697 }
649698 }
650699 }
700+
701+ EdgeBarrier {
702+ id: edgeBarrier
703+
704+ // NB: it does its own positioning according to the specified edge
705+ edge: Qt.RightEdge
706+
707+ onPassed: {
708+ spreadView.snapToSpread();
709+ }
710+ material: Component {
711+ Item {
712+ Rectangle {
713+ width: parent.height
714+ height: parent.width
715+ rotation: 90
716+ anchors.centerIn: parent
717+ gradient: Gradient {
718+ GradientStop { position: 0.0; color: Qt.rgba(0.16,0.16,0.16,0.7)}
719+ GradientStop { position: 1.0; color: Qt.rgba(0.16,0.16,0.16,0)}
720+ }
721+ }
722+ }
723+ }
724+ }
651725}
qml/Stages/SideStage.qmlnew file+118
@@ -0,0 +1,118 @@
1+/*
2+ * Copyright (C) 2016 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+import QtQuick.Layouts 1.1
19+import Ubuntu.Components 1.3
20+import Ubuntu.Gestures 0.1
21+import "../Components"
22+
23+Showable {
24+ id: root
25+ property bool showHint: true
26+ property int panelWidth: units.gu(40)
27+ readonly property alias dragging: hideSideStageDragArea.dragging
28+ readonly property real progress: width / panelWidth
29+
30+ width: 0
31+ shown: false
32+
33+ Item {
34+ id: sideStageDragHandle
35+ anchors {
36+ right: root.left
37+ top: root.top
38+ bottom: root.bottom
39+ }
40+ width: units.gu(2)
41+
42+ opacity: root.shown ? 1 : 0
43+ Behavior on opacity { UbuntuNumberAnimation {} }
44+
45+ Image {
46+ anchors.centerIn: parent
47+ width: hideSideStageDragArea.pressed ? parent.width * 2 : parent.width
48+ height: parent.height
49+ source: "graphics/sidestage_handle@20.png"
50+ Behavior on width { UbuntuNumberAnimation {} }
51+ }
52+ }
53+
54+ Rectangle {
55+ anchors.fill: parent
56+ color: Qt.rgba(0,0,0,0.95)
57+ }
58+
59+ Column {
60+ anchors.verticalCenter: parent.verticalCenter
61+ width: panelWidth - units.gu(6)
62+ x: panelWidth/2 - width/2
63+ spacing: units.gu(3)
64+ opacity: 0.8
65+ visible: showHint
66+
67+ Icon {
68+ width: units.gu(30)
69+ anchors.horizontalCenter: parent.horizontalCenter
70+ source: "graphics/sidestage_drag.svg"
71+ color: enabled ? Qt.rgba(1,1,1,1) : Qt.rgba(1,0,0,1)
72+ keyColor: Qt.rgba(1,1,1,1)
73+ }
74+
75+ Label {
76+ text: i18n.tr("Drag using 3 fingers any application from one window to the other")
77+ width: parent.width
78+ wrapMode: Text.WordWrap
79+ color: enabled ? Qt.rgba(1,1,1,1) : Qt.rgba(1,0,0,1)
80+ }
81+ }
82+
83+ showAnimation: NumberAnimation {
84+ property: "width"
85+ to: panelWidth
86+ duration: UbuntuAnimation.BriskDuration
87+ easing.type: Easing.OutCubic
88+ }
89+
90+ hideAnimation: NumberAnimation {
91+ property: "width"
92+ to: 0
93+ duration: UbuntuAnimation.BriskDuration
94+ easing.type: Easing.OutCubic
95+ }
96+
97+ DragHandle {
98+ id: hideSideStageDragArea
99+ objectName: "hideSideStageDragArea"
100+
101+ direction: Direction.Leftwards
102+ rotation: 180
103+ enabled: root.shown
104+ anchors.right: root.left
105+ width: sideStageDragHandle.width
106+ height: root.height
107+ stretch: true
108+
109+ immediateRecognition: true
110+ maxTotalDragDistance: panelWidth
111+ autoCompleteDragThreshold: panelWidth / 2
112+ }
113+
114+ // SideStage mouse event eater
115+ MouseArea {
116+ anchors.fill: parent
117+ }
118+}
qml/Stages/SpreadDelegate.qml+30−1
@@ -32,6 +32,11 @@ FocusScope {
3232 readonly property alias appWindowOrientationAngle: appWindowWithShadow.orientationAngle
3333 readonly property alias appWindowRotation: appWindowWithShadow.rotation
3434 readonly property alias orientationChangesEnabled: appWindow.orientationChangesEnabled
35+ property int supportedOrientations: application ? application.supportedOrientations :
36+ Qt.PortraitOrientation
37+ | Qt.LandscapeOrientation
38+ | Qt.InvertedPortraitOrientation
39+ | Qt.InvertedLandscapeOrientation
3540
3641 // to be set from outside
3742 property bool interactive: true
@@ -43,6 +48,10 @@ FocusScope {
4348 property int shellOrientationAngle
4449 property int shellOrientation
4550 property QtObject orientations
51+ property bool highlightShown: false
52+
53+ // overrideable from outside
54+ property alias fullscreen: appWindow.fullscreen
4655
4756 function matchShellOrientation() {
4857 if (!root.application)
@@ -153,7 +162,8 @@ FocusScope {
153162 if (!root.application || root.application.rotatesWindowContents) {
154163 return 0;
155164 }
156− var supportedOrientations = root.application.supportedOrientations;
165+
166+ var supportedOrientations = root.supportedOrientations;
157167
158168 if (supportedOrientations === Qt.PrimaryOrientation) {
159169 supportedOrientations = root.orientations.primary;
@@ -261,6 +271,25 @@ FocusScope {
261271 Behavior on opacity { UbuntuNumberAnimation {} }
262272 }
263273
274+ Rectangle {
275+ id: selectionHighlight
276+ objectName: "selectionHighlight"
277+ anchors.fill: appWindow
278+ anchors.margins: -units.gu(1)
279+ color: "white"
280+ opacity: root.highlightShown ? 0.15 : 0
281+ antialiasing: true
282+ visible: opacity > 0
283+ }
284+
285+ Rectangle {
286+ anchors { left: selectionHighlight.left; right: selectionHighlight.right; bottom: selectionHighlight.bottom; }
287+ height: units.dp(2)
288+ color: UbuntuColors.orange
289+ visible: root.highlightShown
290+ antialiasing: true
291+ }
292+
264293 ApplicationWindow {
265294 id: appWindow
266295 objectName: application ? "appWindow_" + application.appId : "appWindow_null"
qml/Stages/SurfaceContainer.qml+1
@@ -118,6 +118,7 @@ FocusScope {
118118
119119
120120 TouchGate {
121+ objectName: "touchGate-"+name
121122 targetItem: surfaceItem
122123 anchors.fill: root
123124 enabled: surfaceItem.enabled
qml/Stages/TabletSideStageTouchGesture.qmlnew file+154
@@ -0,0 +1,154 @@
1+/*
2+ * Copyright (C) 2016 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+import Ubuntu.Gestures 0.1
19+
20+TouchGestureArea {
21+ id: root
22+ minimumTouchPoints: 3
23+ maximumTouchPoints: 3
24+
25+ property bool enableDrag: true
26+ property Component dragComponent
27+ property var dragComponentProperties: undefined
28+
29+ readonly property bool recognisedPress: status == TouchGestureArea.Recognized &&
30+ touchPoints.length >= minimumTouchPoints &&
31+ touchPoints.length <= maximumTouchPoints
32+ readonly property bool recognisedDrag: priv.wasRecognisedPress && dragging
33+
34+ signal pressed(int x, int y)
35+ signal clicked
36+ signal dragStarted
37+ signal dropped
38+ signal cancelled
39+
40+ onEnabledChanged: {
41+ if (!enabled) {
42+ if (priv.dragObject) root.cancelled();
43+ priv.wasRecognisedDrag = false;
44+ priv.wasRecognisedPress = false;
45+ }
46+ }
47+
48+ onRecognisedPressChanged: {
49+ if (recognisedPress) {
50+ // get the app at the center of the gesture
51+ var centerX = 0;
52+ var centerY = 0;
53+ for (var i = 0; i < touchPoints.length; i++) {
54+ centerX += touchPoints[i].x;
55+ centerY += touchPoints[i].y;
56+ }
57+ centerX = centerX/touchPoints.length;
58+ centerY = centerY/touchPoints.length;
59+
60+ pressed(centerX, centerY);
61+ priv.wasRecognisedPress = true;
62+ }
63+ }
64+
65+ onStatusChanged: {
66+ if (status != TouchGestureArea.Recognized) {
67+ if (status == TouchGestureArea.Rejected) {
68+ root.cancelled();
69+ } else if (status == TouchGestureArea.WaitingForTouch) {
70+ if (priv.wasRecognisedPress) {
71+ if (!priv.wasRecognisedDrag) {
72+ root.clicked();
73+ } else {
74+ root.dropped();
75+ }
76+ }
77+ }
78+ priv.wasRecognisedDrag = false;
79+ priv.wasRecognisedPress = false;
80+ }
81+ }
82+
83+ onRecognisedDragChanged: {
84+ if (enableDrag && recognisedDrag) {
85+ priv.wasRecognisedDrag = true;
86+ root.dragStarted()
87+ }
88+ }
89+
90+ QtObject {
91+ id: priv
92+ property var dragObject: null
93+
94+ property bool wasRecognisedPress: false
95+ property bool wasRecognisedDrag: false
96+ }
97+
98+ onCancelled: {
99+ if (priv.dragObject) {
100+ var obj = priv.dragObject;
101+ priv.dragObject = null;
102+
103+ obj.Drag.cancel();
104+ obj.destroy();
105+ }
106+ }
107+
108+ onDragStarted: {
109+ if (dragComponentProperties) {
110+ priv.dragObject = dragComponent.createObject(root, dragComponentProperties);
111+ } else {
112+ priv.dragObject = dragComponent.createObject(root);
113+ }
114+ priv.dragObject.Drag.start();
115+ }
116+
117+ onDropped: {
118+ if (priv.dragObject) {
119+ var obj = priv.dragObject;
120+ priv.dragObject = null;
121+
122+ obj.Drag.drop();
123+ obj.destroy();
124+ }
125+ }
126+
127+ Binding {
128+ target: priv.dragObject
129+ when: priv.dragObject && priv.wasRecognisedDrag
130+ property: "x"
131+ value: {
132+ if (!priv.dragObject) return 0;
133+ var sum = 0;
134+ for (var i = 0; i < root.touchPoints.length; i++) {
135+ sum += root.touchPoints[i].x;
136+ }
137+ return sum/root.touchPoints.length - priv.dragObject.width/2;
138+ }
139+ }
140+
141+ Binding {
142+ target: priv.dragObject
143+ when: priv.dragObject && priv.wasRecognisedDrag
144+ property: "y"
145+ value: {
146+ if (!priv.dragObject) return 0;
147+ var sum = 0;
148+ for (var i = 0; i < root.touchPoints.length; i++) {
149+ sum += root.touchPoints[i].y;
150+ }
151+ return sum/root.touchPoints.length - priv.dragObject.height/2;
152+ }
153+ }
154+}
qml/Stages/TabletStage.qml+469−173
@@ -49,7 +49,7 @@ AbstractStage {
4949 if (delta < 0) { delta += 360; }
5050 delta = delta % 360;
5151
52− var supportedOrientations = spreadDelegate.application.supportedOrientations;
52+ var supportedOrientations = spreadDelegate.supportedOrientations;
5353 if (supportedOrientations === Qt.PrimaryOrientation) {
5454 supportedOrientations = spreadDelegate.orientations.primary;
5555 }
@@ -73,11 +73,32 @@ AbstractStage {
7373 }
7474 }
7575
76+ function pushRightEdge(amount) {
77+ if (spreadView.contentX == -spreadView.shift) {
78+ edgeBarrier.push(amount);
79+ }
80+ }
81+
7682 orientationChangesEnabled: priv.mainAppOrientationChangesEnabled
7783
78− supportedOrientations: mainApp ? mainApp.supportedOrientations
79− : (Qt.PortraitOrientation | Qt.LandscapeOrientation
80− | Qt.InvertedPortraitOrientation | Qt.InvertedLandscapeOrientation)
84+ supportedOrientations: {
85+ if (mainApp) {
86+ var orientations = mainApp.supportedOrientations;
87+ orientations |= Qt.LandscapeOrientation | Qt.InvertedLandscapeOrientation;
88+ if (priv.sideStageAppId && !spreadView.surfaceDragging) {
89+ // If we have a sidestage app, support Portrait orientation
90+ // so that it will switch the sidestage app to mainstage on rotate
91+ orientations |= Qt.PortraitOrientation|Qt.InvertedPortraitOrientation;
92+ }
93+ return orientations;
94+ } else {
95+ // we just don't care
96+ return Qt.PortraitOrientation |
97+ Qt.LandscapeOrientation |
98+ Qt.InvertedPortraitOrientation |
99+ Qt.InvertedLandscapeOrientation;
100+ }
101+ }
81102
82103 // How far left the stage has been dragged, used externally by tutorial code
83104 dragProgress: spreadRepeater.count > 0 ? spreadRepeater.itemAt(0).animatedProgress : 0
@@ -88,13 +109,6 @@ AbstractStage {
88109 spreadView.contentX = -spreadView.shift;
89110 }
90111
91− onShellOrientationChanged: {
92− if (shellOrientation == Qt.PortraitOrientation || shellOrientation == Qt.InvertedPortraitOrientation) {
93− ApplicationManager.focusApplication(priv.mainStageAppId);
94− priv.sideStageAppId = "";
95− }
96− }
97−
98112 onInverseProgressChanged: {
99113 // This can't be a simple binding because that would be triggered after this handler
100114 // while we need it active before doing the anition left/right
@@ -108,6 +122,38 @@ AbstractStage {
108122 priv.oldInverseProgress = inverseProgress;
109123 }
110124
125+ onAltTabPressedChanged: {
126+ if (!spreadEnabled) {
127+ return;
128+ }
129+ if (altTabPressed) {
130+ priv.highlightIndex = Math.min(spreadRepeater.count - 1, 1);
131+ spreadView.snapToSpread();
132+ } else {
133+ for (var i = 0; i < spreadRepeater.count; i++) {
134+ if (spreadRepeater.itemAt(i).zIndex === priv.highlightIndex) {
135+ spreadView.snapTo(i);
136+ return;
137+ }
138+ }
139+ }
140+ }
141+
142+ FocusScope {
143+ focus: root.altTabPressed
144+
145+ Keys.onPressed: {
146+ switch (event.key) {
147+ case Qt.Key_Tab:
148+ priv.highlightIndex = (priv.highlightIndex + 1) % spreadRepeater.count
149+ break;
150+ case Qt.Key_Backtab:
151+ priv.highlightIndex = (priv.highlightIndex + spreadRepeater.count - 1) % spreadRepeater.count
152+ break;
153+ }
154+ }
155+ }
156+
111157 QtObject {
112158 id: priv
113159 objectName: "stagesPriv"
@@ -136,20 +182,9 @@ AbstractStage {
136182
137183 property int oldInverseProgress: 0
138184
139− onFocusedAppIdChanged: {
140− if (priv.focusedAppId.length > 0) {
141− var focusedApp = ApplicationManager.findApplication(focusedAppId);
142− if (focusedApp.stage == ApplicationInfoInterface.SideStage) {
143− priv.sideStageAppId = focusedAppId;
144− } else {
145− priv.mainStageAppId = focusedAppId;
146− root.mainApp = focusedApp;
147− }
148− }
185+ property int highlightIndex: 0
149186
150− appId0 = ApplicationManager.count >= 1 ? ApplicationManager.get(0).appId : "";
151− appId1 = ApplicationManager.count > 1 ? ApplicationManager.get(1).appId : "";
152− }
187+ onFocusedAppIdChanged: updateStageApps()
153188
154189 onFocusedAppDelegateChanged: {
155190 if (focusedAppDelegate) {
@@ -191,6 +226,50 @@ AbstractStage {
191226 }
192227 return oneWayFlick;
193228 }
229+
230+ onHighlightIndexChanged: {
231+ spreadView.contentX = highlightIndex * spreadView.contentWidth / (spreadRepeater.count + 2)
232+ }
233+
234+ function getTopApp(stage) {
235+ for (var i = 0; i < ApplicationManager.count; i++) {
236+ var app = ApplicationManager.get(i)
237+ if (app.stage === stage) {
238+ return app;
239+ }
240+ }
241+ return null;
242+ }
243+
244+ function setAppStage(appId, stage, save) {
245+ var app = ApplicationManager.findApplication(appId);
246+ if (app) {
247+ app.stage = stage;
248+ if (save) {
249+ WindowStateStorage.saveStage(appId, stage);
250+ }
251+ }
252+ }
253+
254+ function updateStageApps() {
255+ var app = priv.getTopApp(ApplicationInfoInterface.MainStage);
256+ priv.mainStageAppId = app ? app.appId : ""
257+ root.mainApp = app;
258+
259+ if (sideStage.shown) {
260+ app = priv.getTopApp(ApplicationInfoInterface.SideStage);
261+ priv.sideStageAppId = app ? app.appId : ""
262+ } else {
263+ priv.sideStageAppId = "";
264+ }
265+
266+ appId0 = ApplicationManager.count >= 1 ? ApplicationManager.get(0).appId : "";
267+ appId1 = ApplicationManager.count > 1 ? ApplicationManager.get(1).appId : "";
268+ }
269+
270+ readonly property bool sideStageEnabled: root.shellOrientation == Qt.LandscapeOrientation ||
271+ root.shellOrientation == Qt.InvertedLandscapeOrientation
272+ Component.onCompleted: updateStageApps();
194273 }
195274
196275 Connections {
@@ -218,7 +297,8 @@ AbstractStage {
218297 ApplicationManager.focusApplication("unity8-dash")
219298 }
220299 if (priv.sideStageAppId == appId) {
221− priv.sideStageAppId = "";
300+ var app = priv.getTopApp(ApplicationInfoInterface.SideStage);
301+ priv.sideStageAppId = app === null ? "" : app.appId;
222302 }
223303
224304 if (ApplicationManager.count == 0) {
@@ -229,6 +309,7 @@ AbstractStage {
229309 // lets make sure the spread doesn't mess up by the changing app list.
230310 spreadView.phase = 0;
231311 spreadView.contentX = -spreadView.shift;
312+
232313 ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
233314 }
234315 }
@@ -282,6 +363,7 @@ AbstractStage {
282363 property int selectedIndex: -1
283364 property int draggedDelegateCount: 0
284365 property int closingIndex: -1
366+ property var selectedApplication: selectedIndex !== -1 ? ApplicationManager.get(selectedIndex) : null
285367
286368 // FIXME: Workaround Flickable's not keepping its contentX still when resized
287369 onContentXChanged: { forceItToRemainStillIfBeingResized(); }
@@ -303,15 +385,8 @@ AbstractStage {
303385 }
304386 }
305387
306− property bool sideStageDragging: sideStageDragHandle.dragging
307− property real sideStageDragProgress: sideStageDragHandle.progress
308−
309− onSideStageDragProgressChanged: {
310− if (sideStageDragProgress == 1) {
311− ApplicationManager.focusApplication(priv.mainStageAppId);
312− priv.sideStageAppId = "";
313− }
314− }
388+ property real sideStageDragProgress: sideStage.progress
389+ property bool surfaceDragging: triGestureArea.recognisedDrag
315390
316391 // In case the ApplicationManager already holds an app when starting up we're missing animations
317392 // Make sure we end up in the same state
@@ -362,7 +437,7 @@ AbstractStage {
362437 }
363438 ]
364439 state: {
365− if (priv.mainStageAppId && !priv.sideStageAppId) {
440+ if ((priv.mainStageAppId && !priv.sideStageAppId) || !priv.sideStageEnabled) {
366441 return "main";
367442 }
368443 if (!priv.mainStageAppId && priv.sideStageAppId) {
@@ -379,12 +454,26 @@ AbstractStage {
379454 // Flickabe.contentX wiggles during resizes. Don't react to it.
380455 return;
381456 }
382− if (spreadView.phase == 0 && spreadView.shiftedContentX > spreadView.width * spreadView.positionMarker2) {
383− spreadView.phase = 1;
384− } else if (spreadView.phase == 1 && spreadView.shiftedContentX > spreadView.width * spreadView.positionMarker4) {
385− spreadView.phase = 2;
386− } else if (spreadView.phase == 1 && spreadView.shiftedContentX < spreadView.width * spreadView.positionMarker2) {
387− spreadView.phase = 0;
457+
458+ switch (phase) {
459+ case 0:
460+ // the "spreadEnabled" part is because when code does "phase = 0; contentX = -shift" to
461+ // dismiss the spread because spreadEnabled went to false, for some reason, during tests,
462+ // Flickable might jump in and change contentX value back, causing the code below to do
463+ // "phase = 1" which will make the spread stay.
464+ // It sucks that we have no control whatsoever over whether or when Flickable animates its
465+ // contentX.
466+ if (root.spreadEnabled && shiftedContentX > width * positionMarker2) {
467+ phase = 1;
468+ }
469+ break;
470+ case 1:
471+ if (shiftedContentX < width * positionMarker2) {
472+ phase = 0;
473+ } else if (shiftedContentX >= width * positionMarker4 && !spreadDragArea.dragging) {
474+ phase = 2;
475+ }
476+ break;
388477 }
389478 }
390479
@@ -395,12 +484,18 @@ AbstractStage {
395484 } else if (shiftedContentX < phase1Width) {
396485 snapTo(1);
397486 } else {
398− // Add 1 pixel to make sure we definitely hit positionMarker4 even with rounding errors of the animation.
399− snapAnimation.targetContentX = spreadView.width * spreadView.positionMarker4 + 1 - shift;
400− snapAnimation.start();
487+ snapToSpread();
401488 }
402489 }
490+
491+ function snapToSpread() {
492+ // Add 1 pixel to make sure we definitely hit positionMarker4 even with rounding errors of the animation.
493+ snapAnimation.targetContentX = (spreadView.width * spreadView.positionMarker4) + 1 - shift;
494+ snapAnimation.start();
495+ }
496+
403497 function snapTo(index) {
498+ snapAnimation.stop();
404499 spreadView.selectedIndex = index;
405500 snapAnimation.targetContentX = -shift;
406501 snapAnimation.start();
@@ -410,55 +505,41 @@ AbstractStage {
410505 // We don't want to really reorder them in the model because that allows us to keep track
411506 // of the last focused order.
412507 function indexToZIndex(index) {
508+ // only shuffle when we've got a main and overlay
509+ if (state !== "mainAndOverlay") return index;
510+
413511 var app = ApplicationManager.get(index);
414512 if (!app) {
415513 return index;
416514 }
417515
418− var active = app.appId == priv.mainStageAppId || app.appId == priv.sideStageAppId;
419− if (active && app.stage == ApplicationInfoInterface.MainStage) {
420− // if this app is active, and its the MainStage, always put it to index 0
516+ // don't shuffle indexes greater than "actives or next"
517+ if (index > 2) return index;
518+
519+ if (app.appId === priv.mainStageAppId) {
520+ // Active main stage always at 0
421521 return 0;
422522 }
423− if (active && app.stage == ApplicationInfoInterface.SideStage) {
424− if (!priv.mainStageAppId) {
425− // Only have SS apps running. Put the active one at 0
426− return 0;
427− }
428523
429− // Precondition now: There's an active MS app and this is SS app:
430− if (spreadView.nextInStack >= 0 && ApplicationManager.get(spreadView.nextInStack).stage == ApplicationInfoInterface.MainStage) {
431− // If the next app coming from the right is a MS app, we need to elevate this SS ap above it.
432− // Put it to at least level 2, or higher if there's more apps coming in before this one.
433− return Math.max(index, 2);
434− } else {
435− // if this is no next app to come in from the right, place this one at index 1, just on top the active MS app.
524+ if (spreadView.nextInStack > 0) {
525+ var nextAppInStack = ApplicationManager.get(spreadView.nextInStack);
526+
527+ if (index === spreadView.nextInStack) {
528+ // this is the next app in stack.
529+
530+ if (app.stage === ApplicationInfoInterface.SideStage) {
531+ // if the next app in stack is a sidestage app, it must order on top of other side stage app
532+ return Math.min(2, ApplicationManager.count-1);
533+ }
436534 return 1;
437535 }
438− }
439− if (index <= 2 && app.stage == ApplicationInfoInterface.MainStage && priv.sideStageAppId) {
440− // Ok, this is an inactive MS app. If there's an active SS app around, we need to place this one
441− // in between the active MS app and the active SS app, so that it comes in from there when dragging from the right.
442− // If there's now active SS app, just leave it where it is.
443− return priv.indexOf(priv.sideStageAppId) < index ? index - 1 : index;
444− }
445− if (index == spreadView.nextInStack && app.stage == ApplicationInfoInterface.SideStage) {
446− // This is a SS app and the next one to come in from the right:
447− if (priv.sideStageAppId && priv.mainStageAppId) {
448− // If there's both, an active MS and an active SS app, put this one right on top of that
449− return 2;
536+ if (nextAppInStack.stage === ApplicationInfoInterface.SideStage) {
537+ // if the next app in stack is a sidestage app, it must order on top of other side stage app
538+ return 1;
450539 }
451− // Or if there's only one other active app, put it on top of that.
452− // The case that there isn't any other active app is already handled above.
453− return 1;
540+ return Math.min(2, ApplicationManager.count-1);
454541 }
455− if (index == 2 && spreadView.nextInStack == 1 && priv.sideStageAppId) {
456− // If its index 2 but not the next one to come in, it means
457− // we've pulled another one down to index 2. Move this one up to 2 instead.
458− return 3;
459− }
460− // don't touch all others... (mostly index > 3 + simple cases where the above doesn't shuffle much)
461− return index;
542+ return Math.min(index+1, ApplicationManager.count-1);
462543 }
463544
464545 SequentialAnimation {
@@ -476,8 +557,12 @@ AbstractStage {
476557 script: {
477558 if (spreadView.selectedIndex >= 0) {
478559 var newIndex = spreadView.selectedIndex;
560+ var application = ApplicationManager.get(newIndex);
561+ if (application.stage === ApplicationInfoInterface.SideStage) {
562+ sideStage.showNow();
563+ }
479564 spreadView.selectedIndex = -1;
480− ApplicationManager.focusApplication(ApplicationManager.get(newIndex).appId);
565+ ApplicationManager.focusApplication(application.appId);
481566 spreadView.phase = 0;
482567 spreadView.contentX = -spreadView.shift;
483568 }
@@ -485,6 +570,11 @@ AbstractStage {
485570 }
486571 }
487572
573+ Behavior on contentX {
574+ enabled: root.altTabPressed
575+ UbuntuNumberAnimation {}
576+ }
577+
488578 MouseArea {
489579 id: spreadRow
490580 x: spreadView.contentX
@@ -495,87 +585,82 @@ AbstractStage {
495585 spreadView.snapTo(0);
496586 }
497587
498− Rectangle {
499− id: sideStageBackground
500− color: "black"
501− width: root.sideStageWidth * (1 - sideStageDragHandle.progress)
502− height: priv.landscapeHeight
503− x: spreadView.width - width
504− z: spreadView.indexToZIndex(priv.indexOf(priv.sideStageAppId))
505− opacity: spreadView.phase == 0 ? 1 : 0
506− Behavior on opacity { UbuntuNumberAnimation {} }
588+ DropArea {
589+ objectName: "MainStageDropArea"
590+ anchors {
591+ left: parent.left
592+ top: parent.top
593+ bottom: parent.bottom
594+ }
595+ width: spreadView.width - sideStage.width
596+ enabled: priv.sideStageEnabled
597+
598+ onDropped: {
599+ priv.setAppStage(drag.source.appId, ApplicationInfoInterface.MainStage, true);
600+ ApplicationManager.focusApplication(drag.source.appId);
601+ }
602+ keys: "SideStage"
507603 }
508604
509− Item {
510− id: sideStageDragHandle
511− anchors.right: sideStageBackground.left
512− anchors.top: sideStageBackground.top
513− width: units.gu(2)
605+ SideStage {
606+ id: sideStage
607+ objectName: "sideStage"
514608 height: priv.landscapeHeight
515− z: sideStageBackground.z
516− opacity: spreadView.phase <= 0 && root.sideStageVisible ? 1 : 0
517− property real progress: 0
518− property bool dragging: false
609+ x: spreadView.width - width
610+ z: {
611+ if (!priv.mainStageAppId) return 0;
519612
520− Behavior on opacity { UbuntuNumberAnimation {} }
613+ if (priv.sideStageAppId && spreadView.nextInStack > 0) {
614+ var nextAppInStack = ApplicationManager.get(spreadView.nextInStack);
521615
522− Connections {
523− target: root
524− onSideStageVisibleChanged: {
525− if (root.sideStageVisible) {
526− sideStageDragHandle.progress = 0;
616+ if (nextAppInStack.stage === ApplicationInfoInterface.MainStage) {
617+ // if the next app in stack is a main stage app, put the sidestage on top of it.
618+ return 2;
527619 }
620+ return 1;
528621 }
622+
623+ return 1;
529624 }
625+ visible: progress != 0
626+ enabled: priv.sideStageEnabled && sideStageDropArea.dropAllowed
627+ opacity: priv.sideStageEnabled && !spreadView.active ? 1 : 0
628+ Behavior on opacity { UbuntuNumberAnimation {} }
530629
531− Image {
532− anchors.centerIn: parent
533− width: sideStageDragHandleMouseArea.pressed ? parent.width * 2 : parent.width
534− height: parent.height
535− source: "graphics/sidestage_handle@20.png"
536− Behavior on width { UbuntuNumberAnimation {} }
630+ onShownChanged: {
631+ if (!shown && ApplicationManager.focusedApplicationId == priv.sideStageAppId) {
632+ ApplicationManager.requestFocusApplication(priv.mainStageAppId);
633+ }
634+ priv.updateStageApps();
635+ if (shown && priv.sideStageAppId) {
636+ ApplicationManager.requestFocusApplication(priv.sideStageAppId);
637+ }
537638 }
538639
539− MouseArea {
540− id: sideStageDragHandleMouseArea
640+ DropArea {
641+ id: sideStageDropArea
642+ objectName: "SideStageDropArea"
541643 anchors.fill: parent
542− enabled: spreadView.shiftedContentX == 0
543− property int startX
544− property var gesturePoints: new Array()
545− property real totalDiff
546−
547− onPressed: {
548− gesturePoints = [];
549− startX = mouseX;
550− totalDiff = 0.0;
551− sideStageDragHandle.progress = 0;
552− sideStageDragHandle.dragging = true;
553− }
554− onMouseXChanged: {
555− totalDiff += mouseX - startX;
556− if (priv.mainStageAppId) {
557− sideStageDragHandle.progress = Math.max(0, totalDiff / root.sideStageWidth);
558− }
559− gesturePoints.push(mouseX);
644+
645+ property bool dropAllowed: true
646+
647+ onEntered: {
648+ dropAllowed = drag.keys != "Disabled";
560649 }
561− onReleased: {
562− if (priv.mainStageAppId) {
563− var oneWayFlick = priv.evaluateOneWayFlick(gesturePoints);
564− sideStageDragSnapAnimation.to = sideStageDragHandle.progress > 0.5 || oneWayFlick ? 1 : 0;
565− sideStageDragSnapAnimation.start();
566− } else {
567− sideStageDragHandle.dragging = false;
650+ onExited: {
651+ dropAllowed = true;
652+ }
653+ onDropped: {
654+ if (drop.keys == "MainStage") {
655+ priv.setAppStage(drop.source.appId, ApplicationInfoInterface.SideStage, true);
656+ ApplicationManager.requestFocusApplication(drop.source.appId);
568657 }
569658 }
570− }
571− UbuntuNumberAnimation {
572− id: sideStageDragSnapAnimation
573− target: sideStageDragHandle
574− property: "progress"
575−
576− onRunningChanged: {
577− if (!running) {
578− sideStageDragHandle.dragging = false;
659+ drag {
660+ onSourceChanged: {
661+ if (!sideStageDropArea.drag.source) {
662+ dropAllowed = true;
663+ }
579664 }
580665 }
581666 }
@@ -590,36 +675,54 @@ AbstractStage {
590675 id: spreadTile
591676 objectName: model.appId ? "tabletSpreadDelegate_" + model.appId
592677 : "tabletSpreadDelegate_null";
593− width: {
594− if (wantsMainStage) {
595− return spreadView.width;
596− } else {
597− return root.sideStageWidth;
598− }
599− }
600− height: {
601− if (wantsMainStage) {
602− return spreadView.height;
603− } else {
604− return priv.landscapeHeight;
605− }
606− }
607− active: model.appId == priv.mainStageAppId || model.appId == priv.sideStageAppId
608− zIndex: spreadView.indexToZIndex(index)
678+ width: spreadView.width
679+ height: spreadView.height
680+ active: appId == priv.mainStageAppId || appId == priv.sideStageAppId
681+ zIndex: selected && stage == ApplicationInfoInterface.MainStage ? 0 : spreadView.indexToZIndex(index)
609682 selected: spreadView.selectedIndex == index
610683 otherSelected: spreadView.selectedIndex >= 0 && !selected
611− isInSideStage: priv.sideStageAppId == model.appId
684+ isInSideStage: priv.sideStageAppId === appId
612685 interactive: !spreadView.interactive && spreadView.phase === 0 && root.interactive
613686 swipeToCloseEnabled: spreadView.interactive && !snapAnimation.running
614687 maximizedAppTopMargin: root.maximizedAppTopMargin
615− dragOffset: !isDash && model.appId == priv.mainStageAppId && root.inverseProgress > 0 && spreadView.phase === 0 ? root.inverseProgress : 0
688+ dragOffset: !isDash && appId == priv.mainStageAppId && root.inverseProgress > 0 && spreadView.phase === 0 ? root.inverseProgress : 0
616689 application: ApplicationManager.get(index)
617690 closeable: !isDash
691+ highlightShown: root.altTabPressed && priv.highlightIndex == zIndex
618692
619693 readonly property bool wantsMainStage: model.stage == ApplicationInfoInterface.MainStage
620694
621695 readonly property bool isDash: model.appId == "unity8-dash"
622696
697+ stage: model.stage
698+ fullscreen: {
699+ if (mainApp && stage === ApplicationInfoInterface.SideStage) {
700+ return mainApp.fullscreen;
701+ }
702+ return application ? application.fullscreen : false;
703+ }
704+
705+ supportedOrientations: {
706+ if (application) {
707+ var orientations = application.supportedOrientations;
708+ if (stage == ApplicationInfoInterface.MainStage) {
709+ // When an app is in the mainstage, it always supports Landscape|InvertedLandscape
710+ // so that we can drag it from the main stage to the side stage
711+ orientations |= Qt.LandscapeOrientation | Qt.InvertedLandscapeOrientation;
712+ }
713+ return orientations;
714+ } else {
715+ // we just don't care
716+ return Qt.PortraitOrientation |
717+ Qt.LandscapeOrientation |
718+ Qt.InvertedPortraitOrientation |
719+ Qt.InvertedLandscapeOrientation;
720+ }
721+ }
722+
723+ readonly property string appId: model.appId
724+ readonly property bool isDash: appId == "unity8-dash"
725+
623726 Binding {
624727 target: spreadTile.application
625728 property: "exemptFromLifecycle"
@@ -650,6 +753,28 @@ AbstractStage {
650753 enabled: spreadView.closingIndex >= 0
651754 UbuntuNumberAnimation {}
652755 }
756+ Connections {
757+ target: priv
758+ onSideStageEnabledChanged: refreshStage()
759+ }
760+
761+ Component.onCompleted: {
762+ refreshStage()
763+ stageChanged.connect(priv.updateStageApps);
764+ }
765+
766+ function refreshStage() {
767+ var stage = ApplicationInfoInterface.MainStage;
768+ if (priv.sideStageEnabled) {
769+ if (application && application.supportedOrientations & (Qt.PortraitOrientation|Qt.InvertedPortraitOrientation)) {
770+ stage = WindowStateStorage.getStage(appId);
771+ }
772+ }
773+
774+ if (model.stage !== stage) {
775+ priv.setAppStage(appId, stage, false);
776+ }
777+ }
653778
654779 // This is required because none of the bindings are triggered in some cases:
655780 // When an app is closed, it might happen that ApplicationManager.get(nextInStack)
@@ -674,7 +799,7 @@ AbstractStage {
674799
675800 // TODO: Hiding tile when progress is such that it will be off screen.
676801 property bool occluded: {
677− if (spreadView.active) return false;
802+ if (spreadView.active && !offScreen) return false;
678803 else if (spreadTile.active) return false;
679804 else if (xTranslateAnimating) return false;
680805 else if (z <= 1 && priv.focusedAppDelegateIsDislocated) return false;
@@ -698,17 +823,87 @@ AbstractStage {
698823 return progress;
699824 }
700825
701− shellOrientationAngle: wantsMainStage ? root.shellOrientationAngle : 0
702− shellOrientation: wantsMainStage ? root.shellOrientation : Qt.PortraitOrientation
703− orientations: Orientations {
704− primary: spreadTile.wantsMainStage ? root.orientations.primary : Qt.PortraitOrientation
705− native_: spreadTile.wantsMainStage ? root.orientations.native_ : Qt.PortraitOrientation
826+ shellOrientationAngle: root.shellOrientationAngle
827+ shellOrientation: root.shellOrientation
828+ orientations: root.orientations
829+
830+ states: [
831+ State {
832+ name: "MainStage"
833+ when: spreadTile.stage == ApplicationInfoInterface.MainStage
834+ },
835+ State {
836+ name: "SideStage"
837+ when: spreadTile.stage == ApplicationInfoInterface.SideStage
838+
839+ PropertyChanges {
840+ target: spreadTile
841+ width: root.sideStageWidth
842+ height: priv.landscapeHeight
843+
844+ supportedOrientations: Qt.PortraitOrientation
845+ shellOrientationAngle: 0
846+ shellOrientation: Qt.PortraitOrientation
847+ orientations: sideStageOrientations
848+ }
849+ }
850+ ]
851+
852+ Orientations {
853+ id: sideStageOrientations
854+ primary: Qt.PortraitOrientation
855+ native_: Qt.PortraitOrientation
706856 portrait: root.orientations.portrait
707857 invertedPortrait: root.orientations.invertedPortrait
708858 landscape: root.orientations.landscape
709859 invertedLandscape: root.orientations.invertedLandscape
710860 }
711861
862+ transitions: [
863+ Transition {
864+ to: "SideStage"
865+ SequentialAnimation {
866+ PropertyAction {
867+ target: spreadTile
868+ properties: "width,height,supportedOrientations,shellOrientationAngle,shellOrientation,orientations"
869+ }
870+ ScriptAction {
871+ script: {
872+ // rotate immediately.
873+ spreadTile.matchShellOrientation();
874+ if (ApplicationManager.focusedApplicationId === spreadTile.appId &&
875+ priv.sideStageEnabled && !sideStage.shown) {
876+ // Sidestage was focused, so show the side stage.
877+ sideStage.show();
878+ // if we've switched to a main app which doesnt support portrait, hide the side stage.
879+ } else if (mainApp && (mainApp.supportedOrientations & (Qt.PortraitOrientation|Qt.InvertedPortraitOrientation)) == 0) {
880+ sideStage.hideNow();
881+ }
882+ }
883+ }
884+ }
885+ },
886+ Transition {
887+ from: "SideStage"
888+ SequentialAnimation {
889+ ScriptAction {
890+ script: {
891+ if (priv.sideStageAppId === spreadTile.appId &&
892+ mainApp && (mainApp.supportedOrientations & (Qt.PortraitOrientation|Qt.InvertedPortraitOrientation)) == 0) {
893+ // The mainstage app did not natively support portrait orientation, so focus the sidestage.
894+ ApplicationManager.requestFocusApplication(spreadTile.appId);
895+ }
896+ }
897+ }
898+ PropertyAction {
899+ target: spreadTile
900+ properties: "width,height,supportedOrientations,shellOrientationAngle,shellOrientation,orientations"
901+ }
902+ ScriptAction { script: { spreadTile.matchShellOrientation(); } }
903+ }
904+ }
905+ ]
906+
712907 onClicked: {
713908 if (spreadView.phase == 2) {
714909 spreadView.snapTo(index);
@@ -728,6 +923,16 @@ AbstractStage {
728923 ApplicationManager.stopApplication(ApplicationManager.get(index).appId);
729924 }
730925
926+ onFocusChanged: {
927+ if (focus && ApplicationManager.focusedApplicationId !== appId) {
928+ ApplicationManager.focusApplication(appId);
929+ }
930+
931+ if (focus && priv.sideStageEnabled && stage === ApplicationInfoInterface.SideStage) {
932+ sideStage.show();
933+ }
934+ }
935+
731936 Binding {
732937 target: root
733938 when: model.appId == priv.mainStageAppId
@@ -752,6 +957,71 @@ AbstractStage {
752957 }
753958 }
754959
960+ TabletSideStageTouchGesture {
961+ id: triGestureArea
962+ anchors.fill: parent
963+ enabled: priv.sideStageEnabled && !spreadView.active
964+ property var dragObject: null
965+ property string appId: ""
966+ dragComponent: dragComponent
967+ dragComponentProperties: { "appId": appId }
968+
969+ onPressed: {
970+ function matchDelegate(obj) { return String(obj.objectName).indexOf("tabletSpreadDelegate") >= 0; }
971+
972+ var delegateAtCenter = Functions.itemAt(spreadRow, x, y, matchDelegate);
973+ if (!delegateAtCenter) return;
974+
975+ appId = delegateAtCenter.appId;
976+ }
977+
978+ onClicked: {
979+ if (sideStage.shown) {
980+ sideStage.hide();
981+ } else {
982+ sideStage.show();
983+ }
984+ }
985+
986+ onDragStarted: {
987+ // If we're dragging to the sidestage.
988+ if (!sideStage.shown) {
989+ sideStage.show();
990+ }
991+ }
992+
993+ Component {
994+ id: dragComponent
995+ SessionContainer {
996+ property string appId: ""
997+ property var application: ApplicationManager.findApplication(appId)
998+
999+ session: application ? application.session : null
1000+ interactive: false
1001+ resizeSurface: false
1002+ focus: false
1003+
1004+ width: units.gu(40)
1005+ height: units.gu(40)
1006+
1007+ Drag.hotSpot.x: width/2
1008+ Drag.hotSpot.y: height/2
1009+ // only accept opposite stage.
1010+ Drag.keys: {
1011+ if (!application) return "Disabled";
1012+
1013+ if (application.stage === ApplicationInfo.MainStage) {
1014+ if (application.supportedOrientations & (Qt.PortraitOrientation|Qt.InvertedPortraitOrientation)) {
1015+ return "MainStage";
1016+ }
1017+ return "Disabled";
1018+ }
1019+ return "SideStage";
1020+ }
1021+ }
1022+ }
1023+ }
1024+
7551025 //eat touch events during the right edge gesture
7561026 MouseArea {
7571027 anchors.fill: parent
@@ -761,7 +1031,8 @@ AbstractStage {
7611031 DirectionalDragArea {
7621032 id: spreadDragArea
7631033 objectName: "spreadDragArea"
764− anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
1034+ x: parent.width - root.dragAreaWidth
1035+ anchors { top: parent.top; bottom: parent.bottom }
7651036 width: root.dragAreaWidth
7661037 direction: Direction.Leftwards
7671038 enabled: (spreadView.phase != 2 && root.spreadEnabled) || dragging
@@ -809,4 +1080,29 @@ AbstractStage {
8091080 }
8101081 }
8111082 }
1083+
1084+ EdgeBarrier {
1085+ id: edgeBarrier
1086+
1087+ // NB: it does its own positioning according to the specified edge
1088+ edge: Qt.RightEdge
1089+
1090+ onPassed: {
1091+ spreadView.snapToSpread();
1092+ }
1093+ material: Component {
1094+ Item {
1095+ Rectangle {
1096+ width: parent.height
1097+ height: parent.width
1098+ rotation: 90
1099+ anchors.centerIn: parent
1100+ gradient: Gradient {
1101+ GradientStop { position: 0.0; color: Qt.rgba(0.16,0.16,0.16,0.7)}
1102+ GradientStop { position: 1.0; color: Qt.rgba(0.16,0.16,0.16,0)}
1103+ }
1104+ }
1105+ }
1106+ }
1107+ }
8121108}
qml/Stages/TransformedTabletSpreadDelegate.qml+84−56
@@ -31,6 +31,7 @@ SpreadDelegate {
3131 // Set this to true when this tile a currently active on either the MS or the SS.
3232 property bool active: false
3333
34+ property int stage
3435 property int zIndex
3536 property real progress: 0
3637 property real animatedProgress: 0
@@ -49,10 +50,11 @@ SpreadDelegate {
4950
5051 property int dragOffset: 0
5152 readonly property alias xTranslateAnimating: xTranslateAnimation.running
53+ readonly property bool offScreen: priv.xTranslate >= 0
5254
5355 dropShadow: spreadView.active ||
5456 (active
55− && (model.stage == ApplicationInfoInterface.MainStage || !priv.shellIsLandscape)
57+ && (stage == ApplicationInfoInterface.MainStage || !priv.shellIsLandscape)
5658 && priv.xTranslate != 0)
5759
5860 onSelectedChanged: {
@@ -141,9 +143,9 @@ SpreadDelegate {
141143 enabled: !spreadView.active &&
142144 !snapAnimation.running &&
143145 model.appId !== "unity8-dash" &&
144− !spreadView.sideStageDragging &&
145146 spreadView.animateX &&
146− !spreadView.beingResized
147+ !spreadView.beingResized &&
148+ priv.state !== "sideStage"
147149 UbuntuNumberAnimation {
148150 id: xTranslateAnimation
149151 duration: UbuntuAnimation.FastDuration
@@ -153,21 +155,20 @@ SpreadDelegate {
153155 property real xTranslate: {
154156 var newTranslate = 0;
155157
156− if (otherSelected) {
157− return priv.selectedXTranslate;
158− }
159−
160− if (isSelected) {
161− if (model.stage == ApplicationInfoInterface.MainStage) {
158+ // selected app or opposite stage active app.
159+ if (isSelected || (otherSelected && root.active && spreadView.selectedApplication && spreadView.selectedApplication.stage !== stage)) {
160+ if (stage == ApplicationInfoInterface.MainStage) {
162161 return linearAnimation(selectedProgress, negativeProgress, selectedXTranslate, -spreadView.width, root.progress);
163162 } else {
164163 return linearAnimation(selectedProgress, negativeProgress, selectedXTranslate, -spreadView.sideStageWidth, root.progress);
165164 }
165+ } else if (otherSelected) {
166+ return selectedXTranslate;
166167 }
167168
168169 // The tile should move a bit to the left if a new one comes on top of it, but not for the Side Stage and not
169170 // when we're only dragging the side stage in on top of a main stage app
170− var shouldMoveAway = spreadView.nextInStack >= 0 && priv.movedActive && model.stage === ApplicationInfoInterface.MainStage &&
171+ var shouldMoveAway = spreadView.nextInStack >= 0 && priv.movedActive && stage === ApplicationInfoInterface.MainStage &&
171172 ApplicationManager.get(spreadView.nextInStack).stage === ApplicationInfoInterface.MainStage;
172173
173174 if (active) {
@@ -177,36 +178,45 @@ SpreadDelegate {
177178 newTranslate += linearAnimation(0, spreadView.positionMarker2, 0, -units.gu(4), root.animatedProgress);
178179 }
179180 newTranslate += root.dragOffset;
180− }
181− if (!spreadView.active && model.appId == "unity8-dash" && !root.active) {
181+ } else if (!spreadView.active && model.appId == "unity8-dash") {
182182 newTranslate -= root.width;
183183 }
184184
185− if (nextInStack && spreadView.phase == 0) {
186− if (model.stage == ApplicationInfoInterface.MainStage) {
187− if (spreadView.sideStageVisible && root.progress > 0) {
188− // Move it so it appears from behind the side stage immediately
189− newTranslate += -spreadView.sideStageWidth;
185+ if (!spreadView.active) {
186+ return newTranslate;
187+ }
188+
189+ var shadowOffset = units.gu(2);
190+
191+ if (spreadView.phase == 0) {
192+ if (nextInStack) {
193+ if (stage == ApplicationInfoInterface.MainStage) {
194+ if (spreadView.sideStageVisible && root.progress > 0) {
195+ // Move it so it appears from behind the side stage immediately
196+ newTranslate += -spreadView.sideStageWidth;
197+ }
190198 }
191− }
192199
193− if (model.stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
194− // This is when we only drag the side stage in, without rotation or snapping
195− newTranslate = linearAnimation(0, spreadView.positionMarker2, 0, -spreadView.sideStageWidth, root.progress);
196− } else {
197− newTranslate += linearAnimation(0, spreadView.positionMarker2, 0, -spreadView.sideStageWidth * spreadView.snapPosition, root.animatedProgress);
200+ if (stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
201+ // This is when we only drag the side stage in, without rotation or snapping
202+ newTranslate = linearAnimation(0, spreadView.positionMarker2, 0, -spreadView.sideStageWidth, root.animatedProgress);
203+ } else {
204+ newTranslate += linearAnimation(0, spreadView.positionMarker2, 0, -spreadView.sideStageWidth * spreadView.snapPosition, root.animatedProgress);
205+ }
206+ } else if (!root.active) {
207+ newTranslate += shadowOffset;
198208 }
199209 }
200210
201211 if (spreadView.phase == 1) {
202212 if (nextInStack) {
203− if (model.stage == ApplicationInfoInterface.MainStage) {
213+ if (stage == ApplicationInfoInterface.MainStage) {
204214 var startValue = -spreadView.sideStageWidth * spreadView.snapPosition + (spreadView.sideStageVisible ? -spreadView.sideStageWidth : 0);
205215 newTranslate += linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, startValue, priv.phase2StartTranslate, root.animatedProgress);
206216 } else {
207217 var endValue = -spreadView.width + spreadView.width * root.zIndex / 6;
208218 if (!spreadView.sideStageVisible) {
209− newTranslate += linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, -spreadView.sideStageWidth, priv.phase2StartTranslate, root.progress);
219+ newTranslate += linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, -spreadView.sideStageWidth, priv.phase2StartTranslate, root.animatedProgress);
210220 } else {
211221 newTranslate += linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, -spreadView.sideStageWidth * spreadView.snapPosition, priv.phase2StartTranslate, root.animatedProgress);
212222 }
@@ -215,16 +225,20 @@ SpreadDelegate {
215225 var startProgress = spreadView.positionMarker2 - (zIndex * spreadView.positionMarker2 / 2);
216226 var endProgress = spreadView.positionMarker4 - (zIndex * spreadView.tileDistance / spreadView.width);
217227 var startTranslate = -root.width + (shouldMoveAway ? -units.gu(4) : 0);
218− newTranslate = linearAnimation(startProgress, endProgress, startTranslate, priv.phase2StartTranslate, root.progress);
228+ newTranslate = linearAnimation(startProgress, endProgress, startTranslate, priv.phase2StartTranslate, root.animatedProgress);
219229 } else {
220230 var startProgress = spreadView.positionMarker2 - (zIndex * spreadView.positionMarker2 / 2);
221231 var endProgress = spreadView.positionMarker4 - (zIndex * spreadView.tileDistance / spreadView.width);
222− newTranslate = linearAnimation(startProgress, endProgress, 0, priv.phase2StartTranslate, root.progress);
232+ newTranslate = linearAnimation(startProgress, endProgress, shadowOffset * Math.max(0, zIndex-2), priv.phase2StartTranslate, root.animatedProgress);
223233 }
224234 }
225235
226236 if (spreadView.phase == 2) {
227− newTranslate = -easingCurve.value * (spreadView.width - root.zIndex * animatedEndDistance);
237+ if (easingCurve.value == 0 && !nextInStack) {
238+ newTranslate = shadowOffset;
239+ } else {
240+ newTranslate = -easingCurve.value * (spreadView.width - root.zIndex * animatedEndDistance);
241+ }
228242 }
229243
230244 return newTranslate;
@@ -235,33 +249,30 @@ SpreadDelegate {
235249 return 1;
236250 }
237251
238− if (otherSelected) {
239− return selectedScale;
240− }
241−
242− if (isSelected) {
252+ // selected app or opposite stage active app.
253+ if (isSelected || (otherSelected && root.active && spreadView.selectedApplication && spreadView.selectedApplication.stage !== stage)) {
243254 return linearAnimation(selectedProgress, negativeProgress, selectedScale, 1, root.progress);
255+ } else if (otherSelected) {
256+ return selectedScale;
244257 }
245258
246259 if (spreadView.phase == 0) {
247260 if (nextInStack) {
248− if (model.stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
261+ if (stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
249262 return 1;
250263 } else {
251264 var targetScale = root.dragStartScale - ((root.dragStartScale - 1) * spreadView.snapPosition);
252265 return linearAnimation(0, spreadView.positionMarker2, root.dragStartScale, targetScale, root.animatedProgress);
253266 }
254− } else if (active) {
255− return 1;
256267 } else {
257− return linearAnimation(0, spreadView.positionMarker2, root.startScale, root.endScale, root.progress);
268+ return 1;
258269 }
259270 }
260271
261272 if (spreadView.phase == 1) {
262273 if (nextInStack) {
263274 var startScale = 1;
264− if (model.stage !== ApplicationInfoInterface.SideStage || spreadView.sideStageVisible) {
275+ if (stage !== ApplicationInfoInterface.SideStage || spreadView.sideStageVisible) {
265276 startScale = root.dragStartScale - ((root.dragStartScale - 1) * spreadView.snapPosition);
266277 }
267278 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, startScale, priv.phase2StartScale, root.animatedProgress);
@@ -283,21 +294,22 @@ SpreadDelegate {
283294 return 0;
284295 }
285296
286− if (otherSelected) {
287− return selectedAngle;
288− }
289− if (isSelected) {
297+ // selected app or opposite stage active app.
298+ if (isSelected || (otherSelected && root.active && spreadView.selectedApplication && spreadView.selectedApplication.stage !== stage)) {
290299 return linearAnimation(selectedProgress, negativeProgress, selectedAngle, 0, root.progress);
300+ } else if (otherSelected) {
301+ return selectedAngle;
291302 }
292303
293304 // The tile should rotate a bit when another one comes on top, but not when only dragging the side stage in
294− var shouldMoveAway = spreadView.nextInStack >= 0 && movedActive &&
305+ var shouldMoveAway = spreadView.nextInStack == -1 ||
306+ spreadView.nextInStack >= 0 && priv.movedActive &&
295307 (ApplicationManager.get(spreadView.nextInStack).stage === ApplicationInfoInterface.MainStage ||
296− model.stage == ApplicationInfoInterface.SideStage);
308+ stage == ApplicationInfoInterface.SideStage);
297309
298310 if (spreadView.phase == 0) {
299311 if (nextInStack) {
300− if (model.stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
312+ if (stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
301313 return 0;
302314 } else {
303315 return linearAnimation(0, spreadView.positionMarker2, root.startAngle, root.startAngle * (1-spreadView.snapPosition), root.animatedProgress);
@@ -309,7 +321,7 @@ SpreadDelegate {
309321 }
310322 if (spreadView.phase == 1) {
311323 if (nextInStack) {
312− if (model.stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
324+ if (stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
313325 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, 0, priv.phase2StartAngle, root.animatedProgress);
314326 } else {
315327 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, root.startAngle * (1-spreadView.snapPosition), priv.phase2StartAngle, root.animatedProgress);
@@ -328,31 +340,47 @@ SpreadDelegate {
328340 }
329341
330342 property real opacityTransform: {
331− if (otherSelected && spreadView.phase == 2) {
343+ // animate opacity for items not snapping into view.
344+ if (spreadView.phase !== 2) return 1;
345+ if (root.isSelected) return 1;
346+
347+ if (otherSelected) {
348+ if (root.active && spreadView.selectedApplication && spreadView.selectedApplication.stage !== stage) {
349+ return 1;
350+ }
332351 return linearAnimation(selectedProgress, negativeProgress, selectedOpacity, 0, root.progress);
333352 }
334−
335353 return 1;
336354 }
337355
338356 property real topMarginProgress: {
339− if (priv.isSelected) {
357+ // selected app or opposite stage active app.
358+ if (isSelected || (otherSelected && root.active && spreadView.selectedApplication && spreadView.selectedApplication.stage !== stage)) {
340359 return linearAnimation(selectedProgress, negativeProgress, selectedTopMarginProgress, 0, root.progress);
341360 }
342− switch (spreadView.phase) {
343− case 0:
361+
362+ if (spreadView.phase == 0) {
344363 return 0;
345− case 1:
346− return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
347− 0, priv.phase2StartTopMarginProgress, root.progress);
364+ } else if (spreadView.phase == 1) {
365+ if (nextInStack) {
366+ return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
367+ 0, 1, root.progress);
368+ }
369+ var startProgress = spreadView.positionMarker2 - (zIndex * spreadView.positionMarker2 / 2);
370+ var endProgress = spreadView.positionMarker4 - (zIndex * spreadView.tileDistance / spreadView.width);
371+ return linearAnimation(startProgress, endProgress, 0, 1, root.progress);
348372 }
349373 return 1;
350374 }
351375
352376 states: [
353377 State {
354− name: "sideStageDragging"; when: spreadView.sideStageDragging && root.isInSideStage
355− PropertyChanges { target: priv; xTranslate: -spreadView.sideStageWidth + spreadView.sideStageWidth * spreadView.sideStageDragProgress }
378+ name: "sideStage";
379+ when: root.isInSideStage && spreadView.shiftedContentX == 0 && spreadView.phase == 0
380+ PropertyChanges {
381+ target: priv;
382+ xTranslate: -spreadView.sideStageWidth + spreadView.sideStageWidth * (1-spreadView.sideStageDragProgress)
383+ }
356384 }
357385 ]
358386 }
qml/Stages/WindowResizeArea.qml+8−5
@@ -40,6 +40,7 @@ MouseArea {
4040 property int defaultHeight: units.gu(50)
4141 property int screenWidth: 0
4242 property int screenHeight: 0
43+ property int leftMargin: 0
4344
4445 QtObject {
4546 id: priv
@@ -72,10 +73,12 @@ MouseArea {
7273 var windowGeometry = windowStateStorage.getGeometry(root.windowId,
7374 Qt.rect(target.x, target.y, defaultWidth, defaultHeight));
7475
75− target.requestedWidth = Math.min(Math.max(windowGeometry.width, d.minimumWidth), screenWidth);
76− target.requestedHeight = Math.min(Math.max(windowGeometry.height, d.minimumHeight), root.screenHeight - PanelState.panelHeight);
77− target.x = Math.max(Math.min(windowGeometry.x, root.screenWidth - target.requestedWidth), 0)
78− target.y = Math.max(Math.min(windowGeometry.y, root.screenHeight - target.requestedHeight), PanelState.panelHeight)
76+ target.requestedWidth = Qt.binding(function() { return Math.min(Math.max(windowGeometry.width, d.minimumWidth), screenWidth - root.leftMargin); });
77+ target.requestedHeight = Qt.binding(function() { return Math.min(Math.max(windowGeometry.height, d.minimumHeight),
78+ root.screenHeight - (target.fullscreen ? 0 : PanelState.panelHeight)); });
79+ target.x = Qt.binding(function() { return Math.max(Math.min(windowGeometry.x, root.screenWidth - root.leftMargin - target.requestedWidth),
80+ (target.fullscreen ? 0 : root.leftMargin)); });
81+ target.y = Qt.binding(function() { return Math.max(Math.min(windowGeometry.y, root.screenHeight - target.requestedHeight), PanelState.panelHeight); });
7982
8083 var windowState = windowStateStorage.getState(root.windowId, WindowStateStorage.WindowStateNormal)
8184 if (windowState === WindowStateStorage.WindowStateMaximized) {
@@ -298,7 +301,7 @@ MouseArea {
298301 }
299302
300303 if (d.topBorder) {
301− var newTargetY = d.startY + deltaY;
304+ var newTargetY = Math.max(d.startY + deltaY, PanelState.panelHeight); // disallow resizing up past Panel
302305 var bottomBorderY = target.y + target.height;
303306 if (bottomBorderY > newTargetY + d.minimumHeight) {
304307 if (bottomBorderY < newTargetY + d.maximumHeight) {
qml/Stages/graphics/sidestage_drag.svgnew file+207
@@ -0,0 +1,207 @@
1+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2+<!-- Created with Inkscape (http://www.inkscape.org/) -->
3+
4+<svg
5+ xmlns:dc="http://purl.org/dc/elements/1.1/"
6+ xmlns:cc="http://creativecommons.org/ns#"
7+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
8+ xmlns:svg="http://www.w3.org/2000/svg"
9+ xmlns="http://www.w3.org/2000/svg"
10+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
11+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
12+ width="540"
13+ height="324"
14+ id="svg4874"
15+ version="1.1"
16+ inkscape:version="0.91+devel r"
17+ viewBox="0 0 540 324"
18+ sodipodi:docname="gesture_3f_touch.svg">
19+ <defs
20+ id="defs4876" />
21+ <sodipodi:namedview
22+ id="base"
23+ pagecolor="#292929"
24+ bordercolor="#666666"
25+ borderopacity="1.0"
26+ inkscape:pageopacity="1"
27+ inkscape:pageshadow="2"
28+ inkscape:zoom="1.1785993"
29+ inkscape:cx="-19.084613"
30+ inkscape:cy="11.707485"
31+ inkscape:document-units="px"
32+ inkscape:current-layer="g4780"
33+ showgrid="false"
34+ showborder="true"
35+ fit-margin-top="0"
36+ fit-margin-left="0"
37+ fit-margin-right="0"
38+ fit-margin-bottom="0"
39+ inkscape:snap-bbox="true"
40+ inkscape:bbox-paths="true"
41+ inkscape:bbox-nodes="true"
42+ inkscape:snap-bbox-edge-midpoints="true"
43+ inkscape:snap-bbox-midpoints="true"
44+ inkscape:object-paths="true"
45+ inkscape:snap-intersection-paths="true"
46+ inkscape:object-nodes="true"
47+ inkscape:snap-smooth-nodes="true"
48+ inkscape:snap-midpoints="true"
49+ inkscape:snap-object-midpoints="true"
50+ inkscape:snap-center="true"
51+ showguides="false"
52+ inkscape:guide-bbox="true"
53+ inkscape:snap-global="true"
54+ inkscape:snap-others="false"
55+ inkscape:snap-page="true">
56+ <inkscape:grid
57+ type="xygrid"
58+ id="grid5451"
59+ empspacing="8"
60+ originx="-2.7715014e-06"
61+ originy="-2.5124622e-06" />
62+ <sodipodi:guide
63+ orientation="1,0"
64+ position="7.9999972,-8.0000025"
65+ id="guide4063"
66+ inkscape:locked="false" />
67+ <sodipodi:guide
68+ orientation="1,0"
69+ position="3.9999972,-8.0000025"
70+ id="guide4065"
71+ inkscape:locked="false" />
72+ <sodipodi:guide
73+ orientation="0,1"
74+ position="-8.0000027,87.999998"
75+ id="guide4067"
76+ inkscape:locked="false" />
77+ <sodipodi:guide
78+ orientation="0,1"
79+ position="-8.0000027,91.999998"
80+ id="guide4069"
81+ inkscape:locked="false" />
82+ <sodipodi:guide
83+ orientation="0,1"
84+ position="104,3.9999975"
85+ id="guide4071"
86+ inkscape:locked="false" />
87+ <sodipodi:guide
88+ orientation="0,1"
89+ position="-5.0000027,7.9999975"
90+ id="guide4073"
91+ inkscape:locked="false" />
92+ <sodipodi:guide
93+ orientation="1,0"
94+ position="91.999996,-8.0000025"
95+ id="guide4075"
96+ inkscape:locked="false" />
97+ <sodipodi:guide
98+ orientation="1,0"
99+ position="87.999997,-8.0000025"
100+ id="guide4077"
101+ inkscape:locked="false" />
102+ <sodipodi:guide
103+ orientation="0,1"
104+ position="-8.0000027,83.999998"
105+ id="guide4074"
106+ inkscape:locked="false" />
107+ <sodipodi:guide
108+ orientation="1,0"
109+ position="11.999997,-8.0000025"
110+ id="guide4076"
111+ inkscape:locked="false" />
112+ <sodipodi:guide
113+ orientation="0,1"
114+ position="-5.0000027,11.999997"
115+ id="guide4078"
116+ inkscape:locked="false" />
117+ <sodipodi:guide
118+ orientation="1,0"
119+ position="83.999997,-9.0000025"
120+ id="guide4080"
121+ inkscape:locked="false" />
122+ <sodipodi:guide
123+ position="47.999997,-8.0000025"
124+ orientation="1,0"
125+ id="guide4170"
126+ inkscape:locked="false" />
127+ <sodipodi:guide
128+ position="-8.0000027,47.999997"
129+ orientation="0,1"
130+ id="guide4172"
131+ inkscape:locked="false" />
132+ </sodipodi:namedview>
133+ <metadata
134+ id="metadata4879">
135+ <rdf:RDF>
136+ <cc:Work
137+ rdf:about="">
138+ <dc:format>image/svg+xml</dc:format>
139+ <dc:type
140+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
141+ <dc:title></dc:title>
142+ </cc:Work>
143+ </rdf:RDF>
144+ </metadata>
145+ <g
146+ inkscape:label="Layer 1"
147+ inkscape:groupmode="layer"
148+ id="layer1"
149+ transform="translate(67.857143,149.49498)">
150+ <g
151+ transform="matrix(0,-1,-1,0,373.50506,516.50504)"
152+ id="g4845"
153+ style="display:inline">
154+ <g
155+ inkscape:export-ydpi="90"
156+ inkscape:export-xdpi="90"
157+ inkscape:export-filename="next01.png"
158+ transform="matrix(-0.9996045,0,0,1,575.94296,-611.00001)"
159+ id="g4778"
160+ inkscape:label="Layer 1">
161+ <g
162+ transform="matrix(-1,0,0,1,575.99999,611)"
163+ id="g4780"
164+ style="display:inline">
165+ <path
166+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:53.6204567px;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;text-align:end;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:end;display:inline;fill:none;fill-opacity:1;stroke:#f7f7f7;stroke-width:4.00079107;stroke-miterlimit:4;stroke-dasharray:none"
167+ d="m 526.90578,343.72628 c 14.3803,3.85163 8.37809,26.10701 -5.96811,22.26448 l -10e-6,10e-6 -50.91437,-13.63709 67.92084,18.21108 c 15.93089,4.26693 9.28148,28.92202 -6.61162,24.66516 l -10e-6,1e-5 -67.93497,-18.15834 58.38757,15.63874 c 15.85637,4.24702 9.23761,28.78845 -6.58115,24.5515 v -2e-5 l 1e-5,-2e-5 -71.03537,-19.00646 -33.09607,-8.85524 -0.18781,-0.0503 v 0 l -6.91408,-1.85189 18.07353,23.47068 c 8.45364,10.48525 11.9521,17.66446 -3.54852,27.89697 l -43.91145,-37.31132 c -28.38683,-22.97811 -35.17793,-41.86133 -27.95127,-70.42985 10.25002,-40.5206 40.5632,-49.89857 76.24862,-40.34047 4.5224,1.21129 36.24839,9.63672 40.29164,10.71968 14.0412,3.76084 12.54552,13.74563 6.76936,26.96549 z"
168+ id="path4845-6"
169+ inkscape:connector-curvature="0"
170+ sodipodi:nodetypes="ccccccccccccccccccccssscc" />
171+ <path
172+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.25;fill:#f7f7f7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.51758671;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
173+ d="m 571.27587,383.27233 a 35.064884,35.05101 0 0 1 -16.02978,29.39649 35.064884,35.05101 0 0 1 -34.38665,28.3418 35.064884,35.05101 0 0 1 -34.85362,-31.56055 l 29.19904,7.8125 c 15.81876,4.23695 22.4371,-20.30571 6.58073,-24.55274 l -28.91769,-7.74609 a 35.064884,35.05101 0 0 1 0.01,-0.0137 l 38.45466,10.2793 c 15.8931,4.25686 22.54288,-20.39714 6.61199,-24.66407 l -48.41758,-12.98242 a 35.064884,35.05101 0 0 1 0,-0.006 l 31.41086,8.41407 c 14.3462,3.84253 20.34945,-18.414 5.96915,-22.26563 l -31.70199,-8.49023 a 35.064884,35.05101 0 0 1 29.21468,-15.70704 35.064884,35.05101 0 0 1 35.06465,35.05274 35.064884,35.05101 0 0 1 -0.0879,2.45312 35.064884,35.05101 0 0 1 11.8797,26.23828 z"
174+ id="ellipse4239-1"
175+ inkscape:connector-curvature="0" />
176+ <g
177+ style="display:inline"
178+ id="g5379"
179+ transform="matrix(-0.99999722,0,0,-0.99999722,645.25394,919.46958)">
180+ <path
181+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:#f7f7f7;stroke-width:4.00079107;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
182+ id="path4116"
183+ sodipodi:type="arc"
184+ sodipodi:cx="808.72699"
185+ sodipodi:cy="244.79436"
186+ sodipodi:rx="250.36082"
187+ sodipodi:ry="263.63275"
188+ sodipodi:start="3.8048178"
189+ sodipodi:end="5.6286868"
190+ sodipodi:open="true"
191+ d="M 611.43997,82.485822 A 250.36082,263.63275 0 0 1 809.81939,-18.835885 250.36082,263.63275 0 0 1 1007.3516,84.304899"
192+ inkscape:transform-center-x="-17.044876"
193+ inkscape:transform-center-y="-0.0015034578"
194+ transform="matrix(0,1,1,0,0,0)" />
195+ <path
196+ inkscape:transform-center-y="4.9738279"
197+ inkscape:transform-center-x="2.0443183"
198+ inkscape:connector-curvature="0"
199+ id="path4198"
200+ d="m 73.559638,1014.0226 14.78221,-18.91287 c 0,0 6.80763,12.50667 9.95238,23.00047 -10.9469,-0.5114 -24.73459,-4.0876 -24.73459,-4.0876 z"
201+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#f7f7f7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3.00059342;marker:none;enable-background:accumulate" />
202+ </g>
203+ </g>
204+ </g>
205+ </g>
206+ </g>
207+</svg>
qml/Stages/graphics/sidestage_open.svgnew file+181
@@ -0,0 +1,181 @@
1+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2+<!-- Created with Inkscape (http://www.inkscape.org/) -->
3+
4+<svg
5+ xmlns:dc="http://purl.org/dc/elements/1.1/"
6+ xmlns:cc="http://creativecommons.org/ns#"
7+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
8+ xmlns:svg="http://www.w3.org/2000/svg"
9+ xmlns="http://www.w3.org/2000/svg"
10+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
11+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
12+ width="162"
13+ height="230"
14+ id="svg4874"
15+ version="1.1"
16+ inkscape:version="0.91+devel r"
17+ viewBox="0 0 162 230"
18+ sodipodi:docname="gesture_3f_tap.svg"
19+ style="shape-rendering:crispEdges">
20+ <defs
21+ id="defs4876" />
22+ <sodipodi:namedview
23+ id="base"
24+ pagecolor="#292929"
25+ bordercolor="#666666"
26+ borderopacity="1.0"
27+ inkscape:pageopacity="1"
28+ inkscape:pageshadow="2"
29+ inkscape:zoom="1.8415614"
30+ inkscape:cx="162.13161"
31+ inkscape:cy="116.01485"
32+ inkscape:document-units="px"
33+ inkscape:current-layer="g4780"
34+ showgrid="false"
35+ showborder="true"
36+ fit-margin-top="0"
37+ fit-margin-left="0"
38+ fit-margin-right="0"
39+ fit-margin-bottom="0"
40+ inkscape:snap-bbox="true"
41+ inkscape:bbox-paths="true"
42+ inkscape:bbox-nodes="true"
43+ inkscape:snap-bbox-edge-midpoints="true"
44+ inkscape:snap-bbox-midpoints="true"
45+ inkscape:object-paths="true"
46+ inkscape:snap-intersection-paths="true"
47+ inkscape:object-nodes="true"
48+ inkscape:snap-smooth-nodes="true"
49+ inkscape:snap-midpoints="true"
50+ inkscape:snap-object-midpoints="true"
51+ inkscape:snap-center="true"
52+ showguides="false"
53+ inkscape:guide-bbox="true"
54+ inkscape:snap-global="false"
55+ inkscape:snap-others="false"
56+ inkscape:snap-page="true">
57+ <inkscape:grid
58+ type="xygrid"
59+ id="grid5451"
60+ empspacing="8"
61+ originx="-2.7715014e-06"
62+ originy="-2.5124622e-06" />
63+ <sodipodi:guide
64+ orientation="1,0"
65+ position="7.9999972,-8.0000026"
66+ id="guide4063"
67+ inkscape:locked="false" />
68+ <sodipodi:guide
69+ orientation="1,0"
70+ position="3.9999972,-8.0000026"
71+ id="guide4065"
72+ inkscape:locked="false" />
73+ <sodipodi:guide
74+ orientation="0,1"
75+ position="-8.0000027,87.999998"
76+ id="guide4067"
77+ inkscape:locked="false" />
78+ <sodipodi:guide
79+ orientation="0,1"
80+ position="-8.0000027,91.999998"
81+ id="guide4069"
82+ inkscape:locked="false" />
83+ <sodipodi:guide
84+ orientation="0,1"
85+ position="104,3.9999975"
86+ id="guide4071"
87+ inkscape:locked="false" />
88+ <sodipodi:guide
89+ orientation="0,1"
90+ position="-5.0000027,7.9999975"
91+ id="guide4073"
92+ inkscape:locked="false" />
93+ <sodipodi:guide
94+ orientation="1,0"
95+ position="91.999997,-8.0000026"
96+ id="guide4075"
97+ inkscape:locked="false" />
98+ <sodipodi:guide
99+ orientation="1,0"
100+ position="87.999997,-8.0000026"
101+ id="guide4077"
102+ inkscape:locked="false" />
103+ <sodipodi:guide
104+ orientation="0,1"
105+ position="-8.0000027,83.999998"
106+ id="guide4074"
107+ inkscape:locked="false" />
108+ <sodipodi:guide
109+ orientation="1,0"
110+ position="11.999997,-8.0000026"
111+ id="guide4076"
112+ inkscape:locked="false" />
113+ <sodipodi:guide
114+ orientation="0,1"
115+ position="-5.0000027,11.999997"
116+ id="guide4078"
117+ inkscape:locked="false" />
118+ <sodipodi:guide
119+ orientation="1,0"
120+ position="83.999997,-9.0000026"
121+ id="guide4080"
122+ inkscape:locked="false" />
123+ <sodipodi:guide
124+ position="47.999997,-8.0000026"
125+ orientation="1,0"
126+ id="guide4170"
127+ inkscape:locked="false" />
128+ <sodipodi:guide
129+ position="-8.0000027,47.999997"
130+ orientation="0,1"
131+ id="guide4172"
132+ inkscape:locked="false" />
133+ </sodipodi:namedview>
134+ <metadata
135+ id="metadata4879">
136+ <rdf:RDF>
137+ <cc:Work
138+ rdf:about="">
139+ <dc:format>image/svg+xml</dc:format>
140+ <dc:type
141+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
142+ <dc:title></dc:title>
143+ </cc:Work>
144+ </rdf:RDF>
145+ </metadata>
146+ <g
147+ inkscape:label="Layer 1"
148+ inkscape:groupmode="layer"
149+ id="layer1"
150+ transform="translate(67.857143,55.494966)">
151+ <g
152+ transform="matrix(0,-1,-1,0,373.50506,516.50504)"
153+ id="g4845"
154+ style="display:inline">
155+ <g
156+ inkscape:export-ydpi="90"
157+ inkscape:export-xdpi="90"
158+ inkscape:export-filename="next01.png"
159+ transform="matrix(-0.9996045,0,0,1,575.94296,-611.00001)"
160+ id="g4778"
161+ inkscape:label="Layer 1">
162+ <g
163+ transform="matrix(-1,0,0,1,575.99999,611)"
164+ id="g4780"
165+ style="display:inline">
166+ <path
167+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:53.6204567px;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;text-align:end;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:end;display:inline;fill:none;fill-opacity:1;stroke:#f7f7f7;stroke-width:4.00079107;stroke-miterlimit:4;stroke-dasharray:none"
168+ d="m 526.90578,343.72628 c 14.3803,3.85163 8.37809,26.10701 -5.96811,22.26448 l -10e-6,10e-6 -50.91437,-13.63709 67.92084,18.21108 c 15.93089,4.26693 9.28148,28.92202 -6.61162,24.66516 l -10e-6,1e-5 -67.93497,-18.15834 58.38757,15.63874 c 15.85637,4.24702 9.23761,28.78845 -6.58115,24.5515 v -2e-5 l 1e-5,-2e-5 -71.03537,-19.00646 -33.09607,-8.85524 -0.18781,-0.0503 v 0 l -6.91408,-1.85189 18.07353,23.47068 c 8.45364,10.48525 11.9521,17.66446 -3.54852,27.89697 l -43.91145,-37.31132 c -28.38683,-22.97811 -35.17793,-41.86133 -27.95127,-70.42985 10.25002,-40.5206 40.5632,-49.89857 76.24862,-40.34047 4.5224,1.21129 36.24839,9.63672 40.29164,10.71968 14.0412,3.76084 12.54552,13.74563 6.76936,26.96549 z"
169+ id="path4845-6"
170+ inkscape:connector-curvature="0"
171+ sodipodi:nodetypes="ccccccccccccccccccccssscc" />
172+ <path
173+ style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.5;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#f7f7f7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3.99999976;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
174+ d="m 571.45563,383.2743 c -0.0175,12.04505 -6.22338,23.19765 -16.35804,29.64062 -3.3052,16.24493 -17.57029,28.04101 -34.23815,28.04493 -18.13447,4.3e-4 -33.08059,-13.83538 -34.83995,-31.50586 l 4.16766,1.11523 c 2.22233,14.95164 15.08799,26.39099 30.67229,26.39063 15.06147,-0.004 27.92054,-10.79228 30.53552,-25.61915 l 0.15631,-0.89648 0.78156,-0.46875 c 9.37156,-5.64255 15.10396,-15.76543 15.12121,-26.70117 v -0.004 c -0.0108,-9.30873 -4.17362,-18.12357 -11.35605,-24.04883 l -0.8265,-0.68359 0.10942,-1.06641 c 0.10013,-0.96319 0.1562,-1.9301 0.16608,-2.89843 -0.004,-17.20453 -13.91519,-31.10752 -31.12755,-31.10743 -10.37812,-5e-5 -19.55027,5.05891 -25.20333,12.84375 l -4.0856,-1.09375 c 6.29481,-9.48451 17.06827,-15.75006 29.28893,-15.75 19.37764,-1e-4 35.12924,15.74527 35.12913,35.11524 v 0.01 0.01 c -0.008,0.77822 -0.0716,1.55426 -0.13091,2.33008 7.57765,6.66645 12.02745,16.21382 12.03797,26.33984 v 0.002 z m -74.3634,2.80273 c -0.004,0.005 -0.008,0.0106 -0.0117,0.0156 l -4.15595,-1.11328 c 0.003,-0.004 0.006,-0.009 0.01,-0.0137 z m -3.51507,-27.4082 -4.14421,-1.10938 c -1.8e-4,-0.002 1.7e-4,-0.004 0,-0.006 l 4.14226,1.10937 c 2.7e-4,0.002 0.002,0.004 0.002,0.006 z"
175+ id="ellipse4488"
176+ inkscape:connector-curvature="0" />
177+ </g>
178+ </g>
179+ </g>
180+ </g>
181+</svg>
qml/Wizard/CheckableSetting.qml+10−2
@@ -31,9 +31,11 @@ ListItem.Empty {
3131 signal linkActivated(string link)
3232
3333 implicitHeight: Math.max(label.height, checkBox.height)
34+ highlightWhenPressed: false
3435
35− Item {
36+ FocusScope {
3637 anchors.fill: parent
38+ focus: true
3739
3840 CheckBox {
3941 id: checkBox
@@ -77,8 +79,14 @@ ListItem.Empty {
7779 rightMargin: listItem.rightMargin
7880 }
7981 wrapMode: Text.Wrap
80− linkColor: theme.palette.normal.foregroundText
82+ linkColor: UbuntuColors.orange
83+ color: textColor
84+ font.weight: Font.Light
85+ fontSize: "small"
86+ lineHeight: 1.2
8187 onLinkActivated: listItem.linkActivated(link)
8288 }
89+
90+ Keys.onSpacePressed: checkBox.trigger()
8391 }
8492}
qml/Wizard/Page.qml+260−44
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013,2015 Canonical, Ltd.
2+ * Copyright (C) 2013-2016 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -16,14 +16,33 @@
1616
1717import QtQuick 2.4
1818import Ubuntu.Components 1.3
19+import Wizard 0.1
1920
2021Item {
21− readonly property real buttonMargin: units.gu(2)
22+ readonly property real maximumContentWidth: units.gu(50)
23+ readonly property bool wideMode: width > units.gu(90) && width > height
24+ readonly property bool contentAnimationRunning: contentAnimation.running
25+
26+ readonly property real buttonMargin: units.gu(3)
2227 readonly property real buttonWidth: (width - buttonMargin * 2) / 2 -
2328 buttonMargin / 2
24− readonly property real topMargin: units.gu(8)
25− readonly property real leftMargin: units.gu(2)
26− readonly property real rightMargin: units.gu(2)
29+ readonly property real buttonBarHeight: units.gu(5)
30+ readonly property real titleRectHeight: customTitle ? units.gu(10) : units.gu(16)
31+
32+ readonly property real topMargin: units.gu(11)
33+ readonly property real bottomMargin: units.gu(3)
34+ readonly property real leftMargin: Math.max(units.gu(3), (width - maximumContentWidth) / 3)
35+ readonly property real rightMargin: leftMargin
36+ readonly property real customMargin: wideMode ? units.gu(8) : units.gu(4) // margin for the content
37+ readonly property real staticMargin: units.gu(3)
38+
39+ // colors
40+ readonly property color backgroundColor: "#fdfdfd"
41+ readonly property color dividerColor: UbuntuColors.silk
42+ readonly property color textColor: UbuntuColors.slate
43+ readonly property color errorColor: theme.palette.normal.negative
44+ readonly property color okColor: theme.palette.normal.positive
45+ readonly property color whiteColor: "white" // workaround for a UITK bug
2746
2847 // If you want to skip a page, mark skipValid false while you figure out
2948 // whether to skip, then set it to true once you've determined the value
@@ -32,9 +51,13 @@ Item {
3251 property bool skip: false
3352
3453 property bool hasBackButton: true
54+ property string backButtonText: i18n.ctr("Button: Go back one page in the Wizard", "Back")
3555 property bool customBack: false
56+ property bool customTitle: false
3657 property alias forwardButtonSourceComponent: forwardButton.sourceComponent
3758 property alias content: contentHolder
59+ property bool lastPage: false
60+ property bool buttonBarVisible: true
3861
3962 property string title: ""
4063
@@ -43,66 +66,259 @@ Item {
4366 visible: false
4467 anchors.fill: parent
4568
46− // We want larger than even fontSize: "x-large", so we use a Text instead
47− // of a Label.
48− Text {
49− id: titleLabel
69+ Timer {
70+ id: indicatorTimer
71+ interval: 1000
72+ triggeredOnStart: true
73+ repeat: true
74+ running: System.wizardEnabled
75+ onTriggered: {
76+ indicatorTime.text = Qt.formatTime(new Date(), "h:mm")
77+ }
78+ }
79+
80+ // page header
81+ Image {
82+ id: titleRect
83+ visible: !lastPage
5084 anchors {
5185 top: parent.top
5286 left: parent.left
5387 right: parent.right
54− topMargin: topMargin
55− leftMargin: leftMargin
56− rightMargin: rightMargin
5788 }
58− wrapMode: Text.Wrap
59− text: title
60− color: theme.palette.normal.baseText
61− font.pixelSize: units.gu(4)
89+ source: customTitle ? "" : (wideMode ? "Pages/data/Desktop_header_bkg.png" : "Pages/data/Phone_header_bkg.png")
90+ height: titleRectHeight
91+ clip: true
92+
93+ // page title
94+ Label {
95+ id: titleLabel
96+ property real animatedTopMargin: 0
97+ anchors {
98+ left: parent.left
99+ right: parent.right
100+ bottom: parent.bottom
101+ bottomMargin: bottomMargin
102+ leftMargin: staticMargin
103+ rightMargin: rightMargin
104+ topMargin: titleLabel.animatedTopMargin
105+ }
106+ text: title
107+ color: customTitle ? textColor : backgroundColor
108+ fontSize: customTitle ? "large" : "x-large"
109+ font.weight: Font.Light
110+ wrapMode: Text.WordWrap
111+ maximumLineCount: 2
112+ elide: Text.ElideRight
113+ }
114+
115+ // indicators
116+ Row {
117+ id: indicatorRow
118+ visible: !customTitle
119+ anchors {
120+ top: parent.top
121+ right: parent.right
122+ topMargin: units.gu(.5)
123+ rightMargin: units.gu(.5)
124+ }
125+ height: units.gu(2)
126+ spacing: units.gu(1)
127+
128+ Icon {
129+ id: indicatorSim
130+ anchors.verticalCenter: parent.verticalCenter
131+ name: "no-simcard"
132+ height: parent.height
133+ width: height
134+ visible: !(root.simManager0.present || root.simManager1.present) && root.modemManager.modems.length > 0
135+ color: "white"
136+ }
137+
138+ Icon {
139+ id: indicatorNet
140+ anchors.verticalCenter: parent.verticalCenter
141+ name: Status.networkIcon
142+ height: parent.height
143+ width: height
144+ color: "white"
145+ }
146+
147+ Icon {
148+ id: indicatorBattery
149+ anchors.verticalCenter: parent.verticalCenter
150+ name: Status.batteryIcon
151+ height: parent.height
152+ width: height * 1.4 // the battery icon is not rectangular :/
153+ color: "white"
154+ }
155+
156+ Label {
157+ id: indicatorTime
158+ anchors.verticalCenter: parent.verticalCenter
159+ color: whiteColor
160+ fontSize: "small"
161+ }
162+ }
62163 }
63164
165+ // content
64166 Item {
65167 id: contentHolder
168+ property real animatedMargin: 0
169+ property real animatedTopMargin: 0
66170 anchors {
67− top: titleLabel.bottom
171+ top: titleRect.bottom
68172 left: parent.left
69173 right: parent.right
70− bottom: backButton.top
71− topMargin: units.gu(4)
72− leftMargin: leftMargin
73− rightMargin: rightMargin
74− bottomMargin: buttonMargin
174+ bottom: buttonBarVisible ? buttonRect.top : parent.bottom
175+ leftMargin: content.animatedMargin
176+ rightMargin: -content.animatedMargin
177+ topMargin: content.animatedTopMargin
75178 }
179+ visible: opacity > 0
76180 }
77181
78− StackButton {
79− id: backButton
80− objectName: "backButton"
81− width: buttonWidth
182+ // button bar
183+ Rectangle {
184+ id: buttonRect
185+ visible: buttonBarVisible
82186 anchors {
83− left: parent.left
84187 bottom: parent.bottom
85− leftMargin: buttonMargin
86− bottomMargin: buttonMargin
188+ bottomMargin: Qt.inputMethod.visible ? Qt.inputMethod.keyboardRectangle.height : 0
189+ left: parent.left
190+ right: parent.right
87191 }
88− z: 1
89− text: i18n.ctr("Button: Go back one page in the Wizard", "Back")
90− visible: pageStack.depth > 1 && hasBackButton
91− backArrow: true
192+ height: buttonBarHeight
193+ color: "#f5f5f5"
194+ z: content.z + 1
92195
93− onClicked: customBack ? backClicked() : pageStack.prev()
196+ StackButton {
197+ id: backButton
198+ objectName: "backButton"
199+ width: buttonWidth
200+ anchors {
201+ left: parent.left
202+ bottom: parent.bottom
203+ leftMargin: staticMargin
204+ verticalCenter: parent.verticalCenter
205+ }
206+ text: backButtonText
207+ visible: pageStack.depth > 1 && hasBackButton
208+ backArrow: true
209+
210+ onClicked: customBack ? backClicked() : pageStack.prev()
211+ }
212+
213+ Loader {
214+ id: forwardButton
215+ objectName: "forwardButton"
216+ width: buttonWidth
217+ anchors {
218+ right: parent.right
219+ bottom: parent.bottom
220+ rightMargin: staticMargin
221+ verticalCenter: parent.verticalCenter
222+ }
223+ }
94224 }
95225
96− Loader {
97− id: forwardButton
98− objectName: "forwardButton"
99− width: buttonWidth
100− anchors {
101− right: parent.right
102− bottom: parent.bottom
103− rightMargin: buttonMargin
104− bottomMargin: buttonMargin
226+ // transitions
227+ function aboutToShow(duration, direction) {
228+ startContentAnimation(duration, direction)
229+ startControlsAnimation(duration)
230+ }
231+
232+ function aboutToShowSecondary(duration) {
233+ secondaryAnimation.restart()
234+ startControlsAnimation(duration)
235+ }
236+
237+ function startContentAnimation(duration, direction) {
238+ contentAnimation.animationDurationBase = duration
239+ contentAnimation.direction = direction
240+ contentAnimation.restart()
241+ }
242+
243+ function startControlsAnimation(showDuration) {
244+ actionsShowAnimation.showDuration = showDuration
245+ actionsShowAnimation.restart()
246+ }
247+
248+ SequentialAnimation { // animation for the button bar
249+ id: actionsShowAnimation
250+ property int showDuration: 0
251+ PropertyAction {
252+ target: buttonRect
253+ property: 'opacity'
254+ value: 0
255+ }
256+ PauseAnimation { duration: Math.max(0, actionsShowAnimation.showDuration - UbuntuAnimation.SnapDuration) }
257+ NumberAnimation {
258+ target: buttonRect
259+ property: 'opacity'
260+ to: 1
261+ duration: UbuntuAnimation.SnapDuration
262+ }
263+ }
264+
265+ SequentialAnimation { // animations for the content
266+ id: contentAnimation
267+ property int animationDurationBase: UbuntuAnimation.BriskDuration
268+ readonly property int additionalDuration: 200
269+ property int direction: Qt.LeftToRight
270+ ScriptAction { // direction of the effect
271+ script: {
272+ if (contentAnimation.direction === Qt.LeftToRight) {
273+ content.animatedMargin = -content.width;
274+ } else {
275+ content.animatedMargin = content.width;
276+ }
277+ }
278+ }
279+ ParallelAnimation {
280+ NumberAnimation { // the slide-in animation
281+ targets: content
282+ property: 'animatedMargin'
283+ to: 0
284+ duration: contentAnimation.animationDurationBase + contentAnimation.additionalDuration
285+ easing.type: Easing.OutCubic
286+ }
287+ NumberAnimation { // opacity animation
288+ targets: [titleLabel,content]
289+ property: 'opacity'
290+ from: 0
291+ to: 1
292+ duration: contentAnimation.animationDurationBase
293+ }
294+ }
295+ }
296+
297+ ParallelAnimation { // animation for the secondary pages
298+ id: secondaryAnimation
299+
300+ NumberAnimation { // the slide-up animation
301+ target: titleLabel
302+ property: 'animatedTopMargin'
303+ from: content.height
304+ to: customMargin
305+ duration: UbuntuAnimation.BriskDuration
306+ easing: UbuntuAnimation.StandardEasing
307+ }
308+ NumberAnimation {
309+ target: content
310+ property: 'animatedTopMargin'
311+ from: content.height
312+ to: 0
313+ duration: UbuntuAnimation.BriskDuration
314+ easing: UbuntuAnimation.StandardEasing
315+ }
316+ NumberAnimation { // opacity animation
317+ target: content
318+ property: 'opacity'
319+ from: 0
320+ to: 1
321+ duration: UbuntuAnimation.BriskDuration
105322 }
106− z: 1
107323 }
108324}
qml/Wizard/Pages.qml+75−31
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013,2014,2015 Canonical, Ltd.
2+ * Copyright (C) 2013-2016 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -15,40 +15,77 @@
1515 */
1616
1717import QtQuick 2.4
18+import MeeGo.QOfono 0.2
1819import Ubuntu.Components 1.3
1920import Ubuntu.SystemSettings.SecurityPrivacy 1.0
21+import Ubuntu.SystemSettings.Diagnostics 1.0
2022import Wizard 0.1
2123import "../Components"
2224
23−Item {
25+StyledItem {
2426 id: root
2527 objectName: "wizardPages"
2628 focus: true
2729
28− // The background wallpaper to use
29− property string background
30−
3130 signal quit()
3231
3332 // These should be set by a security page and we apply the settings when
3433 // the user exits the wizard.
35− property int passwordMethod: UbuntuSecurityPrivacyPanel.Passcode
34+ property int passwordMethod: UbuntuSecurityPrivacyPanel.Passphrase
3635 property string password: ""
3736
37+ property bool seenSIMPage: false // we want to see the SIM page at most once
38+
39+ property alias modemManager: modemManager
40+ property alias simManager0: simManager0
41+ property alias simManager1: simManager1
42+
43+ theme: ThemeSettings {
44+ name: "Ubuntu.Components.Themes.Ambiance"
45+ }
46+
3847 UbuntuSecurityPrivacyPanel {
3948 id: securityPrivacy
4049 objectName: "securityPrivacy"
4150 }
4251
52+ UbuntuDiagnostics {
53+ id: diagnostics
54+ objectName: "diagnostics"
55+ }
56+
57+ OfonoManager { // need it here for the language and country detection
58+ id: modemManager
59+ readonly property bool gotSimCard: available && ((simManager0.ready && simManager0.present) || (simManager1.ready && simManager1.present))
60+ property bool ready: false
61+ onModemsChanged: {
62+ ready = true;
63+ }
64+ }
65+
66+ // Ideally we would query the system more cleverly than hardcoding two
67+ // sims. But we don't yet have a more clever way. :(
68+ OfonoSimManager {
69+ id: simManager0
70+ modemPath: modemManager.modems.length >= 1 ? modemManager.modems[0] : ""
71+ }
72+
73+ OfonoSimManager {
74+ id: simManager1
75+ modemPath: modemManager.modems.length >= 2 ? modemManager.modems[1] : ""
76+ }
77+
4378 function quitWizard() {
4479 pageStack.currentPage.enabled = false;
4580
46− var errorMsg = securityPrivacy.setSecurity("", password, passwordMethod)
47− if (errorMsg !== "") {
48− // Ignore (but log) any errors, since we're past where the user set
49− // the method. Worst case, we just leave the user with a swipe
50− // security method and they fix it in the system settings.
51− console.log("Error setting security method:", errorMsg)
81+ if (password != "") {
82+ var errorMsg = securityPrivacy.setSecurity("", password, passwordMethod)
83+ if (errorMsg !== "") {
84+ // Ignore (but log) any errors, since we're past where the user set
85+ // the method. Worst case, we just leave the user with a swipe
86+ // security method and they fix it in the system settings.
87+ console.log("Error setting security method:", errorMsg)
88+ }
5289 }
5390
5491 quit();
@@ -58,19 +95,10 @@ Item {
5895 anchors.fill: parent
5996 }
6097
61− Image {
62− id: image
63− // Use x/y/height/width instead of anchors so that we don't adjust
64− // the image when the OSK appears.
65− x: 0
66− y: 0
67− height: root.height
68− width: root.width
69− sourceSize.height: height
70− sourceSize.width: width
71− source: root.background
72− fillMode: Image.PreserveAspectCrop
73− visible: status === Image.Ready
98+ Rectangle {
99+ id: background
100+ anchors.fill: root
101+ color: "#fdfdfd"
74102 }
75103
76104 PageList {
@@ -86,18 +114,26 @@ Item {
86114 // If we've opened any extra (non-main) pages, pop them before
87115 // continuing so back button returns to the previous main page.
88116 while (pageList.index < pageStack.depth - 1)
89− pop()
90− load(pageList.next())
117+ pop();
118+ load(pageList.next());
91119 }
92120
93121 function prev() {
94− if (pageList.index >= pageStack.depth - 1)
95− pageList.prev() // update pageList.index, but not for extra pages
122+ var isPrimaryPage = currentPage && !currentPage.customTitle;
123+ if (pageList.index >= pageStack.depth - 1) {
124+ pageList.prev(); // update pageList.index, but not for extra pages
125+ }
96126 pop()
97127 if (!currentPage || currentPage.opacity === 0) { // undo skipped pages
98− prev()
128+ prev();
99129 } else {
100− currentPage.enabled = true
130+ currentPage.enabled = true;
131+ }
132+
133+ if (isPrimaryPage) {
134+ currentPage.aboutToShow(UbuntuAnimation.BriskDuration, Qt.LeftToRight);
135+ } else {
136+ currentPage.aboutToShowSecondary(UbuntuAnimation.BriskDuration);
101137 }
102138 }
103139
@@ -115,6 +151,13 @@ Item {
115151 // Check for immediate skip or not. We may have to wait for
116152 // skipValid to be assigned (see Connections object below)
117153 checkSkip()
154+
155+ var isPrimaryPage = !currentPage.customTitle;
156+ if (isPrimaryPage) {
157+ currentPage.aboutToShow(UbuntuAnimation.BriskDuration, Qt.RightToLeft);
158+ } else {
159+ currentPage.aboutToShowSecondary(UbuntuAnimation.BriskDuration);
160+ }
118161 }
119162
120163 function checkSkip() {
@@ -136,6 +179,7 @@ Item {
136179 objectName: "timeout"
137180 interval: 2000 // wizard pages shouldn't take long
138181 onTriggered: {
182+ console.warn("Wizard page " + pageStack.currentPage.objectName + " skipped due to taking too long!!!");
139183 pageStack.currentPage.skip = true;
140184 pageStack.currentPage.skipValid = true;
141185 }
qml/Wizard/Pages/10-welcome.qml+121−48
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013-2015 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -24,60 +24,124 @@ import ".." as LocalComponents
2424LocalComponents.Page {
2525 objectName: "languagePage"
2626
27− title: i18n.tr("Hi!")
27+ title: i18n.tr("Language")
2828 forwardButtonSourceComponent: forwardButton
2929
3030 UbuntuLanguagePlugin {
3131 id: plugin
3232 }
3333
34− Column {
35− id: column
36− anchors.fill: content
37− spacing: units.gu(1)
38−
39− Label {
40− id: label1
41− anchors.left: parent.left
42− anchors.right: parent.right
43− wrapMode: Text.Wrap
44− text: i18n.tr("Welcome to your Ubuntu device.")
34+ function init()
35+ {
36+ var detectedLang = "";
37+ // try to detect the language+country from the SIM card
38+ if (root.simManager0.present && root.simManager0.preferredLanguages.length > 0) {
39+ detectedLang = root.simManager0.preferredLanguages[0] + "_" + LocalePlugin.mccToCountryCode(root.simManager0.mobileCountryCode);
40+ } else if (root.simManager1.present && root.simManager1.preferredLanguages.length > 0) {
41+ detectedLang = root.simManager1.preferredLanguages[0] + "_" + LocalePlugin.mccToCountryCode(root.simManager1.mobileCountryCode);
42+ } else if (plugin.currentLanguage != -1) {
43+ detectedLang = plugin.languageCodes[plugin.currentLanguage].split(".")[0]; // remove the encoding part, after dot (en_US.utf8 -> en_US)
44+ } else {
45+ detectedLang = "en_US"; // fallback to default lang
4546 }
4647
47− Label {
48− id: label2
49− anchors.left: parent.left
50− anchors.right: parent.right
51− wrapMode: Text.Wrap
52− text: i18n.tr("Let’s get started.")
48+ // preselect the detected language
49+ for (var i = 0; i < plugin.languageCodes.length; i++) {
50+ var code = plugin.languageCodes[i].split(".")[0]; // remove the encoding part, after dot (en_US.utf8 -> en_US)
51+ if (detectedLang === code) {
52+ languagesListView.currentIndex = i;
53+ languagesListView.positionViewAtIndex(i, ListView.Center);
54+ i18n.language = plugin.languageCodes[i];
55+ break;
56+ }
57+ }
58+ }
59+
60+ // splash screen (this has to be on the first page)
61+ Image {
62+ id: splashImage
63+ anchors.top: parent.top
64+ anchors.left: parent.left
65+ anchors.right: parent.right
66+ height: parent.height
67+ source: wideMode ? "data/Desktop_splash_screen_bkg.png" : "data/Phone_splash_screen_bkg.png"
68+ fillMode: Image.PreserveAspectCrop
69+ z: 2
70+ visible: opacity > 0
71+ Component.onCompleted: splashAnimation.start()
72+ }
73+
74+ SequentialAnimation {
75+ id: splashAnimation
76+ PauseAnimation { duration: UbuntuAnimation.BriskDuration }
77+ SmoothedAnimation {
78+ target: splashImage
79+ property: "height"
80+ to: units.gu(16)
81+ duration: UbuntuAnimation.BriskDuration
5382 }
83+ NumberAnimation {
84+ target: splashImage
85+ property: 'opacity'
86+ from: 1
87+ to: 0
88+ }
89+ onStopped: init();
90+ }
91+
92+ ListView {
93+ id: languagesListView
94+ clip: true
95+ snapMode: ListView.SnapToItem
5496
55− Item { // spacer
56− height: units.gu(2)
57− width: units.gu(1) // needed else it will be ignored
97+ anchors {
98+ fill: content
99+ leftMargin: wideMode ? parent.leftMargin : 0
100+ rightMargin: wideMode ? parent.rightMargin : 0
101+ topMargin: wideMode ? parent.customMargin : 0
58102 }
59103
60− ComboButton {
61− id: combo
62− objectName: "languageCombo"
63− anchors.left: parent.left
64− anchors.right: parent.right
65− text: listview.currentItem.text
66− onClicked: expanded = !expanded
67− expandedHeight: column.height - combo.y
68− UbuntuListView {
69− id: listview
70− model: plugin.languageNames
71− currentIndex: plugin.currentLanguage
72− delegate: Standard {
73− objectName: "languageDelegate" + index
74− text: modelData
75− onClicked: {
76− listview.currentIndex = index
77− combo.expanded = false
78− i18n.language = plugin.languageCodes[index]
79− }
104+ model: plugin.languageNames
105+
106+ delegate: ListItem {
107+ id: itemDelegate
108+ objectName: "languageDelegate" + index
109+ highlightColor: backgroundColor
110+ divider.colorFrom: dividerColor
111+ divider.colorTo: backgroundColor
112+ readonly property bool isCurrent: index === ListView.view.currentIndex
113+
114+ Label {
115+ id: langLabel
116+ text: modelData
117+
118+ anchors {
119+ left: parent.left
120+ verticalCenter: parent.verticalCenter
121+ leftMargin: languagesListView.anchors.leftMargin == 0 ? staticMargin : 0
80122 }
123+
124+ fontSize: "medium"
125+ font.weight: itemDelegate.isCurrent ? Font.Normal : Font.Light
126+ color: textColor
127+ }
128+
129+ Image {
130+ anchors {
131+ right: parent.right;
132+ verticalCenter: parent.verticalCenter;
133+ rightMargin: languagesListView.anchors.rightMargin == 0 ? staticMargin : 0
134+ }
135+ fillMode: Image.PreserveAspectFit
136+ height: units.gu(1.5)
137+
138+ source: "data/Tick@30.png"
139+ visible: itemDelegate.isCurrent
140+ }
141+
142+ onClicked: {
143+ languagesListView.currentIndex = index;
144+ i18n.language = plugin.languageCodes[index];
81145 }
82146 }
83147 }
@@ -85,14 +149,23 @@ LocalComponents.Page {
85149 Component {
86150 id: forwardButton
87151 LocalComponents.StackButton {
88− text: i18n.tr("Continue")
152+ text: i18n.tr("Next")
153+ enabled: languagesListView.currentIndex != -1
89154 onClicked: {
90− if (plugin.currentLanguage !== listview.currentIndex) {
91− plugin.currentLanguage = listview.currentIndex;
92− System.updateSessionLanguage(plugin.languageCodes[listview.currentIndex]);
93− i18n.language = i18n.language; // re-notify of change after above call (for qlocale change)
155+ if (plugin.currentLanguage !== languagesListView.currentIndex) {
156+ plugin.currentLanguage = languagesListView.currentIndex;
157+ System.updateSessionLocale(plugin.languageCodes[plugin.currentLanguage]);
158+ }
159+ i18n.language = plugin.languageCodes[plugin.currentLanguage]; // re-notify of change after above call (for qlocale change)
160+
161+ if (!root.modemManager.available || !root.modemManager.ready || root.modemManager.modems.length === 0 ||
162+ (root.simManager0.present && root.simManager0.ready) || (root.simManager1.present && root.simManager1.ready) ||
163+ root.seenSIMPage) { // go to next page
164+ pageStack.next();
165+ }
166+ else {
167+ pageStack.load(Qt.resolvedUrl("sim.qml")); // show the SIM page
94168 }
95− pageStack.next()
96169 }
97170 }
98171 }
qml/Wizard/Pages/20-sim.qmldeleted−77
@@ -1,77 +0,0 @@
1−/*
2− * Copyright (C) 2013 Canonical, Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import QtQuick 2.4
18−import MeeGo.QOfono 0.2
19−import Ubuntu.Components 1.3
20−import ".." as LocalComponents
21−
22−LocalComponents.Page {
23− objectName: "simPage"
24−
25− title: i18n.tr("Add a SIM card and restart your device")
26− forwardButtonSourceComponent: forwardButton
27−
28− skipValid: !manager.available ||
29− (manager.ready && (manager.modems.length < 1 || simManager0.ready)
30− && (manager.modems.length < 2 || simManager1.ready))
31− skip: !manager.available || manager.modems.length === 0 || simManager0.present || simManager1.present
32−
33− OfonoManager {
34− id: manager
35− property bool ready: false
36− onModemsChanged: ready = true
37− }
38−
39− // Ideally we would query the system more cleverly than hardcoding two
40− // modems. But we don't yet have a more clever way. :(
41− OfonoSimManager {
42− id: simManager0
43− modemPath: manager.modems.length >= 1 ? manager.modems[0] : ""
44− }
45−
46− OfonoSimManager {
47− id: simManager1
48− modemPath: manager.modems.length >= 2 ? manager.modems[1] : ""
49− }
50−
51− Column {
52− anchors.fill: content
53− spacing: units.gu(4)
54−
55− Label {
56− anchors.left: parent.left
57− anchors.right: parent.right
58− wrapMode: Text.Wrap
59− text: i18n.tr("Without it, you won’t be able to make calls or use text messaging.")
60− }
61−
62− Image {
63− id: image
64− source: "data/meet_ubuntu_simcard@30.png"
65− height: units.gu(6.5)
66− width: units.gu(9)
67− }
68− }
69−
70− Component {
71− id: forwardButton
72− LocalComponents.StackButton {
73− text: i18n.tr("Skip")
74− onClicked: pageStack.next()
75− }
76− }
77−}
qml/Wizard/Pages/30-passwd-type.qmldeleted−140
@@ -1,140 +0,0 @@
1−/*
2− * Copyright (C) 2014,2015 Canonical, Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import QtQuick 2.4
18−import Ubuntu.Components 1.3
19−import Ubuntu.Components.ListItems 1.3
20−import Ubuntu.SystemSettings.SecurityPrivacy 1.0
21−import ".." as LocalComponents
22−
23−/**
24− * One quirk with this page: we don't actually set the password. We avoid
25− * doing it here because the user can come back to this page and change their
26− * answer. We don't run as root, so if we did set the password immediately,
27− * we'd need to prompt for their previous password when they came back and
28− * changed their answer. Which is silly UX. So instead, we just keep track
29− * of their choice and set the password at the end (see main.qml).
30− * Setting the password shouldn't fail, since Ubuntu Touch has loose password
31− * requirements, but we'll check what we can here. Ideally we'd be able to ask
32− * the system if a password is legal without actually setting that password.
33− */
34−
35−LocalComponents.Page {
36− id: passwdPage
37− objectName: "passwdPage"
38−
39− title: i18n.tr("Lock security")
40− forwardButtonSourceComponent: forwardButton
41−
42− // If the user has set a password some other way (via ubuntu-device-flash
43− // or this isn't the first time the wizard has been run, etc). We can't
44− // properly set the password again, so let's not pretend we can.
45− skip: securityPrivacy.securityType !== UbuntuSecurityPrivacyPanel.Swipe
46−
47− function indexToMethod(index) {
48− if (index === 0)
49− return UbuntuSecurityPrivacyPanel.Swipe
50− else if (index === 1)
51− return UbuntuSecurityPrivacyPanel.Passcode
52− else
53− return UbuntuSecurityPrivacyPanel.Passphrase
54− }
55−
56− function methodToIndex(method) {
57− if (method === UbuntuSecurityPrivacyPanel.Swipe)
58− return 0
59− else if (method === UbuntuSecurityPrivacyPanel.Passcode)
60− return 1
61− else
62− return 2
63− }
64−
65− Connections {
66− target: root
67− onPasswordMethodChanged: selector.selectedIndex = methodToIndex(root.passwordMethod)
68− }
69−
70− Column {
71− id: column
72− anchors.fill: content
73− spacing: units.gu(4)
74−
75− Label {
76− anchors.left: parent.left
77− anchors.right: parent.right
78− wrapMode: Text.Wrap
79− text: i18n.tr("Please select how you’d like to unlock your device.")
80− }
81−
82− ItemSelector {
83− id: selector
84− expanded: true
85− anchors.left: parent.left
86− anchors.right: parent.right
87− property color originalBackground
88−
89− model: ["", "", ""] // otherwise the delegate will show the text itself and we only want subText
90−
91− selectedIndex: methodToIndex(root.passwordMethod)
92−
93− delegate: OptionSelectorDelegate {
94− objectName: "passwdDelegate" + index
95−
96− // use subText because we want the text to be small, and we have no other way to control it
97− subText: {
98− var method = indexToMethod(index)
99− var name = ""
100− var desc = ""
101− if (method === UbuntuSecurityPrivacyPanel.Swipe) {
102− name = i18n.ctr("Label: Type of security method", "Swipe")
103− desc = i18n.ctr("Label: Description of security method", "No security")
104− } else if (method === UbuntuSecurityPrivacyPanel.Passcode) {
105− name = i18n.ctr("Label: Type of security method", "Passcode")
106− desc = i18n.ctr("Label: Description of security method", "4 digits only")
107− } else {
108− name = i18n.ctr("Label: Type of security method", "Passphrase")
109− desc = i18n.ctr("Label: Description of security method", "Numbers and letters")
110− }
111− return "<b>%1</b> (%2)".arg(name).arg(desc)
112− }
113− }
114−
115− style: Item {}
116−
117− Component.onCompleted: {
118− // A visible selected background looks bad in ListItem widgets with our theme
119− originalBackground = theme.palette.selected.background
120− theme.palette.selected.background = "transparent"
121− }
122−
123− Component.onDestruction: {
124− // Restore original theme background
125− theme.palette.selected.background = originalBackground
126− }
127− }
128− }
129−
130− Component {
131− id: forwardButton
132− LocalComponents.StackButton {
133− text: i18n.tr("Continue")
134− onClicked: {
135− root.passwordMethod = indexToMethod(selector.selectedIndex)
136− pageStack.load(Qt.resolvedUrl("passwd-set.qml"))
137− }
138− }
139− }
140−}
qml/Wizard/Pages/30-wifi.qmlnew file+210
@@ -0,0 +1,210 @@
1+/*
2+ * Copyright (C) 2013, 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+import QtQuick.Layouts 1.1
19+import QMenuModel 0.1 as QMenuModel
20+import Ubuntu.Components 1.3
21+import Wizard 0.1
22+import Ubuntu.Connectivity 1.0
23+import ".." as LocalComponents
24+
25+LocalComponents.Page {
26+ id: wifiPage
27+ objectName: "wifiPage"
28+
29+ title: i18n.tr("Connect to Wi‑Fi")
30+ forwardButtonSourceComponent: forwardButton
31+
32+ readonly property bool connected: Connectivity.online
33+
34+ function getExtendedProperty(object, propertyName, defaultValue) {
35+ if (object && object.hasOwnProperty(propertyName)) {
36+ return object[propertyName];
37+ }
38+ return defaultValue;
39+ }
40+
41+ function getAPIcon(adHoc, signalStrength, secure) {
42+ var imageName = "nm-no-connection";
43+
44+ if (adHoc) {
45+ imageName = "nm-adhoc";
46+ } else if (signalStrength == 0) {
47+ imageName = "nm-signal-00";
48+ } else if (signalStrength <= 25) {
49+ imageName = "nm-signal-25";
50+ } else if (signalStrength <= 50) {
51+ imageName = "nm-signal-50";
52+ } else if (signalStrength <= 75) {
53+ imageName = "nm-signal-75";
54+ } else if (signalStrength <= 100) {
55+ imageName = "nm-signal-100";
56+ }
57+
58+ if (secure) {
59+ imageName += "-secure";
60+ }
61+ return imageName;
62+ }
63+
64+ QMenuModel.UnityMenuModel {
65+ id: menuModel
66+ busName: "com.canonical.indicator.network"
67+ actions: { "indicator": "/com/canonical/indicator/network" }
68+ menuObjectPath: "/com/canonical/indicator/network/phone_wifi_settings"
69+ }
70+
71+ Component {
72+ id: accessPointComponent
73+ ListItem {
74+ id: accessPoint
75+ objectName: "accessPoint"
76+ highlightColor: backgroundColor
77+ enabled: menuData && menuData.sensitive || false
78+ divider.colorFrom: dividerColor
79+ divider.colorTo: backgroundColor
80+
81+ property QtObject menuData: null
82+ property var unityMenuModel: menuModel
83+ property var extendedData: menuData && menuData.ext || undefined
84+ property var strengthAction: QMenuModel.UnityMenuAction {
85+ model: unityMenuModel
86+ index: menuIndex
87+ name: getExtendedProperty(extendedData, "xCanonicalWifiApStrengthAction", "")
88+ }
89+ readonly property bool secure: getExtendedProperty(extendedData, "xCanonicalWifiApIsSecure", false)
90+ readonly property bool adHoc: getExtendedProperty(extendedData, "xCanonicalWifiApIsAdhoc", false)
91+ readonly property bool isConnected: menuData && menuData.actionState
92+ readonly property bool isEnterprise: getExtendedProperty(extendedData, "xCanonicalWifiApIsEnterprise", false)
93+ readonly property int signalStrength: strengthAction.valid ? strengthAction.state : 0
94+ property int menuIndex: -1
95+
96+ function loadAttributes() {
97+ if (!unityMenuModel || menuIndex == -1) return;
98+ unityMenuModel.loadExtendedAttributes(menuIndex, {'x-canonical-wifi-ap-is-adhoc': 'bool',
99+ 'x-canonical-wifi-ap-is-secure': 'bool',
100+ 'x-canonical-wifi-ap-is-enterprise': 'bool',
101+ 'x-canonical-wifi-ap-strength-action': 'string'});
102+ }
103+
104+ Icon {
105+ id: apIcon
106+ anchors {
107+ left: parent.left
108+ verticalCenter: parent.verticalCenter
109+ leftMargin: column.anchors.leftMargin == 0 ? staticMargin : 0
110+ }
111+ height: units.gu(2.5)
112+ width: height
113+ name: getAPIcon(accessPoint.adHoc, accessPoint.signalStrength, accessPoint.secure)
114+ color: textColor
115+ }
116+
117+ Column {
118+ anchors.verticalCenter: parent.verticalCenter
119+ anchors.left: apIcon.right
120+ anchors.leftMargin: units.gu(2)
121+ Label {
122+ id: apName
123+ text: menuData && menuData.label || ""
124+ font.weight: accessPoint.isConnected ? Font.Normal : Font.Light
125+ fontSize: "medium"
126+ color: textColor
127+ }
128+ Label {
129+ id: connectedLabel
130+ text: i18n.tr("Connected")
131+ font.weight: Font.Light
132+ fontSize: "small"
133+ color: okColor
134+ visible: accessPoint.isConnected
135+ }
136+ }
137+
138+ onClicked: {
139+ unityMenuModel.activate(menuIndex);
140+ listview.positionViewAtBeginning();
141+ }
142+ }
143+ }
144+
145+ ColumnLayout {
146+ id: column
147+ spacing: units.gu(2)
148+ anchors {
149+ fill: content
150+ topMargin: customMargin
151+ leftMargin: wideMode ? parent.leftMargin : 0
152+ rightMargin: wideMode ? parent.rightMargin : 0
153+ }
154+
155+ Label {
156+ id: label
157+ anchors.left: parent.left
158+ anchors.right: parent.right
159+ anchors.leftMargin: column.anchors.leftMargin == 0 ? staticMargin : 0
160+ font.weight: Font.Light
161+ color: "#68064d"
162+ wrapMode: Text.Wrap
163+ text: listview.count > 0 ? i18n.tr("Available Wi-Fi networks")
164+ : i18n.tr("No available Wi-Fi networks")
165+ }
166+
167+ ListView {
168+ id: listview
169+ anchors.left: parent.left
170+ anchors.right: parent.right
171+ clip: true
172+ model: menuModel
173+ Layout.fillHeight: true
174+
175+ delegate: Loader {
176+ id: loader
177+
178+ readonly property bool isAccessPoint: model.type === "unity.widgets.systemsettings.tablet.accesspoint"
179+ readonly property bool isConnected: item && item.menuData && item.menuData.actionState
180+ readonly property bool isEnterprise: item && item.isEnterprise
181+
182+ height: !!sourceComponent ? (isConnected ? units.gu(9) : units.gu(7)) : 0
183+ anchors.left: parent.left
184+ anchors.right: parent.right
185+
186+ asynchronous: true
187+ sourceComponent: {
188+ if (isAccessPoint && !isEnterprise) {
189+ return accessPointComponent;
190+ }
191+ return null;
192+ }
193+
194+ onLoaded: {
195+ item.menuData = Qt.binding(function() { return model; });
196+ item.menuIndex = Qt.binding(function() { return index; });
197+ item.loadAttributes();
198+ }
199+ }
200+ }
201+ }
202+
203+ Component {
204+ id: forwardButton
205+ LocalComponents.StackButton {
206+ text: (connected || listview.count === 0) ? i18n.tr("Next") : i18n.tr("Skip")
207+ onClicked: pageStack.next()
208+ }
209+ }
210+}
qml/Wizard/Pages/40-location.qmlnew file+291
@@ -0,0 +1,291 @@
1+/*
2+ * Copyright (C) 2013-2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+import AccountsService 0.1
19+import QMenuModel 0.1 as QMenuModel
20+import Qt.labs.folderlistmodel 2.1
21+import Ubuntu.Components 1.3
22+import ".." as LocalComponents
23+
24+LocalComponents.Page {
25+ objectName: "locationPage"
26+
27+ title: i18n.tr("Location Services")
28+ forwardButtonSourceComponent: forwardButton
29+
30+ readonly property bool pathSet: AccountsService.hereLicensePathValid
31+ skipValid: pathSet && (AccountsService.hereLicensePath === "" || termsModel.count > 0)
32+ skip: skipValid && (AccountsService.hereLicensePath === "" || termsModel.count === 2) // no files but . and ..
33+
34+ FolderListModel {
35+ id: termsModel
36+ folder: AccountsService.hereLicensePath
37+ nameFilters: ["*.html"]
38+ showOnlyReadable: true
39+ showDotAndDotDot: true // so that count == 0 means we're not done scanning yet
40+ }
41+
42+ QMenuModel.QDBusActionGroup {
43+ id: locationActionGroup
44+ objectName: "locationActionGroup"
45+ busType: QMenuModel.DBus.SessionBus
46+ busName: "com.canonical.indicator.location"
47+ objectPath: "/com/canonical/indicator/location"
48+ property variant location: action("location-detection-enabled")
49+ property variant gps: action("gps-detection-enabled")
50+ Component.onCompleted: start()
51+ }
52+
53+ Item {
54+ id: column
55+ anchors.fill: content
56+ anchors.topMargin: customMargin
57+ anchors.leftMargin: wideMode ? parent.leftMargin : 0
58+ anchors.rightMargin: wideMode ? parent.rightMargin : 0
59+
60+ Item {
61+ id: hereCheckGroup
62+ anchors {
63+ left: parent.left
64+ right: parent.right
65+ top: parent.top
66+ }
67+ height: hereCheckLabel.height + hereTermsLabel.height
68+
69+ Label {
70+ id: hereCheckLabel
71+ objectName: "hereCheckLabel"
72+ anchors {
73+ left: parent.left
74+ right: hereTermsCheck.left
75+ top: parent.top
76+ leftMargin: column.anchors.leftMargin == 0 ? staticMargin : 0
77+ rightMargin: units.gu(2)
78+ }
79+ lineHeight: 1.2
80+ wrapMode: Text.WordWrap
81+ color: textColor
82+ font.weight: checked ? Font.Normal : Font.Light
83+ text: i18n.tr("Use GPS, Wi-Fi hotspots and mobile network anonymously to detect location (recommended)")
84+ property bool checked: true
85+ }
86+
87+ Label {
88+ id: hereTermsLabel
89+ objectName: "hereTermsLabel"
90+ anchors {
91+ left: parent.left
92+ right: hereTermsCheck.left
93+ top: hereCheckLabel.bottom
94+ topMargin: units.gu(1)
95+ leftMargin: column.anchors.leftMargin == 0 ? staticMargin : 0
96+ rightMargin: units.gu(2)
97+ }
98+
99+ wrapMode: Text.WordWrap
100+ color: textColor
101+ font.weight: Font.Light
102+ fontSize: "small"
103+ lineHeight: 1.2
104+ linkColor: UbuntuColors.orange
105+ text: i18n.tr("By selecting this option you agree to the Nokia HERE") + " <a href='#'>" +
106+ // TRANSLATORS: "terms and conditions" is part of the sentence above
107+ i18n.tr("terms and conditions") + "</a>."
108+ Mouse.forwardTo: hereCheckArea // clicking on the link also selects this group
109+ Mouse.priority: Mouse.AfterItem
110+ onLinkActivated: {
111+ pageStack.load(Qt.resolvedUrl("here-terms.qml"));
112+ }
113+ }
114+
115+ Image {
116+ id: hereTermsCheck
117+ fillMode: Image.PreserveAspectFit
118+ height: units.gu(1.5)
119+ source: "data/Tick@30.png"
120+ opacity: hereCheckLabel.checked ? 1 : 0
121+ anchors.right: parent.right
122+ anchors.verticalCenter: hereCheckLabel.verticalCenter
123+ anchors.rightMargin: column.anchors.rightMargin == 0 ? staticMargin : 0
124+ }
125+
126+ MouseArea {
127+ id: hereCheckArea
128+ anchors.fill: parent
129+ z: hereCheckGroup.z - 1 // so that the link can be activated
130+ onClicked: {
131+ if (!hereCheckLabel.checked) {
132+ hereCheckLabel.checked = true;
133+ gpsCheckLabel.checked = false;
134+ nopeCheckLabel.checked = false;
135+ }
136+ }
137+ }
138+ }
139+
140+ Rectangle {
141+ id: divider
142+ anchors.left: parent.left
143+ anchors.right: parent.right
144+ anchors.top: hereCheckGroup.bottom
145+ anchors.topMargin: units.gu(3)
146+ height: units.dp(1)
147+ color: dividerColor
148+ }
149+
150+ Label {
151+ id: gpsCheckLabel
152+ objectName: "gpsCheckLabel"
153+ anchors {
154+ left: parent.left
155+ right: parent.right
156+ top: divider.bottom
157+ topMargin: units.gu(3)
158+ leftMargin: column.anchors.leftMargin == 0 ? staticMargin : 0
159+ rightMargin: units.gu(2)
160+ }
161+ text: i18n.tr("GPS only")
162+ wrapMode: Text.WordWrap
163+ color: textColor
164+ font.weight: checked ? Font.Normal : Font.Light
165+ width: content.width
166+ property bool checked: false
167+
168+ MouseArea {
169+ anchors.fill: parent
170+ onClicked: {
171+ if (!gpsCheckLabel.checked) {
172+ hereCheckLabel.checked = false;
173+ gpsCheckLabel.checked = true;
174+ nopeCheckLabel.checked = false;
175+ }
176+ }
177+ }
178+ }
179+
180+ Image {
181+ fillMode: Image.PreserveAspectFit
182+ height: units.gu(1.5)
183+ source: "data/Tick@30.png"
184+ opacity: gpsCheckLabel.checked ? 1 : 0
185+ anchors.right: parent.right
186+ anchors.verticalCenter: gpsCheckLabel.verticalCenter
187+ anchors.rightMargin: column.anchors.rightMargin == 0 ? staticMargin : 0
188+ }
189+
190+ Rectangle {
191+ id: divider2
192+ anchors.left: parent.left
193+ anchors.right: parent.right
194+ anchors.top: gpsCheckLabel.bottom
195+ anchors.topMargin: units.gu(3)
196+ height: units.dp(1)
197+ color: dividerColor
198+ }
199+
200+ Label {
201+ id: nopeCheckLabel
202+ objectName: "nopeCheckLabel"
203+ anchors {
204+ left: parent.left
205+ right: parent.right
206+ top: divider2.bottom
207+ topMargin: units.gu(3)
208+ leftMargin: column.anchors.leftMargin == 0 ? staticMargin : 0
209+ rightMargin: units.gu(2)
210+ }
211+ wrapMode: Text.WordWrap
212+ color: textColor
213+ font.weight: checked ? Font.Normal : Font.Light
214+ width: content.width
215+ text: i18n.tr("Don't use my location")
216+ property bool checked: false
217+
218+ MouseArea {
219+ anchors.fill: parent
220+ onClicked: {
221+ if (!nopeCheckLabel.checked) {
222+ hereCheckLabel.checked = false;
223+ gpsCheckLabel.checked = false;
224+ nopeCheckLabel.checked = true;
225+ }
226+ }
227+ }
228+ }
229+
230+ Image {
231+ fillMode: Image.PreserveAspectFit
232+ height: units.gu(1.5)
233+ source: "data/Tick@30.png"
234+ opacity: nopeCheckLabel.checked ? 1 : 0
235+ anchors.right: parent.right
236+ anchors.verticalCenter: nopeCheckLabel.verticalCenter
237+ anchors.rightMargin: column.anchors.rightMargin == 0 ? staticMargin : 0
238+ }
239+
240+ Rectangle {
241+ id: divider3
242+ anchors.left: parent.left
243+ anchors.right: parent.right
244+ anchors.top: nopeCheckLabel.bottom
245+ anchors.topMargin: units.gu(3)
246+ height: units.dp(1)
247+ color: dividerColor
248+ }
249+
250+ Label {
251+ id: infoLabel
252+ anchors {
253+ left: parent.left
254+ right: parent.right
255+ top: divider3.bottom
256+ topMargin: units.gu(4)
257+ leftMargin: column.anchors.leftMargin == 0 ? staticMargin : 0
258+ rightMargin: column.anchors.rightMargin == 0 ? staticMargin : 0
259+ }
260+ wrapMode: Text.Wrap
261+ text: i18n.tr("You can change it later in System Settings.")
262+ color: textColor
263+ fontSize: "small"
264+ font.weight: Font.Light
265+ width: content.width
266+ }
267+ }
268+
269+ Component {
270+ id: forwardButton
271+ LocalComponents.StackButton {
272+ text: i18n.tr("Next")
273+ onClicked: {
274+ var locationOn = gpsCheckLabel.checked || hereCheckLabel.checked;
275+ var gpsOn = gpsCheckLabel.checked || hereCheckLabel.checked;
276+ var hereOn = hereCheckLabel.checked;
277+
278+ // location service doesn't currently listen to updateState
279+ // requests, so we activate the actions if needed.
280+ if (locationActionGroup.location.state != locationOn) {
281+ locationActionGroup.location.activate();
282+ }
283+ if (locationActionGroup.gps.state != gpsOn) {
284+ locationActionGroup.gps.activate();
285+ }
286+ AccountsService.hereEnabled = hereOn;
287+ pageStack.next();
288+ }
289+ }
290+ }
291+}
qml/Wizard/Pages/40-wifi.qmldeleted−219
@@ -1,219 +0,0 @@
1−/*
2− * Copyright (C) 2013 Canonical, Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import QtQuick 2.4
18−import QMenuModel 0.1 as QMenuModel
19−import QtSystemInfo 5.0
20−import Ubuntu.Components 1.3
21−import Ubuntu.Components.ListItems 1.3 as ListItem
22−import Ubuntu.Settings.Menus 0.1 as Menus
23−import ".." as LocalComponents
24−
25−LocalComponents.Page {
26− id: wifiPage
27− objectName: "wifiPage"
28−
29− title: i18n.tr("Connect to Wi‑Fi")
30− forwardButtonSourceComponent: forwardButton
31−
32− readonly property bool connected: networkInfo.accessPointName
33−
34− function getExtendedProperty(object, propertyName, defaultValue) {
35− if (object && object.hasOwnProperty(propertyName)) {
36− return object[propertyName];
37− }
38− return defaultValue;
39− }
40−
41− QMenuModel.UnityMenuModel {
42− id: menuModel
43− busName: "com.canonical.indicator.network"
44− actions: { "indicator": "/com/canonical/indicator/network" }
45− menuObjectPath: "/com/canonical/indicator/network/phone_wifi_settings"
46− }
47−
48− NetworkInfo {
49− id: networkInfo
50−
51− property string accessPointName
52−
53− monitorCurrentNetworkMode: true
54− monitorNetworkName: true
55− monitorNetworkStatus: true
56−
57− onCurrentNetworkModeChanged: getAccessPointName()
58− onNetworkNameChanged: getAccessPointName()
59− onNetworkStatusChanged: if (status !== NetworkInfo.HomeNetwork) accessPointName = ""
60−
61− Component.onCompleted: getAccessPointName()
62−
63− function getAccessPointName() {
64− // 0 is the interface
65− if (currentNetworkMode === NetworkInfo.WlanMode &&
66− networkStatus(NetworkInfo.WlanMode, 0) === NetworkInfo.HomeNetwork)
67− accessPointName = networkName(NetworkInfo.WlanMode, 0);
68− else
69− accessPointName = "";
70− }
71− }
72−
73− Component {
74− id: accessPointComponent
75− ListItem.Standard {
76− id: accessPoint
77− objectName: "accessPoint"
78−
79− property QtObject menuData: null
80− property var unityMenuModel: menuModel
81− property var extendedData: menuData && menuData.ext || undefined
82− property var strengthAction: QMenuModel.UnityMenuAction {
83− model: unityMenuModel
84− index: menuIndex
85− name: getExtendedProperty(extendedData, "xCanonicalWifiApStrengthAction", "")
86− }
87− property bool checked: menuData && menuData.isToggled || false
88− property bool secure: getExtendedProperty(extendedData, "xCanonicalWifiApIsSecure", false)
89− property bool adHoc: getExtendedProperty(extendedData, "xCanonicalWifiApIsAdhoc", false)
90− property int signalStrength: strengthAction.valid ? strengthAction.state : 0
91− property int menuIndex: -1
92−
93− function loadAttributes() {
94− if (!unityMenuModel || menuIndex == -1) return;
95− unityMenuModel.loadExtendedAttributes(menuIndex, {'x-canonical-wifi-ap-is-adhoc': 'bool',
96− 'x-canonical-wifi-ap-is-secure': 'bool',
97− 'x-canonical-wifi-ap-strength-action': 'string'});
98− }
99−
100− signal activate()
101−
102− text: menuData && menuData.label || ""
103− enabled: menuData && menuData.sensitive || false
104− iconName: {
105− var imageName = "nm-signal-100";
106−
107− if (adHoc) {
108− imageName = "nm-adhoc";
109− } else if (signalStrength == 0) {
110− imageName = "nm-signal-00";
111− } else if (signalStrength <= 25) {
112− imageName = "nm-signal-25";
113− } else if (signalStrength <= 50) {
114− imageName = "nm-signal-50";
115− } else if (signalStrength <= 75) {
116− imageName = "nm-signal-75";
117− }
118−
119− if (secure) {
120− imageName += "-secure";
121− }
122− return imageName;
123− }
124− iconFrame: false
125− control: CheckBox {
126− id: checkBoxActive
127−
128− onClicked: {
129− accessPoint.activate();
130− }
131− }
132− style: Rectangle {
133− color: "#4c000000"
134− }
135−
136− Component.onCompleted: {
137− loadAttributes();
138− }
139− onUnityMenuModelChanged: {
140− loadAttributes();
141− }
142− onMenuIndexChanged: {
143− loadAttributes();
144− }
145− onCheckedChanged: {
146− // Can't rely on binding. Checked is assigned on click.
147− checkBoxActive.checked = checked;
148− }
149− onActivate: unityMenuModel.activate(menuIndex);
150− }
151− }
152−
153− Column {
154− id: column
155− spacing: units.gu(2)
156− anchors.top: content.top
157− anchors.bottom: content.bottom
158− anchors.left: wifiPage.left
159− anchors.right: wifiPage.right
160−
161− Label {
162− id: label
163− anchors.left: parent.left
164− anchors.leftMargin: leftMargin
165− anchors.right: parent.right
166− anchors.rightMargin: rightMargin
167− fontSize: "small"
168− text: mainMenu.count > 0 ? i18n.tr("Available networks…")
169− : i18n.tr("No available networks.")
170− }
171−
172− Flickable {
173− anchors.left: parent.left
174− anchors.right: parent.right
175− height: column.height - label.height - column.spacing
176− contentHeight: contentItem.childrenRect.height
177− clip: true
178− flickDeceleration: 1500 * units.gridUnit / 8
179− maximumFlickVelocity: 2500 * units.gridUnit / 8
180− boundsBehavior: (contentHeight > height) ? Flickable.DragAndOvershootBounds : Flickable.StopAtBounds
181−
182− Column {
183− anchors.left: parent.left
184− anchors.right: parent.right
185−
186− Repeater {
187− id: mainMenu
188−
189− model: menuModel
190−
191− delegate: Loader {
192− id: loader
193−
194− readonly property bool isAccessPoint: model.type === "unity.widgets.systemsettings.tablet.accesspoint"
195−
196− anchors.left: parent.left
197− anchors.right: parent.right
198− height: isAccessPoint ? units.gu(6) : 0
199− asynchronous: true
200− sourceComponent: isAccessPoint ? accessPointComponent : null
201−
202− onLoaded: {
203− item.menuData = Qt.binding(function() { return model; });
204− item.menuIndex = Qt.binding(function() { return index; });
205− }
206− }
207− }
208− }
209− }
210− }
211−
212− Component {
213− id: forwardButton
214− LocalComponents.StackButton {
215− text: (connected || mainMenu.count === 0) ? i18n.tr("Continue") : i18n.tr("Skip")
216− onClicked: pageStack.next()
217− }
218− }
219−}
qml/Wizard/Pages/50-location.qmldeleted−150
@@ -1,150 +0,0 @@
1−/*
2− * Copyright (C) 2013-2015 Canonical, Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import QtQuick 2.4
18−import AccountsService 0.1
19−import QMenuModel 0.1 as QMenuModel
20−import Qt.labs.folderlistmodel 2.1
21−import Ubuntu.Components 1.3
22−import ".." as LocalComponents
23−
24−LocalComponents.Page {
25− objectName: "locationPage"
26−
27− title: i18n.tr("Location")
28− forwardButtonSourceComponent: forwardButton
29−
30− property bool pathSet: AccountsService.hereLicensePathValid
31− skipValid: pathSet && (AccountsService.hereLicensePath === "" || termsModel.count > 0)
32− skip: skipValid && (AccountsService.hereLicensePath === "" || termsModel.count === 2) // no files but . and ..
33−
34− FolderListModel {
35− id: termsModel
36− folder: AccountsService.hereLicensePath
37− nameFilters: ["*.html"]
38− showOnlyReadable: true
39− showDotAndDotDot: true // so that count == 0 means we're not done scanning yet
40− }
41−
42− QMenuModel.QDBusActionGroup {
43− id: locationActionGroup
44− objectName: "locationActionGroup"
45− busType: QMenuModel.DBus.SessionBus
46− busName: "com.canonical.indicator.location"
47− objectPath: "/com/canonical/indicator/location"
48− property variant location: action("location-detection-enabled")
49− property variant gps: action("gps-detection-enabled")
50− Component.onCompleted: start()
51− }
52−
53− Column {
54− id: column
55− anchors.fill: content
56− spacing: units.gu(3)
57−
58− Label {
59− anchors.left: parent.left
60− anchors.right: parent.right
61− wrapMode: Text.Wrap
62− text: i18n.tr("Let the phone detect your location:")
63− }
64−
65− LocalComponents.CheckableSetting {
66− id: gpsCheck
67− objectName: "gpsCheck"
68− showDivider: false
69− text: i18n.tr("Using GPS only (less accurate)")
70− onTriggered: {
71− gpsCheck.checked = true;
72− hereCheck.checked = false;
73− nopeCheck.checked = false;
74− }
75− }
76−
77− Column {
78− anchors.left: parent.left
79− anchors.right: parent.right
80− height: childrenRect.height
81−
82− LocalComponents.CheckableSetting {
83− id: hereCheck
84− objectName: "hereCheck"
85− showDivider: false
86− text: i18n.tr("Using GPS, anonymized Wi-Fi and cellular network info (recommended)")
87− checked: true
88− onTriggered: {
89− gpsCheck.checked = false;
90− hereCheck.checked = true;
91− nopeCheck.checked = false;
92− }
93− }
94−
95− Label {
96− objectName: "hereTermsLink"
97− anchors.left: parent.left
98− anchors.leftMargin: hereCheck.labelOffset
99− anchors.right: parent.right
100− wrapMode: Text.Wrap
101− linkColor: theme.palette.normal.foregroundText
102− // Translators: HERE is a trademark for Nokia's location service, you probably shouldn't translate it
103− text: i18n.tr("By selecting this option you agree to the Nokia HERE <a href='#'>terms and conditions</a>.")
104− onLinkActivated: pageStack.load(Qt.resolvedUrl("here-terms.qml"))
105− }
106− }
107−
108− LocalComponents.CheckableSetting {
109− id: nopeCheck
110− objectName: "nopeCheck"
111− showDivider: false
112− text: i18n.tr("Not at all")
113− onTriggered: {
114− gpsCheck.checked = false;
115− hereCheck.checked = false;
116− nopeCheck.checked = true;
117− }
118− }
119−
120− Label {
121− anchors.left: parent.left
122− anchors.right: parent.right
123− wrapMode: Text.Wrap
124− text: i18n.tr("You can change your mind later in <b>System Settings</b>.")
125− }
126− }
127−
128− Component {
129− id: forwardButton
130− LocalComponents.StackButton {
131− text: i18n.tr("Continue")
132− onClicked: {
133− var locationOn = gpsCheck.checked || hereCheck.checked;
134− var gpsOn = gpsCheck.checked || hereCheck.checked;
135− var hereOn = hereCheck.checked;
136−
137− // location service doesn't currently listen to updateState
138− // requests, so we activate the actions if needed.
139− if (locationActionGroup.location.state != locationOn) {
140− locationActionGroup.location.activate();
141− }
142− if (locationActionGroup.gps.state != gpsOn) {
143− locationActionGroup.gps.activate();
144− }
145− AccountsService.hereEnabled = hereOn;
146− pageStack.next()
147− }
148− }
149− }
150−}
qml/Wizard/Pages/50-timezone.qmlnew file+275
@@ -0,0 +1,275 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+import QtQuick.Layouts 1.1
19+import Ubuntu.Components 1.3
20+import Wizard 0.1
21+import Ubuntu.SystemSettings.TimeDate 1.0
22+import Utils 0.1 as Utils
23+import ".." as LocalComponents
24+
25+LocalComponents.Page {
26+ id: tzPage
27+ objectName: "tzPage"
28+
29+ title: i18n.tr("Time Zone")
30+ forwardButtonSourceComponent: forwardButton
31+
32+ property string selectedTimeZone: ""
33+ readonly property bool showingMap: wideMode && width >= units.gu(110)
34+
35+ // for testing
36+ readonly property alias tdModule: timeDatePanel
37+
38+ function highlightTimezone(offset) {
39+ highlightImage.source = "data/timezonemap/timezone_" + offset + ".png";
40+ }
41+
42+ // geo coords conversion functions (adapted from libtimezonemap)
43+ function radians(degrees) {
44+ return degrees * Math.PI / 180;
45+ }
46+
47+ function longitudeToX(longitude, map_width) {
48+ const xdeg_offset = -6;
49+ const x = (map_width * (180.0 + longitude) / 360.0) + (map_width * xdeg_offset / 180.0);
50+ return x;
51+ }
52+
53+ function latitudeToY(latitude, map_height) {
54+ const bottom_lat = -59;
55+ const top_lat = 81;
56+ const top_per = top_lat / 180.0;
57+
58+ var y = 1.25 * Math.log(Math.tan(0.25*Math.PI + 0.4 * radians(latitude)));
59+ const full_range = 4.6068250867599998;
60+ const top_offset = full_range * top_per;
61+ const map_range = Math.abs(1.25 * Math.log(Math.tan(0.25*Math.PI + 0.4 * radians(bottom_lat))) - top_offset);
62+ y = Math.abs(y - top_offset);
63+ y = y / map_range;
64+ y = y * map_height;
65+ return y;
66+ }
67+
68+ function resetViews() {
69+ selectedTimeZone = ""
70+ tzList.currentIndex = -1
71+ highlightImage.source = ""
72+ pinImage.x = 0;
73+ pinImage.y = 0;
74+ }
75+
76+ UbuntuTimeDatePanel {
77+ id: timeDatePanel
78+ }
79+
80+ onContentAnimationRunningChanged: {
81+ if (!contentAnimationRunning) {
82+ if (tzList.count == 1) { // preselect the first (and only) TZ
83+ var tz = tzList.itemAt(0,0);
84+ if (!!tz) {
85+ tz.clicked();
86+ }
87+ }
88+
89+ resetViews();
90+ searchField.forceActiveFocus();
91+ }
92+ }
93+
94+ Component {
95+ id: tzComponent
96+ ListItem {
97+ id: tz
98+ objectName: "tz" + index
99+ highlightColor: backgroundColor
100+ divider.colorFrom: dividerColor
101+ divider.colorTo: backgroundColor
102+ readonly property bool currentTz: ListView.view.currentIndex === index
103+
104+ Column {
105+ anchors.verticalCenter: parent.verticalCenter
106+ anchors.left: parent.left
107+ anchors.leftMargin: !wideMode ? staticMargin : 0
108+ anchors.right: image.left
109+ anchors.rightMargin: units.gu(2)
110+
111+ Label {
112+ id: cityLabel
113+ text: displayName
114+ font.weight: tz.currentTz ? Font.Normal : Font.Light
115+ fontSize: "medium"
116+ color: textColor
117+ elide: Text.ElideMiddle
118+ maximumLineCount: 1
119+ width: parent.width
120+ }
121+ Label {
122+ id: timeLabel
123+ text: Utils.TimezoneFormatter.currentTimeInTimezoneWithAbbrev(timeZone)
124+ font.weight: tz.currentTz ? Font.Normal : Font.Light
125+ fontSize: "small"
126+ color: textColor
127+ }
128+ }
129+ Image {
130+ id: image
131+ anchors {
132+ right: parent.right
133+ verticalCenter: parent.verticalCenter
134+ rightMargin: !wideMode ? staticMargin : 0
135+ }
136+ fillMode: Image.PreserveAspectFit
137+ height: units.gu(1.5)
138+
139+ source: "data/Tick@30.png"
140+ visible: tz.currentTz
141+ }
142+
143+ onClicked: {
144+ highlightTimezone(offset);
145+ ListView.view.currentIndex = index;
146+ selectedTimeZone = timeZone;
147+ //print("Clicked at city with coords:", longitude, latitude);
148+ //print("Clicked on TZ:", timeZone);
149+ //print("Highlight at (x,y):", longitudeToX(longitude, map.width), latitudeToY(latitude, map.height));
150+ pinImage.x = Qt.binding(function() { return longitudeToX(longitude, map.width) - pinImage.width; });
151+ pinImage.y = Qt.binding(function() { return latitudeToY(latitude, map.height) - pinImage.height; });
152+ }
153+ }
154+ }
155+
156+
157+ ColumnLayout {
158+ id: leftColumn
159+ anchors {
160+ left: content.left
161+ top: content.top
162+ bottom: content.bottom
163+ right: !showingMap ? content.right : undefined
164+ leftMargin: showingMap ? staticMargin : (wideMode ? tzPage.leftMargin : 0)
165+ rightMargin: showingMap ? staticMargin : (wideMode ? tzPage.rightMargin : 0)
166+ topMargin: customMargin
167+ }
168+
169+ width: Math.min(parent.width, units.gu(34))
170+
171+ LocalComponents.WizardTextField {
172+ id: searchField
173+ objectName: "tzFilter"
174+ anchors.left: parent.left
175+ anchors.right: parent.right
176+ anchors.leftMargin: !showingMap && !wideMode ? staticMargin : undefined
177+ anchors.rightMargin: !showingMap && !wideMode ? staticMargin : undefined
178+ placeholderText: i18n.tr("Enter your city")
179+ inputMethodHints: Qt.ImhNoPredictiveText
180+ onTextChanged: resetViews();
181+ }
182+
183+ ListView {
184+ Layout.fillHeight: true
185+ id: tzList
186+ objectName: "tzList"
187+ clip: true
188+ anchors.left: parent.left
189+ anchors.right: parent.right
190+ currentIndex: -1
191+ model: TimeZoneModel {
192+ id: timeZoneModel
193+ filter: searchField.text
194+ country: i18n.language.split('_')[1].split('.')[0]
195+ }
196+ delegate: tzComponent
197+ }
198+
199+ ActivityIndicator {
200+ anchors.centerIn: tzList
201+ running: tzList.count == 0 &&
202+ searchField.length > 0 &&
203+ timeZoneModel.listUpdating
204+ visible: running
205+ }
206+ }
207+
208+ Item {
209+ id: mapContainer
210+ visible: showingMap && !contentAnimationRunning
211+ enabled: visible
212+
213+ anchors {
214+ left: leftColumn.right
215+ leftMargin: units.gu(4)
216+ right: content.right
217+ rightMargin: staticMargin
218+ top: content.top
219+ topMargin: customMargin
220+ bottom: parent.bottom
221+ bottomMargin: buttonBarHeight
222+ }
223+
224+ Item {
225+ id: map
226+ width: Math.min(parent.width, height * 1.95) // keep our aspect ratio
227+ height: parent.height
228+ anchors {
229+ centerIn: parent
230+ }
231+
232+ Image {
233+ id: backgroundImage
234+ source: "data/timezonemap/map.png"
235+ sourceSize: Qt.size(map.width, map.height)
236+ fillMode: Image.PreserveAspectFit
237+ smooth: false
238+ visible: mapContainer.visible
239+ asynchronous: true
240+ anchors.fill: parent
241+ }
242+
243+ Image {
244+ id: highlightImage
245+ sourceSize: Qt.size(map.width, map.height)
246+ fillMode: Image.PreserveAspectFit
247+ smooth: false
248+ visible: selectedTimeZone != ""
249+ asynchronous: true
250+ anchors.fill: backgroundImage
251+ }
252+
253+ Image {
254+ id: pinImage
255+ source: "data/timezonemap/pin.png"
256+ visible: x != 0 && y != 0
257+ width: units.dp(12)
258+ height: units.dp(20)
259+ z: map.z + 1
260+ }
261+ }
262+ }
263+
264+ Component {
265+ id: forwardButton
266+ LocalComponents.StackButton {
267+ text: i18n.tr("Next")
268+ enabled: selectedTimeZone != ""
269+ onClicked: {
270+ timeDatePanel.timeZone = selectedTimeZone;
271+ pageStack.next();
272+ }
273+ }
274+ }
275+}
qml/Wizard/Pages/60-reporting.qml → qml/Wizard/Pages/60-account.qmlrenamed+30−20
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013,2014 Canonical, Ltd.
2+ * Copyright (C) 2015-2016 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -16,49 +16,59 @@
1616
1717import QtQuick 2.4
1818import Ubuntu.Components 1.3
19−import Ubuntu.SystemSettings.Diagnostics 1.0
19+import AccountsService 0.1
2020import ".." as LocalComponents
2121
2222LocalComponents.Page {
23− id: reportingPage
24− objectName: "reportingPage"
23+ objectName: "accountPage"
24+ title: i18n.tr("Personalize Your Device")
2525
26− title: i18n.tr("Improving your experience")
2726 forwardButtonSourceComponent: forwardButton
2827
29− skipValid: false
30− skip: !diagnostics.reportCrashes // skip the page when the system is configured not to report crashes
31−
32− UbuntuDiagnostics {
33− id: diagnostics
34− Component.onCompleted: reportingPage.skipValid = true;
28+ QtObject {
29+ id: d
30+ readonly property string validName: nameInput.text.trim()
3531 }
3632
3733 Column {
3834 id: column
39− anchors.fill: content
40− spacing: units.gu(2)
35+ spacing: units.gu(1)
36+ anchors {
37+ fill: content
38+ leftMargin: parent.leftMargin
39+ rightMargin: parent.rightMargin
40+ topMargin: customMargin
41+ }
4142
43+ // name
4244 Label {
45+ id: nameLabel
4346 anchors.left: parent.left
4447 anchors.right: parent.right
45− wrapMode: Text.Wrap
46− text: i18n.tr("Your phone is set up to automatically report errors to Canonical and its partners, the makers of the operating system.")
48+ text: i18n.tr("Preferred Name")
49+ color: textColor
50+ font.weight: Font.Light
4751 }
4852
49− Label {
53+ LocalComponents.WizardTextField {
54+ id: nameInput
55+ objectName: "nameInput"
5056 anchors.left: parent.left
5157 anchors.right: parent.right
52− wrapMode: Text.Wrap
53− text: i18n.tr("This can be disabled in <b>System Settings</b> under <b>Security &amp; Privacy</b>")
58+ inputMethodHints: Qt.ImhNoPredictiveText
5459 }
5560 }
5661
5762 Component {
5863 id: forwardButton
5964 LocalComponents.StackButton {
60− text: i18n.tr("Continue")
61− onClicked: pageStack.next()
65+ text: d.validName ? i18n.tr("Next") : i18n.tr("Skip")
66+ onClicked: {
67+ if (d.validName) {
68+ AccountsService.realName = d.validName;
69+ }
70+ pageStack.next();
71+ }
6272 }
6373 }
6474}
qml/Wizard/Pages/70-passwd-type.qmlnew file+166
@@ -0,0 +1,166 @@
1+/*
2+ * Copyright (C) 2014-2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+import Ubuntu.Components 1.3
19+import Ubuntu.Components.ListItems 1.3
20+import Ubuntu.SystemSettings.SecurityPrivacy 1.0
21+import ".." as LocalComponents
22+
23+/**
24+ * One quirk with this page: we don't actually set the password. We avoid
25+ * doing it here because the user can come back to this page and change their
26+ * answer. We don't run as root, so if we did set the password immediately,
27+ * we'd need to prompt for their previous password when they came back and
28+ * changed their answer. Which is silly UX. So instead, we just keep track
29+ * of their choice and set the password at the end (see Pages.qml).
30+ * Setting the password shouldn't fail, since Ubuntu Touch has loose password
31+ * requirements, but we'll check what we can here. Ideally we'd be able to ask
32+ * the system if a password is legal without actually setting that password.
33+ */
34+
35+LocalComponents.Page {
36+ id: passwdPage
37+ objectName: "passwdPage"
38+
39+ title: i18n.tr("Lock Screen")
40+ forwardButtonSourceComponent: forwardButton
41+
42+ // If the user has set a password some other way (via ubuntu-device-flash
43+ // or this isn't the first time the wizard has been run, etc). We can't
44+ // properly set the password again, so let's not pretend we can.
45+ skip: securityPrivacy.securityType !== UbuntuSecurityPrivacyPanel.Swipe
46+
47+ function indexToMethod(index) {
48+ if (index === 0/* || index === 1*/)
49+ return UbuntuSecurityPrivacyPanel.Passphrase;
50+ else if (index === 1/*2*/)
51+ return UbuntuSecurityPrivacyPanel.Passcode;
52+ else
53+ return UbuntuSecurityPrivacyPanel.Swipe;
54+ }
55+
56+// Component.onCompleted: {
57+// if (root.password !== "") // the user has set a password as part of the previous page
58+// selector.currentIndex = 0;
59+// else
60+// selector.currentIndex = 1;
61+// }
62+
63+ Item {
64+ id: column
65+ anchors.fill: content
66+ anchors.topMargin: customMargin
67+ anchors.leftMargin: wideMode ? parent.leftMargin : 0
68+ anchors.rightMargin: wideMode ? parent.rightMargin : 0
69+
70+ ListView {
71+ id: selector
72+ anchors.left: parent.left
73+ anchors.right: parent.right
74+ boundsBehavior: Flickable.StopAtBounds
75+ clip: true
76+ height: childrenRect.height
77+
78+ // this is the order we want to display it; cf indexToMethod()
79+ model: [/*UbuntuSecurityPrivacyPanel.Passphrase, */UbuntuSecurityPrivacyPanel.Passphrase,
80+ UbuntuSecurityPrivacyPanel.Passcode, UbuntuSecurityPrivacyPanel.Swipe]
81+
82+ delegate: ListItem {
83+ id: itemDelegate
84+ objectName: "passwdDelegate" + index
85+ readonly property bool isCurrent: index === ListView.view.currentIndex
86+ highlightColor: backgroundColor
87+ divider.colorFrom: dividerColor
88+ divider.colorTo: backgroundColor
89+ Label {
90+ anchors.verticalCenter: parent.verticalCenter;
91+ anchors.left: parent.left
92+ anchors.leftMargin: column.anchors.leftMargin == 0 ? staticMargin : 0
93+ fontSize: "medium"
94+ color: textColor
95+ font.weight: itemDelegate.isCurrent ? Font.Normal : Font.Light
96+ text: {
97+ switch (index) {
98+// case 0:
99+// return i18n.ctr("Label: Type of security method", "Ubuntu administrator password");
100+ case 0:
101+ return i18n.ctr("Label: Type of security method", "Create new password");
102+ case 1:
103+ return i18n.ctr("Label: Type of security method", "Create passcode (numbers only)");
104+ case 2:
105+ return i18n.ctr("Label: Type of security method", "No lock code");
106+ }
107+ }
108+ width: parent.width
109+ wrapMode: Text.WordWrap
110+ }
111+
112+ Image {
113+ anchors {
114+ right: parent.right
115+ verticalCenter: parent.verticalCenter
116+ rightMargin: column.anchors.rightMargin == 0 ? staticMargin : 0
117+ }
118+ fillMode: Image.PreserveAspectFit
119+ height: units.gu(1.5)
120+
121+ source: "data/Tick@30.png"
122+ visible: itemDelegate.isCurrent
123+ }
124+
125+ onClicked: {
126+ selector.currentIndex = index;
127+ }
128+ }
129+ }
130+
131+ Rectangle {
132+ id: divider2
133+ anchors.left: parent.left
134+ anchors.right: parent.right
135+ anchors.top: selector.bottom
136+ height: units.dp(1)
137+ color: dividerColor
138+ }
139+ }
140+
141+ Component {
142+ id: forwardButton
143+ LocalComponents.StackButton {
144+ text: i18n.tr("Next")
145+ onClicked: {
146+ var method = indexToMethod(selector.currentIndex);
147+ root.passwordMethod = method;
148+
149+ if (method === UbuntuSecurityPrivacyPanel.Passphrase) { // any password
150+ if (selector.currentIndex == 0/*1*/)
151+ pageStack.load(Qt.resolvedUrl("password-set.qml")); // let the user choose a new password
152+ else
153+ pageStack.next(); // got the password already, go next page
154+ } else if (method === UbuntuSecurityPrivacyPanel.Passcode) { // passcode
155+ if (wideMode) {
156+ pageStack.load(Qt.resolvedUrl("passcode-desktop.qml"));
157+ } else {
158+ pageStack.load(Qt.resolvedUrl("passcode-set.qml"));
159+ }
160+ } else { //swipe
161+ pageStack.next();
162+ }
163+ }
164+ }
165+ }
166+}
qml/Wizard/Pages/75-report-check.qmlnew file+91
@@ -0,0 +1,91 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+import Ubuntu.Components 1.3
19+import Ubuntu.Web 0.2
20+import ".." as LocalComponents
21+
22+LocalComponents.Page {
23+ id: reportingPage
24+ objectName: "reportingPage"
25+
26+ title: webview.visible ? i18n.tr("Privacy Policy") : i18n.tr("Help Us Improve")
27+ forwardButtonSourceComponent: !webview.visible ? forwardButton : null
28+ customBack: true
29+ customTitle: webview.visible
30+
31+ skip: !diagnostics.reportCrashes // skip the page when the system is configured not to report crashes
32+
33+ onBackClicked: {
34+ if (webview.visible) {
35+ webview.visible = false;
36+ } else {
37+ pageStack.prev();
38+ }
39+ }
40+
41+ Column {
42+ id: column
43+ anchors {
44+ fill: content
45+ leftMargin: leftMargin
46+ rightMargin: rightMargin
47+ topMargin: customMargin
48+ }
49+
50+ LocalComponents.CheckableSetting {
51+ anchors {
52+ left: parent.left
53+ right: parent.right
54+ }
55+
56+ id: reportCheck
57+ objectName: "reportCheck"
58+ showDivider: false
59+ text: i18n.tr("Improve system performance by sending us crashes and error reports.") + "<br><br><a href='#'>" +
60+ i18n.tr("Privacy policy") + "</a>"
61+ checked: diagnostics.reportCrashes
62+ visible: !webview.visible
63+ onLinkActivated: {
64+ webview.url = "http://www.ubuntu.com/legal/terms-and-policies/privacy-policy";
65+ webview.visible = true;
66+ }
67+ }
68+
69+ WebView {
70+ id: webview
71+ objectName: "webview"
72+ anchors.left: parent.left
73+ anchors.right: parent.right
74+ anchors.leftMargin: -leftMargin
75+ anchors.rightMargin: -rightMargin
76+ height: parent.height
77+ visible: false
78+ }
79+ }
80+
81+ Component {
82+ id: forwardButton
83+ LocalComponents.StackButton {
84+ text: i18n.tr("Next")
85+ onClicked: {
86+ diagnostics.setReportCrashes(reportCheck.checked);
87+ pageStack.next();
88+ }
89+ }
90+ }
91+}
qml/Wizard/Pages/80-finished.qml+94−16
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013,2014 Canonical, Ltd.
2+ * Copyright (C) 2013-2015 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -21,37 +21,115 @@ import ".." as LocalComponents
2121LocalComponents.Page {
2222 objectName: "finishedPage"
2323
24− title: i18n.tr("All done")
25− forwardButtonSourceComponent: forwardButton
2624 hasBackButton: false
25+ customTitle: true
26+ lastPage: true
27+ buttonBarVisible: false
2728
28− Column {
29+ Component.onCompleted: {
30+ state = "reanchored";
31+ }
32+
33+ states: State {
34+ name: "reanchored"
35+ AnchorChanges { target: bgImage; anchors.top: parent.top; anchors.bottom: parent.bottom }
36+ AnchorChanges { target: column;
37+ anchors.verticalCenter: parent.verticalCenter;
38+ anchors.top: undefined
39+ }
40+ }
41+
42+ transitions: Transition {
43+ ParallelAnimation {
44+ AnchorAnimation {
45+ targets: [bgImage, column]
46+ duration: UbuntuAnimation.SlowDuration
47+ easing.type: Easing.OutCirc
48+ }
49+ NumberAnimation {
50+ targets: [bgImage,column]
51+ property: "opacity"
52+ from: 0
53+ to: 1
54+ duration: UbuntuAnimation.SlowDuration
55+ easing.type: Easing.OutCirc
56+ }
57+ }
58+ }
59+
60+ Image {
61+ id: bgImage
62+ source: wideMode ? "data/Desktop_splash_screen_bkg.png" : "data/Phone_splash_screen_bkg.png"
63+ scale: Image.PreserveAspectFit
64+ anchors.left: parent.left
65+ anchors.right: parent.right
66+ anchors.bottom: parent.top // outside to let it slide down
67+ visible: opacity > 0
68+ }
69+
70+ Item {
2971 id: column
30− anchors.fill: content
31− spacing: units.gu(1)
72+ anchors.leftMargin: leftMargin
73+ anchors.rightMargin: rightMargin
74+ anchors.left: parent.left
75+ anchors.right: parent.right
76+ anchors.top: parent.bottom // outside to let it slide in
77+ height: childrenRect.height
78+ visible: opacity > 0
3279
3380 Label {
81+ id: welcomeLabel
3482 anchors.left: parent.left
3583 anchors.right: parent.right
84+ horizontalAlignment: Text.AlignHCenter
3685 wrapMode: Text.Wrap
37− fontSize: "large"
38− font.bold: true
39− text: i18n.tr("Nice work!")
86+ fontSize: "x-large"
87+ font.weight: Font.Light
88+ lineHeight: 1.2
89+ text: i18n.tr("Welcome to Ubuntu")
90+ color: whiteColor
4091 }
4192
4293 Label {
94+ id: welcomeText
4395 anchors.left: parent.left
4496 anchors.right: parent.right
97+ anchors.top: welcomeLabel.bottom
98+ anchors.topMargin: units.gu(2)
99+ horizontalAlignment: Text.AlignHCenter
45100 wrapMode: Text.Wrap
46− text: i18n.tr("Your device is now ready to use.")
101+ fontSize: "large"
102+ font.weight: Font.Light
103+ lineHeight: 1.2
104+ text: i18n.tr("You are ready to use your device now")
105+ color: whiteColor
47106 }
48− }
49107
50− Component {
51− id: forwardButton
52− LocalComponents.StackButton {
53− text: i18n.tr("Finish")
54− onClicked: root.quitWizard()
108+ Rectangle {
109+ anchors {
110+ top: welcomeText.bottom
111+ horizontalCenter: parent.horizontalCenter
112+ topMargin: units.gu(4)
113+ }
114+ color: "transparent"
115+ border.width: units.dp(1)
116+ border.color: whiteColor
117+ radius: units.dp(4)
118+ width: buttonLabel.paintedWidth + units.gu(6)
119+ height: buttonLabel.paintedHeight + units.gu(1.8)
120+
121+ Label {
122+ id: buttonLabel
123+ color: whiteColor
124+ text: i18n.tr("Get Started")
125+ fontSize: "medium"
126+ anchors.centerIn: parent
127+ }
128+ AbstractButton {
129+ objectName: "finishButton"
130+ anchors.fill: parent
131+ onClicked: root.quitWizard()
132+ }
55133 }
56134 }
57135}
qml/Wizard/Pages/data/Desktop_header_bkg.pngnew file

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

qml/Wizard/Pages/here-terms.qml+72−65
@@ -15,8 +15,6 @@
1515 */
1616
1717import QtQuick 2.4
18−import AccountsService 0.1
19−import Qt.labs.folderlistmodel 2.1
2018import Ubuntu.Components 1.3
2119import Ubuntu.Web 0.2
2220import ".." as LocalComponents
@@ -26,86 +24,93 @@ LocalComponents.Page {
2624
2725 title: i18n.tr("Terms & Conditions")
2826 customBack: true
27+ customTitle: true
2928
30− FolderListModel {
31− id: termsModel
32− folder: AccountsService.hereLicensePath
33− nameFilters: ["*.html"]
34− showDirs: false
35− showOnlyReadable: true
36− onCountChanged: loadFileContent()
37− }
38−
39− function makeFileName(lang, country) {
40− return lang + "_" + country + ".html"
41− }
42−
43− function defaultCountryForLanguage(lang) {
44− if (lang === "da") return "DK"
45− if (lang === "en") return "US"
46− if (lang === "ko") return "KR"
47− if (lang === "zh") return "CN"
48− return lang.toUpperCase()
49− }
50−
51− function determineFileName() {
52− var codes = i18n.language.split(".")[0].split("_")
53− var defaultCountry = defaultCountryForLanguage(codes[0])
54− if (codes.count === 1)
55− codes = [codes[0], defaultCountry]
56− var perfectMatch = makeFileName(codes[0], codes[1])
57− var nearMatch = makeFileName(codes[0], defaultCountry)
58− var nearMatchExists = false
59−
60− for (var i = 0; i < termsModel.count; i++) {
61− var fileName = termsModel.get(i, "fileName")
62− if (fileName == perfectMatch) {
63− return perfectMatch
64− } else if (fileName == nearMatch) {
65− nearMatchExists = true
66− }
67− }
68−
69− if (nearMatchExists) {
70− return nearMatch
29+ onBackClicked: {
30+ if (webview.visible) {
31+ showBrowser(false);
7132 } else {
72− return makeFileName("en", "US")
33+ pageStack.prev();
7334 }
7435 }
7536
76− function loadFileContent() {
77− var xhr = new XMLHttpRequest
78− xhr.open("GET", AccountsService.hereLicensePath + "/" + determineFileName())
79− xhr.onreadystatechange = function() {
80− if (xhr.readyState == XMLHttpRequest.DONE) {
81− termsLabel.text = xhr.responseText
82− }
83− }
84− xhr.send()
85− }
86−
87− onBackClicked: {
88− if (webview.visible) {
89− termsLabel.visible = true
37+ function showBrowser(show) {
38+ if (show) {
39+ label1.visible = false;
40+ label2.visible = false;
41+ label3.visible = false;
42+ label4.visible = false;
43+ webview.visible = true;
9044 } else {
91− pageStack.prev()
45+ webview.visible = false;
46+ label1.visible = true;
47+ label2.visible = true;
48+ label3.visible = true;
49+ label4.visible = true;
9250 }
9351 }
9452
9553 Column {
9654 id: column
9755 anchors.fill: content
56+ anchors.leftMargin: staticMargin
57+ anchors.rightMargin: wideMode ? parent.rightMargin : staticMargin
58+ spacing: units.gu(3)
59+
60+ Label {
61+ id: label1
62+ anchors.left: parent.left
63+ anchors.right: parent.right
64+ wrapMode: Text.Wrap
65+ color: textColor
66+ fontSize: "small"
67+ font.weight: Font.Light
68+ lineHeight: 1.2
69+ text: i18n.tr("Your device uses positioning technologies provided by HERE.")
70+ }
71+
72+ Label {
73+ id: label2
74+ anchors.left: parent.left
75+ anchors.right: parent.right
76+ wrapMode: Text.Wrap
77+ color: textColor
78+ fontSize: "small"
79+ font.weight: Font.Light
80+ lineHeight: 1.2
81+ text: i18n.tr("To provide you with positioning services and to improve their quality, HERE collects " +
82+ "information about nearby cell towers and Wi-Fi hotspots around your current location " +
83+ "whenever your position is being found.")
84+ }
85+
86+ Label {
87+ id: label3
88+ anchors.left: parent.left
89+ anchors.right: parent.right
90+ wrapMode: Text.Wrap
91+ color: textColor
92+ fontSize: "small"
93+ font.weight: Font.Light
94+ lineHeight: 1.2
95+ text: i18n.tr("The information collected is used to analyze the service and to " +
96+ "improve the use of service, but not to identify you personally.")
97+ }
9898
9999 Label {
100− id: termsLabel
101− objectName: "termsLabel"
100+ id: label4
102101 anchors.left: parent.left
103102 anchors.right: parent.right
104103 wrapMode: Text.Wrap
105− linkColor: theme.palette.normal.foregroundText
104+ color: textColor
105+ fontSize: "small"
106+ font.weight: Font.Light
107+ lineHeight: 1.2
108+ linkColor: UbuntuColors.orange
109+ text: i18n.tr("By continuing, you agree to the HERE platform <a href=\"http://here.com/terms/service-terms\">Service Terms</a> " +
110+ "and <a href=\"http://here.com/privacy/privacy-policy\">Privacy Policy</a>.");
106111 onLinkActivated: {
107− webview.url = link
108− termsLabel.visible = false
112+ showBrowser(true);
113+ webview.url = link;
109114 }
110115 }
111116
@@ -114,8 +119,10 @@ LocalComponents.Page {
114119 objectName: "webview"
115120 anchors.left: parent.left
116121 anchors.right: parent.right
122+ anchors.leftMargin: -leftMargin
123+ anchors.rightMargin: -rightMargin
117124 height: parent.height
118− visible: !termsLabel.visible
125+ visible: false
119126 }
120127 }
121128}
qml/Wizard/Pages/passwd-confirm.qml → qml/Wizard/Pages/passcode-confirm.qmlrenamed+12−29
@@ -26,11 +26,10 @@ import "../../Components" as UnityComponents
2626 */
2727
2828LocalComponents.Page {
29− id: passwdConfirmPage
30− objectName: "passwdConfirmPage"
31− forwardButtonSourceComponent: forwardButton
32−
33− skip: root.passwordMethod === UbuntuSecurityPrivacyPanel.Swipe
29+ id: passcodeConfirmPage
30+ objectName: "passcodeConfirmPage"
31+ customTitle: true
32+ backButtonText: i18n.tr("Cancel")
3433
3534 // If we are entering this page, clear any saved password and get focus
3635 onEnabledChanged: if (enabled) lockscreen.clear(false)
@@ -38,32 +37,26 @@ LocalComponents.Page {
3837 UnityComponents.Lockscreen {
3938 id: lockscreen
4039 anchors {
41− fill: parent
42− topMargin: topMargin
43− leftMargin: leftMargin
44− rightMargin: rightMargin
45− bottomMargin: buttonMargin
40+ fill: content
4641 }
4742
48− infoText: root.passwordMethod === UbuntuSecurityPrivacyPanel.Passphrase ?
49− i18n.tr("Confirm passphrase") :
50− i18n.tr("Confirm passcode")
43+ infoText: i18n.tr("Confirm passcode")
44+
45+ errorText: i18n.tr("Incorrect passcode.") + "\n" + i18n.ctr("Enter the passcode again", "Please re-enter.")
5146
52− errorText: root.passwordMethod === UbuntuSecurityPrivacyPanel.Passphrase ?
53− i18n.tr("Sorry, incorrect passphrase.") + "\n" + i18n.tr("Please try again.") :
54− i18n.tr("Sorry, incorrect passcode.") + "\n" + i18n.tr("Please try again.")
47+ foregroundColor: textColor
5548
5649 showEmergencyCallButton: false
5750 showCancelButton: false
58− alphaNumeric: root.passwordMethod === UbuntuSecurityPrivacyPanel.Passphrase
51+ alphaNumeric: false
5952 minPinLength: 4
6053 maxPinLength: 4
6154
6255 onEntered: {
6356 if (passphrase === root.password) {
64− confirmTimer.start()
57+ confirmTimer.start();
6558 } else {
66− clear(true)
59+ clear(true);
6760 }
6861 }
6962
@@ -73,14 +66,4 @@ LocalComponents.Page {
7366 onTriggered: pageStack.next()
7467 }
7568 }
76−
77− Component {
78− id: forwardButton
79− LocalComponents.StackButton {
80− visible: root.passwordMethod === UbuntuSecurityPrivacyPanel.Passphrase
81− enabled: root.password === lockscreen.passphrase
82− text: i18n.tr("Continue")
83− onClicked: pageStack.next()
84− }
85− }
8669}
qml/Wizard/Pages/passcode-desktop.qmlnew file+132
@@ -0,0 +1,132 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+import QtQuick.Layouts 1.1
19+import Ubuntu.Components 1.3
20+import ".." as LocalComponents
21+
22+/**
23+ * See the main passwd-type page for an explanation of why we don't actually
24+ * directly set the password here.
25+ */
26+
27+LocalComponents.Page {
28+ id: passwdSetPage
29+ objectName: "passcodeDesktopPage"
30+ title: i18n.tr("Lock Screen Passcode")
31+ forwardButtonSourceComponent: forwardButton
32+
33+ readonly property alias password: passwordField.text
34+ readonly property alias password2: password2Field.text
35+
36+ GridLayout {
37+ id: column
38+ columns: 1
39+ rows: 3
40+ anchors.fill: content
41+ anchors.leftMargin: leftMargin
42+ anchors.rightMargin: rightMargin
43+ anchors.topMargin: customMargin
44+ rowSpacing: units.gu(3)
45+
46+ Label {
47+ id: infoLabel
48+ objectName: "infoLabel"
49+ anchors {
50+ left: parent.left
51+ right: parent.right
52+ }
53+ wrapMode: Text.Wrap
54+ font.weight: Font.Light
55+ color: textColor
56+ text: i18n.tr("Enter 4 numbers to setup your passcode")
57+ }
58+
59+ GridLayout {
60+ columns: 2
61+ columnSpacing: units.gu(2)
62+ rowSpacing: units.gu(2)
63+
64+ Label {
65+ text: i18n.tr("Choose passcode")
66+ color: textColor
67+ }
68+ LocalComponents.WizardTextField {
69+ Layout.fillWidth: true
70+ id: passwordField
71+ objectName: "passwordField"
72+ echoMode: TextInput.Password
73+ inputMethodHints: Qt.ImhDigitsOnly
74+ validator: RegExpValidator { regExp: /^\d{4}$/ }
75+ maximumLength: 4
76+ onAccepted: password2Field.forceActiveFocus()
77+ }
78+
79+ Label {
80+ text: i18n.tr("Confirm passcode")
81+ color: textColor
82+ }
83+ LocalComponents.WizardTextField {
84+ Layout.fillWidth: true
85+ id: password2Field
86+ objectName: "password2Field"
87+ echoMode: TextInput.Password
88+ inputMethodHints: Qt.ImhDigitsOnly
89+ validator: RegExpValidator { regExp: /^\d{4}$/ }
90+ maximumLength: 4
91+ }
92+
93+ Label {
94+ Layout.row: 2
95+ Layout.column: 1
96+ id: errorLabel
97+ property bool hasError: password && password != password2
98+ wrapMode: Text.Wrap
99+ color: hasError ? errorColor : UbuntuColors.ash
100+ visible: password && password2
101+ fontSize: "small"
102+ text: {
103+ if (password) {
104+ if (password2.length < password2Field.maximumLength)
105+ return i18n.tr("Passcode too short");
106+ else if (password == password2)
107+ return i18n.tr("Passcodes match");
108+ else if (password2)
109+ return i18n.tr("Passcodes do not match");
110+ }
111+ return "";
112+ }
113+ }
114+ }
115+
116+ Item { // spacer
117+ Layout.fillHeight: true
118+ }
119+ }
120+
121+ Component {
122+ id: forwardButton
123+ LocalComponents.StackButton {
124+ text: i18n.tr("Next")
125+ enabled: password != "" && password == password2
126+ onClicked: {
127+ root.password = password;
128+ pageStack.next();
129+ }
130+ }
131+ }
132+}
qml/Wizard/Pages/passwd-set.qml → qml/Wizard/Pages/passcode-set.qmlrenamed+12−28
@@ -26,11 +26,10 @@ import "../../Components" as UnityComponents
2626 */
2727
2828LocalComponents.Page {
29− id: passwdSetPage
30− objectName: "passwdSetPage"
31− forwardButtonSourceComponent: forwardButton
32−
33− skip: root.passwordMethod === UbuntuSecurityPrivacyPanel.Swipe
29+ id: passcodeSetPage
30+ objectName: "passcodeSetPage"
31+ customTitle: true
32+ backButtonText: i18n.tr("Cancel")
3433
3534 // If we are entering this page, clear any saved password and get focus
3635 onEnabledChanged: if (enabled) lockscreen.clear(false)
@@ -43,52 +42,37 @@ LocalComponents.Page {
4342 Timer {
4443 id: confirmTimer
4544 interval: UbuntuAnimation.SnapDuration
46− onTriggered: pageStack.load(Qt.resolvedUrl("passwd-confirm.qml"));
45+ onTriggered: pageStack.load(Qt.resolvedUrl("passcode-confirm.qml"));
4746 }
4847
4948 UnityComponents.Lockscreen {
5049 id: lockscreen
5150 anchors {
52− fill: parent
53− topMargin: topMargin
54− leftMargin: leftMargin
55− rightMargin: rightMargin
56− bottomMargin: buttonMargin
51+ fill: content
5752 }
5853
59− infoText: root.passwordMethod === UbuntuSecurityPrivacyPanel.Passphrase ?
60− i18n.tr("Enter passphrase") :
61− i18n.tr("Choose your passcode")
54+ infoText: i18n.tr("Choose passcode")
55+ foregroundColor: textColor
6256
6357 // Note that the number four comes from PAM settings,
6458 // which we don't have a good way to interrogate. We
6559 // only do this matching instead of PAM because we want
6660 // to set the password via PAM in a different place
6761 // than this page. See comments at top of passwd-type file.
68− errorText: i18n.tr("Passphrase must be 4 characters long")
62+ errorText: i18n.tr("Passcode must be 4 characters long")
6963
7064 showEmergencyCallButton: false
7165 showCancelButton: false
72− alphaNumeric: root.passwordMethod === UbuntuSecurityPrivacyPanel.Passphrase
66+ alphaNumeric: false
7367 minPinLength: 4
7468 maxPinLength: 4
7569
7670 onEntered: {
7771 if (passphrase.length >= 4) {
78− passwdSetPage.confirm();
72+ passcodeSetPage.confirm();
7973 } else {
80− lockscreen.clear(true)
74+ lockscreen.clear(true);
8175 }
8276 }
8377 }
84−
85− Component {
86− id: forwardButton
87− LocalComponents.StackButton {
88− visible: root.passwordMethod === UbuntuSecurityPrivacyPanel.Passphrase
89− enabled: lockscreen.passphrase.length >= 4
90− text: i18n.tr("Continue")
91− onClicked: passwdSetPage.confirm()
92− }
93− }
9478}
qml/Wizard/Pages/password-set.qmlnew file+149
@@ -0,0 +1,149 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+import QtQuick.Layouts 1.1
19+import Ubuntu.Components 1.3
20+import ".." as LocalComponents
21+
22+/**
23+ * See the main passwd-type page for an explanation of why we don't actually
24+ * directly set the password here.
25+ */
26+
27+LocalComponents.Page {
28+ id: passwdSetPage
29+ objectName: "passwdSetPage"
30+ title: i18n.tr("Lock Screen Password")
31+ forwardButtonSourceComponent: forwardButton
32+
33+ readonly property alias password: passwordField.text
34+ readonly property alias password2: password2Field.text
35+ readonly property bool passwordsMatching: password == password2 && password.trim().length > 7
36+
37+ Flickable {
38+ id: column
39+ clip: true
40+ flickableDirection: Flickable.VerticalFlick
41+ anchors.fill: content
42+ anchors.leftMargin: parent.leftMargin
43+ anchors.rightMargin: parent.rightMargin
44+ anchors.topMargin: customMargin
45+
46+ bottomMargin: Qt.inputMethod.keyboardRectangle.height - height
47+
48+ Behavior on contentY { UbuntuNumberAnimation {} }
49+
50+ // info label
51+ Label {
52+ id: infoLabel
53+ objectName: "infoLabel"
54+ anchors {
55+ left: parent.left
56+ right: parent.right
57+ }
58+ wrapMode: Text.Wrap
59+ font.weight: Font.Light
60+ color: textColor
61+ text: i18n.tr("Enter at least 8 characters")
62+ }
63+
64+ // password
65+ Label {
66+ id: pass1Label
67+ anchors {
68+ left: parent.left
69+ right: parent.right
70+ top: infoLabel.bottom
71+ topMargin: units.gu(3)
72+ }
73+ text: i18n.tr("Choose password")
74+ color: textColor
75+ }
76+ LocalComponents.WizardTextField {
77+ id: passwordField
78+ anchors {
79+ left: parent.left
80+ right: parent.right
81+ top: pass1Label.bottom
82+ topMargin: units.gu(1)
83+ }
84+ objectName: "passwordField"
85+ echoMode: TextInput.Password
86+ onAccepted: password2Field.forceActiveFocus()
87+ onActiveFocusChanged: {
88+ if (activeFocus) {
89+ column.contentY = pass1Label.y
90+ }
91+ }
92+ }
93+
94+ // password 2
95+ Label {
96+ id: pass2Label
97+ anchors {
98+ left: parent.left
99+ right: parent.right
100+ top: passwordField.bottom
101+ topMargin: units.gu(3)
102+ }
103+ text: i18n.tr("Confirm password")
104+ color: textColor
105+ }
106+ LocalComponents.WizardTextField {
107+ anchors {
108+ left: parent.left
109+ right: parent.right
110+ top: pass2Label.bottom
111+ topMargin: units.gu(1)
112+ }
113+ id: password2Field
114+ objectName: "password2Field"
115+ echoMode: TextInput.Password
116+ onActiveFocusChanged: {
117+ if (activeFocus) {
118+ column.contentY = pass2Label.y
119+ }
120+ }
121+ }
122+
123+ // password meter
124+ LocalComponents.PasswordMeter {
125+ id: passMeter
126+ anchors {
127+ left: parent.left
128+ right: parent.right
129+ top: password2Field.bottom
130+ topMargin: units.gu(1)
131+ }
132+
133+ password: passwordField.text
134+ matching: passwordsMatching ? true : (password2.trim().length > 0 ? false : undefined)
135+ }
136+ }
137+
138+ Component {
139+ id: forwardButton
140+ LocalComponents.StackButton {
141+ text: i18n.tr("Next")
142+ enabled: passwordsMatching
143+ onClicked: {
144+ root.password = password;
145+ pageStack.next();
146+ }
147+ }
148+ }
149+}
qml/Wizard/Pages/sim.qmlnew file+108
@@ -0,0 +1,108 @@
1+/*
2+ * Copyright (C) 2013-2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+import MeeGo.QOfono 0.2
19+import Ubuntu.Components 1.3
20+import Ubuntu.Components.Popups 1.3
21+import Unity.Session 0.1
22+import ".." as LocalComponents
23+
24+LocalComponents.Page {
25+ objectName: "simPage"
26+
27+ title: i18n.tr("No SIM card installed")
28+ forwardButtonSourceComponent: forwardButton
29+ customTitle: true
30+ hasBackButton: false
31+
32+ property bool hadSIM: root.simManager0.present || root.simManager1.present
33+
34+ Timer {
35+ id: timer
36+ interval: 300
37+ running: true
38+ onTriggered: {
39+ hadSIM = root.simManager0.present || root.simManager1.present;
40+ }
41+ }
42+
43+ Connections {
44+ target: root.modemManager
45+ onGotSimCardChanged: {
46+ if (!hadSIM && root.modemManager.gotSimCard) { // show the restart dialog in case a SIM gets inserted
47+ restartDialog.visible = true;
48+ }
49+ }
50+ }
51+
52+ Dialog {
53+ id: restartDialog
54+ title: i18n.tr("SIM card added")
55+ text: i18n.tr("You must restart the device to access the mobile network.")
56+
57+ Button {
58+ id: restartButton
59+ text: i18n.tr("Restart")
60+ onClicked: {
61+ DBusUnitySessionService.reboot();
62+ }
63+ }
64+ }
65+
66+ Column {
67+ anchors {
68+ fill: content
69+ leftMargin: staticMargin
70+ rightMargin: staticMargin
71+ }
72+ spacing: units.gu(3)
73+
74+ Label {
75+ anchors.left: parent.left
76+ anchors.right: parent.right
77+ wrapMode: Text.Wrap
78+ text: i18n.tr("You won’t be able to make calls or use text messaging without a SIM.")
79+ fontSize: "small"
80+ font.weight: Font.Light
81+ lineHeight: 1.2
82+ color: restartDialog.visible ? theme.palette.normal.backgroundText : textColor
83+ }
84+
85+ Label {
86+ anchors.left: parent.left
87+ anchors.right: parent.right
88+ wrapMode: Text.Wrap
89+ textFormat: Text.RichText
90+ text: i18n.tr("To proceed with no SIM tap Skip.")
91+ fontSize: "small"
92+ font.weight: Font.Light
93+ lineHeight: 1.2
94+ color: restartDialog.visible ? theme.palette.normal.backgroundText : textColor
95+ }
96+ }
97+
98+ Component {
99+ id: forwardButton
100+ LocalComponents.StackButton {
101+ text: i18n.tr("Skip")
102+ onClicked: {
103+ seenSIMPage = true;
104+ pageStack.next();
105+ }
106+ }
107+ }
108+}
qml/Wizard/PasswordMeter.qmlnew file+106
@@ -0,0 +1,106 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+import Ubuntu.Components 1.3
19+
20+Item {
21+ property string password: ""
22+ readonly property int passwordScore: scorePassword(password)
23+ property var matching
24+
25+ function scorePassword(pass) {
26+ var score = 0;
27+ if (!pass)
28+ return score;
29+
30+ // award every unique letter until 5 repetitions
31+ var letters = Object();
32+ for (var i=0; i<pass.length; i++) {
33+ letters[pass[i]] = (letters[pass[i]] || 0) + 1;
34+ score += 5.0 / letters[pass[i]];
35+ }
36+
37+ // bonus points for mixing it up
38+ var variations = {
39+ digits: /\d/.test(pass),
40+ lower: /[a-z]/.test(pass),
41+ upper: /[A-Z]/.test(pass),
42+ nonWords: /\W/.test(pass),
43+ }
44+
45+ var variationCount = 0;
46+ for (var check in variations) {
47+ variationCount += (variations[check] === true) ? 1 : 0;
48+ }
49+ score += (variationCount - 1) * 10;
50+
51+ return parseInt(score);
52+ }
53+
54+ Rectangle {
55+ id: passwordStrengthMeter
56+ anchors {
57+ left: parent.left
58+ right: parent.right
59+ }
60+ width: parent.width
61+ height: units.gu(0.5)
62+ color: {
63+ if (passwordScore > 80)
64+ return okColor;
65+ else if (passwordScore > 60)
66+ return "#f9c00f";
67+ else if (passwordScore >= 30)
68+ return errorColor;
69+
70+ return errorColor;
71+ }
72+ visible: password.length > 0
73+ }
74+
75+ Label {
76+ id: passwordStrengthInfo
77+ anchors {
78+ left: parent.left
79+ right: parent.right
80+ top: passwordStrengthMeter.bottom
81+ topMargin: units.gu(.5)
82+ }
83+ wrapMode: Text.Wrap
84+ text: {
85+ if (matching !== undefined) {
86+ if (matching)
87+ return i18n.tr("Passwords match");
88+ else if (!matching)
89+ return i18n.tr("Passwords do not match");
90+ }
91+
92+ if (passwordScore > 80)
93+ return i18n.tr("Strong password");
94+ else if (passwordScore > 60)
95+ return i18n.tr("Fair password");
96+ else if (passwordScore >= 30)
97+ return i18n.tr("Weak password");
98+
99+ return i18n.tr("Very weak password");
100+ }
101+ color: UbuntuColors.ash
102+ fontSize: "small"
103+ font.weight: Font.Light
104+ visible: password.length > 0
105+ }
106+}
qml/Wizard/StackButton.qml+6−11
@@ -25,23 +25,18 @@ AbstractButton {
2525 property bool backArrow: false
2626
2727 width: label.width
28− height: label.height + units.gu(4)
28+ height: units.gu(5)
2929
3030 Label {
3131 id: label
3232 anchors.verticalCenter: parent.verticalCenter
3333 anchors.left: parent.left
3434 anchors.right: parent.right
35− color: enabled ? theme.palette.normal.backgroundText : Qt.darker(theme.palette.normal.backgroundText, 1.5)
36− text: {
37− if (backArrow) {
38− // Translators: This is the arrow for "Back" buttons
39− return i18n.tr("〈 %1".arg(stackButton.text))
40− } else {
41− // Translators: This is the arrow for "Forward" buttons
42− return i18n.tr("%1 〉".arg(stackButton.text))
43− }
44− }
35+ color: "#333333"
36+ opacity: enabled ? 1.0 : 0.5
37+ fontSize: "medium"
38+ font.weight: backArrow ? Font.Light : Font.DemiBold
39+ text: stackButton.text
4540 horizontalAlignment: backArrow ? Text.AlignLeft : Text.AlignRight
4641 }
4742}
qml/Wizard/Wizard.qml−9
@@ -22,9 +22,6 @@ import "../Components"
2222Showable {
2323 id: root
2424
25− // The background wallpaper to use
26− property string background
27−
2825 // This is a bool instead of an alias because Loader classes like to emit
2926 // changed signals for 'active' during startup even if they aren't actually
3027 // changing values. Having it cached as a proper Qml bool property prevents
@@ -46,12 +43,6 @@ Showable {
4643 active: System.wizardEnabled
4744 source: "Pages.qml"
4845
49− Binding {
50− target: loader.item
51− property: "background"
52− value: root.background
53− }
54−
5546 Connections {
5647 target: loader.item
5748 onQuit: root.hide()
qml/Wizard/WizardTextField.qmlnew file+25
@@ -0,0 +1,25 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+import Ubuntu.Components 1.3
19+import Ubuntu.Components.Themes.Ambiance 1.3
20+
21+TextField {
22+ id: styledItem
23+ style: TextFieldStyle {
24+ }
25+}
src/CMakeLists.txt+1−1
@@ -33,7 +33,7 @@ endif()
3333
3434pkg_check_modules(ANDROID_PROPERTIES REQUIRED libandroid-properties)
3535add_executable(${SHELL_APP} ${SOURCE_FILES})
36−qt5_use_modules(${SHELL_APP} Gui Qml Quick Test)
36+qt5_use_modules(${SHELL_APP} DBus Gui Qml Quick Test)
3737
3838if (NOT "${ANDROID_PROPERTIES_INCLUDE_DIRS}" STREQUAL "")
3939 set_target_properties(${SHELL_APP} PROPERTIES INCLUDE_DIRECTORIES ${ANDROID_PROPERTIES_INCLUDE_DIRS})
src/CachingNetworkManagerFactory.cpp+2−2
@@ -24,12 +24,12 @@
2424CachingNetworkAccessManager::CachingNetworkAccessManager(QObject *parent)
2525 : QNetworkAccessManager(parent)
2626{
27− m_networkingStatus = new ubuntu::connectivity::NetworkingStatus(this);
27+ m_networkingStatus = new connectivityqt::Connectivity(QDBusConnection::sessionBus(), this);
2828}
2929
3030QNetworkReply* CachingNetworkAccessManager::createRequest(Operation op, const QNetworkRequest &request, QIODevice *outgoingData)
3131{
32− if (m_networkingStatus->status() != ubuntu::connectivity::NetworkingStatus::Status::Online) {
32+ if (m_networkingStatus->status() != connectivityqt::Connectivity::Status::Online) {
3333 qDebug() << "Not connected to the internet. Request for" << request.url().toString() << "will be served only from the cache.";
3434 QNetworkRequest req(request);
3535 req.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysCache);
src/CachingNetworkManagerFactory.h+3−2
@@ -21,10 +21,11 @@
2121#include <QQmlNetworkAccessManagerFactory>
2222#include <QNetworkAccessManager>
2323
24−#include <ubuntu/connectivity/networking-status.h>
24+#include <connectivityqt/connectivity.h>
2525
2626class CachingNetworkAccessManager : public QNetworkAccessManager
2727{
28+Q_OBJECT
2829public:
2930 CachingNetworkAccessManager(QObject *parent = 0);
3031
@@ -32,7 +33,7 @@ protected:
3233 QNetworkReply* createRequest(Operation op, const QNetworkRequest &req, QIODevice *outgoingData = 0) override;
3334
3435private:
35− ubuntu::connectivity::NetworkingStatus* m_networkingStatus;
36+ connectivityqt::Connectivity* m_networkingStatus;
3637};
3738
3839class CachingNetworkManagerFactory : public QQmlNetworkAccessManagerFactory
src/Dash/CMakeLists.txt+1−1
@@ -9,7 +9,7 @@ endif()
99
1010add_executable(unity8-dash ${DASH_SRCS})
1111
12−qt5_use_modules(unity8-dash Gui Qml Quick Test)
12+qt5_use_modules(unity8-dash DBus Gui Qml Quick Test)
1313
1414# For it to find libUbuntuGestures.so, needed by Ubuntu.Gestures QML module.
1515set_target_properties(unity8-dash PROPERTIES INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${SHELL_PRIVATE_LIBDIR}")
src/MouseTouchAdaptor.cpp+53−9
@@ -71,6 +71,8 @@ using QTest::QTouchEventSequence;
7171namespace {
7272MouseTouchAdaptor *g_instance = nullptr;
7373
74+const Qt::KeyboardModifiers TRI_PRESS_MODIFIER = Qt::ShiftModifier|Qt::ControlModifier|Qt::AltModifier;
75+
7476Qt::MouseButton translateMouseButton(xcb_button_t detail)
7577{
7678 switch (detail) {
@@ -81,10 +83,25 @@ Qt::MouseButton translateMouseButton(xcb_button_t detail)
8183 default: return Qt::NoButton;
8284 }
8385}
86+
87+Qt::KeyboardModifiers translateMofidier(uint32_t mod)
88+{
89+ Qt::KeyboardModifiers qtMod = Qt::NoModifier;
90+
91+ if (mod & 0x01) qtMod |= Qt::ShiftModifier;
92+ if (mod & 0x04) qtMod |= Qt::ControlModifier;
93+ if (mod & 0x08) qtMod |= Qt::AltModifier;
94+ if (mod & 0x80) qtMod |= Qt::MetaModifier;
95+
96+ return qtMod;
97+}
8498} // end of anonymous namespace
8599
86100MouseTouchAdaptor::MouseTouchAdaptor()
87− : QObject(nullptr), m_leftButtonIsPressed(false), m_enabled(true)
101+ : QObject(nullptr)
102+ , m_leftButtonIsPressed(false)
103+ , m_triPressModifier(false)
104+ , m_enabled(true)
88105{
89106 QCoreApplication::instance()->installNativeEventFilter(this);
90107
@@ -192,17 +209,20 @@ bool MouseTouchAdaptor::xi2HandleEvent(xcb_ge_event_t *event)
192209 return handleButtonPress(
193210 static_cast<WId>(xiDeviceEvent->event),
194211 xiDeviceEvent->detail,
212+ xiDeviceEvent->mods.base_mods,
195213 fixed1616ToReal(xiDeviceEvent->event_x),
196214 fixed1616ToReal(xiDeviceEvent->event_y));
197215 case XI_ButtonRelease:
198216 return handleButtonRelease(
199217 static_cast<WId>(xiDeviceEvent->event),
200218 xiDeviceEvent->detail,
219+ xiDeviceEvent->mods.base_mods,
201220 fixed1616ToReal(xiDeviceEvent->event_x),
202221 fixed1616ToReal(xiDeviceEvent->event_y));
203222 case XI_Motion:
204223 return handleMotionNotify(
205224 static_cast<WId>(xiDeviceEvent->event),
225+ xiDeviceEvent->mods.base_mods,
206226 fixed1616ToReal(xiDeviceEvent->event_x),
207227 fixed1616ToReal(xiDeviceEvent->event_y));
208228 return true;
@@ -232,17 +252,18 @@ bool MouseTouchAdaptor::nativeEventFilter(const QByteArray & eventType,
232252 switch (xcbEvent->response_type & ~0x80) {
233253 case XCB_BUTTON_PRESS: {
234254 auto pressEvent = reinterpret_cast<xcb_button_press_event_t *>(xcbEvent);
235− return handleButtonPress(static_cast<WId>(pressEvent->event), pressEvent->detail,
255+ return handleButtonPress(static_cast<WId>(pressEvent->event), pressEvent->detail, 0,
236256 pressEvent->event_x, pressEvent->event_y);
237257 }
238258 case XCB_BUTTON_RELEASE: {
239259 auto releaseEvent = reinterpret_cast<xcb_button_release_event_t *>(xcbEvent);
240− return handleButtonRelease(static_cast<WId>(releaseEvent->event), releaseEvent->detail,
260+ return handleButtonRelease(static_cast<WId>(releaseEvent->event), releaseEvent->detail, 0,
241261 releaseEvent->event_x, releaseEvent->event_y);
242262 }
243263 case XCB_MOTION_NOTIFY: {
244264 auto motionEvent = reinterpret_cast<xcb_motion_notify_event_t *>(xcbEvent);
245− return handleMotionNotify(static_cast<WId>(motionEvent->event), motionEvent->event_x, motionEvent->event_y);
265+ return handleMotionNotify(static_cast<WId>(motionEvent->event), 0,
266+ motionEvent->event_x, motionEvent->event_y);
246267 }
247268 case XCB_GE_GENERIC:
248269 if (m_xi2Enabled) {
@@ -255,9 +276,10 @@ bool MouseTouchAdaptor::nativeEventFilter(const QByteArray & eventType,
255276 };
256277}
257278
258−bool MouseTouchAdaptor::handleButtonPress(WId windowId, uint32_t detail, int x, int y)
279+bool MouseTouchAdaptor::handleButtonPress(WId windowId, uint32_t detail, uint32_t modifiers, int x, int y)
259280{
260281 Qt::MouseButton button = translateMouseButton(detail);
282+ Qt::KeyboardModifiers qtMod = translateMofidier(modifiers);
261283
262284 // Just eat the event if it wasn't a left mouse press
263285 if (button != Qt::LeftButton)
@@ -270,19 +292,24 @@ bool MouseTouchAdaptor::handleButtonPress(WId windowId, uint32_t detail, int x,
270292 QTouchEventSequence touchEvent = QTest::touchEvent(targetWindow, m_touchDevice,
271293 false /* autoCommit */);
272294 touchEvent.press(0 /* touchId */, windowPos);
295+ if (qtMod == TRI_PRESS_MODIFIER) {
296+ touchEvent.press(1, windowPos);
297+ touchEvent.press(2, windowPos);
298+ m_triPressModifier = true;
299+ }
273300 touchEvent.commit(false /* processEvents */);
274301
275302 m_leftButtonIsPressed = true;
276303 return true;
277304}
278305
279−bool MouseTouchAdaptor::handleButtonRelease(WId windowId, uint32_t detail, int x, int y)
306+bool MouseTouchAdaptor::handleButtonRelease(WId windowId, uint32_t detail, uint32_t, int x, int y)
280307{
281308 Qt::MouseButton button = translateMouseButton(detail);
282309
283− // Just eat the event if it wasn't a left mouse release
310+ // Don't eat the event if it wasn't a left mouse press
284311 if (button != Qt::LeftButton)
285− return true;
312+ return false;
286313
287314 QWindow *targetWindow = findQWindowWithXWindowID(windowId);
288315
@@ -291,17 +318,23 @@ bool MouseTouchAdaptor::handleButtonRelease(WId windowId, uint32_t detail, int x
291318 QTouchEventSequence touchEvent = QTest::touchEvent(targetWindow, m_touchDevice,
292319 false /* autoCommit */);
293320 touchEvent.release(0 /* touchId */, windowPos);
321+ if (m_triPressModifier) {
322+ touchEvent.release(1, windowPos);
323+ touchEvent.release(2, windowPos);
324+ }
294325 touchEvent.commit(false /* processEvents */);
295326
296327 m_leftButtonIsPressed = false;
328+ m_triPressModifier = false;
297329 return true;
298330}
299331
300−bool MouseTouchAdaptor::handleMotionNotify(WId windowId, int x, int y)
332+bool MouseTouchAdaptor::handleMotionNotify(WId windowId, uint32_t modifiers, int x, int y)
301333{
302334 if (!m_leftButtonIsPressed) {
303335 return true;
304336 }
337+ Qt::KeyboardModifiers qtMod = translateMofidier(modifiers);
305338
306339 QWindow *targetWindow = findQWindowWithXWindowID(windowId);
307340
@@ -310,6 +343,17 @@ bool MouseTouchAdaptor::handleMotionNotify(WId windowId, int x, int y)
310343 QTouchEventSequence touchEvent = QTest::touchEvent(targetWindow, m_touchDevice,
311344 false /* autoCommit */);
312345 touchEvent.move(0 /* touchId */, windowPos);
346+ if (m_triPressModifier) {
347+ if (qtMod == TRI_PRESS_MODIFIER) {
348+ touchEvent.move(1, windowPos);
349+ touchEvent.move(2, windowPos);
350+ } else {
351+ // released modifiers
352+ touchEvent.release(1, windowPos);
353+ touchEvent.release(2, windowPos);
354+ m_triPressModifier = false;
355+ }
356+ }
313357 touchEvent.commit(false /* processEvents */);
314358
315359 return true;
src/MouseTouchAdaptor.h+5−3
@@ -51,13 +51,15 @@ private:
5151 void fetchXInput2Info();
5252 bool xi2HandleEvent(xcb_ge_event_t *event);
5353
54− bool handleButtonPress(WId windowId, uint32_t detail, int x, int y);
55− bool handleButtonRelease(WId windowId, uint32_t detail, int x, int y);
56− bool handleMotionNotify(WId windowId, int x, int y);
54+ bool handleButtonPress(WId windowId, uint32_t detail, uint32_t modifiers, int x, int y);
55+ bool handleButtonRelease(WId windowId, uint32_t detail, uint32_t modifiers, int x, int y);
56+ bool handleMotionNotify(WId windowId, uint32_t modifiers, int x, int y);
5757 QWindow *findQWindowWithXWindowID(WId windowId);
5858
5959 QTouchDevice *m_touchDevice;
6060 bool m_leftButtonIsPressed;
61+ bool m_triPressModifier;
62+
6163
6264 bool m_enabled;
6365
tests/autopilot/unity8/dash.py+1−2
@@ -211,8 +211,7 @@ class GenericScopeView(ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase):
211211 self.get_root_instance().select_single(
212212 objectName='processingIndicator').visible.wait_for(False)
213213 return preview_list.select_single(
214− Preview, objectName='preview{}'.format(
215− preview_list.initialIndex))
214+ Preview, objectName='preview')
216215
217216 @autopilot_logging.log_action(logger.debug)
218217 def click_scope_item(self, category, title, press_duration=0.10):
tests/autopilot/unity8/shell/tests/test_helpers.py−2
@@ -117,13 +117,11 @@ class GenericScopeViewHelperTestCase(tests.DashBaseTestCase):
117117 def test_open_preview(self):
118118 preview = self.generic_scope.open_preview('0', 'Title.0.0')
119119 self.assertIsInstance(preview, dash_helpers.Preview)
120− self.assertTrue(preview.isCurrent)
121120
122121 def test_open_preview_of_non_visible_item(self):
123122 """Open an item that requires swiping to make it visible."""
124123 preview = self.generic_scope.open_preview('2', 'Title.2.0')
125124 self.assertIsInstance(preview, dash_helpers.Preview)
126− self.assertTrue(preview.isCurrent)
127125
128126
129127class DashAppsHelperTestCase(tests.DashBaseTestCase):
tests/libs/UbuntuGestures/tst_TouchRegistry.cpp+59
@@ -42,6 +42,10 @@ public:
4242 QSet<int> ownedTouches;
4343 QSet<int> lostTouches;
4444 QList<TouchMemento> unownedTouchEvents;
45+
46+Q_SIGNALS:
47+ void gainedOwnership();
48+ void lostOwnership();
4549};
4650
4751class tst_TouchRegistry : public QObject
@@ -68,6 +72,7 @@ private Q_SLOTS:
6872 void removeOldUndecidedCandidates();
6973 void interimOwnerWontGetUnownedTouchEvents();
7074 void candidateVanishes();
75+ void candicateOwnershipReentrace();
7176
7277private:
7378 TouchRegistry *touchRegistry;
@@ -864,6 +869,58 @@ void tst_TouchRegistry::candidateVanishes()
864869 QVERIFY(mainCandidate.ownedTouches.contains(0));
865870}
866871
872+/*
873+ Regression test for canidate reentrance
874+
875+ Bug caused by candidates getting removed during ownership resolution
876+ */
877+void tst_TouchRegistry::candicateOwnershipReentrace()
878+{
879+ DummyCandidate mainCandidate;
880+ DummyCandidate candicate2;
881+ DummyCandidate candicate3;
882+
883+ {
884+ QList<QTouchEvent::TouchPoint> touchPoints;
885+ touchPoints.append(QTouchEvent::TouchPoint(0));
886+ touchPoints[0].setState(Qt::TouchPointPressed);
887+ QTouchEvent touchEvent(QEvent::TouchBegin,
888+ 0 /* device */,
889+ Qt::NoModifier,
890+ Qt::TouchPointPressed,
891+ touchPoints);
892+ touchRegistry->update(&touchEvent);
893+ }
894+
895+ // Re-entrance!
896+ connect(&candicate2, &DummyCandidate::lostOwnership, this, [&]() {
897+ touchRegistry->removeCandidateOwnerForTouch(0, &candicate2);
898+ });
899+
900+ touchRegistry->addCandidateOwnerForTouch(0, &mainCandidate);
901+ touchRegistry->addCandidateOwnerForTouch(0, &candicate2);
902+ touchRegistry->addCandidateOwnerForTouch(0, &candicate3);
903+
904+ {
905+ QList<QTouchEvent::TouchPoint> touchPoints;
906+ touchPoints.append(QTouchEvent::TouchPoint(0));
907+ touchPoints[0].setState(Qt::TouchPointMoved);
908+ QTouchEvent touchEvent(QEvent::TouchUpdate,
909+ 0 /* device */,
910+ Qt::NoModifier,
911+ Qt::TouchPointMoved,
912+ touchPoints);
913+ touchRegistry->update(&touchEvent);
914+ }
915+
916+ touchRegistry->requestTouchOwnership(0, &mainCandidate);
917+
918+ QCOMPARE(mainCandidate.ownedTouches.count(), 1);
919+ QCOMPARE(candicate2.lostTouches.count(), 1);
920+ QCOMPARE(candicate3.lostTouches.count(), 1);
921+}
922+
923+
867924////////////// TouchMemento //////////
868925
869926TouchMemento::TouchMemento(const QTouchEvent *touchEvent)
@@ -897,8 +954,10 @@ bool DummyCandidate::event(QEvent *e)
897954
898955 if (touchOwnershipEvent->gained()) {
899956 ownedTouches.insert(touchOwnershipEvent->touchId());
957+ Q_EMIT gainedOwnership();
900958 } else {
901959 lostTouches.insert(touchOwnershipEvent->touchId());
960+ Q_EMIT lostOwnership();
902961 }
903962 return true;
904963 } else if (e->type() == UnownedTouchEvent::unownedTouchEventType()) {
tests/mocks/AccountsService/AccountsService.cpp+23−3
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013, 2015 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -12,8 +12,6 @@
1212 *
1313 * You should have received a copy of the GNU General Public License
1414 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− *
16− * Author: Michael Terry <michael.terry@canonical.com>
1715 */
1816
1917#include "AccountsService.h"
@@ -172,3 +170,25 @@ bool AccountsService::hereLicensePathValid() const
172170{
173171 return !m_hereLicensePath.isNull();
174172}
173+
174+QString AccountsService::realName() const
175+{
176+ return m_realName;
177+}
178+
179+void AccountsService::setRealName(const QString &realName)
180+{
181+ m_realName = realName;
182+ Q_EMIT realNameChanged();
183+}
184+
185+QString AccountsService::email() const
186+{
187+ return m_email;
188+}
189+
190+void AccountsService::setEmail(const QString &email)
191+{
192+ m_email = email;
193+ Q_EMIT emailChanged();
194+}
tests/mocks/AccountsService/AccountsService.h+12−5
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013, 2015 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -12,9 +12,6 @@
1212 *
1313 * You should have received a copy of the GNU General Public License
1414 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− *
16− * Authors: Gerry Boland <gerry.boland@canonical.com>
17− * Michael Terry <michael.terry@canonical.com>
1815 */
1916
2017#ifndef UNITY_MOCK_ACCOUNTSSERVICE_H
@@ -75,11 +72,13 @@ class AccountsService: public QObject
7572 Q_PROPERTY(bool hereLicensePathValid // qml sees a null string as "", so we use proxy setting for that
7673 READ hereLicensePathValid
7774 NOTIFY hereLicensePathChanged)
75+ Q_PROPERTY(QString realName READ realName WRITE setRealName NOTIFY realNameChanged)
76+ Q_PROPERTY(QString email READ email WRITE setEmail NOTIFY emailChanged)
7877
7978public:
8079 enum PasswordDisplayHint {
8180 Keyboard,
82− Numeric,
81+ Numeric
8382 };
8483
8584 explicit AccountsService(QObject *parent = 0);
@@ -107,6 +106,10 @@ public:
107106 QString hereLicensePath() const;
108107 void setHereLicensePath(const QString &path);
109108 bool hereLicensePathValid() const;
109+ QString realName() const;
110+ void setRealName(const QString &realName);
111+ QString email() const;
112+ void setEmail(const QString &email);
110113
111114Q_SIGNALS:
112115 void userChanged();
@@ -120,6 +123,8 @@ Q_SIGNALS:
120123 void failedLoginsChanged();
121124 void hereEnabledChanged();
122125 void hereLicensePathChanged();
126+ void realNameChanged();
127+ void emailChanged();
123128
124129private:
125130 bool m_enableLauncherWhileLocked;
@@ -132,6 +137,8 @@ private:
132137 QStringList m_demoEdgesCompleted;
133138 bool m_hereEnabled;
134139 QString m_hereLicensePath;
140+ QString m_realName;
141+ QString m_email;
135142};
136143
137144#endif
tests/mocks/GSettings.1.0/fake_gsettings.cpp+64
@@ -22,6 +22,8 @@ GSettingsControllerQml* GSettingsControllerQml::s_controllerInstance = 0;
2222
2323GSettingsControllerQml::GSettingsControllerQml()
2424 : m_usageMode("Staged")
25+ , m_autohideLauncher(false)
26+ , m_launcherWidth(8)
2527{
2628}
2729
@@ -88,6 +90,32 @@ void GSettingsControllerQml::setLifecycleExemptAppids(const QStringList &appIds)
8890 }
8991}
9092
93+bool GSettingsControllerQml::autohideLauncher() const
94+{
95+ return m_autohideLauncher;
96+}
97+
98+void GSettingsControllerQml::setAutohideLauncher(bool autohideLauncher)
99+{
100+ if (m_autohideLauncher != autohideLauncher) {
101+ m_autohideLauncher = autohideLauncher;
102+ Q_EMIT autohideLauncherChanged(autohideLauncher);
103+ }
104+}
105+
106+int GSettingsControllerQml::launcherWidth() const
107+{
108+ return m_launcherWidth;
109+}
110+
111+void GSettingsControllerQml::setLauncherWidth(int launcherWidth)
112+{
113+ if (m_launcherWidth != launcherWidth) {
114+ m_launcherWidth = launcherWidth;
115+ Q_EMIT launcherWidthChanged(launcherWidth);
116+ }
117+}
118+
91119GSettingsSchemaQml::GSettingsSchemaQml(QObject *parent): QObject(parent) {
92120}
93121
@@ -129,6 +157,10 @@ GSettingsQml::GSettingsQml(QObject *parent)
129157 this, &GSettingsQml::lockedOutTimeChanged);
130158 connect(GSettingsControllerQml::instance(), &GSettingsControllerQml::lifecycleExemptAppidsChanged,
131159 this, &GSettingsQml::lifecycleExemptAppidsChanged);
160+ connect(GSettingsControllerQml::instance(), &GSettingsControllerQml::autohideLauncherChanged,
161+ this, &GSettingsQml::autohideLauncherChanged);
162+ connect(GSettingsControllerQml::instance(), &GSettingsControllerQml::launcherWidthChanged,
163+ this, &GSettingsQml::launcherWidthChanged);
132164}
133165
134166GSettingsSchemaQml * GSettingsQml::schema() const {
@@ -192,9 +224,41 @@ QStringList GSettingsQml::lifecycleExemptAppids() const
192224 }
193225}
194226
227+bool GSettingsQml::autohideLauncher() const
228+{
229+ if (m_schema->id() == "com.canonical.Unity8") {
230+ return GSettingsControllerQml::instance()->autohideLauncher();
231+ } else {
232+ return false;
233+ }
234+}
235+
236+int GSettingsQml::launcherWidth() const
237+{
238+ if (m_schema->id() == "com.canonical.Unity8") {
239+ return GSettingsControllerQml::instance()->launcherWidth();
240+ } else {
241+ return false;
242+ }
243+}
244+
195245void GSettingsQml::setLifecycleExemptAppids(const QStringList &appIds)
196246{
197247 if (m_schema->id() == "com.canonical.qtmir") {
198248 GSettingsControllerQml::instance()->setLifecycleExemptAppids(appIds);
199249 }
200250}
251+
252+void GSettingsQml::setAutohideLauncher(bool autohideLauncher)
253+{
254+ if (m_schema->id() == "com.canonical.Unity8") {
255+ GSettingsControllerQml::instance()->setAutohideLauncher(autohideLauncher);
256+ }
257+}
258+
259+void GSettingsQml::setLauncherWidth(int launcherWidth)
260+{
261+ if (m_schema->id() == "com.canonical.Unity8") {
262+ GSettingsControllerQml::instance()->setLauncherWidth(launcherWidth);
263+ }
264+}
tests/mocks/GSettings.1.0/fake_gsettings.h+18
@@ -50,6 +50,8 @@ class GSettingsQml: public QObject
5050 Q_PROPERTY(QString usageMode READ usageMode WRITE setUsageMode NOTIFY usageModeChanged)
5151 Q_PROPERTY(qint64 lockedOutTime READ lockedOutTime WRITE setLockedOutTime NOTIFY lockedOutTimeChanged)
5252 Q_PROPERTY(QStringList lifecycleExemptAppids READ lifecycleExemptAppids WRITE setLifecycleExemptAppids NOTIFY lifecycleExemptAppidsChanged)
53+ Q_PROPERTY(bool autohideLauncher READ autohideLauncher WRITE setAutohideLauncher NOTIFY autohideLauncherChanged)
54+ Q_PROPERTY(int launcherWidth READ launcherWidth WRITE setLauncherWidth NOTIFY launcherWidthChanged)
5355
5456public:
5557 GSettingsQml(QObject *parent = nullptr);
@@ -59,11 +61,15 @@ public:
5961 QString usageMode() const;
6062 qint64 lockedOutTime() const;
6163 QStringList lifecycleExemptAppids() const;
64+ bool autohideLauncher() const;
65+ int launcherWidth() const;
6266
6367 void setPictureUri(const QString &str);
6468 void setUsageMode(const QString &usageMode);
6569 void setLockedOutTime(qint64 timestamp);
6670 void setLifecycleExemptAppids(const QStringList &appIds);
71+ void setAutohideLauncher(bool autohideLauncher);
72+ void setLauncherWidth(int launcherWidth);
6773
6874Q_SIGNALS:
6975 void schemaChanged();
@@ -71,6 +77,8 @@ Q_SIGNALS:
7177 void usageModeChanged(const QString&);
7278 void lockedOutTimeChanged(qint64);
7379 void lifecycleExemptAppidsChanged(const QStringList &);
80+ void autohideLauncherChanged(bool);
81+ void launcherWidthChanged(int launcherWidth);
7482
7583private:
7684 GSettingsSchemaQml* m_schema;
@@ -98,11 +106,19 @@ public:
98106 QStringList lifecycleExemptAppids() const;
99107 Q_INVOKABLE void setLifecycleExemptAppids(const QStringList &appIds);
100108
109+ bool autohideLauncher() const;
110+ Q_INVOKABLE void setAutohideLauncher(bool autohideLauncher);
111+
112+ int launcherWidth() const;
113+ Q_INVOKABLE void setLauncherWidth(int launcherWidth);
114+
101115Q_SIGNALS:
102116 void pictureUriChanged(const QString&);
103117 void usageModeChanged(const QString&);
104118 void lockedOutTimeChanged(qint64 timestamp);
105119 void lifecycleExemptAppidsChanged(const QStringList&);
120+ void autohideLauncherChanged(bool autohideLauncher);
121+ void launcherWidthChanged(int launcherWidth);
106122
107123private:
108124 GSettingsControllerQml();
@@ -111,6 +127,8 @@ private:
111127 QString m_usageMode;
112128 qint64 m_lockedOutTime;
113129 QStringList m_lifecycleExemptAppids;
130+ bool m_autohideLauncher;
131+ int m_launcherWidth;
114132
115133 static GSettingsControllerQml* s_controllerInstance;
116134 QList<GSettingsQml *> m_registeredGSettings;
tests/mocks/IntegratedLightDM/liblightdm/UsersModel.cpp−14
@@ -47,20 +47,6 @@ UsersModel::UsersModel(QObject *parent) :
4747 roles[HasMessagesRole] = "hasMessages";
4848 roles[ImagePathRole] = "imagePath";
4949 setRoleNames(roles);
50−
51− // Now modify our mock user backgrounds
52− QDir bgdir = QDir("/usr/share/demo-assets/shell/backgrounds/");
53− QStringList backgrounds = bgdir.entryList(QDir::Files | QDir::NoDotAndDotDot);
54−
55− for (int i = 0, j = 0; i < d->entries.size(); i++) {
56− Entry &entry = d->entries[i];
57− if (entry.background.isNull() && !backgrounds.isEmpty()) {
58− entry.background = bgdir.filePath(backgrounds[j++]);
59− if (j >= backgrounds.length()) {
60− j = 0;
61− }
62− }
63− }
6450}
6551
6652UsersModel::~UsersModel()
tests/mocks/MeeGo/QOfono/MockOfonoSimManager.qml+1
@@ -23,6 +23,7 @@ Item {
2323 property string modemPath
2424 readonly property alias present: d.present
2525 readonly property alias ready: d.ready
26+ readonly property var preferredLanguages: ["de", "fr", "it", "en"]
2627
2728 QtObject {
2829 id: d
tests/mocks/Ubuntu/Connectivity/plugin.cpp+1
@@ -36,6 +36,7 @@ QmlConnectivityNetworkingPlugin::registerTypes(const char *uri)
3636{
3737 // @uri Ubuntu.Connectivity
3838 qmlRegisterSingletonType<NetworkingStatus>(uri, 1, 0, "NetworkingStatus", networkingStatusSingletonProvider);
39+ qmlRegisterSingletonType<NetworkingStatus>(uri, 1, 0, "Connectivity", networkingStatusSingletonProvider);
3940}
4041
4142void
tests/mocks/Ubuntu/SystemSettings/CMakeLists.txt+2
@@ -1,2 +1,4 @@
11add_subdirectory(LanguagePlugin)
22add_subdirectory(SecurityPrivacy)
3+add_subdirectory(TimeDate)
4+add_subdirectory(Diagnostics)
tests/mocks/Ubuntu/SystemSettings/Diagnostics/CMakeLists.txtnew file+1
@@ -0,0 +1 @@
1+add_unity8_mock(Ubuntu.SystemSettings.Diagnostics 1.0 Ubuntu/SystemSettings/Diagnostics)
tests/mocks/Ubuntu/SystemSettings/Diagnostics/MockDiagnostics.qmlnew file+24
@@ -0,0 +1,24 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+
19+Item {
20+ property bool reportCrashes: true
21+ function setReportCrashes(report) {
22+ reportCrashes = report;
23+ }
24+}
tests/mocks/Ubuntu/SystemSettings/Diagnostics/qmldirnew file+2
@@ -0,0 +1,2 @@
1+module Ubuntu.SystemSettings.Diagnostics
2+UbuntuDiagnostics 1.0 MockDiagnostics.qml
tests/mocks/Ubuntu/SystemSettings/LanguagePlugin/MockLanguagePlugin.qml+2−2
@@ -17,7 +17,7 @@
1717import QtQuick 2.4
1818
1919Item {
20− property var languageNames: ["English", "French", "Spanish"]
21− property var languageCodes: ["en", "fr", "es"]
20+ property var languageNames: ["English (United States)", "French (France)", "Spanish (Spain)"]
21+ property var languageCodes: ["en_US", "fr_FR", "es_ES"]
2222 property int currentLanguage: 0
2323}
tests/mocks/Ubuntu/SystemSettings/SecurityPrivacy/MockSecurityPrivacy.cpp+1−1
@@ -31,5 +31,5 @@ QString MockSecurityPrivacy::setSecurity(const QString &oldPasswd, const QString
3131{
3232 m_type = newType;
3333 Q_EMIT setSecurityCalled(oldPasswd, newPasswd, newType);
34− return "";
34+ return QString();
3535}
tests/mocks/Ubuntu/SystemSettings/TimeDate/CMakeLists.txtnew file+1
@@ -0,0 +1 @@
1+add_unity8_mock(Ubuntu.SystemSettings.TimeDate 1.0 Ubuntu/SystemSettings/TimeDate)
tests/mocks/Ubuntu/SystemSettings/TimeDate/MockTimeDate.qmlnew file+25
@@ -0,0 +1,25 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+
19+Item {
20+ id: root
21+ property string timeZone: "Europe/Prague"
22+ onTimeZoneChanged: root.timeZoneChangedCalled(root.timeZone);
23+
24+ signal timeZoneChangedCalled(string tz);
25+}
tests/mocks/Ubuntu/SystemSettings/TimeDate/qmldirnew file+2
@@ -0,0 +1,2 @@
1+module Ubuntu.SystemSettings.TimeDate
2+UbuntuTimeDatePanel 1.0 MockTimeDate.qml
tests/mocks/Unity/Application/ApplicationManager.cpp+10−25
@@ -151,10 +151,6 @@ void ApplicationManager::add(ApplicationInfo *application) {
151151
152152 beginInsertRows(QModelIndex(), m_runningApplications.size(), m_runningApplications.size());
153153 m_runningApplications.append(application);
154− endInsertRows();
155− Q_EMIT applicationAdded(application->appId());
156− Q_EMIT countChanged();
157− if (count() == 1) Q_EMIT emptyChanged(isEmpty()); // was empty but not anymore
158154
159155 connect(application, &ApplicationInfo::sessionChanged, this, [application, this]() {
160156 QModelIndex appIndex = findIndex(application);
@@ -171,6 +167,16 @@ void ApplicationManager::add(ApplicationInfo *application) {
171167 if (!appIndex.isValid()) return;
172168 Q_EMIT dataChanged(appIndex, appIndex, QVector<int>() << ApplicationManager::RoleState);
173169 });
170+ connect(application, &ApplicationInfo::stageChanged, this, [application, this]() {
171+ QModelIndex appIndex = findIndex(application);
172+ if (!appIndex.isValid()) return;
173+ Q_EMIT dataChanged(appIndex, appIndex, QVector<int>() << ApplicationManager::RoleStage);
174+ });
175+
176+ endInsertRows();
177+ Q_EMIT applicationAdded(application->appId());
178+ Q_EMIT countChanged();
179+ if (count() == 1) Q_EMIT emptyChanged(isEmpty()); // was empty but not anymore
174180}
175181
176182void ApplicationManager::remove(ApplicationInfo *application) {
@@ -202,23 +208,11 @@ void ApplicationManager::move(int from, int to) {
202208
203209ApplicationInfo* ApplicationManager::startApplication(const QString &appId,
204210 const QStringList &arguments)
205−{
206− return startApplication(appId, NoFlag, arguments);
207−}
208−
209−ApplicationInfo* ApplicationManager::startApplication(const QString &appId,
210− ExecFlags flags,
211− const QStringList &arguments)
212211{
213212 Q_UNUSED(arguments)
214213 ApplicationInfo *application = add(appId);
215214 if (!application)
216215 return 0;
217−
218− if (flags.testFlag(ApplicationManager::ForceMainStage)
219− && application->stage() == ApplicationInfo::SideStage) {
220− application->setStage(ApplicationInfo::MainStage);
221− }
222216 application->setState(ApplicationInfo::Starting);
223217
224218 return application;
@@ -317,7 +311,6 @@ void ApplicationManager::buildListOfAvailableApplications()
317311 application->setAppId("unity8-dash");
318312 application->setName("Unity 8 Mock Dash");
319313 application->setScreenshotId("unity8-dash");
320− application->setStage(ApplicationInfo::MainStage);
321314 application->setSupportedOrientations(Qt::PrimaryOrientation);
322315 m_availableApplications.append(application);
323316
@@ -326,7 +319,6 @@ void ApplicationManager::buildListOfAvailableApplications()
326319 application->setName("Dialer");
327320 application->setScreenshotId("dialer");
328321 application->setIconId("dialer-app");
329− application->setStage(ApplicationInfo::SideStage);
330322 application->setSupportedOrientations(Qt::PortraitOrientation
331323 | Qt::InvertedPortraitOrientation);
332324 m_availableApplications.append(application);
@@ -350,7 +342,6 @@ void ApplicationManager::buildListOfAvailableApplications()
350342 application->setScreenshotId("gallery");
351343 application->setIconId("gallery");
352344 application->setFullscreen(true);
353− application->setStage(ApplicationInfo::MainStage);
354345 m_availableApplications.append(application);
355346
356347 application = new ApplicationInfo(this);
@@ -358,7 +349,6 @@ void ApplicationManager::buildListOfAvailableApplications()
358349 application->setName("Facebook");
359350 application->setScreenshotId("facebook");
360351 application->setIconId("facebook");
361− application->setStage(ApplicationInfo::SideStage);
362352 m_availableApplications.append(application);
363353
364354 application = new ApplicationInfo(this);
@@ -374,7 +364,6 @@ void ApplicationManager::buildListOfAvailableApplications()
374364 application->setName("Twitter");
375365 application->setScreenshotId("twitter");
376366 application->setIconId("twitter");
377− application->setStage(ApplicationInfo::SideStage);
378367 m_availableApplications.append(application);
379368
380369 application = new ApplicationInfo(this);
@@ -390,7 +379,6 @@ void ApplicationManager::buildListOfAvailableApplications()
390379 application->setIconId("gmail");
391380 application->setScreenshotId("gmail-webapp.svg");
392381 application->setFullscreen(false);
393− application->setStage(ApplicationInfo::MainStage);
394382 application->setSupportedOrientations(Qt::PortraitOrientation
395383 | Qt::LandscapeOrientation
396384 | Qt::InvertedPortraitOrientation
@@ -403,7 +391,6 @@ void ApplicationManager::buildListOfAvailableApplications()
403391 application->setIconId("soundcloud");
404392 application->setScreenshotId("music");
405393 application->setFullscreen(false);
406− application->setStage(ApplicationInfo::MainStage);
407394 application->setSupportedOrientations(Qt::PortraitOrientation
408395 | Qt::LandscapeOrientation
409396 | Qt::InvertedPortraitOrientation
@@ -423,14 +410,12 @@ void ApplicationManager::buildListOfAvailableApplications()
423410 application->setAppId("notes-app");
424411 application->setName("Notepad");
425412 application->setIconId("notepad");
426− application->setStage(ApplicationInfo::SideStage);
427413 m_availableApplications.append(application);
428414
429415 application = new ApplicationInfo(this);
430416 application->setAppId("calendar-app");
431417 application->setName("Calendar");
432418 application->setIconId("calendar");
433− application->setStage(ApplicationInfo::SideStage);
434419 m_availableApplications.append(application);
435420
436421 application = new ApplicationInfo(this);
tests/mocks/Unity/Application/ApplicationManager.h−8
@@ -46,11 +46,6 @@ class ApplicationManager : public ApplicationManagerInterface {
4646 RoleSession = RoleExemptFromLifecycle+1,
4747 RoleFullscreen,
4848 };
49− enum Flag {
50− NoFlag = 0x0,
51− ForceMainStage = 0x1,
52− };
53− Q_DECLARE_FLAGS(ExecFlags, Flag)
5449
5550 // QAbstractItemModel methods.
5651 int rowCount(const QModelIndex& parent = QModelIndex()) const override;
@@ -65,7 +60,6 @@ class ApplicationManager : public ApplicationManagerInterface {
6560 Q_INVOKABLE bool focusApplication(const QString &appId) override;
6661 Q_INVOKABLE void unfocusCurrentApplication() override;
6762 Q_INVOKABLE ApplicationInfo *startApplication(const QString &appId, const QStringList &arguments = QStringList()) override;
68− Q_INVOKABLE ApplicationInfo *startApplication(const QString &appId, ExecFlags flags, const QStringList &arguments = QStringList());
6963 Q_INVOKABLE bool stopApplication(const QString &appId) override;
7064
7165 QString focusedApplicationId() const override;
@@ -98,8 +92,6 @@ class ApplicationManager : public ApplicationManagerInterface {
9892 static ApplicationManager *the_application_manager;
9993};
10094
101−Q_DECLARE_OPERATORS_FOR_FLAGS(ApplicationManager::ExecFlags)
102−
10395Q_DECLARE_METATYPE(ApplicationManager*)
10496
10597#endif // APPLICATION_MANAGER_H
tests/mocks/Unity/Application/MirSurface.cpp+6−2
@@ -17,13 +17,15 @@
1717#include "MirSurface.h"
1818
1919#include <QDebug>
20+#include <QQmlEngine>
2021
2122MirSurface::MirSurface(const QString& name,
2223 Mir::Type type,
2324 Mir::State state,
2425 const QUrl& screenshot,
25− const QUrl &qmlFilePath)
26− : unity::shell::application::MirSurfaceInterface(nullptr)
26+ const QUrl &qmlFilePath,
27+ QObject *parent)
28+ : unity::shell::application::MirSurfaceInterface(parent)
2729 , m_name(name)
2830 , m_type(type)
2931 , m_state(state)
@@ -38,6 +40,8 @@ MirSurface::MirSurface(const QString& name,
3840 , m_slowToResize(false)
3941{
4042// qDebug() << "MirSurface::MirSurface() " << name;
43+ QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership);
44+
4145 m_delayedResizeTimer.setInterval(600);
4246 m_delayedResizeTimer.setSingleShot(true);
4347 connect(&m_delayedResizeTimer, &QTimer::timeout, this, &MirSurface::applyDelayedResize);
tests/mocks/Unity/Application/MirSurface.h+2−1
@@ -41,7 +41,8 @@ public:
4141 Mir::Type type,
4242 Mir::State state,
4343 const QUrl& screenshot,
44− const QUrl &qmlFilePath = QUrl());
44+ const QUrl &qmlFilePath = QUrl(),
45+ QObject *parent = nullptr);
4546 virtual ~MirSurface();
4647
4748 ////
tests/mocks/Unity/Application/MirSurfaceItem.cpp+17
@@ -156,6 +156,10 @@ void MirSurfaceItem::createQmlContentItem()
156156
157157void MirSurfaceItem::touchEvent(QTouchEvent * event)
158158{
159+ if (event->type() == QEvent::TouchBegin) {
160+ m_touchTrail.clear();
161+ }
162+
159163 if (event->touchPointStates() & Qt::TouchPointPressed) {
160164 ++m_touchPressCount;
161165 Q_EMIT touchPressCountChanged(m_touchPressCount);
@@ -163,6 +167,19 @@ void MirSurfaceItem::touchEvent(QTouchEvent * event)
163167 ++m_touchReleaseCount;
164168 Q_EMIT touchReleaseCountChanged(m_touchReleaseCount);
165169 }
170+
171+ Q_FOREACH(QTouchEvent::TouchPoint touchPoint, event->touchPoints()) {
172+ QString id(touchPoint.id());
173+ QVariantList list = m_touchTrail[id].toList();
174+ list.append(QVariant::fromValue(touchPoint.pos()));
175+ if (list.count() > 100) list.pop_front();
176+ m_touchTrail[id] = list;
177+ }
178+
179+ if (m_qmlItem) {
180+ QQmlProperty touchTrail(m_qmlItem, "touchTrail");
181+ touchTrail.write(m_touchTrail);
182+ }
166183}
167184
168185void MirSurfaceItem::mousePressEvent(QMouseEvent * event)
tests/mocks/Unity/Application/MirSurfaceItem.h+1
@@ -123,6 +123,7 @@ private:
123123 int m_touchReleaseCount;
124124 int m_mousePressCount;
125125 int m_mouseReleaseCount;
126+ QVariantMap m_touchTrail;
126127
127128 FillMode m_fillMode{Stretch};
128129
tests/mocks/Unity/Application/SurfaceManager.cpp+8−10
@@ -34,6 +34,13 @@ SurfaceManager::SurfaceManager(QObject *parent) :
3434 QObject(parent)
3535 , m_virtualKeyboard(nullptr)
3636{
37+ m_virtualKeyboard = new VirtualKeyboard(this);
38+ connect(m_virtualKeyboard, &QObject::destroyed, this, [this](QObject *obj) {
39+ MirSurface* surface = qobject_cast<MirSurface*>(obj);
40+ m_virtualKeyboard = nullptr;
41+ Q_EMIT inputMethodSurfaceChanged();
42+ Q_EMIT surfaceDestroyed(surface);
43+ });
3744}
3845
3946MirSurface *SurfaceManager::createSurface(const QString& name,
@@ -58,17 +65,8 @@ MirSurface *SurfaceManager::createSurface(const QString& name,
5865 return surface;
5966}
6067
61−MirSurface *SurfaceManager::inputMethodSurface()
68+MirSurface *SurfaceManager::inputMethodSurface() const
6269{
63− if (!m_virtualKeyboard) {
64− m_virtualKeyboard = new VirtualKeyboard;
65− connect(m_virtualKeyboard, &QObject::destroyed, this, [this](QObject *obj) {
66− m_virtualKeyboard = nullptr;
67− MirSurface* surface = qobject_cast<MirSurface*>(obj);
68− Q_EMIT surfaceDestroyed(surface);
69− });
70− Q_EMIT surfaceCreated(m_virtualKeyboard);
71− }
7270 return m_virtualKeyboard;
7371}
7472
tests/mocks/Unity/Application/SurfaceManager.h+3−3
@@ -25,7 +25,7 @@
2525class SurfaceManager : public QObject
2626{
2727 Q_OBJECT
28−
28+ Q_PROPERTY(MirSurface* inputMethodSurface READ inputMethodSurface NOTIFY inputMethodSurfaceChanged)
2929 Q_PROPERTY(int newSurfaceMinimumWidth READ newSurfaceMinimumWidth WRITE setNewSurfaceMinimumWidth NOTIFY newSurfaceMinimumWidthChanged)
3030 Q_PROPERTY(int newSurfaceMaximumWidth READ newSurfaceMaximumWidth WRITE setNewSurfaceMaximumWidth NOTIFY newSurfaceMaximumWidthChanged)
3131 Q_PROPERTY(int newSurfaceMinimumHeight READ newSurfaceMinimumHeight WRITE setNewSurfaceMinimumHeight NOTIFY newSurfaceMinimumHeightChanged)
@@ -43,8 +43,7 @@ public:
4343 Mir::State state,
4444 const QUrl& screenshot);
4545
46− // To be used in the tests
47− Q_INVOKABLE MirSurface* inputMethodSurface();
46+ MirSurface* inputMethodSurface() const;
4847
4948 int newSurfaceMinimumWidth() const { return m_newSurfaceMinimumWidth; }
5049 void setNewSurfaceMinimumWidth(int value);
@@ -65,6 +64,7 @@ public:
6564 void setNewSurfaceHeightIncrement(int);
6665
6766Q_SIGNALS:
67+ void inputMethodSurfaceChanged();
6868 void countChanged();
6969 void surfaceCreated(MirSurface *surface);
7070 void surfaceDestroyed(MirSurface*surface);
tests/mocks/Unity/Application/VirtualKeyboard.cpp+3−2
@@ -22,12 +22,13 @@
2222
2323#include <QDebug>
2424
25−VirtualKeyboard::VirtualKeyboard()
25+VirtualKeyboard::VirtualKeyboard(QObject *parent)
2626 : MirSurface("input-method",
2727 Mir::InputMethodType,
2828 Mir::MinimizedState,
2929 QUrl("qrc:///Unity/Application/vkb_portrait.png"),
30− QUrl("qrc:///Unity/Application/VirtualKeyboard.qml"))
30+ QUrl("qrc:///Unity/Application/VirtualKeyboard.qml"),
31+ parent)
3132{
3233}
3334
tests/mocks/Unity/Application/VirtualKeyboard.h+1−1
@@ -25,7 +25,7 @@ class VirtualKeyboard : public MirSurface
2525{
2626 Q_OBJECT
2727public:
28− explicit VirtualKeyboard();
28+ explicit VirtualKeyboard(QObject *parent = nullptr);
2929 ~VirtualKeyboard();
3030};
3131
tests/mocks/Unity/Application/plugin.cpp+2−1
@@ -78,8 +78,9 @@ void FakeUnityApplicationQmlPlugin::registerTypes(const char *uri)
7878 qmlRegisterUncreatableType<unity::shell::application::ApplicationInfoInterface>(uri, 0, 1, "ApplicationInfoInterface", "Abstract interface. Cannot be created in QML");
7979 qmlRegisterUncreatableType<Session>(uri, 0, 1, "Session", "Session can't be instantiated from QML");
8080 qmlRegisterUncreatableType<MirSurface>(uri, 0, 1, "MirSurface", "MirSurface can't be instantiated from QML");
81+ qmlRegisterUncreatableType<unity::shell::application::MirSurfaceInterface>(
82+ uri, 0, 1, "MirSurface", "MirSurface can't be instantiated from QML");
8183 qmlRegisterType<MirSurfaceItem>(uri, 0, 1, "MirSurfaceItem");
82−
8384 qmlRegisterType<ApplicationInfo>(uri, 0, 1, "ApplicationInfo");
8485
8586 qmlRegisterSingletonType<ApplicationManager>(uri, 0, 1, "ApplicationManager", applicationManagerSingleton);
tests/mocks/Unity/Application/resources/MirSurfaceItem.qml+59
@@ -29,6 +29,9 @@ Rectangle {
2929 property alias screenshotSource: screenshotImage.source
3030 property int orientationAngle
3131
32+ property var touchTrail
33+ onTouchTrailChanged: canvas.requestPaint()
34+
3235 Image {
3336 id: screenshotImage
3437 anchors.fill: parent
@@ -67,4 +70,60 @@ Rectangle {
6770 width: (rotation == 0 || rotation == 180 ? parent.width : parent.height)
6871 height:(rotation == 0 || rotation == 180 ? parent.height : parent.width)
6972 }
73+
74+ Canvas {
75+ id: canvas
76+ anchors.fill: parent
77+ onPaint: {
78+ // get context to draw with
79+ var ctx = getContext("2d")
80+ ctx.reset(0, 0, canvas.width, canvas.height);
81+ ctx.lineWidth = 20;
82+
83+ if (touchTrail === undefined) return;
84+
85+ var i = 0;
86+ for (var prop in touchTrail) {
87+ var trail = touchTrail[prop];
88+
89+ if (trail.length > 0) {
90+ ctx.fillStyle = Qt.rgba(0, 0, 1, 0.3);
91+ ctx.strokeStyle = Qt.rgba(0, 0, 1, 0.3);
92+ ctx.beginPath();
93+ ctx.moveTo(trail[0].x, trail[0].y);
94+ ctx.ellipse(trail[0].x-1, trail[0].y-1, 2, 2);
95+ ctx.stroke();
96+
97+ ctx.beginPath();
98+ if (i == 0) {
99+ ctx.strokeStyle = Qt.rgba(1, 1, 0, 0.3);
100+ } else if (i == 1) {
101+ ctx.strokeStyle = Qt.rgba(0, 1, 1, 0.3);
102+
103+ } else if (i == 2) {
104+ ctx.strokeStyle = Qt.rgba(1, 0, 1, 0.3);
105+
106+ } else if (i == 3) {
107+ ctx.strokeStyle = Qt.rgba(0, 0.5, 0.5, 0.3);
108+
109+ } else {
110+ ctx.strokeStyle = Qt.rgba(0.7, 0.5, 0, 0.3);
111+ }
112+
113+ for (var i = 1; i < trail.length; i++) {
114+ ctx.lineTo(trail[i].x, trail[i].y)
115+ }
116+ ctx.stroke();
117+
118+ ctx.beginPath();
119+ ctx.fillStyle = Qt.rgba(1, 0, 0, 0.3);
120+ ctx.strokeStyle = Qt.rgba(1, 0, 0, 0.3);
121+ ctx.ellipse(trail[i-1].x-1, trail[i-1].y-1, 2, 2);
122+ ctx.stroke();
123+ }
124+ i++;
125+ }
126+
127+ }
128+ }
70129}
tests/mocks/Unity/Launcher/MockLauncherModel.cpp+2
@@ -25,6 +25,7 @@ MockLauncherModel::MockLauncherModel(QObject* parent): LauncherModelInterface(pa
2525 MockLauncherItem *item = new MockLauncherItem("dialer-app", "/usr/share/applications/dialer-app.desktop", "Dialer", "dialer-app", this);
2626 item->setProgress(0);
2727 item->setPinned(true);
28+ item->setRunning(true);
2829 item->setFocused(true);
2930 m_list.append(item);
3031 item = new MockLauncherItem("camera-app", "/usr/share/applications/camera-app.desktop", "Camera", "camera", this);
@@ -34,6 +35,7 @@ MockLauncherModel::MockLauncherModel(QObject* parent): LauncherModelInterface(pa
3435 item = new MockLauncherItem("gallery-app", "/usr/share/applications/gallery-app.desktop", "Gallery", "gallery", this);
3536 item->setProgress(50);
3637 item->setCountVisible(true);
38+ item->setRunning(true);
3739 item->setAlerting(false);
3840 m_list.append(item);
3941 item = new MockLauncherItem("music-app", "/usr/share/applications/music-app.desktop", "Music", "soundcloud", this);
tests/mocks/Utils/CMakeLists.txt+3−1
@@ -16,13 +16,15 @@ set(QMLPLUGIN_SRC
1616 ${CMAKE_SOURCE_DIR}/plugins/Utils/windowscreenshotprovider.cpp
1717 ${CMAKE_SOURCE_DIR}/plugins/Utils/easingcurve.cpp
1818 ${CMAKE_SOURCE_DIR}/plugins/Utils/inputwatcher.cpp
19+ ${CMAKE_SOURCE_DIR}/plugins/Utils/timezoneFormatter.cpp
1920 ${CMAKE_SOURCE_DIR}/plugins/Utils/applicationsfiltermodel.cpp
2021 ${CMAKE_SOURCE_DIR}/plugins/Utils/inputeventgenerator.cpp
22+ ${CMAKE_SOURCE_DIR}/plugins/Utils/deviceconfigparser.cpp
23+ ${CMAKE_SOURCE_DIR}/plugins/Utils/globalfunctions.cpp
2124 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationManagerInterface.h
2225 constants.cpp
2326 plugin.cpp
2427 windowstatestorage.cpp
25− plugin.cpp
2628 )
2729
2830add_library(FakeUtils-qml SHARED
tests/mocks/Utils/plugin.cpp+14
@@ -35,8 +35,11 @@
3535#include <windowinputfilter.h>
3636#include <windowscreenshotprovider.h>
3737#include <easingcurve.h>
38+#include <timezoneFormatter.h>
3839#include <applicationsfiltermodel.h>
3940#include <inputeventgenerator.h>
41+#include <deviceconfigparser.h>
42+#include <globalfunctions.h>
4043
4144static QObject *createWindowStateStorage(QQmlEngine *engine, QJSEngine *scriptEngine)
4245{
@@ -52,6 +55,13 @@ static QObject *createConstants(QQmlEngine *engine, QJSEngine *scriptEngine)
5255 return new Constants();
5356}
5457
58+static QObject *createGlobalFunctions(QQmlEngine *engine, QJSEngine *scriptEngine)
59+{
60+ Q_UNUSED(engine)
61+ Q_UNUSED(scriptEngine)
62+ return new GlobalFunctions();
63+}
64+
5565void FakeUtilsPlugin::registerTypes(const char *uri)
5666{
5767 Q_ASSERT(uri == QLatin1String("Utils"));
@@ -64,9 +74,13 @@ void FakeUtilsPlugin::registerTypes(const char *uri)
6474 qmlRegisterSingletonType<WindowStateStorage>(uri, 0, 1, "WindowStateStorage", createWindowStateStorage);
6575 qmlRegisterType<InputWatcher>(uri, 0, 1, "InputWatcher");
6676 qmlRegisterSingletonType<Constants>(uri, 0, 1, "Constants", createConstants);
77+ qmlRegisterSingletonType<TimezoneFormatter>(uri, 0, 1, "TimezoneFormatter",
78+ [](QQmlEngine*, QJSEngine*) -> QObject* { return new TimezoneFormatter; });
6779 qmlRegisterType<ActiveFocusLogger>(uri, 0, 1, "ActiveFocusLogger");
6880 qmlRegisterType<ApplicationsFilterModel>(uri, 0, 1, "ApplicationsFilterModel");
6981 qmlRegisterType<InputEventGenerator>(uri, 0, 1, "InputEventGenerator");
82+ qmlRegisterType<DeviceConfigParser>(uri, 0, 1, "DeviceConfigParser");
83+ qmlRegisterSingletonType<GlobalFunctions>(uri, 0, 1, "Functions", createGlobalFunctions);
7084}
7185
7286void FakeUtilsPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
tests/mocks/Utils/windowstatestorage.cpp+13
@@ -16,6 +16,8 @@
1616
1717#include "windowstatestorage.h"
1818
19+#include <unity/shell/application/ApplicationInfoInterface.h>
20+
1921WindowStateStorage::WindowStateStorage(QObject *parent):
2022 QObject(parent)
2123{
@@ -45,10 +47,21 @@ QRect WindowStateStorage::getGeometry(const QString &windowId, const QRect &defa
4547 return m_geometry.value(windowId).toRect();
4648}
4749
50+void WindowStateStorage::saveStage(const QString &appId, int stage)
51+{
52+ m_stage[appId] = stage;
53+}
54+
55+int WindowStateStorage::getStage(const QString &appId) const
56+{
57+ return m_stage.value(appId, unity::shell::application::ApplicationInfoInterface::MainStage);
58+}
59+
4860void WindowStateStorage::clear()
4961{
5062 m_state.clear();
5163 m_geometry.clear();
64+ m_stage.clear();
5265}
5366
5467void WindowStateStorage::saveState(const QString &windowId, WindowState state)
tests/mocks/Utils/windowstatestorage.h+4
@@ -36,6 +36,9 @@ public:
3636 Q_INVOKABLE void saveGeometry(const QString &windowId, const QRect &rect);
3737 Q_INVOKABLE QRect getGeometry(const QString &windowId, const QRect &defaultValue);
3838
39+ Q_INVOKABLE void saveStage(const QString &appId, int stage);
40+ Q_INVOKABLE int getStage(const QString &appId) const;
41+
3942 // Only in the mock, to easily restore a fresh state
4043 Q_INVOKABLE void clear();
4144
@@ -47,5 +50,6 @@ private:
4750 QVariantMap geometry() const;
4851
4952 QHash<QString, WindowState> m_state;
53+ QHash<QString, int> m_stage;
5054 QVariantMap m_geometry;
5155};
tests/mocks/Wizard/CMakeLists.txt+6−1
@@ -1,4 +1,5 @@
11include_directories(
2+ ${GLIB_INCLUDE_DIRS} ${GEONAMES_INCLUDE_DIRS}
23 ${CMAKE_SOURCE_DIR}/plugins/Wizard
34)
45
@@ -6,7 +7,11 @@ add_library(MockWizard-qml MODULE
67 mockplugin.cpp
78 MockSystem.cpp
89 ${CMAKE_SOURCE_DIR}/plugins/Wizard/PageList.cpp
10+ ${CMAKE_SOURCE_DIR}/plugins/Wizard/timezonemodel.cpp
11+ ${CMAKE_SOURCE_DIR}/plugins/Wizard/LocalePlugin.cpp
12+ ${CMAKE_SOURCE_DIR}/plugins/Wizard/Status.cpp
913)
1014
11−qt5_use_modules(MockWizard-qml Qml)
15+qt5_use_modules(MockWizard-qml DBus Qml Concurrent)
16+target_link_libraries(MockWizard-qml ${GLIB_LDFLAGS} ${GEONAMES_LDFLAGS})
1217add_unity8_mock(Wizard 0.1 Wizard TARGETS MockWizard-qml)
tests/mocks/Wizard/MockSystem.cpp+4−2
@@ -14,6 +14,8 @@
1414 * with this program. If not, see <http://www.gnu.org/licenses/>.
1515 */
1616
17+#include <QDebug>
18+
1719#include "MockSystem.h"
1820
1921MockSystem::MockSystem()
@@ -33,7 +35,7 @@ void MockSystem::setWizardEnabled(bool enabled)
3335 Q_EMIT wizardEnabledChanged();
3436}
3537
36−void MockSystem::updateSessionLanguage(const QString &locale)
38+void MockSystem::updateSessionLocale(const QString &locale)
3739{
38− Q_EMIT updateSessionLanguageCalled(locale);
40+ Q_EMIT updateSessionLocaleCalled(locale);
3941}
tests/mocks/Wizard/MockSystem.h+2−2
@@ -32,11 +32,11 @@ public:
3232 void setWizardEnabled(bool enabled);
3333
3434public Q_SLOTS:
35− void updateSessionLanguage(const QString &locale);
35+ void updateSessionLocale(const QString &locale);
3636
3737Q_SIGNALS:
3838 void wizardEnabledChanged();
39− void updateSessionLanguageCalled(const QString &locale); // only in mock
39+ void updateSessionLocaleCalled(const QString &locale); // only in mock
4040
4141private:
4242 Q_DISABLE_COPY(MockSystem)
tests/mocks/Wizard/mockplugin.cpp+7−8
@@ -17,19 +17,18 @@
1717#include "mockplugin.h"
1818#include "MockSystem.h"
1919#include "PageList.h"
20+#include "timezonemodel.h"
21+#include "LocalePlugin.h"
22+#include "Status.h"
2023
2124#include <QtQml/qqml.h>
2225
23−static QObject *system_provider(QQmlEngine *engine, QJSEngine *scriptEngine)
24−{
25− Q_UNUSED(engine)
26− Q_UNUSED(scriptEngine)
27− return new MockSystem();
28−}
29−
3026void MockWizardPlugin::registerTypes(const char *uri)
3127{
3228 Q_ASSERT(uri == QLatin1String("Wizard"));
3329 qmlRegisterType<PageList>(uri, 0, 1, "PageList");
34− qmlRegisterSingletonType<MockSystem>(uri, 0, 1, "System", system_provider);
30+ qmlRegisterSingletonType<MockSystem>(uri, 0, 1, "System", [](QQmlEngine*, QJSEngine*) -> QObject* { return new MockSystem; });
31+ qmlRegisterSingletonType<Status>(uri, 0, 1, "Status", [](QQmlEngine*, QJSEngine*) -> QObject* { return new Status; });
32+ qmlRegisterType<TimeZoneLocationModel>(uri, 0, 1, "TimeZoneModel");
33+ qmlRegisterType<LocalePlugin>(uri, 0, 1, "LocalePlugin");
3534}
tests/plugins/AccountsService/CMakeLists.txt+5
@@ -10,11 +10,13 @@ macro(make_dbus_class NAME INTERFACE)
1010endmacro(make_dbus_class)
1111
1212make_dbus_class(Accounts org.freedesktop.Accounts)
13+make_dbus_class(Input com.ubuntu.AccountsService.Input)
1314make_dbus_class(Properties org.freedesktop.DBus.Properties)
1415make_dbus_class(SecurityPrivacy com.ubuntu.touch.AccountsService.SecurityPrivacy)
1516make_dbus_class(Location com.ubuntu.location.providers.here.AccountsService)
1617make_dbus_class(AccountsUser org.freedesktop.Accounts.User)
1718make_dbus_class(AccountsPrivate com.canonical.unity.AccountsService.Private)
19+make_dbus_class(UscInput com.canonical.Unity.Input)
1820
1921include_directories(
2022 ${CMAKE_CURRENT_BINARY_DIR}
@@ -27,12 +29,15 @@ add_executable(mock-server
2729 ${CMAKE_CURRENT_BINARY_DIR}/AccountsAdaptor.cpp
2830 ${CMAKE_CURRENT_BINARY_DIR}/AccountsPrivateAdaptor.cpp
2931 ${CMAKE_CURRENT_BINARY_DIR}/AccountsUserAdaptor.cpp
32+ ${CMAKE_CURRENT_BINARY_DIR}/InputAdaptor.cpp
3033 ${CMAKE_CURRENT_BINARY_DIR}/PropertiesAdaptor.cpp
3134 ${CMAKE_CURRENT_BINARY_DIR}/LocationAdaptor.cpp
3235 ${CMAKE_CURRENT_BINARY_DIR}/SecurityPrivacyAdaptor.cpp
36+ ${CMAKE_CURRENT_BINARY_DIR}/UscInputAdaptor.cpp
3337 server.cpp
3438 AccountsServer.cpp
3539 PropertiesServer.cpp
40+ UscServer.cpp
3641 )
3742qt5_use_modules(mock-server Core DBus)
3843
tests/plugins/AccountsService/PropertiesServer.cpp+16−4
@@ -32,9 +32,9 @@ PropertiesServer::PropertiesServer(QObject *parent)
3232 Reset();
3333}
3434
35−QDBusVariant PropertiesServer::Get(const QString &interface, const QString &property)
35+QDBusVariant PropertiesServer::Get(const QString &interface, const QString &property) const
3636{
37− if (m_properties[interface].contains(property)) {
37+ if (m_properties.contains(interface) && m_properties[interface].contains(property)) {
3838 return QDBusVariant(m_properties[interface][property]);
3939 } else {
4040 sendErrorReply(QDBusError::InvalidArgs, "Bad interface or property");
@@ -42,6 +42,16 @@ QDBusVariant PropertiesServer::Get(const QString &interface, const QString &prop
4242 }
4343}
4444
45+QVariantMap PropertiesServer::GetAll(const QString &interface) const
46+{
47+ if (m_properties.contains(interface)) {
48+ return m_properties[interface];
49+ } else {
50+ sendErrorReply(QDBusError::InvalidArgs, "Bad interface");
51+ return QVariantMap();
52+ }
53+}
54+
4555void PropertiesServer::Set(const QString &interface, const QString &property, const QDBusVariant &variant)
4656{
4757 QVariant newValue = variant.variant();
@@ -57,8 +67,8 @@ void PropertiesServer::Set(const QString &interface, const QString &property, co
5767
5868 oldValue = newValue;
5969
60− // Special case for Background file.
61− if (interface == "org.freedesktop.Accounts.User" && property == "BackgroundFile") {
70+ // Special case for user properties.
71+ if (interface == "org.freedesktop.Accounts.User") {
6272 Q_EMIT Changed();
6373 } else {
6474 QVariantMap propertyChanges;
@@ -83,10 +93,12 @@ void PropertiesServer::Reset()
8393 m_properties["com.canonical.unity.AccountsService"]["LauncherItems"] = QVariant::fromValue(QList<QVariantMap>());
8494 m_properties["com.canonical.unity.AccountsService.Private"]["FailedLogins"] = 0;
8595 m_properties["com.ubuntu.touch.AccountsService.SecurityPrivacy"]["StatsWelcomeScreen"] = true;
96+ m_properties["com.ubuntu.AccountsService.Input"]["MousePrimaryButton"] = "right";
8697 m_properties["com.ubuntu.AccountsService.SecurityPrivacy"]["EnableLauncherWhileLocked"] = true;
8798 m_properties["com.ubuntu.AccountsService.SecurityPrivacy"]["EnableIndicatorsWhileLocked"] = true;
8899 m_properties["com.ubuntu.AccountsService.SecurityPrivacy"]["PasswordDisplayHint"] = AccountsService::Keyboard;
89100 m_properties["com.ubuntu.location.providers.here.AccountsService"]["LicenseAccepted"] = false;
90101 m_properties["com.ubuntu.location.providers.here.AccountsService"]["LicenseBasePath"] = "";
91102 m_properties["org.freedesktop.Accounts.User"]["BackgroundFile"] = "";
103+ m_properties["org.freedesktop.Accounts.User"]["RealName"] = "";
92104}
tests/plugins/AccountsService/PropertiesServer.h+2−1
@@ -34,7 +34,8 @@ public:
3434 explicit PropertiesServer(QObject *parent = 0);
3535
3636public Q_SLOTS:
37− QDBusVariant Get(const QString &interface, const QString &property);
37+ QDBusVariant Get(const QString &interface, const QString &property) const;
38+ QVariantMap GetAll(const QString &interface) const;
3839 void Set(const QString &interface, const QString &property, const QDBusVariant &variant);
3940
4041 // mock only.
tests/plugins/AccountsService/UscServer.cppnew file+28
@@ -0,0 +1,28 @@
1+/*
2+ * Copyright 2016 Canonical Ltd.
3+ *
4+ * This program is free software: you can redistribute it and/or modify it
5+ * under the terms of the GNU General Public License version 3, as published
6+ * by the Free Software Foundation.
7+ *
8+ * This program is distributed in the hope that it will be useful, but
9+ * WITHOUT ANY WARRANTY; without even the implied warranties of
10+ * MERCHANTABILITY, SATISFACTORY QUALITY or FITNESS FOR A PARTICULAR
11+ * PURPOSE. See the GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * version 3 along with this program. If not, see
15+ * <http://www.gnu.org/licenses/>
16+ */
17+
18+#include "UscServer.h"
19+
20+UscServer::UscServer(QObject *parent)
21+ : QObject(parent)
22+{
23+}
24+
25+void UscServer::setMousePrimaryButton(int button)
26+{
27+ Q_EMIT setMousePrimaryButtonCalled(button);
28+}
tests/plugins/AccountsService/UscServer.hnew file+39
@@ -0,0 +1,39 @@
1+/*
2+ * Copyright 2016 Canonical Ltd.
3+ *
4+ * This program is free software: you can redistribute it and/or modify it
5+ * under the terms of the GNU General Public License version 3, as published
6+ * by the Free Software Foundation.
7+ *
8+ * This program is distributed in the hope that it will be useful, but
9+ * WITHOUT ANY WARRANTY; without even the implied warranties of
10+ * MERCHANTABILITY, SATISFACTORY QUALITY or FITNESS FOR A PARTICULAR
11+ * PURPOSE. See the GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * version 3 along with this program. If not, see
15+ * <http://www.gnu.org/licenses/>
16+ */
17+
18+#ifndef UNITY_USCSERVER_H
19+#define UNITY_USCSERVER_H
20+
21+#include <QDBusContext>
22+#include <QObject>
23+
24+class UscServer: public QObject, protected QDBusContext
25+{
26+ Q_OBJECT
27+
28+public:
29+ explicit UscServer(QObject *parent = nullptr);
30+
31+public Q_SLOTS:
32+ void setMousePrimaryButton(int button);
33+
34+Q_SIGNALS:
35+ // mock only.
36+ void setMousePrimaryButtonCalled(int button);
37+};
38+
39+#endif
tests/plugins/AccountsService/client.cpp+63−9
@@ -42,7 +42,16 @@ public:
4242 : QObject(parent)
4343 , m_userInterface(nullptr)
4444 , m_spy(this, &AccountsServiceTest::propertiesChanged)
45+ , m_mousePrimaryButtonSpy(this, &AccountsServiceTest::setMousePrimaryButtonCalled)
4546 {
47+ m_uscInputInterface = new QDBusInterface("com.canonical.Unity.Input",
48+ "/com/canonical/Unity/Input",
49+ "com.canonical.Unity.Input",
50+ QDBusConnection::sessionBus(),
51+ this);
52+
53+ QObject::connect(m_uscInputInterface, SIGNAL(setMousePrimaryButtonCalled(int)),
54+ this, SIGNAL(setMousePrimaryButtonCalled(int)));
4655 }
4756
4857private Q_SLOTS:
@@ -71,6 +80,7 @@ private Q_SLOTS:
7180
7281 delete m_userInterface;
7382 m_spy.clear();
83+ m_mousePrimaryButtonSpy.clear();
7484 }
7585
7686 void testInvalids()
@@ -144,7 +154,7 @@ private Q_SLOTS:
144154 QCOMPARE(session.demoEdgesCompleted(), QStringList() << "testedge");
145155 }
146156
147− void testAsynchornousChangeForDemoEdges()
157+ void testAsynchronousChangeForDemoEdges()
148158 {
149159 AccountsService session(this, QTest::currentTestFunction());
150160
@@ -156,7 +166,7 @@ private Q_SLOTS:
156166 QTRY_COMPARE(session.demoEdges(), true);
157167 }
158168
159− void testAsynchornousChangeForFailedLogins()
169+ void testAsynchronousChangeForFailedLogins()
160170 {
161171 AccountsService session(this, QTest::currentTestFunction());
162172
@@ -168,7 +178,7 @@ private Q_SLOTS:
168178 QTRY_COMPARE(session.failedLogins(), (uint)5);
169179 }
170180
171− void testAsynchornousChangeForStatsWelcomeScreen()
181+ void testAsynchronousChangeForStatsWelcomeScreen()
172182 {
173183 AccountsService session(this, QTest::currentTestFunction());
174184
@@ -180,7 +190,7 @@ private Q_SLOTS:
180190 QTRY_COMPARE(session.statsWelcomeScreen(), false);
181191 }
182192
183− void testAsynchornousChangeForStatsEnableLauncherWhileLocked()
193+ void testAsynchronousChangeForEnableLauncherWhileLocked()
184194 {
185195 AccountsService session(this, QTest::currentTestFunction());
186196
@@ -192,7 +202,7 @@ private Q_SLOTS:
192202 QTRY_COMPARE(session.enableLauncherWhileLocked(), false);
193203 }
194204
195− void testAsynchornousChangeForStatsEnableIndicatorsWhileLocked()
205+ void testAsynchronousChangeForEnableIndicatorsWhileLocked()
196206 {
197207 AccountsService session(this, QTest::currentTestFunction());
198208
@@ -204,7 +214,7 @@ private Q_SLOTS:
204214 QTRY_COMPARE(session.enableIndicatorsWhileLocked(), false);
205215 }
206216
207− void testAsynchornousChangeForStatsPasswordDisplayHint()
217+ void testAsynchronousChangeForPasswordDisplayHint()
208218 {
209219 AccountsService session(this, QTest::currentTestFunction());
210220
@@ -216,7 +226,7 @@ private Q_SLOTS:
216226 QTRY_COMPARE(session.passwordDisplayHint(), AccountsService::Numeric);
217227 }
218228
219− void testAsynchornousChangeForStatsLicenseAccepted()
229+ void testAsynchronousChangeForLicenseAccepted()
220230 {
221231 AccountsService session(this, QTest::currentTestFunction());
222232
@@ -228,7 +238,7 @@ private Q_SLOTS:
228238 QTRY_COMPARE(session.hereEnabled(), true);
229239 }
230240
231− void testAsynchornousChangeForLicenseBasePath()
241+ void testAsynchronousChangeForLicenseBasePath()
232242 {
233243 AccountsService session(this, QTest::currentTestFunction());
234244
@@ -240,7 +250,7 @@ private Q_SLOTS:
240250 QTRY_COMPARE(session.hereLicensePath(), QString("/"));
241251 }
242252
243− void testAsynchornousChangeForStatsBackgroundFile()
253+ void testAsynchronousChangeForBackgroundFile()
244254 {
245255 AccountsService session(this, QTest::currentTestFunction());
246256
@@ -252,12 +262,56 @@ private Q_SLOTS:
252262 QTRY_COMPARE(session.backgroundFile(), QString("/test/BackgroundFile"));
253263 }
254264
265+ void testProxyOnStartup()
266+ {
267+ AccountsService session(this, QTest::currentTestFunction());
268+
269+ QTRY_COMPARE(m_mousePrimaryButtonSpy.count(), 1);
270+ QList<QVariant> arguments = m_mousePrimaryButtonSpy.takeFirst();
271+ QCOMPARE(arguments.at(0).toInt(), 1);
272+ }
273+
274+ void testProxyOnChange()
275+ {
276+ AccountsService session(this, QTest::currentTestFunction());
277+ QTRY_COMPARE(m_mousePrimaryButtonSpy.count(), 1);
278+ m_mousePrimaryButtonSpy.clear();
279+
280+ ASSERT_DBUS_CALL(m_userInterface->asyncCall("Set",
281+ "com.ubuntu.AccountsService.Input",
282+ "MousePrimaryButton",
283+ dbusVariant("left")));
284+
285+ QTRY_COMPARE(m_mousePrimaryButtonSpy.count(), 1);
286+ QList<QVariant> arguments = m_mousePrimaryButtonSpy.takeFirst();
287+ QCOMPARE(arguments.at(0).toInt(), 0);
288+ }
289+
290+ void testInvalidPrimaryButton()
291+ {
292+ AccountsService session(this, QTest::currentTestFunction());
293+ QTRY_COMPARE(m_mousePrimaryButtonSpy.count(), 1);
294+ m_mousePrimaryButtonSpy.clear();
295+
296+ ASSERT_DBUS_CALL(m_userInterface->asyncCall("Set",
297+ "com.ubuntu.AccountsService.Input",
298+ "MousePrimaryButton",
299+ dbusVariant("NOPE")));
300+
301+ QTRY_COMPARE(m_mousePrimaryButtonSpy.count(), 1);
302+ QList<QVariant> arguments = m_mousePrimaryButtonSpy.takeFirst();
303+ QCOMPARE(arguments.at(0).toInt(), 0);
304+ }
305+
255306Q_SIGNALS:
256307 void propertiesChanged(const QString &interface, const QVariantMap &changed, const QStringList &invalid);
308+ void setMousePrimaryButtonCalled(int button);
257309
258310private:
311+ QDBusInterface* m_uscInputInterface;
259312 QDBusInterface* m_userInterface;
260313 QSignalSpy m_spy;
314+ QSignalSpy m_mousePrimaryButtonSpy;
261315};
262316
263317QTEST_MAIN(AccountsServiceTest)
tests/plugins/AccountsService/interfaces.xml+16
@@ -5,6 +5,11 @@
55 <arg name="property" type="s" direction="in" />
66 <arg name="value" type="v" direction="out" />
77 </method>
8+ <method name="GetAll">
9+ <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QVariantMap"/>
10+ <arg name="interface" type="s" direction="in" />
11+ <arg name="value" type="a{sv}" direction="out" />
12+ </method>
813 <method name="Set">
914 <arg name="interface" type="s" direction="in" />
1015 <arg name="property" type="s" direction="in" />
@@ -32,10 +37,21 @@
3237 <arg name="success" type="b" direction="out" />
3338 </method>
3439 </interface>
40+ <interface name="com.ubuntu.AccountsService.Input" />
3541 <interface name="com.ubuntu.touch.AccountsService.SecurityPrivacy" />
3642 <interface name="com.ubuntu.location.providers.here.AccountsService" />
3743 <interface name="org.freedesktop.Accounts.User">
3844 <signal name="Changed" />
3945 </interface>
4046 <interface name="com.canonical.unity.AccountsService.Private" />
47+
48+ <interface name="com.canonical.Unity.Input">
49+ <method name="setMousePrimaryButton">
50+ <arg name="button" type="i" direction="in" />
51+ </method>
52+ <!-- Just for testing -->
53+ <signal name="setMousePrimaryButtonCalled">
54+ <arg name="button" type="i" direction="out" />
55+ </signal>
56+ </interface>
4157</node>
tests/plugins/AccountsService/server.cpp+12
@@ -19,10 +19,13 @@
1919
2020#include "AccountsAdaptor.h"
2121#include "AccountsServer.h"
22+#include "InputAdaptor.h"
2223#include "PropertiesServer.h"
2324#include "SecurityPrivacyAdaptor.h"
2425#include "LocationAdaptor.h"
2526#include "AccountsPrivateAdaptor.h"
27+#include "UscInputAdaptor.h"
28+#include "UscServer.h"
2629#include <QCoreApplication>
2730
2831int main(int argc, char *argv[])
@@ -31,11 +34,20 @@ int main(int argc, char *argv[])
3134
3235 auto accounts = new AccountsServer(&a);
3336 new AccountsAdaptor(accounts);
37+ new InputAdaptor(accounts);
3438 new SecurityPrivacyAdaptor(accounts);
3539 new LocationAdaptor(accounts);
3640 new AccountsPrivateAdaptor(accounts);
41+
42+ auto usc = new UscServer(&a);
43+ new UscInputAdaptor(usc);
44+
3745 // We use the session bus for testing. The real plugin uses the system bus
3846 auto connection = QDBusConnection::sessionBus();
47+ if (!connection.registerObject("/com/canonical/Unity/Input", usc))
48+ return 1;
49+ if (!connection.registerService("com.canonical.Unity.Input"))
50+ return 1;
3951 if (!connection.registerObject("/org/freedesktop/Accounts", accounts))
4052 return 1;
4153 if (!connection.registerService("org.freedesktop.Accounts"))
tests/plugins/Dash/cardcreator/1.res+4−6
@@ -5,12 +5,10 @@ AbstractButton {
55 property string backgroundShapeStyle: "inset";
66 property real fontScale: 1.0;
77 property var scopeStyle: null;
8− property int titleAlignment: Text.AlignLeft;
98 property int fixedHeaderHeight: -1;
109 property size fixedArtShapeSize: Qt.size(-1, -1);
1110 readonly property string title: cardData && cardData["title"] || "";
12− property bool asynchronous: true;
13− property bool showHeader: true;
11+ property bool showHeader: true;
1412 implicitWidth: childrenRect.width;
1513 enabled: true;
1614
@@ -25,7 +23,7 @@ Item {
2523 objectName: "artShapeLoader";
2624 readonly property string cardArt: cardData && cardData["art"] || "";
2725 active: cardArt != "";
28− asynchronous: root.asynchronous;
26+ asynchronous: true;
2927 visible: status == Loader.Ready;
3028 sourceComponent: Item {
3129 id: artShape;
@@ -82,7 +80,7 @@ Item {
8280 id: artImage;
8381 objectName: "artImage";
8482 source: artShapeLoader.cardArt;
85− asynchronous: root.asynchronous;
83+ asynchronous: true;
8684 width: root.width;
8785 height: width / artShape.aspect;
8886 }
@@ -108,7 +106,7 @@ Label {
108106 width: undefined;
109107 text: root.title;
110108 font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
111− horizontalAlignment: root.titleAlignment;
109+ horizontalAlignment: Text.AlignHCenter;
112110 }
113111UbuntuShape {
114112 id: touchdown;
tests/plugins/Dash/cardcreator/1.res.cardcreatornew file+119
@@ -0,0 +1,119 @@
1+AbstractButton {
2+ id: root;
3+ property var cardData;
4+ property string artShapeStyle: "inset";
5+ property string backgroundShapeStyle: "inset";
6+ property real fontScale: 1.0;
7+ property var scopeStyle: null;
8+ property size fixedArtShapeSize: Qt.size(-1, -1);
9+ readonly property string title: cardData && cardData["title"] || "";
10+ property bool showHeader: true;
11+ implicitWidth: childrenRect.width;
12+ enabled: true;
13+
14+readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);
15+Item {
16+ id: artShapeHolder;
17+ height: root.fixedArtShapeSize.height > 0 ? root.fixedArtShapeSize.height : artShapeLoader.height;
18+ width: root.fixedArtShapeSize.width > 0 ? root.fixedArtShapeSize.width : artShapeLoader.width;
19+ anchors { horizontalCenter: parent.horizontalCenter; }
20+ Loader {
21+ id: artShapeLoader;
22+ objectName: "artShapeLoader";
23+ readonly property string cardArt: cardData && cardData["art"] || "";
24+ active: cardArt != "";
25+ asynchronous: false;
26+ visible: status == Loader.Ready;
27+ sourceComponent: Item {
28+ id: artShape;
29+ objectName: "artShape";
30+ visible: image.status == Image.Ready;
31+ readonly property alias image: artImage;
32+ ShaderEffectSource {
33+ id: artShapeSource;
34+ sourceItem: artImage;
35+ anchors.centerIn: parent;
36+ width: 1;
37+ height: 1;
38+ hideSource: true;
39+ }
40+ Loader {
41+ anchors.fill: parent;
42+ visible: true;
43+ sourceComponent: root.artShapeStyle === "icon" ? artShapeIconComponent : artShapeShapeComponent;
44+ Component {
45+ id: artShapeShapeComponent;
46+ UbuntuShape {
47+ source: artShapeSource;
48+ sourceFillMode: UbuntuShape.PreserveAspectCrop;
49+ radius: "medium";
50+ aspect: {
51+ switch (root.artShapeStyle) {
52+ case "inset": return UbuntuShape.Inset;
53+ case "shadow": return UbuntuShape.DropShadow;
54+ default:
55+ case "flat": return UbuntuShape.Flat;
56+ }
57+ }
58+ }
59+ }
60+ Component {
61+ id: artShapeIconComponent;
62+ ProportionalShape { source: artShapeSource; aspect: UbuntuShape.DropShadow; }
63+ }
64+ }
65+ readonly property real fixedArtShapeSizeAspect: (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) ? root.fixedArtShapeSize.width / root.fixedArtShapeSize.height : -1;
66+ readonly property real aspect: fixedArtShapeSizeAspect > 0 ? fixedArtShapeSizeAspect : 1.6;
67+ Component.onCompleted: { updateWidthHeightBindings(); }
68+ Connections { target: root; onFixedArtShapeSizeChanged: updateWidthHeightBindings(); }
69+ function updateWidthHeightBindings() {
70+ if (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) {
71+ width = root.fixedArtShapeSize.width;
72+ height = root.fixedArtShapeSize.height;
73+ } else {
74+ width = Qt.binding(function() { return image.status !== Image.Ready ? 0 : image.width });
75+ height = Qt.binding(function() { return image.status !== Image.Ready ? 0 : image.height });
76+ }
77+ }
78+ CroppedImageMinimumSourceSize {
79+ id: artImage;
80+ objectName: "artImage";
81+ source: artShapeLoader.cardArt;
82+ asynchronous: false;
83+ width: root.width;
84+ height: width / artShape.aspect;
85+ }
86+ }
87+ }
88+ }
89+readonly property int headerHeight: titleLabel.height;
90+Label {
91+ id: titleLabel;
92+ objectName: "titleLabel";
93+ anchors { right: parent.right;
94+ left: parent.left;
95+ top: artShapeHolder.bottom;
96+ topMargin: units.gu(1);
97+ }
98+ elide: Text.ElideRight;
99+ fontSize: "small";
100+ wrapMode: Text.Wrap;
101+ maximumLineCount: 2;
102+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
103+ color: root.scopeStyle ? root.scopeStyle.foreground : theme.palette.normal.baseText;
104+ visible: showHeader ;
105+ width: undefined;
106+ text: root.title;
107+ font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
108+ horizontalAlignment: Text.AlignHCenter;
109+ }
110+UbuntuShape {
111+ id: touchdown;
112+ objectName: "touchdown";
113+ anchors { fill: artShapeHolder }
114+ visible: root.artShapeStyle != "shadow" && root.artShapeStyle != "icon" && root.pressed;
115+ radius: "medium";
116+ borderSource: "radius_pressed.sci"
117+}
118+implicitHeight: titleLabel.y + titleLabel.height + units.gu(1);
119+}
tests/plugins/Dash/cardcreator/10.res+3−5
@@ -5,11 +5,9 @@ AbstractButton {
55 property string backgroundShapeStyle: "inset";
66 property real fontScale: 1.0;
77 property var scopeStyle: null;
8− property int titleAlignment: Text.AlignLeft;
98 property int fixedHeaderHeight: -1;
109 property size fixedArtShapeSize: Qt.size(-1, -1);
1110 readonly property string title: cardData && cardData["title"] || "";
12− property bool asynchronous: true;
1311 property bool showHeader: true;
1412 implicitWidth: childrenRect.width;
1513 enabled: true;
@@ -18,7 +16,7 @@ Loader {
1816 id: backgroundLoader;
1917 objectName: "backgroundLoader";
2018 anchors.fill: parent;
21− asynchronous: root.asynchronous;
19+ asynchronous: true;
2220 visible: status == Loader.Ready;
2321 sourceComponent: UbuntuShape {
2422 objectName: "background";
@@ -59,7 +57,7 @@ Row {
5957 objectName: "outerRow";
6058 property real margins: units.gu(1);
6159 spacing: margins;
62− height: root.fixedHeaderHeight != -1 ? root.fixedHeaderHeight : implicitHeight;
60+ height: root.fixedHeaderHeight;
6361 anchors { top: parent.top;
6462 topMargin: units.gu(1);
6563left: parent.left;
@@ -103,7 +101,7 @@ left: parent.left;
103101 width: undefined;
104102 text: root.title;
105103 font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
106− horizontalAlignment: root.titleAlignment;
104+ horizontalAlignment: Text.AlignLeft;
107105 }
108106,Label {
109107 id: subtitleLabel;
tests/plugins/Dash/cardcreator/10.res.cardcreatornew file+137
@@ -0,0 +1,137 @@
1+AbstractButton {
2+ id: root;
3+ property var cardData;
4+ property string artShapeStyle: "inset";
5+ property string backgroundShapeStyle: "inset";
6+ property real fontScale: 1.0;
7+ property var scopeStyle: null;
8+ property size fixedArtShapeSize: Qt.size(-1, -1);
9+ readonly property string title: cardData && cardData["title"] || "";
10+ property bool showHeader: true;
11+ implicitWidth: childrenRect.width;
12+ enabled: true;
13+
14+Loader {
15+ id: backgroundLoader;
16+ objectName: "backgroundLoader";
17+ anchors.fill: parent;
18+ asynchronous: false;
19+ visible: status == Loader.Ready;
20+ sourceComponent: UbuntuShape {
21+ objectName: "background";
22+ radius: "medium";
23+ aspect: {
24+ switch (root.backgroundShapeStyle) {
25+ case "inset": return UbuntuShape.Inset;
26+ case "shadow": return UbuntuShape.DropShadow;
27+ default:
28+ case "flat": return UbuntuShape.Flat;
29+ }
30+ }
31+ backgroundColor: getColor(0) || "white";
32+ secondaryBackgroundColor: getColor(1) || backgroundColor;
33+ backgroundMode: UbuntuShape.VerticalGradient;
34+ anchors.fill: parent;
35+ source: backgroundImage.source ? backgroundImage : null;
36+ property real luminance: Style.luminance(backgroundColor);
37+ property Image backgroundImage: Image {
38+ objectName: "backgroundImage";
39+ source: {
40+ if (cardData && typeof cardData["background"] === "string") return cardData["background"];
41+ else return "";
42+ }
43+ }
44+ function getColor(index) {
45+ if (cardData && typeof cardData["background"] === "object"
46+ && (cardData["background"]["type"] === "color" || cardData["background"]["type"] === "gradient")) {
47+ return cardData["background"]["elements"][index];
48+ } else return index === 0 ? "" : "";
49+ }
50+ }
51+ }
52+readonly property size artShapeSize: Qt.size(-1, -1);
53+readonly property int headerHeight: row.height;
54+Row {
55+ id: row;
56+ objectName: "outerRow";
57+ property real margins: units.gu(1);
58+ spacing: margins;
59+ anchors { top: parent.top;
60+ topMargin: units.gu(1);
61+left: parent.left;
62+ }
63+ anchors.right: parent.right;
64+ anchors.margins: margins;
65+ anchors.rightMargin: 0;
66+ data: [
67+ CroppedImageMinimumSourceSize {
68+ id: mascotImage;
69+ objectName: "mascotImage";
70+ anchors { verticalCenter: parent.verticalCenter; }
71+ source: cardData && cardData["mascot"] || "";
72+ width: units.gu(6);
73+ height: units.gu(5.625);
74+ horizontalAlignment: Image.AlignHCenter;
75+ verticalAlignment: Image.AlignVCenter;
76+ visible: showHeader;
77+
78+ }
79+,Item {
80+ id: headerTitleContainer;
81+ anchors { verticalCenter: parent.verticalCenter; }
82+ width: parent.width - x;
83+ implicitHeight: titleLabel.height + subtitleLabel.height;
84+ data: [
85+ Label {
86+ id: titleLabel;
87+ objectName: "titleLabel";
88+ anchors { right: parent.right;
89+rightMargin: units.gu(1);
90+left: parent.left;
91+ top: parent.top; }
92+ elide: Text.ElideRight;
93+ fontSize: "small";
94+ wrapMode: Text.Wrap;
95+ maximumLineCount: 2;
96+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
97+ color: backgroundLoader.active && backgroundLoader.item && root.scopeStyle ? root.scopeStyle.getTextColor(backgroundLoader.item.luminance) : (backgroundLoader.item && backgroundLoader.item.luminance > 0.7 ? theme.palette.normal.baseText : "white");
98+ visible: showHeader ;
99+ width: undefined;
100+ text: root.title;
101+ font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
102+ horizontalAlignment: Text.AlignLeft;
103+ }
104+,Label {
105+ id: subtitleLabel;
106+ objectName: "subtitleLabel";
107+ anchors { right: parent.right;
108+ left: parent.left;
109+rightMargin: units.gu(1);
110+top: titleLabel.bottom;
111+ }
112+ anchors.topMargin: units.dp(2);
113+ elide: Text.ElideRight;
114+ maximumLineCount: 1;
115+ fontSize: "x-small";
116+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
117+ color: backgroundLoader.active && backgroundLoader.item && root.scopeStyle ? root.scopeStyle.getTextColor(backgroundLoader.item.luminance) : (backgroundLoader.item && backgroundLoader.item.luminance > 0.7 ? theme.palette.normal.baseText : "white");
118+ visible: titleLabel.visible && titleLabel.text;
119+ text: cardData && cardData["subtitle"] || "";
120+ font.weight: Font.Light;
121+ }
122+
123+ ]
124+ }
125+
126+ ]
127+ }
128+UbuntuShape {
129+ id: touchdown;
130+ objectName: "touchdown";
131+ anchors { fill: backgroundLoader }
132+ visible: root.artShapeStyle != "shadow" && root.artShapeStyle != "icon" && root.pressed;
133+ radius: "medium";
134+ borderSource: "radius_pressed.sci"
135+ }
136+implicitHeight: row.y + row.height + units.gu(1);
137+}
tests/plugins/Dash/cardcreator/11.res+5−7
@@ -5,11 +5,9 @@ AbstractButton {
55 property string backgroundShapeStyle: "inset";
66 property real fontScale: 1.0;
77 property var scopeStyle: null;
8− property int titleAlignment: Text.AlignLeft;
98 property int fixedHeaderHeight: -1;
109 property size fixedArtShapeSize: Qt.size(-1, -1);
1110 readonly property string title: cardData && cardData["title"] || "";
12− property bool asynchronous: true;
1311 property bool showHeader: true;
1412 implicitWidth: childrenRect.width;
1513 enabled: true;
@@ -18,7 +16,7 @@ Loader {
1816 id: backgroundLoader;
1917 objectName: "backgroundLoader";
2018 anchors.fill: parent;
21− asynchronous: root.asynchronous;
19+ asynchronous: true;
2220 visible: status == Loader.Ready;
2321 sourceComponent: UbuntuShape {
2422 objectName: "background";
@@ -63,7 +61,7 @@ Item {
6361 objectName: "artShapeLoader";
6462 readonly property string cardArt: cardData && cardData["art"] || decodeURI("%5C");
6563 active: cardArt != "";
66− asynchronous: root.asynchronous;
64+ asynchronous: true;
6765 visible: status == Loader.Ready;
6866 sourceComponent: Item {
6967 id: artShape;
@@ -120,7 +118,7 @@ Item {
120118 id: artImage;
121119 objectName: "artImage";
122120 source: artShapeLoader.cardArt;
123− asynchronous: root.asynchronous;
121+ asynchronous: true;
124122 width: root.width;
125123 height: width / artShape.aspect;
126124 onStatusChanged: if (status === Image.Error) source = decodeURI("%5C");
@@ -134,7 +132,7 @@ Row {
134132 objectName: "outerRow";
135133 property real margins: units.gu(1);
136134 spacing: margins;
137− height: root.fixedHeaderHeight != -1 ? root.fixedHeaderHeight : implicitHeight;
135+ height: root.fixedHeaderHeight;
138136 anchors { top: artShapeHolder.bottom;
139137 topMargin: units.gu(1);
140138left: parent.left;
@@ -178,7 +176,7 @@ left: parent.left;
178176 width: undefined;
179177 text: root.title;
180178 font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
181− horizontalAlignment: root.titleAlignment;
179+ horizontalAlignment: Text.AlignLeft;
182180 }
183181,Label {
184182 id: subtitleLabel;
tests/plugins/Dash/cardcreator/11.res.cardcreatornew file+210
@@ -0,0 +1,210 @@
1+AbstractButton {
2+ id: root;
3+ property var cardData;
4+ property string artShapeStyle: "inset";
5+ property string backgroundShapeStyle: "inset";
6+ property real fontScale: 1.0;
7+ property var scopeStyle: null;
8+ property size fixedArtShapeSize: Qt.size(-1, -1);
9+ readonly property string title: cardData && cardData["title"] || "";
10+ property bool showHeader: true;
11+ implicitWidth: childrenRect.width;
12+ enabled: true;
13+
14+Loader {
15+ id: backgroundLoader;
16+ objectName: "backgroundLoader";
17+ anchors.fill: parent;
18+ asynchronous: false;
19+ visible: status == Loader.Ready;
20+ sourceComponent: UbuntuShape {
21+ objectName: "background";
22+ radius: "medium";
23+ aspect: {
24+ switch (root.backgroundShapeStyle) {
25+ case "inset": return UbuntuShape.Inset;
26+ case "shadow": return UbuntuShape.DropShadow;
27+ default:
28+ case "flat": return UbuntuShape.Flat;
29+ }
30+ }
31+ backgroundColor: getColor(0) || "white";
32+ secondaryBackgroundColor: getColor(1) || backgroundColor;
33+ backgroundMode: UbuntuShape.VerticalGradient;
34+ anchors.fill: parent;
35+ source: backgroundImage.source ? backgroundImage : null;
36+ property real luminance: Style.luminance(backgroundColor);
37+ property Image backgroundImage: Image {
38+ objectName: "backgroundImage";
39+ source: {
40+ if (cardData && typeof cardData["background"] === "string") return cardData["background"];
41+ else return "";
42+ }
43+ }
44+ function getColor(index) {
45+ if (cardData && typeof cardData["background"] === "object"
46+ && (cardData["background"]["type"] === "color" || cardData["background"]["type"] === "gradient")) {
47+ return cardData["background"]["elements"][index];
48+ } else return index === 0 ? "#E9E9E9" : undefined;
49+ }
50+ }
51+ }
52+readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);
53+Item {
54+ id: artShapeHolder;
55+ height: root.fixedArtShapeSize.height > 0 ? root.fixedArtShapeSize.height : artShapeLoader.height;
56+ width: root.fixedArtShapeSize.width > 0 ? root.fixedArtShapeSize.width : artShapeLoader.width;
57+ anchors { horizontalCenter: parent.horizontalCenter; }
58+ Loader {
59+ id: artShapeLoader;
60+ objectName: "artShapeLoader";
61+ readonly property string cardArt: cardData && cardData["art"] || "";
62+ active: cardArt != "";
63+ asynchronous: false;
64+ visible: status == Loader.Ready;
65+ sourceComponent: Item {
66+ id: artShape;
67+ objectName: "artShape";
68+ visible: image.status == Image.Ready;
69+ readonly property alias image: artImage;
70+ ShaderEffectSource {
71+ id: artShapeSource;
72+ sourceItem: artImage;
73+ anchors.centerIn: parent;
74+ width: 1;
75+ height: 1;
76+ hideSource: true;
77+ }
78+ Loader {
79+ anchors.fill: parent;
80+ visible: true;
81+ sourceComponent: root.artShapeStyle === "icon" ? artShapeIconComponent : artShapeShapeComponent;
82+ Component {
83+ id: artShapeShapeComponent;
84+ UbuntuShape {
85+ source: artShapeSource;
86+ sourceFillMode: UbuntuShape.PreserveAspectCrop;
87+ radius: "medium";
88+ aspect: {
89+ switch (root.artShapeStyle) {
90+ case "inset": return UbuntuShape.Inset;
91+ case "shadow": return UbuntuShape.DropShadow;
92+ default:
93+ case "flat": return UbuntuShape.Flat;
94+ }
95+ }
96+ }
97+ }
98+ Component {
99+ id: artShapeIconComponent;
100+ ProportionalShape { source: artShapeSource; aspect: UbuntuShape.DropShadow; }
101+ }
102+ }
103+ readonly property real fixedArtShapeSizeAspect: (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) ? root.fixedArtShapeSize.width / root.fixedArtShapeSize.height : -1;
104+ readonly property real aspect: fixedArtShapeSizeAspect > 0 ? fixedArtShapeSizeAspect : 1;
105+ Component.onCompleted: { updateWidthHeightBindings(); }
106+ Connections { target: root; onFixedArtShapeSizeChanged: updateWidthHeightBindings(); }
107+ function updateWidthHeightBindings() {
108+ if (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) {
109+ width = root.fixedArtShapeSize.width;
110+ height = root.fixedArtShapeSize.height;
111+ } else {
112+ width = Qt.binding(function() { return image.status !== Image.Ready ? 0 : image.width });
113+ height = Qt.binding(function() { return image.status !== Image.Ready ? 0 : image.height });
114+ }
115+ }
116+ CroppedImageMinimumSourceSize {
117+ id: artImage;
118+ objectName: "artImage";
119+ source: artShapeLoader.cardArt;
120+ asynchronous: false;
121+ width: root.width;
122+ height: width / artShape.aspect;
123+ }
124+ }
125+ }
126+ }
127+readonly property int headerHeight: row.height;
128+Row {
129+ id: row;
130+ objectName: "outerRow";
131+ property real margins: units.gu(1);
132+ spacing: margins;
133+ anchors { top: artShapeHolder.bottom;
134+ topMargin: units.gu(1);
135+left: parent.left;
136+ }
137+ anchors.right: parent.right;
138+ anchors.margins: margins;
139+ anchors.rightMargin: 0;
140+ data: [
141+ CroppedImageMinimumSourceSize {
142+ id: mascotImage;
143+ objectName: "mascotImage";
144+ anchors { verticalCenter: parent.verticalCenter; }
145+ source: cardData && cardData["mascot"] || "";
146+ width: units.gu(6);
147+ height: units.gu(5.625);
148+ horizontalAlignment: Image.AlignHCenter;
149+ verticalAlignment: Image.AlignVCenter;
150+ visible: showHeader;
151+ }
152+,Item {
153+ id: headerTitleContainer;
154+ anchors { verticalCenter: parent.verticalCenter; }
155+ width: parent.width - x;
156+ implicitHeight: titleLabel.height + subtitleLabel.height;
157+ data: [
158+ Label {
159+ id: titleLabel;
160+ objectName: "titleLabel";
161+ anchors { right: parent.right;
162+rightMargin: units.gu(1);
163+left: parent.left;
164+ top: parent.top; }
165+ elide: Text.ElideRight;
166+ fontSize: "small";
167+ wrapMode: Text.Wrap;
168+ maximumLineCount: 2;
169+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
170+ color: backgroundLoader.active && backgroundLoader.item && root.scopeStyle ? root.scopeStyle.getTextColor(backgroundLoader.item.luminance) : (backgroundLoader.item && backgroundLoader.item.luminance > 0.7 ? theme.palette.normal.baseText : "white");
171+ visible: showHeader ;
172+ width: undefined;
173+ text: root.title;
174+ font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
175+ horizontalAlignment: Text.AlignLeft;
176+ }
177+,Label {
178+ id: subtitleLabel;
179+ objectName: "subtitleLabel";
180+ anchors { right: parent.right;
181+ left: parent.left;
182+rightMargin: units.gu(1);
183+top: titleLabel.bottom;
184+ }
185+ anchors.topMargin: units.dp(2);
186+ elide: Text.ElideRight;
187+ maximumLineCount: 1;
188+ fontSize: "x-small";
189+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
190+ color: backgroundLoader.active && backgroundLoader.item && root.scopeStyle ? root.scopeStyle.getTextColor(backgroundLoader.item.luminance) : (backgroundLoader.item && backgroundLoader.item.luminance > 0.7 ? theme.palette.normal.baseText : "white");
191+ visible: titleLabel.visible && titleLabel.text;
192+ text: cardData && cardData["subtitle"] || "";
193+ font.weight: Font.Light;
194+ }
195+
196+ ]
197+ }
198+
199+ ]
200+ }
201+UbuntuShape {
202+ id: touchdown;
203+ objectName: "touchdown";
204+ anchors { fill: backgroundLoader }
205+ visible: root.artShapeStyle != "shadow" && root.artShapeStyle != "icon" && root.pressed;
206+ radius: "medium";
207+ borderSource: "radius_pressed.sci"
208+ }
209+implicitHeight: row.y + row.height + units.gu(1);
210+}
tests/plugins/Dash/cardcreator/2.res+3−5
@@ -5,11 +5,9 @@ AbstractButton {
55 property string backgroundShapeStyle: "inset";
66 property real fontScale: 1.0;
77 property var scopeStyle: null;
8− property int titleAlignment: Text.AlignLeft;
98 property int fixedHeaderHeight: -1;
109 property size fixedArtShapeSize: Qt.size(-1, -1);
1110 readonly property string title: cardData && cardData["title"] || "";
12− property bool asynchronous: true;
1311 property bool showHeader: true;
1412 implicitWidth: childrenRect.width;
1513 enabled: true;
@@ -18,7 +16,7 @@ Loader {
1816 id: backgroundLoader;
1917 objectName: "backgroundLoader";
2018 anchors.fill: parent;
21− asynchronous: root.asynchronous;
19+ asynchronous: true;
2220 visible: status == Loader.Ready;
2321 sourceComponent: UbuntuShape {
2422 objectName: "background";
@@ -59,7 +57,7 @@ Row {
5957 objectName: "outerRow";
6058 property real margins: units.gu(1);
6159 spacing: margins;
62− height: root.fixedHeaderHeight != -1 ? root.fixedHeaderHeight : implicitHeight;
60+ height: root.fixedHeaderHeight;
6361 anchors { top: parent.top;
6462 topMargin: units.gu(1);
6563 left: parent.left;
@@ -102,7 +100,7 @@ CroppedImageMinimumSourceSize {
102100 width: undefined;
103101 text: root.title;
104102 font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
105− horizontalAlignment: root.titleAlignment;
103+ horizontalAlignment: Text.AlignLeft;
106104 }
107105 ,Label {
108106 id: subtitleLabel;
tests/plugins/Dash/cardcreator/2.res.cardcreatornew file+136
@@ -0,0 +1,136 @@
1+AbstractButton {
2+ id: root;
3+ property var cardData;
4+ property string artShapeStyle: "inset";
5+ property string backgroundShapeStyle: "inset";
6+ property real fontScale: 1.0;
7+ property var scopeStyle: null;
8+ property size fixedArtShapeSize: Qt.size(-1, -1);
9+ readonly property string title: cardData && cardData["title"] || "";
10+ property bool showHeader: true;
11+ implicitWidth: childrenRect.width;
12+ enabled: true;
13+
14+Loader {
15+ id: backgroundLoader;
16+ objectName: "backgroundLoader";
17+ anchors.fill: parent;
18+ asynchronous: false;
19+ visible: status == Loader.Ready;
20+ sourceComponent: UbuntuShape {
21+ objectName: "background";
22+ radius: "medium";
23+ aspect: {
24+ switch (root.backgroundShapeStyle) {
25+ case "inset": return UbuntuShape.Inset;
26+ case "shadow": return UbuntuShape.DropShadow;
27+ default:
28+ case "flat": return UbuntuShape.Flat;
29+ }
30+ }
31+ backgroundColor: getColor(0) || "white";
32+ secondaryBackgroundColor: getColor(1) || backgroundColor;
33+ backgroundMode: UbuntuShape.VerticalGradient;
34+ anchors.fill: parent;
35+ source: backgroundImage.source ? backgroundImage : null;
36+ property real luminance: Style.luminance(backgroundColor);
37+ property Image backgroundImage: Image {
38+ objectName: "backgroundImage";
39+ source: {
40+ if (cardData && typeof cardData["background"] === "string") return cardData["background"];
41+ else return "";
42+ }
43+ }
44+ function getColor(index) {
45+ if (cardData && typeof cardData["background"] === "object"
46+ && (cardData["background"]["type"] === "color" || cardData["background"]["type"] === "gradient")) {
47+ return cardData["background"]["elements"][index];
48+ } else return index === 0 ? "#E9E9E9" : undefined;
49+ }
50+ }
51+ }
52+readonly property size artShapeSize: Qt.size(-1, -1);
53+readonly property int headerHeight: row.height;
54+Row {
55+ id: row;
56+ objectName: "outerRow";
57+ property real margins: units.gu(1);
58+ spacing: margins;
59+ anchors { top: parent.top;
60+ topMargin: units.gu(1);
61+ left: parent.left;
62+ }
63+ anchors.right: parent.right;
64+ anchors.margins: margins;
65+ anchors.rightMargin: 0;
66+ data: [
67+CroppedImageMinimumSourceSize {
68+ id: mascotImage;
69+ objectName: "mascotImage";
70+ anchors { verticalCenter: parent.verticalCenter; }
71+ source: cardData && cardData["mascot"] || "";
72+ width: units.gu(6);
73+ height: units.gu(5.625);
74+ horizontalAlignment: Image.AlignHCenter;
75+ verticalAlignment: Image.AlignVCenter;
76+ visible: showHeader;
77+ }
78+,Item {
79+ id: headerTitleContainer;
80+ anchors { verticalCenter: parent.verticalCenter; }
81+ width: parent.width - x;
82+ implicitHeight: titleLabel.height + subtitleLabel.height;
83+ data: [
84+ Label {
85+ id: titleLabel;
86+ objectName: "titleLabel";
87+ anchors { right: parent.right;
88+ rightMargin: units.gu(1);
89+ left: parent.left;
90+ top: parent.top; }
91+ elide: Text.ElideRight;
92+ fontSize: "small";
93+ wrapMode: Text.Wrap;
94+ maximumLineCount: 2;
95+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
96+ color: backgroundLoader.active && backgroundLoader.item && root.scopeStyle ? root.scopeStyle.getTextColor(backgroundLoader.item.luminance) : (backgroundLoader.item && backgroundLoader.item.luminance > 0.7 ? theme.palette.normal.baseText : "white");
97+ visible: showHeader ;
98+ width: undefined;
99+ text: root.title;
100+ font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
101+ horizontalAlignment: Text.AlignLeft;
102+ }
103+ ,Label {
104+ id: subtitleLabel;
105+ objectName: "subtitleLabel";
106+ anchors { right: parent.right;
107+ left: parent.left;
108+ rightMargin: units.gu(1);
109+ top: titleLabel.bottom;
110+ }
111+ anchors.topMargin: units.dp(2);
112+ elide: Text.ElideRight;
113+ maximumLineCount: 1;
114+ fontSize: "x-small";
115+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
116+ color: backgroundLoader.active && backgroundLoader.item && root.scopeStyle ? root.scopeStyle.getTextColor(backgroundLoader.item.luminance) : (backgroundLoader.item && backgroundLoader.item.luminance > 0.7 ? theme.palette.normal.baseText : "white");
117+ visible: titleLabel.visible && titleLabel.text;
118+ text: cardData && cardData["subtitle"] || "";
119+ font.weight: Font.Light;
120+ }
121+
122+ ]
123+ }
124+
125+ ]
126+ }
127+UbuntuShape {
128+ id: touchdown;
129+ objectName: "touchdown";
130+ anchors { fill: backgroundLoader }
131+ visible: root.artShapeStyle != "shadow" && root.artShapeStyle != "icon" && root.pressed;
132+ radius: "medium";
133+ borderSource: "radius_pressed.sci"
134+}
135+implicitHeight: row.y + row.height + units.gu(1);
136+}
tests/plugins/Dash/cardcreator/3.res+3−5
@@ -5,11 +5,9 @@ AbstractButton {
55 property string backgroundShapeStyle: "inset";
66 property real fontScale: 1.0;
77 property var scopeStyle: null;
8− property int titleAlignment: Text.AlignLeft;
98 property int fixedHeaderHeight: -1;
109 property size fixedArtShapeSize: Qt.size(-1, -1);
1110 readonly property string title: cardData && cardData["title"] || "";
12− property bool asynchronous: true;
1311 property bool showHeader: true;
1412 implicitWidth: childrenRect.width;
1513 enabled: true;
@@ -25,7 +23,7 @@ Item {
2523 objectName: "artShapeLoader";
2624 readonly property string cardArt: cardData && cardData["art"] || decodeURI("IHAVE%5C%22ESCAPED%5C%22QUOTES%5C%22");
2725 active: cardArt != "";
28− asynchronous: root.asynchronous;
26+ asynchronous: true;
2927 visible: status == Loader.Ready;
3028 sourceComponent: Item {
3129 id: artShape;
@@ -82,7 +80,7 @@ Item {
8280 id: artImage;
8381 objectName: "artImage";
8482 source: artShapeLoader.cardArt;
85− asynchronous: root.asynchronous;
83+ asynchronous: true;
8684 width: root.width;
8785 height: width / artShape.aspect;
8886 onStatusChanged: if (status === Image.Error) source = decodeURI("IHAVE%5C%22ESCAPED%5C%22QUOTES%5C%22");
@@ -109,7 +107,7 @@ Label {
109107 width: undefined;
110108 text: root.title;
111109 font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
112− horizontalAlignment: root.titleAlignment;
110+ horizontalAlignment: Text.AlignLeft;
113111 }
114112Label {
115113 id: subtitleLabel;
tests/plugins/Dash/cardcreator/3.res.cardcreatornew file+137
@@ -0,0 +1,137 @@
1+AbstractButton {
2+ id: root;
3+ property var cardData;
4+ property string artShapeStyle: "inset";
5+ property string backgroundShapeStyle: "inset";
6+ property real fontScale: 1.0;
7+ property var scopeStyle: null;
8+ property size fixedArtShapeSize: Qt.size(-1, -1);
9+ readonly property string title: cardData && cardData["title"] || "";
10+ property bool showHeader: true;
11+ implicitWidth: childrenRect.width;
12+ enabled: true;
13+
14+readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);
15+Item {
16+ id: artShapeHolder;
17+ height: root.fixedArtShapeSize.height > 0 ? root.fixedArtShapeSize.height : artShapeLoader.height;
18+ width: root.fixedArtShapeSize.width > 0 ? root.fixedArtShapeSize.width : artShapeLoader.width;
19+ anchors { horizontalCenter: parent.horizontalCenter; }
20+ Loader {
21+ id: artShapeLoader;
22+ objectName: "artShapeLoader";
23+ readonly property string cardArt: cardData && cardData["art"] || "";
24+ active: cardArt != "";
25+ asynchronous: false;
26+ visible: status == Loader.Ready;
27+ sourceComponent: Item {
28+ id: artShape;
29+ objectName: "artShape";
30+ visible: image.status == Image.Ready;
31+ readonly property alias image: artImage;
32+ ShaderEffectSource {
33+ id: artShapeSource;
34+ sourceItem: artImage;
35+ anchors.centerIn: parent;
36+ width: 1;
37+ height: 1;
38+ hideSource: true;
39+ }
40+ Loader {
41+ anchors.fill: parent;
42+ visible: true;
43+ sourceComponent: root.artShapeStyle === "icon" ? artShapeIconComponent : artShapeShapeComponent;
44+ Component {
45+ id: artShapeShapeComponent;
46+ UbuntuShape {
47+ source: artShapeSource;
48+ sourceFillMode: UbuntuShape.PreserveAspectCrop;
49+ radius: "medium";
50+ aspect: {
51+ switch (root.artShapeStyle) {
52+ case "inset": return UbuntuShape.Inset;
53+ case "shadow": return UbuntuShape.DropShadow;
54+ default:
55+ case "flat": return UbuntuShape.Flat;
56+ }
57+ }
58+ }
59+ }
60+ Component {
61+ id: artShapeIconComponent;
62+ ProportionalShape { source: artShapeSource; aspect: UbuntuShape.DropShadow; }
63+ }
64+ }
65+ readonly property real fixedArtShapeSizeAspect: (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) ? root.fixedArtShapeSize.width / root.fixedArtShapeSize.height : -1;
66+ readonly property real aspect: fixedArtShapeSizeAspect > 0 ? fixedArtShapeSizeAspect : 0.75;
67+ Component.onCompleted: { updateWidthHeightBindings(); }
68+ Connections { target: root; onFixedArtShapeSizeChanged: updateWidthHeightBindings(); }
69+ function updateWidthHeightBindings() {
70+ if (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) {
71+ width = root.fixedArtShapeSize.width;
72+ height = root.fixedArtShapeSize.height;
73+ } else {
74+ width = Qt.binding(function() { return image.status !== Image.Ready ? 0 : image.width });
75+ height = Qt.binding(function() { return image.status !== Image.Ready ? 0 : image.height });
76+ }
77+ }
78+ CroppedImageMinimumSourceSize {
79+ id: artImage;
80+ objectName: "artImage";
81+ source: artShapeLoader.cardArt;
82+ asynchronous: false;
83+ width: root.width;
84+ height: width / artShape.aspect;
85+ }
86+ }
87+ }
88+ }
89+readonly property int headerHeight: titleLabel.height + subtitleLabel.height + subtitleLabel.anchors.topMargin;
90+Label {
91+ id: titleLabel;
92+ objectName: "titleLabel";
93+ anchors { right: parent.right;
94+ left: parent.left;
95+ top: artShapeHolder.bottom;
96+ topMargin: units.gu(1);
97+ }
98+ elide: Text.ElideRight;
99+ fontSize: "small";
100+ wrapMode: Text.Wrap;
101+ maximumLineCount: 2;
102+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
103+ color: root.scopeStyle ? root.scopeStyle.foreground : theme.palette.normal.baseText;
104+ visible: showHeader ;
105+ width: undefined;
106+ text: root.title;
107+ font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
108+ horizontalAlignment: Text.AlignLeft;
109+ }
110+Label {
111+ id: subtitleLabel;
112+ objectName: "subtitleLabel";
113+ anchors { left: titleLabel.left;
114+ leftMargin: titleLabel.leftMargin;
115+ right: titleLabel.right;
116+ top: titleLabel.bottom;
117+ }
118+ anchors.topMargin: units.dp(2);
119+ elide: Text.ElideRight;
120+ maximumLineCount: 1;
121+ fontSize: "x-small";
122+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
123+ color: root.scopeStyle ? root.scopeStyle.foreground : theme.palette.normal.baseText;
124+ visible: titleLabel.visible && titleLabel.text;
125+ text: cardData && cardData["subtitle"] || "";
126+ font.weight: Font.Light;
127+ }
128+UbuntuShape {
129+ id: touchdown;
130+ objectName: "touchdown";
131+ anchors { fill: artShapeHolder }
132+ visible: root.artShapeStyle != "shadow" && root.artShapeStyle != "icon" && root.pressed;
133+ radius: "medium";
134+ borderSource: "radius_pressed.sci"
135+}
136+implicitHeight: subtitleLabel.y + subtitleLabel.height + units.gu(1);
137+}
tests/plugins/Dash/cardcreator/4.res+3−5
@@ -5,11 +5,9 @@ AbstractButton {
55 property string backgroundShapeStyle: "inset";
66 property real fontScale: 1.0;
77 property var scopeStyle: null;
8− property int titleAlignment: Text.AlignLeft;
98 property int fixedHeaderHeight: -1;
109 property size fixedArtShapeSize: Qt.size(-1, -1);
1110 readonly property string title: cardData && cardData["title"] || "";
12− property bool asynchronous: true;
1311 property bool showHeader: true;
1412 implicitWidth: childrenRect.width;
1513 enabled: true;
@@ -21,7 +19,7 @@ Row {
2119 objectName: "outerRow";
2220 property real margins: units.gu(1);
2321 spacing: margins;
24− height: root.fixedHeaderHeight != -1 ? root.fixedHeaderHeight : implicitHeight;
22+ height: root.fixedHeaderHeight;
2523 anchors { top: parent.top;
2624 topMargin: units.gu(1);
2725 left: parent.left;
@@ -33,7 +31,7 @@ data: [
3331Loader {
3432 id: mascotShapeLoader;
3533 objectName: "mascotShapeLoader";
36− asynchronous: root.asynchronous;
34+ asynchronous: true;
3735 active: mascotImage.status === Image.Ready;
3836 visible: showHeader && active && status == Loader.Ready;
3937 width: units.gu(6);
@@ -77,7 +75,7 @@ Label {
7775 width: undefined;
7876 text: root.title;
7977 font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
80− horizontalAlignment: root.titleAlignment;
78+ horizontalAlignment: Text.AlignLeft;
8179 }
8280,Label {
8381 id: subtitleLabel;
tests/plugins/Dash/cardcreator/4.res.cardcreatornew file+109
@@ -0,0 +1,109 @@
1+AbstractButton {
2+ id: root;
3+ property var cardData;
4+ property string artShapeStyle: "inset";
5+ property string backgroundShapeStyle: "inset";
6+ property real fontScale: 1.0;
7+ property var scopeStyle: null;
8+ property size fixedArtShapeSize: Qt.size(-1, -1);
9+ readonly property string title: cardData && cardData["title"] || "";
10+ property bool showHeader: true;
11+ implicitWidth: childrenRect.width;
12+ enabled: true;
13+
14+readonly property size artShapeSize: Qt.size(-1, -1);
15+readonly property int headerHeight: row.height;
16+Row {
17+ id: row;
18+ objectName: "outerRow";
19+ property real margins: units.gu(1);
20+ spacing: margins;
21+ anchors { top: parent.top;
22+ topMargin: units.gu(1);
23+ left: parent.left;
24+}
25+ anchors.right: parent.right;
26+ anchors.margins: margins;
27+ anchors.rightMargin: 0;
28+data: [
29+Loader {
30+ id: mascotShapeLoader;
31+ objectName: "mascotShapeLoader";
32+ asynchronous: false;
33+ active: mascotImage.status === Image.Ready;
34+ visible: showHeader && active && status == Loader.Ready;
35+ width: units.gu(6);
36+ height: units.gu(5.625);
37+ sourceComponent: UbuntuShape { image: mascotImage }
38+ anchors { verticalCenter: parent.verticalCenter; }
39+ }
40+
41+,CroppedImageMinimumSourceSize {
42+ id: mascotImage;
43+ objectName: "mascotImage";
44+ anchors { verticalCenter: parent.verticalCenter; }
45+ source: cardData && cardData["mascot"] || "";
46+ width: units.gu(6);
47+ height: units.gu(5.625);
48+ horizontalAlignment: Image.AlignHCenter;
49+ verticalAlignment: Image.AlignVCenter;
50+ visible: false;
51+ }
52+
53+,Item {
54+ id: headerTitleContainer;
55+ anchors { verticalCenter: parent.verticalCenter; }
56+ width: parent.width - x;
57+ implicitHeight: titleLabel.height + subtitleLabel.height;
58+ data: [
59+Label {
60+ id: titleLabel;
61+ objectName: "titleLabel";
62+ anchors { right: parent.right;
63+ rightMargin: units.gu(1);
64+ left: parent.left;
65+ top: parent.top; }
66+ elide: Text.ElideRight;
67+ fontSize: "small";
68+ wrapMode: Text.Wrap;
69+ maximumLineCount: 2;
70+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
71+ color: root.scopeStyle ? root.scopeStyle.foreground : theme.palette.normal.baseText;
72+ visible: showHeader ;
73+ width: undefined;
74+ text: root.title;
75+ font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
76+ horizontalAlignment: Text.AlignLeft;
77+ }
78+,Label {
79+ id: subtitleLabel;
80+ objectName: "subtitleLabel";
81+ anchors { right: parent.right;
82+ left: parent.left;
83+ rightMargin: units.gu(1);
84+ top: titleLabel.bottom;
85+ }
86+ anchors.topMargin: units.dp(2);
87+ elide: Text.ElideRight;
88+ maximumLineCount: 1;
89+ fontSize: "x-small";
90+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
91+ color: root.scopeStyle ? root.scopeStyle.foreground : theme.palette.normal.baseText;
92+ visible: titleLabel.visible && titleLabel.text;
93+ text: cardData && cardData["subtitle"] || "";
94+ font.weight: Font.Light;
95+ }
96+]
97+}
98+]
99+}
100+UbuntuShape {
101+ id: touchdown;
102+ objectName: "touchdown";
103+ anchors { fill: root }
104+ visible: root.artShapeStyle != "shadow" && root.artShapeStyle != "icon" && root.pressed;
105+ radius: "medium";
106+ borderSource: "radius_pressed.sci"
107+}
108+implicitHeight: row.y + row.height + units.gu(1);
109+}
tests/plugins/Dash/cardcreator/5.res+5−7
@@ -5,11 +5,9 @@ AbstractButton {
55 property string backgroundShapeStyle: "inset";
66 property real fontScale: 1.0;
77 property var scopeStyle: null;
8− property int titleAlignment: Text.AlignLeft;
98 property int fixedHeaderHeight: -1;
109 property size fixedArtShapeSize: Qt.size(-1, -1);
1110 readonly property string title: cardData && cardData["title"] || "";
12− property bool asynchronous: true;
1311 property bool showHeader: true;
1412 implicitWidth: childrenRect.width;
1513 enabled: false;
@@ -25,7 +23,7 @@ Item {
2523 objectName: "artShapeLoader";
2624 readonly property string cardArt: cardData && cardData["art"] || "";
2725 active: cardArt != "";
28− asynchronous: root.asynchronous;
26+ asynchronous: true;
2927 visible: status == Loader.Ready;
3028 sourceComponent: Item {
3129 id: artShape;
@@ -82,7 +80,7 @@ Item {
8280 id: artImage;
8381 objectName: "artImage";
8482 source: artShapeLoader.cardArt;
85− asynchronous: root.asynchronous;
83+ asynchronous: true;
8684 width: root.width;
8785 height: width / artShape.aspect;
8886 }
@@ -91,10 +89,10 @@ Item {
9189 }
9290Loader {
9391 id: overlayLoader;
94− readonly property real overlayHeight: (fixedHeaderHeight > 0 ? fixedHeaderHeight : headerHeight) + units.gu(2);
92+ readonly property real overlayHeight: root.fixedHeaderHeight + units.gu(2);
9593 anchors.fill: artShapeHolder;
9694 active: artShapeLoader.active && artShapeLoader.item && artShapeLoader.item.image.status === Image.Ready || false;
97− asynchronous: root.asynchronous;
95+ asynchronous: true;
9896 visible: showHeader && status == Loader.Ready;
9997 sourceComponent: UbuntuShapeOverlay {
10098 id: overlay;
@@ -126,7 +124,7 @@ Label {
126124 width: undefined;
127125 text: root.title;
128126 font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
129− horizontalAlignment: root.titleAlignment;
127+ horizontalAlignment: Text.AlignLeft;
130128 }
131129Label {
132130 id: subtitleLabel;
tests/plugins/Dash/cardcreator/5.res.cardcreatornew file+156
@@ -0,0 +1,156 @@
1+AbstractButton {
2+ id: root;
3+ property var cardData;
4+ property string artShapeStyle: "inset";
5+ property string backgroundShapeStyle: "inset";
6+ property real fontScale: 1.0;
7+ property var scopeStyle: null;
8+ property size fixedArtShapeSize: Qt.size(-1, -1);
9+ readonly property string title: cardData && cardData["title"] || "";
10+ property bool showHeader: true;
11+ implicitWidth: childrenRect.width;
12+ enabled: false;
13+
14+readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);
15+Item {
16+ id: artShapeHolder;
17+ height: root.fixedArtShapeSize.height > 0 ? root.fixedArtShapeSize.height : artShapeLoader.height;
18+ width: root.fixedArtShapeSize.width > 0 ? root.fixedArtShapeSize.width : artShapeLoader.width;
19+ anchors { horizontalCenter: parent.horizontalCenter; }
20+ Loader {
21+ id: artShapeLoader;
22+ objectName: "artShapeLoader";
23+ readonly property string cardArt: cardData && cardData["art"] || "";
24+ active: cardArt != "";
25+ asynchronous: false;
26+ visible: status == Loader.Ready;
27+ sourceComponent: Item {
28+ id: artShape;
29+ objectName: "artShape";
30+ visible: image.status == Image.Ready;
31+ readonly property alias image: artImage;
32+ ShaderEffectSource {
33+ id: artShapeSource;
34+ sourceItem: artImage;
35+ anchors.centerIn: parent;
36+ width: 1;
37+ height: 1;
38+ hideSource: false;
39+ }
40+ Loader {
41+ anchors.fill: parent;
42+ visible: false;
43+ sourceComponent: root.artShapeStyle === "icon" ? artShapeIconComponent : artShapeShapeComponent;
44+ Component {
45+ id: artShapeShapeComponent;
46+ UbuntuShape {
47+ source: artShapeSource;
48+ sourceFillMode: UbuntuShape.PreserveAspectCrop;
49+ radius: "medium";
50+ aspect: {
51+ switch (root.artShapeStyle) {
52+ case "inset": return UbuntuShape.Inset;
53+ case "shadow": return UbuntuShape.DropShadow;
54+ default:
55+ case "flat": return UbuntuShape.Flat;
56+ }
57+ }
58+ }
59+ }
60+ Component {
61+ id: artShapeIconComponent;
62+ ProportionalShape { source: artShapeSource; aspect: UbuntuShape.DropShadow; }
63+ }
64+ }
65+ readonly property real fixedArtShapeSizeAspect: (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) ? root.fixedArtShapeSize.width / root.fixedArtShapeSize.height : -1;
66+ readonly property real aspect: fixedArtShapeSizeAspect > 0 ? fixedArtShapeSizeAspect : 1;
67+ Component.onCompleted: { updateWidthHeightBindings(); }
68+ Connections { target: root; onFixedArtShapeSizeChanged: updateWidthHeightBindings(); }
69+ function updateWidthHeightBindings() {
70+ if (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) {
71+ width = root.fixedArtShapeSize.width;
72+ height = root.fixedArtShapeSize.height;
73+ } else {
74+ width = Qt.binding(function() { return image.status !== Image.Ready ? 0 : image.width });
75+ height = Qt.binding(function() { return image.status !== Image.Ready ? 0 : image.height });
76+ }
77+ }
78+ CroppedImageMinimumSourceSize {
79+ id: artImage;
80+ objectName: "artImage";
81+ source: artShapeLoader.cardArt;
82+ asynchronous: false;
83+ width: root.width;
84+ height: width / artShape.aspect;
85+ }
86+ }
87+ }
88+ }
89+Loader {
90+ id: overlayLoader;
91+ readonly property real overlayHeight: headerHeight + units.gu(2);
92+ anchors.fill: artShapeHolder;
93+ active: artShapeLoader.active && artShapeLoader.item && artShapeLoader.item.image.status === Image.Ready || false;
94+ asynchronous: false;
95+ visible: showHeader && status == Loader.Ready;
96+ sourceComponent: UbuntuShapeOverlay {
97+ id: overlay;
98+ property real luminance: Style.luminance(overlayColor);
99+ aspect: UbuntuShape.Flat;
100+ radius: "medium";
101+ overlayColor: cardData && cardData["overlayColor"] || "#99000000";
102+ overlayRect: Qt.rect(0, 1 - overlayLoader.overlayHeight / height, 1, 1);
103+ }
104+ }
105+readonly property int headerHeight: titleLabel.height + subtitleLabel.height + subtitleLabel.anchors.topMargin;
106+Label {
107+ id: titleLabel;
108+ objectName: "titleLabel";
109+ anchors { right: parent.right;
110+ rightMargin: units.gu(1);
111+ left: parent.left;
112+ leftMargin: units.gu(1);
113+ top: overlayLoader.top;
114+ topMargin: units.gu(1) + overlayLoader.height - overlayLoader.overlayHeight;
115+ }
116+ elide: Text.ElideRight;
117+ fontSize: "small";
118+ wrapMode: Text.Wrap;
119+ maximumLineCount: 2;
120+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
121+ color: root.scopeStyle && overlayLoader.item ? root.scopeStyle.getTextColor(overlayLoader.item.luminance) : (overlayLoader.item && overlayLoader.item.luminance > 0.7 ? theme.palette.normal.baseText : "white");
122+ visible: showHeader && overlayLoader.active;
123+ width: undefined;
124+ text: root.title;
125+ font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
126+ horizontalAlignment: Text.AlignLeft;
127+ }
128+Label {
129+ id: subtitleLabel;
130+ objectName: "subtitleLabel";
131+ anchors { left: titleLabel.left;
132+ leftMargin: titleLabel.leftMargin;
133+ rightMargin: units.gu(1);
134+ right: titleLabel.right;
135+ top: titleLabel.bottom;
136+ }
137+ anchors.topMargin: units.dp(2);
138+ elide: Text.ElideRight;
139+ maximumLineCount: 1;
140+ fontSize: "x-small";
141+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
142+ color: root.scopeStyle && overlayLoader.item ? root.scopeStyle.getTextColor(overlayLoader.item.luminance) : (overlayLoader.item && overlayLoader.item.luminance > 0.7 ? theme.palette.normal.baseText : "white");
143+ visible: titleLabel.visible && titleLabel.text;
144+ text: cardData && cardData["subtitle"] || "";
145+ font.weight: Font.Light;
146+ }
147+UbuntuShape {
148+ id: touchdown;
149+ objectName: "touchdown";
150+ anchors { fill: artShapeHolder }
151+ visible: root.artShapeStyle != "shadow" && root.artShapeStyle != "icon" && root.pressed;
152+ radius: "medium";
153+ borderSource: "radius_pressed.sci"
154+}
155+implicitHeight: artShapeHolder.height;
156+}
tests/plugins/Dash/cardcreator/6.res+2−4
@@ -5,11 +5,9 @@ AbstractButton {
55 property string backgroundShapeStyle: "inset";
66 property real fontScale: 1.0;
77 property var scopeStyle: null;
8− property int titleAlignment: Text.AlignLeft;
98 property int fixedHeaderHeight: -1;
109 property size fixedArtShapeSize: Qt.size(-1, -1);
1110 readonly property string title: cardData && cardData["title"] || "";
12− property bool asynchronous: true;
1311 property bool showHeader: true;
1412 implicitWidth: childrenRect.width;
1513 enabled: true;
@@ -18,7 +16,7 @@ Loader {
1816 id: backgroundLoader;
1917 objectName: "backgroundLoader";
2018 anchors.fill: parent;
21− asynchronous: root.asynchronous;
19+ asynchronous: true;
2220 visible: status == Loader.Ready;
2321 sourceComponent: UbuntuShape {
2422 objectName: "background";
@@ -81,7 +79,7 @@ Item {
8179 width: undefined;
8280 text: root.title;
8381 font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
84− horizontalAlignment: root.titleAlignment;
82+ horizontalAlignment: Text.AlignLeft;
8583 }
8684,Label {
8785 id: subtitleLabel;
tests/plugins/Dash/cardcreator/6.res.cardcreatornew file+126
@@ -0,0 +1,126 @@
1+AbstractButton {
2+ id: root;
3+ property var cardData;
4+ property string artShapeStyle: "inset";
5+ property string backgroundShapeStyle: "inset";
6+ property real fontScale: 1.0;
7+ property var scopeStyle: null;
8+ property size fixedArtShapeSize: Qt.size(-1, -1);
9+ readonly property string title: cardData && cardData["title"] || "";
10+ property bool showHeader: true;
11+ implicitWidth: childrenRect.width;
12+ enabled: true;
13+
14+Loader {
15+ id: backgroundLoader;
16+ objectName: "backgroundLoader";
17+ anchors.fill: parent;
18+ asynchronous: false;
19+ visible: status == Loader.Ready;
20+ sourceComponent: UbuntuShape {
21+ objectName: "background";
22+ radius: "medium";
23+ aspect: {
24+ switch (root.backgroundShapeStyle) {
25+ case "inset": return UbuntuShape.Inset;
26+ case "shadow": return UbuntuShape.DropShadow;
27+ default:
28+ case "flat": return UbuntuShape.Flat;
29+ }
30+ }
31+ backgroundColor: getColor(0) || "white";
32+ secondaryBackgroundColor: getColor(1) || backgroundColor;
33+ backgroundMode: UbuntuShape.VerticalGradient;
34+ anchors.fill: parent;
35+ source: backgroundImage.source ? backgroundImage : null;
36+ property real luminance: Style.luminance(backgroundColor);
37+ property Image backgroundImage: Image {
38+ objectName: "backgroundImage";
39+ source: {
40+ if (cardData && typeof cardData["background"] === "string") return cardData["background"];
41+ else return decodeURI("http://assets.ubuntu.com/sites/ubuntu/latest/u/img/logos/logo-ubuntu-grey.png");
42+ }
43+ }
44+ function getColor(index) {
45+ if (cardData && typeof cardData["background"] === "object"
46+ && (cardData["background"]["type"] === "color" || cardData["background"]["type"] === "gradient")) {
47+ return cardData["background"]["elements"][index];
48+ } else return index === 0 ? undefined : undefined;
49+ }
50+ }
51+ }
52+readonly property size artShapeSize: Qt.size(-1, -1);
53+readonly property int headerHeight: titleLabel.height + subtitleLabel.height + subtitleLabel.anchors.topMargin;
54+Item {
55+ id: headerTitleContainer;
56+ anchors { right: parent.right; left: parent.left;
57+ top: parent.top;
58+ topMargin: units.gu(1);
59+ leftMargin: units.gu(1);
60+ }
61+ width: parent.width - x;
62+ implicitHeight: titleLabel.height + subtitleLabel.height;
63+ data: [
64+ Label {
65+ id: titleLabel;
66+ objectName: "titleLabel";
67+ anchors { right: emblemIcon.left;
68+ rightMargin: emblemIcon.width > 0 ? units.gu(0.5) : 0;
69+ left: parent.left;
70+ top: parent.top; }
71+ elide: Text.ElideRight;
72+ fontSize: "small";
73+ wrapMode: Text.Wrap;
74+ maximumLineCount: 2;
75+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
76+ color: backgroundLoader.active && backgroundLoader.item && root.scopeStyle ? root.scopeStyle.getTextColor(backgroundLoader.item.luminance) : (backgroundLoader.item && backgroundLoader.item.luminance > 0.7 ? theme.palette.normal.baseText : "white");
77+ visible: showHeader ;
78+ width: undefined;
79+ text: root.title;
80+ font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
81+ horizontalAlignment: Text.AlignLeft;
82+ }
83+,Label {
84+ id: subtitleLabel;
85+ objectName: "subtitleLabel";
86+ anchors { right: parent.right;
87+ left: parent.left;
88+ rightMargin: units.gu(1);
89+ top: titleLabel.bottom;
90+ }
91+ anchors.topMargin: units.dp(2);
92+ elide: Text.ElideRight;
93+ maximumLineCount: 1;
94+ fontSize: "x-small";
95+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
96+ color: backgroundLoader.active && backgroundLoader.item && root.scopeStyle ? root.scopeStyle.getTextColor(backgroundLoader.item.luminance) : (backgroundLoader.item && backgroundLoader.item.luminance > 0.7 ? theme.palette.normal.baseText : "white");
97+ visible: titleLabel.visible && titleLabel.text;
98+ text: cardData && cardData["subtitle"] || "";
99+ font.weight: Font.Light;
100+ }
101+,Icon {
102+ id: emblemIcon;
103+ objectName: "emblemIcon";
104+ anchors {
105+ bottom: titleLabel.baseline;
106+ right: parent.right;
107+ rightMargin: units.gu(1);
108+ }
109+ source: cardData && cardData["emblem"] || "";
110+ color: backgroundLoader.active && backgroundLoader.item && root.scopeStyle ? root.scopeStyle.getTextColor(backgroundLoader.item.luminance) : (backgroundLoader.item && backgroundLoader.item.luminance > 0.7 ? theme.palette.normal.baseText : "white");
111+ height: source != "" ? titleLabel.font.pixelSize : 0;
112+ width: implicitWidth > 0 && implicitHeight > 0 ? (implicitWidth / implicitHeight * height) : implicitWidth;
113+ }
114+
115+ ]
116+ }
117+UbuntuShape {
118+ id: touchdown;
119+ objectName: "touchdown";
120+ anchors { fill: backgroundLoader }
121+ visible: root.artShapeStyle != "shadow" && root.artShapeStyle != "icon" && root.pressed;
122+ radius: "medium";
123+ borderSource: "radius_pressed.sci"
124+}
125+implicitHeight: headerTitleContainer.y + headerTitleContainer.height + units.gu(1);
126+}
tests/plugins/Dash/cardcreator/7.res+3−5
@@ -5,11 +5,9 @@ AbstractButton {
55 property string backgroundShapeStyle: "inset";
66 property real fontScale: 1.0;
77 property var scopeStyle: null;
8− property int titleAlignment: Text.AlignLeft;
98 property int fixedHeaderHeight: -1;
109 property size fixedArtShapeSize: Qt.size(-1, -1);
1110 readonly property string title: cardData && cardData["title"] || "";
12− property bool asynchronous: true;
1311 property bool showHeader: true;
1412 implicitWidth: childrenRect.width;
1513 enabled: true;
@@ -18,7 +16,7 @@ Loader {
1816 id: backgroundLoader;
1917 objectName: "backgroundLoader";
2018 anchors.fill: parent;
21− asynchronous: root.asynchronous;
19+ asynchronous: true;
2220 visible: status == Loader.Ready;
2321 sourceComponent: UbuntuShape {
2422 objectName: "background";
@@ -59,7 +57,7 @@ Row {
5957 objectName: "outerRow";
6058 property real margins: units.gu(1);
6159 spacing: margins;
62− height: root.fixedHeaderHeight != -1 ? root.fixedHeaderHeight : implicitHeight;
60+ height: root.fixedHeaderHeight;
6361 anchors { top: parent.top;
6462 topMargin: units.gu(1);
6563 left: parent.left;
@@ -103,7 +101,7 @@ CroppedImageMinimumSourceSize {
103101 width: undefined;
104102 text: root.title;
105103 font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
106− horizontalAlignment: root.titleAlignment;
104+ horizontalAlignment: Text.AlignLeft;
107105 }
108106,Label {
109107 id: subtitleLabel;
tests/plugins/Dash/cardcreator/7.res.cardcreatornew file+149
@@ -0,0 +1,149 @@
1+AbstractButton {
2+ id: root;
3+ property var cardData;
4+ property string artShapeStyle: "inset";
5+ property string backgroundShapeStyle: "inset";
6+ property real fontScale: 1.0;
7+ property var scopeStyle: null;
8+ property size fixedArtShapeSize: Qt.size(-1, -1);
9+ readonly property string title: cardData && cardData["title"] || "";
10+ property bool showHeader: true;
11+ implicitWidth: childrenRect.width;
12+ enabled: true;
13+
14+Loader {
15+ id: backgroundLoader;
16+ objectName: "backgroundLoader";
17+ anchors.fill: parent;
18+ asynchronous: false;
19+ visible: status == Loader.Ready;
20+ sourceComponent: UbuntuShape {
21+ objectName: "background";
22+ radius: "medium";
23+ aspect: {
24+ switch (root.backgroundShapeStyle) {
25+ case "inset": return UbuntuShape.Inset;
26+ case "shadow": return UbuntuShape.DropShadow;
27+ default:
28+ case "flat": return UbuntuShape.Flat;
29+ }
30+ }
31+ backgroundColor: getColor(0) || "white";
32+ secondaryBackgroundColor: getColor(1) || backgroundColor;
33+ backgroundMode: UbuntuShape.VerticalGradient;
34+ anchors.fill: parent;
35+ source: backgroundImage.source ? backgroundImage : null;
36+ property real luminance: Style.luminance(backgroundColor);
37+ property Image backgroundImage: Image {
38+ objectName: "backgroundImage";
39+ source: {
40+ if (cardData && typeof cardData["background"] === "string") return cardData["background"];
41+ else return "";
42+ }
43+ }
44+ function getColor(index) {
45+ if (cardData && typeof cardData["background"] === "object"
46+ && (cardData["background"]["type"] === "color" || cardData["background"]["type"] === "gradient")) {
47+ return cardData["background"]["elements"][index];
48+ } else return index === 0 ? "#E9E9E9" : "#E9AAE9";
49+ }
50+ }
51+ }
52+readonly property size artShapeSize: Qt.size(-1, -1);
53+readonly property int headerHeight: row.height;
54+Row {
55+ id: row;
56+ objectName: "outerRow";
57+ property real margins: units.gu(1);
58+ spacing: margins;
59+ anchors { top: parent.top;
60+ topMargin: units.gu(1);
61+ left: parent.left;
62+ }
63+ anchors.right: parent.right;
64+ anchors.margins: margins;
65+ anchors.rightMargin: 0;
66+ data: [
67+CroppedImageMinimumSourceSize {
68+ id: mascotImage;
69+ objectName: "mascotImage";
70+ anchors { verticalCenter: parent.verticalCenter; }
71+ source: cardData && cardData["mascot"] || "";
72+ width: units.gu(6);
73+ height: units.gu(5.625);
74+ horizontalAlignment: Image.AlignHCenter;
75+ verticalAlignment: Image.AlignVCenter;
76+ visible: showHeader;
77+ }
78+
79+,Item {
80+ id: headerTitleContainer;
81+ anchors { verticalCenter: parent.verticalCenter; }
82+ width: parent.width - x;
83+ implicitHeight: titleLabel.height + subtitleLabel.height + attributesRow.height;
84+ data: [
85+ Label {
86+ id: titleLabel;
87+ objectName: "titleLabel";
88+ anchors { right: parent.right;
89+ rightMargin: units.gu(1);
90+ left: parent.left;
91+ top: parent.top; }
92+ elide: Text.ElideRight;
93+ fontSize: "small";
94+ wrapMode: Text.Wrap;
95+ maximumLineCount: 2;
96+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
97+ color: backgroundLoader.active && backgroundLoader.item && root.scopeStyle ? root.scopeStyle.getTextColor(backgroundLoader.item.luminance) : (backgroundLoader.item && backgroundLoader.item.luminance > 0.7 ? theme.palette.normal.baseText : "white");
98+ visible: showHeader ;
99+ width: undefined;
100+ text: root.title;
101+ font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
102+ horizontalAlignment: Text.AlignLeft;
103+ }
104+,Label {
105+ id: subtitleLabel;
106+ objectName: "subtitleLabel";
107+ anchors { right: parent.right;
108+ left: parent.left;
109+ rightMargin: units.gu(1);
110+ top: titleLabel.bottom;
111+ }
112+ anchors.topMargin: units.dp(2);
113+ elide: Text.ElideRight;
114+ maximumLineCount: 1;
115+ fontSize: "x-small";
116+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
117+ color: backgroundLoader.active && backgroundLoader.item && root.scopeStyle ? root.scopeStyle.getTextColor(backgroundLoader.item.luminance) : (backgroundLoader.item && backgroundLoader.item.luminance > 0.7 ? theme.palette.normal.baseText : "white");
118+ visible: titleLabel.visible && titleLabel.text;
119+ text: cardData && cardData["subtitle"] || "";
120+ font.weight: Font.Light;
121+ }
122+,CardAttributes {
123+ id: attributesRow;
124+ objectName: "attributesRow";
125+ anchors { right: parent.right;
126+ left: parent.left;
127+ rightMargin: units.gu(1);
128+ top: subtitleLabel.bottom;
129+ }
130+ color: backgroundLoader.active && backgroundLoader.item && root.scopeStyle ? root.scopeStyle.getTextColor(backgroundLoader.item.luminance) : (backgroundLoader.item && backgroundLoader.item.luminance > 0.7 ? theme.palette.normal.baseText : "white");
131+ fontScale: root.fontScale;
132+ model: cardData && cardData["attributes"];
133+ }
134+
135+ ]
136+ }
137+
138+ ]
139+ }
140+UbuntuShape {
141+ id: touchdown;
142+ objectName: "touchdown";
143+ anchors { fill: backgroundLoader }
144+ visible: root.artShapeStyle != "shadow" && root.artShapeStyle != "icon" && root.pressed;
145+ radius: "medium";
146+ borderSource: "radius_pressed.sci"
147+}
148+implicitHeight: row.y + row.height + units.gu(1);
149+}
tests/plugins/Dash/cardcreator/8.res+3−5
@@ -5,11 +5,9 @@ AbstractButton {
55 property string backgroundShapeStyle: "inset";
66 property real fontScale: 1.0;
77 property var scopeStyle: null;
8− property int titleAlignment: Text.AlignLeft;
98 property int fixedHeaderHeight: -1;
109 property size fixedArtShapeSize: Qt.size(-1, -1);
1110 readonly property string title: cardData && cardData["title"] || "";
12− property bool asynchronous: true;
1311 property bool showHeader: true;
1412 implicitWidth: childrenRect.width;
1513 enabled: true;
@@ -18,7 +16,7 @@ Loader {
1816 id: backgroundLoader;
1917 objectName: "backgroundLoader";
2018 anchors.fill: parent;
21− asynchronous: root.asynchronous;
19+ asynchronous: true;
2220 visible: status == Loader.Ready;
2321 sourceComponent: UbuntuShape {
2422 objectName: "background";
@@ -59,7 +57,7 @@ Row {
5957 objectName: "outerRow";
6058 property real margins: units.gu(1);
6159 spacing: margins;
62− height: root.fixedHeaderHeight != -1 ? root.fixedHeaderHeight : implicitHeight;
60+ height: root.fixedHeaderHeight;
6361 anchors { top: parent.top;
6462 topMargin: units.gu(1);
6563left: parent.left;
@@ -94,7 +92,7 @@ left: parent.left;
9492 width: parent.width - x;
9593 text: root.title;
9694 font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
97− horizontalAlignment: root.titleAlignment;
95+ horizontalAlignment: Text.AlignLeft;
9896 }
9997
10098 ]
tests/plugins/Dash/cardcreator/8.res.cardcreatornew file+107
@@ -0,0 +1,107 @@
1+AbstractButton {
2+ id: root;
3+ property var cardData;
4+ property string artShapeStyle: "inset";
5+ property string backgroundShapeStyle: "inset";
6+ property real fontScale: 1.0;
7+ property var scopeStyle: null;
8+ property size fixedArtShapeSize: Qt.size(-1, -1);
9+ readonly property string title: cardData && cardData["title"] || "";
10+ property bool showHeader: true;
11+ implicitWidth: childrenRect.width;
12+ enabled: true;
13+
14+Loader {
15+ id: backgroundLoader;
16+ objectName: "backgroundLoader";
17+ anchors.fill: parent;
18+ asynchronous: false;
19+ visible: status == Loader.Ready;
20+ sourceComponent: UbuntuShape {
21+ objectName: "background";
22+ radius: "medium";
23+ aspect: {
24+ switch (root.backgroundShapeStyle) {
25+ case "inset": return UbuntuShape.Inset;
26+ case "shadow": return UbuntuShape.DropShadow;
27+ default:
28+ case "flat": return UbuntuShape.Flat;
29+ }
30+ }
31+ backgroundColor: getColor(0) || "white";
32+ secondaryBackgroundColor: getColor(1) || backgroundColor;
33+ backgroundMode: UbuntuShape.VerticalGradient;
34+ anchors.fill: parent;
35+ source: backgroundImage.source ? backgroundImage : null;
36+ property real luminance: Style.luminance(backgroundColor);
37+ property Image backgroundImage: Image {
38+ objectName: "backgroundImage";
39+ source: {
40+ if (cardData && typeof cardData["background"] === "string") return cardData["background"];
41+ else return "";
42+ }
43+ }
44+ function getColor(index) {
45+ if (cardData && typeof cardData["background"] === "object"
46+ && (cardData["background"]["type"] === "color" || cardData["background"]["type"] === "gradient")) {
47+ return cardData["background"]["elements"][index];
48+ } else return index === 0 ? "#E9E9E9" : "#E9AAE9";
49+ }
50+ }
51+ }
52+readonly property size artShapeSize: Qt.size(-1, -1);
53+readonly property int headerHeight: row.height;
54+Row {
55+ id: row;
56+ objectName: "outerRow";
57+ property real margins: units.gu(1);
58+ spacing: margins;
59+ anchors { top: parent.top;
60+ topMargin: units.gu(1);
61+left: parent.left;
62+ }
63+ anchors.right: parent.right;
64+ anchors.margins: margins;
65+ anchors.rightMargin: 0;
66+ data: [
67+ CroppedImageMinimumSourceSize {
68+ id: mascotImage;
69+ objectName: "mascotImage";
70+ anchors { verticalCenter: parent.verticalCenter; }
71+ source: cardData && cardData["mascot"] || "";
72+ width: units.gu(6);
73+ height: units.gu(5.625);
74+ horizontalAlignment: Image.AlignHCenter;
75+ verticalAlignment: Image.AlignVCenter;
76+ visible: showHeader;
77+ }
78+,Label {
79+ id: titleLabel;
80+ objectName: "titleLabel";
81+ anchors { verticalCenter: parent.verticalCenter;
82+ }
83+ elide: Text.ElideRight;
84+ fontSize: "small";
85+ wrapMode: Text.Wrap;
86+ maximumLineCount: 2;
87+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
88+ color: backgroundLoader.active && backgroundLoader.item && root.scopeStyle ? root.scopeStyle.getTextColor(backgroundLoader.item.luminance) : (backgroundLoader.item && backgroundLoader.item.luminance > 0.7 ? theme.palette.normal.baseText : "white");
89+ visible: showHeader ;
90+ width: parent.width - x;
91+ text: root.title;
92+ font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
93+ horizontalAlignment: Text.AlignLeft;
94+ }
95+
96+ ]
97+ }
98+UbuntuShape {
99+ id: touchdown;
100+ objectName: "touchdown";
101+ anchors { fill: backgroundLoader }
102+ visible: root.artShapeStyle != "shadow" && root.artShapeStyle != "icon" && root.pressed;
103+ radius: "medium";
104+ borderSource: "radius_pressed.sci"
105+}
106+implicitHeight: row.y + row.height + units.gu(1);
107+}
tests/plugins/Dash/cardcreator/9.res+4−5
@@ -5,11 +5,9 @@ AbstractButton {
55 property string backgroundShapeStyle: "inset";
66 property real fontScale: 1.0;
77 property var scopeStyle: null;
8− property int titleAlignment: Text.AlignLeft;
98 property int fixedHeaderHeight: -1;
109 property size fixedArtShapeSize: Qt.size(-1, -1);
1110 readonly property string title: cardData && cardData["title"] || "";
12− property bool asynchronous: true;
1311 property bool showHeader: true;
1412 implicitWidth: childrenRect.width;
1513 enabled: true;
@@ -36,7 +34,7 @@ top: parent.top;
3634 width: undefined;
3735 text: root.title;
3836 font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
39− horizontalAlignment: root.titleAlignment;
37+ horizontalAlignment: Text.AlignLeft;
4038 }
4139Label {
4240 id: subtitleLabel;
@@ -72,7 +70,7 @@ CardAudioProgress {
7270 id: audioButton;
7371 anchors.fill: undefined;
7472 width: height;
75− height: (root.fixedHeaderHeight > 0 ? root.fixedHeaderHeight : headerHeight) + 2 * units.gu(1);
73+ height: root.fixedHeaderHeight + 2 * units.gu(1);
7674 readonly property url source: (cardData["quickPreviewData"] && cardData["quickPreviewData"]["uri"]) || "";
7775 UbuntuShape {
7876 anchors.fill: parent;
@@ -93,7 +91,8 @@ CardAudioProgress {
9391 opacity: 0.9;
9492 name: DashAudioPlayer.playing && AudioUrlComparer.compare(parent.source, DashAudioPlayer.currentSource) ? "media-playback-pause" : "media-playback-start";
9593 color: "white";
96− }
94+ asynchronous: true;
95+ }
9796 onClicked: {
9897 if (AudioUrlComparer.compare(source, DashAudioPlayer.currentSource)) {
9998 if (DashAudioPlayer.playing) {
tests/plugins/Dash/cardcreator/9.res.cardcreatornew file+119
@@ -0,0 +1,119 @@
1+AbstractButton {
2+ id: root;
3+ property var cardData;
4+ property string artShapeStyle: "inset";
5+ property string backgroundShapeStyle: "inset";
6+ property real fontScale: 1.0;
7+ property var scopeStyle: null;
8+ property size fixedArtShapeSize: Qt.size(-1, -1);
9+ readonly property string title: cardData && cardData["title"] || "";
10+ property bool showHeader: true;
11+ implicitWidth: childrenRect.width;
12+ enabled: true;
13+
14+readonly property size artShapeSize: Qt.size(-1, -1);
15+readonly property int headerHeight: titleLabel.height + subtitleLabel.height + subtitleLabel.anchors.topMargin + audioProgressBar.height + audioProgressBar.anchors.topMargin;
16+Label {
17+ id: titleLabel;
18+ objectName: "titleLabel";
19+ anchors { right: parent.right;
20+rightMargin: units.gu(1);
21+left: audioButton.right;
22+ leftMargin: units.gu(1);
23+top: parent.top;
24+ topMargin: units.gu(1);
25+ }
26+ elide: Text.ElideRight;
27+ fontSize: "small";
28+ wrapMode: Text.Wrap;
29+ maximumLineCount: 2;
30+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
31+ color: root.scopeStyle ? root.scopeStyle.foreground : theme.palette.normal.baseText;
32+ visible: showHeader ;
33+ width: undefined;
34+ text: root.title;
35+ font.weight: cardData && cardData["subtitle"] ? Font.DemiBold : Font.Normal;
36+ horizontalAlignment: Text.AlignLeft;
37+ }
38+Label {
39+ id: subtitleLabel;
40+ objectName: "subtitleLabel";
41+ anchors { left: titleLabel.left;
42+ leftMargin: titleLabel.leftMargin;
43+rightMargin: units.gu(1);
44+right: titleLabel.right;
45+top: titleLabel.bottom;
46+ }
47+ anchors.topMargin: units.dp(2);
48+ elide: Text.ElideRight;
49+ maximumLineCount: 1;
50+ fontSize: "x-small";
51+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
52+ color: root.scopeStyle ? root.scopeStyle.foreground : theme.palette.normal.baseText;
53+ visible: titleLabel.visible && titleLabel.text;
54+ text: cardData && cardData["subtitle"] || "";
55+ font.weight: Font.Light;
56+ }
57+CardAudioProgress {
58+ id: audioProgressBar;
59+ duration: (cardData["quickPreviewData"] && cardData["quickPreviewData"]["duration"]) || 0;
60+ source: (cardData["quickPreviewData"] && cardData["quickPreviewData"]["uri"]) || "";
61+ anchors {
62+ bottom: audioButton.bottom;
63+ left: audioButton.right;
64+ right: parent.right;
65+ margins: units.gu(1);
66+ }
67+ color: root.scopeStyle ? root.scopeStyle.foreground : theme.palette.normal.baseText;
68+ }AbstractButton {
69+ id: audioButton;
70+ anchors.fill: undefined;
71+ width: height;
72+ height: headerHeight + 2 * units.gu(1);
73+ readonly property url source: (cardData["quickPreviewData"] && cardData["quickPreviewData"]["uri"]) || "";
74+ UbuntuShape {
75+ anchors.fill: parent;
76+ visible: parent.pressed;
77+ radius: "medium";
78+ }
79+ Rectangle {
80+ color: Qt.rgba(0, 0, 0, 0.5);
81+ anchors.centerIn: parent;
82+ width: parent.width * 0.5;
83+ height: width;
84+ radius: width / 2;
85+ }
86+ Icon {
87+ anchors.centerIn: parent;
88+ width: parent.width * 0.3;
89+ height: width;
90+ opacity: 0.9;
91+ name: DashAudioPlayer.playing && AudioUrlComparer.compare(parent.source, DashAudioPlayer.currentSource) ? "media-playback-pause" : "media-playback-start";
92+ color: "white";
93+ asynchronous: false;
94+ }
95+ onClicked: {
96+ if (AudioUrlComparer.compare(source, DashAudioPlayer.currentSource)) {
97+ if (DashAudioPlayer.playing) {
98+ DashAudioPlayer.pause();
99+ } else {
100+ DashAudioPlayer.play();
101+ }
102+ } else {
103+ var playlist = (cardData["quickPreviewData"] && cardData["quickPreviewData"]["playlist"]) || null;
104+ DashAudioPlayer.playSource(source, playlist);
105+ }
106+ }
107+ onPressAndHold: {
108+ root.pressAndHold();
109+ }
110+ }UbuntuShape {
111+ id: touchdown;
112+ objectName: "touchdown";
113+ anchors { fill: root }
114+ visible: root.artShapeStyle != "shadow" && root.artShapeStyle != "icon" && root.pressed;
115+ radius: "medium";
116+ borderSource: "radius_pressed.sci"
117+ }
118+implicitHeight: audioButton.height;
119+}
tests/plugins/Dash/cardcreatortest.cpp+32−22
@@ -45,6 +45,30 @@ private Q_SLOTS:
4545 delete view;
4646 }
4747
48+ void compareFileContents(const QString &filePath, const QString cardStringResult)
49+ {
50+ QFile testResultFile(filePath);
51+ QVERIFY(testResultFile.open(QIODevice::ReadOnly));
52+ QTextStream ts2(&testResultFile);
53+
54+ // Record failed results to /tmp
55+ const QString executedResult = cardStringResult;
56+ QTemporaryFile tmpFile(QDir::tempPath() + QDir::separator() + "testCardCreatorFailedResultXXXXXX");
57+ tmpFile.open();
58+ tmpFile.setAutoRemove(false);
59+ tmpFile.write(executedResult.toUtf8().constData());
60+
61+ // Line by line comparison
62+ const QStringList expectedLines = ts2.readAll().trimmed().replace(QRegExp("\n\\s*\n"),"\n").split("\n");
63+ const QStringList cardStringResultLines = cardStringResult.trimmed().replace(QRegExp("\n\\s*\n"),"\n").split("\n");
64+ for (int i = 0; i < expectedLines.size(); ++i) {
65+ QCOMPARE(cardStringResultLines[i].simplified(), expectedLines[i].simplified());
66+ }
67+
68+ // Remove the result if it passed
69+ tmpFile.setAutoRemove(true);
70+ }
71+
4872 void testKnownCases()
4973 {
5074 const QString templateString("template: ");
@@ -68,33 +92,19 @@ private Q_SLOTS:
6892 const QString templateJSON = lines[0].mid(templateString.length());
6993 const QString componentsJSON = lines[1].mid(componentsString.length());
7094 const QString resultFileName = lines[2].mid(resultString.length());
71−
7295 QVariant cardStringResult;
73− QMetaObject::invokeMethod(view->rootObject(), "cardString", Q_RETURN_ARG(QVariant, cardStringResult), Q_ARG(QVariant, templateJSON), Q_ARG(QVariant, componentsJSON));
74−
75− QFile testResultFile(testDirPath + resultFileName);
76− QVERIFY(testResultFile.open(QIODevice::ReadOnly));
77− QTextStream ts2(&testResultFile);
96+ QVariant createCardComponentResult;
7897
79− // Record failed results to /tmp
80− const QString executedResult = cardStringResult.toString();
81− QTemporaryFile tmpFile(QDir::tempPath() + QDir::separator() + "testCardCreatorFailedResultXXXXXX");
82− tmpFile.open();
83− tmpFile.setAutoRemove(false);
84− tmpFile.write(executedResult.toUtf8().constData());
98+ QMetaObject::invokeMethod(view->rootObject(), "cardString", Q_RETURN_ARG(QVariant, cardStringResult), Q_ARG(QVariant, templateJSON), Q_ARG(QVariant, componentsJSON), Q_ARG(QVariant, false));
99+ compareFileContents(testDirPath + resultFileName, cardStringResult.toString());
85100
86− // Line by line comparison
87− const QStringList expectedLines = ts2.readAll().trimmed().replace(QRegExp("\n\\s*\n"),"\n").split("\n");
88− const QStringList cardStringResultLines = cardStringResult.toString().trimmed().replace(QRegExp("\n\\s*\n"),"\n").split("\n");
89− for (int i = 0; i < expectedLines.size(); ++i) {
90− QCOMPARE(cardStringResultLines[i].simplified(), expectedLines[i].simplified());
91− }
101+ QMetaObject::invokeMethod(view->rootObject(), "cardString", Q_RETURN_ARG(QVariant, cardStringResult), Q_ARG(QVariant, templateJSON), Q_ARG(QVariant, componentsJSON), Q_ARG(QVariant, true));
102+ compareFileContents(testDirPath + resultFileName + ".cardcreator", cardStringResult.toString());
92103
93− // Remove the result if it passed
94− tmpFile.setAutoRemove(true);
104+ QMetaObject::invokeMethod(view->rootObject(), "createCardComponent", Q_RETURN_ARG(QVariant, createCardComponentResult), Q_ARG(QVariant, templateJSON), Q_ARG(QVariant, componentsJSON), Q_ARG(QVariant, false));
105+ QVERIFY(createCardComponentResult.toBool());
95106
96− QVariant createCardComponentResult;
97− QMetaObject::invokeMethod(view->rootObject(), "createCardComponent", Q_RETURN_ARG(QVariant, createCardComponentResult), Q_ARG(QVariant, templateJSON), Q_ARG(QVariant, componentsJSON));
107+ QMetaObject::invokeMethod(view->rootObject(), "createCardComponent", Q_RETURN_ARG(QVariant, createCardComponentResult), Q_ARG(QVariant, templateJSON), Q_ARG(QVariant, componentsJSON), Q_ARG(QVariant, true));
98108 QVERIFY(createCardComponentResult.toBool());
99109 }
100110 }
tests/plugins/Dash/cardcreatortest.qml+4−4
@@ -19,11 +19,11 @@ import "../../../plugins/Dash/CardCreator.js" as CardCreator
1919
2020Item {
2121 id: root
22− function cardString(template, components) {
23− return CardCreator.cardString(JSON.parse(template), JSON.parse(components));
22+ function cardString(template, components, isCardCreator) {
23+ return CardCreator.cardString(JSON.parse(template), JSON.parse(components), isCardCreator);
2424 }
2525
26− function createCardComponent(template, components) {
27− return CardCreator.createCardComponent(root, JSON.parse(template), JSON.parse(components)) !== null;
26+ function createCardComponent(template, components, isCardCreator) {
27+ return CardCreator.createCardComponent(root, JSON.parse(template), JSON.parse(components), isCardCreator) !== null;
2828 }
2929}
tests/plugins/IntegratedLightDM/CMakeLists.txt+25
@@ -65,3 +65,28 @@ add_unity8_uitest(GreeterUsers GreeterUsersModelTestExec
6565 DEPENDS MockLightDM
6666 LIGHTDM
6767 )
68+
69+add_executable(GreeterIntegratedTestExec
70+ integrated.cpp
71+ )
72+qt5_use_modules(GreeterIntegratedTestExec Core DBus Quick Test)
73+target_link_libraries(GreeterIntegratedTestExec
74+ ${GLIB_LIBRARIES}
75+ integratedLightDMSession
76+ )
77+target_include_directories(GreeterIntegratedTestExec PUBLIC
78+ ${GLIB_INCLUDE_DIRS}
79+ ${CMAKE_SOURCE_DIR}/plugins/IntegratedLightDM/liblightdm
80+ )
81+add_unity8_unittest(GreeterIntegrated dbus-test-runner
82+ ENVIRONMENT LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/plugins/IntegratedLightDM/liblightdm/
83+ ARG_PREFIX "--parameter"
84+ ARGS
85+ --task $<TARGET_FILE:mock-server>
86+ --task-name server
87+ --ignore-return
88+ --task $<TARGET_FILE:GreeterIntegratedTestExec>
89+ --task-name client
90+ --wait-for org.freedesktop.Accounts
91+ DEPENDS integratedLightDMSession
92+ )
tests/plugins/IntegratedLightDM/integrated.cppnew file+91
@@ -0,0 +1,91 @@
1+/*
2+ * Copyright (C) 2016 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include "UsersModel.h"
18+
19+#include <glib.h>
20+#include <QDBusInterface>
21+#include <QDBusReply>
22+#include <QtTest>
23+
24+class GreeterIntegratedTest : public QObject
25+{
26+ Q_OBJECT
27+
28+private Q_SLOTS:
29+
30+ void init()
31+ {
32+ m_accounts = new QDBusInterface(QStringLiteral("org.freedesktop.Accounts"),
33+ QStringLiteral("/org/freedesktop/Accounts"),
34+ QStringLiteral("org.freedesktop.Accounts"),
35+ QDBusConnection::sessionBus());
36+ QDBusReply<bool> addReply = m_accounts->call(QStringLiteral("AddUser"),
37+ g_get_user_name());
38+ QVERIFY(addReply.isValid());
39+ QCOMPARE(addReply.value(), true);
40+
41+ m_user = new QDBusInterface(QStringLiteral("org.freedesktop.Accounts"),
42+ QStringLiteral("/%1").arg(g_get_user_name()),
43+ QStringLiteral("org.freedesktop.DBus.Properties"),
44+ QDBusConnection::sessionBus());
45+
46+ m_model = new QLightDM::UsersModel();
47+ QVERIFY(m_model);
48+ }
49+
50+ void cleanup()
51+ {
52+ QDBusReply<bool> addReply = m_accounts->call(QStringLiteral("RemoveUser"),
53+ g_get_user_name());
54+ QVERIFY(addReply.isValid());
55+ QCOMPARE(addReply.value(), true);
56+
57+ delete m_model;
58+ delete m_accounts;
59+ delete m_user;
60+ }
61+
62+ void testWatchRealName()
63+ {
64+ auto index = m_model->index(0, 0);
65+
66+ QCOMPARE(m_model->data(index, QLightDM::UsersModel::RealNameRole).toString(),
67+ QStringLiteral(""));
68+
69+ // The real AccountsService doesn't let you set via dbus properties. You
70+ // have to call SetRealName instead (so that they can protect the call
71+ // via policykit). But our test server does let us.
72+ QVariant wrapped = QVariant::fromValue(QDBusVariant(QStringLiteral("Test User")));
73+ QDBusReply<void> reply = m_user->call(QStringLiteral("Set"),
74+ QStringLiteral("org.freedesktop.Accounts.User"),
75+ QStringLiteral("RealName"),
76+ wrapped);
77+ QVERIFY(reply.isValid());
78+
79+ QTRY_COMPARE(m_model->data(index, QLightDM::UsersModel::RealNameRole).toString(),
80+ QStringLiteral("Test User"));
81+ }
82+
83+private:
84+ QLightDM::UsersModel *m_model;
85+ QDBusInterface *m_accounts;
86+ QDBusInterface *m_user;
87+};
88+
89+QTEST_MAIN(GreeterIntegratedTest)
90+
91+#include "integrated.moc"
tests/plugins/Ubuntu/Gestures/CMakeLists.txt+7−1
@@ -20,6 +20,11 @@ macro(build_gesture_test CLASSNAME)
2020 target_link_libraries(${CLASSNAME}TestExec UbuntuGesturesQml UbuntuGestures)
2121endmacro()
2222
23+set(UNITY_IMPORT_PATHS
24+ ${CMAKE_BINARY_DIR}/tests/utils/modules
25+ ${UNITY_PLUGINPATH}
26+)
27+
2328macro(add_gesture_unit_test CLASSNAME)
2429 build_gesture_test(${CLASSNAME})
2530 add_unity8_unittest(${CLASSNAME} ${CLASSNAME}TestExec
@@ -36,7 +41,7 @@ macro(add_gesture_ui_test CLASSNAME)
3641 DEPENDS UbuntuGesturesTest-qmlfiles
3742 ${ARGN}
3843 )
39− add_manual_qml_test(. ${CLASSNAME} IMPORT_PATHS ${UNITY_PLUGINPATH})
44+ add_manual_qml_test(. ${CLASSNAME} IMPORT_PATHS ${UNITY_IMPORT_PATHS})
4045endmacro()
4146
4247add_gesture_ui_test(DirectionalDragArea)
@@ -46,3 +51,4 @@ add_gesture_ui_test(TouchDispatcher)
4651add_gesture_ui_test(TouchGate)
4752add_gesture_unit_test(Damper)
4853add_gesture_unit_test(AxisVelocityCalculator)
54+add_gesture_ui_test(TouchGestureArea)
tests/plugins/Ubuntu/Gestures/GestureTest.cpp+44
@@ -17,6 +17,7 @@
1717#include "GestureTest.h"
1818
1919#include <qpa/qwindowsysteminterface.h>
20+#include <private/qquickwindow_p.h>
2021#include <QQmlEngine>
2122#include <QQuickView>
2223#include <QtTest>
@@ -72,6 +73,49 @@ void GestureTest::cleanup()
7273 m_view = nullptr;
7374}
7475
76+void GestureTest::sendTouchPress(qint64 timestamp, int id, QPointF pos)
77+{
78+ sendTouch(timestamp, id, pos, Qt::TouchPointPressed, QEvent::TouchBegin);
79+}
80+
81+void GestureTest::sendTouchUpdate(qint64 timestamp, int id, QPointF pos)
82+{
83+ sendTouch(timestamp, id, pos, Qt::TouchPointMoved, QEvent::TouchUpdate);
84+}
85+
86+void GestureTest::sendTouchRelease(qint64 timestamp, int id, QPointF pos)
87+{
88+ sendTouch(timestamp, id, pos, Qt::TouchPointReleased, QEvent::TouchEnd);
89+}
90+
91+void GestureTest::sendTouch(qint64 timestamp, int id, QPointF pos,
92+ Qt::TouchPointState pointState, QEvent::Type eventType)
93+{
94+ m_fakeTimerFactory->updateTime(timestamp);
95+
96+ QTouchEvent::TouchPoint point;
97+
98+ point.setState(pointState);
99+ point.setId(id);
100+ point.setScenePos(pos);
101+ point.setPos(pos);
102+
103+ QList<QTouchEvent::TouchPoint> points;
104+ points << point;
105+
106+ QTouchEvent touchEvent(eventType, m_device, Qt::NoModifier, Qt::TouchPointPressed, points);
107+ QCoreApplication::sendEvent(m_view, &touchEvent);
108+
109+ QQuickWindowPrivate *windowPrivate = QQuickWindowPrivate::get(m_view);
110+ windowPrivate->flushDelayedTouchEvent();
111+}
112+
113+void GestureTest::passTime(qint64 timeSpanMs)
114+{
115+ qint64 finalTime = m_fakeTimerFactory->timeSource()->msecsSinceReference() + timeSpanMs;
116+ m_fakeTimerFactory->updateTime(finalTime);
117+}
118+
75119////////////////////////// TouchMemento /////////////////////////////
76120
77121TouchMemento::TouchMemento(const QTouchEvent *touchEvent)
tests/plugins/Ubuntu/Gestures/GestureTest.h+13−1
@@ -72,7 +72,7 @@ class GestureTest : public QObject
7272{
7373 Q_OBJECT
7474public:
75− // \param qmlFilename name of the qml file to be loaded by the QQuickView
75+ // \param qmlFilename name of the qml file to be loaded by the QQuickView
7676 GestureTest(const QString &qmlFilename);
7777
7878protected Q_SLOTS:
@@ -80,6 +80,18 @@ protected Q_SLOTS:
8080 virtual void init(); // called right before each and every test function is executed
8181 virtual void cleanup(); // called right after each and every test function is executed
8282
83+protected:
84+ // QTest::touchEvent takes QPoint instead of QPointF and I don't want to
85+ // lose precision due to rounding.
86+ // Besides, those helper functions lead to more compact code.
87+ void sendTouchPress(qint64 timestamp, int id, QPointF pos);
88+ void sendTouchUpdate(qint64 timestamp, int id, QPointF pos);
89+ void sendTouchRelease(qint64 timestamp, int id, QPointF pos);
90+ void sendTouch(qint64 timestamp, int id, QPointF pos,
91+ Qt::TouchPointState pointState, QEvent::Type eventType);
92+
93+ void passTime(qint64 timeSpanMs);
94+
8395protected:
8496 QTouchDevice *m_device;
8597 QQuickView *m_view;
tests/plugins/Ubuntu/Gestures/tst_DirectionalDragArea.cpp−56
@@ -20,7 +20,6 @@
2020#include <QtQml/QQmlEngine>
2121#include <QPointer>
2222#include <private/qquickmousearea_p.h>
23−#include <private/qquickwindow_p.h>
2423
2524
2625#include <DirectionalDragArea.h>
@@ -109,18 +108,6 @@ private Q_SLOTS:
109108 void makoRightEdgeDrag_verticalDownwards();
110109 void makoLeftEdgeDrag_slowStart();
111110 void makoLeftEdgeDrag_movesSlightlyBackwardsOnStart();
112−
113−private:
114− // QTest::touchEvent takes QPoint instead of QPointF and I don't want to
115− // lose precision due to rounding.
116− // Besides, those helper functions lead to more compact code.
117− void sendTouchPress(qint64 timestamp, int id, QPointF pos);
118− void sendTouchUpdate(qint64 timestamp, int id, QPointF pos);
119− void sendTouchRelease(qint64 timestamp, int id, QPointF pos);
120− void sendTouch(qint64 timestamp, int id, QPointF pos,
121− Qt::TouchPointState pointState, QEvent::Type eventType);
122−
123− void passTime(qint64 timeSpanMs);
124111};
125112
126113tst_DirectionalDragArea::tst_DirectionalDragArea()
@@ -141,49 +128,6 @@ void tst_DirectionalDragArea::init()
141128 QTRY_COMPARE(m_view->height(), (int)m_view->rootObject()->height());
142129}
143130
144−void tst_DirectionalDragArea::sendTouchPress(qint64 timestamp, int id, QPointF pos)
145−{
146− sendTouch(timestamp, id, pos, Qt::TouchPointPressed, QEvent::TouchBegin);
147−}
148−
149−void tst_DirectionalDragArea::sendTouchUpdate(qint64 timestamp, int id, QPointF pos)
150−{
151− sendTouch(timestamp, id, pos, Qt::TouchPointMoved, QEvent::TouchUpdate);
152−}
153−
154−void tst_DirectionalDragArea::sendTouchRelease(qint64 timestamp, int id, QPointF pos)
155−{
156− sendTouch(timestamp, id, pos, Qt::TouchPointReleased, QEvent::TouchEnd);
157−}
158−
159−void tst_DirectionalDragArea::sendTouch(qint64 timestamp, int id, QPointF pos,
160− Qt::TouchPointState pointState, QEvent::Type eventType)
161−{
162− m_fakeTimerFactory->updateTime(timestamp);
163−
164− QTouchEvent::TouchPoint point;
165−
166− point.setState(pointState);
167− point.setId(id);
168− point.setScenePos(pos);
169− point.setPos(pos);
170−
171− QList<QTouchEvent::TouchPoint> points;
172− points << point;
173−
174− QTouchEvent touchEvent(eventType, m_device, Qt::NoModifier, Qt::TouchPointPressed, points);
175− QCoreApplication::sendEvent(m_view, &touchEvent);
176−
177− QQuickWindowPrivate *windowPrivate = QQuickWindowPrivate::get(m_view);
178− windowPrivate->flushDelayedTouchEvent();
179−}
180−
181−void tst_DirectionalDragArea::passTime(qint64 timeSpanMs)
182−{
183− qint64 finalTime = m_fakeTimerFactory->timeSource()->msecsSinceReference() + timeSpanMs;
184− m_fakeTimerFactory->updateTime(finalTime);
185−}
186−
187131namespace {
188132QPointF calculateInitialTouchPos(DirectionalDragArea *edgeDragArea)
189133{
tests/plugins/Ubuntu/Gestures/tst_TouchGestureArea.cppnew file+270
@@ -0,0 +1,270 @@
1+/*
2+ * Copyright (C) 2016 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include "GestureTest.h"
18+
19+#include <TouchGestureArea.h>
20+
21+#include <QQuickItem>
22+#include <QQuickView>
23+#include <QtTest>
24+
25+namespace {
26+QPointF calculateInitialTouchPos(TouchGestureArea *edgeDragArea)
27+{
28+ QPointF localCenter(edgeDragArea->width() / 2., edgeDragArea->height() / 2.);
29+ return edgeDragArea->mapToScene(localCenter);
30+}
31+}
32+
33+class tst_TouchGestureArea: public GestureTest
34+{
35+ Q_OBJECT
36+public:
37+ tst_TouchGestureArea();
38+private Q_SLOTS:
39+ void init() override; // called right before each and every test function is executed
40+
41+ void minimumTouchPoints();
42+ void maximumTouchPoints();
43+ void minimumAndMaximumTouchPoints();
44+ void rejectGestureAfterRecognitionPeriod();
45+ void releaseAndPressRecognisedGestureDoesNotRejectForPeriod();
46+ void topAreaReceivesOwnershipFirstWithEqualPoints();
47+ void topAreaReceivesOwnershipFirstWithMorePoints();
48+
49+private:
50+ void initGestureComponent(TouchGestureArea *area);
51+
52+ QQuickItem *m_blueRect;
53+ TouchGestureArea *m_gestureBottom;
54+ TouchGestureArea *m_gestureMiddle;
55+ TouchGestureArea *m_gestureTop;
56+};
57+
58+tst_TouchGestureArea::tst_TouchGestureArea()
59+ : GestureTest(QStringLiteral("tst_TouchGestureArea.qml"))
60+{
61+}
62+
63+inline void tst_TouchGestureArea::initGestureComponent(TouchGestureArea* area)
64+{
65+ area->setRecognitionTimer(m_fakeTimerFactory->createTimer(area));
66+ area->setMinimumTouchPoints(1);
67+ area->setMaximumTouchPoints(INT_MAX);
68+ area->setRecognitionPeriod(50);
69+ area->setReleaseRejectPeriod(100);
70+ // start tests with area disabled (enable as desired)
71+ area->setEnabled(false);
72+}
73+
74+void tst_TouchGestureArea::init()
75+{
76+ GestureTest::init();
77+
78+ m_blueRect = m_view->rootObject()->findChild<QQuickItem*>("blueRect");
79+ Q_ASSERT(m_blueRect != nullptr);
80+
81+ m_gestureBottom =
82+ m_view->rootObject()->findChild<TouchGestureArea*>("touchGestureAreaBottom");
83+ Q_ASSERT(m_gestureBottom != nullptr);
84+
85+ m_gestureMiddle =
86+ m_view->rootObject()->findChild<TouchGestureArea*>("touchGestureAreaMiddle");
87+ Q_ASSERT(m_gestureMiddle != nullptr);
88+
89+ m_gestureTop =
90+ m_view->rootObject()->findChild<TouchGestureArea*>("touchGestureAreaTop");
91+ Q_ASSERT(m_gestureTop != nullptr);
92+
93+ initGestureComponent(m_gestureBottom);
94+ initGestureComponent(m_gestureMiddle);
95+ initGestureComponent(m_gestureTop);
96+}
97+
98+void tst_TouchGestureArea::minimumTouchPoints()
99+{
100+ m_gestureBottom->setEnabled(true);
101+ m_gestureBottom->setMinimumTouchPoints(4);
102+
103+ QPointF touchPoint = calculateInitialTouchPos(m_gestureBottom);
104+
105+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::WaitingForTouch);
106+ QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
107+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::Undecided);
108+ QTest::touchEvent(m_view, m_device).stationary(0)
109+ .press(1, touchPoint.toPoint());
110+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::Undecided);
111+ QTest::touchEvent(m_view, m_device).stationary(0)
112+ .stationary(1)
113+ .press(2, touchPoint.toPoint());
114+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::Undecided);
115+ QTest::touchEvent(m_view, m_device).stationary(0)
116+ .stationary(1)
117+ .stationary(2)
118+ .press(3, touchPoint.toPoint());
119+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::Recognized);
120+ // test minimum overflow
121+ QTest::touchEvent(m_view, m_device).stationary(0)
122+ .stationary(1)
123+ .stationary(2)
124+ .stationary(3)
125+ .press(4, touchPoint.toPoint());
126+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::Recognized);
127+}
128+
129+void tst_TouchGestureArea::maximumTouchPoints()
130+{
131+ m_gestureBottom->setEnabled(true);
132+ m_gestureBottom->setMaximumTouchPoints(2);
133+
134+ QPointF touchPoint = calculateInitialTouchPos(m_gestureBottom);
135+
136+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::WaitingForTouch);
137+ QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
138+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::Recognized);
139+ QTest::touchEvent(m_view, m_device).stationary(0)
140+ .press(1, touchPoint.toPoint());
141+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::Recognized);
142+
143+ // test maximum overflow
144+ QTest::touchEvent(m_view, m_device).stationary(0)
145+ .stationary(1)
146+ .press(2, touchPoint.toPoint());
147+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::Rejected);
148+ // test still rejected
149+ QTest::touchEvent(m_view, m_device).stationary(0)
150+ .stationary(1)
151+ .press(3, touchPoint.toPoint());
152+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::Rejected);
153+}
154+
155+void tst_TouchGestureArea::minimumAndMaximumTouchPoints()
156+{
157+ m_gestureBottom->setEnabled(true);
158+ m_gestureBottom->setMinimumTouchPoints(2);
159+ m_gestureBottom->setMaximumTouchPoints(2);
160+
161+ QPointF touchPoint = calculateInitialTouchPos(m_gestureBottom);
162+
163+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::WaitingForTouch);
164+ QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
165+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::Undecided);
166+ QTest::touchEvent(m_view, m_device).stationary(0)
167+ .press(1, touchPoint.toPoint());
168+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::Recognized);
169+
170+ // test maximum overflow
171+ QTest::touchEvent(m_view, m_device).stationary(0)
172+ .stationary(1)
173+ .press(2, touchPoint.toPoint());
174+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::Rejected);
175+}
176+
177+void tst_TouchGestureArea::rejectGestureAfterRecognitionPeriod()
178+{
179+ m_gestureBottom->setEnabled(true);
180+ m_gestureBottom->setMinimumTouchPoints(2);
181+ m_gestureBottom->setMaximumTouchPoints(2);
182+
183+ QPointF touchPoint = calculateInitialTouchPos(m_gestureBottom);
184+
185+ QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
186+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::Undecided); // Recognition period is 50.
187+ passTime(40);
188+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::Undecided);
189+ passTime(10);
190+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::Rejected);
191+}
192+
193+void tst_TouchGestureArea::releaseAndPressRecognisedGestureDoesNotRejectForPeriod()
194+{
195+ m_gestureBottom->setEnabled(true);
196+ m_gestureBottom->setMinimumTouchPoints(2);
197+ m_gestureBottom->setMaximumTouchPoints(2);
198+
199+ bool wasRejected = false;
200+ connect(m_gestureBottom, &TouchGestureArea::statusChanged,
201+ this, [&wasRejected](int status) {
202+ if (status == TouchGestureArea::Rejected) wasRejected = true;
203+ });
204+
205+ QPointF touchPoint = calculateInitialTouchPos(m_gestureBottom);
206+
207+ QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint())
208+ .press(1, touchPoint.toPoint());
209+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::Recognized);
210+ passTime(5);
211+ QTest::touchEvent(m_view, m_device).stationary(0)
212+ .release(1, touchPoint.toPoint()); // Release period is 100.
213+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::Recognized);
214+ passTime(70);
215+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::Recognized);
216+ QCOMPARE(wasRejected, false);
217+ passTime(29);
218+ QCOMPARE(wasRejected, false);
219+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::Recognized);
220+ passTime(1);
221+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::Rejected);
222+}
223+
224+void tst_TouchGestureArea::topAreaReceivesOwnershipFirstWithEqualPoints()
225+{
226+ m_gestureBottom->setEnabled(true);
227+ m_gestureMiddle->setEnabled(true);
228+ m_gestureTop->setEnabled(true);
229+
230+ m_gestureBottom->setMinimumTouchPoints(1);
231+ m_gestureMiddle->setMinimumTouchPoints(1);
232+ m_gestureTop->setMinimumTouchPoints(1);
233+
234+ QPointF touchPoint = calculateInitialTouchPos(m_gestureBottom);
235+
236+ QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
237+
238+ QCOMPARE((int)m_gestureTop->status(), (int)TouchGestureArea::Recognized);
239+ // Lower items will not recieve the touch events
240+ QCOMPARE((int)m_gestureMiddle->status(), (int)TouchGestureArea::WaitingForTouch);
241+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::WaitingForTouch);
242+}
243+
244+void tst_TouchGestureArea::topAreaReceivesOwnershipFirstWithMorePoints()
245+{
246+ m_gestureBottom->setEnabled(true);
247+ m_gestureMiddle->setEnabled(true);
248+ m_gestureTop->setEnabled(true);
249+
250+ m_gestureBottom->setMinimumTouchPoints(1);
251+ m_gestureMiddle->setMinimumTouchPoints(1);
252+ m_gestureTop->setMinimumTouchPoints(2);
253+
254+ QPointF touchPoint = calculateInitialTouchPos(m_gestureBottom);
255+
256+ QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
257+ QCOMPARE((int)m_gestureTop->status(), (int)TouchGestureArea::Undecided);
258+ QCOMPARE((int)m_gestureMiddle->status(), (int)TouchGestureArea::Undecided);
259+ // The middle item will accept the events; so the bottom item will not get a chance.
260+ QCOMPARE((int)m_gestureBottom->status(), (int)TouchGestureArea::WaitingForTouch);
261+
262+ QTest::touchEvent(m_view, m_device).stationary(0)
263+ .press(1, touchPoint.toPoint());
264+ QCOMPARE((int)m_gestureTop->status(), (int)TouchGestureArea::Recognized);
265+ QCOMPARE((int)m_gestureMiddle->status(), (int)TouchGestureArea::Rejected);
266+}
267+
268+QTEST_MAIN(tst_TouchGestureArea)
269+
270+#include "tst_TouchGestureArea.moc"
tests/plugins/Ubuntu/Gestures/tst_TouchGestureArea.qmlnew file+90
@@ -0,0 +1,90 @@
1+/*
2+ * Copyright (C) 2016 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+import Ubuntu.Components 1.3
19+import Ubuntu.Gestures 0.1
20+import Unity.Test 0.1
21+
22+Rectangle {
23+ width: units.gu(60)
24+ height: units.gu(60)
25+ color: "white"
26+
27+ Binding {
28+ target: MouseTouchAdaptor
29+ property: "enabled"
30+ value: true
31+ }
32+
33+ Rectangle {
34+ id: blueRect
35+ objectName: "blueRect"
36+ color: "blue"
37+ width: units.gu(50)
38+ height: units.gu(50)
39+
40+ Timer {
41+ id: resetColorTimer
42+ interval: 300
43+ repeat: false
44+ onTriggered: {
45+ blueRect.color = "blue";
46+ }
47+ }
48+
49+ function blink(color) {
50+ blueRect.color = color;
51+ resetColorTimer.start();
52+ }
53+
54+ TouchGestureArea {
55+ anchors.fill: parent
56+ objectName: "touchGestureAreaBottom"
57+ minimumTouchPoints: 1
58+
59+ onStatusChanged: {
60+ if (status == TouchGestureArea.Recognized) {
61+ blueRect.blink("red");
62+ }
63+ }
64+ }
65+
66+ TouchGestureArea {
67+ anchors.fill: parent
68+ objectName: "touchGestureAreaMiddle"
69+ minimumTouchPoints: 2
70+
71+ onStatusChanged: {
72+ if (status == TouchGestureArea.Recognized) {
73+ blueRect.blink("yellow");
74+ }
75+ }
76+ }
77+
78+ TouchGestureArea {
79+ anchors.fill: parent
80+ objectName: "touchGestureAreaTop"
81+ minimumTouchPoints: 3
82+
83+ onStatusChanged: {
84+ if (status == TouchGestureArea.Recognized) {
85+ blueRect.blink("green");
86+ }
87+ }
88+ }
89+ }
90+}
tests/plugins/Unity/Launcher/launchermodeltest.cpp+1
@@ -48,6 +48,7 @@ public:
4848 QString comment() const override { return "this is a mock"; }
4949 QUrl icon() const override { return QUrl(); }
5050 ApplicationInfoInterface::Stage stage() const override { return ApplicationInfoInterface::MainStage; }
51+ void setStage(ApplicationInfoInterface::Stage) override {}
5152 ApplicationInfoInterface::State state() const override { return ApplicationInfoInterface::Running; }
5253 bool focused() const override { return m_focused; }
5354 QString splashTitle() const override { return QString(); }
tests/plugins/Utils/CMakeLists.txt+7−1
@@ -4,15 +4,21 @@ include_directories(
44 ${CMAKE_CURRENT_BINARY_DIR}
55 )
66
7+add_definitions(-DCONFIG_TESTING)
8+
79foreach(util_test
810 QLimitProxyModel
911 UnitySortFilterProxyModel
1012 WindowInputMonitor
13+ DeviceConfigParser
1114)
1215 add_executable(${util_test}TestExec ${util_test}Test.cpp ModelTest.cpp)
1316 qt5_use_modules(${util_test}TestExec Test Core Qml)
1417 target_link_libraries(${util_test}TestExec Utils-qml)
15− add_unity8_unittest(${util_test} ${util_test}TestExec ADD_TEST)
18+ add_unity8_unittest(${util_test} ${util_test}TestExec ADD_TEST
19+ ENVIRONMENT XDG_CONFIG_HOME=${CMAKE_CURRENT_BINARY_DIR}
20+ )
21+
1622endforeach()
1723
1824# plain qml test
tests/plugins/Utils/DeviceConfigParserTest.cppnew file+70
@@ -0,0 +1,70 @@
1+/*
2+ * Copyright (C) 2016 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+// local
18+#include "deviceconfigparser.h"
19+
20+// Qt
21+#include <QTest>
22+#include <QDebug>
23+#include <QSettings>
24+
25+class DeviceConfigParserTest : public QObject
26+{
27+ Q_OBJECT
28+
29+private Q_SLOTS:
30+
31+ void initTestCase() {
32+ }
33+
34+ void testDefaults()
35+ {
36+ DeviceConfigParser p;
37+ p.setName("nonexistent");
38+
39+ QCOMPARE(p.supportedOrientations(), Qt::PortraitOrientation | Qt::InvertedPortraitOrientation | Qt::LandscapeOrientation | Qt::InvertedLandscapeOrientation);
40+ QCOMPARE(p.primaryOrientation(), Qt::PrimaryOrientation);
41+ QCOMPARE(p.portraitOrientation(), Qt::PortraitOrientation);
42+ QCOMPARE(p.invertedPortraitOrientation(), Qt::InvertedPortraitOrientation);
43+ QCOMPARE(p.landscapeOrientation(), Qt::LandscapeOrientation);
44+ QCOMPARE(p.invertedLandscapeOrientation(), Qt::InvertedLandscapeOrientation);
45+ }
46+
47+ void testCustomFile() {
48+ QSettings s("./devices.conf", QSettings::IniFormat);
49+ s.beginGroup("fakedevice");
50+
51+ s.setValue("SupportedOrientations", QStringList() << "Portrait" << "Landscape" << "InvertedLandscape");
52+ s.setValue("PrimaryOrientation", "InvertedLandscape");
53+ s.sync();
54+
55+ DeviceConfigParser p;
56+ p.setName("fakedevice");
57+
58+ QCOMPARE(p.supportedOrientations(), Qt::PortraitOrientation | Qt::LandscapeOrientation | Qt::InvertedLandscapeOrientation);
59+ QCOMPARE(p.primaryOrientation(), Qt::InvertedLandscapeOrientation);
60+ QCOMPARE(p.portraitOrientation(), Qt::PortraitOrientation);
61+ QCOMPARE(p.invertedPortraitOrientation(), Qt::InvertedPortraitOrientation);
62+ QCOMPARE(p.landscapeOrientation(), Qt::LandscapeOrientation);
63+ QCOMPARE(p.invertedLandscapeOrientation(), Qt::InvertedLandscapeOrientation);
64+ }
65+
66+};
67+
68+QTEST_GUILESS_MAIN(DeviceConfigParserTest)
69+
70+#include "DeviceConfigParserTest.moc"
tests/qmltests/CMakeLists.txt+1
@@ -5,6 +5,7 @@ add_unity8_qmltest(. OrientedShell LIGHTDM)
55add_unity8_qmltest(. DisabledScreenNotice)
66add_unity8_qmltest(. Shell LIGHTDM)
77add_unity8_qmltest(. ShellWithPin LIGHTDM)
8+add_unity8_qmltest(. DeviceConfiguration)
89add_unity8_qmltest(Components Background)
910add_unity8_qmltest(Components Carousel)
1011add_unity8_qmltest(Components Dialogs LIGHTDM)
tests/qmltests/Components/tst_Lockscreen.qml+14−7
@@ -235,11 +235,14 @@ Rectangle {
235235
236236 function test_unlock_data() {
237237 return [
238− {tag: "numeric", alphanumeric: false, password: "1234", minPinLength: 4, maxPinLength: 4},
239− {tag: "alphanumeric", alphanumeric: true, password: "password", minPinLength: -1, maxPinLength: -1},
240− {tag: "numeric (wrong)", alphanumeric: false, password: "4321", minPinLength: 4, maxPinLength: 4},
241− {tag: "alphanumeric (wrong)", alphanumeric: true, password: "drowssap", minPinLength: -1, maxPinLength: -1},
242− {tag: "flexible length", alphanumeric: false, password: "1234", minPinLength: -1, maxPinLength: -1},
238+ {tag: "numeric", alphanumeric: false, password: "1234", minPinLength: 4, maxPinLength: 4, keyboard: false},
239+ {tag: "alphanumeric", alphanumeric: true, password: "password", minPinLength: -1, maxPinLength: -1, keyboard: false},
240+ {tag: "numeric (wrong)", alphanumeric: false, password: "4321", minPinLength: 4, maxPinLength: 4, keyboard: false},
241+ {tag: "alphanumeric (wrong)", alphanumeric: true, password: "drowssap", minPinLength: -1, maxPinLength: -1, keyboard: false},
242+ {tag: "flexible length", alphanumeric: false, password: "1234", minPinLength: -1, maxPinLength: -1, keyboard: false},
243+ {tag: "numeric", alphanumeric: false, password: "1234", minPinLength: 4, maxPinLength: 4, keyboard: true},
244+ {tag: "numeric (wrong)", alphanumeric: false, password: "4321", minPinLength: 4, maxPinLength: 4, keyboard: true},
245+ {tag: "flexible length", alphanumeric: false, password: "1234", minPinLength: -1, maxPinLength: -1, keyboard: true}
243246 ]
244247 }
245248
@@ -259,8 +262,12 @@ Rectangle {
259262 } else {
260263 for (var i = 0; i < data.password.length; ++i) {
261264 var character = data.password.charAt(i)
262− var button = findChild(lockscreen, "pinPadButton" + character)
263− mouseClick(button, units.gu(1), units.gu(1))
265+ if (data.keyboard) {
266+ typeString(character)
267+ } else {
268+ var button = findChild(lockscreen, "pinPadButton" + character)
269+ mouseClick(button, units.gu(1), units.gu(1))
270+ }
264271 }
265272 var confirmButton = findChild(lockscreen, "confirmButton");
266273 mouseClick(confirmButton, units.gu(1), units.gu(1));
tests/qmltests/Dash/Previews/tst_PreviewHeader.qml+18−1
@@ -38,6 +38,14 @@ Rectangle {
3838 "attributes": [{"value":"text1","icon":"image://theme/ok"},{"value":"text2","icon":"image://theme/cancel"}]
3939 }
4040
41+ property var emblemheaderjson: {
42+ "title": "THE TITLE",
43+ "subtitle": "Something catchy",
44+ "mascot": "../graphics/play_button.png",
45+ "attributes": [{"value":"text1","icon":"image://theme/ok"},{"value":"text2","icon":"image://theme/cancel"}],
46+ "emblem": Qt.resolvedUrl("../artwork/emblem.png")
47+ }
48+
4149 property var brokenheaderjson: {
4250 "title": "THE TITLE",
4351 "subtitle": "Something catchy",
@@ -64,7 +72,7 @@ Rectangle {
6472
6573 PreviewHeader {
6674 id: previewHeader
67− widgetData: headerjson
75+ widgetData: emblemheaderjson
6876 width: units.gu(30)
6977
7078 Rectangle {
@@ -136,6 +144,15 @@ Rectangle {
136144 }
137145 }
138146
147+ function test_emblem() {
148+ previewHeader.widgetData = emblemheaderjson;
149+
150+ var emblemIcon = findChild(previewHeader, "emblemIcon");
151+ var titleLabel = findChild(previewHeader, "titleLabel");
152+ verify(emblemIcon.height > 0);
153+ compare(emblemIcon.height, titleLabel.font.pixelSize);
154+ }
155+
139156 function test_json() {
140157 headerjson = origHeaderjson;
141158 previewHeader.widgetData = headerjson;
tests/qmltests/Dash/Previews/tst_PreviewSharing.qml+17−3
@@ -28,13 +28,18 @@ Rectangle {
2828
2929 property var shareData: {
3030 "uri": [
31− "Text here",
32− "text here 2",
33− "text here 3"
31+ "file:///this/is/an/url",
32+ "file:///this/is/an/url/2",
33+ "file:///this/is/an/url/3"
3434 ],
3535 "content-type": "text"
3636 }
3737
38+ property var shareDataString: {
39+ "uri": "file:///this/is/an/url",
40+ "content-type": "text"
41+ }
42+
3843 PreviewSharing {
3944 id: previewSharing
4045 anchors { left: parent.left; bottom: parent.bottom; }
@@ -55,5 +60,14 @@ Rectangle {
5560 mouseClick(previewSharing);
5661 compare(peerPicker.visible, true);
5762 }
63+
64+ function test_createExportedItems() {
65+ var exportedItems = previewSharing.createExportedItems(shareData["uri"]);
66+ for (var i = 0; i < exportedItems.length; i++) {
67+ compare(exportedItems[i].url, shareData["uri"][i]);
68+ }
69+ exportedItems = previewSharing.createExportedItems(shareDataString["uri"]);
70+ compare(exportedItems[0].url, shareDataString["uri"]);
71+ }
5872 }
5973}
tests/qmltests/Dash/tst_CardTool.qml+4−4
@@ -339,22 +339,22 @@ Rectangle {
339339 function test_card_title_alignment(data) {
340340 selector.selectedIndex = data.index;
341341
342− tryCompare(cardTool, "titleAlignment", Text.AlignLeft);
342+ tryCompareFunction(function() { return findChild(internalCard, "titleLabel").horizontalAlignment == Text.AlignLeft; }, true);
343343
344344 cardTool.components['title'] = { "field": "title", "align": "center" };
345345 cardTool.componentsChanged();
346346
347− tryCompare(cardTool, "titleAlignment", data.value);
347+ tryCompareFunction(function() { return findChild(internalCard, "titleLabel").horizontalAlignment == data.value; }, true);
348348
349349 cardTool.components['title'] = { "field": "title" };
350350 cardTool.componentsChanged();
351351
352− tryCompare(cardTool, "titleAlignment", data.value);
352+ tryCompareFunction(function() { return findChild(internalCard, "titleLabel").horizontalAlignment == data.value; }, true);
353353
354354 cardTool.components['title'] = { "field": "title", "align": "left" };
355355 cardTool.componentsChanged();
356356
357− tryCompare(cardTool, "titleAlignment", Text.AlignLeft);
357+ tryCompareFunction(function() { return findChild(internalCard, "titleLabel").horizontalAlignment == Text.AlignLeft; }, true);
358358 }
359359
360360 function test_categoryLayout_data() {
tests/qmltests/Dash/tst_Dash.qml+89
@@ -70,6 +70,42 @@ Item {
7070 waitForRendering(findChild(dash, "scopeLoader0").item);
7171 }
7272
73+ function scrollToCategory(categoryName) {
74+ var dashContentList = findChild(dash, "dashContentList");
75+ var genericScopeView = dashContentList.currentItem;
76+ var categoryListView = findChild(genericScopeView, "categoryListView");
77+ tryCompareFunction(function() {
78+ var category = findChild(genericScopeView, categoryName);
79+ if (category && category.y > 0 && category.y < genericScopeView.height) return true;
80+ touchFlick(genericScopeView, genericScopeView.width/2, units.gu(20),
81+ genericScopeView.width/2, genericScopeView.y)
82+ tryCompare(categoryListView, "moving", false);
83+ return false;
84+ }, true);
85+
86+ tryCompareFunction(function() { return findChild(genericScopeView, "delegate0") !== null; }, true);
87+ return findChild(genericScopeView, categoryName);
88+ }
89+
90+ function clickCategoryDelegate(category, delegate) {
91+ var dashContentList = findChild(dash, "dashContentList");
92+ var genericScopeView = dashContentList.currentItem;
93+ if (category === undefined) category = 0;
94+ if (delegate === undefined) delegate = 0;
95+ tryCompareFunction(function() {
96+ var cardGrid = findChild(genericScopeView, "dashCategory"+category);
97+ if (cardGrid != null) {
98+ var tile = findChild(cardGrid, "delegate"+delegate);
99+ return tile != null;
100+ }
101+ return false;
102+ },
103+ true);
104+ var tile = findChild(findChild(genericScopeView, "dashCategory"+category), "delegate"+delegate);
105+ waitForRendering(tile);
106+ mouseClick(tile);
107+ }
108+
73109 function test_manage_dash_clickscope_unfavoritable() {
74110 // Show the manage dash
75111 touchFlick(dash, dash.width / 2, dash.height - 1, dash.width / 2, units.gu(2));
@@ -565,5 +601,58 @@ Item {
565601 tryCompare(dashTempScopeItem, "x", dashTempScopeItem.width);
566602 tryCompare(dashTempScopeItem, "visible", false);
567603 }
604+
605+ function test_openScope()
606+ {
607+ scrollToCategory("dashCategory2");
608+ clickCategoryDelegate(2, 2);
609+
610+ var dashTempScopeItem = findChild(dash, "dashTempScopeItem");
611+ tryCompare(dashTempScopeItem, "x", 0);
612+ tryCompare(dashTempScopeItem, "visible", true);
613+ tryCompare(dashContent, "x", -dash.width);
614+
615+ // Go back
616+ var dashTempScopeItemHeader = findChild(dashTempScopeItem, "scopePageHeader");
617+ var backButton = findChild(findChild(dashTempScopeItemHeader, "innerPageHeader"), "customBackButton");
618+ mouseClick(backButton);
619+
620+ // Check temp scope is gone
621+ tryCompare(dashTempScopeItem, "x", dash.width);
622+ tryCompare(dashTempScopeItem, "visible", false);
623+ tryCompare(dashContent, "x", 0);
624+ }
625+
626+ function test_tempScopeItemXOnResize()
627+ {
628+ // Go to a temp scope
629+ touchFlick(dash, dash.width / 2, dash.height - 1, dash.width / 2, units.gu(2));
630+ var bottomEdgeController = findInvisibleChild(dash, "bottomEdgeController");
631+ tryCompare(bottomEdgeController, "progress", 1);
632+ var nonfavScopesListCategory = findChild(dash, "scopesListCategoryother");
633+ var nonfavScopesListCategoryList = findChild(nonfavScopesListCategory, "scopesListCategoryInnerList");
634+ tryCompare(nonfavScopesListCategoryList, "currentIndex", 0);
635+ mouseClick(nonfavScopesListCategoryList.currentItem);
636+ var dashTempScopeItem = findChild(dash, "dashTempScopeItem");
637+ tryCompare(dashTempScopeItem, "x", 0);
638+ tryCompare(dashTempScopeItem, "visible", true);
639+
640+ shell.width = units.gu(80);
641+ tryCompare(dashTempScopeItem, "x", 0);
642+ tryCompare(dashContent, "x", -dash.width);
643+
644+ shell.width = units.gu(40);
645+ tryCompare(dashTempScopeItem, "x", 0);
646+
647+ // Go back
648+ var dashTempScopeItemHeader = findChild(dashTempScopeItem, "scopePageHeader");
649+ var backButton = findChild(findChild(dashTempScopeItemHeader, "innerPageHeader"), "customBackButton");
650+ mouseClick(backButton);
651+
652+ // Check temp scope is gone
653+ tryCompare(dashTempScopeItem, "x", dash.width);
654+ tryCompare(dashTempScopeItem, "visible", false);
655+ tryCompare(dashContent, "x", 0);
656+ }
568657 }
569658}
tests/qmltests/Greeter/tst_NarrowView.qml+9
@@ -518,5 +518,14 @@ Item {
518518 tryCompare(coverPage, "shown", false);
519519 tryCompare(coverPage, "showProgress", 0);
520520 }
521+
522+ // Make sure that if user has a mouse, they can still get rid of cover page
523+ function test_mouseClickHidesCoverPage() {
524+ var coverPage = findChild(view, "coverPage");
525+
526+ verify(coverPage.shown);
527+ mouseClick(coverPage, coverPage.width/2, coverPage.height - units.gu(2));
528+ verify(!coverPage.shown);
529+ }
521530 }
522531}
tests/qmltests/Launcher/tst_Launcher.qml+254−18
@@ -28,12 +28,13 @@ import Utils 0.1 // For EdgeBarrierSettings
2828 launcher. */
2929Item {
3030 id: root
31− width: units.gu(50)
32− height: units.gu(55)
31+ width: units.gu(140)
32+ height: units.gu(70)
3333
3434 Loader {
3535 id: launcherLoader
3636 anchors.fill: parent
37+ focus: true
3738 property bool itemDestroyed: false
3839 sourceComponent: Component {
3940 Launcher {
@@ -68,6 +69,7 @@ Item {
6869
6970 Component.onCompleted: {
7071 launcherLoader.itemDestroyed = false;
72+ launcherLoader.focus = true
7173 edgeBarrierControls.target = testCase.findChild(this, "edgeBarrierController");
7274 }
7375 Component.onDestruction: {
@@ -77,11 +79,40 @@ Item {
7779 }
7880 }
7981
82+ Binding {
83+ target: launcherLoader.item
84+ property: "lockedVisible"
85+ value: lockedVisibleCheckBox.checked
86+ }
87+ Binding {
88+ target: launcherLoader.item
89+ property: "panelWidth"
90+ value: units.gu(Math.round(widthSlider.value))
91+ }
92+
8093 ColumnLayout {
8194 anchors { bottom: parent.bottom; right: parent.right; margins: units.gu(1) }
8295 spacing: units.gu(1)
8396 width: childrenRect.width
8497
98+ RowLayout {
99+ CheckBox {
100+ id: lockedVisibleCheckBox
101+ checked: false
102+ }
103+ Label {
104+ text: "Launcher always visible"
105+ }
106+ }
107+
108+ Slider {
109+ id: widthSlider
110+ Layout.fillWidth: true
111+ minimumValue: 6
112+ maximumValue: 12
113+ value: 10
114+ }
115+
85116 MouseTouchEmulationCheckbox {}
86117
87118 EdgeBarrierControls {
@@ -102,6 +133,15 @@ Item {
102133 Layout.fillWidth: true
103134 }
104135
136+ Button {
137+ text: "open for kbd navigation"
138+ onClicked: {
139+ launcherLoader.item.openForKeyboardNavigation()
140+ launcherLoader.item.forceActiveFocus();// = true
141+ }
142+ Layout.fillWidth: true
143+ }
144+
105145 Row {
106146 spacing: units.gu(1)
107147
@@ -206,10 +246,6 @@ Item {
206246 // growing while populating it with icons etc.
207247 tryCompare(listView, "flicking", false);
208248
209− // Make sure noone changed the height of the window. The issue this test case
210− // is verifying only happens on certain heights of the Launcher
211− compare(root.height, units.gu(55));
212−
213249 compare(listView.contentY, -listView.topMargin, "Launcher did not start up with first item unfolded");
214250
215251 // Now do check that snapping is in fact enabled
@@ -266,13 +302,39 @@ Item {
266302
267303 function positionLauncherListAtBeginning() {
268304 var listView = testCase.findChild(launcherLoader.item, "launcherListView");
269− listView.contentY = -listView.topMargin;
305+ var moveAnimation = findInvisibleChild(listView, "moveAnimation")
306+
307+ listView.moveToIndex(0);
308+
309+ waitForRendering(listView);
310+ tryCompare(moveAnimation, "running", false);
270311 }
271312 function positionLauncherListAtEnd() {
272313 var listView = testCase.findChild(launcherLoader.item, "launcherListView");
273− if ((listView.contentHeight + listView.topMargin + listView.bottomMargin) > listView.height) {
274− listView.contentY = listView.topMargin + listView.contentHeight
275− - listView.height;
314+ var moveAnimation = findInvisibleChild(listView, "moveAnimation")
315+
316+ listView.moveToIndex(listView.count -1);
317+
318+ waitForRendering(listView);
319+ tryCompare(moveAnimation, "running", false);
320+ }
321+
322+ function assertFocusOnIndex(index) {
323+ var launcherPanel = findChild(launcher, "launcherPanel");
324+ var launcherListView = findChild(launcher, "launcherListView");
325+ var bfbFocusHighlight = findChild(launcher, "bfbFocusHighlight");
326+
327+ waitForRendering(launcher);
328+ tryCompare(launcherPanel, "highlightIndex", index);
329+ compare(bfbFocusHighlight.visible, index === -1);
330+ for (var i = 0; i < launcherListView.count; i++) {
331+ var item = findChild(launcher, "launcherDelegate" + i);
332+ // Delegates might be destroyed when not visible. We can't check if they paint a focus highlight.
333+ // Make sure the requested index does have focus. for the others, try best effort to check if they don't
334+ if (index === i || item) {
335+ var focusRing = findChild(item, "focusRing")
336+ tryCompare(focusRing, "visible", index === i);
337+ }
276338 }
277339 }
278340
@@ -290,10 +352,10 @@ Item {
290352 dragLauncherIntoView()
291353
292354 // tapping on the center of the screen should dismiss the launcher
293− mouseClick(launcher)
355+ mouseClick(launcher, panel.width + units.gu(5), launcher.height / 2)
294356
295357 // should eventually get fully retracted (hidden)
296− tryCompare(panel, "x", -launcher.panelWidth, 1000)
358+ tryCompare(panel, "x", -launcher.panelWidth, 2000)
297359 }
298360
299361 /* If I click on the icon of an application on the launcher
@@ -377,6 +439,7 @@ Item {
377439 wait(100)
378440 compare(launcher.maxPanelX, -launcher.panelWidth, "Launcher moved even if it shouldn't")
379441 }
442+
380443 waitUntilLauncherDisappears();
381444 launcher.available = true;
382445 }
@@ -400,6 +463,8 @@ Item {
400463 dragLauncherIntoView();
401464 var launcherListView = findChild(launcher, "launcherListView");
402465 for (var i = 0; i < launcherListView.count; ++i) {
466+ launcherListView.moveToIndex(i);
467+ waitForRendering(launcherListView);
403468 var delegate = findChild(launcherListView, "launcherDelegate" + i)
404469 compare(findChild(delegate, "countEmblem").visible, LauncherModel.get(i).countVisible)
405470 // Intentionally allow type coercion (string/number)
@@ -421,7 +486,7 @@ Item {
421486 var launcherListView = findChild(launcher, "launcherListView");
422487 for (var i = 0; i < launcherListView.count; ++i) {
423488 var delegate = findChild(launcherListView, "launcherDelegate" + i)
424− compare(findChild(delegate, "runningHighlight").visible, LauncherModel.get(i).running)
489+ compare(findChild(delegate, "runningHighlight0").visible, LauncherModel.get(i).running)
425490 }
426491 }
427492
@@ -460,6 +525,7 @@ Item {
460525 launcher.lastSelectedApplication = "";
461526 dragLauncherIntoView();
462527 var listView = findChild(launcher, "launcherListView");
528+ var moveAnimation = findInvisibleChild(listView, "moveAnimation")
463529
464530 // flicking is unreliable. sometimes it works, sometimes the
465531 // list view moves just a tiny bit or not at all, making tests fail.
@@ -470,12 +536,14 @@ Item {
470536 } else {
471537 positionLauncherListAtEnd();
472538 }
473− tryCompare(listView, "flicking", false);
474−
475539 var oldY = listView.contentY;
476540
477541 mouseClick(listView, listView.width / 2, data.clickY);
478− tryCompare(listView, "flicking", false);
542+
543+ if (data.expectFlick) {
544+ tryCompare(moveAnimation, "running", true);
545+ }
546+ tryCompare(moveAnimation, "running", false);
479547
480548 if (data.expectFlick) {
481549 verify(listView.contentY != oldY);
@@ -764,14 +832,15 @@ Item {
764832 function test_launcher_dismiss() {
765833 dragLauncherIntoView();
766834 verify(launcher.state == "visible");
767− mouseClick(root);
835+
836+ mouseClick(root, root.width / 2, units.gu(1));
768837 waitUntilLauncherDisappears();
769838 verify(launcher.state == "");
770839
771840 // and repeat, as a test for regression in lpbug#1531339
772841 dragLauncherIntoView();
773842 verify(launcher.state == "visible");
774− mouseClick(root);
843+ mouseClick(root, root.width / 2, units.gu(1));
775844 waitUntilLauncherDisappears();
776845 verify(launcher.state == "");
777846 }
@@ -1044,5 +1113,172 @@ Item {
10441113 LauncherModel.setCountVisible(LauncherModel.get(1).appId, 0)
10451114 LauncherModel.setCount(LauncherModel.get(1).appId, oldCount)
10461115 }
1116+
1117+ function test_longpressSuperKeyShowsHints() {
1118+ var shortCutHint0 = findChild(findChild(launcher, "launcherDelegate0"), "shortcutHint");
1119+
1120+ tryCompare(shortCutHint0, "visible", false);
1121+
1122+ launcher.superPressed = true;
1123+ tryCompare(launcher, "state", "visible");
1124+ tryCompare(shortCutHint0, "visible", true);
1125+
1126+ launcher.superPressed = false;
1127+ tryCompare(launcher, "state", "");
1128+ tryCompare(shortCutHint0, "visible", false);
1129+ }
1130+
1131+ function test_keyboardNavigation() {
1132+ var bfbFocusHighlight = findChild(launcher, "bfbFocusHighlight");
1133+ var quickList = findChild(launcher, "quickList");
1134+ var launcherPanel = findChild(launcher, "launcherPanel");
1135+ var launcherListView = findChild(launcher, "launcherListView");
1136+ var last = launcherListView.count - 1;
1137+
1138+ compare(bfbFocusHighlight.visible, false);
1139+ launcher.openForKeyboardNavigation();
1140+ tryCompare(launcherPanel, "x", 0);
1141+ waitForRendering(launcher);
1142+
1143+ assertFocusOnIndex(-1);
1144+
1145+ // Down should go down
1146+ keyClick(Qt.Key_Down);
1147+ assertFocusOnIndex(0);
1148+
1149+ // Tab should go down
1150+ keyClick(Qt.Key_Tab);
1151+ assertFocusOnIndex(1);
1152+
1153+ // Up should go up
1154+ keyClick(Qt.Key_Up);
1155+ assertFocusOnIndex(0);
1156+
1157+ // Backtab should go up
1158+ keyClick(Qt.Key_Backtab);
1159+ assertFocusOnIndex(-1); // BFB
1160+
1161+ // The list should wrap around
1162+ keyClick(Qt.Key_Up);
1163+ waitForRendering(launcher);
1164+ assertFocusOnIndex(last);
1165+
1166+ keyClick(Qt.Key_Down);
1167+ waitForRendering(launcher);
1168+ keyClick(Qt.Key_Down);
1169+ assertFocusOnIndex(0); // Back to Top
1170+
1171+ // Right opens the quicklist
1172+ keyClick(Qt.Key_Right);
1173+ assertFocusOnIndex(0); // Navigating the quicklist... the launcher focus should not move
1174+ tryCompare(quickList, "visible", true);
1175+ tryCompare(quickList, "selectedIndex", 0)
1176+
1177+ // Down should move down the quicklist
1178+ keyClick(Qt.Key_Down);
1179+ tryCompare(quickList, "selectedIndex", 1)
1180+
1181+ // The quicklist should wrap around too
1182+ keyClick(Qt.Key_Down);
1183+ keyClick(Qt.Key_Down);
1184+ keyClick(Qt.Key_Down);
1185+ tryCompare(quickList, "selectedIndex", 0)
1186+
1187+ // Left gets us back to the launcher
1188+ keyClick(Qt.Key_Left);
1189+ assertFocusOnIndex(0);
1190+ tryCompare(quickList, "visible", false);
1191+
1192+ // Launcher navigation should still work
1193+ // Go bar to top by wrapping around
1194+ keyClick(Qt.Key_Down);
1195+ assertFocusOnIndex(1);
1196+
1197+ keyClick(Qt.Key_Enter);
1198+ assertFocusOnIndex(-2);
1199+ }
1200+
1201+ function test_selectQuicklistItemByKeyboard() {
1202+ launcher.openForKeyboardNavigation();
1203+ waitForRendering(launcher);
1204+
1205+ signalSpy.clear();
1206+ signalSpy.signalName = "quickListTriggered"
1207+
1208+ keyClick(Qt.Key_Down); // Down to launcher item 0
1209+ keyClick(Qt.Key_Down); // Down to launcher item 1
1210+ keyClick(Qt.Key_Right); // Into quicklist
1211+ keyClick(Qt.Key_Down); // Down to quicklist item 1
1212+ keyClick(Qt.Key_Down); // Down to quicklist item 2
1213+ keyClick(Qt.Key_Enter); // Trigger it
1214+
1215+ compare(signalSpy.count, 1, "Quicklist signal wasn't triggered")
1216+ compare(signalSpy.signalArguments[0][0], LauncherModel.get(1).appId)
1217+ compare(signalSpy.signalArguments[0][1], 2)
1218+ assertFocusOnIndex(-2);
1219+ }
1220+
1221+ function test_hideNotWorkingWhenLockedOut_data() {
1222+ return [
1223+ {tag: "locked visible", locked: true},
1224+ {tag: "no locked visible", locked: false},
1225+ ]
1226+ }
1227+
1228+ function test_hideNotWorkingWhenLockedOut(data) {
1229+ launcher.lockedVisible = data.locked;
1230+ if (data.locked) {
1231+ tryCompare(launcher, "state", "visible");
1232+ } else {
1233+ tryCompare(launcher, "state", "");
1234+ }
1235+
1236+ launcher.hide();
1237+ waitForRendering(launcher);
1238+ if (data.locked) {
1239+ verify(launcher.state == "visible");
1240+ } else {
1241+ verify(launcher.state == "");
1242+ }
1243+ }
1244+
1245+ function test_cancelKbdNavigationWitMouse_data() {
1246+ return [
1247+ {tag: "locked out - no quicklist", autohide: false, withQuickList: false },
1248+ {tag: "locked out - with quicklist", autohide: false, withQuickList: true },
1249+ {tag: "autohide - no quicklist", autohide: true, withQuickList: false },
1250+ {tag: "autohide - with quicklist", autohide: true, withQuickList: true },
1251+ ]
1252+ }
1253+
1254+ function test_cancelKbdNavigationWitMouse(data) {
1255+ launcher.autohideEnabled = data.autohide;
1256+ launcher.openForKeyboardNavigation();
1257+ waitForRendering(launcher);
1258+
1259+ var launcherPanel = findChild(launcher, "launcherPanel");
1260+ tryCompare(launcherPanel, "x", 0);
1261+
1262+ var quickList = findChild(launcher, "quickList");
1263+
1264+ keyClick(Qt.Key_Down); // Down to launcher item 0
1265+ keyClick(Qt.Key_Down); // Down to launcher item 1
1266+
1267+ if (data.withQuickList) {
1268+ keyClick(Qt.Key_Right); // Into quicklist
1269+ tryCompare(quickList, "visible", true)
1270+ }
1271+ waitForRendering(launcher)
1272+
1273+ mouseClick(root);
1274+
1275+ if (data.autohide) {
1276+ tryCompare(launcher, "state", "");
1277+ } else {
1278+ tryCompare(launcher, "state", "visible");
1279+ }
1280+
1281+ assertFocusOnIndex(-2);
1282+ }
10471283 }
10481284}
tests/qmltests/Panel/Indicators/tst_MenuItemFactory.qml+2−4
@@ -274,15 +274,14 @@ Item {
274274
275275 function test_create_progressMenu_data() {
276276 return [
277− {label: "testLabel1", enabled: true, value: 10 },
278− {label: "testLabel2", enabled: false, value: 55 },
277+ {label: "testLabel1", value: 10 },
278+ {label: "testLabel2", value: 55 },
279279 ];
280280 }
281281
282282 function test_create_progressMenu(data) {
283283 menuData.type = "com.canonical.indicator.progress"
284284 menuData.label = data.label;
285− menuData.sensitive = data.enabled;
286285 menuData.actionState = data.value;
287286
288287 loader.data = menuData;
@@ -292,7 +291,6 @@ Item {
292291
293292 compare(loader.item.text, data.label, "Label does not match data");
294293 compare(loader.item.value, data.value, "Value does not match data");
295− compare(loader.item.enabled, data.enabled, "Enabled does not match data");
296294 }
297295
298296 function test_create_standardMenu_data() {
tests/qmltests/Stages/tst_DesktopStage.qml+2−1
@@ -65,8 +65,9 @@ Item {
6565 property bool itemDestroyed: false
6666 sourceComponent: Component {
6767 DesktopStage {
68− color: "darkblue"
68+ color: "white"
6969 anchors.fill: parent
70+ background: "../../../qml/graphics/tablet_background.jpg"
7071
7172 Component.onCompleted: {
7273 edgeBarrierControls.target = testCase.findChild(this, "edgeBarrierController");
tests/qmltests/Stages/tst_PhoneStage.qml+19−6
@@ -40,12 +40,6 @@ Item {
4040 orientations: Orientations {}
4141 }
4242
43− Binding {
44− target: ApplicationManager
45− property: "rightMargin"
46− value: phoneStage.anchors.rightMargin
47− }
48−
4943 Rectangle {
5044 anchors { fill: parent; leftMargin: phoneStage.width }
5145
@@ -77,6 +71,15 @@ Item {
7771 ApplicationManager.get(appList.selectedAppIndex).setState(ApplicationInfoInterface.Stopped);
7872 }
7973 }
74+ EdgeBarrierControls {
75+ id: edgeBarrierControls
76+ text: "Drag here to pull out spread"
77+ backgroundColor: "blue"
78+ onDragged: { phoneStage.pushRightEdge(amount); }
79+ Component.onCompleted: {
80+ edgeBarrierControls.target = testCase.findChild(phoneStage, "edgeBarrierController");
81+ }
82+ }
8083 }
8184 ListView {
8285 id: appList
@@ -463,5 +466,15 @@ Item {
463466
464467 tryCompare(delegate.application, "requestedState", ApplicationInfoInterface.RequestedRunning);
465468 }
469+
470+ function test_mouseEdgePush() {
471+ var spreadView = findChild(phoneStage, "spreadView")
472+ addApps(1);
473+ mouseMove(phoneStage, phoneStage.width - 1, units.gu(10));
474+ for (var i = 0; i < units.gu(10); i++) {
475+ phoneStage.pushRightEdge(1);
476+ }
477+ tryCompare(spreadView, "phase", 2);
478+ }
466479 }
467480}
tests/qmltests/Stages/tst_SpreadDelegate.qml+7
@@ -308,5 +308,12 @@ Rectangle {
308308 tryCompare(closeAnimation, "running", false);
309309 }
310310
311+ function test_showHighLight() {
312+ loadWithGalleryApp.clicked();
313+ var highlightRect = findChild(spreadDelegateLoader.item, "selectionHighlight")
314+ tryCompare(highlightRect, "visible", false)
315+ spreadDelegateLoader.item.highlightShown = true;
316+ tryCompare(highlightRect, "visible", true)
317+ }
311318 }
312319}
tests/qmltests/Stages/tst_TabletStage.qml+162−1
@@ -20,7 +20,9 @@ import Ubuntu.Components 1.3
2020import Ubuntu.Components.ListItems 1.3 as ListItem
2121import Unity.Application 0.1
2222import Unity.Test 0.1
23+import Utils 0.1
2324
25+import ".."
2426import "../../../qml/Stages"
2527import "../../../qml/Components"
2628
@@ -77,6 +79,26 @@ Rectangle {
7779 Column {
7880 anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
7981 spacing: units.gu(1)
82+
83+ EdgeBarrierControls {
84+ id: edgeBarrierControls
85+ text: "Drag here to pull out spread"
86+ backgroundColor: "blue"
87+ onDragged: { tabletStageLoader.item.pushRightEdge(amount); }
88+ Component.onCompleted: {
89+ edgeBarrierControls.target = testCase.findChild(tabletStageLoader, "edgeBarrierController");
90+ }
91+ }
92+
93+ Button {
94+ text: testCase.sideStage ? testCase.sideStage.shown ? "Hide Side-stage" : "Show Side-stage" : ""
95+ enabled: testCase.sideStage
96+ onClicked: {
97+ if (testCase.sideStage.shown) testCase.sideStage.hide();
98+ else testCase.sideStage.show();
99+ }
100+ }
101+
80102 ApplicationCheckBox {
81103 id: webbrowserCheckBox
82104 appId: "webbrowser-app"
@@ -102,6 +124,7 @@ Rectangle {
102124 when: windowShown
103125
104126 property Item tabletStage: tabletStageLoader.status === Loader.Ready ? tabletStageLoader.item : null
127+ property Item sideStage: tabletStage ? findChild(tabletStage, "sideStage") : null
105128
106129 function init() {
107130 tabletStageLoader.active = true;
@@ -119,6 +142,7 @@ Rectangle {
119142 }
120143
121144 waitUntilAppSurfaceShowsUp("unity8-dash");
145+ sideStage.hideNow()
122146 }
123147
124148 function cleanup() {
@@ -138,6 +162,7 @@ Rectangle {
138162 galleryCheckBox.checked = false;
139163 dialerCheckBox.checked = false;
140164 facebookCheckBox.checked = false;
165+ WindowStateStorage.clear();
141166 }
142167
143168 function waitUntilAppSurfaceShowsUp(appId) {
@@ -177,6 +202,8 @@ Rectangle {
177202 }
178203
179204 function test_tappingSwitchesFocusBetweenStages() {
205+ WindowStateStorage.saveStage(dialerCheckBox.appId, ApplicationInfoInterface.SideStage)
206+
180207 webbrowserCheckBox.checked = true;
181208 waitUntilAppSurfaceShowsUp(webbrowserCheckBox.appId);
182209 var webbrowserApp = ApplicationManager.findApplication(webbrowserCheckBox.appId);
@@ -212,6 +239,8 @@ Rectangle {
212239 }
213240
214241 function test_closeAppInSideStage() {
242+ WindowStateStorage.saveStage(dialerCheckBox.appId, ApplicationInfoInterface.SideStage)
243+
215244 dialerCheckBox.checked = true;
216245 waitUntilAppSurfaceShowsUp(dialerCheckBox.appId);
217246
@@ -283,6 +312,8 @@ Rectangle {
283312
284313
285314 function test_foregroundMainAndSideStageAppsAreKeptRunning() {
315+ WindowStateStorage.saveStage(facebookCheckBox.appId, ApplicationInfoInterface.SideStage)
316+ WindowStateStorage.saveStage(dialerCheckBox.appId, ApplicationInfoInterface.SideStage)
286317
287318 var stagesPriv = findInvisibleChild(tabletStage, "stagesPriv");
288319 verify(stagesPriv);
@@ -341,6 +372,8 @@ Rectangle {
341372 }
342373
343374 function test_foregroundAppsAreSuspendedWhenStageIsSuspended() {
375+ WindowStateStorage.saveStage(dialerCheckBox.appId, ApplicationInfoInterface.SideStage)
376+
344377 webbrowserCheckBox.checked = true;
345378 waitUntilAppSurfaceShowsUp(webbrowserCheckBox.appId)
346379 var webbrowserApp = ApplicationManager.findApplication(webbrowserCheckBox.appId);
@@ -365,6 +398,134 @@ Rectangle {
365398 tryCompare(webbrowserApp, "requestedState", ApplicationInfoInterface.RequestedRunning);
366399 tryCompare(dialerApp, "requestedState", ApplicationInfoInterface.RequestedRunning);
367400 }
368− }
369401
402+ function test_mouseEdgePush() {
403+ var spreadView = findChild(tabletStageLoader, "spreadView")
404+ mouseMove(tabletStageLoader, tabletStageLoader.width - 1, units.gu(10));
405+ for (var i = 0; i < units.gu(10); i++) {
406+ tabletStageLoader.item.pushRightEdge(1);
407+ }
408+ tryCompare(spreadView, "phase", 2);
409+ }
410+
411+ function test_threeFingerTapOpensSideStage_data() {
412+ return [
413+ { tag: "1 finger", touchIds: [0], result: false },
414+ { tag: "2 finger", touchIds: [0, 1], result: false },
415+ { tag: "3 finger", touchIds: [0, 1, 2], result: true },
416+ { tag: "4 finger", touchIds: [0, 1, 2, 3], result: false },
417+ ];
418+ }
419+
420+ function test_threeFingerTapOpensSideStage(data) {
421+ multiTouchTap(data.touchIds, tabletStage, tabletStage.width / 2, tabletStage.height / 2);
422+ wait(200);
423+ tryCompare(sideStage, "shown", data.result);
424+ }
425+
426+
427+ function test_threeFingerTapClosesSideStage_data() {
428+ return [
429+ { tag: "1 finger", touchIds: [0], result: true },
430+ { tag: "2 finger", touchIds: [0, 1], result: true },
431+ { tag: "3 finger", touchIds: [0, 1, 2], result: false },
432+ { tag: "4 finger", touchIds: [0, 1, 2, 3], result: true },
433+ ];
434+ }
435+
436+ function test_threeFingerTapClosesSideStage(data) {
437+ sideStage.showNow();
438+
439+ multiTouchTap(data.touchIds, tabletStage, tabletStage.width / 2, tabletStage.height / 2);
440+ wait(200);
441+ tryCompare(sideStage, "shown", data.result);
442+ }
443+
444+ function test_threeFingerDragOpensSidestage() {
445+ multiTouchDragUntil([0,1,2],
446+ tabletStage,
447+ tabletStage.width / 4,
448+ tabletStage.height / 4,
449+ units.gu(1),
450+ 0,
451+ function() { return sideStage.shown; });
452+ }
453+
454+ function test_applicationLoadsInCorrectStage_data() {
455+ return [
456+ { tag: "MainStage", stage: ApplicationInfoInterface.MainStage, mainStageAppId: "webbrowser-app", sideStageAppId: ""},
457+ { tag: "SideStage", stage: ApplicationInfoInterface.SideStage, mainStageAppId: "unity8-dash", sideStageAppId: "webbrowser-app" },
458+ ];
459+ }
460+
461+ function test_applicationLoadsInCorrectStage(data) {
462+ WindowStateStorage.saveStage(webbrowserCheckBox.appId, data.stage)
463+
464+ var stagesPriv = findInvisibleChild(tabletStage, "stagesPriv");
465+ verify(stagesPriv);
466+
467+ tryCompare(stagesPriv, "mainStageAppId", "unity8-dash");
468+ tryCompare(stagesPriv, "sideStageAppId", "");
469+
470+ webbrowserCheckBox.checked = true;
471+ waitUntilAppSurfaceShowsUp(webbrowserCheckBox.appId)
472+
473+ tryCompare(stagesPriv, "mainStageAppId", data.mainStageAppId);
474+ tryCompare(stagesPriv, "sideStageAppId", data.sideStageAppId);
475+ }
476+
477+ function test_loadSideStageByDragginFromMainStage() {
478+ sideStage.showNow();
479+ webbrowserCheckBox.checked = true;
480+ waitUntilAppSurfaceShowsUp(webbrowserCheckBox.appId);
481+
482+ var appDelegate = findChild(tabletStage, "tabletSpreadDelegate_" + webbrowserCheckBox.appId);
483+ verify(appDelegate);
484+ compare(appDelegate.stage, ApplicationInfoInterface.MainStage);
485+
486+ var pos = tabletStage.width - sideStage.width - (tabletStage.width - sideStage.width) / 2;
487+ var end_pos = tabletStage.width - sideStage.width / 2;
488+
489+ multiTouchDragUntil([0,1,2],
490+ tabletStage,
491+ pos,
492+ tabletStage.height / 2,
493+ units.gu(3),
494+ 0,
495+ function() {
496+ pos += units.gu(3);
497+ return sideStage.shown && !sideStage.showAnimation.running &&
498+ pos >= end_pos;
499+ });
500+
501+ tryCompare(appDelegate, "stage", ApplicationInfoInterface.SideStage);
502+ }
503+
504+ function test_unloadSideStageByDragginFromStageStage() {
505+ sideStage.showNow();
506+ WindowStateStorage.saveStage(webbrowserCheckBox.appId, ApplicationInfoInterface.SideStage)
507+ webbrowserCheckBox.checked = true;
508+ waitUntilAppSurfaceShowsUp(webbrowserCheckBox.appId);
509+
510+ var appDelegate = findChild(tabletStage, "tabletSpreadDelegate_" + webbrowserCheckBox.appId);
511+ verify(appDelegate);
512+ compare(appDelegate.stage, ApplicationInfoInterface.SideStage);
513+
514+ var pos = tabletStage.width - sideStage.width / 2;
515+ var end_pos = tabletStage.width - sideStage.width - (tabletStage.width - sideStage.width) / 2;
516+
517+ multiTouchDragUntil([0,1,2],
518+ tabletStage,
519+ pos,
520+ tabletStage.height / 2,
521+ -units.gu(3),
522+ 0,
523+ function() {
524+ pos -= units.gu(3);
525+ return pos <= end_pos;
526+ });
527+
528+ tryCompare(appDelegate, "stage", ApplicationInfoInterface.MainStage);
529+ }
530+ }
370531}
tests/qmltests/Tutorial/tst_Tutorial.qml+3−4
@@ -191,7 +191,7 @@ Rectangle {
191191 CheckBox {
192192 activeFocusOnPress: false
193193 onCheckedChanged: {
194− var surface = SurfaceManager.inputMethodSurface();
194+ var surface = SurfaceManager.inputMethodSurface;
195195 if (checked) {
196196 surface.setState(Mir.RestoredState);
197197 } else {
@@ -256,7 +256,6 @@ Rectangle {
256256 if (app) {
257257 app.setStage(ApplicationInfoInterface.SideStage);
258258 }
259− SurfaceManager.inputMethodSurface().destroy(); // so that InputMethod will notice new one
260259 // kill all (fake) running apps
261260 killApps();
262261
@@ -742,7 +741,7 @@ Rectangle {
742741 var tutorialLeft = findChild(shell, "tutorialLeft");
743742 verify(tutorialLeft.shown);
744743
745− var surface = SurfaceManager.inputMethodSurface();
744+ var surface = SurfaceManager.inputMethodSurface;
746745 surface.setState(Mir.RestoredState);
747746
748747 var inputMethod = findInvisibleChild(shell, "inputMethod");
@@ -752,7 +751,7 @@ Rectangle {
752751 }
753752
754753 function test_oskPreventsTutorial() {
755− var surface = SurfaceManager.inputMethodSurface();
754+ var surface = SurfaceManager.inputMethodSurface;
756755 var inputMethod = findInvisibleChild(shell, "inputMethod");
757756
758757 AccountsService.demoEdges = false;
tests/qmltests/Wizard/tst_Wizard.qml+170−199
@@ -21,6 +21,7 @@ import MeeGo.QOfono 0.2
2121import QMenuModel 0.1
2222import Ubuntu.Components 1.3
2323import Ubuntu.SystemSettings.SecurityPrivacy 1.0
24+import Ubuntu.SystemSettings.TimeDate 1.0
2425import Unity.Test 0.1 as UT
2526import Wizard 0.1
2627import "../../../qml/Wizard"
@@ -39,7 +40,6 @@ Item {
3940 Wizard {
4041 id: wizard
4142 anchors.fill: parent
42− background: Qt.resolvedUrl("../../../qml/graphics/phone_background.jpg")
4343
4444 Component.onDestruction: {
4545 wizardLoader.itemDestroyed = true;
@@ -51,7 +51,7 @@ Item {
5151 SignalSpy {
5252 id: updateSessionLanguageSpy
5353 target: System
54− signalName: "updateSessionLanguageCalled"
54+ signalName: "updateSessionLocaleCalled"
5555 }
5656
5757 SignalSpy {
@@ -69,6 +69,11 @@ Item {
6969 signalName: "activated"
7070 }
7171
72+ SignalSpy {
73+ id: timezoneSpy
74+ signalName: "timeZoneChangedCalled"
75+ }
76+
7277 function setup() {
7378 AccountsService.hereEnabled = false;
7479 AccountsService.hereLicensePath = Qt.resolvedUrl("licenses");
@@ -82,6 +87,7 @@ Item {
8287 setSecuritySpy.clear();
8388 activateLocationSpy.clear();
8489 activateGPSSpy.clear();
90+ timezoneSpy.clear();
8591
8692 ActionData.data = {
8793 "location-detection-enabled": {
@@ -180,11 +186,6 @@ Item {
180186 tap(findChild(page, "forwardButton"));
181187 }
182188
183− page = waitForPage("passwdPage");
184− if (name === page.objectName) return page;
185− tap(findChild(page, "passwdDelegate0"));
186− tap(findChild(page, "forwardButton"));
187−
188189 page = waitForPage("wifiPage");
189190 if (name === page.objectName) return page;
190191 tap(findChild(page, "forwardButton"));
@@ -195,6 +196,31 @@ Item {
195196 tap(findChild(page, "forwardButton"));
196197 }
197198
199+ page = waitForPage("tzPage");
200+ if (name === page.objectName) return page;
201+ var tzList = findChild(page, "tzList");
202+ verify(tzList);
203+ waitForRendering(tzList);
204+ page.selectedTimeZone = "Pacific/Honolulu";
205+ tap(findChild(page, "forwardButton"));
206+
207+ page = waitForPage("accountPage");
208+ if (name === page.objectName) return page;
209+ tap(findChild(page, "nameInput"));
210+ typeString("foobar");
211+ tap(findChild(page, "passInput"));
212+ typeString("12345678");
213+ var pass2Input = findChild(page, "pass2Input");
214+ // give it a bit of time to finish the scroll-into-view animation
215+ tryCompareFunction(function() { return pass2Input !== null; }, true);
216+ tap(pass2Input);
217+ typeString("12345678");
218+ tap(findChild(page, "forwardButton"));
219+
220+ page = waitForPage("passwdPage");
221+ if (name === page.objectName) return page;
222+ tap(findChild(page, "forwardButton"));
223+
198224 if (!skipReporting) {
199225 page = waitForPage("reportingPage");
200226 if (name === page.objectName) return page;
@@ -203,7 +229,8 @@ Item {
203229
204230 page = waitForPage("finishedPage");
205231 if (name === page.objectName) return page;
206− tap(findChild(page, "forwardButton"));
232+ waitUntilTransitionsEnd(page);
233+ tap(findChild(page, "finishButton"));
207234
208235 tryCompare(wizard, "shown", false);
209236 compare(name, null);
@@ -212,100 +239,43 @@ Item {
212239
213240 function test_languageChange() {
214241 var page = goToPage("languagePage");
215− tap(findChild(page, "languageCombo"));
216− waitForRendering(findChild(page, "languageDelegate1"));
217−
218− // For some reason, the delegate *sometimes* (like 1 in 10 maybe)
219− // needs more time before it can process a tap() call. I can't
220− // find a rhyme or reason, its properties all seem the same in
221− // cases where it works and does not. This failure to receive a
222− // tap() call below does *not* happen when running in xvfb, so
223− // jenkins is unaffected (and we don't have to worry about 100 not
224− // being enough time in its slow environment). This wait() call is
225− // just to help local runs not trip up.
226− wait(100);
227− tap(findChild(page, "languageDelegate1"));
228−
229− tryCompare(i18n, "language", "fr");
242+ tap(findChild(page, "languageDelegate1")); // should invoke "fr" lang
243+
244+ tryCompare(i18n, "language", "fr_FR");
230245 tap(findChild(page, "forwardButton"));
231246 tryCompare(updateSessionLanguageSpy, "count", 1);
232− compare(updateSessionLanguageSpy.signalArguments[0][0], "fr");
247+ compare(updateSessionLanguageSpy.signalArguments[0][0], "fr_FR");
233248 }
234249
235250 function test_languageNoChange() {
236− goToPage("simPage"); // one past language page
251+ var page = goToPage("languagePage");
252+ tap(findChild(page, "forwardButton"));
237253 compare(updateSessionLanguageSpy.count, 0);
238254 }
239255
240256 function test_simUnavailableSkip() {
241257 MockQOfono.available = false;
242− goToPage("passwdPage", true);
258+ goToPage("wifiPage", true);
243259 }
244260
245261 function test_simNoModemsSkip() {
246262 MockQOfono.setModems([], [], []);
247− goToPage("passwdPage", true);
263+ goToPage("wifiPage", true);
248264 }
249265
250266 function test_simFirstSkip() {
251267 MockQOfono.setModems(["a", "b"], [true, false], []);
252− goToPage("passwdPage", true);
268+ goToPage("wifiPage", true);
253269 }
254270
255271 function test_simSecondSkip() {
256272 MockQOfono.setModems(["a", "b"], [false, true], []);
257− goToPage("passwdPage", true);
273+ goToPage("wifiPage", true);
258274 }
259275
260276 function test_simBothSkip() {
261277 MockQOfono.setModems(["a", "b"], [true, true], []);
262− goToPage("passwdPage", true);
263− }
264−
265− function test_simWaitOnManagerAsync() {
266− MockQOfono.ready = false;
267− MockQOfono.setModems(["a"], [false], []);
268−
269− // Go to SIM page, which will be waiting for skip to be valid
270− var page = goToPage("languagePage");
271− tap(findChild(page, "forwardButton"));
272− verifyPageIsBlocked("simPage");
273−
274− // Now release QOfono from blocking the page
275− MockQOfono.ready = true;
276−
277− waitForPage("simPage");
278− }
279−
280− function test_simWaitOnCardAsync() {
281− MockQOfono.setModems(["a"], [false], [false]);
282−
283− // Go to SIM page, which will be waiting for skip to be valid
284− var page = goToPage("languagePage");
285− tap(findChild(page, "forwardButton"));
286− verifyPageIsBlocked("simPage");
287−
288− // Now release QOfono from blocking the page
289− MockQOfono.setModems(["a"], [false], [true]);
290−
291− waitForPage("simPage");
292− }
293−
294− function test_simWaitTimeout() {
295− MockQOfono.setModems(["a"], [false], [false]);
296−
297− // Go to SIM page, which will be waiting for skip to be valid
298− var page = goToPage("languagePage");
299− tap(findChild(page, "forwardButton"));
300− verifyPageIsBlocked("simPage");
301−
302− var timeout = findInvisibleChild(wizard, "timeout");
303− timeout.interval = 100; // reduce our delay
304−
305− // Now just wait for timeout
306− compare(timeout.running, true);
307− waitForPage("passwdPage");
308− compare(timeout.running, false);
278+ goToPage("wifiPage", true);
309279 }
310280
311281 function enterPasscode(passcode) {
@@ -332,33 +302,32 @@ Item {
332302
333303 function test_passwdPasscode() {
334304 var page = goToPage("passwdPage");
335−
305+ tap(findChild(page, "passwdDelegate2")); // passcode option
336306 tap(findChild(page, "forwardButton"));
337− page = waitForPage("passwdSetPage");
307+ page = waitForPage("passcodeSetPage");
338308
339309 enterPasscode("1111");
340− page = waitForPage("passwdConfirmPage");
310+ page = waitForPage("passcodeConfirmPage");
341311
342312 // make sure we go back to 'set' page not 'type' page
343313 tap(findChild(page, "backButton"));
344− page = waitForPage("passwdSetPage");
314+ page = waitForPage("passcodeSetPage");
345315
346316 enterPasscode("1111");
347− page = waitForPage("passwdConfirmPage");
317+ page = waitForPage("passcodeConfirmPage");
348318
349319 enterPasscode("1112");
350320 var error = findChild(page, "wrongNoticeLabel");
351321 tryCompareFunction(function() { return error.text !== ""; }, true);
352322
353323 enterPasscode("1111");
354− page = waitForPage("wifiPage");
355324
356325 // now finish up
357− tap(findChild(page, "forwardButton"));
358− page = waitForPage("locationPage");
326+ page = waitForPage("reportingPage");
359327 tap(findChild(page, "forwardButton"));
360328 page = waitForPage("finishedPage");
361− tap(findChild(page, "forwardButton"));
329+ waitUntilTransitionsEnd(page);
330+ tap(findChild(page, "finishButton"));
362331
363332 tryCompare(setSecuritySpy, "count", 1);
364333 compare(setSecuritySpy.signalArguments[0][0], "");
@@ -368,96 +337,85 @@ Item {
368337
369338 function test_passwdPassphrase() {
370339 var page = goToPage("passwdPage");
371− tap(findChild(page, "passwdDelegate2"));
372−
340+ tap(findChild(page, "passwdDelegate1")); // password option
373341 tap(findChild(page, "forwardButton"));
374342 page = waitForPage("passwdSetPage");
375343
344+ var passwdField = findChild(page, "passwordField");
345+ verify(passwdField);
346+ tap(passwdField);
376347 typeString("aaa");
377− var continueButton = findChild(page, "forwardButton");
378− tryCompare(continueButton.item, "enabled", false);
379− keyClick(Qt.Key_Enter);
380− var error = findChild(page, "wrongNoticeLabel");
381− tryCompareFunction(function() { return error.text !== ""; }, true);
348+ var continueButton = findChild(page, "okButton");
349+ tryCompare(continueButton, "enabled", false);
382350
383− typeString("aaaa");
351+ tap(passwdField);
352+ passwdField.selectAll(); // overwrite the text
353+ typeString("12345678");
384354 tap(continueButton);
385− page = waitForPage("passwdConfirmPage");
355+ wait(200);
386356
387357 // make sure we go back to 'set' page not 'type' page
388− var back = findChild(page, "backButton");
358+ var back = findChild(page, "cancelButton");
389359 tap(back);
390− page = waitForPage("passwdSetPage");
391−
392− typeString("aaaa");
393− keyClick(Qt.Key_Enter);
394− page = waitForPage("passwdConfirmPage");
360+ wait(200);
395361
396− continueButton = findChild(page, "forwardButton");
362+ passwdField = findChild(page, "passwordField");
363+ tryCompareFunction(function() {return !!passwdField}, true);
364+ tap(passwdField);
397365 typeString("aaab");
398− tryCompare(continueButton.item, "enabled", false);
399− keyClick(Qt.Key_Enter);
400− var error = findChild(page, "wrongNoticeLabel");
366+
367+ continueButton = findChild(page, "okButton");
368+ tryCompare(continueButton, "enabled", false);
369+ var error = findChild(page, "infoLabel");
401370 tryCompareFunction(function() { return error.text !== ""; }, true);
402371
403− typeString("aaaa");
372+ tap(passwdField);
373+ passwdField.selectAll(); // overwrite the text
374+ typeString("12345678");
375+ tap(continueButton);
376+ wait(200);
377+
378+ tap(passwdField);
379+ passwdField.selectAll(); // overwrite the text
380+ typeString("12345678");
404381 tap(continueButton);
405− page = waitForPage("wifiPage");
406382
407383 // now finish up
408− tap(findChild(page, "forwardButton"));
409− page = waitForPage("locationPage");
384+ page = waitForPage("reportingPage");
410385 tap(findChild(page, "forwardButton"));
411386 page = waitForPage("finishedPage");
412− tap(findChild(page, "forwardButton"));
387+ waitUntilTransitionsEnd(page);
388+ tap(findChild(page, "finishButton"));
413389
414390 tryCompare(setSecuritySpy, "count", 1);
415391 compare(setSecuritySpy.signalArguments[0][0], "");
416− compare(setSecuritySpy.signalArguments[0][1], "aaaa");
392+ compare(setSecuritySpy.signalArguments[0][1], "12345678");
417393 compare(setSecuritySpy.signalArguments[0][2], UbuntuSecurityPrivacyPanel.Passphrase);
418394 }
419395
420396 function test_passwdSwipe() {
421− goToPage(null, false, false, true);
422−
423− tryCompare(setSecuritySpy, "count", 1);
424− compare(setSecuritySpy.signalArguments[0][0], "");
425− compare(setSecuritySpy.signalArguments[0][1], "");
426− compare(setSecuritySpy.signalArguments[0][2], UbuntuSecurityPrivacyPanel.Swipe);
427− }
428−
429− function test_locationSkipNoPath() {
430− AccountsService.hereLicensePath = "";
431− goToPage("finishedPage", false, true, true);
432− }
433−
434− function test_locationSkipNoFiles() {
435− AccountsService.hereLicensePath = Qt.resolvedUrl("nolicenses");
436− goToPage("finishedPage", false, true, true);
437− }
438−
439− function test_locationWaitOnPath() {
440− AccountsService.hereLicensePath = " "; // means we're still getting the path from dbus
441−
442− var page = goToPage("wifiPage");
397+ var page = goToPage("passwdPage");
398+ tap(findChild(page, "passwdDelegate3")); // swipe option
443399
444− var pages = findChild(wizard, "wizardPages");
445− var stack = findChild(pages, "pageStack");
400+ // now finish up
446401 tap(findChild(page, "forwardButton"));
447− // don't simply call tryCompare here, because stack.currentPage will be swapped out itself
448− tryCompareFunction(function() { return stack.currentPage.objectName; }, "locationPage");
449− compare(stack.currentPage.enabled, false);
450− compare(stack.currentPage.skipValid, false);
402+ page = waitForPage("reportingPage");
403+ tap(findChild(page, "forwardButton"));
404+ page = waitForPage("finishedPage");
405+ waitUntilTransitionsEnd(page);
406+ tap(findChild(page, "finishButton"));
451407
452− AccountsService.hereLicensePath = "";
453− waitForPage("finishedPage", false, false, true);
408+ tryCompare(setSecuritySpy, "count", 1);
409+ //compare(setSecuritySpy.signalArguments[0][0], ""); // FIXME the swipe method collides with the account password
410+ //compare(setSecuritySpy.signalArguments[0][1], "");
411+ compare(setSecuritySpy.signalArguments[0][2], UbuntuSecurityPrivacyPanel.Swipe);
454412 }
455413
456414 function test_locationGpsOnly() {
457415 var page = goToPage("locationPage");
458− var gpsCheck = findChild(page, "gpsCheck");
459− var hereCheck = findChild(page, "hereCheck");
460− var nopeCheck = findChild(page, "nopeCheck");
416+ var gpsCheck = findChild(page, "gpsCheckLabel");
417+ var hereCheck = findChild(page, "hereCheckLabel");
418+ var nopeCheck = findChild(page, "nopeCheckLabel");
461419
462420 var locationActionGroup = findInvisibleChild(page, "locationActionGroup");
463421 activateLocationSpy.target = locationActionGroup.location;
@@ -476,9 +434,9 @@ Item {
476434
477435 function test_locationNope() {
478436 var page = goToPage("locationPage");
479− var gpsCheck = findChild(page, "gpsCheck");
480− var hereCheck = findChild(page, "hereCheck");
481− var nopeCheck = findChild(page, "nopeCheck");
437+ var gpsCheck = findChild(page, "gpsCheckLabel");
438+ var hereCheck = findChild(page, "hereCheckLabel");
439+ var nopeCheck = findChild(page, "nopeCheckLabel");
482440
483441 var locationActionGroup = findInvisibleChild(page, "locationActionGroup");
484442 activateLocationSpy.target = locationActionGroup.location;
@@ -491,15 +449,15 @@ Item {
491449
492450 tap(findChild(page, "forwardButton"));
493451 tryCompare(AccountsService, "hereEnabled", false);
494− tryCompare(activateLocationSpy, "count", 0)
495− tryCompare(activateGPSSpy, "count", 0)
452+ tryCompare(activateLocationSpy, "count", 0);
453+ tryCompare(activateGPSSpy, "count", 0);
496454 }
497455
498456 function test_locationHere() {
499457 var page = goToPage("locationPage");
500− var gpsCheck = findChild(page, "gpsCheck");
501− var hereCheck = findChild(page, "hereCheck");
502− var nopeCheck = findChild(page, "nopeCheck");
458+ var gpsCheck = findChild(page, "gpsCheckLabel");
459+ var hereCheck = findChild(page, "hereCheckLabel");
460+ var nopeCheck = findChild(page, "nopeCheckLabel");
503461
504462 var locationActionGroup = findInvisibleChild(page, "locationActionGroup");
505463 activateLocationSpy.target = locationActionGroup.location;
@@ -512,68 +470,81 @@ Item {
512470
513471 tap(findChild(page, "forwardButton"));
514472 tryCompare(AccountsService, "hereEnabled", true);
515− tryCompare(activateLocationSpy, "count", 1)
516− tryCompare(activateGPSSpy, "count", 1)
473+ tryCompare(activateLocationSpy, "count", 1);
474+ tryCompare(activateGPSSpy, "count", 1);
517475 }
518476
519− function test_locationHereTerms() {
520− var page = goToPage("locationPage");
477+ function test_timezonePage() {
478+ var page = goToPage("tzPage");
479+ verify(page);
480+ timezoneSpy.target = page.tdModule;
521481
522− var link = findChild(page, "hereTermsLink");
482+ var tzFilter = findChild(page, "tzFilter");
483+ verify(tzFilter);
484+ tap(tzFilter);
485+ typeString("London");
523486
524− // Test our language lookup code a bit
487+ var tzList = findChild(page, "tzList");
488+ verify(tzList);
525489
526− i18n.language = "fr_FR.UTF-8";
527− link.linkActivated("not-used");
528− page = waitForPage("hereTermsPage");
529− tryCompare(findChild(page, "termsLabel"), "text", "fr_FR\n");
530− tap(findChild(page, "backButton"));
531− waitForPage("locationPage");
490+ // test filtering works and returns some results
491+ tryCompareFunction(function() { return tzList.count > 0; }, true);
532492
533− i18n.language = "fr_CA";
534− link.linkActivated("not-used");
535− page = waitForPage("hereTermsPage");
536− tryCompare(findChild(page, "termsLabel"), "text", "fr_CA\n");
537− tap(findChild(page, "backButton"));
538− waitForPage("locationPage");
493+ // just tap the first one
494+ tap(findChild(page, "tz0"));
539495
540− i18n.language = "fr_US";
541− link.linkActivated("not-used");
542− page = waitForPage("hereTermsPage");
543− tryCompare(findChild(page, "termsLabel"), "text", "fr_FR\n");
544− tap(findChild(page, "backButton"));
545− waitForPage("locationPage");
496+ // go next and verify the (mock) signal got fired
497+ tap(findChild(page, "forwardButton"));
498+ tryCompare(timezoneSpy, "count", 1);
499+ tryCompare(page.tdModule, "timeZone", timezoneSpy.signalArguments[0][0]);
500+ }
546501
547− i18n.language = "fr.utf8";
548− link.linkActivated("not-used");
549− page = waitForPage("hereTermsPage");
550− tryCompare(findChild(page, "termsLabel"), "text", "fr_FR\n");
551− tap(findChild(page, "backButton"));
552− waitForPage("locationPage");
502+ function test_accountPage() {
503+ var page = goToPage("accountPage");
504+ var password1 = findChild(page, "passInput");
505+ var password2 = findChild(page, "pass2Input");
506+ var forwardButton = findChild(page, "forwardButton");
553507
554− i18n.language = "es"; // will not be found
555− link.linkActivated("not-used");
556− page = waitForPage("hereTermsPage");
557− tryCompare(findChild(page, "termsLabel"), "text", "en_US\n");
508+ tap(findChild(page, "nameInput"));
509+ typeString("foobar");
558510
559− // OK, done with languages, back to actual page
511+ verify(forwardButton.enabled, false); // no passwords yet
560512
561− var label = findChild(page, "termsLabel");
562− label.linkActivated(Qt.resolvedUrl("licenses/en_US.html"));
563− tryCompare(label, "visible", false);
513+ tap(password1);
514+ typeString("12345");
564515
565− var webview = findChild(page, "webview");
566− tryCompare(webview, "visible", true);
567− tryCompare(webview, "url", Qt.resolvedUrl("licenses/en_US.html"));
568− tryCompare(webview, "loadProgress", 100);
516+ verify(forwardButton.enabled, false); // password too short
569517
570− tap(findChild(page, "backButton"));
571− waitForPage("hereTermsPage"); // confirm we're on same page
572− tryCompare(webview, "visible", false);
573− tryCompare(label, "visible", true);
518+ tap(password2);
519+ typeString("12345678");
574520
575− tap(findChild(page, "backButton"));
576− waitForPage("locationPage");
521+ verify(forwardButton.enabled, false); // passwords don't match
522+
523+ tap(password1);
524+ password1.selectAll();
525+ typeString("12345678");
526+
527+ verify(forwardButton.enabled, true); // now they match
528+
529+ var pages = findChild(wizard, "wizardPages");
530+ var security = findInvisibleChild(pages, "securityPrivacy");
531+ setSecuritySpy.target = security;
532+
533+ // now finish up
534+ tap(forwardButton);
535+ page = waitForPage("passwdPage");
536+ tap(findChild(page, "passwdDelegate0")); // account option
537+ tap(findChild(page, "forwardButton"));
538+ page = waitForPage("reportingPage");
539+ tap(findChild(page, "forwardButton"));
540+ page = waitForPage("finishedPage");
541+ waitUntilTransitionsEnd(page);
542+ tap(findChild(page, "finishButton"));
543+
544+ tryCompare(setSecuritySpy, "count", 1);
545+ compare(setSecuritySpy.signalArguments[0][0], "");
546+ compare(setSecuritySpy.signalArguments[0][1], "12345678");
547+ compare(setSecuritySpy.signalArguments[0][2], UbuntuSecurityPrivacyPanel.Passphrase);
577548 }
578549 }
579550}
tests/qmltests/tst_DeviceConfiguration.qmlnew file+49
@@ -0,0 +1,49 @@
1+/*
2+ * Copyright (C) 2016 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+import QtTest 1.0
19+import Unity.Test 0.1
20+import "../../qml"
21+
22+Item {
23+ id: root
24+ width: units.gu(40)
25+ height: units.gu(70)
26+
27+ DeviceConfiguration {
28+ id: deviceConfiguration
29+ }
30+
31+ UnityTestCase {
32+ id: testCase
33+ name: "DeviceConfiguration"
34+ when: windowShown
35+
36+ function test_defaults() {
37+ deviceConfiguration.name = "nonexisting"
38+ compare(deviceConfiguration.primaryOrientation, -1)
39+ compare(deviceConfiguration.landscapeOrientation, Qt.LandscapeOrientation)
40+ compare(deviceConfiguration.portraitOrientation, Qt.PortraitOrientation)
41+ compare(deviceConfiguration.invertedLandscapeOrientation, Qt.InvertedLandscapeOrientation)
42+ compare(deviceConfiguration.invertedPortraitOrientation, Qt.InvertedPortraitOrientation)
43+ compare(deviceConfiguration.supportedOrientations, Qt.PortraitOrientation
44+ | Qt.InvertedPortraitOrientation
45+ | Qt.LandscapeOrientation
46+ | Qt.InvertedLandscapeOrientation)
47+ }
48+ }
49+}
tests/qmltests/tst_DisabledScreenNotice.qml+76−2
@@ -15,18 +15,62 @@
1515 */
1616
1717import QtQuick 2.4
18+import QtQuick.Layouts 1.1
1819import QtTest 1.0
1920import Unity.Test 0.1
21+import Ubuntu.Components 1.3
22+import Ubuntu.Components.ListItems 1.3
2023import "../../qml"
2124
2225Item {
2326 id: root
24− width: units.gu(40)
27+ width: units.gu(80)
2528 height: units.gu(70)
2629
2730 DisabledScreenNotice {
2831 id: touchScreenPad
2932 anchors.fill: parent
33+ anchors.rightMargin: units.gu(40)
34+
35+ screen: QtObject {
36+ property int orientation: {
37+ switch (orientationSelector.selectedIndex) {
38+ case 0:
39+ return Qt.PortraitOrientation;
40+ case 1:
41+ return Qt.LandscapeOrientation;
42+ case 2:
43+ return Qt.InvertedPortraitOrientation;
44+ case 3:
45+ return Qt.InvertedLandscapeOrientation;
46+ }
47+ }
48+ }
49+ orientationLock: QtObject {
50+ property bool enabled: orientationLockCheckBox.checked
51+ }
52+ }
53+
54+ Column {
55+ anchors.fill: parent
56+ anchors.leftMargin: units.gu(40)
57+
58+ ItemSelector {
59+ id: orientationSelector
60+ model: ["Portrait", "Landscape", "InvertedPortrait", "InvertedLandscape"]
61+ selectedIndex: 0
62+ }
63+
64+ RowLayout {
65+ CheckBox {
66+ id: orientationLockCheckBox
67+ checked: false
68+ }
69+ Label {
70+ text: "Rotation lock"
71+ Layout.fillWidth: true
72+ }
73+ }
3074 }
3175
3276 UnityTestCase {
@@ -36,10 +80,40 @@ Item {
3680
3781 function test_mouseAreaHidesOnFirstTap() {
3882 var noticeArea = findChild(touchScreenPad, "infoNoticeArea")
39− print("noticeArea", noticeArea.visible)
4083 compare(noticeArea.visible, true)
4184 tap(root)
4285 tryCompare(noticeArea, "visible", false)
4386 }
87+
88+ function test_rotation_data() {
89+ return [
90+ {tag: "portrait", selectedOrientation: 0, expectedAngle: 0},
91+ {tag: "landscape", selectedOrientation: 1, expectedAngle: 270},
92+ {tag: "invertedportrait", selectedOrientation: 2, expectedAngle: 180},
93+ {tag: "invertetlandscape", selectedOrientation: 3, expectedAngle: 90}
94+ ];
95+ }
96+
97+ function test_rotation(data) {
98+ var content = findChild(touchScreenPad, "contentContainer");
99+
100+ var oldRotation = content.rotation;
101+
102+ // Turn on orientation lock
103+ orientationLockCheckBox.checked = true;
104+
105+ // simulate the rotation
106+ orientationSelector.selectedIndex = data.selectedOrientation;
107+
108+ // Make sure it is still in the old rotation (as we have the lock turned on)
109+ expectFail("", "Rotation lock set. Expecting automatic rotation to fail.");
110+ tryCompareFunction(function() { return content.rotation != oldRotation; }, true, 300);
111+
112+ // Now uncheck the rotation lock
113+ orientationLockCheckBox.checked = false;
114+
115+ // And make sure it catches up to the expected angle
116+ tryCompare(content, "rotation", data.expectedAngle);
117+ }
44118 }
45119}
tests/qmltests/tst_OrientedShell.qml+56−12
@@ -232,6 +232,9 @@ Rectangle {
232232 onClicked: {
233233 orientedShellLoader.item.physicalOrientation = root.physicalOrientation0;
234234 }
235+ color: orientedShellLoader.item && orientedShellLoader.item.physicalOrientation === root.physicalOrientation0 ?
236+ UbuntuColors.green :
237+ __styleInstance.defaultColor
235238 }
236239 Button {
237240 id: rotate90Button
@@ -240,6 +243,9 @@ Rectangle {
240243 onClicked: {
241244 orientedShellLoader.item.physicalOrientation = root.physicalOrientation90;
242245 }
246+ color: orientedShellLoader.item && orientedShellLoader.item.physicalOrientation === root.physicalOrientation90 ?
247+ UbuntuColors.green :
248+ __styleInstance.defaultColor
243249 }
244250 Button {
245251 id: rotate180Button
@@ -248,6 +254,9 @@ Rectangle {
248254 onClicked: {
249255 orientedShellLoader.item.physicalOrientation = root.physicalOrientation180;
250256 }
257+ color: orientedShellLoader.item && orientedShellLoader.item.physicalOrientation === root.physicalOrientation180 ?
258+ UbuntuColors.green :
259+ __styleInstance.defaultColor
251260 }
252261 Button {
253262 id: rotate270Button
@@ -256,6 +265,9 @@ Rectangle {
256265 onClicked: {
257266 orientedShellLoader.item.physicalOrientation = root.physicalOrientation270;
258267 }
268+ color: orientedShellLoader.item && orientedShellLoader.item.physicalOrientation === root.physicalOrientation270 ?
269+ UbuntuColors.green :
270+ __styleInstance.defaultColor
259271 }
260272 RowLayout {
261273 Layout.fillWidth: true
@@ -314,7 +326,7 @@ Rectangle {
314326 checked: false
315327 activeFocusOnPress: false
316328 onCheckedChanged: {
317− var surface = SurfaceManager.inputMethodSurface();
329+ var surface = SurfaceManager.inputMethodSurface;
318330 if (checked) {
319331 surface.setState(Mir.RestoredState);
320332 } else {
@@ -471,14 +483,46 @@ Rectangle {
471483 tearDown();
472484 }
473485
474− function test_appSupportingOnlyPrimaryOrientationMakesShellStayPut_data() {
486+ function test_appSupportingOnlyPrimaryOrientationMakesPhoneShellStayPut() {
487+ loadShell("mako");
488+
489+ // unity8-dash supports only primary orientation and should be already running
490+ var dashAppWindow = findChild(shell, "appWindow_unity8-dash");
491+ verify(dashAppWindow);
492+ compare(ApplicationManager.focusedApplicationId, "unity8-dash");
493+ var dashApp = dashAppWindow.application
494+ verify(dashApp);
495+ compare(dashApp.rotatesWindowContents, false);
496+ compare(dashApp.supportedOrientations, Qt.PrimaryOrientation);
497+ compare(dashApp.stage, ApplicationInfoInterface.MainStage);
498+
499+ tryCompareFunction(function(){return dashApp.session.lastSurface != null;}, true);
500+ verify(checkAppSurfaceOrientation(dashAppWindow, dashApp, root.primaryOrientationAngle));
501+
502+ compare(shell.transformRotationAngle, root.primaryOrientationAngle);
503+ rotateTo(90);
504+
505+ verify(checkAppSurfaceOrientation(dashAppWindow, dashApp, root.primaryOrientationAngle));
506+ compare(shell.transformRotationAngle, root.primaryOrientationAngle);
507+
508+ rotateTo(180);
509+
510+ verify(checkAppSurfaceOrientation(dashAppWindow, dashApp, root.primaryOrientationAngle));
511+ compare(shell.transformRotationAngle, root.primaryOrientationAngle);
512+
513+ rotateTo(270);
514+
515+ verify(checkAppSurfaceOrientation(dashAppWindow, dashApp, root.primaryOrientationAngle));
516+ compare(shell.transformRotationAngle, root.primaryOrientationAngle);
517+ }
518+
519+ function test_appSupportingOnlyPrimaryOrientationWillOnlyRotateInLandscape_data() {
475520 return [
476− {tag: "mako", deviceName: "mako"},
477521 {tag: "manta", deviceName: "manta"},
478522 {tag: "flo", deviceName: "flo"}
479523 ];
480524 }
481− function test_appSupportingOnlyPrimaryOrientationMakesShellStayPut(data) {
525+ function test_appSupportingOnlyPrimaryOrientationWillOnlyRotateInLandscape(data) {
482526 loadShell(data.deviceName);
483527
484528 // unity8-dash supports only primary orientation and should be already running
@@ -502,13 +546,13 @@ Rectangle {
502546
503547 rotateTo(180);
504548
505− verify(checkAppSurfaceOrientation(dashAppWindow, dashApp, root.primaryOrientationAngle));
506− compare(shell.transformRotationAngle, root.primaryOrientationAngle);
549+ verify(checkAppSurfaceOrientation(dashAppWindow, dashApp, root.primaryOrientationAngle + 180));
550+ compare(shell.transformRotationAngle, root.primaryOrientationAngle + 180);
507551
508552 rotateTo(270);
509553
510− verify(checkAppSurfaceOrientation(dashAppWindow, dashApp, root.primaryOrientationAngle));
511− compare(shell.transformRotationAngle, root.primaryOrientationAngle);
554+ verify(checkAppSurfaceOrientation(dashAppWindow, dashApp, root.primaryOrientationAngle + 180));
555+ compare(shell.transformRotationAngle, root.primaryOrientationAngle + 180);
512556 }
513557
514558 function test_greeterRemainsInPrimaryOrientation_data() {
@@ -813,12 +857,12 @@ Rectangle {
813857
814858 var twitterApp = ApplicationManager.startApplication("twitter-webapp");
815859 verify(twitterApp);
860+ twitterApp.stage = ApplicationInfoInterface.SideStage;
816861
817862 // ensure the mock twitter-webapp is as we expect
818863 compare(twitterApp.rotatesWindowContents, false);
819864 compare(twitterApp.supportedOrientations, Qt.PortraitOrientation | Qt.LandscapeOrientation
820865 | Qt.InvertedPortraitOrientation | Qt.InvertedLandscapeOrientation);
821− compare(twitterApp.stage, ApplicationInfoInterface.SideStage);
822866
823867 // Wait until spreadRepeaterConnections hs caught the new SpreadDelegate and
824868 // set up the signalSpy target accordingly.
@@ -852,11 +896,11 @@ Rectangle {
852896
853897 var dialerApp = ApplicationManager.startApplication("dialer-app");
854898 verify(dialerApp);
899+ dialerApp.stage = ApplicationInfoInterface.SideStage;
855900
856901 // ensure the mock dialer-app is as we expect
857902 compare(dialerApp.rotatesWindowContents, false);
858903 compare(dialerApp.supportedOrientations, Qt.PortraitOrientation | Qt.InvertedPortraitOrientation);
859− compare(dialerApp.stage, ApplicationInfoInterface.SideStage);
860904
861905 tryCompareFunction(function(){ return dialerDelegate != null; }, true);
862906 tryCompare(dialerDelegate, "orientationChangesEnabled", true);
@@ -899,11 +943,11 @@ Rectangle {
899943
900944 var dialerApp = ApplicationManager.startApplication("dialer-app");
901945 verify(dialerApp);
946+ dialerApp.stage = ApplicationInfoInterface.SideStage;
902947
903948 // ensure the mock dialer-app is as we expect
904949 compare(dialerApp.rotatesWindowContents, false);
905950 compare(dialerApp.supportedOrientations, Qt.PortraitOrientation | Qt.InvertedPortraitOrientation);
906− compare(dialerApp.stage, ApplicationInfoInterface.SideStage);
907951
908952 tryCompareFunction(function(){ return dialerDelegate != null; }, true);
909953 waitUntilAppDelegateIsFullyInit(dialerDelegate);
@@ -919,12 +963,12 @@ Rectangle {
919963
920964 var twitterApp = ApplicationManager.startApplication("twitter-webapp");
921965 verify(twitterApp);
966+ twitterApp.stage = ApplicationInfoInterface.SideStage;
922967
923968 // ensure the mock twitter-webapp is as we expect
924969 compare(twitterApp.rotatesWindowContents, false);
925970 compare(twitterApp.supportedOrientations, Qt.PortraitOrientation | Qt.LandscapeOrientation
926971 | Qt.InvertedPortraitOrientation | Qt.InvertedLandscapeOrientation);
927− compare(twitterApp.stage, ApplicationInfoInterface.SideStage);
928972
929973 tryCompareFunction(function(){ return twitterDelegate != null; }, true);
930974 waitUntilAppDelegateIsFullyInit(twitterDelegate);
tests/qmltests/tst_Shell.qml+241−59
@@ -26,6 +26,7 @@ import Unity.Application 0.1
2626import Unity.Connectivity 0.1
2727import Unity.Indicators 0.1
2828import Unity.Notifications 1.0
29+import Unity.Launcher 0.1
2930import Unity.Test 0.1
3031import Powerd 0.1
3132import Wizard 0.1 as Wizard
@@ -49,11 +50,6 @@ Rectangle {
4950 shellLoader.active = true;
5051 }
5152
52− MouseArea {
53− id: clickThroughCatcher
54− anchors.fill: shellContainer
55− }
56−
5753 Item {
5854 id: shellContainer
5955 anchors.left: root.left
@@ -121,10 +117,6 @@ Rectangle {
121117 Component.onDestruction: {
122118 shellLoader.itemDestroyed = true;
123119 }
124− Component.onCompleted: {
125− var keyMapper = testCase.findChild(__shell, "physicalKeysMapper");
126− keyMapper.controlInsteadOfAlt = true;
127− }
128120 }
129121 }
130122 }
@@ -213,6 +205,31 @@ Rectangle {
213205 checked: true
214206 color: "white"
215207 }
208+ ListItem.ItemSelector {
209+ id: ctrlModifier
210+ anchors { left: parent.left; right: parent.right }
211+ activeFocusOnPress: false
212+ text: "Ctrl key as"
213+ model: ["Ctrl", "Alt", "Super"]
214+ onSelectedIndexChanged: {
215+ var keyMapper = testCase.findChild(shellContainer, "physicalKeysMapper");
216+ keyMapper.controlInsteadOfAlt = selectedIndex == 1;
217+ keyMapper.controlInsteadOfSuper = selectedIndex == 2;
218+ }
219+ }
220+
221+ Row {
222+ anchors { left: parent.left; right: parent.right }
223+ CheckBox {
224+ id: autohideLauncherCheckbox
225+ onCheckedChanged: {
226+ GSettingsController.setAutohideLauncher(checked)
227+ }
228+ }
229+ Label {
230+ text: "Autohide launcher"
231+ }
232+ }
216233
217234 Label { text: "Applications"; font.bold: true }
218235
@@ -296,12 +313,6 @@ Rectangle {
296313 signalName: "applicationRemoved"
297314 }
298315
299− SignalSpy {
300− id: clickThroughSpy
301− target: clickThroughCatcher
302− signalName: "clicked"
303− }
304−
305316 Telephony.CallEntry {
306317 id: phoneCall
307318 phoneNumber: "+447812221111"
@@ -666,6 +677,7 @@ Rectangle {
666677 function swipeAwayGreeter() {
667678 var greeter = findChild(shell, "greeter");
668679 tryCompare(greeter, "fullyShown", true);
680+ waitForGreeterToStabilize();
669681 removeTimeConstraintsFromDirectionalDragAreas(greeter);
670682
671683 var touchX = shell.width - (shell.edgeSize / 2);
@@ -769,7 +781,7 @@ Rectangle {
769781 loadShell("phone");
770782 swipeAwayGreeter();
771783 var item = findChild(shell, "inputMethod");
772− var surface = SurfaceManager.inputMethodSurface();
784+ var surface = SurfaceManager.inputMethodSurface;
773785
774786 surface.setState(Mir.MinimizedState);
775787 tryCompare(item, "visible", false);
@@ -840,7 +852,6 @@ Rectangle {
840852 function test_launchedAppHasActiveFocus(data) {
841853 loadShell(data.formFactor);
842854 shell.usageScenario = data.usageScenario;
843− waitForGreeterToStabilize();
844855 swipeAwayGreeter();
845856
846857 var webApp = ApplicationManager.startApplication("webbrowser-app");
@@ -908,6 +919,7 @@ Rectangle {
908919 function dragLauncherIntoView() {
909920 var launcher = findChild(shell, "launcher");
910921 var launcherPanel = findChild(launcher, "launcherPanel");
922+ waitForRendering(launcher);
911923 verify(launcherPanel.x = - launcherPanel.width);
912924
913925 var touchStartX = 2;
@@ -1419,9 +1431,17 @@ Rectangle {
14191431 tryCompare(galleryApp, "requestedState", ApplicationInfoInterface.RequestedRunning);
14201432 }
14211433
1422− function test_altTabSwitchesFocus() {
1423− loadShell("desktop");
1424− shell.usageScenario = "desktop"
1434+ function test_altTabSwitchesFocus_data() {
1435+ return [
1436+ { tag: "windowed", shellType: "desktop" },
1437+ { tag: "staged", shellType: "phone" },
1438+ { tag: "sidestaged", shellType: "tablet" }
1439+ ];
1440+ }
1441+
1442+ function test_altTabSwitchesFocus(data) {
1443+ loadShell(data.shellType);
1444+ shell.usageScenario = data.shellType;
14251445 waitForRendering(root)
14261446
14271447 var desktopStage = findChild(shell, "stage");
@@ -1436,21 +1456,13 @@ Rectangle {
14361456
14371457 // Do a quick alt-tab and see if focus changes
14381458 tryCompare(app3.session.lastSurface, "activeFocus", true)
1439− keyClick(Qt.Key_Tab, Qt.ControlModifier)
1459+ keyClick(Qt.Key_Tab, Qt.AltModifier)
14401460 tryCompare(app2.session.lastSurface, "activeFocus", true)
14411461
1442− var desktopSpread = findChild(shell, "spread")
1443−
1444− tryCompare(desktopSpread, "state", "")
1445−
1446− // Just press Alt, make sure the spread comes up
1447− keyPress(Qt.Key_Control);
1462+ // Press Alt+Tab
1463+ keyPress(Qt.Key_Alt);
14481464 keyClick(Qt.Key_Tab);
1449− tryCompare(desktopSpread, "state", "altTab")
1450−
1451− // Release control, check if spread disappears
1452− keyRelease(Qt.Key_Control)
1453− tryCompare(desktopSpread, "state", "")
1465+ keyRelease(Qt.Key_Alt)
14541466
14551467 // Focus should have switched back now
14561468 tryCompare(app3.session.lastSurface, "activeFocus", true)
@@ -1477,7 +1489,7 @@ Rectangle {
14771489 tryCompare(desktopSpread, "state", "")
14781490
14791491 // Just press Alt, make sure the spread comes up
1480− keyPress(Qt.Key_Control);
1492+ keyPress(Qt.Key_Alt);
14811493 keyClick(Qt.Key_Tab);
14821494 tryCompare(desktopSpread, "state", "altTab")
14831495 tryCompare(spreadRepeater, "highlightedIndex", 1)
@@ -1498,7 +1510,7 @@ Rectangle {
14981510 tryCompare(spreadRepeater, "highlightedIndex", 0)
14991511
15001512 // Release control, check if spread disappears
1501− keyRelease(Qt.Key_Control)
1513+ keyRelease(Qt.Key_Alt)
15021514 tryCompare(desktopSpread, "state", "")
15031515
15041516 // Make sure that after wrapping around once, we have the same one focused as at the beginning
@@ -1511,7 +1523,7 @@ Rectangle {
15111523 var spreadRepeater = findInvisibleChild(shell, "spreadRepeater");
15121524 verify(spreadRepeater !== null);
15131525
1514− keyPress(Qt.Key_Control)
1526+ keyPress(Qt.Key_Alt)
15151527 keyClick(Qt.Key_Tab);
15161528 tryCompare(spreadRepeater, "highlightedIndex", 1);
15171529
@@ -1533,7 +1545,7 @@ Rectangle {
15331545 keyClick(Qt.Key_Backtab);
15341546 tryCompare(spreadRepeater, "highlightedIndex", 1);
15351547
1536− keyRelease(Qt.Key_Control);
1548+ keyRelease(Qt.Key_Alt);
15371549 }
15381550
15391551 function test_highlightFollowsMouse() {
@@ -1542,7 +1554,7 @@ Rectangle {
15421554 var spreadRepeater = findInvisibleChild(shell, "spreadRepeater");
15431555 verify(spreadRepeater !== null);
15441556
1545− keyPress(Qt.Key_Control)
1557+ keyPress(Qt.Key_Alt)
15461558 keyClick(Qt.Key_Tab);
15471559
15481560 tryCompare(spreadRepeater, "highlightedIndex", 1);
@@ -1561,7 +1573,7 @@ Rectangle {
15611573
15621574 verify(y < 4000);
15631575
1564− keyRelease(Qt.Key_Control);
1576+ keyRelease(Qt.Key_Alt);
15651577 }
15661578
15671579 function test_closeFromSpread() {
@@ -1570,7 +1582,7 @@ Rectangle {
15701582 var spreadRepeater = findInvisibleChild(shell, "spreadRepeater");
15711583 verify(spreadRepeater !== null);
15721584
1573− keyPress(Qt.Key_Control)
1585+ keyPress(Qt.Key_Alt)
15741586 keyClick(Qt.Key_Tab);
15751587
15761588 appRemovedSpy.clear();
@@ -1597,7 +1609,7 @@ Rectangle {
15971609 tryCompare(appRemovedSpy, "count", 1)
15981610 compare(appRemovedSpy.signalArguments[0][0], closedAppId);
15991611
1600− keyRelease(Qt.Key_Control);
1612+ keyRelease(Qt.Key_Alt);
16011613 }
16021614
16031615 function test_selectFromSpreadWithMouse_data() {
@@ -1617,7 +1629,7 @@ Rectangle {
16171629 var spreadRepeater = findInvisibleChild(shell, "spreadRepeater");
16181630 verify(spreadRepeater !== null);
16191631
1620− keyPress(Qt.Key_Control)
1632+ keyPress(Qt.Key_Alt)
16211633 keyClick(Qt.Key_Tab);
16221634
16231635 var focusAppId = ApplicationManager.get(2).appId;
@@ -1644,7 +1656,7 @@ Rectangle {
16441656 tryCompare(stage, "state", "");
16451657 tryCompare(ApplicationManager, "focusedApplicationId", focusAppId);
16461658
1647− keyRelease(Qt.Key_Control);
1659+ keyRelease(Qt.Key_Alt);
16481660 }
16491661
16501662 function test_progressiveAutoScrolling() {
@@ -1653,7 +1665,7 @@ Rectangle {
16531665 var appRepeater = findInvisibleChild(shell, "appRepeater");
16541666 verify(appRepeater !== null);
16551667
1656− keyPress(Qt.Key_Control)
1668+ keyPress(Qt.Key_Alt)
16571669 keyClick(Qt.Key_Tab);
16581670
16591671 var spreadFlickable = findChild(shell, "spreadFlickable")
@@ -1664,7 +1676,7 @@ Rectangle {
16641676 var x = 0;
16651677 var y = shell.height * .5
16661678 mouseMove(shell, x, y)
1667− while (x <= spreadFlickable.width) {
1679+ while (x <= shell.width) {
16681680 x+=10;
16691681 mouseMove(shell, x, y)
16701682 wait(0); // spin the loop so bindings get evaluated
@@ -1679,7 +1691,7 @@ Rectangle {
16791691 }
16801692 tryCompare(spreadFlickable, "contentX", 0);
16811693
1682− keyRelease(Qt.Key_Control);
1694+ keyRelease(Qt.Key_Alt);
16831695 }
16841696
16851697 // This makes sure the hoverMouseArea is set to invisible AND disabled
@@ -1691,13 +1703,13 @@ Rectangle {
16911703 tryCompare(hoverMouseArea, "enabled", false)
16921704 tryCompare(hoverMouseArea, "visible", false)
16931705
1694− keyPress(Qt.Key_Control)
1706+ keyPress(Qt.Key_Alt)
16951707 keyClick(Qt.Key_Tab);
16961708
16971709 tryCompare(hoverMouseArea, "enabled", true)
16981710 tryCompare(hoverMouseArea, "visible", true)
16991711
1700− keyRelease(Qt.Key_Control)
1712+ keyRelease(Qt.Key_Alt)
17011713
17021714 tryCompare(hoverMouseArea, "enabled", false)
17031715 tryCompare(hoverMouseArea, "visible", false)
@@ -1714,7 +1726,7 @@ Rectangle {
17141726 var appRepeater = findInvisibleChild(shell, "appRepeater");
17151727 verify(appRepeater !== null);
17161728
1717− keyPress(Qt.Key_Control)
1729+ keyPress(Qt.Key_Alt)
17181730 keyClick(Qt.Key_Tab);
17191731
17201732 tryCompare(spreadRepeater, "highlightedIndex", 1);
@@ -1735,33 +1747,46 @@ Rectangle {
17351747
17361748 verify(y < 4000);
17371749
1738− keyRelease(Qt.Key_Control);
1750+ keyRelease(Qt.Key_Alt);
17391751 }
17401752
1741− function test_focusAppFromLauncherExitsSpread() {
1742− loadDesktopShellWithApps()
1753+ function test_focusAppFromLauncherExitsSpread_data() {
1754+ return [
1755+ {tag: "autohide launcher", launcherLocked: false },
1756+ {tag: "locked launcher", launcherLocked: true }
1757+ ]
1758+ }
17431759
1744− var desktopSpread = findChild(shell, "spread");
1760+ function test_focusAppFromLauncherExitsSpread(data) {
1761+ loadDesktopShellWithApps()
17451762 var launcher = findChild(shell, "launcher");
1763+ var desktopSpread = findChild(shell, "spread");
17461764 var bfb = findChild(launcher, "buttonShowDashHome");
17471765
1748− keyPress(Qt.Key_Control)
1766+ GSettingsController.setAutohideLauncher(!data.launcherLocked);
1767+ waitForRendering(shell);
1768+
1769+ keyPress(Qt.Key_Alt)
17491770 keyClick(Qt.Key_Tab);
17501771
17511772 tryCompare(desktopSpread, "state", "altTab")
17521773
1753− revealLauncherByEdgePushWithMouse();
1754− tryCompare(launcher, "x", 0);
1755− mouseMove(bfb, bfb.width / 2, bfb.height / 2)
1756− waitForRendering(shell)
1774+ if (!data.launcherLocked) {
1775+ revealLauncherByEdgePushWithMouse();
1776+ tryCompare(launcher, "x", 0);
1777+ mouseMove(bfb, bfb.width / 2, bfb.height / 2)
1778+ waitForRendering(shell)
1779+ }
17571780
17581781 mouseClick(bfb, bfb.width / 2, bfb.height / 2)
1759− tryCompare(launcher, "state", "")
1782+ if (!data.launcherLocked) {
1783+ tryCompare(launcher, "state", "")
1784+ }
17601785 tryCompare(desktopSpread, "state", "")
17611786
17621787 tryCompare(ApplicationManager, "focusedApplicationId", "unity8-dash")
17631788
1764− keyRelease(Qt.Key_Control);
1789+ keyRelease(Qt.Key_Alt);
17651790 }
17661791
17671792 // regression test for http://pad.lv/1443319
@@ -1849,6 +1874,31 @@ Rectangle {
18491874 compare(appDelegate.y >= PanelState.panelHeight, true);
18501875 }
18511876
1877+ function test_cantResizeWindowUnderPanel() {
1878+ loadShell("desktop");
1879+ shell.usageScenario = "desktop";
1880+ waitForRendering(shell);
1881+
1882+ var app = ApplicationManager.startApplication("dialer-app")
1883+ waitUntilAppWindowIsFullyLoaded(app);
1884+
1885+ var appContainer = findChild(shell, "appContainer");
1886+ var appDelegate = findChild(appContainer, "appDelegate_dialer-app");
1887+ var decoration = findChild(appDelegate, "appWindowDecoration_dialer-app");
1888+ verify(decoration);
1889+
1890+ // move it away from launcher and panel
1891+ appDelegate.x = units.gu(10)
1892+ appDelegate.y = units.gu(10)
1893+
1894+ // drag-resize the area up
1895+ mousePress(decoration, decoration.width/2, -units.gu(1));
1896+ mouseMove(decoration, decoration.width/2, -units.gu(100));
1897+
1898+ // verify we don't go past the panel
1899+ compare(appDelegate.y >= PanelState.panelHeight, true);
1900+ }
1901+
18521902 function test_restoreWindowStateFixesIfUnderPanel() {
18531903 loadDesktopShellWithApps();
18541904 var appRepeater = findChild(shell, "appRepeater")
@@ -1984,6 +2034,138 @@ Rectangle {
19842034 }
19852035 }
19862036
2037+ function test_superTabToCycleLauncher_data() {
2038+ return [
2039+ {tag: "autohide launcher", launcherLocked: false},
2040+ {tag: "locked launcher", launcherLocked: true}
2041+ ]
2042+ }
2043+
2044+ function test_superTabToCycleLauncher(data) {
2045+ loadShell("desktop");
2046+ shell.usageScenario = "desktop";
2047+ GSettingsController.setAutohideLauncher(!data.launcherLocked);
2048+ waitForRendering(shell);
2049+
2050+ var launcher = findChild(shell, "launcher");
2051+ var launcherPanel = findChild(launcher, "launcherPanel");
2052+ var firstAppInLauncher = LauncherModel.get(0).appId;
2053+
2054+ compare(launcher.state, data.launcherLocked ? "visible": "");
2055+ compare(launcherPanel.highlightIndex, -2);
2056+ compare(ApplicationManager.focusedApplicationId, "unity8-dash");
2057+
2058+ // Use Super + Tab Tab to cycle to the first entry in the launcher
2059+ keyPress(Qt.Key_Super_L, Qt.MetaModifier);
2060+ keyClick(Qt.Key_Tab);
2061+ tryCompare(launcher, "state", "visible");
2062+ tryCompare(launcherPanel, "highlightIndex", -1);
2063+ keyClick(Qt.Key_Tab);
2064+ tryCompare(launcherPanel, "highlightIndex", 0);
2065+ keyRelease(Qt.Key_Super_L, Qt.MetaModifier);
2066+ tryCompare(launcher, "state", data.launcherLocked ? "visible" : "");
2067+ tryCompare(launcherPanel, "highlightIndex", -2);
2068+ tryCompare(ApplicationManager, "focusedApplicationId", firstAppInLauncher);
2069+
2070+ // Now go back to the dash
2071+ keyPress(Qt.Key_Super_L, Qt.MetaModifier);
2072+ keyClick(Qt.Key_Tab);
2073+ tryCompare(launcher, "state", "visible");
2074+ tryCompare(launcherPanel, "highlightIndex", -1);
2075+ keyRelease(Qt.Key_Super_L, Qt.MetaModifier);
2076+ tryCompare(launcher, "state", data.launcherLocked ? "visible" : "");
2077+ tryCompare(launcherPanel, "highlightIndex", -2);
2078+ tryCompare(ApplicationManager, "focusedApplicationId", "unity8-dash");
2079+ }
2080+
2081+ function test_longpressSuperOpensLauncher() {
2082+ loadShell("desktop");
2083+ var launcher = findChild(shell, "launcher");
2084+ var shortcutHint = findChild(findChild(launcher, "launcherDelegate0"), "shortcutHint")
2085+
2086+ compare(launcher.state, "");
2087+ keyPress(Qt.Key_Super_L, Qt.MetaModifier);
2088+ tryCompare(launcher, "state", "visible");
2089+ tryCompare(shortcutHint, "visible", true);
2090+
2091+ keyRelease(Qt.Key_Super_L, Qt.MetaModifier);
2092+ tryCompare(launcher, "state", "");
2093+ tryCompare(shortcutHint, "visible", false);
2094+ }
2095+
2096+ function test_metaNumberLaunchesFromLauncher_data() {
2097+ return [
2098+ {tag: "Meta+1", key: Qt.Key_1, index: 0},
2099+ {tag: "Meta+2", key: Qt.Key_2, index: 1},
2100+ {tag: "Meta+4", key: Qt.Key_5, index: 4},
2101+ {tag: "Meta+0", key: Qt.Key_0, index: 9},
2102+ ]
2103+ }
2104+
2105+ function test_metaNumberLaunchesFromLauncher(data) {
2106+ loadShell("desktop");
2107+ var launcher = findChild(shell, "launcher");
2108+ var appId = LauncherModel.get(data.index).appId;
2109+ waitForRendering(shell);
2110+
2111+ keyClick(data.key, Qt.MetaModifier);
2112+ tryCompare(ApplicationManager, "focusedApplicationId", appId);
2113+ }
2114+
2115+ function test_altF1OpensLauncherForKeyboardNavigation() {
2116+ loadShell("desktop");
2117+ waitForRendering(shell);
2118+ var launcher = findChild(shell, "launcher");
2119+
2120+ keyClick(Qt.Key_F1, Qt.AltModifier);
2121+ tryCompare(launcher, "state", "visible");
2122+ tryCompare(launcher, "focus", true)
2123+ }
2124+
2125+ function test_lockedOutLauncherAddsMarginsToMaximized() {
2126+ loadShell("desktop");
2127+ shell.usageScenario = "desktop";
2128+ waitForRendering(shell);
2129+ var appContainer = findChild(shell, "appContainer");
2130+ var launcher = findChild(shell, "launcher");
2131+
2132+ var app = ApplicationManager.startApplication("music-app");
2133+ waitUntilAppWindowIsFullyLoaded(app);
2134+ var appDelegate = findChild(appContainer, "appDelegate_music-app");
2135+ appDelegate.maximize();
2136+ tryCompare(appDelegate, "visuallyMaximized", true);
2137+ waitForRendering(shell);
2138+
2139+ GSettingsController.setAutohideLauncher(true);
2140+ waitForRendering(shell)
2141+ var hiddenSize = appDelegate.width;
2142+
2143+ GSettingsController.setAutohideLauncher(false);
2144+ waitForRendering(shell)
2145+ var shownSize = appDelegate.width;
2146+
2147+ compare(shownSize + launcher.panelWidth, hiddenSize);
2148+ }
2149+
2150+ function test_fullscreenAppHidesLockedOutLauncher() {
2151+ loadShell("desktop");
2152+ shell.usageScenario = "desktop";
2153+
2154+ var launcher = findChild(shell, "launcher");
2155+ var launcherPanel = findChild(launcher, "launcherPanel");
2156+
2157+ GSettingsController.setAutohideLauncher(false);
2158+ waitForRendering(shell)
2159+
2160+ tryCompare(launcher, "lockedVisible", true);
2161+
2162+ var cameraApp = ApplicationManager.startApplication("camera-app");
2163+ waitUntilAppWindowIsFullyLoaded(cameraApp);
2164+
2165+ tryCompare(launcher, "lockedVisible", false);
2166+ }
2167+
2168+
19872169 function test_inputEventsOnEdgesEndUpInAppSurface_data() {
19882170 return [
19892171 { tag: "phone", repeaterName: "spreadRepeater" },
tests/utils/modules/Unity/Test/UnityTestCase.qml+67−11
@@ -313,7 +313,10 @@ TestCase {
313313 // perform a drag in the given direction until the given condition is true
314314 // The condition is a function to be evaluated after every step
315315 function touchDragUntil(item, startX, startY, stepX, stepY, condition) {
316+ multiTouchDragUntil([0], item, startX, startY, stepX, stepY, condition);
317+ }
316318
319+ function multiTouchDragUntil(touchIds, item, startX, startY, stepX, stepY, condition) {
317320 var root = fetchRootItem(item);
318321 var pos = item.mapToItem(root, startX, startY);
319322
@@ -326,7 +329,9 @@ TestCase {
326329 stepY = stepEnd.y - stepStart.y;
327330
328331 var event = touchEvent(item)
329− event.press(0 /* touchId */, pos.x, pos.y)
332+ for (var i = 0; i < touchIds.length; i++) {
333+ event.press(touchIds[i], pos.x, pos.y)
334+ }
330335 event.commit()
331336
332337 // we have to stop at some point
@@ -341,23 +346,30 @@ TestCase {
341346 pos.y += stepY;
342347
343348 event = touchEvent(item);
344− event.move(0 /* touchId */, pos.x, pos.y);
349+ for (i = 0; i < touchIds.length; i++) {
350+ event.move(touchIds[i], pos.x, pos.y);
351+ }
345352 event.commit();
346353
347354 stepsDone += 1;
348355 }
349356
350357 event = touchEvent(item)
351− event.release(0 /* touchId */, pos.x, pos.y)
358+ for (i = 0; i < touchIds.length; i++) {
359+ event.release(touchIds[i], pos.x, pos.y)
360+ }
352361 event.commit()
353362 }
354363
355− function touchMove(item, tox, toy) {
364+ function touchMove(item, tox, toy) { multiTouchMove(0, item, tox, toy); }
365+
366+ function multiTouchMove(touchId, item, tox, toy) {
367+ if (typeof touchId !== "number") touchId = 0;
356368 var root = fetchRootItem(item)
357369 var rootPoint = item.mapToItem(root, tox, toy)
358370
359371 var event = touchEvent(item);
360− event.move(0 /* touchId */, rootPoint.x, rootPoint.y);
372+ event.move(touchId, rootPoint.x, rootPoint.y);
361373 event.commit();
362374 }
363375
@@ -394,21 +406,55 @@ TestCase {
394406 return item
395407 }
396408
397− function touchPress(item, x, y) {
409+ function touchPress(item, x, y) { touchPress(0, item, x, y, []); }
410+
411+ /*! \brief Release a touch point
412+
413+ \param touchId The touchId to be pressed
414+ \param item The item
415+ \param x The x coordinate of the press, defaults to horizontal center
416+ \param y The y coordinate of the press, defaults to vertical center
417+ \param stationaryPoints An array of touchIds which are "already touched"
418+ */
419+ function multiTouchPress(touchId, item, x, y, stationaryPoints) {
420+ if (typeof touchId !== "number") touchId = 0;
421+ if (typeof x !== "number") x = item.width / 2;
422+ if (typeof y !== "number") y = item.height / 2;
423+ if (typeof stationaryPoints !== "object") stationaryPoints = []
398424 var root = fetchRootItem(item)
399425 var rootPoint = item.mapToItem(root, x, y)
400426
401427 var event = touchEvent(item)
402− event.press(0 /* touchId */, rootPoint.x, rootPoint.y)
428+ event.press(touchId, rootPoint.x, rootPoint.y)
429+ for (var i = 0; i < stationaryPoints.length; i++) {
430+ event.stationary(stationaryPoints[i]);
431+ }
403432 event.commit()
404433 }
405434
406− function touchRelease(item, x, y) {
435+ function touchRelease(item, x, y) { multiTouchRelease(0, item, x, y, []); }
436+
437+ /*! \brief Release a touch point
438+
439+ \param touchId The touchId to be released
440+ \param item The item
441+ \param x The x coordinate of the release, defaults to horizontal center
442+ \param y The y coordinate of the release, defaults to vertical center
443+ \param stationaryPoints An array of touchIds which are "still touched"
444+ */
445+ function multiTouchRelease(touchId, item, x, y, stationaryPoints) {
446+ if (typeof touchId !== "number") touchId = 0;
447+ if (typeof x !== "number") x = item.width / 2;
448+ if (typeof y !== "number") y = item.height / 2;
449+ if (typeof stationaryPoints !== "object") stationaryPoints = []
407450 var root = fetchRootItem(item)
408451 var rootPoint = item.mapToItem(root, x, y)
409452
410453 var event = touchEvent(item)
411− event.release(0 /* touchId */, rootPoint.x, rootPoint.y)
454+ event.release(touchId, rootPoint.x, rootPoint.y)
455+ for (var i = 0; i < stationaryPoints.length; i++) {
456+ event.stationary(stationaryPoints[i]);
457+ }
412458 event.commit()
413459 }
414460
@@ -419,6 +465,11 @@ TestCase {
419465 \param y The y coordinate of the tap, defaults to vertical center
420466 */
421467 function tap(item, x, y) {
468+ multiTouchTap([0], item, x, y);
469+ }
470+
471+ function multiTouchTap(touchIds, item, x, y) {
472+ if (typeof touchIds !== "object") touchIds = [0];
422473 if (typeof x !== "number") x = item.width / 2;
423474 if (typeof y !== "number") y = item.height / 2;
424475
@@ -426,14 +477,19 @@ TestCase {
426477 var rootPoint = item.mapToItem(root, x, y)
427478
428479 var event = touchEvent(item)
429− event.press(0 /* touchId */, rootPoint.x, rootPoint.y)
480+ for (var i = 0; i < touchIds.length; i++) {
481+ event.press(touchIds[i], rootPoint.x, rootPoint.y)
482+ }
430483 event.commit()
431484
432485 event = touchEvent(item)
433− event.release(0 /* touchId */, rootPoint.x, rootPoint.y)
486+ for (i = 0; i < touchIds.length; i++) {
487+ event.release(touchIds[i], rootPoint.x, rootPoint.y)
488+ }
434489 event.commit()
435490 }
436491
492+
437493 Component.onCompleted: {
438494 var rootItem = parent;
439495 while (rootItem.parent != undefined) {