on xenial help sign in
Merged with trunk.
commita1582195c7c60297b84e25601952dc7961e30c3f authorLeo Arias <leo.arias@canonical.com> dateMon 2 Mar 2015, 22:21 (12 years ago) parentf52a87545c4ba4
105 files changed+4046−2008
cmake/modules/QmlTest.cmake+1−1 data/CMakeLists.txt+1−1 com.canonical.Unity8.gschema.xml → data/com.canonical.Unity8.gschema.xml debian/changelog+41 debian/unity8-common.install+1−1 plugins/AccountsService/AccountsService.qmltypes+3 plugins/Dash/CardAttributes.qml+2 plugins/Dash/CardCreator.js+4 plugins/Dash/Dash.qmltypes+17−1 plugins/Greeter/Unity/Launcher/Launcher.qmltypes+4−1 plugins/LightDM/LightDM.qmltypes+2−2 plugins/LightDM/liblightdm/Greeter.h−2 plugins/ScreenGrabber/ScreenGrabber.qmltypes+1−1 plugins/Ubuntu/Gestures/Gestures.qmltypes+15−1 plugins/Unity/Indicators/Indicators.qmltypes+10−38 plugins/Unity/Launcher/Launcher.qmltypes+3 plugins/Unity/Session/Session.qmltypes+5−1 plugins/Utils/Utils.qmltypes+60−41 plugins/Wizard/Wizard.qmltypes−3 po/de.po+4−4 po/gd.po+27−21 po/lv.po+105−92 po/nl.po+5−3 qml/Components/DelayedLockscreen.qml+3−1 qml/Components/PassphraseLockscreen.qml+2−3 qml/Components/PinLockscreen.qml+3−4 qml/Components/ZoomableImage.qml+14−14 qml/Dash/Dash.qml+2−1 qml/Dash/GenericScopeView.qml+2−1 qml/Greeter/CoverPage.qml+216 qml/Greeter/Greeter.qml+319−147 qml/Greeter/GreeterContent.qml−163 qml/Greeter/Infographics.qml+1 qml/Greeter/LoginList.qml+67−85 qml/Greeter/NarrowView.qml+170 qml/Greeter/WideView.qml+134 qml/Launcher/Launcher.qml+40−3 qml/Panel/IndicatorItem.qml+2 qml/Shell.qml+67−353 qml/Stages/ApplicationWindow.qml+1−1 qml/Stages/PhoneStage.qml+5−1 qml/Wizard/Pages/20-sim.qml+5−1 tests/autopilot/unity8/shell/__init__.py+1 tests/autopilot/unity8/shell/emulators/greeter.py+19−3 tests/autopilot/unity8/shell/emulators/main_window.py+17−6 tests/autopilot/unity8/shell/tests/__init__.py+2−3 tests/autopilot/unity8/shell/tests/test_lock_screen.py+8−12 tests/autopilot/unity8/shell/tests/test_notifications.py+19 tests/mocks/AccountsService/AccountsService.qmltypes+3 tests/mocks/LightDM/LightDM.qmltypes+33−13 tests/mocks/LightDM/liblightdm/Greeter.h−2 tests/mocks/MeeGo/QOfono/CMakeLists.txt+1 tests/mocks/MeeGo/QOfono/MockOfonoManager.qml−23 tests/mocks/MeeGo/QOfono/MockOfonoSimManager.qml+12−2 tests/mocks/MeeGo/QOfono/MockQOfono.cpp+41−5 tests/mocks/MeeGo/QOfono/MockQOfono.h+12−3 tests/mocks/MeeGo/QOfono/MockQOfonoManager.cpp+66 tests/mocks/MeeGo/QOfono/MockQOfonoManager.h+49 tests/mocks/MeeGo/QOfono/QOfono.qmltypes+30 tests/mocks/MeeGo/QOfono/plugin.cpp+3−1 tests/mocks/MeeGo/QOfono/qmldir−1 tests/mocks/QMenuModel/QMenuModel.qmltypes+33 tests/mocks/Ubuntu/SystemSettings/LanguagePlugin/LanguagePlugin.qmltypes+10 tests/mocks/Ubuntu/SystemSettings/SecurityPrivacy/SecurityPrivacy.qmltypes+39 tests/mocks/Unity/Application/Application.qmltypes+42 tests/mocks/Unity/Indicators/Indicators.qmltypes+7−33 tests/mocks/Unity/Launcher/Launcher.qmltypes+3 tests/mocks/Unity/Notifications/MockActionModel.cpp+1−1 tests/mocks/Unity/Notifications/MockNotification.cpp+4 tests/mocks/Unity/Notifications/MockNotification.h+2−1 tests/mocks/Unity/Notifications/MockNotificationModel.cpp+1−1 tests/mocks/Unity/Notifications/Notifications.qmltypes+139 tests/mocks/Unity/Unity.qmltypes+21 tests/mocks/Wizard/Wizard.qmltypes+4−3 tests/plugins/Dash/cardcreator/2.res+1 tests/plugins/Dash/cardcreator/3.res+1 tests/plugins/Dash/cardcreator/4.res+1 tests/plugins/Dash/cardcreator/5.res+1 tests/plugins/Dash/cardcreator/6.res+2 tests/plugins/Dash/cardcreator/7.res+1 tests/plugins/LightDM/CMakeLists.txt+19−4 tests/plugins/LightDM/usersmodel.cpp+84 tests/plugins/Ubuntu/Gestures/tst_DirectionalDragArea.cpp+9 tests/plugins/Ubuntu/Gestures/tst_TouchGate.cpp+7 tests/qmltests/CMakeLists.txt+4−3 tests/qmltests/Dash/Previews/tst_PreviewRatingInput.qml+1−1 tests/qmltests/Dash/tst_Dash.qml+22 tests/qmltests/Greeter/TestView.qml+99 tests/qmltests/Greeter/tst_Greeter.qml+472 tests/qmltests/Greeter/tst_MultiGreeter.qml−475 tests/qmltests/Greeter/tst_NarrowView.qml+522 tests/qmltests/Greeter/tst_SingleGreeter.qml−244 tests/qmltests/Greeter/tst_WideView.qml+519 tests/qmltests/Tutorial/tst_Tutorial.qml+5−4 tests/qmltests/Wizard/tst_Wizard.qml+48−5 tests/qmltests/tst_Shell.qml+65−43 tests/qmltests/tst_ShellWithPin.qml+97−48 tests/qmltests/tst_TabletShell.qml+50−60 tests/utils/modules/Unity/Test/CMakeLists.txt+1 tests/utils/modules/Unity/Test/Test.qmltypes+1 tests/utils/modules/Unity/Test/TouchEventSequenceWrapper.cpp+10−1 tests/utils/modules/Unity/Test/TouchEventSequenceWrapper.h+4−1 tests/utils/modules/Unity/Test/UnityTestCase.qml+11−11 tests/utils/modules/Unity/Test/testutil.cpp+2−2 tests/utils/modules/Unity/Test/testutil.h+1−1
cmake/modules/QmlTest.cmake+1−1
@@ -28,7 +28,7 @@ endif()
2828
2929set(qmlscene_exe ${CMAKE_BINARY_DIR}/tests/uqmlscene/uqmlscene)
3030
31−set(test_env UNITY_TESTING=1)
31+set(test_env LC_ALL=C UNITY_TESTING=1)
3232
3333macro(add_manual_qml_test SUBPATH COMPONENT_NAME)
3434 set(options NO_ADD_TEST NO_TARGETS)
data/CMakeLists.txt+1−1
@@ -9,5 +9,5 @@ install(FILES
99 DESTINATION ${CMAKE_INSTALL_DATADIR}/applications
1010 )
1111
12−# install url dispatcher files
12+install(FILES com.canonical.Unity8.gschema.xml DESTINATION ${CMAKE_INSTALL_DATADIR}/glib-2.0/schemas)
1313install(FILES unity8-dash.url-dispatcher DESTINATION ${CMAKE_INSTALL_DATADIR}/url-dispatcher/urls)
com.canonical.Unity8.gschema.xml → data/com.canonical.Unity8.gschema.xmlrenamed

The content is the same.

debian/changelog+41
@@ -1,3 +1,44 @@
1+unity8 (8.02+15.04.20150226-0ubuntu1) vivid; urgency=medium
2+
3+ [ CI Train Bot ]
4+ * New rebuild forced.
5+
6+ [ Pete Woods ]
7+ * Only set scopes active when the screen is on (LP: #1422879)
8+
9+ -- CI Train Bot <ci-train-bot@canonical.com> Thu, 26 Feb 2015 19:39:54 +0000
10+
11+unity8 (8.02+15.04.20150224-0ubuntu1) vivid; urgency=medium
12+
13+ [ Albert Astals Cid ]
14+ * Dash manage hint should not be visible in previews+test (LP:
15+ #1422260)
16+ * Do not declare the hostname property twice
17+ * Fix some warnings in Mock Notifications
18+ * Make qmluittests pass with Qt 5.4
19+ * Preserve the aspect ratio for the card attribute images (LP:
20+ #1411751)
21+ * Update qmltypes
22+
23+ [ Andrea Cimitan ]
24+ * Add maximumLineCount for subtitle label in cards (LP: #1414870)
25+
26+ [ Michael Terry ]
27+ * Move the gschema file to data/ and install it via cmake, not dpkg.
28+ * Refactor the greeter code to be more compartmented and isolatable.
29+ * Silence a couple qml warnings. (LP: #1423286)
30+ * Wait for libqofono to finish initializing before we decide whether
31+ to skip the wizard's SIM page or not.
32+
33+ [ Michael Zanetti ]
34+ * enable revealing the launcher by hitting the left edge using the
35+ mouse
36+
37+ [ Ying-Chun Liu ]
38+ * Add Autopilot Notification helper.
39+
40+ -- CI Train Bot <ci-train-bot@canonical.com> Tue, 24 Feb 2015 10:17:16 +0000
41+
142unity8 (8.02+15.04.20150216.1-0ubuntu1) vivid; urgency=medium
243
344 [ Andrea Cimitan ]
debian/unity8-common.install+1−1
@@ -1,7 +1,7 @@
1+usr/share/glib-2.0/schemas
12usr/share/locale
23usr/share/unity8/Components
34usr/share/unity8/Dash
45usr/share/unity8/Notifications
56usr/share/unity8/graphics
6−com.canonical.Unity8.gschema.xml usr/share/glib-2.0/schemas/
77var/lib/polkit-1/localauthority/10-vendor.d
plugins/AccountsService/AccountsService.qmltypes+3
@@ -29,5 +29,8 @@ Module {
2929 Property { name: "statsWelcomeScreen"; type: "bool"; isReadonly: true }
3030 Property { name: "passwordDisplayHint"; type: "PasswordDisplayHint"; isReadonly: true }
3131 Property { name: "failedLogins"; type: "uint" }
32+ Property { name: "hereEnabled"; type: "bool" }
33+ Property { name: "hereLicensePath"; type: "string"; isReadonly: true }
34+ Property { name: "hereLicensePathValid"; type: "bool"; isReadonly: true }
3235 }
3336}
plugins/Dash/CardAttributes.qml+2
@@ -49,6 +49,8 @@ GridLayout {
4949 Icon {
5050 id: icon
5151 height: units.gu(2)
52+ // FIXME Workaround for bug https://bugs.launchpad.net/ubuntu/+source/ubuntu-ui-toolkit/+bug/1421293
53+ width: implicitWidth > 0 && implicitHeight > 0 ? (implicitWidth / implicitHeight * height) : implicitWidth
5254 source: "icon" in modelData && modelData["icon"] || ""
5355 color: grid.color
5456 }
plugins/Dash/CardCreator.js+4
@@ -224,6 +224,8 @@ var kTitleLabelCode = 'Label { \n\
224224
225225// %1 is used as extra anchors of emblemIcon
226226// %2 is used as color of emblemIcon
227+// FIXME The width code is a
228+// Workaround for bug https://bugs.launchpad.net/ubuntu/+source/ubuntu-ui-toolkit/+bug/1421293
227229var kEmblemIconCode = 'Icon { \n\
228230 id: emblemIcon; \n\
229231 objectName: "emblemIcon"; \n\
@@ -235,6 +237,7 @@ var kEmblemIconCode = 'Icon { \n\
235237 source: cardData && cardData["emblem"] || ""; \n\
236238 color: %2; \n\
237239 height: source != "" ? titleLabel.font.pixelSize : 0; \n\
240+ width: implicitWidth > 0 && implicitHeight > 0 ? (implicitWidth / implicitHeight * height) : implicitWidth; \n\
238241 }\n';
239242
240243// %1 is used as anchors of touchdown effect
@@ -255,6 +258,7 @@ var kSubtitleLabelCode = 'Label { \n\
255258 anchors { %1 } \n\
256259 anchors.topMargin: units.dp(2); \n\
257260 elide: Text.ElideRight; \n\
261+ maximumLineCount: 1; \n\
258262 fontSize: "x-small"; \n\
259263 font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale); \n\
260264 color: %2; \n\
plugins/Dash/Dash.qmltypes+17−1
@@ -15,9 +15,25 @@ Module {
1515 Property { name: "delegate"; type: "QQmlComponent"; isPointer: true }
1616 Property { name: "columnSpacing"; type: "double" }
1717 Property { name: "rowSpacing"; type: "double" }
18+ Property { name: "cacheBuffer"; type: "int" }
1819 Property { name: "displayMarginBeginning"; type: "double" }
1920 Property { name: "displayMarginEnd"; type: "double" }
2021 }
22+ Component {
23+ name: "CroppedImageSizer"
24+ prototype: "QObject"
25+ exports: ["Dash/CroppedImageSizer 0.1"]
26+ exportMetaObjectRevisions: [0]
27+ Property { name: "source"; type: "QUrl" }
28+ Property { name: "width"; type: "double" }
29+ Property { name: "height"; type: "double" }
30+ Property { name: "sourceSize"; type: "QSize"; isReadonly: true }
31+ Signal { name: "inputParamsChanged" }
32+ Method {
33+ name: "setImageSize"
34+ Parameter { name: "imageSize"; type: "QSize" }
35+ }
36+ }
2137 Component {
2238 name: "HorizontalJournal"
2339 defaultProperty: "data"
@@ -40,7 +56,7 @@ Module {
4056 Property { name: "forceNoClip"; type: "bool" }
4157 Property { name: "stickyHeaderHeight"; type: "int"; isReadonly: true }
4258 Property { name: "headerItemShownHeight"; type: "double"; isReadonly: true }
43− Property { name: "cacheBuffer"; type: "double" }
59+ Property { name: "cacheBuffer"; type: "int" }
4460 Signal { name: "headerChanged" }
4561 Method { name: "positionAtBeginning" }
4662 Method { name: "showHeader" }
plugins/Greeter/Unity/Launcher/Launcher.qmltypes+4−1
@@ -4,7 +4,7 @@ import QtQuick.tooling 1.1
44// It is used for QML tooling purposes only.
55//
66// This file was auto-generated by:
7−// 'qmlplugindump -notrelocatable Unity.Launcher 0.1 plugins'
7+// 'qmlplugindump -notrelocatable Unity.Launcher 0.1 plugins/Greeter'
88
99Module {
1010 Component {
@@ -25,6 +25,7 @@ Module {
2525 name: "requestRemove"
2626 Parameter { name: "appId"; type: "string" }
2727 }
28+ Method { name: "refresh" }
2829 Method {
2930 name: "get"
3031 type: "unity::shell::launcher::LauncherItemInterface*"
@@ -136,6 +137,8 @@ Module {
136137 type: "unity::shell::application::ApplicationManagerInterface"
137138 isPointer: true
138139 }
140+ Property { name: "onlyPinned"; type: "bool" }
141+ Signal { name: "hint" }
139142 Method {
140143 name: "move"
141144 Parameter { name: "oldIndex"; type: "int" }
plugins/LightDM/LightDM.qmltypes+2−2
@@ -114,7 +114,7 @@ Module {
114114 Method { name: "invalidate" }
115115 }
116116 Component {
117− name: "QSortFilterProxyModelQML"
117+ name: "UnitySortFilterProxyModelQML"
118118 prototype: "QSortFilterProxyModel"
119119 Property { name: "model"; type: "QAbstractItemModel"; isPointer: true }
120120 Property { name: "totalCount"; type: "int"; isReadonly: true }
@@ -242,7 +242,7 @@ Module {
242242 }
243243 Component {
244244 name: "UsersModel"
245− prototype: "QSortFilterProxyModelQML"
245+ prototype: "UnitySortFilterProxyModelQML"
246246 exports: ["LightDM/Users 0.1"]
247247 isCreatable: false
248248 isSingleton: true
plugins/LightDM/liblightdm/Greeter.h−2
@@ -47,8 +47,6 @@ class Q_DECL_EXPORT Greeter : public QObject
4747 Q_PROPERTY(bool hasGuestAccount READ hasGuestAccountHint CONSTANT)
4848 Q_PROPERTY(bool locked READ lockHint CONSTANT)
4949
50− Q_PROPERTY(QString hostname READ hostname CONSTANT)
51−
5250 Q_ENUMS(PromptType MessageType)
5351
5452public:
plugins/ScreenGrabber/ScreenGrabber.qmltypes+1−1
@@ -4,7 +4,7 @@ import QtQuick.tooling 1.1
44// It is used for QML tooling purposes only.
55//
66// This file was auto-generated by:
7−// 'qmlplugindump -nonrelocatable ScreenGrabber 0.1 plugins'
7+// 'qmlplugindump -notrelocatable ScreenGrabber 0.1 plugins'
88
99Module {
1010 Component {
plugins/Ubuntu/Gestures/Gestures.qmltypes+15−1
@@ -33,7 +33,8 @@ Module {
3333 "Rightwards": 0,
3434 "Leftwards": 1,
3535 "Downwards": 2,
36− "Upwards": 3
36+ "Upwards": 3,
37+ "Horizontal": 4
3738 }
3839 }
3940 Method {
@@ -151,4 +152,17 @@ Module {
151152 exportMetaObjectRevisions: [0]
152153 Signal { name: "pressedOutside" }
153154 }
155+ Component {
156+ name: "TouchGate"
157+ defaultProperty: "data"
158+ prototype: "QQuickItem"
159+ exports: ["Ubuntu.Gestures/TouchGate 0.1"]
160+ exportMetaObjectRevisions: [0]
161+ Property { name: "targetItem"; type: "QQuickItem"; isPointer: true }
162+ Signal {
163+ name: "targetItemChanged"
164+ Parameter { name: "item"; type: "QQuickItem"; isPointer: true }
165+ }
166+ Signal { name: "pressed" }
167+ }
154168}
plugins/Unity/Indicators/Indicators.qmltypes+10−38
@@ -55,10 +55,15 @@ Module {
5555 exports: ["Unity.Indicators/IndicatorsManager 0.1"]
5656 exportMetaObjectRevisions: [0]
5757 Property { name: "loaded"; type: "bool"; isReadonly: true }
58+ Property { name: "profile"; type: "string" }
5859 Signal {
5960 name: "loadedChanged"
6061 Parameter { type: "bool" }
6162 }
63+ Signal {
64+ name: "profileChanged"
65+ Parameter { type: "string" }
66+ }
6267 Signal {
6368 name: "indicatorLoaded"
6469 Parameter { name: "indicator_name"; type: "string" }
@@ -67,10 +72,7 @@ Module {
6772 name: "indicatorAboutToBeUnloaded"
6873 Parameter { name: "indicator_name"; type: "string" }
6974 }
70− Method {
71− name: "load"
72− Parameter { name: "profile"; type: "string" }
73− }
75+ Method { name: "load" }
7476 Method { name: "unload" }
7577 }
7678 Component {
@@ -79,14 +81,12 @@ Module {
7981 exports: ["Unity.Indicators/IndicatorsModel 0.1"]
8082 exportMetaObjectRevisions: [0]
8183 Property { name: "count"; type: "int"; isReadonly: true }
84+ Property { name: "profile"; type: "string" }
8285 Signal {
8386 name: "indicatorDataChanged"
8487 Parameter { name: "data"; type: "QVariant" }
8588 }
86− Method {
87− name: "load"
88− Parameter { name: "profile"; type: "string" }
89− }
89+ Method { name: "load" }
9090 Method { name: "unload" }
9191 Method {
9292 name: "data"
@@ -106,8 +106,7 @@ Module {
106106 values: {
107107 "Identifier": 0,
108108 "Position": 1,
109− "IndicatorProperties": 2,
110− "IsVisible": 3
109+ "IndicatorProperties": 2
111110 }
112111 }
113112 }
@@ -200,12 +199,6 @@ Module {
200199 }
201200 }
202201 }
203− Component {
204− name: "QAbstractProxyModel"
205− prototype: "QAbstractItemModel"
206− Property { name: "sourceModel"; type: "QAbstractItemModel"; isPointer: true }
207− }
208− Component { name: "QIdentityProxyModel"; prototype: "QAbstractProxyModel" }
209202 Component {
210203 name: "RootStateObject"
211204 prototype: "QObject"
@@ -215,7 +208,7 @@ Module {
215208 Property { name: "rightLabel"; type: "string"; isReadonly: true }
216209 Property { name: "icons"; type: "QStringList"; isReadonly: true }
217210 Property { name: "accessibleName"; type: "string"; isReadonly: true }
218− Property { name: "visible"; type: "bool"; isReadonly: true }
211+ Property { name: "indicatorVisible"; type: "bool"; isReadonly: true }
219212 Signal { name: "updated" }
220213 }
221214 Component {
@@ -268,25 +261,4 @@ Module {
268261 }
269262 Method { name: "pop"; type: "UnityMenuModel*" }
270263 }
271− Component {
272− name: "VisibleIndicatorsModel"
273− prototype: "QIdentityProxyModel"
274− exports: ["Unity.Indicators/VisibleIndicatorsModel 0.1"]
275− exportMetaObjectRevisions: [0]
276− Property { name: "model"; type: "QAbstractItemModel"; isPointer: true }
277− Property { name: "visible"; type: "QVariantMap" }
278− Signal { name: "totalCountChanged" }
279− Method {
280− name: "onBeginRowInserted"
281− Parameter { type: "QModelIndex" }
282− Parameter { name: "start"; type: "int" }
283− Parameter { name: "end"; type: "int" }
284− }
285− Method {
286− name: "onRowInserted"
287− Parameter { type: "QModelIndex" }
288− Parameter { name: "start"; type: "int" }
289− Parameter { name: "end"; type: "int" }
290− }
291− }
292264}
plugins/Unity/Launcher/Launcher.qmltypes+3
@@ -25,6 +25,7 @@ Module {
2525 name: "requestRemove"
2626 Parameter { name: "appId"; type: "string" }
2727 }
28+ Method { name: "refresh" }
2829 Method {
2930 name: "get"
3031 type: "unity::shell::launcher::LauncherItemInterface*"
@@ -136,6 +137,8 @@ Module {
136137 type: "unity::shell::application::ApplicationManagerInterface"
137138 isPointer: true
138139 }
140+ Property { name: "onlyPinned"; type: "bool" }
141+ Signal { name: "hint" }
139142 Method {
140143 name: "move"
141144 Parameter { name: "oldIndex"; type: "int" }
plugins/Unity/Session/Session.qmltypes+5−1
@@ -9,7 +9,7 @@ import QtQuick.tooling 1.1
99Module {
1010 Component {
1111 name: "DBusUnitySessionService"
12− prototype: "QObject"
12+ prototype: "UnityDBusObject"
1313 exports: ["Unity.Session/DBusUnitySessionService 0.1"]
1414 isCreatable: false
1515 isSingleton: true
@@ -33,6 +33,9 @@ Module {
3333 Method { name: "RequestLogout" }
3434 Method { name: "RequestReboot" }
3535 Method { name: "RequestShutdown" }
36+ Method { name: "logout" }
37+ Method { name: "reboot" }
38+ Method { name: "shutdown" }
3639 }
3740 Component {
3841 name: "OrientationLock"
@@ -44,4 +47,5 @@ Module {
4447 Property { name: "enabled"; type: "bool"; isReadonly: true }
4548 Property { name: "savedOrientation"; type: "Qt::ScreenOrientation" }
4649 }
50+ Component { name: "UnityDBusObject"; prototype: "QObject" }
4751}
plugins/Utils/Utils.qmltypes+60−41
@@ -60,9 +60,49 @@ Module {
6060 Method { name: "invalidate" }
6161 }
6262 Component {
63− name: "QSortFilterProxyModelQML"
63+ name: "RelativeTimeFormatter"
64+ prototype: "TimeFormatter"
65+ exports: ["Utils/RelativeTimeFormatter 0.1"]
66+ exportMetaObjectRevisions: [0]
67+ }
68+ Component {
69+ name: "TimeFormatter"
70+ prototype: "QObject"
71+ exports: ["Utils/GDateTimeFormatter 0.1", "Utils/TimeFormatter 0.1"]
72+ exportMetaObjectRevisions: [0, 0]
73+ Property { name: "format"; type: "string" }
74+ Property { name: "timeString"; type: "string"; isReadonly: true }
75+ Property { name: "time"; type: "qlonglong" }
76+ Signal {
77+ name: "formatChanged"
78+ Parameter { name: "format"; type: "string" }
79+ }
80+ Signal {
81+ name: "timeStringChanged"
82+ Parameter { name: "timeString"; type: "string" }
83+ }
84+ Signal {
85+ name: "timeChanged"
86+ Parameter { name: "time"; type: "qlonglong" }
87+ }
88+ }
89+ Component {
90+ name: "UnityMenuModelPaths"
91+ prototype: "QObject"
92+ exports: ["Utils/UnityMenuModelPaths 0.1"]
93+ exportMetaObjectRevisions: [0]
94+ Property { name: "source"; type: "QVariant" }
95+ Property { name: "busName"; type: "QByteArray"; isReadonly: true }
96+ Property { name: "actions"; type: "QVariantMap"; isReadonly: true }
97+ Property { name: "menuObjectPath"; type: "QByteArray"; isReadonly: true }
98+ Property { name: "busNameHint"; type: "QByteArray" }
99+ Property { name: "actionsHint"; type: "QByteArray" }
100+ Property { name: "menuObjectPathHint"; type: "QByteArray" }
101+ }
102+ Component {
103+ name: "UnitySortFilterProxyModelQML"
64104 prototype: "QSortFilterProxyModel"
65− exports: ["Utils/SortFilterProxyModel 0.1"]
105+ exports: ["Utils/UnitySortFilterProxyModel 0.1"]
66106 exportMetaObjectRevisions: [0]
67107 Property { name: "model"; type: "QAbstractItemModel"; isPointer: true }
68108 Property { name: "totalCount"; type: "int"; isReadonly: true }
@@ -107,50 +147,29 @@ Module {
107147 }
108148 }
109149 Component {
110− name: "RelativeTimeFormatter"
111− prototype: "TimeFormatter"
112− exports: ["Utils/RelativeTimeFormatter 0.1"]
150+ name: "WindowKeysFilter"
151+ defaultProperty: "data"
152+ prototype: "QQuickItem"
153+ exports: ["Utils/WindowKeysFilter 0.1"]
113154 exportMetaObjectRevisions: [0]
114155 }
115156 Component {
116− name: "TimeFormatter"
157+ name: "WindowStateStorage"
117158 prototype: "QObject"
118− exports: ["Utils/GDateTimeFormatter 0.1", "Utils/TimeFormatter 0.1"]
119− exportMetaObjectRevisions: [0, 0]
120− Property { name: "format"; type: "string" }
121− Property { name: "timeString"; type: "string"; isReadonly: true }
122− Property { name: "time"; type: "qlonglong" }
123− Signal {
124− name: "formatChanged"
125− Parameter { name: "format"; type: "string" }
126− }
127− Signal {
128− name: "timeStringChanged"
129− Parameter { name: "timeString"; type: "string" }
159+ exports: ["Utils/WindowStateStorage 0.1"]
160+ isCreatable: false
161+ isSingleton: true
162+ exportMetaObjectRevisions: [0]
163+ Method {
164+ name: "saveGeometry"
165+ Parameter { name: "windowId"; type: "string" }
166+ Parameter { name: "rect"; type: "QRect" }
130167 }
131− Signal {
132− name: "timeChanged"
133− Parameter { name: "time"; type: "qlonglong" }
168+ Method {
169+ name: "getGeometry"
170+ type: "QRect"
171+ Parameter { name: "windowId"; type: "string" }
172+ Parameter { name: "defaultValue"; type: "QRect" }
134173 }
135174 }
136− Component {
137− name: "UnityMenuModelPaths"
138− prototype: "QObject"
139− exports: ["Utils/UnityMenuModelPaths 0.1"]
140− exportMetaObjectRevisions: [0]
141− Property { name: "source"; type: "QVariant" }
142− Property { name: "busName"; type: "QByteArray"; isReadonly: true }
143− Property { name: "actions"; type: "QVariantMap"; isReadonly: true }
144− Property { name: "menuObjectPath"; type: "QByteArray"; isReadonly: true }
145− Property { name: "busNameHint"; type: "QByteArray" }
146− Property { name: "actionsHint"; type: "QByteArray" }
147− Property { name: "menuObjectPathHint"; type: "QByteArray" }
148− }
149− Component {
150− name: "WindowKeysFilter"
151− defaultProperty: "data"
152− prototype: "QQuickItem"
153− exports: ["Utils/WindowKeysFilter 0.1"]
154− exportMetaObjectRevisions: [0]
155− }
156175}
plugins/Wizard/Wizard.qmltypes−3
@@ -11,8 +11,6 @@ Module {
1111 name: "PageList"
1212 prototype: "QObject"
1313 exports: ["Wizard/PageList 0.1"]
14− isCreatable: false
15− isSingleton: true
1614 exportMetaObjectRevisions: [0]
1715 Property { name: "index"; type: "int"; isReadonly: true }
1816 Property { name: "numPages"; type: "int"; isReadonly: true }
@@ -33,4 +31,3 @@ Module {
3331 }
3432 }
3533}
36−
po/de.po+4−4
@@ -8,15 +8,15 @@ msgstr ""
88"Project-Id-Version: unity\n"
99"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
1010"POT-Creation-Date: 2015-02-13 11:45+0100\n"
11−"PO-Revision-Date: 2015-02-18 14:18+0000\n"
11+"PO-Revision-Date: 2015-02-27 13:29+0000\n"
1212"Last-Translator: Niklas Wenzel <nikwen.developer@gmail.com>\n"
1313"Language-Team: German <de@li.org>\n"
1414"MIME-Version: 1.0\n"
1515"Content-Type: text/plain; charset=UTF-8\n"
1616"Content-Transfer-Encoding: 8bit\n"
1717"Plural-Forms: nplurals=2; plural=n != 1;\n"
18−"X-Launchpad-Export-Date: 2015-02-19 05:39+0000\n"
19−"X-Generator: Launchpad (build 17341)\n"
18+"X-Launchpad-Export-Date: 2015-02-28 06:19+0000\n"
19+"X-Generator: Launchpad (build 17361)\n"
2020
2121#: plugins/LightDM/Greeter.cpp:112
2222msgid "Password: "
@@ -362,7 +362,7 @@ msgstr ""
362362
363363#: qml/Tutorial/TutorialRight.qml:122
364364msgid "View all your running tasks."
365−msgstr ""
365+msgstr "Sehen Sie alle Ihre laufenden Anwendungen."
366366
367367#: qml/Wizard/Page.qml:89
368368msgid "Back"
po/gd.po+27−21
@@ -8,16 +8,16 @@ msgstr ""
88"Project-Id-Version: unity8\n"
99"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
1010"POT-Creation-Date: 2015-02-13 11:45+0100\n"
11−"PO-Revision-Date: 2015-02-11 15:01+0000\n"
12−"Last-Translator: GunChleoc <Unknown>\n"
11+"PO-Revision-Date: 2015-02-27 01:01+0000\n"
12+"Last-Translator: Akerbeltz <fios@akerbeltz.org>\n"
1313"Language-Team: Fòram na Gàidhlig\n"
1414"MIME-Version: 1.0\n"
1515"Content-Type: text/plain; charset=UTF-8\n"
1616"Content-Transfer-Encoding: 8bit\n"
1717"Plural-Forms: nplurals=4; plural=(n==1 || n==11) ? 0 : (n==2 || n==12) ? 1 : "
1818"(n > 2 && n < 20) ? 2 : 3;\n"
19−"X-Launchpad-Export-Date: 2015-02-17 06:29+0000\n"
20−"X-Generator: Launchpad (build 17341)\n"
19+"X-Launchpad-Export-Date: 2015-02-27 05:54+0000\n"
20+"X-Generator: Launchpad (build 17361)\n"
2121"Language: gd\n"
2222
2323#: plugins/LightDM/Greeter.cpp:112
@@ -187,7 +187,7 @@ msgstr "Leig às airson ath-nuadhachadh…"
187187
188188#: qml/Dash/ScopeSettings/ScopeSettingBoolean.qml:43
189189msgid "Enable location data"
190−msgstr ""
190+msgstr "Cuir an comas dàta an ionaid"
191191
192192#: qml/Dash/ScopesList.qml:66
193193msgid "Manage"
@@ -199,7 +199,7 @@ msgstr "Dhachaigh"
199199
200200#: qml/Dash/ScopesList.qml:113
201201msgid "Also installed"
202−msgstr ""
202+msgstr "Air a stàladh cuideachd"
203203
204204#: qml/Greeter/Greeter.qml:141
205205msgid "Unlock"
@@ -319,55 +319,55 @@ msgstr ""
319319
320320#: qml/Tutorial/TutorialBottomFinish.qml:24
321321msgid "This action does different things for different apps"
322−msgstr ""
322+msgstr "Nì seo diofar rudan do dhiofar aplacaidean"
323323
324324#: qml/Tutorial/TutorialBottomFinish.qml:25
325325msgid "Tap here to finish."
326−msgstr ""
326+msgstr "Thoir gnogag an-seo gus a choileanadh."
327327
328328#: qml/Tutorial/TutorialBottom.qml:28
329329msgid "Open special menus"
330−msgstr ""
330+msgstr "Fosgail na clàran-taice sònraichte"
331331
332332#: qml/Tutorial/TutorialBottom.qml:29
333333msgid "Swipe up from the bottom edge."
334−msgstr ""
334+msgstr "Dèan grad-shlaighdeadh a-nìos on oir."
335335
336336#: qml/Tutorial/TutorialLeftFinish.qml:24
337337msgid "These are the shortcuts to favorite apps"
338−msgstr ""
338+msgstr "Seo na h-ath-ghoiridean ris na h-aplacaidean as fhearr leat"
339339
340340#: qml/Tutorial/TutorialLeftFinish.qml:25 qml/Tutorial/TutorialRight.qml:144
341341msgid "Tap here to continue."
342−msgstr ""
342+msgstr "Thoir gnogag an-seo gus leantainn air adhart."
343343
344344#: qml/Tutorial/TutorialLeft.qml:26
345345msgid "Open the launcher"
346−msgstr ""
346+msgstr "Fosgail an lòinsear"
347347
348348#: qml/Tutorial/TutorialLeft.qml:27
349349msgid "Short swipe from the left edge."
350−msgstr ""
350+msgstr "Grad-shlaighdeadh goirid on oir chlì."
351351
352352#: qml/Tutorial/TutorialPage.qml:178
353353msgid "You almost got it!"
354−msgstr ""
354+msgstr "Cha mhòr!"
355355
356356#: qml/Tutorial/TutorialPage.qml:195
357357msgid "Try again."
358−msgstr ""
358+msgstr "Feuch ris a-rithist."
359359
360360#: qml/Tutorial/TutorialRight.qml:31
361361msgid "To view open apps"
362−msgstr ""
362+msgstr "Gus aplacaidean fosgailte fhaicinn"
363363
364364#: qml/Tutorial/TutorialRight.qml:32
365365msgid "Long swipe from the right edge."
366−msgstr ""
366+msgstr "Grad-shlaighdeadh on oir dheas."
367367
368368#: qml/Tutorial/TutorialRight.qml:122
369369msgid "View all your running tasks."
370−msgstr ""
370+msgstr "Seall gach saothair a tha thu a' ruith."
371371
372372#: qml/Wizard/Page.qml:89
373373msgid "Back"
@@ -426,7 +426,7 @@ msgstr "Còd-faire"
426426
427427#: qml/Wizard/Pages/30-passwd-type.qml:101
428428msgid "4 digits only"
429−msgstr ""
429+msgstr "4 àireamhan a-mhàin"
430430
431431#: qml/Wizard/Pages/30-passwd-type.qml:103
432432msgid "Passphrase"
@@ -458,11 +458,13 @@ msgstr "Faodaidh am fòn aithneachadh far a bheil thu:"
458458
459459#: qml/Wizard/Pages/50-location.qml:69
460460msgid "Using GPS only (less accurate)"
461−msgstr ""
461+msgstr "Na cleachd ach GPS (chan eil seo cho pongail)"
462462
463463#: qml/Wizard/Pages/50-location.qml:86
464464msgid "Using GPS, anonymized Wi-Fi and cellular network info (recommended)"
465465msgstr ""
466+"A' cleachdadh GPS, WiFi gun urra is fiosrachadh an lìonraidh mobile "
467+"(mholamaid seo)"
466468
467469#. TRANSLATORS: HERE is a trademark for Nokia's location service, you probably shouldn't translate it
468470#: qml/Wizard/Pages/50-location.qml:103
@@ -470,6 +472,8 @@ msgid ""
470472"By selecting this option you agree to the Nokia HERE <a href='#'>terms and "
471473"conditions</a>."
472474msgstr ""
475+"Ma thaghas tu an roghainn seo, bidh thu ag aontachadh ri <a "
476+"href='#'>teirmichean is cumhaichean</a> an Nokia HERE."
473477
474478#: qml/Wizard/Pages/50-location.qml:112
475479msgid "Not at all"
@@ -478,6 +482,8 @@ msgstr "Chan fhaod idir"
478482#: qml/Wizard/Pages/50-location.qml:124
479483msgid "You can change your mind later in <b>System Settings</b>."
480484msgstr ""
485+"'S urrainn dhut a chaochladh a chur romhad às a dhèidh seo ann an "
486+"<b>Roghainnean an t-siostaim</b>."
481487
482488#: qml/Wizard/Pages/60-reporting.qml:24
483489msgid "Improving your experience"
po/lv.po+105−92
@@ -8,15 +8,15 @@ msgstr ""
88"Project-Id-Version: unity\n"
99"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
1010"POT-Creation-Date: 2015-02-13 11:45+0100\n"
11−"PO-Revision-Date: 2014-08-21 09:46+0000\n"
12−"Last-Translator: Jānis Marks Gailis <jm-gailis@fai-vianet.fr>\n"
11+"PO-Revision-Date: 2015-02-28 16:18+0000\n"
12+"Last-Translator: Rūdolfs Mazurs <Unknown>\n"
1313"Language-Team: Latvian <lv@li.org>\n"
1414"MIME-Version: 1.0\n"
1515"Content-Type: text/plain; charset=UTF-8\n"
1616"Content-Transfer-Encoding: 8bit\n"
1717"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2;\n"
18−"X-Launchpad-Export-Date: 2015-02-17 06:30+0000\n"
19−"X-Generator: Launchpad (build 17341)\n"
18+"X-Launchpad-Export-Date: 2015-03-01 05:31+0000\n"
19+"X-Generator: Launchpad (build 17361)\n"
2020
2121#: plugins/LightDM/Greeter.cpp:112
2222msgid "Password: "
@@ -25,38 +25,39 @@ msgstr "Parole: "
2525#: plugins/Unity/Launcher/launcheritem.cpp:45
2626#: plugins/Unity/Launcher/launcheritem.cpp:100
2727msgid "Pin shortcut"
28−msgstr ""
28+msgstr "Piespraust īsceļu"
2929
3030#: plugins/Unity/Launcher/launcheritem.cpp:100
3131msgid "Unpin shortcut"
32−msgstr ""
32+msgstr "Noņemt īsceļu"
3333
3434#: qml/Components/DelayedLockscreen.qml:47
3535msgid "Device Locked"
36−msgstr ""
36+msgstr "Ierīce bloķēta"
3737
3838#: qml/Components/DelayedLockscreen.qml:62
3939msgid "You have been locked out due to too many failed passphrase attempts."
40−msgstr ""
40+msgstr "Bloķēts, jo pārāk bieži kļūdaini ievadījāt paroli."
4141
4242#: qml/Components/DelayedLockscreen.qml:63
4343msgid "You have been locked out due to too many failed passcode attempts."
44−msgstr ""
44+msgstr "Bloķēts, jo pārāk bieži kļūdaini ievadījāt piekļuves kodu."
4545
4646#: qml/Components/DelayedLockscreen.qml:72
4747#, qt-format
4848msgid "Please wait %1 minute and then try again…"
4949msgid_plural "Please wait %1 minutes and then try again…"
50−msgstr[0] ""
51−msgstr[1] ""
50+msgstr[0] "Lūdzu, uzgaidiet %1 minūti un tad mēģiniet vēlreiz..."
51+msgstr[1] "Lūdzu, uzgaidiet %1 minūtes un tad mēģiniet vēlreiz..."
52+msgstr[2] "Lūdzu, uzgaidiet %1 minūtes un tad mēģiniet vēlreiz..."
5253
5354#: qml/Components/Dialogs.qml:86
5455msgid "Log out"
55−msgstr "Iziet"
56+msgstr "Izrakstīties"
5657
5758#: qml/Components/Dialogs.qml:87
5859msgid "Are you sure you want to log out?"
59−msgstr ""
60+msgstr "Vai tiešām vēlaties izrakstīties?"
6061
6162#: qml/Components/Dialogs.qml:89 qml/Components/Dialogs.qml:111
6263#: qml/Components/Dialogs.qml:134
@@ -74,7 +75,7 @@ msgstr "Izslēgt"
7475
7576#: qml/Components/Dialogs.qml:109
7677msgid "Are you sure you want to shut down?"
77−msgstr ""
78+msgstr "Vai tiešām vēlaties izslēgt?"
7879
7980#: qml/Components/Dialogs.qml:131
8081msgid "Reboot"
@@ -82,7 +83,7 @@ msgstr "Pārstartēt"
8283
8384#: qml/Components/Dialogs.qml:132
8485msgid "Are you sure you want to reboot?"
85−msgstr ""
86+msgstr "Vai tiešām vēlaties pārstartēt?"
8687
8788#: qml/Components/Dialogs.qml:154
8889msgid "Power"
@@ -93,6 +94,8 @@ msgid ""
9394"Are you sure you would like\n"
9495"to power off?"
9596msgstr ""
97+"Vai tiešām vēlaties\n"
98+"izslēgt?"
9699
97100#: qml/Components/Dialogs.qml:157
98101msgid "Power off"
@@ -100,7 +103,7 @@ msgstr "Izslēgt"
100103
101104#: qml/Components/Dialogs.qml:166
102105msgid "Restart"
103−msgstr ""
106+msgstr "Pārstartēt"
104107
105108#: qml/Components/Dialogs.qml:175
106109msgid "Cancel"
@@ -108,19 +111,19 @@ msgstr "Atcelt"
108111
109112#: qml/Components/Lockscreen.qml:238
110113msgid "Return to Call"
111−msgstr ""
114+msgstr "Atgriezties pie zvana"
112115
113116#: qml/Components/Lockscreen.qml:238
114117msgid "Emergency Call"
115−msgstr "Neatliekamās Palīdzības zvans"
118+msgstr "Ārkārtas situācijas zvans"
116119
117120#: qml/Components/Lockscreen.qml:270
118121msgid "OK"
119−msgstr "LABI"
122+msgstr "Labi"
120123
121124#: qml/Dash/GenericScopeView.qml:528 qml/Dash/GenericScopeView.qml:686
122125msgid "See less"
123−msgstr "Redzēt mazāk"
126+msgstr "Skatīt mazāk"
124127
125128#: qml/Dash/GenericScopeView.qml:528
126129msgid "See all"
@@ -161,7 +164,7 @@ msgstr "Novērtēt šo"
161164
162165#: qml/Dash/Previews/PreviewRatingInput.qml:126
163166msgid "Add a review"
164−msgstr "Komentēt"
167+msgstr "Pievienot atsauksmi"
165168
166169#: qml/Dash/Previews/PreviewRatingInput.qml:166
167170msgid "Send"
@@ -169,31 +172,31 @@ msgstr "Sūtīt"
169172
170173#: qml/Dash/PullToRefreshScopeStyle.qml:55
171174msgid "Pull to refresh…"
172−msgstr ""
175+msgstr "Vilt, lai atsvaidzinātu..."
173176
174177#: qml/Dash/PullToRefreshScopeStyle.qml:60
175178msgid "Release to refresh…"
176−msgstr ""
179+msgstr "Atlaist, lai atsvaidzinātu..."
177180
178181#: qml/Dash/ScopeSettings/ScopeSettingBoolean.qml:43
179182msgid "Enable location data"
180−msgstr ""
183+msgstr "Ieslēgt atrašanās vietas datus"
181184
182185#: qml/Dash/ScopesList.qml:66
183186msgid "Manage"
184−msgstr ""
187+msgstr "Pārvaldīt"
185188
186189#: qml/Dash/ScopesList.qml:112
187190msgid "Home"
188−msgstr ""
191+msgstr "Mājas"
189192
190193#: qml/Dash/ScopesList.qml:113
191194msgid "Also installed"
192−msgstr ""
195+msgstr "Arī instalēja"
193196
194197#: qml/Greeter/Greeter.qml:141
195198msgid "Unlock"
196−msgstr ""
199+msgstr "Atbloķēt"
197200
198201#: qml/Notifications/NotificationMenuItemFactory.qml:128
199202msgid "Show password"
@@ -201,7 +204,7 @@ msgstr "Rādīt paroli"
201204
202205#: qml/Panel/ActiveCallHint.qml:79
203206msgid "Tap to return to call..."
204−msgstr ""
207+msgstr "Uzsitiet, lai atzvanītu..."
205208
206209#: qml/Panel/ActiveCallHint.qml:92
207210msgid "Conference"
@@ -209,7 +212,7 @@ msgstr "Konference"
209212
210213#: qml/Panel/Indicators/MenuItemFactory.qml:643
211214msgid "Nothing is playing"
212−msgstr ""
215+msgstr "Nekas netiek atskaņots"
213216
214217#: qml/Panel/Indicators/MenuItemFactory.qml:791
215218msgid "In queue…"
@@ -221,7 +224,7 @@ msgstr "Lejupielādē"
221224
222225#: qml/Panel/Indicators/MenuItemFactory.qml:797
223226msgid "Paused, tap to resume"
224−msgstr ""
227+msgstr "Pauzēts, uzsitiet, lai turpinātu"
225228
226229#: qml/Panel/Indicators/MenuItemFactory.qml:799
227230msgid "Canceled"
@@ -233,11 +236,11 @@ msgstr "Pabeigts"
233236
234237#: qml/Panel/Indicators/MenuItemFactory.qml:803
235238msgid "Failed, tap to retry"
236−msgstr "Neizdevās, spiest lai mēģināt vēlreiz"
239+msgstr "Neizdevās, uzsitiet, lai mēģinātu vēlreiz"
237240
238241#: qml/Panel/Indicators/MessageMenuItemFactory.qml:149
239242msgid "Reply"
240−msgstr ""
243+msgstr "Atbildēt"
241244
242245#: qml/Panel/Indicators/ModemInfoItem.qml:105
243246msgid "Unlock SIM"
@@ -250,40 +253,40 @@ msgstr "Viesabonēšana"
250253#: qml/Shell.qml:402
251254#, qt-format
252255msgid "Enter %1"
253−msgstr ""
256+msgstr "Ievadiet %1"
254257
255258#: qml/Shell.qml:403 qml/Wizard/Pages/passwd-set.qml:60
256259msgid "Enter passphrase"
257−msgstr ""
260+msgstr "Ievadiet paroli"
258261
259262#: qml/Shell.qml:404
260263msgid "Enter passcode"
261−msgstr ""
264+msgstr "Ievadiet piekļuves kodu"
262265
263266#: qml/Shell.qml:405
264267#, qt-format
265268msgid "Sorry, incorrect %1"
266−msgstr ""
269+msgstr "Piedodiet, nepareizs %1"
267270
268271#: qml/Shell.qml:406
269272msgid "Sorry, incorrect passphrase"
270−msgstr ""
273+msgstr "Piedodiet, nepareiza parole"
271274
272275#: qml/Shell.qml:407
273276msgid "Please re-enter"
274−msgstr ""
277+msgstr "Lūdzu, ievadiet vēlreiz"
275278
276279#: qml/Shell.qml:408
277280msgid "Sorry, incorrect passcode"
278−msgstr ""
281+msgstr "Piedodiet, nepareizs piekļuves kods"
279282
280283#: qml/Shell.qml:502 qml/Wizard/Pages/passwd-confirm.qml:53
281284msgid "Sorry, incorrect passphrase."
282−msgstr ""
285+msgstr "Piedodiet, nepareiza parole."
283286
284287#: qml/Shell.qml:503 qml/Wizard/Pages/passwd-confirm.qml:54
285288msgid "Sorry, incorrect passcode."
286−msgstr ""
289+msgstr "Piedodiet, nepareizs piekļuves kods."
287290
288291#: qml/Shell.qml:504
289292msgid "This will be your last attempt."
@@ -294,159 +297,165 @@ msgid ""
294297"If passphrase is entered incorrectly, your phone will conduct a factory "
295298"reset and all personal data will be deleted."
296299msgstr ""
300+"Ja parole netiks pareizi ievadīta, jūsu tālrunis tiks atstatīts uz rūpnīcas "
301+"iestatījumiem un visa personīgā informācija tiks izdzēsta."
297302
298303#: qml/Shell.qml:507
299304msgid ""
300305"If passcode is entered incorrectly, your phone will conduct a factory reset "
301306"and all personal data will be deleted."
302307msgstr ""
308+"Ja piekļuves kods netiks pareizi ievadīts, jūsu tālrunis tiks atstatīts uz "
309+"rūpnīcas iestatījumiem un visa personīgā informācija tiks izdzēsta."
303310
304311#: qml/Tutorial/TutorialBottomFinish.qml:24
305312msgid "This action does different things for different apps"
306−msgstr ""
313+msgstr "Šī darbība dažādās lietotnēs darīs dažādas lietas"
307314
308315#: qml/Tutorial/TutorialBottomFinish.qml:25
309316msgid "Tap here to finish."
310−msgstr ""
317+msgstr "Uzsitiet šeit, lai pabeigtu."
311318
312319#: qml/Tutorial/TutorialBottom.qml:28
313320msgid "Open special menus"
314−msgstr ""
321+msgstr "Atvērt īpašas izvēlnes"
315322
316323#: qml/Tutorial/TutorialBottom.qml:29
317324msgid "Swipe up from the bottom edge."
318−msgstr ""
325+msgstr "Velciet no apakšējās malas."
319326
320327#: qml/Tutorial/TutorialLeftFinish.qml:24
321328msgid "These are the shortcuts to favorite apps"
322−msgstr ""
329+msgstr "Šie ir īsceļi uz iecienītajām lietotnēm"
323330
324331#: qml/Tutorial/TutorialLeftFinish.qml:25 qml/Tutorial/TutorialRight.qml:144
325332msgid "Tap here to continue."
326−msgstr ""
333+msgstr "Uzsitiet šeit, lai turpinātu."
327334
328335#: qml/Tutorial/TutorialLeft.qml:26
329336msgid "Open the launcher"
330−msgstr ""
337+msgstr "Atvērt palaidēju"
331338
332339#: qml/Tutorial/TutorialLeft.qml:27
333340msgid "Short swipe from the left edge."
334−msgstr ""
341+msgstr "Īss vilciens no kreisās malas."
335342
336343#: qml/Tutorial/TutorialPage.qml:178
337344msgid "You almost got it!"
338−msgstr ""
345+msgstr "Gandrīz sanāca!"
339346
340347#: qml/Tutorial/TutorialPage.qml:195
341348msgid "Try again."
342−msgstr ""
349+msgstr "Mēģiniet vēlreiz."
343350
344351#: qml/Tutorial/TutorialRight.qml:31
345352msgid "To view open apps"
346−msgstr ""
353+msgstr "Lai redzētu atvērtās lietotnes"
347354
348355#: qml/Tutorial/TutorialRight.qml:32
349356msgid "Long swipe from the right edge."
350−msgstr ""
357+msgstr "Garš vilciens no labās malas."
351358
352359#: qml/Tutorial/TutorialRight.qml:122
353360msgid "View all your running tasks."
354−msgstr ""
361+msgstr "Redzēt visus palaistos uzdevumus."
355362
356363#: qml/Wizard/Page.qml:89
357364msgid "Back"
358−msgstr ""
365+msgstr "Atpakaļ"
359366
360367#: qml/Wizard/Pages/10-welcome.qml:27
361368msgid "Hi!"
362−msgstr ""
369+msgstr "Sveiki!"
363370
364371#: qml/Wizard/Pages/10-welcome.qml:44
365372msgid "Welcome to your Ubuntu phone."
366−msgstr ""
373+msgstr "Laipni lūdza Ubuntu viedtālrunī."
367374
368375#: qml/Wizard/Pages/10-welcome.qml:52
369376msgid "Let’s get started."
370−msgstr ""
377+msgstr "Sāksim."
371378
372379#: qml/Wizard/Pages/10-welcome.qml:88 qml/Wizard/Pages/30-passwd-type.qml:128
373380#: qml/Wizard/Pages/40-wifi.qml:215 qml/Wizard/Pages/50-location.qml:131
374381#: qml/Wizard/Pages/60-reporting.qml:50 qml/Wizard/Pages/passwd-confirm.qml:82
375382#: qml/Wizard/Pages/passwd-set.qml:90
376383msgid "Continue"
377−msgstr ""
384+msgstr "Turpināt"
378385
379386#: qml/Wizard/Pages/20-sim.qml:25
380387msgid "Add a SIM card and restart your device"
381−msgstr ""
388+msgstr "Pievienojiet SIM karti un pārstartējiet ierīci"
382389
383390#: qml/Wizard/Pages/20-sim.qml:55
384391msgid "Without it, you won’t be able to make calls or use text messaging."
385−msgstr ""
392+msgstr "Bez tās jūs nevarēsiet piezvanīt vai sūtīt īsziņas."
386393
387394#: qml/Wizard/Pages/20-sim.qml:69 qml/Wizard/Pages/40-wifi.qml:215
388395msgid "Skip"
389−msgstr ""
396+msgstr "Izlaist"
390397
391398#: qml/Wizard/Pages/30-passwd-type.qml:39
392399msgid "Lock security"
393−msgstr ""
400+msgstr "Bloķēšanas drošība"
394401
395402#: qml/Wizard/Pages/30-passwd-type.qml:74
396403msgid "Please select how you’d like to unlock your phone."
397−msgstr ""
404+msgstr "Lūdzu, izvēlieties, kā vēlaties atbloķēt savu tālruni."
398405
399406#: qml/Wizard/Pages/30-passwd-type.qml:97
400407msgid "Swipe"
401−msgstr ""
408+msgstr "Vilkt"
402409
403410#: qml/Wizard/Pages/30-passwd-type.qml:98
404411msgid "No security"
405−msgstr ""
412+msgstr "Nav drošības"
406413
407414#: qml/Wizard/Pages/30-passwd-type.qml:100
408415msgid "Passcode"
409−msgstr ""
416+msgstr "Piekļuves kods"
410417
411418#: qml/Wizard/Pages/30-passwd-type.qml:101
412419msgid "4 digits only"
413−msgstr ""
420+msgstr "Tikai četri cipari"
414421
415422#: qml/Wizard/Pages/30-passwd-type.qml:103
416423msgid "Passphrase"
417−msgstr ""
424+msgstr "Parole"
418425
419426#: qml/Wizard/Pages/30-passwd-type.qml:104
420427msgid "Numbers and letters"
421−msgstr ""
428+msgstr "Skaitļi un burti"
422429
423430#: qml/Wizard/Pages/40-wifi.qml:29
424431msgid "Connect to Wi‑Fi"
425−msgstr ""
432+msgstr "Savienoties ar Wi‑Fi"
426433
427434#: qml/Wizard/Pages/40-wifi.qml:168
428435msgid "Available networks…"
429−msgstr ""
436+msgstr "Pieejamie tīkli…"
430437
431438#: qml/Wizard/Pages/40-wifi.qml:169
432439msgid "No available networks."
433−msgstr ""
440+msgstr "Nav pieejamu tīklu."
434441
435442#: qml/Wizard/Pages/50-location.qml:27
436443msgid "Location"
437−msgstr ""
444+msgstr "Atrašanās vieta"
438445
439446#: qml/Wizard/Pages/50-location.qml:62
440447msgid "Let the phone detect your location:"
441−msgstr ""
448+msgstr "Ļaut tālrunim noteikt jūsu atrašanās vietu:"
442449
443450#: qml/Wizard/Pages/50-location.qml:69
444451msgid "Using GPS only (less accurate)"
445−msgstr ""
452+msgstr "Izmantot tikai GPS (mazāk precīzi)"
446453
447454#: qml/Wizard/Pages/50-location.qml:86
448455msgid "Using GPS, anonymized Wi-Fi and cellular network info (recommended)"
449456msgstr ""
457+"Izmantot GPS, anonimizētu bezvadu (Wi-Fi) un mobilā tīklā informāciju "
458+"(ieteicams)"
450459
451460#. TRANSLATORS: HERE is a trademark for Nokia's location service, you probably shouldn't translate it
452461#: qml/Wizard/Pages/50-location.qml:103
@@ -454,81 +463,85 @@ msgid ""
454463"By selecting this option you agree to the Nokia HERE <a href='#'>terms and "
455464"conditions</a>."
456465msgstr ""
466+"Izvēloties šo opciju, jūs piekrītat Nokia HERE <a href='#'>nosacījumiem</a>."
457467
458468#: qml/Wizard/Pages/50-location.qml:112
459469msgid "Not at all"
460−msgstr ""
470+msgstr "Necik"
461471
462472#: qml/Wizard/Pages/50-location.qml:124
463473msgid "You can change your mind later in <b>System Settings</b>."
464−msgstr ""
474+msgstr "Vēlāk jūs varat to mainīt <b>Sistēmas iestatījumos</b>."
465475
466476#: qml/Wizard/Pages/60-reporting.qml:24
467477msgid "Improving your experience"
468−msgstr ""
478+msgstr "Jūsu pieredzes uzlabošana"
469479
470480#: qml/Wizard/Pages/60-reporting.qml:36
471481msgid ""
472482"Your phone is set up to automatically report errors to Canonical and its "
473483"partners, the makers of the operating system."
474484msgstr ""
485+"Jūsu tālrunis automātiski sūtīts kļūdu ziņojumus Canonical un tā partneriem "
486+"— šīs operētājsistēmas izstrādātājiem."
475487
476488#: qml/Wizard/Pages/60-reporting.qml:43
477489msgid ""
478490"This can be disabled in <b>System Settings</b> under <b>Security &amp; "
479491"Privacy</b>"
480492msgstr ""
493+"To varat atslēgt <b>Sistēmas iestatījumos</b> zem <b>Drošība un privātums</b>"
481494
482495#: qml/Wizard/Pages/80-finished.qml:24
483496msgid "All done"
484−msgstr ""
497+msgstr "Viss paveikts"
485498
486499#: qml/Wizard/Pages/80-finished.qml:39
487500msgid "Nice work!"
488−msgstr ""
501+msgstr "Lieliski!"
489502
490503#: qml/Wizard/Pages/80-finished.qml:46
491504msgid "Your phone is now ready to use."
492−msgstr ""
505+msgstr "Jūsu tālrunis tagad ir gatavs lietošanai."
493506
494507#: qml/Wizard/Pages/80-finished.qml:53
495508msgid "Finish"
496−msgstr ""
509+msgstr "Pabeigt"
497510
498511#: qml/Wizard/Pages/here-terms.qml:27
499512msgid "Terms & Conditions"
500−msgstr ""
513+msgstr "Nosacījumi"
501514
502515#: qml/Wizard/Pages/passwd-confirm.qml:49
503516msgid "Confirm passphrase"
504−msgstr ""
517+msgstr "Apstipriniet paroli"
505518
506519#: qml/Wizard/Pages/passwd-confirm.qml:50
507520msgid "Confirm passcode"
508−msgstr ""
521+msgstr "Apstipriniet piekļuves kodu"
509522
510523#: qml/Wizard/Pages/passwd-confirm.qml:53
511524#: qml/Wizard/Pages/passwd-confirm.qml:54
512525msgid "Please try again."
513−msgstr ""
526+msgstr "Lūdzu, mēģiniet vēlreiz."
514527
515528#: qml/Wizard/Pages/passwd-set.qml:61
516529msgid "Choose your passcode"
517−msgstr ""
530+msgstr "Izvēlieties savu paroli"
518531
519532#: qml/Wizard/Pages/passwd-set.qml:68
520533msgid "Passphrase must be 4 characters long"
521−msgstr ""
534+msgstr "Piekļuves kodam ir jābūt 4 rakstzīmju garumā"
522535
523536#: qml/Wizard/StackButton.qml:39
524537#, qt-format
525538msgid "〈 %1"
526−msgstr ""
539+msgstr "〈 %1"
527540
528541#: qml/Wizard/StackButton.qml:42
529542#, qt-format
530543msgid "%1 〉"
531−msgstr ""
544+msgstr "%1 〉"
532545
533546#~ msgid "Confirm"
534547#~ msgstr "Apstiprināt"
po/nl.po+5−3
@@ -8,15 +8,15 @@ msgstr ""
88"Project-Id-Version: unity\n"
99"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
1010"POT-Creation-Date: 2015-02-13 11:45+0100\n"
11−"PO-Revision-Date: 2015-02-20 12:47+0000\n"
11+"PO-Revision-Date: 2015-02-26 10:21+0000\n"
1212"Last-Translator: rob <linuxned@gmail.com>\n"
1313"Language-Team: Dutch <nl@li.org>\n"
1414"MIME-Version: 1.0\n"
1515"Content-Type: text/plain; charset=UTF-8\n"
1616"Content-Transfer-Encoding: 8bit\n"
1717"Plural-Forms: nplurals=2; plural=n != 1;\n"
18−"X-Launchpad-Export-Date: 2015-02-21 05:35+0000\n"
19−"X-Generator: Launchpad (build 17355)\n"
18+"X-Launchpad-Export-Date: 2015-02-27 05:54+0000\n"
19+"X-Generator: Launchpad (build 17361)\n"
2020
2121#: plugins/LightDM/Greeter.cpp:112
2222msgid "Password: "
@@ -314,6 +314,8 @@ msgstr ""
314314#: qml/Tutorial/TutorialBottomFinish.qml:24
315315msgid "This action does different things for different apps"
316316msgstr ""
317+"Deze actie doet verschillende dingen afhankelijk van de app die gebruikt "
318+"wordt"
317319
318320#: qml/Tutorial/TutorialBottomFinish.qml:25
319321msgid "Tap here to finish."
qml/Components/DelayedLockscreen.qml+3−1
@@ -20,11 +20,13 @@ import "../Components"
2020
2121Item {
2222 id: root
23− anchors.fill: parent
2423
2524 property int delayMinutes
2625 property bool alphaNumeric
2726
27+ signal entered(string passphrase) // unused
28+ signal cancel() // unused
29+
2830 function clear(playAnimation) {}
2931
3032 Column {
qml/Components/PassphraseLockscreen.qml+2−3
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013,2014,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
@@ -20,8 +20,7 @@ import "../Components"
2020
2121Item {
2222 id: root
23− anchors.top: parent.top
24− anchors.topMargin: units.gu(4)
23+ y: units.gu(4)
2524 height: shakeContainer.height
2625
2726 property string infoText
qml/Components/PinLockscreen.qml+3−4
@@ -21,9 +21,7 @@ import "../Components"
2121
2222Column {
2323 id: root
24− anchors.top: parent.top
25− anchors.topMargin: units.gu(4)
26− anchors.horizontalCenter: parent.horizontalCenter
24+ y: units.gu(4)
2725 spacing: units.gu(2)
2826
2927 property string infoText
@@ -123,6 +121,7 @@ Column {
123121 objectName: "backspaceIcon"
124122 anchors { right: parent.right; top: parent.top; bottom: parent.bottom }
125123 width: height
124+ enabled: root.entryEnabled
126125
127126 Icon {
128127 anchors.fill: parent
@@ -130,7 +129,7 @@ Column {
130129 color: "#f3f3e7"
131130 }
132131
133− opacity: (pinentryField.text.length && !pinentryField.incorrectOverride) > 0 ? 1 : 0
132+ opacity: (pinentryField.text.length > 0 && !pinentryField.incorrectOverride) ? 1 : 0
134133
135134 Behavior on opacity {
136135 UbuntuNumberAnimation {}
qml/Components/ZoomableImage.qml+14−14
@@ -107,20 +107,6 @@ Item {
107107 }
108108 }
109109
110− PinchArea {
111− id: pinchArea
112− objectName: "pinchArea"
113− property real minScale: 1.0
114− anchors.fill: parent
115− enabled: zoomable ? zoomable : false
116−
117− pinch.target: image
118− pinch.minimumScale: minScale
119− pinch.maximumScale: 10
120−
121− onPinchFinished: flickable.returnToBounds()
122− }
123−
124110 MouseArea {
125111 id: mouseArea
126112 objectName: "mouseArea"
@@ -153,4 +139,18 @@ Item {
153139 }
154140 }
155141 }
142+
143+ PinchArea {
144+ id: pinchArea
145+ objectName: "pinchArea"
146+ property real minScale: 1.0
147+ anchors.fill: flickable
148+ enabled: zoomable ? zoomable : false
149+
150+ pinch.target: image
151+ pinch.minimumScale: minScale
152+ pinch.maximumScale: 10
153+
154+ onPinchFinished: flickable.returnToBounds()
155+ }
156156}
qml/Dash/Dash.qml+2−1
@@ -300,10 +300,11 @@ Showable {
300300 }
301301
302302 Image {
303+ objectName: "overviewHint"
303304 source: "graphics/overview_hint.png"
304305 anchors.horizontalCenter: parent.horizontalCenter
305306 opacity: !scopeItem.scope && (scopes.count == 0 || dashContent.pageHeaderTotallyVisible) &&
306− (overviewDragHandle.enabled || overviewDragHandle.status.progress != 0) ? 1 : 0
307+ (overviewDragHandle.enabled || bottomEdgeController.progress != 0) ? 1 : 0
307308 Behavior on opacity {
308309 enabled: bottomEdgeController.progress == 0
309310 UbuntuNumberAnimation {}
qml/Dash/GenericScopeView.qml+2−1
@@ -19,6 +19,7 @@ import Ubuntu.Components 1.1
1919import Utils 0.1
2020import Unity 0.2
2121import Dash 0.1
22+import Powerd 0.1
2223import "../Components"
2324import "../Components/ListItems" as ListItems
2425
@@ -104,7 +105,7 @@ FocusScope {
104105 Binding {
105106 target: scope
106107 property: "isActive"
107− value: isCurrent && !subPageLoader.open
108+ value: isCurrent && !subPageLoader.open && (Powerd.status === Powerd.On)
108109 }
109110
110111 UnitySortFilterProxyModel {
qml/Greeter/CoverPage.qmlnew file+216
@@ -0,0 +1,216 @@
1+/*
2+ * Copyright (C) 2013,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.3
18+import Ubuntu.Components 1.1
19+import Ubuntu.Gestures 0.1
20+import "../Components"
21+
22+Showable {
23+ id: root
24+
25+ property real dragHandleLeftMargin
26+ property real launcherOffset
27+ property alias background: greeterBackground.source
28+ property real backgroundTopMargin
29+ property var infographicModel
30+ property bool draggable: true
31+
32+ property alias infographics: infographics
33+
34+ readonly property real showProgress: MathUtils.clamp((width - Math.abs(x)) / width, 0, 1)
35+
36+ signal tease()
37+
38+ function hideRight() {
39+ d.forceRightOnNextHideAnimation = true;
40+ hide();
41+ }
42+
43+ QtObject {
44+ id: d
45+ property bool forceRightOnNextHideAnimation: false
46+ }
47+
48+ prepareToHide: function () {
49+ hideTranslation.from = root.x + translation.x
50+ hideTranslation.to = root.x > 0 || d.forceRightOnNextHideAnimation ? root.width : -root.width;
51+ d.forceRightOnNextHideAnimation = false;
52+ }
53+
54+ // We don't directly bind "x" because that's owned by the DragHandle. So
55+ // instead, we can get a little extra horizontal push by using transforms.
56+ transform: Translate { id: translation; x: root.draggable ? launcherOffset : 0 }
57+
58+ MouseArea { anchors.fill: parent; }
59+
60+ Rectangle {
61+ // In case background fails to load
62+ id: backgroundBackup
63+ anchors.fill: parent
64+ color: "black"
65+ }
66+
67+ CrossFadeImage {
68+ id: greeterBackground
69+ objectName: "greeterBackground"
70+ anchors {
71+ fill: parent
72+ topMargin: root.backgroundTopMargin
73+ }
74+ fillMode: Image.PreserveAspectCrop
75+ // Limit how much memory we'll reserve for this image
76+ sourceSize.height: height
77+ sourceSize.width: width
78+ }
79+
80+ Rectangle {
81+ anchors.fill: parent
82+ color: "black"
83+ opacity: 0.4
84+ }
85+
86+ Infographics {
87+ id: infographics
88+ objectName: "infographics"
89+ height: parent.height
90+ model: root.infographicModel
91+ clip: true // clip large data bubbles
92+
93+ anchors {
94+ verticalCenter: parent.verticalCenter
95+ left: parent.left
96+ right: parent.right
97+ }
98+ }
99+
100+ Label {
101+ id: swipeHint
102+ property real baseOpacity: 0.5
103+ opacity: 0.0
104+ anchors.horizontalCenter: parent.horizontalCenter
105+ anchors.bottom: parent.bottom
106+ anchors.bottomMargin: units.gu(5)
107+ text: "《 " + i18n.tr("Unlock") + " 》"
108+ color: "white"
109+ font.weight: Font.Light
110+
111+ SequentialAnimation on opacity {
112+ id: showLabelAnimation
113+ running: false
114+ loops: 2
115+
116+ StandardAnimation {
117+ from: 0.0
118+ to: swipeHint.baseOpacity
119+ duration: UbuntuAnimation.SleepyDuration
120+ }
121+ PauseAnimation { duration: UbuntuAnimation.BriskDuration }
122+ StandardAnimation {
123+ from: swipeHint.baseOpacity
124+ to: 0.0
125+ duration: UbuntuAnimation.SleepyDuration
126+ }
127+ }
128+ }
129+
130+ DragHandle {
131+ id: dragHandle
132+ anchors.fill: parent
133+ anchors.leftMargin: root.dragHandleLeftMargin
134+ enabled: root.draggable
135+ direction: Direction.Horizontal
136+
137+ onDraggingChanged: {
138+ if (dragging) {
139+ root.tease();
140+ showLabelAnimation.start();
141+ }
142+ }
143+ }
144+
145+ // right side shadow
146+ Image {
147+ anchors.left: parent.right
148+ anchors.top: parent.top
149+ anchors.bottom: parent.bottom
150+ fillMode: Image.Tile
151+ source: "../graphics/dropshadow_right.png"
152+ }
153+
154+ // left side shadow
155+ Image {
156+ anchors.right: parent.left
157+ anchors.top: parent.top
158+ anchors.bottom: parent.bottom
159+ fillMode: Image.Tile
160+ source: "../graphics/dropshadow_left.png"
161+ }
162+
163+ Binding {
164+ id: positionLock
165+
166+ property bool enabled: false
167+ onEnabledChanged: {
168+ if (enabled === __enabled) {
169+ return;
170+ }
171+
172+ if (enabled) {
173+ if (root.x > 0) {
174+ value = Qt.binding(function() { return root.width; })
175+ } else {
176+ value = Qt.binding(function() { return -root.width; })
177+ }
178+ }
179+
180+ __enabled = enabled;
181+ }
182+
183+ property bool __enabled: false
184+
185+ target: root
186+ when: __enabled
187+ property: "x"
188+ }
189+
190+ hideAnimation: SequentialAnimation {
191+ id: hideAnimation
192+ objectName: "hideAnimation"
193+ property var target // unused, here to silence Showable warning
194+ StandardAnimation {
195+ id: hideTranslation
196+ property: "x"
197+ target: root
198+ }
199+ PropertyAction { target: root; property: "visible"; value: false }
200+ PropertyAction { target: positionLock; property: "enabled"; value: true }
201+ }
202+
203+ showAnimation: SequentialAnimation {
204+ id: showAnimation
205+ objectName: "showAnimation"
206+ property var target // unused, here to silence Showable warning
207+ PropertyAction { target: root; property: "visible"; value: true }
208+ PropertyAction { target: positionLock; property: "enabled"; value: false }
209+ StandardAnimation {
210+ property: "x"
211+ target: root
212+ to: 0
213+ duration: UbuntuAnimation.FastDuration
214+ }
215+ }
216+}
qml/Greeter/Greeter.qml+319−147
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013,2014,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
@@ -14,66 +14,185 @@
1414 * along with this program. If not, see <http://www.gnu.org/licenses/>.
1515 */
1616
17−import QtQuick 2.0
18−import Ubuntu.Components 0.1
19−import Ubuntu.Gestures 0.1
17+import QtQuick 2.3
18+import AccountsService 0.1
2019import LightDM 0.1 as LightDM
20+import Ubuntu.Components 1.1
21+import Ubuntu.SystemImage 0.1
22+import Unity.Connectivity 0.1
23+import Unity.Launcher 0.1
2124import "../Components"
2225
2326Showable {
24− id: greeter
25− enabled: shown
26− created: greeterContentLoader.status == Loader.Ready && greeterContentLoader.item.ready
27+ id: root
28+ created: loader.status == Loader.Ready
2729
2830 property real dragHandleLeftMargin: 0
29− property alias dragging: dragHandle.dragging
3031
3132 property url background
3233
33− // so that it can be replaced in tests with a mock object
34− property var inputMethod: Qt.inputMethod
34+ // How far to offset the top greeter layer during a launcher left-drag
35+ property real launcherOffset
3536
36− prepareToHide: function () {
37− hideTranslation.to = greeter.x > 0 || d.forceRightOnNextHideAnimation ? greeter.width : -greeter.width;
38− d.forceRightOnNextHideAnimation = false;
37+ readonly property bool active: shown || hasLockedApp
38+ readonly property bool fullyShown: loader.item ? loader.item.fullyShown : false
39+
40+ // True when the greeter is waiting for PAM or other setup process
41+ readonly property alias waiting: d.waiting
42+
43+ property string lockedApp: ""
44+ readonly property bool hasLockedApp: lockedApp !== ""
45+
46+ property bool forcedUnlock
47+ readonly property bool locked: LightDM.Greeter.active && !LightDM.Greeter.authenticated && !forcedUnlock
48+
49+ property bool tabletMode
50+ property url viewSource // only used for testing
51+
52+ property int maxFailedLogins: -1 // disabled by default for now, will enable via settings in future
53+ property int failedLoginsDelayAttempts: 7 // number of failed logins
54+ property int failedLoginsDelayMinutes: 5 // minutes of forced waiting
55+
56+ signal tease()
57+ signal sessionStarted()
58+ signal emergencyCall()
59+
60+ function forceShow() {
61+ showNow();
62+ loader.item.reset();
3963 }
4064
41− QtObject {
42− id: d
43− property bool forceRightOnNextHideAnimation: false
65+ function notifyAppFocused(appId) {
66+ if (!active) {
67+ return;
68+ }
69+
70+ if (hasLockedApp) {
71+ if (appId === lockedApp) {
72+ hide(); // show locked app
73+ } else {
74+ show();
75+ d.startUnlock(false /* toTheRight */);
76+ }
77+ } else if (appId !== "unity8-dash") { // dash isn't started by user
78+ d.startUnlock(false /* toTheRight */);
79+ }
4480 }
4581
46− property bool loadContent: required
82+ function notifyAboutToFocusApp(appId) {
83+ if (!active) {
84+ return;
85+ }
4786
48− // 1 when fully shown and 0 when fully hidden
49− property real showProgress: visible ? MathUtils.clamp((width - Math.abs(x)) / width, 0, 1) : 0
87+ // A hint that we're about to focus an app. This way we can look
88+ // a little more responsive, rather than waiting for the above
89+ // notifyAppFocused call. We also need this in case we have a locked
90+ // app, in order to show lockscreen instead of new app.
91+ d.startUnlock(false /* toTheRight */);
92+ }
5093
51− property alias model: greeterContentLoader.model
52− property bool locked: true
94+ // This is a just a glorified notifyAboutToFocusApp(), but it does one
95+ // other thing: it hides any cover pages to the RIGHT, because the user
96+ // just came from a launcher drag starting on the left.
97+ // It also returns a boolean value, indicating whether there was a visual
98+ // change or not (the shell only wants to hide the launcher if there was
99+ // a change).
100+ function notifyShowingDashFromDrag() {
101+ if (!active) {
102+ return false;
103+ }
53104
54− readonly property bool narrowMode: !multiUser && height > width
55− readonly property bool multiUser: LightDM.Users.count > 1
105+ return d.startUnlock(true /* toTheRight */);
106+ }
56107
57− readonly property int currentIndex: greeterContentLoader.currentIndex
108+ QtObject {
109+ id: d
58110
59− signal selected(int uid)
60− signal unlocked(int uid)
61− signal tapped()
111+ readonly property bool multiUser: LightDM.Users.count > 1
112+ property int currentIndex
113+ property int delayMinutes
114+ property bool waiting
115+
116+ // We want 'launcherOffset' to animate down to zero. But not to animate
117+ // while being dragged. So ideally we change this only when the user
118+ // lets go and launcherOffset drops to zero. But we need to wait for
119+ // the behavior to be enabled first. So we cache the last known good
120+ // launcherOffset value to cover us during that brief gap between
121+ // release and the behavior turning on.
122+ property real lastKnownPositiveOffset // set in a launcherOffsetChanged below
123+ property real launcherOffsetProxy: (shown && !launcherOffsetProxyBehavior.enabled) ? lastKnownPositiveOffset : 0
124+ Behavior on launcherOffsetProxy {
125+ id: launcherOffsetProxyBehavior
126+ enabled: launcherOffset === 0
127+ UbuntuNumberAnimation {}
128+ }
129+
130+ function selectUser(uid, reset) {
131+ d.waiting = true;
132+ if (reset) {
133+ loader.item.reset();
134+ }
135+ currentIndex = uid;
136+ var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
137+ AccountsService.user = user;
138+ LauncherModel.setUser(user);
139+ LightDM.Greeter.authenticate(user); // always resets auth state
140+ }
141+
142+ function login() {
143+ enabled = false;
144+ if (LightDM.Greeter.startSessionSync()) {
145+ sessionStarted();
146+ loader.item.notifyAuthenticationSucceeded();
147+ } else {
148+ loader.item.notifyAuthenticationFailed();
149+ }
150+ enabled = true;
151+ }
62152
63− function hideRight() {
64− d.forceRightOnNextHideAnimation = true;
65− hide();
153+ function startUnlock(toTheRight) {
154+ if (loader.item) {
155+ return loader.item.tryToUnlock(toTheRight);
156+ } else {
157+ return false;
158+ }
159+ }
66160 }
67161
68− function tryToUnlock() {
69− if (created) {
70− greeterContentLoader.item.tryToUnlock()
162+ onLauncherOffsetChanged: {
163+ if (launcherOffset > 0) {
164+ d.lastKnownPositiveOffset = launcherOffset;
71165 }
72166 }
73167
74− function reset() {
75− if (created) {
76− greeterContentLoader.item.reset()
168+ onForcedUnlockChanged: {
169+ if (forcedUnlock && shown) {
170+ // pretend we were just authenticated
171+ loader.item.notifyAuthenticationSucceeded();
172+ }
173+ }
174+
175+ onRequiredChanged: {
176+ if (required) {
177+ d.waiting = true;
178+ lockedApp = "";
179+ }
180+ }
181+
182+ Component.onCompleted: {
183+ Connectivity.unlockAllModems();
184+ }
185+
186+ Timer {
187+ id: forcedDelayTimer
188+ interval: 1000 * 60
189+ onTriggered: {
190+ if (d.delayMinutes > 0) {
191+ d.delayMinutes -= 1;
192+ if (d.delayMinutes > 0) {
193+ start(); // go again
194+ }
195+ }
77196 }
78197 }
79198
@@ -82,152 +201,205 @@ Showable {
82201 MouseArea { anchors.fill: parent }
83202
84203 Loader {
85− id: greeterContentLoader
86− objectName: "greeterContentLoader"
204+ id: loader
205+ objectName: "loader"
206+
87207 anchors.fill: parent
88− property var model: LightDM.Users
89− property int currentIndex: 0
90− property var infographicModel: LightDM.Infographic
91− readonly property int backgroundTopMargin: -greeter.y
92208
93− source: loadContent ? "GreeterContent.qml" : ""
209+ active: root.required
210+ source: root.viewSource.toString() ? root.viewSource :
211+ (d.multiUser || root.tabletMode) ? "WideView.qml" : "NarrowView.qml"
94212
95213 onLoaded: {
96− selected(currentIndex);
214+ root.lockedApp = "";
215+ root.forceActiveFocus();
216+ d.selectUser(d.currentIndex, true);
217+ LightDM.Infographic.readyForDataChange();
97218 }
98219
99220 Connections {
100− target: greeterContentLoader.item
101−
221+ target: loader.item
102222 onSelected: {
103− greeter.selected(uid);
104− greeterContentLoader.currentIndex = uid;
223+ d.selectUser(index, true);
224+ }
225+ onResponded: {
226+ if (root.locked) {
227+ LightDM.Greeter.respond(response);
228+ } else {
229+ if (LightDM.Greeter.active && !LightDM.Greeter.authenticated) { // could happen if forcedUnlock
230+ d.login();
231+ }
232+ loader.item.hide();
233+ }
234+ }
235+ onTease: root.tease()
236+ onEmergencyCall: root.emergencyCall()
237+ onRequiredChanged: {
238+ if (!loader.item.required) {
239+ root.hide();
240+ }
105241 }
106− onUnlocked: greeter.unlocked(uid);
107242 }
243+
108244 Binding {
109− target: greeterContentLoader.item
110− property: "inputMethod"
111− value: greeter.inputMethod
245+ target: loader.item
246+ property: "backgroundTopMargin"
247+ value: -root.y
112248 }
113− }
114249
115− DragHandle {
116− id: dragHandle
117− anchors.fill: parent
118− anchors.leftMargin: greeter.dragHandleLeftMargin
119− enabled: (greeter.narrowMode || !greeter.locked) && greeter.enabled && greeter.shown
120− direction: Direction.Horizontal
250+ Binding {
251+ target: loader.item
252+ property: "launcherOffset"
253+ value: d.launcherOffsetProxy
254+ }
121255
122− onTapped: {
123− greeter.tapped();
124− showLabelAnimation.start();
256+ Binding {
257+ target: loader.item
258+ property: "dragHandleLeftMargin"
259+ value: root.dragHandleLeftMargin
125260 }
126261
127− onDraggingChanged: {
128− if (dragging) {
129− showLabelAnimation.start();
130− }
262+ Binding {
263+ target: loader.item
264+ property: "delayMinutes"
265+ value: d.delayMinutes
266+ }
267+
268+ Binding {
269+ target: loader.item
270+ property: "background"
271+ value: root.background
272+ }
273+
274+ Binding {
275+ target: loader.item
276+ property: "locked"
277+ value: root.locked
278+ }
279+
280+ Binding {
281+ target: loader.item
282+ property: "alphanumeric"
283+ value: AccountsService.passwordDisplayHint === AccountsService.Keyboard
284+ }
285+
286+ Binding {
287+ target: loader.item
288+ property: "currentIndex"
289+ value: d.currentIndex
290+ }
291+
292+ Binding {
293+ target: loader.item
294+ property: "userModel"
295+ value: LightDM.Users
296+ }
297+
298+ Binding {
299+ target: loader.item
300+ property: "infographicModel"
301+ value: LightDM.Infographic
131302 }
132303 }
133304
134− Label {
135− id: swipeHint
136− property real baseOpacity: 0.5
137− opacity: 0.0
138− anchors.horizontalCenter: parent.horizontalCenter
139− anchors.bottom: parent.bottom
140− anchors.bottomMargin: units.gu(5)
141− text: "《 " + i18n.tr("Unlock") + " 》"
142− color: "white"
143− font.weight: Font.Light
144−
145− SequentialAnimation on opacity {
146− id: showLabelAnimation
147− running: false
148− loops: 2
149−
150− StandardAnimation {
151− from: 0.0
152− to: swipeHint.baseOpacity
153− duration: UbuntuAnimation.SleepyDuration
305+ Connections {
306+ target: LightDM.Greeter
307+
308+ onShowGreeter: root.forceShow()
309+
310+ onHideGreeter: {
311+ d.login();
312+ loader.item.hide();
313+ }
314+
315+ onShowMessage: {
316+ if (!LightDM.Greeter.active) {
317+ return; // could happen if hideGreeter() comes in before we prompt
318+ }
319+
320+ // inefficient, but we only rarely deal with messages
321+ var html = text.replace(/&/g, "&amp;")
322+ .replace(/</g, "&lt;")
323+ .replace(/>/g, "&gt;")
324+ .replace(/\n/g, "<br>");
325+ if (isError) {
326+ html = "<font color=\"#df382c\">" + html + "</font>";
154327 }
155− PauseAnimation { duration: UbuntuAnimation.BriskDuration }
156− StandardAnimation {
157− from: swipeHint.baseOpacity
158− to: 0.0
159− duration: UbuntuAnimation.SleepyDuration
328+
329+ loader.item.showMessage(html);
330+ }
331+
332+ onShowPrompt: {
333+ d.waiting = false;
334+
335+ if (!LightDM.Greeter.active) {
336+ return; // could happen if hideGreeter() comes in before we prompt
160337 }
338+
339+ loader.item.showPrompt(text, isSecret, isDefaultPrompt);
161340 }
162− }
163341
164− // right side shadow
165− Image {
166− anchors.left: parent.right
167− anchors.top: parent.top
168− anchors.bottom: parent.bottom
169− fillMode: Image.Tile
170− source: "../graphics/dropshadow_right.png"
171− }
342+ onAuthenticationComplete: {
343+ d.waiting = false;
172344
173− // left side shadow
174− Image {
175− anchors.right: parent.left
176− anchors.top: parent.top
177− anchors.bottom: parent.bottom
178− fillMode: Image.Tile
179− source: "../graphics/dropshadow_left.png"
180− }
345+ if (LightDM.Greeter.authenticated) {
346+ AccountsService.failedLogins = 0;
347+ d.login();
348+ if (!LightDM.Greeter.promptless) {
349+ loader.item.hide();
350+ }
351+ } else {
352+ if (!LightDM.Greeter.promptless) {
353+ AccountsService.failedLogins++;
354+ }
181355
182− Binding {
183− id: positionLock
356+ // Check if we should initiate a factory reset
357+ if (maxFailedLogins >= 2) { // require at least a warning
358+ if (AccountsService.failedLogins === maxFailedLogins - 1) {
359+ loader.item.showLastChance();
360+ } else if (AccountsService.failedLogins >= maxFailedLogins) {
361+ SystemImage.factoryReset(); // Ouch!
362+ }
363+ }
184364
185− property bool enabled: false
186− onEnabledChanged: {
187− if (enabled === __enabled) {
188− return;
189− }
365+ // Check if we should initiate a forced login delay
366+ if (failedLoginsDelayAttempts > 0 && AccountsService.failedLogins % failedLoginsDelayAttempts == 0) {
367+ d.delayMinutes = failedLoginsDelayMinutes;
368+ forcedDelayTimer.start();
369+ }
190370
191− if (enabled) {
192− if (greeter.x > 0) {
193− value = Qt.binding(function() { return greeter.width; })
194− } else {
195− value = Qt.binding(function() { return -greeter.width; })
371+ loader.item.notifyAuthenticationFailed();
372+ if (!LightDM.Greeter.promptless) {
373+ d.selectUser(d.currentIndex, false);
196374 }
197375 }
198−
199− __enabled = enabled;
200376 }
201377
202− property bool __enabled: false
378+ onRequestAuthenticationUser: {
379+ // Find index for requested user, if it exists
380+ for (var i = 0; i < LightDM.Users.count; i++) {
381+ if (user === LightDM.Users.data(i, LightDM.UserRoles.NameRole)) {
382+ d.selectUser(i, true);
383+ return;
384+ }
385+ }
386+ }
387+ }
203388
204− target: greeter
205− when: __enabled
206− property: "x"
389+ Binding {
390+ target: LightDM.Greeter
391+ property: "active"
392+ value: root.active
207393 }
208394
209− hideAnimation: SequentialAnimation {
210− id: hideAnimation
211− objectName: "hideAnimation"
212− StandardAnimation {
213− id: hideTranslation
214− property: "x"
215− target: greeter
216− }
217− PropertyAction { target: greeter; property: "visible"; value: false }
218− PropertyAction { target: positionLock; property: "enabled"; value: true }
395+ Binding {
396+ target: LightDM.Infographic
397+ property: "username"
398+ value: AccountsService.statsWelcomeScreen ? LightDM.Users.data(d.currentIndex, LightDM.UserRoles.NameRole) : ""
219399 }
220400
221− showAnimation: SequentialAnimation {
222− id: showAnimation
223− objectName: "showAnimation"
224− PropertyAction { target: greeter; property: "visible"; value: true }
225− PropertyAction { target: positionLock; property: "enabled"; value: false }
226− StandardAnimation {
227− property: "x"
228− target: greeter
229− to: 0
230− duration: UbuntuAnimation.FastDuration
231− }
401+ Connections {
402+ target: i18n
403+ onLanguageChanged: LightDM.Infographic.readyForDataChange()
232404 }
233405}
qml/Greeter/GreeterContent.qmldeleted−163
@@ -1,163 +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.0
18−import AccountsService 0.1
19−import Ubuntu.Components 0.1
20−import LightDM 0.1 as LightDM
21−import "../Components"
22−
23−Item {
24− id: root
25− anchors.fill: parent
26−
27− property var inputMethod
28−
29− property bool ready: background.source == "" || background.status == Image.Ready || background.status == Image.Error
30−
31− signal selected(int uid)
32− signal unlocked(int uid)
33−
34− function tryToUnlock() {
35− if (loginLoader.item) {
36− loginLoader.item.tryToUnlock()
37− }
38− }
39−
40− function reset() {
41− if (loginLoader.item) {
42− loginLoader.item.reset()
43− }
44− }
45−
46− Rectangle {
47− // In case background fails to load
48− id: backgroundBackup
49− anchors.fill: parent
50− color: "black"
51− }
52−
53− CrossFadeImage {
54− id: background
55− objectName: "greeterBackground"
56− anchors {
57− fill: parent
58− topMargin: backgroundTopMargin
59− }
60− fillMode: Image.PreserveAspectCrop
61− // Limit how much memory we'll reserve for this image
62− sourceSize.height: height
63− sourceSize.width: width
64− source: greeter.background
65− }
66−
67− Rectangle {
68− anchors.fill: parent
69− color: "black"
70− opacity: 0.4
71− }
72−
73− Loader {
74− id: loginLoader
75− objectName: "loginLoader"
76− anchors {
77− left: parent.left
78− leftMargin: Math.min(parent.width * 0.16, units.gu(20))
79− top: parent.top
80− }
81− width: units.gu(29)
82− height: inputMethod && inputMethod.visible ? parent.height - inputMethod.keyboardRectangle.height
83− : parent.height
84− Behavior on height { UbuntuNumberAnimation {} }
85−
86− // TODO: Once we have a system API for determining which mode we are
87− // in, tablet/phone/desktop, that should be used instead of narrowMode.
88− source: greeter.narrowMode ? "" : "LoginList.qml"
89−
90− onLoaded: {
91− item.currentIndex = greeterContentLoader.currentIndex;
92− }
93−
94− Binding {
95− target: loginLoader.item
96− property: "model"
97− value: greeterContentLoader.model
98− }
99−
100− Connections {
101− target: loginLoader.item
102−
103− onSelected: {
104− root.selected(uid);
105− }
106−
107− onUnlocked: {
108− root.unlocked(uid);
109− }
110−
111− onCurrentIndexChanged: {
112− if (greeterContentLoader.currentIndex !== loginLoader.item.currentIndex) {
113− greeterContentLoader.currentIndex = loginLoader.item.currentIndex;
114− }
115− }
116− }
117− }
118−
119− Infographics {
120− id: infographics
121− objectName: "infographics"
122− height: narrowMode ? parent.height : 0.75 * parent.height
123− model: greeterContentLoader.infographicModel
124− clip: true // clip large data bubbles
125−
126− property string selectedUser
127− property string infographicUser: AccountsService.statsWelcomeScreen ? selectedUser : ""
128− onInfographicUserChanged: greeterContentLoader.infographicModel.username = infographicUser
129−
130− Component.onCompleted: {
131− selectedUser = greeterContentLoader.model.data(greeterContentLoader.currentIndex, LightDM.UserRoles.NameRole)
132− greeterContentLoader.infographicModel.username = infographicUser
133− greeterContentLoader.infographicModel.readyForDataChange()
134− }
135−
136− Connections {
137− target: root
138− onSelected: infographics.selectedUser = greeterContentLoader.model.data(uid, LightDM.UserRoles.NameRole)
139− }
140−
141− Connections {
142− target: i18n
143− onLanguageChanged: greeterContentLoader.infographicModel.readyForDataChange()
144− }
145−
146− anchors {
147− verticalCenter: parent.verticalCenter
148− left: narrowMode ? root.left : loginLoader.right
149− right: root.right
150− }
151− }
152−
153− Clock {
154− id: clock
155− visible: narrowMode
156−
157− anchors {
158− top: parent.top
159− topMargin: units.gu(2)
160− horizontalCenter: parent.horizontalCenter
161− }
162− }
163−}
qml/Greeter/Infographics.qml+1
@@ -38,6 +38,7 @@ Item {
3838
3939 Connections {
4040 target: model
41+ ignoreUnknownSignals: model === undefined
4142
4243 onDataAboutToAppear: startHideAnimation() // hide "no data" label
4344 onDataAppeared: startShowAnimation()
qml/Greeter/LoginList.qml+67−85
@@ -14,44 +14,76 @@
1414 * along with this program. If not, see <http://www.gnu.org/licenses/>.
1515 */
1616
17−import QtQuick 2.0
18−import Ubuntu.Components 0.1
19−import LightDM 0.1 as LightDM
17+import QtQuick 2.3
18+import Ubuntu.Components 1.1
2019import "../Components"
2120
2221Item {
2322 id: root
2423
25− property alias userList: userList
2624 property alias model: userList.model
27− property alias currentIndex: userList.currentIndex
25+ property int currentIndex
26+ property bool locked
2827
2928 readonly property int numAboveBelow: 4
3029 readonly property int cellHeight: units.gu(5)
3130 readonly property int highlightedHeight: units.gu(10)
3231 readonly property int moveDuration: 200
32+ readonly property string currentUser: userList.currentItem.username
3333 property bool wasPrompted: false
3434
35− signal selected(int uid)
36− signal unlocked(int uid)
35+ signal selected(int index)
36+ signal responded(string response)
3737
3838 function tryToUnlock() {
39− if (LightDM.Greeter.promptless) {
40− if (LightDM.Greeter.authenticated) {
41− root.unlocked(userList.currentIndex)
39+ if (wasPrompted) {
40+ passwordInput.forceActiveFocus();
41+ } else {
42+ if (root.locked) {
43+ root.selected(currentIndex);
4244 } else {
43− root.resetAuthentication()
45+ root.responded("");
4446 }
47+ }
48+ }
49+
50+ function showMessage(html) {
51+ if (infoLabel.text === "") {
52+ infoLabel.text = html;
4553 } else {
46− passwordInput.forceActiveFocus()
54+ infoLabel.text += "<br>" + html;
55+ }
56+ }
57+
58+ function showPrompt(text, isSecret, isDefaultPrompt) {
59+ passwordInput.text = "";
60+ passwordInput.promptText = text;
61+ passwordInput.enabled = true;
62+ passwordInput.echoMode = isSecret ? TextInput.Password : TextInput.Normal;
63+ if (wasPrompted) // stay in text field if second prompt
64+ passwordInput.focus = true;
65+ wasPrompted = true;
66+ }
67+
68+ function showError() {
69+ wrongPasswordAnimation.start();
70+ root.resetAuthentication();
71+ if (wasPrompted) {
72+ passwordInput.focus = true;
4773 }
4874 }
4975
5076 function reset() {
51− root.resetAuthentication()
77+ root.resetAuthentication();
5278 }
5379
54− Keys.onEscapePressed: root.resetAuthentication()
80+ Keys.onEscapePressed: {
81+ selected(currentIndex);
82+ }
83+
84+ onCurrentIndexChanged: {
85+ userList.currentIndex = currentIndex;
86+ }
5587
5688 Rectangle {
5789 id: highlightItem
@@ -81,26 +113,24 @@ Item {
81113 flickDeceleration: 10000
82114
83115 readonly property bool movingInternally: moveTimer.running || userList.moving
84−
85− onCurrentIndexChanged: {
86− if (LightDM.Greeter.authenticationUser != userList.model.data(currentIndex, LightDM.UserRoles.NameRole)) {
87− root.resetAuthentication();
88− }
89− }
90−
91116 onMovingInternallyChanged: {
92− // Only emit the selected signal once we stop moving to avoid frequent background changes
93117 if (!movingInternally) {
94− root.selected(userList.currentIndex);
118+ root.selected(currentIndex);
95119 }
96120 }
97121
122+ onCurrentIndexChanged: {
123+ root.resetAuthentication();
124+ moveTimer.start();
125+ }
126+
98127 delegate: Item {
99128 width: parent.width
100129 height: root.cellHeight
101130
102131 readonly property bool belowHighlight: (userList.currentIndex < 0 && index > 0) || (userList.currentIndex >= 0 && index > userList.currentIndex)
103132 readonly property int belowOffset: root.highlightedHeight - root.cellHeight
133+ readonly property string username: name
104134
105135 opacity: {
106136 // The goal here is to make names less and less opaque as they
@@ -148,11 +178,8 @@ Item {
148178 topMargin: parent.belowHighlight ? parent.belowOffset : 0
149179 }
150180 height: parent.height
151− enabled: userList.currentIndex !== index
152− onClicked: {
153− moveTimer.start();
154− userList.currentIndex = index;
155− }
181+ enabled: userList.currentIndex !== index && parent.opacity > 0
182+ onClicked: root.selected(index)
156183
157184 Behavior on anchors.topMargin { NumberAnimation { duration: root.moveDuration; easing.type: Easing.InOutQuad; } }
158185 }
@@ -204,6 +231,11 @@ Item {
204231 width: parent.width - anchors.margins * 2
205232 opacity: userList.movingInternally ? 0 : 1
206233
234+ property string promptText
235+ placeholderText: root.wasPrompted ? promptText
236+ : (root.locked ? i18n.tr("Retry")
237+ : i18n.tr("Tap to unlock"))
238+
207239 Behavior on opacity {
208240 NumberAnimation { duration: 100 }
209241 }
@@ -213,9 +245,11 @@ Item {
213245 return;
214246 root.focus = true; // so that it can handle Escape presses for us
215247 enabled = false;
216− LightDM.Greeter.respond(text);
248+ root.responded(text);
249+ }
250+ Keys.onEscapePressed: {
251+ root.selected(currentIndex);
217252 }
218− Keys.onEscapePressed: root.resetAuthentication()
219253
220254 Image {
221255 anchors {
@@ -223,7 +257,7 @@ Item {
223257 rightMargin: units.gu(2)
224258 verticalCenter: parent.verticalCenter
225259 }
226− visible: LightDM.Greeter.promptless
260+ visible: !root.wasPrompted
227261 source: "graphics/icon_arrow.png"
228262 }
229263
@@ -247,7 +281,7 @@ Item {
247281 id: passwordMouseArea
248282 objectName: "passwordMouseArea"
249283 anchors.fill: passwordInput
250− enabled: LightDM.Greeter.promptless
284+ enabled: !root.wasPrompted
251285 onClicked: root.tryToUnlock()
252286 }
253287
@@ -256,62 +290,10 @@ Item {
256290 return;
257291 }
258292 infoLabel.text = "";
259− passwordInput.placeholderText = "";
293+ passwordInput.promptText = "";
260294 passwordInput.text = "";
261295 passwordInput.focus = false;
262296 passwordInput.enabled = true;
263297 root.wasPrompted = false;
264− LightDM.Greeter.authenticate(userList.model.data(currentIndex, LightDM.UserRoles.NameRole));
265− }
266−
267− Connections {
268− target: LightDM.Greeter
269−
270− onShowMessage: {
271− // inefficient, but we only rarely deal with messages
272− var html = text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\n/g, "<br>")
273− if (isError)
274− html = "<font color=\"#df382c\">" + html + "</font>"
275− if (infoLabel.text == "")
276− infoLabel.text = html
277− else
278− infoLabel.text = infoLabel.text + "<br>" + html
279− }
280−
281− onShowPrompt: {
282− passwordInput.text = "";
283− passwordInput.placeholderText = text;
284− passwordInput.enabled = true;
285− passwordInput.echoMode = isSecret ? TextInput.Password : TextInput.Normal;
286− if (root.wasPrompted) // stay in text field if second prompt
287− passwordInput.focus = true;
288− root.wasPrompted = true;
289− }
290−
291− onAuthenticationComplete: {
292− if (LightDM.Greeter.promptless) {
293− passwordInput.placeholderText = LightDM.Greeter.authenticated ? "Tap to unlock" : "Retry";
294− return;
295− }
296− if (LightDM.Greeter.authenticated) {
297− root.unlocked(userList.currentIndex);
298− } else {
299− wrongPasswordAnimation.start();
300− root.resetAuthentication();
301− passwordInput.focus = true;
302− }
303− passwordInput.text = "";
304− }
305−
306− onRequestAuthenticationUser: {
307− // Find index for requested user, if it exists
308− for (var i = 0; i < userList.model.count; i++) {
309− if (user == userList.model.data(i, LightDM.UserRoles.NameRole)) {
310− moveTimer.start();
311− userList.currentIndex = i;
312− return;
313− }
314− }
315− }
316298 }
317299}
qml/Greeter/NarrowView.qmlnew file+170
@@ -0,0 +1,170 @@
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.3
18+import Ubuntu.Components 1.1
19+import "../Components"
20+
21+FocusScope {
22+ id: root
23+
24+ property alias dragHandleLeftMargin: coverPage.dragHandleLeftMargin
25+ property alias launcherOffset: coverPage.launcherOffset
26+ property int currentIndex // unused
27+ property alias delayMinutes: lockscreen.delayMinutes
28+ property alias backgroundTopMargin: coverPage.backgroundTopMargin
29+ property url background
30+ property bool locked
31+ property bool alphanumeric
32+ property var userModel // unused
33+ property alias infographicModel: coverPage.infographicModel
34+ readonly property bool fullyShown: coverPage.showProgress === 1 || lockscreen.shown
35+ readonly property bool required: coverPage.required || lockscreen.required
36+
37+ signal selected(int index) // unused
38+ signal responded(string response)
39+ signal tease()
40+ signal emergencyCall()
41+
42+ function showMessage(html) {
43+ // TODO
44+ }
45+
46+ function showPrompt(text, isSecret, isDefaultPrompt) {
47+ lockscreen.promptText = isDefaultPrompt ? "" : text.toLowerCase();
48+ lockscreen.maybeShow();
49+ }
50+
51+ function showLastChance() {
52+ var title = lockscreen.alphaNumeric ?
53+ i18n.tr("Sorry, incorrect passphrase.") :
54+ i18n.tr("Sorry, incorrect passcode.");
55+ var text = i18n.tr("This will be your last attempt.") + " " +
56+ (lockscreen.alphaNumeric ?
57+ i18n.tr("If passphrase is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted.") :
58+ i18n.tr("If passcode is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted."));
59+ lockscreen.showInfoPopup(title, text);
60+ }
61+
62+ function hide() {
63+ lockscreen.hide();
64+ coverPage.hide();
65+ }
66+
67+ function notifyAuthenticationSucceeded() {
68+ lockscreen.hide();
69+ }
70+
71+ function notifyAuthenticationFailed() {
72+ lockscreen.clear(true);
73+ }
74+
75+ function reset() {
76+ coverPage.show();
77+ }
78+
79+ function tryToUnlock(toTheRight) {
80+ var coverChanged = coverPage.shown;
81+ lockscreen.maybeShow();
82+ if (toTheRight) {
83+ coverPage.hideRight();
84+ } else {
85+ coverPage.hide();
86+ }
87+ return coverChanged;
88+ }
89+
90+ onLockedChanged: {
91+ if (locked) {
92+ lockscreen.maybeShow();
93+ } else {
94+ lockscreen.hide();
95+ }
96+ }
97+
98+ Lockscreen {
99+ id: lockscreen
100+ objectName: "lockscreen"
101+
102+ shown: false
103+ showAnimation: StandardAnimation { property: "opacity"; to: 1 }
104+ hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
105+ anchors.fill: parent
106+ visible: required
107+ enabled: !coverPage.shown
108+ background: root.background
109+ darkenBackground: 0.4
110+ alphaNumeric: root.alphanumeric
111+ minPinLength: 4
112+ maxPinLength: 4
113+
114+ property string promptText
115+ infoText: promptText !== "" ? i18n.tr("Enter %1").arg(promptText) :
116+ alphaNumeric ? i18n.tr("Enter passphrase") :
117+ i18n.tr("Enter passcode")
118+ errorText: promptText !== "" ? i18n.tr("Sorry, incorrect %1").arg(promptText) :
119+ alphaNumeric ? i18n.tr("Sorry, incorrect passphrase") + "\n" +
120+ i18n.tr("Please re-enter") :
121+ i18n.tr("Sorry, incorrect passcode")
122+
123+ onEntered: root.responded(passphrase)
124+ onCancel: coverPage.show()
125+ onEmergencyCall: root.emergencyCall()
126+
127+ function maybeShow() {
128+ if (root.locked && !shown) {
129+ showNow();
130+ }
131+ }
132+ }
133+
134+ Rectangle {
135+ anchors.fill: parent
136+ color: "black"
137+ opacity: coverPage.showProgress * 0.8
138+ }
139+
140+ CoverPage {
141+ id: coverPage
142+ objectName: "coverPage"
143+ height: parent.height
144+ width: parent.width
145+ background: root.background
146+ onTease: root.tease()
147+
148+ onShowProgressChanged: {
149+ if (showProgress === 1) {
150+ lockscreen.reset();
151+ }
152+
153+ if (showProgress === 0) {
154+ if (root.locked) {
155+ lockscreen.clear(false); // to reset focus if necessary
156+ } else {
157+ root.responded("");
158+ }
159+ }
160+ }
161+
162+ Clock {
163+ anchors {
164+ top: parent.top
165+ topMargin: units.gu(2)
166+ horizontalCenter: parent.horizontalCenter
167+ }
168+ }
169+ }
170+}
qml/Greeter/WideView.qmlnew file+134
@@ -0,0 +1,134 @@
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.3
18+import Ubuntu.Components 1.1
19+
20+FocusScope {
21+ id: root
22+
23+ property alias dragHandleLeftMargin: coverPage.dragHandleLeftMargin
24+ property alias launcherOffset: coverPage.launcherOffset
25+ property alias currentIndex: loginList.currentIndex
26+ property int delayMinutes // TODO
27+ property alias backgroundTopMargin: coverPage.backgroundTopMargin
28+ property alias background: coverPage.background
29+ property bool locked
30+ property bool alphanumeric // unused
31+ property alias userModel: loginList.model
32+ property alias infographicModel: coverPage.infographicModel
33+ readonly property bool fullyShown: coverPage.showProgress === 1
34+ readonly property bool required: coverPage.required
35+
36+ // so that it can be replaced in tests with a mock object
37+ property var inputMethod: Qt.inputMethod
38+
39+ signal selected(int index)
40+ signal responded(string response)
41+ signal tease()
42+ signal emergencyCall() // unused
43+
44+ function showMessage(html) {
45+ loginList.showMessage(html);
46+ }
47+
48+ function showPrompt(text, isSecret, isDefaultPrompt) {
49+ loginList.showPrompt(text, isSecret, isDefaultPrompt);
50+ }
51+
52+ function showLastChance() {
53+ // TODO
54+ }
55+
56+ function hide() {
57+ coverPage.hide();
58+ }
59+
60+ function notifyAuthenticationSucceeded() {
61+ // Nothing needed
62+ }
63+
64+ function notifyAuthenticationFailed() {
65+ loginList.showError();
66+ }
67+
68+ function reset() {
69+ loginList.reset();
70+ }
71+
72+ function tryToUnlock(toTheRight) {
73+ if (root.locked) {
74+ coverPage.show();
75+ loginList.tryToUnlock();
76+ return false;
77+ } else {
78+ var coverChanged = coverPage.shown;
79+ if (toTheRight) {
80+ coverPage.hideRight();
81+ } else {
82+ coverPage.hide();
83+ }
84+ return coverChanged;
85+ }
86+ }
87+
88+ Rectangle {
89+ anchors.fill: parent
90+ color: "black"
91+ opacity: coverPage.showProgress * 0.8
92+ }
93+
94+ CoverPage {
95+ id: coverPage
96+ objectName: "coverPage"
97+ height: parent.height
98+ width: parent.width
99+ draggable: !root.locked
100+
101+ infographics {
102+ height: 0.75 * parent.height
103+ anchors.leftMargin: loginList.x + loginList.width
104+ }
105+
106+ onTease: root.tease()
107+
108+ onShowProgressChanged: {
109+ if (showProgress === 0 && !root.locked) {
110+ root.responded("");
111+ }
112+ }
113+
114+ LoginList {
115+ id: loginList
116+ objectName: "loginList"
117+
118+ anchors {
119+ left: parent.left
120+ leftMargin: Math.min(parent.width * 0.16, units.gu(20))
121+ top: parent.top
122+ }
123+ width: units.gu(29)
124+ height: inputMethod && inputMethod.visible ? parent.height - inputMethod.keyboardRectangle.height
125+ : parent.height
126+ Behavior on height { UbuntuNumberAnimation {} }
127+
128+ locked: root.locked
129+
130+ onSelected: root.selected(index)
131+ onResponded: root.responded(response)
132+ }
133+ }
134+}
qml/Launcher/Launcher.qml+40−3
@@ -93,10 +93,10 @@ Item {
9393 Timer {
9494 id: dismissTimer
9595 objectName: "dismissTimer"
96− interval: 5000
96+ interval: 500
9797 onTriggered: {
9898 if (root.autohideEnabled) {
99− if (!panel.preventHiding) {
99+ if (!panel.preventHiding && !hoverArea.containsMouse) {
100100 root.state = ""
101101 } else {
102102 dismissTimer.restart()
@@ -159,7 +159,7 @@ Item {
159159
160160 MouseArea {
161161 id: launcherDragArea
162− enabled: root.available && root.state == "visible"
162+ enabled: root.available && (root.state == "visible" || root.state == "visibleTemporary")
163163 anchors.fill: panel
164164 anchors.rightMargin: -units.gu(2)
165165 drag {
@@ -245,6 +245,33 @@ Item {
245245 }
246246 }
247247
248+ // TODO: This should be replaced by some mechanism that reveals the launcher
249+ // after a certain resistance has been overcome, like unity7 does. However,
250+ // as we don't get relative mouse coordinates yet, this will do for now.
251+ MouseArea {
252+ id: hoverArea
253+ anchors { fill: panel; rightMargin: -1 }
254+ hoverEnabled: true
255+ propagateComposedEvents: true
256+ onContainsMouseChanged: {
257+ if (containsMouse) {
258+ root.switchToNextState("visibleTemporary");
259+ } else {
260+ dismissTimer.restart();
261+ }
262+ }
263+ onPressed: mouse.accepted = false;
264+
265+ // We need to eat touch events here in order to make sure that
266+ // we don't trigger both, the dragArea and the hoverArea
267+ MultiPointTouchArea {
268+ anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
269+ width: units.dp(1)
270+ mouseEnabled: false
271+ enabled: parent.enabled
272+ }
273+ }
274+
248275 EdgeDragArea {
249276 id: dragArea
250277 objectName: "launcherDragArea"
@@ -300,6 +327,16 @@ Item {
300327 target: panel
301328 x: -root.x // so we never go past panelWidth, even when teased by tutorial
302329 }
330+ PropertyChanges { target: hoverArea; enabled: false }
331+ },
332+ State {
333+ name: "visibleTemporary"
334+ extend: "visible"
335+ PropertyChanges {
336+ target: root
337+ autohideEnabled: true
338+ }
339+ PropertyChanges { target: hoverArea; enabled: true }
303340 },
304341 State {
305342 name: "teasing"
qml/Panel/IndicatorItem.qml+2
@@ -95,6 +95,8 @@ IndicatorDelegate {
9595 id: itemImage
9696 objectName: "icon"+index
9797 height: iconHeight
98+ // FIXME Workaround for bug https://bugs.launchpad.net/ubuntu/+source/ubuntu-ui-toolkit/+bug/1421293
99+ width: implicitWidth > 0 && implicitHeight > 0 ? (implicitWidth / implicitHeight * height) : implicitWidth;
98100 source: modelData
99101 color: root.color
100102 Behavior on color { ColorAnimation { duration: UbuntuAnimation.FastDuration; easing: UbuntuAnimation.StandardEasing } }
qml/Shell.qml+67−353
@@ -22,9 +22,7 @@ import Unity.Application 0.1
2222import Ubuntu.Components 0.1
2323import Ubuntu.Components.Popups 1.0
2424import Ubuntu.Gestures 0.1
25−import Ubuntu.SystemImage 0.1
2625import Ubuntu.Telephony 0.1 as Telephony
27−import Unity.Connectivity 0.1
2826import Unity.Launcher 0.1
2927import Utils 0.1
3028import LightDM 0.1 as LightDM
@@ -46,9 +44,9 @@ import Unity.Indicators 0.1 as Indicators
4644Item {
4745 id: shell
4846
49− // Disable everything so that user can't swipe greeter or launcher until
50− // we get first prompt/authenticate, which will re-enable the shell.
51− enabled: false
47+ // Disable everything while greeter is waiting, so that the user can't swipe
48+ // the greeter or launcher until we know whether the session is locked.
49+ enabled: !greeter.waiting
5250
5351 // this is only here to select the width / height of the window if not running fullscreen
5452 property bool tablet: false
@@ -61,18 +59,9 @@ Item {
6159 : gsImageTester.status == Image.Ready ? gsImageTester.source : defaultBackground
6260 readonly property real panelHeight: panel.panelHeight
6361
64− readonly property bool locked: LightDM.Greeter.active && !LightDM.Greeter.authenticated && !forcedUnlock
65− readonly property alias hasLockedApp: greeter.hasLockedApp
66− readonly property bool forcedUnlock: tutorial.running
67− onForcedUnlockChanged: if (forcedUnlock) lockscreen.hide()
68−
6962 property bool sideStageEnabled: shell.width >= units.gu(100)
7063 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
7164
72− property int maxFailedLogins: -1 // disabled by default for now, will enable via settings in future
73− property int failedLoginsDelayAttempts: 7 // number of failed logins
74− property int failedLoginsDelayMinutes: 5 // minutes of forced waiting
75−
7665 property int orientation
7766 readonly property int deviceOrientationAngle: Screen.angleBetween(Screen.primaryOrientation, Screen.orientation)
7867 onDeviceOrientationAngleChanged: {
@@ -99,7 +88,7 @@ Item {
9988 }
10089
10190 function startLockedApp(app) {
102− if (shell.locked) {
91+ if (greeter.locked) {
10392 greeter.lockedApp = app;
10493 }
10594 shell.activateApplication(app);
@@ -117,6 +106,7 @@ Item {
117106
118107 GSettings {
119108 id: backgroundSettings
109+ objectName: "backgroundSettings"
120110 schema.id: "org.gnome.desktop.background"
121111 }
122112
@@ -222,53 +212,32 @@ Item {
222212
223213 Connections {
224214 target: ApplicationManager
225− onFocusRequested: {
226− if (greeter.narrowMode) {
227− if (appId === "dialer-app" && callManager.hasCalls && shell.locked) {
228− // If we are in the middle of a call, make dialer lockedApp and show it.
229− // This can happen if user backs out of dialer back to greeter, then
230− // launches dialer again.
231− greeter.lockedApp = appId;
232− }
233− if (greeter.hasLockedApp) {
234− if (appId === greeter.lockedApp) {
235− lockscreen.hide() // show locked app
236− } else {
237− greeter.startUnlock() // show lockscreen if necessary
238− }
239− }
240− greeter.hide();
241− } else {
242− if (LightDM.Greeter.active) {
243− greeter.startUnlock()
244− }
245− }
246− }
247215
216+ // This signal is also fired when we try to focus the current app
217+ // again. We rely on this!
248218 onFocusedApplicationIdChanged: {
249− if (greeter.hasLockedApp && greeter.lockedApp !== ApplicationManager.focusedApplicationId) {
250− greeter.startUnlock()
251− }
252− panel.indicators.hide();
253− }
254−
255− onApplicationAdded: {
256− if (appId != "unity8-dash") {
257− if (greeter.shown) {
258− greeter.startUnlock();
259− }
219+ var appId = ApplicationManager.focusedApplicationId;
260220
221+ if (tutorial.running && appId != "unity8-dash") {
261222 // If this happens on first boot, we may be in edge
262223 // tutorial or wizard while receiving a call. But a call
263224 // is more important than wizard so just bail out of those.
264− if (tutorial.running) {
265− tutorial.finish();
266− wizard.hide();
267− }
225+ tutorial.finish();
226+ wizard.hide();
268227 }
269− if (greeter.narrowMode && greeter.hasLockedApp && appId === greeter.lockedApp) {
270− lockscreen.hide() // show locked app
228+
229+ if (appId === "dialer-app" && callManager.hasCalls && greeter.locked) {
230+ // If we are in the middle of a call, make dialer lockedApp and show it.
231+ // This can happen if user backs out of dialer back to greeter, then
232+ // launches dialer again.
233+ greeter.lockedApp = appId;
271234 }
235+ greeter.notifyAppFocused(appId);
236+
237+ panel.indicators.hide();
238+ }
239+
240+ onApplicationAdded: {
272241 launcher.hide();
273242 }
274243 }
@@ -292,7 +261,6 @@ Item {
292261
293262 property bool interactive: tutorial.spreadEnabled
294263 && !greeter.shown
295− && !lockscreen.shown
296264 && panel.indicators.fullyClosed
297265 && launcher.progress == 0
298266 && !notifications.useModal
@@ -328,7 +296,7 @@ Item {
328296 Binding {
329297 target: applicationsDisplayLoader.item
330298 property: "inverseProgress"
331− value: launcher.progress
299+ value: greeter.locked ? 0 : launcher.progress
332300 }
333301 Binding {
334302 target: applicationsDisplayLoader.item
@@ -379,299 +347,50 @@ Item {
379347 }
380348 }
381349
382− Lockscreen {
383− id: lockscreen
384− objectName: "lockscreen"
350+ Greeter {
351+ id: greeter
352+ objectName: "greeter"
385353
386354 hides: [launcher, panel.indicators]
387− shown: false
388− enabled: true
389− showAnimation: StandardAnimation { property: "opacity"; to: 1 }
390− hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
391− y: panel.panelHeight
392− visible: required
393− width: parent.width
394− height: parent.height - panel.panelHeight
355+ tabletMode: shell.sideStageEnabled
356+ launcherOffset: launcher.progress
357+ forcedUnlock: tutorial.running
395358 background: shell.background
396− darkenBackground: 0.4
397− alphaNumeric: AccountsService.passwordDisplayHint === AccountsService.Keyboard
398− minPinLength: 4
399− maxPinLength: 4
400−
401− property string promptText
402− infoText: promptText !== "" ? i18n.tr("Enter %1").arg(promptText) :
403− alphaNumeric ? i18n.tr("Enter passphrase") :
404− i18n.tr("Enter passcode")
405− errorText: promptText !== "" ? i18n.tr("Sorry, incorrect %1").arg(promptText) :
406− alphaNumeric ? i18n.tr("Sorry, incorrect passphrase") + "\n" +
407− i18n.tr("Please re-enter") :
408− i18n.tr("Sorry, incorrect passcode")
409−
410− // FIXME: We *should* show emergency dialer if there is a SIM present,
411− // regardless of whether the side stage is enabled. But right now,
412− // the assumption is that narrow screens are phones which have SIMs
413− // and wider screens are tablets which don't. When we do allow this
414− // on devices with a side stage and a SIM, work should be done to
415− // ensure that the main stage is disabled while the dialer is present
416− // in the side stage. See the FIXME in the stage loader in this file.
417− showEmergencyCallButton: !shell.sideStageEnabled
418−
419− onEntered: LightDM.Greeter.respond(passphrase);
420− onCancel: greeter.show()
421− onEmergencyCall: startLockedApp("dialer-app")
422−
423− onShownChanged: if (shown) greeter.lockedApp = ""
424−
425− function maybeShow() {
426− if (!shell.forcedUnlock && !shown) {
427− showNow();
428− }
429− }
430−
431− Timer {
432− id: forcedDelayTimer
433− interval: 1000 * 60
434− onTriggered: {
435− if (lockscreen.delayMinutes > 0) {
436− lockscreen.delayMinutes -= 1
437− if (lockscreen.delayMinutes > 0) {
438− start() // go again
439− }
440− }
441− }
442− }
443−
444− Component.onCompleted: {
445− if (greeter.narrowMode) {
446− LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
447− }
448− }
449− }
450−
451− Connections {
452− target: LightDM.Greeter
453359
454− onShowGreeter: greeter.show()
455− onHideGreeter: greeter.login()
456−
457− onShowPrompt: {
458− shell.enabled = true;
459− if (!LightDM.Greeter.active) {
460− return; // could happen if hideGreeter() comes in before we prompt
461− }
462− if (greeter.narrowMode) {
463− lockscreen.promptText = isDefaultPrompt ? "" : text.toLowerCase();
464− lockscreen.maybeShow();
465− }
466− }
467−
468− onPromptlessChanged: {
469− if (!LightDM.Greeter.active) {
470− return; // could happen if hideGreeter() comes in before we prompt
471− }
472− if (greeter.narrowMode) {
473− if (LightDM.Greeter.promptless && LightDM.Greeter.authenticated) {
474− lockscreen.hide()
475− } else {
476− lockscreen.reset();
477− lockscreen.maybeShow();
478− }
479− }
480− }
481−
482− onAuthenticationComplete: {
483− shell.enabled = true;
484− if (LightDM.Greeter.authenticated) {
485− AccountsService.failedLogins = 0
486− }
487− // Else only penalize user for a failed login if they actually were
488− // prompted for a password. We do this below after the promptless
489− // early exit.
490−
491− if (LightDM.Greeter.promptless) {
492− return;
493− }
494−
495− if (LightDM.Greeter.authenticated) {
496− greeter.login();
497− } else {
498− AccountsService.failedLogins++
499− if (maxFailedLogins >= 2) { // require at least a warning
500− if (AccountsService.failedLogins === maxFailedLogins - 1) {
501− var title = lockscreen.alphaNumeric ?
502− i18n.tr("Sorry, incorrect passphrase.") :
503− i18n.tr("Sorry, incorrect passcode.")
504− var text = i18n.tr("This will be your last attempt.") + " " +
505− (lockscreen.alphaNumeric ?
506− i18n.tr("If passphrase is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted.") :
507− i18n.tr("If passcode is entered incorrectly, your phone will conduct a factory reset and all personal data will be deleted."))
508− lockscreen.showInfoPopup(title, text)
509− } else if (AccountsService.failedLogins >= maxFailedLogins) {
510− SystemImage.factoryReset() // Ouch!
511− }
512− }
513− if (failedLoginsDelayAttempts > 0 && AccountsService.failedLogins % failedLoginsDelayAttempts == 0) {
514− lockscreen.delayMinutes = failedLoginsDelayMinutes
515− forcedDelayTimer.start()
516− }
517−
518− lockscreen.clear(true);
519− if (greeter.narrowMode) {
520− LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
521− }
522− }
523− }
524− }
525−
526− Binding {
527− target: LightDM.Greeter
528− property: "active"
529− value: greeter.shown || lockscreen.shown || greeter.hasLockedApp
530− }
531−
532− Rectangle {
533360 anchors.fill: parent
534− color: "black"
535− opacity: greeterWrapper.showProgress * 0.8
536− }
537−
538− Item {
539− // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
540− id: greeterWrapper
541− objectName: "greeterWrapper"
542− x: (greeter.narrowMode && greeter.showProgress > 0) ? launcher.progress : 0
543− y: panel.panelHeight
544− width: parent.width
545− height: parent.height - panel.panelHeight
361+ anchors.topMargin: panel.panelHeight
546362
547− Behavior on x {
548− enabled: !launcher.dashSwipe
549− StandardAnimation {}
550− }
363+ // avoid overlapping with Launcher's edge drag area
364+ // FIXME: Fix TouchRegistry & friends and remove this workaround
365+ // Issue involves launcher's DDA getting disabled on a long
366+ // left-edge drag
367+ dragHandleLeftMargin: launcher.available ? launcher.dragAreaWidth + 1 : 0
551368
552− property bool fullyShown: showProgress === 1.0
553− onFullyShownChanged: {
554− // Wait until the greeter is completely covering lockscreen before resetting it.
555− if (greeter.narrowMode && fullyShown && !LightDM.Greeter.authenticated) {
556− lockscreen.reset();
557− lockscreen.maybeShow();
558− }
369+ onSessionStarted: {
370+ launcher.hide();
559371 }
560372
561− readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
562− onShowProgressChanged: {
563− if (showProgress === 0) {
564− if ((LightDM.Greeter.promptless && LightDM.Greeter.authenticated) || shell.forcedUnlock) {
565− greeter.login()
566− } else if (greeter.narrowMode) {
567− lockscreen.clear(false) // to reset focus if necessary
568− }
373+ onTease: {
374+ if (!tutorial.running) {
375+ launcher.tease();
569376 }
570377 }
571378
572− Greeter {
573− id: greeter
574− objectName: "greeter"
575−
576− signal sessionStarted() // helpful for tests
577−
578− property string lockedApp: ""
579− property bool hasLockedApp: lockedApp !== ""
580−
581− hides: [launcher, panel.indicators]
582− loadContent: required || lockscreen.required // keeps content in memory for quick show()
583−
584− locked: shell.locked
585−
586− background: shell.background
587−
588− width: parent.width
589− height: parent.height
590−
591−
592− // avoid overlapping with Launcher's edge drag area
593− // FIXME: Fix TouchRegistry & friends and remove this workaround
594− // Issue involves launcher's DDA getting disabled on a long
595− // left-edge drag
596− dragHandleLeftMargin: launcher.available ? launcher.dragAreaWidth + 1 : 0
597−
598− function startUnlock() {
599− if (narrowMode) {
600− if (!LightDM.Greeter.authenticated) {
601− lockscreen.maybeShow()
602− }
603− hide()
604− } else {
605− show()
606− tryToUnlock()
607− }
608− }
609−
610− function login() {
611− enabled = false;
612− if (LightDM.Greeter.startSessionSync()) {
613− sessionStarted();
614− greeter.hide();
615− lockscreen.hide();
616− launcher.hide();
617− }
618− enabled = true;
619− }
620−
621− Timer {
622− // See powerConnection for why this is useful
623− id: showGreeterDelayed
624− interval: 1
625− onTriggered: {
626− greeter.showNow();
627− }
628− }
629−
630− onShownChanged: {
631− if (shown) {
632− // Disable everything so that user can't swipe greeter or
633− // launcher until we get the next prompt/authenticate, which
634− // will re-enable the shell.
635− shell.enabled = false;
636−
637− if (greeter.narrowMode) {
638− LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
639− } else {
640− reset()
641− }
642− greeter.lockedApp = "";
643− greeter.forceActiveFocus();
644− }
645− }
646−
647− Component.onCompleted: {
648− Connectivity.unlockAllModems()
649− }
650−
651− onUnlocked: greeter.hide()
652− onSelected: {
653− // Update launcher items for new user
654− var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
655− AccountsService.user = user;
656− LauncherModel.setUser(user);
657− }
379+ onEmergencyCall: startLockedApp("dialer-app")
658380
659− onTapped: {
660− if (!tutorial.running) {
661− launcher.tease();
662− }
663− }
664− onDraggingChanged: {
665− if (dragging && !tutorial.running) {
666− launcher.tease();
667− }
381+ Timer {
382+ // See powerConnection for why this is useful
383+ id: showGreeterDelayed
384+ interval: 1
385+ onTriggered: {
386+ greeter.forceShow();
668387 }
388+ }
669389
670− Binding {
671− target: ApplicationManager
672− property: "suspended"
673− value: (greeter.shown && greeterWrapper.showProgress == 1) || lockscreen.shown
674− }
390+ Binding {
391+ target: ApplicationManager
392+ property: "suspended"
393+ value: greeter.shown
675394 }
676395 }
677396
@@ -680,7 +399,7 @@ Item {
680399 target: callManager
681400
682401 onHasCallsChanged: {
683− if (shell.locked && callManager.hasCalls && greeter.lockedApp !== "dialer-app") {
402+ if (greeter.locked && callManager.hasCalls && greeter.lockedApp !== "dialer-app") {
684403 // We just received an incoming call while locked. The
685404 // indicator will have already launched dialer-app for us, but
686405 // there is a race between "hasCalls" changing and the dialer
@@ -720,9 +439,7 @@ Item {
720439 return
721440 }
722441
723− if (LightDM.Greeter.active) {
724− greeter.startUnlock()
725− }
442+ greeter.notifyAboutToFocusApp("unity8-dash");
726443
727444 var animate = !LightDM.Greeter.active && !stages.shown
728445 dash.setCurrentScope(0, animate, false)
@@ -730,17 +447,11 @@ Item {
730447 }
731448
732449 function showDash() {
733− if (greeter.hasLockedApp || // just in case user gets here
734− (!greeter.narrowMode && shell.locked)) {
735− return
736− }
737−
738− if (greeter.shown) {
739− greeter.hideRight();
450+ if (greeter.notifyShowingDashFromDrag()) {
740451 launcher.fadeOut();
741452 }
742453
743− if (ApplicationManager.focusedApplicationId != "unity8-dash") {
454+ if (!greeter.locked && ApplicationManager.focusedApplicationId != "unity8-dash") {
744455 ApplicationManager.requestFocusApplication("unity8-dash")
745456 launcher.fadeOut();
746457 }
@@ -758,7 +469,9 @@ Item {
758469 anchors.fill: parent //because this draws indicator menus
759470 indicators {
760471 hides: [launcher]
761− available: tutorial.panelEnabled && (!shell.locked || AccountsService.enableIndicatorsWhileLocked) && !greeter.hasLockedApp
472+ available: tutorial.panelEnabled
473+ && (!greeter.locked || AccountsService.enableIndicatorsWhileLocked)
474+ && !greeter.hasLockedApp
762475 contentEnabled: tutorial.panelContentEnabled
763476 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
764477
@@ -772,7 +485,7 @@ Item {
772485 }
773486 }
774487 callHint {
775− greeterShown: greeter.shown || lockscreen.shown
488+ greeterShown: greeter.shown
776489 }
777490
778491 property bool topmostApplicationIsFullscreen:
@@ -794,7 +507,9 @@ Item {
794507 anchors.bottom: parent.bottom
795508 width: parent.width
796509 dragAreaWidth: shell.edgeSize
797− available: tutorial.launcherEnabled && (!shell.locked || AccountsService.enableLauncherWhileLocked) && !greeter.hasLockedApp
510+ available: tutorial.launcherEnabled
511+ && (!greeter.locked || AccountsService.enableLauncherWhileLocked)
512+ && !greeter.hasLockedApp
798513 inverted: usageModeSettings.usageMode === "Staged"
799514 shadeBackground: !tutorial.running
800515
@@ -806,11 +521,10 @@ Item {
806521 }
807522 }
808523 onLauncherApplicationSelected: {
809− if (greeter.hasLockedApp) {
810− greeter.startUnlock()
811− }
812− if (!tutorial.running)
524+ if (!tutorial.running) {
525+ greeter.notifyAboutToFocusApp(appId);
813526 shell.activateApplication(appId)
527+ }
814528 }
815529 onShownChanged: {
816530 if (shown) {
qml/Stages/ApplicationWindow.qml+1−1
@@ -43,7 +43,7 @@ FocusScope {
4343 readonly property color splashColor: root.application ? root.application.splashColor : "#00000000"
4444 readonly property color splashColorHeader: root.application ? root.application.splashColorHeader : "#00000000"
4545 readonly property color splashColorFooter: root.application ? root.application.splashColorFooter : "#00000000"
46− readonly property url defaultScreenshot: root.application ? root.application.defaultScreenshot : ""
46+ readonly property url defaultScreenshot: (root.application && root.application.defaultScreenshot !== undefined) ? root.application.defaultScreenshot : ""
4747
4848 // Whether the Application had a surface before but lost it.
4949 property bool hadSurface: sessionContainer.surfaceContainer.hadSurface
qml/Stages/PhoneStage.qml+5−1
@@ -125,7 +125,11 @@ Rectangle {
125125
126126 onFocusedAppIdChanged: focusedAppDelegate = spreadRepeater.itemAt(0);
127127
128− onFocusedAppDelegateChanged: focusedAppDelegate.focus = true;
128+ onFocusedAppDelegateChanged: {
129+ if (focusedAppDelegate) {
130+ focusedAppDelegate.focus = true;
131+ }
132+ }
129133
130134 function indexOf(appId) {
131135 for (var i = 0; i < applicationManager.count; i++) {
qml/Wizard/Pages/20-sim.qml+5−1
@@ -25,11 +25,15 @@ LocalComponents.Page {
2525 title: i18n.tr("Add a SIM card and restart your device")
2626 forwardButtonSourceComponent: forwardButton
2727
28− // No need for skipValid, since OfonoManager does everything synchronously
28+ skipValid: !manager.available ||
29+ (manager.ready && (manager.modems.length < 1 || simManager0.ready)
30+ && (manager.modems.length < 2 || simManager1.ready))
2931 skip: !manager.available || manager.modems.length === 0 || simManager0.present || simManager1.present
3032
3133 OfonoManager {
3234 id: manager
35+ property bool ready: false
36+ onModemsChanged: ready = true
3337 }
3438
3539 // Ideally we would query the system more cleverly than hardcoding two
tests/autopilot/unity8/shell/__init__.py+1
@@ -27,6 +27,7 @@ import logging
2727
2828logger = logging.getLogger(__name__)
2929
30+
3031def disable_qml_mocking(fn):
3132 """Simple decorator that disables the QML mocks from being loaded."""
3233 @wraps(fn)
tests/autopilot/unity8/shell/emulators/greeter.py+19−3
@@ -19,6 +19,7 @@
1919
2020import ubuntuuitoolkit
2121
22+from autopilot.utilities import sleep
2223from unity8.shell.emulators import UnityEmulatorBase
2324
2425
@@ -26,12 +27,27 @@ class Greeter(UnityEmulatorBase):
2627 """An emulator that understands the greeter screen."""
2728
2829 def wait_swiped_away(self):
29− self.showProgress.wait_for(0)
30+ # We have to be careful here, because coverPage can go away at any time
31+ # if there isn't a lockscreen behind it (it hides completely, then
32+ # the greeter disposes it). But if there *is* a lockscreen, then we
33+ # need a different check, by looking at its showProgress. So make our
34+ # own wait_for loop and check both possibilities.
35+ for i in range(10):
36+ if not self.required:
37+ return
38+ coverPage = self.select_single(objectName='coverPage')
39+ if coverPage.showProgress == 0:
40+ return
41+ sleep(1)
42+
43+ raise AssertionError("Greeter cover page still up after 10s")
44+
3045
3146 def swipe(self):
3247 """Swipe the greeter screen away."""
33− self.created.wait_for(True)
34− self.showProgress.wait_for(1)
48+ self.waiting.wait_for(False)
49+ coverPage = self.select_single(objectName='coverPage')
50+ coverPage.showProgress.wait_for(1)
3551
3652 rect = self.globalRect
3753 start_x = rect[0] + rect[2] - 3
tests/autopilot/unity8/shell/emulators/main_window.py+17−6
@@ -35,12 +35,6 @@ class QQuickView(emulators.UnityEmulatorBase):
3535 def get_greeter(self):
3636 return self.select_single(Greeter)
3737
38− def get_greeter_content_loader(self):
39− return self.wait_select_single(
40− "QQuickLoader",
41− objectName="greeterContentLoader"
42− )
43−
4438 def get_login_loader(self):
4539 return self.select_single("QQuickLoader", objectName="loginLoader")
4640
@@ -174,3 +168,20 @@ class QQuickView(emulators.UnityEmulatorBase):
174168 'PinPadButton',
175169 objectName='pinPadButton{}'.format(button_id)
176170 )
171+
172+ @autopilot_logging.log_action(logger.info)
173+ def wait_for_notification(self):
174+ """Wait for a notification dialog to appear.
175+
176+ :return: An object for the notification dialog data.
177+ :raise StateNotFoundError: if the timeout expires when the
178+ notification has not appeared.
179+
180+ """
181+ notify_list = self.select_single('Notifications',
182+ objectName='notificationList')
183+ visible_notification = notify_list.wait_select_single('Notification',
184+ visible=True)
185+ return {'summary': visible_notification.summary,
186+ 'body': visible_notification.body,
187+ 'iconSource': visible_notification.iconSource}
tests/autopilot/unity8/shell/tests/__init__.py+2−3
@@ -375,9 +375,8 @@ class UnityTestCase(AutopilotTestCase):
375375 self._proxy = None
376376
377377 def wait_for_unity(self):
378− greeter_content_loader = self.main_window.wait_select_single(
379− objectName='greeterContentLoader')
380− greeter_content_loader.progress.wait_for(1)
378+ greeter = self.main_window.wait_select_single(objectName='greeter')
379+ greeter.waiting.wait_for(False)
381380
382381 def get_dash(self):
383382 pid = process_helpers.get_job_pid('unity8-dash')
tests/autopilot/unity8/shell/tests/test_lock_screen.py+8−12
@@ -39,14 +39,13 @@ class TestLockscreen(UnityTestCase):
3939 self.launch_unity()
4040 greeter = self.main_window.get_greeter()
4141
42− if greeter.narrowMode:
42+ if not greeter.tabletMode:
4343 greeter.swipe()
4444 lockscreen = self._wait_for_lockscreen()
4545 self.main_window.enter_pin_code("1234")
46− self.assertThat(lockscreen.shown, Eventually(Equals(False)))
4746 else:
4847 self._enter_prompt_passphrase("1234\n")
49− self.assertThat(greeter.shown, Eventually(Equals(False)))
48+ self.assertThat(greeter.shown, Eventually(Equals(False)))
5049
5150 def test_can_unlock_passphrase_screen(self):
5251 """Must be able to unlock the passphrase entry screen."""
@@ -55,14 +54,13 @@ class TestLockscreen(UnityTestCase):
5554 self.launch_unity()
5655 greeter = self.main_window.get_greeter()
5756
58− if greeter.narrowMode:
57+ if not greeter.tabletMode:
5958 greeter.swipe()
6059 lockscreen = self._wait_for_lockscreen()
6160 self._enter_pin_passphrase("password")
62− self.assertThat(lockscreen.shown, Eventually(Equals(False)))
6361 else:
6462 self._enter_prompt_passphrase("password")
65− self.assertThat(greeter.shown, Eventually(Equals(False)))
63+ self.assertThat(greeter.shown, Eventually(Equals(False)))
6664
6765 def test_pin_screen_wrong_code(self):
6866 """Entering the wrong pin code must not dismiss the lock screen."""
@@ -70,18 +68,17 @@ class TestLockscreen(UnityTestCase):
7068 self.launch_unity()
7169 greeter = self.main_window.get_greeter()
7270
73− if greeter.narrowMode:
71+ if not greeter.tabletMode:
7472 greeter.swipe()
7573 lockscreen = self._wait_for_lockscreen()
7674 self.main_window.enter_pin_code("4321")
7775 pinentryField = self.main_window.get_pinentryField()
7876 self.assertThat(pinentryField.text, Eventually(Equals("")))
79− self.assertThat(lockscreen.shown, Eventually(Equals(True)))
8077 else:
8178 self._enter_prompt_passphrase("4231\n")
8279 prompt = self.main_window.get_greeter().get_prompt()
8380 self.assertThat(prompt.text, Eventually(Equals("")))
84− self.assertThat(greeter.shown, Eventually(Equals(True)))
81+ self.assertThat(greeter.shown, Eventually(Equals(True)))
8582
8683 def test_passphrase_screen_wrong_password(self):
8784 """Entering the wrong password must not dismiss the lock screen."""
@@ -89,18 +86,17 @@ class TestLockscreen(UnityTestCase):
8986 self.launch_unity()
9087 greeter = self.main_window.get_greeter()
9188
92− if greeter.narrowMode:
89+ if not greeter.tabletMode:
9390 greeter.swipe()
9491 lockscreen = self._wait_for_lockscreen()
9592 self._enter_pin_passphrase("foobar")
9693 pinentryField = self.main_window.get_pinentryField()
9794 self.assertThat(pinentryField.text, Eventually(Equals("")))
98− self.assertThat(lockscreen.shown, Eventually(Equals(True)))
9995 else:
10096 self._enter_prompt_passphrase("foobar")
10197 prompt = self.main_window.get_greeter().get_prompt()
10298 self.assertThat(prompt.text, Eventually(Equals("")))
103− self.assertThat(greeter.shown, Eventually(Equals(True)))
99+ self.assertThat(greeter.shown, Eventually(Equals(True)))
104100
105101 def _wait_for_lockscreen(self):
106102 """Wait for the lock screen to load, and return it."""
tests/autopilot/unity8/shell/tests/test_notifications.py+19
@@ -693,3 +693,22 @@ class EphemeralNotificationsTests(NotificationsBase):
693693 self.assertThat(get_notification, Eventually(NotEquals(None)))
694694 self._assert_notification(
695695 get_notification(), summary, body, False, False, 1.0)
696+
697+ def test_notification_helper(self):
698+ """ use the create notification script to get a notification dialog.
699+ Check that the arguments passed to the script match the fields. """
700+
701+ unity_proxy = self.launch_unity()
702+ unlock_unity(unity_proxy)
703+
704+ summary = 'Helper summary'
705+ body = 'Helper body'
706+
707+ notification = shell.create_ephemeral_notification(summary, body)
708+ notification.show()
709+
710+ notification_data = self.main_window.wait_for_notification()
711+
712+ self.assertThat(notification_data['summary'],
713+ Eventually(Equals(summary)))
714+ self.assertThat(notification_data['body'], Eventually(Equals(body)))
tests/mocks/AccountsService/AccountsService.qmltypes+3
@@ -29,5 +29,8 @@ Module {
2929 Property { name: "statsWelcomeScreen"; type: "bool" }
3030 Property { name: "passwordDisplayHint"; type: "PasswordDisplayHint"; isReadonly: true }
3131 Property { name: "failedLogins"; type: "uint" }
32+ Property { name: "hereEnabled"; type: "bool" }
33+ Property { name: "hereLicensePath"; type: "string" }
34+ Property { name: "hereLicensePathValid"; type: "bool"; isReadonly: true }
3235 }
3336}
tests/mocks/LightDM/LightDM.qmltypes+33−13
@@ -10,10 +10,6 @@ Module {
1010 Component {
1111 name: "Greeter"
1212 prototype: "QObject"
13− exports: ["LightDM/Greeter 0.1"]
14− isCreatable: false
15− isSingleton: true
16− exportMetaObjectRevisions: [0]
1713 Property { name: "active"; type: "bool" }
1814 Property { name: "authenticated"; type: "bool"; isReadonly: true }
1915 Property { name: "authenticationUser"; type: "string"; isReadonly: true }
@@ -62,6 +58,32 @@ Module {
6258 Parameter { name: "isActive"; type: "bool" }
6359 }
6460 }
61+ Component {
62+ name: "MockGreeter"
63+ prototype: "Greeter"
64+ exports: ["LightDM/Greeter 0.1"]
65+ isCreatable: false
66+ isSingleton: true
67+ exportMetaObjectRevisions: [0]
68+ Property { name: "mockMode"; type: "string" }
69+ Signal {
70+ name: "mockModeChanged"
71+ Parameter { name: "mode"; type: "string" }
72+ }
73+ }
74+ Component {
75+ name: "MockUsersModel"
76+ prototype: "UsersModel"
77+ exports: ["LightDM/Users 0.1"]
78+ isCreatable: false
79+ isSingleton: true
80+ exportMetaObjectRevisions: [0]
81+ Property { name: "mockMode"; type: "string" }
82+ Signal {
83+ name: "mockModeChanged"
84+ Parameter { name: "mode"; type: "string" }
85+ }
86+ }
6587 Component {
6688 name: "QAbstractProxyModel"
6789 prototype: "QAbstractItemModel"
@@ -86,6 +108,11 @@ Module {
86108 "BackgroundPathRole": 263
87109 }
88110 }
111+ Property { name: "mockMode"; type: "string" }
112+ Signal {
113+ name: "mockModeChanged"
114+ Parameter { name: "mode"; type: "string" }
115+ }
89116 }
90117 Component {
91118 name: "QSortFilterProxyModel"
@@ -114,7 +141,7 @@ Module {
114141 Method { name: "invalidate" }
115142 }
116143 Component {
117− name: "QSortFilterProxyModelQML"
144+ name: "UnitySortFilterProxyModelQML"
118145 prototype: "QSortFilterProxyModel"
119146 Property { name: "model"; type: "QAbstractItemModel"; isPointer: true }
120147 Property { name: "totalCount"; type: "int"; isReadonly: true }
@@ -240,12 +267,5 @@ Module {
240267 Method { name: "nextDataSourceSlot" }
241268 Method { name: "readyForDataChangeSlot" }
242269 }
243− Component {
244− name: "UsersModel"
245− prototype: "QSortFilterProxyModelQML"
246− exports: ["LightDM/Users 0.1"]
247− isCreatable: false
248− isSingleton: true
249− exportMetaObjectRevisions: [0]
250− }
270+ Component { name: "UsersModel"; prototype: "UnitySortFilterProxyModelQML" }
251271}
tests/mocks/LightDM/liblightdm/Greeter.h−2
@@ -43,8 +43,6 @@ class Q_DECL_EXPORT Greeter : public QObject
4343 Q_PROPERTY(bool hasGuestAccount READ hasGuestAccountHint CONSTANT)
4444 Q_PROPERTY(bool locked READ lockHint CONSTANT)
4545
46− Q_PROPERTY(QString hostname READ hostname CONSTANT)
47−
4846 Q_ENUMS(PromptType MessageType)
4947
5048 //Mock-only API for testing purposes
tests/mocks/MeeGo/QOfono/CMakeLists.txt+1
@@ -1,6 +1,7 @@
11set(MOCK_QOFONO_SOURCES
22 plugin.cpp
33 MockQOfono.cpp
4+ MockQOfonoManager.cpp
45)
56
67add_library(MockQOfono MODULE ${MOCK_QOFONO_SOURCES})
tests/mocks/MeeGo/QOfono/MockOfonoManager.qmldeleted−23
@@ -1,23 +0,0 @@
1−/*
2− * Copyright (C) 2014 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.3
18−import MeeGo.QOfono 0.2
19−
20−Item {
21− readonly property var modems: MockQOfono.modems
22− readonly property bool available: MockQOfono.available
23−}
tests/mocks/MeeGo/QOfono/MockOfonoSimManager.qml+12−2
@@ -22,21 +22,31 @@ Item {
2222
2323 property string modemPath
2424 readonly property alias present: d.present
25+ readonly property alias ready: d.ready
2526
2627 QtObject {
2728 id: d
2829
2930 property bool present: false
31+ property bool ready: false
3032
3133 function updatePresence() {
3234 d.present = MockQOfono.isModemPresent(simManager.modemPath)
35+ d.ready = MockQOfono.isModemReady(simManager.modemPath);
3336 }
3437 }
3538
36− onModemPathChanged: d.updatePresence()
39+ // Simulate QOfono's asynchronous initialization
40+ Timer {
41+ id: asyncTimer
42+ interval: 1
43+ onTriggered: d.updatePresence()
44+ }
45+
46+ onModemPathChanged: asyncTimer.start()
3747
3848 Connections {
3949 target: MockQOfono
40− onModemsChanged: d.updatePresence()
50+ onModemsChanged: asyncTimer.start()
4151 }
4252}
tests/mocks/MeeGo/QOfono/MockQOfono.cpp+41−5
@@ -16,13 +16,31 @@
1616
1717#include "MockQOfono.h"
1818
19−MockQOfono::MockQOfono(QObject *parent)
20− : QObject(parent),
19+static MockQOfono *m_instance = nullptr;
20+
21+MockQOfono *MockQOfono::instance()
22+{
23+ if (!m_instance) {
24+ m_instance = new MockQOfono();
25+ }
26+ return m_instance;
27+}
28+
29+MockQOfono::MockQOfono()
30+ : QObject(),
2131 m_available(false),
32+ m_ready(false),
2233 m_modems()
2334{
2435}
2536
37+MockQOfono::~MockQOfono()
38+{
39+ if (m_instance == this) {
40+ m_instance = nullptr;
41+ }
42+}
43+
2644bool MockQOfono::available() const
2745{
2846 return m_available;
@@ -34,21 +52,39 @@ void MockQOfono::setAvailable(bool available)
3452 Q_EMIT availableChanged();
3553}
3654
55+bool MockQOfono::ready() const
56+{
57+ return m_ready;
58+}
59+
60+void MockQOfono::setReady(bool ready)
61+{
62+ m_ready = ready;
63+ Q_EMIT readyChanged();
64+}
65+
3766QStringList MockQOfono::modems() const
3867{
3968 return m_modems.keys();
4069}
4170
42−void MockQOfono::setModems(const QStringList &modems, const QList<bool> &present)
71+void MockQOfono::setModems(const QStringList &modems, const QList<bool> &present, const QList<bool> &ready)
4372{
4473 m_modems.clear();
4574 for (int i = 0; i < modems.length(); i++) {
46− m_modems[modems[i]] = present[i];
75+ QList<bool> props;
76+ props << present.value(i, true) << ready.value(i, true);
77+ m_modems[modems[i]] = props;
4778 }
4879 Q_EMIT modemsChanged();
4980}
5081
5182bool MockQOfono::isModemPresent(const QString &modem)
5283{
53− return m_modems.value(modem, false);
84+ return m_modems.value(modem).value(0, false);
85+}
86+
87+bool MockQOfono::isModemReady(const QString &modem)
88+{
89+ return m_modems.value(modem).value(1, false);
5490}
tests/mocks/MeeGo/QOfono/MockQOfono.h+12−3
@@ -28,25 +28,34 @@ class MockQOfono : public QObject
2828{
2929 Q_OBJECT
3030 Q_PROPERTY(bool available READ available WRITE setAvailable NOTIFY availableChanged)
31+ Q_PROPERTY(bool ready READ ready WRITE setReady NOTIFY readyChanged)
3132 Q_PROPERTY(QStringList modems READ modems NOTIFY modemsChanged)
3233
3334public:
34− explicit MockQOfono(QObject *parent = 0);
35+ static MockQOfono *instance();
36+ ~MockQOfono();
3537
3638 bool available() const;
3739 void setAvailable(bool available);
40+ bool ready() const;
41+ void setReady(bool ready);
3842 QStringList modems() const;
3943
40− Q_INVOKABLE void setModems(const QStringList &modems, const QList<bool> &present);
44+ Q_INVOKABLE void setModems(const QStringList &modems, const QList<bool> &present, const QList<bool> &ready);
4145 Q_INVOKABLE bool isModemPresent(const QString &modem);
46+ Q_INVOKABLE bool isModemReady(const QString &modem);
4247
4348Q_SIGNALS:
4449 void availableChanged();
50+ void readyChanged();
4551 void modemsChanged();
4652
4753private:
54+ explicit MockQOfono();
55+
4856 bool m_available;
49− QMap<QString, bool> m_modems;
57+ bool m_ready;
58+ QMap<QString, QList<bool>> m_modems;
5059};
5160
5261#endif // MOCK_QOFONO_H
tests/mocks/MeeGo/QOfono/MockQOfonoManager.cppnew file+66
@@ -0,0 +1,66 @@
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+#include "MockQOfono.h"
18+#include "MockQOfonoManager.h"
19+#include <QTimer>
20+
21+MockQOfonoManager::MockQOfonoManager(QObject *parent)
22+ : QObject(parent),
23+ m_modemsSet(false),
24+ m_startedSet(false)
25+{
26+ connect(MockQOfono::instance(), SIGNAL(availableChanged()),
27+ this, SIGNAL(availableChanged()));
28+ connect(MockQOfono::instance(), SIGNAL(readyChanged()),
29+ this, SLOT(checkReady()));
30+ connect(MockQOfono::instance(), SIGNAL(modemsChanged()),
31+ this, SLOT(maybeModemsChanged()));
32+
33+ checkReady();
34+}
35+
36+bool MockQOfonoManager::available() const
37+{
38+ return MockQOfono::instance()->available();
39+}
40+
41+QStringList MockQOfonoManager::modems() const
42+{
43+ return m_modemsSet ? MockQOfono::instance()->modems() : QStringList();
44+}
45+
46+void MockQOfonoManager::maybeModemsChanged()
47+{
48+ if (m_modemsSet) {
49+ Q_EMIT modemsChanged();
50+ }
51+}
52+
53+void MockQOfonoManager::checkReady()
54+{
55+ if (!m_startedSet && MockQOfono::instance()->ready()) {
56+ m_startedSet = true;
57+ // Simulate QOfono's asynchronous initialization
58+ QTimer::singleShot(1, this, SLOT(setModems()));
59+ }
60+}
61+
62+void MockQOfonoManager::setModems()
63+{
64+ m_modemsSet = true;
65+ Q_EMIT modemsChanged(); // always emitted, even if new modem list is empty too
66+}
tests/mocks/MeeGo/QOfono/MockQOfonoManager.hnew file+49
@@ -0,0 +1,49 @@
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+#ifndef MOCK_QOFONO_MANAGER_H
18+#define MOCK_QOFONO_MANAGER_H
19+
20+#include <QObject>
21+#include <QStringList>
22+
23+class MockQOfonoManager : public QObject
24+{
25+ Q_OBJECT
26+ Q_PROPERTY(bool available READ available NOTIFY availableChanged)
27+ Q_PROPERTY(QStringList modems READ modems NOTIFY modemsChanged)
28+
29+public:
30+ explicit MockQOfonoManager(QObject *parent = 0);
31+
32+ bool available() const;
33+ QStringList modems() const;
34+
35+Q_SIGNALS:
36+ void availableChanged();
37+ void modemsChanged();
38+
39+private Q_SLOTS:
40+ void maybeModemsChanged();
41+ void checkReady();
42+ void setModems();
43+
44+private:
45+ bool m_modemsSet;
46+ bool m_startedSet;
47+};
48+
49+#endif // MOCK_QOFONO_MANAGER_H
tests/mocks/MeeGo/QOfono/QOfono.qmltypesnew file+30
@@ -0,0 +1,30 @@
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 MeeGo.QOfono 0.2 tests/mocks'
8+
9+Module {
10+ Component {
11+ name: "MockQOfono"
12+ prototype: "QObject"
13+ exports: ["MeeGo.QOfono/MockQOfono 0.2"]
14+ isCreatable: false
15+ isSingleton: true
16+ exportMetaObjectRevisions: [0]
17+ Property { name: "available"; type: "bool" }
18+ Property { name: "modems"; type: "QStringList"; isReadonly: true }
19+ Method {
20+ name: "setModems"
21+ Parameter { name: "modems"; type: "QStringList" }
22+ Parameter { name: "present"; type: "QList<bool>" }
23+ }
24+ Method {
25+ name: "isModemPresent"
26+ type: "bool"
27+ Parameter { name: "modem"; type: "string" }
28+ }
29+ }
30+}
tests/mocks/MeeGo/QOfono/plugin.cpp+3−1
@@ -16,6 +16,7 @@
1616
1717#include "plugin.h"
1818#include "MockQOfono.h"
19+#include "MockQOfonoManager.h"
1920
2021#include <QtQml>
2122
@@ -23,11 +24,12 @@ static QObject *mock_provider(QQmlEngine *engine, QJSEngine *scriptEngine)
2324{
2425 Q_UNUSED(engine)
2526 Q_UNUSED(scriptEngine)
26− return new MockQOfono();
27+ return MockQOfono::instance();
2728}
2829
2930void BackendPlugin::registerTypes(const char *uri)
3031{
3132 Q_ASSERT(uri == QLatin1String("MeeGo.QOfono"));
3233 qmlRegisterSingletonType<MockQOfono>(uri, 0, 2, "MockQOfono", mock_provider); // just for mock
34+ qmlRegisterType<MockQOfonoManager>(uri, 0, 2, "OfonoManager");
3335}
tests/mocks/MeeGo/QOfono/qmldir−1
@@ -1,4 +1,3 @@
11module MeeGo.QOfono
22plugin MockQOfono
3−OfonoManager 0.2 MockOfonoManager.qml
43OfonoSimManager 0.2 MockOfonoSimManager.qml
tests/mocks/QMenuModel/QMenuModel.qmltypes+33
@@ -7,12 +7,45 @@ import QtQuick.tooling 1.1
77// 'qmlplugindump -notrelocatable QMenuModel 0.1 tests/mocks'
88
99Module {
10+ Component {
11+ name: "ActionData"
12+ prototype: "QObject"
13+ exports: ["QMenuModel/ActionData 0.1"]
14+ isCreatable: false
15+ isSingleton: true
16+ exportMetaObjectRevisions: [0]
17+ Property { name: "data"; type: "QVariant" }
18+ }
1019 Component {
1120 name: "ActionStateParser"
1221 prototype: "QObject"
1322 exports: ["QMenuModel/ActionStateParser 0.1"]
1423 exportMetaObjectRevisions: [0]
1524 }
25+ Component {
26+ name: "DBusEnums"
27+ prototype: "QObject"
28+ exports: ["QMenuModel/DBus 0.1"]
29+ isCreatable: false
30+ exportMetaObjectRevisions: [0]
31+ Enum {
32+ name: "BusType"
33+ values: {
34+ "None": 0,
35+ "SessionBus": 1,
36+ "SystemBus": 2,
37+ "LastBusType": 3
38+ }
39+ }
40+ Enum {
41+ name: "ConnectionStatus"
42+ values: {
43+ "Disconnected": 0,
44+ "Connecting": 1,
45+ "Connected": 2
46+ }
47+ }
48+ }
1649 Component {
1750 name: "UnityMenuModel"
1851 prototype: "QAbstractListModel"
tests/mocks/Ubuntu/SystemSettings/LanguagePlugin/LanguagePlugin.qmltypesnew file+10
@@ -0,0 +1,10 @@
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 Ubuntu.SystemSettings.LanguagePlugin 1.0 tests/mocks'
8+
9+Module {
10+}
tests/mocks/Ubuntu/SystemSettings/SecurityPrivacy/SecurityPrivacy.qmltypesnew file+39
@@ -0,0 +1,39 @@
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 Ubuntu.SystemSettings.SecurityPrivacy 1.0 tests/mocks'
8+
9+Module {
10+ Component {
11+ name: "MockSecurityPrivacy"
12+ prototype: "QObject"
13+ exports: [
14+ "Ubuntu.SystemSettings.SecurityPrivacy/UbuntuSecurityPrivacyPanel 1.0"
15+ ]
16+ exportMetaObjectRevisions: [0]
17+ Enum {
18+ name: "SecurityType"
19+ values: {
20+ "Swipe": 0,
21+ "Passcode": 1,
22+ "Passphrase": 2
23+ }
24+ }
25+ Signal {
26+ name: "setSecurityCalled"
27+ Parameter { name: "oldPasswd"; type: "string" }
28+ Parameter { name: "newPasswd"; type: "string" }
29+ Parameter { name: "newType"; type: "SecurityType" }
30+ }
31+ Method {
32+ name: "setSecurity"
33+ type: "string"
34+ Parameter { name: "oldPasswd"; type: "string" }
35+ Parameter { name: "newPasswd"; type: "string" }
36+ Parameter { name: "newType"; type: "SecurityType" }
37+ }
38+ }
39+}
tests/mocks/Unity/Application/Application.qmltypes+42
@@ -239,6 +239,8 @@ Module {
239239 Property { name: "name"; type: "string"; isReadonly: true }
240240 Property { name: "live"; type: "bool"; isReadonly: true }
241241 Property { name: "orientation"; type: "Qt::ScreenOrientation" }
242+ Property { name: "touchPressCount"; type: "int" }
243+ Property { name: "touchReleaseCount"; type: "int" }
242244 Signal {
243245 name: "typeChanged"
244246 Parameter { type: "Type" }
@@ -251,6 +253,14 @@ Module {
251253 name: "liveChanged"
252254 Parameter { name: "live"; type: "bool" }
253255 }
256+ Signal {
257+ name: "touchPressCountChanged"
258+ Parameter { name: "count"; type: "int" }
259+ }
260+ Signal {
261+ name: "touchReleaseCountChanged"
262+ Parameter { name: "count"; type: "int" }
263+ }
254264 Signal { name: "inputMethodRequested" }
255265 Signal { name: "inputMethodDismissed" }
256266 Signal { name: "deregister" }
@@ -296,6 +306,10 @@ Module {
296306 name: "addChildSession"
297307 Parameter { name: "session"; type: "Session"; isPointer: true }
298308 }
309+ Method {
310+ name: "removeChildSession"
311+ Parameter { name: "session"; type: "Session"; isPointer: true }
312+ }
299313 }
300314 Component {
301315 name: "SessionManager"
@@ -345,6 +359,34 @@ Module {
345359 }
346360 Method { name: "inputMethodSurface"; type: "MirSurfaceItem*" }
347361 }
362+ Component {
363+ name: "UbuntuKeyboardInfo"
364+ prototype: "QObject"
365+ exports: ["Unity.Application/UbuntuKeyboardInfo 0.1"]
366+ isCreatable: false
367+ isSingleton: true
368+ exportMetaObjectRevisions: [0]
369+ Property { name: "x"; type: "double"; isReadonly: true }
370+ Property { name: "y"; type: "double"; isReadonly: true }
371+ Property { name: "width"; type: "double"; isReadonly: true }
372+ Property { name: "height"; type: "double"; isReadonly: true }
373+ Signal {
374+ name: "xChanged"
375+ Parameter { name: "x"; type: "double" }
376+ }
377+ Signal {
378+ name: "yChanged"
379+ Parameter { name: "y"; type: "double" }
380+ }
381+ Signal {
382+ name: "widthChanged"
383+ Parameter { name: "width"; type: "double" }
384+ }
385+ Signal {
386+ name: "heightChanged"
387+ Parameter { name: "height"; type: "double" }
388+ }
389+ }
348390 Component {
349391 name: "unity::shell::application::ApplicationInfoInterface"
350392 prototype: "QObject"
tests/mocks/Unity/Indicators/Indicators.qmltypes+7−33
@@ -29,6 +29,7 @@ Module {
2929 exports: ["Unity.Indicators/FakeIndicatorsModel 0.1"]
3030 exportMetaObjectRevisions: [0]
3131 Property { name: "count"; type: "int"; isReadonly: true }
32+ Property { name: "profile"; type: "string" }
3233 Property { name: "modelData"; type: "QVariant" }
3334 Method {
3435 name: "load"
@@ -64,16 +65,17 @@ Module {
6465 exportMetaObjectRevisions: [0]
6566 Method {
6667 name: "setCachedModelData"
67− Parameter { name: "bus"; type: "QByteArray" }
6868 Parameter { name: "path"; type: "QByteArray" }
69− Parameter { name: "actions"; type: "QVariantMap" }
7069 Parameter { name: "data"; type: "QVariant" }
7170 }
7271 Method {
7372 name: "setCachedModelData"
74− Parameter { name: "bus"; type: "QByteArray" }
7573 Parameter { name: "path"; type: "QByteArray" }
76− Parameter { name: "actions"; type: "QVariantMap" }
74+ }
75+ Method {
76+ name: "getCachedModelData"
77+ type: "QVariant"
78+ Parameter { name: "path"; type: "QByteArray" }
7779 }
7880 }
7981 Component {
@@ -105,8 +107,7 @@ Module {
105107 values: {
106108 "Identifier": 0,
107109 "Position": 1,
108− "IndicatorProperties": 2,
109− "IsVisible": 3
110+ "IndicatorProperties": 2
110111 }
111112 }
112113 }
@@ -182,12 +183,6 @@ Module {
182183 }
183184 }
184185 }
185− Component {
186− name: "QAbstractProxyModel"
187− prototype: "QAbstractItemModel"
188− Property { name: "sourceModel"; type: "QAbstractItemModel"; isPointer: true }
189− }
190− Component { name: "QIdentityProxyModel"; prototype: "QAbstractProxyModel" }
191186 Component {
192187 name: "SharedUnityMenuModel"
193188 prototype: "QObject"
@@ -234,25 +229,4 @@ Module {
234229 }
235230 Method { name: "pop"; type: "UnityMenuModel*" }
236231 }
237− Component {
238− name: "VisibleIndicatorsModel"
239− prototype: "QIdentityProxyModel"
240− exports: ["Unity.Indicators/VisibleIndicatorsModel 0.1"]
241− exportMetaObjectRevisions: [0]
242− Property { name: "model"; type: "QAbstractItemModel"; isPointer: true }
243− Property { name: "visible"; type: "QVariantMap" }
244− Signal { name: "totalCountChanged" }
245− Method {
246− name: "onBeginRowInserted"
247− Parameter { type: "QModelIndex" }
248− Parameter { name: "start"; type: "int" }
249− Parameter { name: "end"; type: "int" }
250− }
251− Method {
252− name: "onRowInserted"
253− Parameter { type: "QModelIndex" }
254− Parameter { name: "start"; type: "int" }
255− Parameter { name: "end"; type: "int" }
256− }
257− }
258232}
tests/mocks/Unity/Launcher/Launcher.qmltypes+3
@@ -63,6 +63,7 @@ Module {
6363 type: "string"
6464 Parameter { name: "appId"; type: "string" }
6565 }
66+ Method { name: "emitHint" }
6667 }
6768 Component {
6869 name: "MockQuickListModel"
@@ -141,6 +142,8 @@ Module {
141142 type: "unity::shell::application::ApplicationManagerInterface"
142143 isPointer: true
143144 }
145+ Property { name: "onlyPinned"; type: "bool" }
146+ Signal { name: "hint" }
144147 Method {
145148 name: "move"
146149 Parameter { name: "oldIndex"; type: "int" }
tests/mocks/Unity/Notifications/MockActionModel.cpp+1−1
@@ -30,7 +30,7 @@ ActionModel::ActionModel(QObject *parent) : QStringListModel(parent), p(new Acti
3030ActionModel::~ActionModel() {
3131}
3232
33−int ActionModel::rowCount(const QModelIndex &index) const {
33+int ActionModel::rowCount(const QModelIndex &) const {
3434 return p->labels.size();
3535}
3636
tests/mocks/Unity/Notifications/MockNotification.cpp+4
@@ -140,6 +140,10 @@ ActionModel* MockNotification::getActions() const {
140140 return p->actionsModel;
141141}
142142
143+QStringList MockNotification::rawActions() const {
144+ return p->actions;
145+}
146+
143147void MockNotification::setActions(const QStringList &actions) {
144148 if(p->actions != actions) {
145149 p->actions = actions;
tests/mocks/Unity/Notifications/MockNotification.h+2−1
@@ -38,7 +38,7 @@ class MockNotification : public QObject {
3838 Q_PROPERTY(QString icon READ getIcon WRITE setIcon NOTIFY iconChanged)
3939 Q_PROPERTY(QString secondaryIcon READ getSecondaryIcon WRITE setSecondaryIcon NOTIFY secondaryIconChanged)
4040 Q_PROPERTY(Type type READ getType WRITE setType NOTIFY typeChanged)
41− Q_PROPERTY(QStringList rawActions WRITE setActions)
41+ Q_PROPERTY(QStringList rawActions READ rawActions WRITE setActions)
4242 Q_PROPERTY(ActionModel* actions READ getActions NOTIFY actionsChanged)
4343 Q_PROPERTY(QVariantMap hints READ getHints WRITE setHints NOTIFY hintsChanged)
4444
@@ -83,6 +83,7 @@ public:
8383 Type getType() const;
8484 void setType(Type type);
8585 ActionModel* getActions() const;
86+ QStringList rawActions() const;
8687 void setActions(const QStringList &actions);
8788 QVariantMap getHints() const;
8889 void setHints(const QVariantMap& hints);
tests/mocks/Unity/Notifications/MockNotificationModel.cpp+1−1
@@ -37,7 +37,7 @@ MockNotificationModel::MockNotificationModel(QObject *parent) : QAbstractListMod
3737MockNotificationModel::~MockNotificationModel() {
3838}
3939
40−int MockNotificationModel::rowCount(const QModelIndex &parent) const {
40+int MockNotificationModel::rowCount(const QModelIndex &) const {
4141 return m_queue.size();
4242}
4343
tests/mocks/Unity/Notifications/Notifications.qmltypes+139
@@ -7,4 +7,143 @@ import QtQuick.tooling 1.1
77// 'qmlplugindump -notrelocatable Unity.Notifications 1.0 tests/mocks'
88
99Module {
10+ Component {
11+ name: "ActionModel"
12+ prototype: "QStringListModel"
13+ exports: ["Unity.Notifications/ActionModel 1.0"]
14+ exportMetaObjectRevisions: [0]
15+ Enum {
16+ name: "ActionsRoles"
17+ values: {
18+ "RoleActionLabel": 257,
19+ "RoleActionId": 258
20+ }
21+ }
22+ Property { name: "count"; type: "int"; isReadonly: true }
23+ Method {
24+ name: "data"
25+ type: "QVariant"
26+ Parameter { name: "row"; type: "int" }
27+ Parameter { name: "role"; type: "int" }
28+ }
29+ Method {
30+ name: "append"
31+ Parameter { name: "id"; type: "string" }
32+ Parameter { name: "label"; type: "string" }
33+ }
34+ }
35+ Component {
36+ name: "MockNotification"
37+ prototype: "QObject"
38+ exports: ["Unity.Notifications/Notification 1.0"]
39+ exportMetaObjectRevisions: [0]
40+ Enum {
41+ name: "Type"
42+ values: {
43+ "PlaceHolder": 0,
44+ "Confirmation": 1,
45+ "Ephemeral": 2,
46+ "Interactive": 3,
47+ "SnapDecision": 4
48+ }
49+ }
50+ Property { name: "summary"; type: "string" }
51+ Property { name: "body"; type: "string" }
52+ Property { name: "nid"; type: "int" }
53+ Property { name: "value"; type: "int" }
54+ Property { name: "icon"; type: "string" }
55+ Property { name: "secondaryIcon"; type: "string" }
56+ Property { name: "type"; type: "Type" }
57+ Property { name: "rawActions"; type: "QStringList" }
58+ Property { name: "actions"; type: "ActionModel"; isReadonly: true; isPointer: true }
59+ Property { name: "hints"; type: "QVariantMap" }
60+ Signal {
61+ name: "summaryChanged"
62+ Parameter { name: "summary"; type: "string" }
63+ }
64+ Signal {
65+ name: "bodyChanged"
66+ Parameter { name: "body"; type: "string" }
67+ }
68+ Signal {
69+ name: "idChanged"
70+ Parameter { name: "id"; type: "int" }
71+ }
72+ Signal {
73+ name: "valueChanged"
74+ Parameter { name: "value"; type: "int" }
75+ }
76+ Signal {
77+ name: "iconChanged"
78+ Parameter { name: "icon"; type: "string" }
79+ }
80+ Signal {
81+ name: "secondaryIconChanged"
82+ Parameter { name: "secondaryIcon"; type: "string" }
83+ }
84+ Signal {
85+ name: "typeChanged"
86+ Parameter { name: "type"; type: "Type" }
87+ }
88+ Signal {
89+ name: "actionsChanged"
90+ Parameter { name: "actions"; type: "QStringList" }
91+ }
92+ Signal {
93+ name: "hintsChanged"
94+ Parameter { name: "hints"; type: "QVariantMap" }
95+ }
96+ Signal {
97+ name: "dataChanged"
98+ Parameter { name: "nid"; type: "int" }
99+ }
100+ Signal {
101+ name: "completed"
102+ Parameter { name: "nid"; type: "int" }
103+ }
104+ Signal {
105+ name: "actionInvoked"
106+ Parameter { name: "action"; type: "string" }
107+ }
108+ Method {
109+ name: "invokeAction"
110+ Parameter { name: "action"; type: "string" }
111+ }
112+ Method { name: "close" }
113+ }
114+ Component {
115+ name: "MockNotificationModel"
116+ prototype: "QAbstractListModel"
117+ exports: ["Unity.Notifications/NotificationModel 1.0"]
118+ exportMetaObjectRevisions: [0]
119+ Property { name: "count"; type: "int"; isReadonly: true }
120+ Signal {
121+ name: "actionInvoked"
122+ Parameter { name: "action"; type: "string" }
123+ }
124+ Signal {
125+ name: "queueSizeChanged"
126+ Parameter { name: "newSize"; type: "int" }
127+ }
128+ Method {
129+ name: "onCompleted"
130+ Parameter { name: "id"; type: "int" }
131+ }
132+ Method {
133+ name: "append"
134+ Parameter { name: "n"; type: "MockNotification"; isPointer: true }
135+ }
136+ Method {
137+ name: "getRaw"
138+ type: "MockNotification*"
139+ Parameter { name: "notificationId"; type: "int" }
140+ }
141+ Method { name: "queued"; type: "int" }
142+ Method {
143+ name: "remove"
144+ Parameter { name: "id"; type: "int" }
145+ }
146+ Method { name: "removeSecond" }
147+ }
148+ Component { name: "QStringListModel"; prototype: "QAbstractListModel" }
10149}
tests/mocks/Unity/Unity.qmltypes+21
@@ -169,7 +169,18 @@ Module {
169169 Parameter { name: "scope"; type: "Scope"; isPointer: true }
170170 }
171171 Method { name: "clear" }
172+ Method { name: "clearFavorites" }
172173 Method { name: "load" }
174+ Method {
175+ name: "setFavorite"
176+ Parameter { name: "scopeId"; type: "string" }
177+ Parameter { name: "favorite"; type: "bool" }
178+ }
179+ Method {
180+ name: "moveFavoriteTo"
181+ Parameter { name: "scopeId"; type: "string" }
182+ Parameter { name: "index"; type: "int" }
183+ }
173184 Method {
174185 name: "getScopeFromAll"
175186 type: "Scope*"
@@ -443,6 +454,16 @@ Module {
443454 type: "unity::shell::scopes::ScopeInterface*"
444455 Parameter { name: "scopeId"; type: "string" }
445456 }
457+ Method {
458+ name: "setFavorite"
459+ Parameter { name: "scopeId"; type: "string" }
460+ Parameter { name: "favorite"; type: "bool" }
461+ }
462+ Method {
463+ name: "moveFavoriteTo"
464+ Parameter { name: "scopeId"; type: "string" }
465+ Parameter { name: "index"; type: "int" }
466+ }
446467 }
447468 Component {
448469 name: "unity::shell::scopes::SettingsModelInterface"
tests/mocks/Wizard/Wizard.qmltypes+4−3
@@ -15,6 +15,10 @@ Module {
1515 isSingleton: true
1616 exportMetaObjectRevisions: [0]
1717 Property { name: "wizardEnabled"; type: "bool" }
18+ Signal {
19+ name: "updateSessionLanguageCalled"
20+ Parameter { name: "locale"; type: "string" }
21+ }
1822 Method {
1923 name: "updateSessionLanguage"
2024 Parameter { name: "locale"; type: "string" }
@@ -24,8 +28,6 @@ Module {
2428 name: "PageList"
2529 prototype: "QObject"
2630 exports: ["Wizard/PageList 0.1"]
27− isCreatable: false
28− isSingleton: true
2931 exportMetaObjectRevisions: [0]
3032 Property { name: "index"; type: "int"; isReadonly: true }
3133 Property { name: "numPages"; type: "int"; isReadonly: true }
@@ -33,4 +35,3 @@ Module {
3335 Method { name: "next"; type: "string" }
3436 }
3537}
36−
tests/plugins/Dash/cardcreator/2.res+1
@@ -109,6 +109,7 @@ CroppedImageMinimumSourceSize {
109109 }
110110 anchors.topMargin: units.dp(2);
111111 elide: Text.ElideRight;
112+ maximumLineCount: 1;
112113 fontSize: "x-small";
113114 font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
114115 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");
tests/plugins/Dash/cardcreator/3.res+1
@@ -89,6 +89,7 @@ Label {
8989 }
9090 anchors.topMargin: units.dp(2);
9191 elide: Text.ElideRight;
92+ maximumLineCount: 1;
9293 fontSize: "x-small";
9394 font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
9495 color: root.scopeStyle ? root.scopeStyle.foreground : Theme.palette.normal.baseText;
tests/plugins/Dash/cardcreator/4.res+1
@@ -89,6 +89,7 @@ Label {
8989 }
9090 anchors.topMargin: units.dp(2);
9191 elide: Text.ElideRight;
92+ maximumLineCount: 1;
9293 fontSize: "x-small";
9394 font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
9495 color: root.scopeStyle ? root.scopeStyle.foreground : Theme.palette.normal.baseText;
tests/plugins/Dash/cardcreator/5.res+1
@@ -134,6 +134,7 @@ Label {
134134 }
135135 anchors.topMargin: units.dp(2);
136136 elide: Text.ElideRight;
137+ maximumLineCount: 1;
137138 fontSize: "x-small";
138139 font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
139140 color: root.scopeStyle && overlayLoader.item ? root.scopeStyle.getTextColor(overlayLoader.item.luminance) : (overlayLoader.item && overlayLoader.item.luminance > 0.7 ? Theme.palette.normal.baseText : "white");
tests/plugins/Dash/cardcreator/6.res+2
@@ -88,6 +88,7 @@ Item {
8888 }
8989 anchors.topMargin: units.dp(2);
9090 elide: Text.ElideRight;
91+ maximumLineCount: 1;
9192 fontSize: "x-small";
9293 font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
9394 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");
@@ -106,6 +107,7 @@ Item {
106107 source: cardData && cardData["emblem"] || "";
107108 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");
108109 height: source != "" ? titleLabel.font.pixelSize : 0;
110+ width: implicitWidth > 0 && implicitHeight > 0 ? (implicitWidth / implicitHeight * height) : implicitWidth;
109111 }
110112
111113 ]
tests/plugins/Dash/cardcreator/7.res+1
@@ -110,6 +110,7 @@ CroppedImageMinimumSourceSize {
110110 }
111111 anchors.topMargin: units.dp(2);
112112 elide: Text.ElideRight;
113+ maximumLineCount: 1;
113114 fontSize: "x-small";
114115 font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
115116 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");
tests/plugins/LightDM/CMakeLists.txt+19−4
@@ -4,17 +4,32 @@ add_executable(GreeterDBusTestExec
44 )
55qt5_use_modules(GreeterDBusTestExec Core DBus Quick Test)
66
7+add_executable(GreeterUsersModelTestExec
8+ usersmodel.cpp
9+ ${CMAKE_SOURCE_DIR}/plugins/LightDM/UsersModel.cpp
10+ ${CMAKE_SOURCE_DIR}/plugins/Utils/unitysortfilterproxymodelqml.cpp
11+ )
12+qt5_use_modules(GreeterUsersModelTestExec Core Test)
13+
714include_directories(
815 ${CMAKE_CURRENT_BINARY_DIR}
916 ${CMAKE_SOURCE_DIR}/plugins/LightDM
17+ ${CMAKE_SOURCE_DIR}/plugins/Utils
1018 ${CMAKE_SOURCE_DIR}/tests/mocks/LightDM
11− ${LIBLIGHTDM_INCLUDE_DIRS}
1219 )
1320
14−add_dependencies(GreeterDBusTestExec MockLightDM)
15−target_link_libraries(GreeterDBusTestExec -L${CMAKE_BINARY_DIR}/tests/mocks/LightDM/liblightdm
16− -llightdm-qt5-2)
21+target_link_libraries(GreeterDBusTestExec
22+ -L${CMAKE_BINARY_DIR}/tests/mocks/LightDM/liblightdm
23+ -llightdm-qt5-2
24+ )
25+
26+target_link_libraries(GreeterUsersModelTestExec
27+ -L${CMAKE_BINARY_DIR}/tests/mocks/LightDM/liblightdm
28+ -llightdm-qt5-2
29+ )
1730
1831add_definitions(-DCURRENT_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}")
1932
2033add_binary_qml_test(GreeterDBus "${CMAKE_BINARY_DIR}/tests/mocks/LightDM/liblightdm" MockLightDM "QML2_IMPORT_PATH=${CMAKE_BINARY_DIR}/tests/mocks")
34+
35+add_binary_qml_test(GreeterUsersModel "${CMAKE_BINARY_DIR}/tests/mocks/LightDM/liblightdm" MockLightDM "LIBLIGHTDM_MOCK_MODE=full")
tests/plugins/LightDM/usersmodel.cppnew file+84
@@ -0,0 +1,84 @@
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+#include "UsersModel.h"
18+
19+#include <QLightDM/UsersModel>
20+#include <QtTest>
21+
22+class GreeterUsersModelTest : public QObject
23+{
24+ Q_OBJECT
25+
26+private:
27+ static int findName(QAbstractItemModel *model, const QString &name)
28+ {
29+ for (int i = 0; i < model->rowCount(QModelIndex()); i++) {
30+ if (model->data(model->index(i, 0), QLightDM::UsersModel::NameRole).toString() == name) {
31+ return i;
32+ }
33+ }
34+ return -1;
35+ }
36+
37+ static QString getStringValue(QAbstractItemModel *model, const QString &name, int role)
38+ {
39+ int i = findName(model, name);
40+ return model->data(model->index(i, 0), role).toString();
41+ }
42+
43+private Q_SLOTS:
44+
45+ void init()
46+ {
47+ model = new UsersModel();
48+ QVERIFY(model);
49+ sourceModel = new QLightDM::UsersModel();
50+ QVERIFY(sourceModel);
51+ }
52+
53+ void cleanup()
54+ {
55+ delete model;
56+ delete sourceModel;
57+ }
58+
59+ void testMangleColor()
60+ {
61+ QString background = getStringValue(sourceModel, "color-background", QLightDM::UsersModel::BackgroundPathRole);
62+ QVERIFY(background == "#dd4814");
63+
64+ background = getStringValue(model, "color-background", QLightDM::UsersModel::BackgroundPathRole);
65+ QVERIFY(background == "data:image/svg+xml,<svg><rect width='100%' height='100%' fill='#dd4814'/></svg>");
66+ }
67+
68+ void testMangleEmptyName()
69+ {
70+ QString name = getStringValue(sourceModel, "empty-name", QLightDM::UsersModel::RealNameRole);
71+ QVERIFY(name == "");
72+
73+ name = getStringValue(model, "empty-name", QLightDM::UsersModel::RealNameRole);
74+ QVERIFY(name == "empty-name");
75+ }
76+
77+private:
78+ UsersModel *model;
79+ QLightDM::UsersModel *sourceModel;
80+};
81+
82+QTEST_MAIN(GreeterUsersModelTest)
83+
84+#include "usersmodel.moc"
tests/plugins/Ubuntu/Gestures/tst_DirectionalDragArea.cpp+9
@@ -20,6 +20,9 @@
2020#include <QtQml/QQmlEngine>
2121#include <QPointer>
2222#include <private/qquickmousearea_p.h>
23+#if (QT_VERSION >= QT_VERSION_CHECK(5, 4, 0))
24+ #include <private/qquickwindow_p.h>
25+#endif
2326
2427
2528#include <DirectionalDragArea.h>
@@ -814,10 +817,16 @@ void tst_DirectionalDragArea::sceneDistance()
814817
815818 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
816819
820+#if (QT_VERSION >= QT_VERSION_CHECK(5, 4, 0))
821+ QQuickWindowPrivate *wp = QQuickWindowPrivate::get(m_view);
822+#endif
817823 for (int i = 0; i < totalMovementSteps; ++i) {
818824 touchPoint += touchMovement;
819825 passTime(movementTimeStepMs);
820826 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
827+#if (QT_VERSION >= QT_VERSION_CHECK(5, 4, 0))
828+ wp->flushDelayedTouchEvent();
829+#endif
821830 }
822831
823832 qreal actualDragDistance = ((qreal)totalMovementSteps) * movementStepDistance;
tests/plugins/Ubuntu/Gestures/tst_TouchGate.cpp+7
@@ -19,6 +19,9 @@
1919#include <QQmlEngine>
2020#include <QQuickView>
2121#include <QSharedPointer>
22+#if (QT_VERSION >= QT_VERSION_CHECK(5, 4, 0))
23+ #include <private/qquickwindow_p.h>
24+#endif
2225
2326// C++ std lib
2427#include <functional>
@@ -192,6 +195,10 @@ void tst_TouchGate::holdsEventsUntilGainsOwnership()
192195
193196 if (!ownershipAfterTouchEnd) {
194197 touchRegistry->removeCandidateOwnerForTouch(0, candidateItem);
198+#if (QT_VERSION >= QT_VERSION_CHECK(5, 4, 0))
199+ QQuickWindowPrivate *wp = QQuickWindowPrivate::get(testItem->window());
200+ wp->flushDelayedTouchEvent();
201+#endif
195202 // TouchGate should now open its flood gates and let testItem get all
196203 // events from touch 0 produced so far
197204 QCOMPARE(testItem->touchEventsReceived.count(), 2);
tests/qmltests/CMakeLists.txt+4−3
@@ -60,10 +60,11 @@ add_qml_test(Dash/ScopeSettings ScopeSettingList)
6060add_qml_test(Dash/ScopeSettings ScopeSettingNumber)
6161add_qml_test(Dash/ScopeSettings ScopeSettingString)
6262add_qml_test(Dash/ScopeSettings ScopeSettingsWidgetFactory)
63−add_qml_test(Greeter MultiGreeter)
64−add_qml_test(Greeter SingleGreeter)
65−add_qml_test(Greeter Infographics)
6663add_qml_test(Greeter Clock)
64+add_qml_test(Greeter Greeter)
65+add_qml_test(Greeter Infographics)
66+add_qml_test(Greeter NarrowView)
67+add_qml_test(Greeter WideView)
6768add_qml_test(Launcher Launcher)
6869add_qml_test(Notifications Notifications)
6970add_qml_test(Notifications VisualSnapDecisionsQueue)
tests/qmltests/Dash/Previews/tst_PreviewRatingInput.qml+1−1
@@ -196,7 +196,7 @@ Rectangle {
196196 compare(args[1], "rated");
197197 compare(args[2]["rating"], data.triggeredData["rating"]);
198198 compare(args[2]["review"], data.triggeredData["review"]);
199− verify(args[2]["author"]); // Just verifying it exists now
199+ verify(args[2]["author"] !== undefined); // Just verifying it exists now
200200 }
201201 }
202202}
tests/qmltests/Dash/tst_Dash.qml+22
@@ -429,6 +429,28 @@ Item {
429429 tryCompare(dashContentList, "currentIndex", 1)
430430 }
431431
432+ function test_preview_no_show_manage_dash_hint() {
433+ var dashContentList = findChild(dash, "dashContentList");
434+ compare(dashContentList.currentIndex, 0);
435+ compare(dashContentList.currentItem.scopeId, "MockScope1");
436+
437+ tryCompareFunction(function() {
438+ var cardGrid = findChild(dashContentList, "dashCategory0");
439+ if (cardGrid != null) {
440+ var tile = findChild(cardGrid, "delegate0");
441+ return tile != null;
442+ }
443+ return false;
444+ },
445+ true);
446+ var tile = findChild(findChild(dashContentList, "dashCategory0"), "delegate0");
447+ waitForRendering(tile);
448+ mouseClick(tile);
449+
450+ var overviewHint = findChild(dash, "overviewHint");
451+ tryCompare(overviewHint, "opacity", 0);
452+ }
453+
432454 function test_close_temp_scope_preview_opening_scope() {
433455 // Show the manage dash
434456 touchFlick(dash, dash.width / 2, dash.height - 1, dash.width / 2, units.gu(2));
tests/qmltests/Greeter/TestView.qmlnew file+99
@@ -0,0 +1,99 @@
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.3
18+import Ubuntu.Components 1.1
19+import "../Components"
20+
21+Item {
22+ objectName: "testView"
23+
24+ property real dragHandleLeftMargin
25+ property real launcherOffset
26+ property int currentIndex
27+ property int delayMinutes
28+ property real backgroundTopMargin
29+ property url background
30+ property bool locked
31+ property bool alphanumeric
32+ property var userModel
33+ property var infographicModel
34+ readonly property bool fullyShown: _fullyShown
35+ readonly property bool required: _required
36+
37+ property bool _fullyShown: true
38+ property bool _required: true
39+
40+ signal selected(int index)
41+ signal responded(string response)
42+ signal tease()
43+ signal emergencyCall()
44+
45+ signal _showMessageCalled(string html)
46+ signal _showPromptCalled(string text, bool isSecret, bool isDefaultPrompt)
47+ signal _showLastChanceCalled()
48+ signal _hideCalled()
49+ signal _notifyAuthenticationSucceededCalled()
50+ signal _notifyAuthenticationFailedCalled()
51+ signal _resetCalled()
52+ signal _tryToUnlockCalled(bool toTheRight)
53+
54+ function showMessage(html) {
55+ _showMessageCalled(html);
56+ }
57+
58+ function showPrompt(text, isSecret, isDefaultPrompt) {
59+ _showPromptCalled(text, isSecret, isDefaultPrompt);
60+ }
61+
62+ function showLastChance() {
63+ _showLastChanceCalled();
64+ }
65+
66+ function hide() {
67+ _hideCalled();
68+ _required = false;
69+ _fullyShown = false;
70+ }
71+
72+ function notifyAuthenticationSucceeded() {
73+ _notifyAuthenticationSucceededCalled();
74+ }
75+
76+ function notifyAuthenticationFailed() {
77+ _notifyAuthenticationFailedCalled();
78+ }
79+
80+ function reset() {
81+ _resetCalled();
82+ }
83+
84+ function tryToUnlock(toTheRight) {
85+ _tryToUnlockCalled(toTheRight);
86+ return true;
87+ }
88+
89+ Rectangle {
90+ anchors.fill: parent
91+ color: "black"
92+
93+ Label {
94+ text: "Fake view, nothing to see here"
95+ color: "white"
96+ anchors.centerIn: parent
97+ }
98+ }
99+}
tests/qmltests/Greeter/tst_Greeter.qmlnew file+472
@@ -0,0 +1,472 @@
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 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.0
18+import QtTest 1.0
19+import ".."
20+import "../../../qml/Greeter"
21+import Ubuntu.Components 0.1
22+import AccountsService 0.1
23+import LightDM 0.1 as LightDM
24+import Unity.Test 0.1 as UT
25+
26+Item {
27+ width: units.gu(120)
28+ height: units.gu(80)
29+
30+ property url defaultBackground: Qt.resolvedUrl("../../../qml/graphics/tablet_background.jpg")
31+
32+ Component.onCompleted: {
33+ // set the mock mode before loading
34+ LightDM.Greeter.mockMode = "full";
35+ LightDM.Users.mockMode = "full";
36+ loader.active = true;
37+ }
38+
39+ Loader {
40+ id: loader
41+
42+ active: false
43+ anchors.fill: parent
44+
45+ property bool itemDestroyed: false
46+ sourceComponent: Component {
47+ Greeter {
48+ width: loader.width
49+ height: loader.height
50+ background: defaultBackground
51+ viewSource: Qt.resolvedUrl("TestView.qml")
52+
53+ Component.onDestruction: {
54+ loader.itemDestroyed = true;
55+ }
56+ }
57+ }
58+ }
59+
60+ SignalSpy {
61+ id: teaseSpy
62+ target: loader.item
63+ signalName: "tease"
64+ }
65+
66+ SignalSpy {
67+ id: sessionStartedSpy
68+ target: loader.item
69+ signalName: "sessionStarted"
70+ }
71+
72+ SignalSpy {
73+ id: emergencyCallSpy
74+ target: loader.item
75+ signalName: "emergencyCall"
76+ }
77+
78+ UT.UnityTestCase {
79+ id: testCase
80+ name: "Greeter"
81+ when: windowShown
82+
83+ property Item greeter: loader.status === Loader.Ready ? loader.item : null
84+ property Item view
85+
86+ SignalSpy {
87+ id: viewShowMessageSpy
88+ target: testCase.view
89+ signalName: "_showMessageCalled"
90+ }
91+
92+ SignalSpy {
93+ id: viewShowPromptSpy
94+ target: testCase.view
95+ signalName: "_showPromptCalled"
96+ }
97+
98+ SignalSpy {
99+ id: viewShowLastChanceSpy
100+ target: testCase.view
101+ signalName: "_showLastChanceCalled"
102+ }
103+
104+ SignalSpy {
105+ id: viewHideSpy
106+ target: testCase.view
107+ signalName: "_hideCalled"
108+ }
109+
110+ SignalSpy {
111+ id: viewAuthenticationSucceededSpy
112+ target: testCase.view
113+ signalName: "_notifyAuthenticationSucceededCalled"
114+ }
115+
116+ SignalSpy {
117+ id: viewAuthenticationFailedSpy
118+ target: testCase.view
119+ signalName: "_notifyAuthenticationFailedCalled"
120+ }
121+
122+ SignalSpy {
123+ id: viewResetSpy
124+ target: testCase.view
125+ signalName: "_resetCalled"
126+ }
127+
128+ SignalSpy {
129+ id: viewTryToUnlockSpy
130+ target: testCase.view
131+ signalName: "_tryToUnlockCalled"
132+ }
133+
134+ function init() {
135+ teaseSpy.clear();
136+ sessionStartedSpy.clear();
137+ emergencyCallSpy.clear();
138+ viewShowMessageSpy.clear();
139+ viewShowPromptSpy.clear();
140+ viewShowLastChanceSpy.clear();
141+ viewHideSpy.clear();
142+ viewAuthenticationSucceededSpy.clear();
143+ viewAuthenticationFailedSpy.clear();
144+ viewResetSpy.clear();
145+ viewTryToUnlockSpy.clear();
146+ tryCompare(greeter, "waiting", false);
147+ view = findChild(greeter, "testView");
148+ verifySelected(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
149+ }
150+
151+ function cleanup() {
152+ loader.itemDestroyed = false;
153+ loader.active = false;
154+ tryCompare(loader, "status", Loader.Null);
155+ tryCompare(loader, "item", null);
156+ tryCompare(loader, "itemDestroyed", true);
157+ loader.active = true;
158+ tryCompare(loader, "status", Loader.Ready);
159+ removeTimeConstraintsFromDirectionalDragAreas(loader.item);
160+ }
161+
162+ function getIndexOf(name) {
163+ for (var i = 0; i < LightDM.Users.count; i++) {
164+ if (name === LightDM.Users.data(i, LightDM.UserRoles.NameRole)) {
165+ return i;
166+ }
167+ }
168+ fail("Didn't find name")
169+ return -1;
170+ }
171+
172+ function selectUser(name) {
173+ var i = getIndexOf(name);
174+ view.selected(i);
175+ verifySelected(name);
176+ return i;
177+ }
178+
179+ function verifySelected(name) {
180+ var i = getIndexOf(name);
181+ compare(view.currentIndex, i);
182+ compare(AccountsService.user, name);
183+ compare(LightDM.Greeter.authenticationUser, name);
184+ }
185+
186+ function verifyLoggedIn() {
187+ tryCompare(sessionStartedSpy, "count", 1);
188+ verify(viewAuthenticationSucceededSpy.count > 0);
189+ compare(LightDM.Greeter.authenticated, true);
190+ compare(greeter.shown, false);
191+ }
192+
193+ function test_unlockPass() {
194+ selectUser("has-password");
195+ tryCompare(viewShowPromptSpy, "count", 1);
196+ compare(viewShowPromptSpy.signalArguments[0][0], "Password");
197+ compare(viewShowPromptSpy.signalArguments[0][1], true);
198+ compare(viewShowPromptSpy.signalArguments[0][2], true);
199+
200+ view.responded("password");
201+ verifyLoggedIn();
202+ }
203+
204+ function test_unlockFail() {
205+ selectUser("has-password");
206+ tryCompare(viewShowPromptSpy, "count", 1);
207+ compare(viewShowPromptSpy.signalArguments[0][0], "Password");
208+ compare(viewShowPromptSpy.signalArguments[0][1], true);
209+ compare(viewShowPromptSpy.signalArguments[0][2], true);
210+
211+ view.responded("wr0ng p4ssw0rd");
212+ tryCompare(viewAuthenticationFailedSpy, "count", 1);
213+
214+ tryCompare(viewShowPromptSpy, "count", 2);
215+ compare(viewShowPromptSpy.signalArguments[0][0], "Password");
216+ compare(viewShowPromptSpy.signalArguments[0][1], true);
217+ compare(viewShowPromptSpy.signalArguments[0][2], true);
218+ }
219+
220+ function test_promptless() {
221+ selectUser("no-password");
222+ tryCompare(viewAuthenticationSucceededSpy, "count", 1);
223+ compare(sessionStartedSpy.count, 1);
224+ compare(viewShowPromptSpy.count, 0);
225+ compare(viewHideSpy.count, 0);
226+ compare(view.locked, false);
227+ }
228+
229+ function test_twoFactorPass() {
230+ selectUser("two-factor");
231+ tryCompare(viewShowPromptSpy, "count", 1);
232+ compare(viewShowPromptSpy.signalArguments[0][0], "Password");
233+ compare(viewShowPromptSpy.signalArguments[0][1], true);
234+ compare(viewShowPromptSpy.signalArguments[0][2], true);
235+
236+ view.responded("password");
237+ tryCompare(viewShowPromptSpy, "count", 2);
238+ compare(viewShowPromptSpy.signalArguments[1][0], "otp");
239+ compare(viewShowPromptSpy.signalArguments[1][1], false);
240+ compare(viewShowPromptSpy.signalArguments[1][2], false);
241+
242+ view.responded("otp");
243+ verifyLoggedIn();
244+ }
245+
246+ function test_twoFactorFailOnFirst() {
247+ selectUser("two-factor");
248+ tryCompare(viewShowPromptSpy, "count", 1);
249+ compare(viewShowPromptSpy.signalArguments[0][0], "Password");
250+ compare(viewShowPromptSpy.signalArguments[0][1], true);
251+ compare(viewShowPromptSpy.signalArguments[0][2], true);
252+
253+ view.responded("wr0ng p4ssw0rd");
254+ tryCompare(viewAuthenticationFailedSpy, "count", 1);
255+
256+ tryCompare(viewShowPromptSpy, "count", 2);
257+ compare(viewShowPromptSpy.signalArguments[0][0], "Password");
258+ compare(viewShowPromptSpy.signalArguments[0][1], true);
259+ compare(viewShowPromptSpy.signalArguments[0][2], true);
260+ }
261+
262+ function test_twoFactorFailOnSecond() {
263+ selectUser("two-factor");
264+ tryCompare(viewShowPromptSpy, "count", 1);
265+ compare(viewShowPromptSpy.signalArguments[0][0], "Password");
266+ compare(viewShowPromptSpy.signalArguments[0][1], true);
267+ compare(viewShowPromptSpy.signalArguments[0][2], true);
268+
269+ view.responded("password");
270+ tryCompare(viewShowPromptSpy, "count", 2);
271+ compare(viewShowPromptSpy.signalArguments[1][0], "otp");
272+ compare(viewShowPromptSpy.signalArguments[1][1], false);
273+ compare(viewShowPromptSpy.signalArguments[1][2], false);
274+
275+ view.responded("wr0ng p4ssw0rd");
276+ tryCompare(viewAuthenticationFailedSpy, "count", 1);
277+
278+ tryCompare(viewShowPromptSpy, "count", 3);
279+ compare(viewShowPromptSpy.signalArguments[0][0], "Password");
280+ compare(viewShowPromptSpy.signalArguments[0][1], true);
281+ compare(viewShowPromptSpy.signalArguments[0][2], true);
282+ }
283+
284+ function test_htmlInfoPrompt() {
285+ selectUser("html-info-prompt");
286+ tryCompare(viewShowPromptSpy, "count", 1);
287+ compare(viewShowMessageSpy.count, 1);
288+ compare(viewShowMessageSpy.signalArguments[0][0], "&lt;b&gt;&amp;&lt;/b&gt;");
289+ }
290+
291+ function test_multiInfoPrompt() {
292+ selectUser("multi-info-prompt");
293+ tryCompare(viewShowPromptSpy, "count", 1);
294+ compare(viewShowMessageSpy.count, 3);
295+ compare(viewShowMessageSpy.signalArguments[0][0], "Welcome to Unity Greeter");
296+ compare(viewShowMessageSpy.signalArguments[1][0], "<font color=\"#df382c\">This is an error</font>");
297+ compare(viewShowMessageSpy.signalArguments[2][0], "You should have seen three messages");
298+ }
299+
300+ function test_waiting() {
301+ // Make sure we unset 'waiting' on prompt
302+ selectUser("has-password");
303+ compare(greeter.waiting, true);
304+ tryCompare(greeter, "waiting", false);
305+
306+ // Make sure we unset 'waiting' on authentication
307+ selectUser("no-password");
308+ compare(greeter.waiting, true);
309+ tryCompare(greeter, "waiting", false);
310+ }
311+
312+ function test_locked() {
313+ selectUser("has-password");
314+ compare(view.locked, true);
315+
316+ LightDM.Greeter.active = false;
317+ compare(view.locked, false);
318+ LightDM.Greeter.active = true;
319+
320+ greeter.forcedUnlock = true;
321+ compare(view.locked, false);
322+ greeter.forcedUnlock = false;
323+
324+ selectUser("no-password");
325+ tryCompare(view, "locked", false);
326+ selectUser("has-password");
327+ }
328+
329+ function test_fullyShown() {
330+ compare(greeter.fullyShown, true);
331+ view.hide();
332+ compare(greeter.fullyShown, false);
333+ }
334+
335+ function test_alphanumeric() {
336+ selectUser("has-password");
337+ compare(view.alphanumeric, true);
338+ selectUser("has-pin");
339+ compare(view.alphanumeric, false);
340+ }
341+
342+ function test_background() {
343+ greeter.background = "testing";
344+ compare(view.background, Qt.resolvedUrl("testing"));
345+ }
346+
347+ function test_notifyAboutToFocusApp() {
348+ greeter.notifyAboutToFocusApp("fake-app");
349+ compare(viewTryToUnlockSpy.count, 1);
350+ compare(viewTryToUnlockSpy.signalArguments[0][0], false);
351+ }
352+
353+ function test_notifyShowingDashFromDrag() {
354+ compare(greeter.notifyShowingDashFromDrag("fake-app"), true);
355+ compare(viewTryToUnlockSpy.count, 1);
356+ compare(viewTryToUnlockSpy.signalArguments[0][0], true);
357+ }
358+
359+ function test_dragHandleLeftMargin() {
360+ compare(view.dragHandleLeftMargin, 0);
361+ greeter.dragHandleLeftMargin = 5;
362+ compare(view.dragHandleLeftMargin, 5);
363+ }
364+
365+ function test_launcherOffset() {
366+ compare(view.launcherOffset, 0);
367+ greeter.launcherOffset = 5;
368+ tryCompare(view, "launcherOffset", 5);
369+ }
370+
371+ function test_laucherOffsetAnimation() {
372+ // Our logic for smoothing launcherOffset when it suddenly goes to
373+ // zero is a bit complicated. Let's just make sure it works here.
374+
375+ launcherOffsetWatcher.target = view;
376+
377+ // should follow immediately
378+ launcherOffsetWatcher.values = [];
379+ greeter.launcherOffset = 100;
380+ compare(view.launcherOffset, 100);
381+ compare(launcherOffsetWatcher.values.length, 1);
382+
383+ // should interpolate values until it reaches 0
384+ launcherOffsetWatcher.values = [];
385+ greeter.launcherOffset = 0;
386+ tryCompare(view, "launcherOffset", 0);
387+ verify(launcherOffsetWatcher.values.length > 1);
388+ for (var i = 0; i < launcherOffsetWatcher.values.length - 1; ++i) {
389+ verify(launcherOffsetWatcher.values[i] > 0.0);
390+ verify(launcherOffsetWatcher.values[i] < 100.0);
391+ }
392+ }
393+ Connections {
394+ id: launcherOffsetWatcher
395+ property var values: []
396+ onLauncherOffsetChanged: {
397+ values.push(target.launcherOffset);
398+ }
399+ }
400+
401+ function test_backgroundTopMargin() {
402+ compare(view.backgroundTopMargin, 0);
403+ greeter.y = 5;
404+ compare(view.backgroundTopMargin, -5);
405+ }
406+
407+ function test_differentPrompt() {
408+ selectUser("different-prompt");
409+ tryCompare(viewShowPromptSpy, "count", 1);
410+ compare(viewShowPromptSpy.signalArguments[0][0], "Secret word");
411+ compare(viewShowPromptSpy.signalArguments[0][1], true);
412+ compare(viewShowPromptSpy.signalArguments[0][2], false);
413+ }
414+
415+ function test_authError() {
416+ selectUser("auth-error");
417+ tryCompare(viewAuthenticationFailedSpy, "count", 1);
418+ compare(viewShowPromptSpy.count, 0);
419+ compare(view.locked, true);
420+ }
421+
422+ function test_statsWelcomeScreen() {
423+ // Test logic in greeter that turns statsWelcomeScreen setting into infographic changes
424+ selectUser("has-password");
425+ compare(LightDM.Infographic.username, "has-password");
426+ AccountsService.statsWelcomeScreen = false;
427+ compare(LightDM.Infographic.username, "");
428+ AccountsService.statsWelcomeScreen = true;
429+ compare(LightDM.Infographic.username, "has-password");
430+ }
431+
432+ function test_dbusRequestAuthenticationUser() {
433+ selectUser("no-password");
434+ LightDM.Greeter.requestAuthenticationUser("has-password");
435+ verifySelected("has-password");
436+ }
437+
438+ function test_dbusHideGreeter() {
439+ compare(view.required, true);
440+ LightDM.Greeter.hideGreeter();
441+ compare(view.required, false);
442+ compare(greeter.required, false);
443+ }
444+
445+ function test_dbusShowGreeterFromHiddenState() {
446+ greeter.hide();
447+ compare(greeter.required, false);
448+
449+ LightDM.Greeter.showGreeter();
450+ compare(greeter.required, true);
451+ compare(greeter.fullyShown, true);
452+ view = findChild(greeter, "testView");
453+ compare(view.required, true);
454+
455+ // Can't test some of the stuff called on 'view' here because
456+ // the view was torn down and created again. So the spies missed
457+ // the good stuff while it down. See next test for more.
458+ }
459+
460+ function test_dbusShowGreeterFromShownState() {
461+ selectUser("has-password");
462+ compare(viewResetSpy.count, 1);
463+ tryCompare(viewShowPromptSpy, "count", 1);
464+
465+ viewResetSpy.clear();
466+ viewShowPromptSpy.clear();
467+
468+ LightDM.Greeter.showGreeter();
469+ compare(viewResetSpy.count, 1);
470+ }
471+ }
472+}
tests/qmltests/Greeter/tst_MultiGreeter.qmldeleted−475
@@ -1,475 +0,0 @@
1−/*
2− * Copyright 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.0
18−import QtQuick.Layouts 1.1
19−import QtTest 1.0
20−import ".."
21−import "../../../qml/Greeter"
22−import Ubuntu.Components 0.1
23−import LightDM 0.1 as LightDM
24−import Unity.Test 0.1 as UT
25−
26−Item {
27− id: root
28− width: units.gu(140)
29− height: units.gu(80)
30−
31− QtObject {
32− id: fakeInputMethod
33− property bool visible: fakeKeyboard.visible
34− property var keyboardRectangle: QtObject {
35− property real x: fakeKeyboard.x
36− property real y: fakeKeyboard.y
37− property real width: fakeKeyboard.width
38− property real height: fakeKeyboard.height
39− }
40− }
41−
42− Binding {
43− target: LightDM.Greeter
44− property: "mockMode"
45− value: "full"
46− }
47− Binding {
48− target: LightDM.Users
49− property: "mockMode"
50− value: "full"
51− }
52−
53− Greeter {
54− id: greeter
55− width: units.gu(120)
56− height: root.height
57− inputMethod: fakeInputMethod
58− locked: !LightDM.Greeter.authenticated
59− }
60−
61− Component {
62− id: greeterComponent
63− Greeter {
64− SignalSpy {
65− objectName: "selectedSpy"
66− target: parent
67− signalName: "selected"
68− }
69− }
70− }
71−
72− Rectangle {
73− id: fakeKeyboard
74− color: "green"
75− opacity: 0.7
76− anchors.bottom: root.bottom
77− width: greeter.width
78− height: greeter.height * 0.6
79− visible: keyboardVisibleCheckbox.checked
80− Text {
81− text: "Keyboard Rectangle"
82− color: "yellow"
83− font.bold: true
84− fontSizeMode: Text.Fit
85− minimumPixelSize: 10; font.pixelSize: 200
86− verticalAlignment: Text.AlignVCenter
87− x: (parent.width - width) / 2
88− y: (parent.height - height) / 2
89− width: parent.width
90− height: parent.height
91− }
92− }
93−
94− Item {
95− anchors {
96− top: root.top
97− bottom: root.bottom
98− left: greeter.right
99− right: root.right
100− }
101− RowLayout {
102− Layout.fillWidth: true
103− CheckBox {
104− id: keyboardVisibleCheckbox
105− }
106− Label { text: "Keyboard Visible"; anchors.verticalCenter: parent.verticalCenter }
107− }
108− }
109−
110− SignalSpy {
111− id: unlockSpy
112− target: greeter
113− signalName: "unlocked"
114− }
115−
116− SignalSpy {
117− id: selectionSpy
118− target: greeter
119− signalName: "selected"
120− }
121−
122− SignalSpy {
123− id: tappedSpy
124− target: greeter
125− signalName: "tapped"
126− }
127−
128− UT.UnityTestCase {
129− name: "MultiGreeter"
130− when: windowShown
131−
132− function cleanup() {
133− keyboardVisibleCheckbox.checked = false;
134− }
135−
136− function select_index(i) {
137− // We could be anywhere in list; find target index to know which direction
138− var userlist = findChild(greeter, "userList")
139− if (userlist.currentIndex == i)
140− keyClick(Qt.Key_Escape) // Reset state if we're not moving
141− while (userlist.currentIndex != i) {
142− var next = userlist.currentIndex + 1
143− if (userlist.currentIndex > i) {
144− next = userlist.currentIndex - 1
145− }
146− var account = findChild(greeter, "username"+next)
147− mouseClick(account, 1, 1)
148− tryCompare(userlist, "currentIndex", next)
149− tryCompare(userlist, "movingInternally", false)
150− }
151− }
152−
153− function select_user(name) {
154− // We could be anywhere in list; find target index to know which direction
155− for (var i = 0; i < greeter.model.count; i++) {
156− if (greeter.model.data(i, LightDM.UserRoles.NameRole) == name) {
157− break
158− }
159− }
160− if (i == greeter.model.count) {
161− fail("Didn't find name")
162− return -1
163− }
164− select_index(i)
165− return i
166− }
167−
168− function test_properties() {
169− compare(greeter.multiUser, true)
170− compare(greeter.narrowMode, false)
171− }
172−
173− function test_cycle_data() {
174− var data = new Array()
175− for (var i = 0; i < greeter.model.count; i++) {
176− data[i] = {tag: greeter.model.data(i, LightDM.UserRoles.NameRole), uid: i }
177− }
178− return data
179− }
180−
181− function test_cycle(data) {
182− selectionSpy.clear();
183− var userList = findChild(greeter, "userList")
184− var waitForSignal = data.uid != 0 && userList.currentIndex != data.uid
185− select_index(data.uid)
186− tryCompare(userList, "currentIndex", data.uid)
187− tryCompare(greeter, "locked", data.tag !== "no-password")
188− if (waitForSignal) {
189− selectionSpy.wait()
190− tryCompare(selectionSpy, "count", 1)
191− }
192− }
193−
194− function test_unlock_password() {
195− select_user("no-password") // to guarantee a selected signal
196− unlockSpy.clear()
197− select_user("has-password")
198− var passwordInput = findChild(greeter, "passwordInput")
199− tryCompare(passwordInput, "opacity", 1)
200− mouseClick(passwordInput, 1, 1)
201− compare(unlockSpy.count, 0)
202− typeString("password")
203− keyClick(Qt.Key_Enter)
204− unlockSpy.wait()
205− }
206−
207− function test_unlock_wrong_password() {
208− select_user("no-password") // to guarantee a selected signal
209− unlockSpy.clear()
210− select_user("has-password")
211− wait(0) // spin event loop to start any pending animations
212− var passwordInput = findChild(greeter, "passwordInput")
213− tryCompare(passwordInput, "opacity", 1) // wait for opacity animation to be finished
214− mouseClick(passwordInput, 1, 1)
215− compare(unlockSpy.count, 0)
216− typeString("wr0ng p4ssw0rd")
217− keyClick(Qt.Key_Enter)
218− compare(unlockSpy.count, 0)
219− }
220−
221− function test_unlock_no_password() {
222− unlockSpy.clear()
223− select_user("no-password")
224− var passwordInput = findChild(greeter, "passwordInput")
225− tryCompare(passwordInput, "opacity", 1)
226− mouseClick(passwordInput, 1, 1)
227− unlockSpy.wait()
228− compare(unlockSpy.count, 1)
229− }
230−
231− function test_empty_name() {
232− for (var i = 0; i < greeter.model.count; i++) {
233− if (greeter.model.data(i, LightDM.UserRoles.NameRole) == "empty-name") {
234− compare(greeter.model.data(i, LightDM.UserRoles.RealNameRole), greeter.model.data(i, LightDM.UserRoles.NameRole))
235− return
236− }
237− }
238− fail("Didn't find empty-name")
239− }
240−
241− function test_auth_error() {
242− select_user("auth-error")
243− var passwordInput = findChild(greeter, "passwordInput")
244− tryCompare(passwordInput, "placeholderText", "Retry")
245− }
246−
247− function test_different_prompt() {
248− select_user("different-prompt")
249− var passwordInput = findChild(greeter, "passwordInput")
250− tryCompare(passwordInput, "placeholderText", "Secret word")
251− }
252−
253− function test_no_response() {
254− unlockSpy.clear()
255− select_user("no-response")
256− var passwordInput = findChild(greeter, "passwordInput")
257− tryCompare(passwordInput, "opacity", 1)
258− mouseClick(passwordInput, 1, 1)
259− compare(unlockSpy.count, 0)
260− typeString("password")
261− keyClick(Qt.Key_Enter)
262− tryCompare(passwordInput, "enabled", false)
263− keyClick(Qt.Key_Escape)
264− tryCompare(passwordInput, "enabled", true)
265− compare(unlockSpy.count, 0)
266− }
267−
268− function test_two_factor_correct() {
269− unlockSpy.clear()
270− select_user("two-factor")
271− var passwordInput = findChild(greeter, "passwordInput")
272− tryCompare(passwordInput, "opacity", 1)
273− tryCompare(passwordInput, "echoMode", TextInput.Password)
274− tryCompare(passwordInput, "placeholderText", "Password")
275− mouseClick(passwordInput, 1, 1)
276− compare(unlockSpy.count, 0)
277− typeString("password")
278− keyClick(Qt.Key_Enter)
279− tryCompare(passwordInput, "echoMode", TextInput.Normal)
280− tryCompare(passwordInput, "placeholderText", "otp")
281− tryCompare(passwordInput, "enabled", true)
282− typeString("otp")
283− keyClick(Qt.Key_Enter)
284− unlockSpy.wait()
285− }
286−
287− function test_two_factor_wrong1() {
288− unlockSpy.clear()
289− select_user("two-factor")
290− var passwordInput = findChild(greeter, "passwordInput")
291− tryCompare(passwordInput, "opacity", 1)
292− tryCompare(passwordInput, "placeholderText", "Password")
293− mouseClick(passwordInput, 1, 1)
294− compare(unlockSpy.count, 0)
295− typeString("wr0ng p4ssw0rd")
296− keyClick(Qt.Key_Enter)
297− tryCompare(passwordInput, "placeholderText", "Password")
298− tryCompare(passwordInput, "enabled", true)
299− compare(unlockSpy.count, 0)
300− }
301−
302− function test_two_factor_wrong2() {
303− unlockSpy.clear()
304− select_user("two-factor")
305− var passwordInput = findChild(greeter, "passwordInput")
306− tryCompare(passwordInput, "opacity", 1)
307− tryCompare(passwordInput, "placeholderText", "Password")
308− mouseClick(passwordInput, 1, 1)
309− compare(unlockSpy.count, 0)
310− typeString("password")
311− keyClick(Qt.Key_Enter)
312− tryCompare(passwordInput, "placeholderText", "otp")
313− tryCompare(passwordInput, "enabled", true)
314− typeString("wr0ng p4ssw0rd")
315− keyClick(Qt.Key_Enter)
316− tryCompare(passwordInput, "placeholderText", "Password")
317− tryCompare(passwordInput, "enabled", true)
318− compare(unlockSpy.count, 0)
319− }
320−
321− function test_unicode() {
322− var index = select_user("unicode")
323− var label = findChild(greeter, "username"+index)
324− tryCompare(label, "text", "가나다라마")
325− }
326−
327− function test_long_name() {
328− var index = select_user("long-name")
329− var label = findChild(greeter, "username"+index)
330− tryCompare(label, "truncated", true)
331− }
332−
333− function test_info_prompt() {
334− select_user("info-prompt")
335− var label = findChild(greeter, "infoLabel")
336− tryCompare(label, "text", "Welcome to Unity Greeter")
337− tryCompare(label, "opacity", 1)
338− tryCompare(label, "clip", true)
339− tryCompareFunction(function() {return label.contentWidth > label.width;}, false) // c.f. wide-info-prompt
340− var passwordInput = findChild(greeter, "passwordInput")
341− mouseClick(passwordInput, 1, 1)
342− keyClick(Qt.Key_Escape)
343− }
344−
345− function test_info_prompt_escape() {
346− select_user("info-prompt")
347− var passwordInput = findChild(greeter, "passwordInput")
348− mouseClick(passwordInput, 1, 1)
349− keyClick(Qt.Key_Escape)
350− var label = findChild(greeter, "infoLabel")
351− tryCompare(label, "text", "Welcome to Unity Greeter")
352− tryCompare(label, "opacity", 1)
353− }
354−
355− function test_wide_info_prompt() {
356− select_user("wide-info-prompt")
357− var label = findChild(greeter, "infoLabel")
358− tryCompare(label, "clip", true)
359− tryCompareFunction(function() {return label.contentWidth > label.width;}, true)
360− }
361−
362− function test_html_info_prompt() {
363− select_user("html-info-prompt")
364− var label = findChild(greeter, "infoLabel")
365− tryCompare(label, "text", "&lt;b&gt;&amp;&lt;/b&gt;")
366− }
367−
368− function test_long_info_prompt() {
369− select_user("long-info-prompt")
370− var label = findChild(greeter, "infoLabel")
371− tryCompare(label, "text", "Welcome to Unity Greeter<br><br>We like to annoy you with super ridiculously long messages.<br>Like this one<br><br>This is the last line of a multiple line message.")
372− tryCompare(label, "textFormat", Text.StyledText) // for parsing above correctly
373− tryCompare(label, "clip", true)
374− tryCompareFunction(function() {return label.contentWidth > label.width;}, true)
375− }
376−
377− function test_multi_info_prompt() {
378− select_user("multi-info-prompt")
379− var label = findChild(greeter, "infoLabel")
380− tryCompare(label, "text", "Welcome to Unity Greeter<br><font color=\"#df382c\">This is an error</font><br>You should have seen three messages")
381− tryCompare(label, "textFormat", Text.StyledText) // for parsing above correctly
382− }
383−
384− function test_bg_color() {
385− var index = select_user("color-background")
386− compare(greeter.model.data(index, LightDM.UserRoles.BackgroundPathRole), "data:image/svg+xml,<svg><rect width='100%' height='100%' fill='#dd4814'/></svg>")
387− }
388−
389− function test_bg_none() {
390− var index = select_user("no-background")
391− compare(greeter.model.data(index, LightDM.UserRoles.BackgroundPathRole), "")
392− }
393−
394− function test_tappedSignal_data() {
395− return [
396− {tag: "left", posX: units.gu(2)},
397− {tag: "right", posX: greeter.width - units.gu(2)}
398− ]
399− }
400−
401− function test_tappedSignal(data) {
402− select_user("no-password");
403− tappedSpy.clear();
404− tap(greeter, data.posX, greeter.height - units.gu(1))
405− tryCompare(tappedSpy, "count", 1)
406− }
407−
408− function test_teaseLockedUnlocked_data() {
409− return [
410− {tag: "unlocked", locked: false, narrow: false},
411− {tag: "locked", locked: true, narrow: false},
412− ];
413− }
414−
415− function test_teaseLockedUnlocked(data) {
416− tappedSpy.clear()
417− greeter.locked = data.locked;
418−
419− tap(greeter, greeter.width - units.gu(5), greeter.height - units.gu(1));
420−
421− if (!data.locked || data.narrow) {
422− tappedSpy.wait()
423− tryCompare(tappedSpy, "count", 1);
424− } else {
425− // waiting 100ms to make sure nothing happens
426− wait(100);
427− compare(tappedSpy.count, 0, "Greeter teasing not disabled even though it's locked.");
428− }
429−
430− // Reset value
431− greeter.locked = false;
432− }
433−
434− function test_dbus_set_active_entry() {
435− select_user("no-password") // to guarantee a selected signal
436− selectionSpy.clear()
437− LightDM.Greeter.requestAuthenticationUser("has-password")
438−
439− selectionSpy.wait()
440− tryCompare(selectionSpy, "count", 1)
441−
442− var userlist = findChild(greeter, "userList")
443− compare(greeter.model.data(userlist.currentIndex, LightDM.UserRoles.NameRole), "has-password")
444− }
445−
446− function test_initial_selected_signal() {
447− var greeterObj = greeterComponent.createObject(this)
448− var spy = findChild(greeterObj, "selectedSpy")
449− spy.wait()
450− tryCompare(spy, "count", 1)
451− greeterObj.destroy()
452− }
453−
454− function test_login_list_not_covered_by_keyboard() {
455− var loginList = findChild(greeter, "loginLoader").item;
456− compare(loginList.height, greeter.height);
457−
458− // when the vkb shows up, loginList is moved up to remain fully uncovered
459−
460− keyboardVisibleCheckbox.checked = true;
461−
462− tryCompare(loginList, "height", greeter.height - fakeInputMethod.keyboardRectangle.height);
463− tryCompareFunction( function() {
464− var loginListRect = loginList.mapToItem(greeter, 0, 0, loginList.width, loginList.height);
465− return loginListRect.y + loginListRect.height <= fakeInputMethod.keyboardRectangle.y;
466− }, true);
467−
468− // once the vkb goes away, loginList goes back to its full height
469−
470− keyboardVisibleCheckbox.checked = false;
471−
472− tryCompare(loginList, "height", greeter.height);
473− }
474− }
475−}
tests/qmltests/Greeter/tst_NarrowView.qmlnew file+522
@@ -0,0 +1,522 @@
1+/*
2+ * Copyright 2014 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.0
18+import QtTest 1.0
19+import ".."
20+import "../../../qml/Greeter"
21+import LightDM 0.1 as LightDM
22+import Ubuntu.Components 0.1
23+import Unity.Test 0.1 as UT
24+
25+Item {
26+ id: root
27+ width: units.gu(90)
28+ height: units.gu(80)
29+
30+ Row {
31+ anchors.fill: parent
32+ Loader {
33+ id: loader
34+ width: root.width - controls.width
35+ height: parent.height
36+
37+ property bool itemDestroyed: false
38+ sourceComponent: Component {
39+ NarrowView {
40+ background: Qt.resolvedUrl("../../../qml/graphics/phone_background.jpg")
41+ userModel: LightDM.Users
42+ infographicModel: LightDM.Infographic
43+
44+ launcherOffset: parseFloat(launcherOffsetField.text)
45+ currentIndex: parseInt(currentIndexField.text, 10)
46+ delayMinutes: parseInt(delayMinutesField.text, 10)
47+ backgroundTopMargin: parseFloat(backgroundTopMarginField.text)
48+ locked: lockedCheckBox.checked
49+ alphanumeric: alphanumericCheckBox.checked
50+
51+ Component.onDestruction: {
52+ loader.itemDestroyed = true
53+ }
54+
55+ onSelected: {
56+ currentIndexField.text = index;
57+ }
58+ }
59+ }
60+ }
61+
62+ Rectangle {
63+ id: controls
64+ color: "white"
65+ width: units.gu(40)
66+ height: parent.height
67+
68+ Column {
69+ anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
70+ spacing: units.gu(1)
71+
72+ Row {
73+ Button {
74+ text: "Show Last Chance"
75+ onClicked: loader.item.showLastChance()
76+ }
77+ }
78+ Row {
79+ Button {
80+ text: "Hide"
81+ onClicked: loader.item.hide()
82+ }
83+ }
84+ Row {
85+ Button {
86+ text: "Reset"
87+ onClicked: loader.item.reset()
88+ }
89+ }
90+ Row {
91+ Button {
92+ text: "Show Message"
93+ onClicked: loader.item.showMessage(messageField.text)
94+ }
95+ TextField {
96+ id: messageField
97+ width: units.gu(10)
98+ text: ""
99+ }
100+ }
101+ Row {
102+ Button {
103+ text: "Show Prompt"
104+ onClicked: loader.item.showPrompt(promptField.text, isSecretCheckBox.checked, isDefaultPromptCheckBox.checked)
105+ }
106+ TextField {
107+ id: promptField
108+ width: units.gu(10)
109+ text: ""
110+ }
111+ CheckBox {
112+ id: isSecretCheckBox
113+ }
114+ Label {
115+ text: "secret"
116+ }
117+ CheckBox {
118+ id: isDefaultPromptCheckBox
119+ }
120+ Label {
121+ text: "default"
122+ }
123+ }
124+ Row {
125+ Button {
126+ text: "Authenticated"
127+ onClicked: {
128+ if (successCheckBox.checked) {
129+ loader.item.notifyAuthenticationSucceeded();
130+ } else {
131+ loader.item.notifyAuthenticationFailed();
132+ }
133+ }
134+ }
135+ CheckBox {
136+ id: successCheckBox
137+ }
138+ Label {
139+ text: "success"
140+ }
141+ }
142+ Row {
143+ Button {
144+ text: "Try To Unlock"
145+ onClicked: loader.item.tryToUnlock(toTheRightCheckBox.checked)
146+ }
147+ CheckBox {
148+ id: toTheRightCheckBox
149+ }
150+ Label {
151+ text: "toTheRight"
152+ }
153+ }
154+ Row {
155+ TextField {
156+ id: launcherOffsetField
157+ width: units.gu(10)
158+ text: "0"
159+ }
160+ Label {
161+ text: "launcherOffset"
162+ }
163+ }
164+ Row {
165+ TextField {
166+ id: currentIndexField
167+ width: units.gu(10)
168+ text: "0"
169+ }
170+ Label {
171+ text: "currentIndex"
172+ }
173+ }
174+ Row {
175+ TextField {
176+ id: delayMinutesField
177+ width: units.gu(10)
178+ text: "0"
179+ }
180+ Label {
181+ text: "delayMinutes"
182+ }
183+ }
184+ Row {
185+ TextField {
186+ id: backgroundTopMarginField
187+ width: units.gu(10)
188+ text: "0"
189+ }
190+ Label {
191+ text: "backgroundTopMargin"
192+ }
193+ }
194+ Row {
195+ CheckBox {
196+ id: lockedCheckBox
197+ }
198+ Label {
199+ text: "locked"
200+ }
201+ }
202+ Row {
203+ CheckBox {
204+ id: alphanumericCheckBox
205+ }
206+ Label {
207+ text: "alphanumeric"
208+ }
209+ }
210+ Row {
211+ Label {
212+ text: "selected: " + selectedSpy.count
213+ }
214+ }
215+ Row {
216+ Label {
217+ text: "responded: " + respondedSpy.count
218+ }
219+ }
220+ Row {
221+ Label {
222+ text: "teased: " + teaseSpy.count
223+ }
224+ }
225+ Row {
226+ Label {
227+ text: "emergency: " + emergencySpy.count
228+ }
229+ }
230+ Row {
231+ Button {
232+ text: "Reload View"
233+ onClicked: {
234+ loader.active = false;
235+ loader.active = true;
236+ }
237+ }
238+ }
239+ }
240+ }
241+ }
242+
243+ Binding {
244+ target: LightDM.Infographic
245+ property: "username"
246+ value: "single"
247+ }
248+
249+ SignalSpy {
250+ id: selectedSpy
251+ target: loader.item
252+ signalName: "selected"
253+ }
254+
255+ SignalSpy {
256+ id: respondedSpy
257+ target: loader.item
258+ signalName: "responded"
259+ }
260+
261+ SignalSpy {
262+ id: teaseSpy
263+ target: loader.item
264+ signalName: "tease"
265+ }
266+
267+ SignalSpy {
268+ id: emergencySpy
269+ target: loader.item
270+ signalName: "emergencyCall"
271+ }
272+
273+ SignalSpy {
274+ id: infographicDataChangedSpy
275+ target: LightDM.Infographic
276+ signalName: "dataChanged"
277+ }
278+
279+ UT.UnityTestCase {
280+ name: "NarrowView"
281+ when: windowShown
282+
283+ property Item view: loader.status === Loader.Ready ? loader.item : null
284+
285+ function init() {
286+ view.currentIndex = 0; // break binding with text field
287+
288+ selectedSpy.clear();
289+ respondedSpy.clear();
290+ teaseSpy.clear();
291+ emergencySpy.clear();
292+ infographicDataChangedSpy.clear();
293+ }
294+
295+ function cleanup() {
296+ loader.itemDestroyed = false;
297+ loader.active = false;
298+ tryCompare(loader, "status", Loader.Null);
299+ tryCompare(loader, "item", null);
300+ tryCompare(loader, "itemDestroyed", true);
301+ loader.active = true;
302+ tryCompare(loader, "status", Loader.Ready);
303+ removeTimeConstraintsFromDirectionalDragAreas(loader.item);
304+ }
305+
306+ function swipeAwayCover(toTheRight) {
307+ if (toTheRight === undefined) {
308+ toTheRight = false;
309+ }
310+
311+ tryCompare(view, "fullyShown", true);
312+ var touchY = view.height / 2;
313+ if (toTheRight) {
314+ touchFlick(view, 0, touchY, view.width, touchY);
315+ } else {
316+ touchFlick(view, view.width, touchY, 0, touchY);
317+ }
318+ var coverPage = findChild(view, "coverPage");
319+ tryCompare(coverPage, "showProgress", 0);
320+ waitForRendering(view);
321+ }
322+
323+ function enterPin(pin) {
324+ for (var i = 0; i < pin.length; ++i) {
325+ var character = pin.charAt(i);
326+ var button = findChild(view, "pinPadButton" + character);
327+ tap(button);
328+ }
329+ }
330+
331+ function test_tease_data() {
332+ return [
333+ {tag: "left", x: 0, offset: 0, count: 1},
334+ {tag: "leftWithOffsetPass", x: 10, offset: 10, count: 1},
335+ {tag: "leftWithOffsetFail", x: 9, offset: 10, count: 0},
336+ {tag: "right", x: view.width, offset: 0, count: 1},
337+ ]
338+ }
339+ function test_tease(data) {
340+ view.dragHandleLeftMargin = data.offset;
341+ tap(view, data.x, 0);
342+ compare(teaseSpy.count, data.count);
343+ }
344+
345+ function test_respondedWithPin() {
346+ view.locked = true;
347+ swipeAwayCover();
348+ enterPin("1234");
349+ compare(respondedSpy.count, 1);
350+ compare(respondedSpy.signalArguments[0][0], "1234");
351+ }
352+
353+ function test_respondedWithPassphrase() {
354+ view.locked = true;
355+ view.alphanumeric = true;
356+ swipeAwayCover();
357+ typeString("test");
358+ keyClick(Qt.Key_Enter);
359+ compare(respondedSpy.count, 1);
360+ compare(respondedSpy.signalArguments[0][0], "test");
361+ }
362+
363+ function test_respondedWithSwipe_data() {
364+ return [
365+ {tag: "left", toTheRight: false, hiddenX: -view.width},
366+ {tag: "right", toTheRight: true, hiddenX: view.width},
367+ ];
368+ }
369+ function test_respondedWithSwipe(data) {
370+ swipeAwayCover(data.toTheRight);
371+ var coverPage = findChild(view, "coverPage");
372+ compare(coverPage.x, data.hiddenX);
373+ compare(respondedSpy.count, 1);
374+ compare(respondedSpy.signalArguments[0][0], "");
375+ }
376+
377+ function test_emergencyCall() {
378+ view.locked = true;
379+ swipeAwayCover();
380+ var emergencyCallLabel = findChild(view, "emergencyCallLabel");
381+ tap(emergencyCallLabel);
382+ compare(emergencySpy.count, 1);
383+ }
384+
385+ function test_fullyShown() {
386+ tryCompare(view, "fullyShown", true);
387+ swipeAwayCover();
388+ tryCompare(view, "fullyShown", false);
389+ view.locked = true;
390+ tryCompare(view, "fullyShown", true);
391+ view.locked = false;
392+ tryCompare(view, "fullyShown", false);
393+ }
394+
395+ function test_required() {
396+ tryCompare(view, "required", true);
397+ swipeAwayCover();
398+ tryCompare(view, "required", false);
399+ view.locked = true;
400+ tryCompare(view, "required", true);
401+ view.locked = false;
402+ tryCompare(view, "required", false);
403+ }
404+
405+ function test_tryToUnlock() {
406+ var coverPage = findChild(view, "coverPage");
407+ tryCompare(coverPage, "showProgress", 1);
408+ compare(view.tryToUnlock(false), true);
409+ tryCompare(coverPage, "showProgress", 0);
410+ compare(view.tryToUnlock(false), false);
411+ }
412+
413+ /*
414+ Regression test for https://bugs.launchpad.net/ubuntu/+source/unity8/+bug/1388359
415+ "User metrics can no longer be changed by double tap"
416+ */
417+ function test_doubleTapSwitchesToNextInfographic() {
418+ var infographicPrivate = findInvisibleChild(view, "infographicPrivate");
419+ verify(infographicPrivate);
420+
421+ // wait for the UI to settle down before double tapping it
422+ tryCompare(infographicPrivate, "animating", false);
423+
424+ var dataCircle = findChild(view, "dataCircle");
425+ verify(dataCircle);
426+
427+ tap(dataCircle);
428+ wait(1);
429+ tap(dataCircle);
430+
431+ tryCompare(infographicDataChangedSpy, "count", 1);
432+ }
433+
434+ function test_movesBackIntoPlaceWhenNotDraggedFarEnough() {
435+ var coverPage = findChild(view, "coverPage");
436+
437+ var dragEvaluator = findInvisibleChild(coverPage, "edgeDragEvaluator");
438+ verify(dragEvaluator);
439+
440+ // Make it easier to get a rejection/rollback. Otherwise would have to inject
441+ // a fake timer into dragEvaluator.
442+ // Afterall, we are testing if the CoverPage indeed moves back on a
443+ // rollback decision, not the drag evaluation itself.
444+ dragEvaluator.minDragDistance = dragEvaluator.maxDragDistance / 2;
445+
446+ // it starts as fully shown
447+ compare(coverPage.x, 0);
448+
449+ // then we drag it a bit
450+ var startX = coverPage.width - 1;
451+ var touchY = coverPage.height / 2;
452+ var dragXDelta = -(dragEvaluator.minDragDistance * 0.3);
453+ touchFlick(coverPage,
454+ startX , touchY, // start pos
455+ startX + dragXDelta, touchY, // end pos
456+ true /* beginTouch */, false /* endTouch */);
457+
458+ // which should make it move a bit
459+ tryCompareFunction(function() {return coverPage.x < 0;}, true);
460+
461+ // then we release it
462+ touchRelease(coverPage, startX + dragXDelta, touchY);
463+
464+ // which should make it move back into its original position as it didn't move
465+ // far enough to have it hidden
466+ tryCompare(coverPage, "x", 0);
467+ }
468+
469+ function test_dragToHide_data() {
470+ return [
471+ {tag: "left", startX: view.width * 0.95, endX: view.width * 0.1, hiddenX: -view.width},
472+ {tag: "right", startX: view.width * 0.1, endX: view.width * 0.95, hiddenX: view.width},
473+ ];
474+ }
475+ function test_dragToHide(data) {
476+ var coverPage = findChild(view, "coverPage");
477+ compare(coverPage.x, 0);
478+ compare(coverPage.visible, true);
479+ compare(coverPage.shown, true);
480+ compare(coverPage.showProgress, 1);
481+ compare(view.fullyShown, true);
482+
483+ touchFlick(view,
484+ data.startX, view.height / 2, // start pos
485+ data.endX, view.height / 2); // end pos
486+
487+ tryCompare(coverPage, "x", data.hiddenX);
488+ tryCompare(coverPage, "visible", false);
489+ tryCompare(coverPage, "shown", false);
490+ tryCompare(coverPage, "showProgress", 0);
491+ compare(view.fullyShown, false);
492+ }
493+
494+ function test_hiddenViewRemainsHiddenAfterResize_data() {
495+ return [
496+ {tag: "left", startX: view.width * 0.95, endX: view.width * 0.1},
497+ {tag: "right", startX: view.width * 0.1, endX: view.width * 0.95},
498+ ];
499+ }
500+ function test_hiddenViewRemainsHiddenAfterResize(data) {
501+ touchFlick(view,
502+ data.startX, view.height / 2, // start pos
503+ data.endX, view.height / 2); // end pos
504+
505+ var coverPage = findChild(view, "coverPage");
506+ tryCompare(coverPage, "x", data.tag == "left" ? -view.width : view.width);
507+ tryCompare(coverPage, "visible", false);
508+ tryCompare(coverPage, "shown", false);
509+ tryCompare(coverPage, "showProgress", 0);
510+
511+ // flip dimensions to simulate an orientation change
512+ view.width = loader.height;
513+ view.height = loader.width;
514+
515+ // All properties should remain consistent
516+ tryCompare(coverPage, "x", data.tag == "left" ? -view.width : view.width);
517+ tryCompare(coverPage, "visible", false);
518+ tryCompare(coverPage, "shown", false);
519+ tryCompare(coverPage, "showProgress", 0);
520+ }
521+ }
522+}
tests/qmltests/Greeter/tst_SingleGreeter.qmldeleted−244
@@ -1,244 +0,0 @@
1−/*
2− * Copyright 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.0
18−import QtTest 1.0
19−import ".."
20−import "../../../qml/Greeter"
21−import AccountsService 0.1
22−import LightDM 0.1 as LightDM
23−import Ubuntu.Components 0.1
24−import Unity.Test 0.1 as UT
25−
26−Rectangle {
27− color: "darkblue"
28− width: units.gu(60)
29− height: units.gu(80)
30−
31− Button {
32− anchors.centerIn: parent
33− text: "Show Greeter"
34− onClicked: {
35− if (greeterLoader.item)
36− greeterLoader.item.show();
37− }
38− }
39−
40− Loader {
41− id: greeterLoader
42− anchors.fill: parent
43−
44− property bool itemDestroyed: false
45−
46− sourceComponent: Component {
47− Greeter {
48− width: greeterLoader.width
49− height: greeterLoader.height
50−
51− Component.onDestruction: {
52− greeterLoader.itemDestroyed = true;
53− }
54− SignalSpy {
55− objectName: "selectedSpy"
56− target: parent
57− signalName: "selected"
58− }
59− }
60− }
61− }
62−
63− SignalSpy {
64− id: unlockSpy
65− target: greeterLoader.item
66− signalName: "unlocked"
67− }
68−
69− SignalSpy {
70− id: tappedSpy
71− target: greeterLoader.item
72− signalName: "tapped"
73− }
74−
75− SignalSpy {
76− id: infographicDataChangedSpy
77− target: LightDM.Infographic
78− signalName: "dataChanged"
79− }
80−
81− UT.UnityTestCase {
82− name: "SingleGreeter"
83− when: windowShown
84−
85− property Greeter greeter: greeterLoader.item
86−
87− function cleanup() {
88− AccountsService.statsWelcomeScreen = true
89−
90− // force a reload so that we get a fresh Greeter for the next test
91− greeterLoader.itemDestroyed = false;
92− greeterLoader.active = false;
93− tryCompare(greeterLoader, "itemDestroyed", true);
94−
95− unlockSpy.clear();
96− tappedSpy.clear();
97−
98− greeterLoader.active = true;
99− tryCompare(greeterLoader, "status", Loader.Ready);
100− removeTimeConstraintsFromDirectionalDragAreas(greeterLoader.item);
101− }
102−
103− function test_properties() {
104− compare(greeter.multiUser, false)
105− compare(greeter.narrowMode, true)
106− }
107−
108− function test_teasingArea_data() {
109− return [
110− {tag: "left", posX: units.gu(2), leftPressed: true, rightPressed: false},
111− {tag: "right", posX: greeter.width - units.gu(2), leftPressed: false, rightPressed: true}
112− ]
113− }
114−
115− function test_teasingArea(data) {
116− tappedSpy.clear()
117− tap(greeter, data.posX, greeter.height - units.gu(1))
118− tappedSpy.wait()
119− tryCompare(tappedSpy, "count", 1)
120− }
121−
122− function test_statsWelcomeScreen() {
123− // Test logic in greeter that turns statsWelcomeScreen setting into infographic changes
124− compare(AccountsService.statsWelcomeScreen, true)
125− tryCompare(LightDM.Infographic, "username", "single")
126− AccountsService.statsWelcomeScreen = false
127− tryCompare(LightDM.Infographic, "username", "")
128− AccountsService.statsWelcomeScreen = true
129− tryCompare(LightDM.Infographic, "username", "single")
130− }
131−
132− function test_initial_selected_signal() {
133− var selectedSpy = findChild(greeter, "selectedSpy");
134− selectedSpy.wait();
135− tryCompare(selectedSpy, "count", 1);
136− }
137−
138− /*
139− Regression test for https://bugs.launchpad.net/ubuntu/+source/unity8/+bug/1388359
140− "User metrics can no longer be changed by double tap"
141− */
142− function test_doubleTapSwitchesToNextInfographic() {
143− infographicDataChangedSpy.clear();
144−
145− var infographicPrivate = findInvisibleChild(greeter, "infographicPrivate");
146− verify(infographicPrivate);
147−
148− // wait for the UI to settle down before double tapping it
149− tryCompare(infographicPrivate, "animating", false);
150−
151− var dataCircle = findChild(greeter, "dataCircle");
152− verify(dataCircle);
153−
154− tap(dataCircle);
155− wait(1);
156− tap(dataCircle);
157−
158− tryCompare(infographicDataChangedSpy, "count", 1);
159− }
160−
161− function test_movesBackIntoPlaceWhenNotDraggedFarEnough() {
162−
163− var dragEvaluator = findInvisibleChild(greeter, "edgeDragEvaluator");
164− verify(dragEvaluator);
165−
166− // Make it easier to get a rejection/rollback. Otherwise would have to inject
167− // a fake timer into dragEvaluator.
168− // Afterall, we are testing if the Greeter indeed moves back on a
169− // rollback decision, not the drag evaluation itself.
170− dragEvaluator.minDragDistance = dragEvaluator.maxDragDistance / 2;
171−
172− // it starts as fully shown
173− compare(greeter.x, 0);
174−
175− // then we drag it a bit
176− var startX = greeter.width - 1;
177− var touchY = greeter.height / 2;
178− var dragXDelta = -(dragEvaluator.minDragDistance * 0.3);
179− touchFlick(greeter,
180− startX , touchY, // start pos
181− startX + dragXDelta, touchY, // end pos
182− true /* beginTouch */, false /* endTouch */);
183−
184− // which should make it move a bit
185− tryCompareFunction(function(){return greeter.x < 0;}, true);
186−
187− // then we release it
188− touchRelease(greeter, startX + dragXDelta, touchY);
189−
190− // which should make it move back into its original position as it didn't move
191− // far enough to have it hidden
192− tryCompare(greeter, "x", 0);
193− }
194−
195− function test_dragToHide_data() {
196− return [
197− {tag: "left", startX: greeter.width * 0.95, endX: greeter.width * 0.1, hiddenX: -greeter.width},
198− {tag: "right", startX: greeter.width * 0.1, endX: greeter.width * 0.95, hiddenX: greeter.width},
199− ];
200− }
201− function test_dragToHide(data) {
202− compare(greeter.x, 0);
203− compare(greeter.visible, true);
204− compare(greeter.shown, true);
205− compare(greeter.showProgress, 1);
206−
207− touchFlick(greeter,
208− data.startX, greeter.height / 2, // start pos
209− data.endX, greeter.height / 2); // end pos
210−
211− tryCompare(greeter, "x", data.hiddenX);
212− tryCompare(greeter, "visible", false);
213− tryCompare(greeter, "shown", false);
214− tryCompare(greeter, "showProgress", 0);
215− }
216−
217− function test_hiddenGreeterRemainsHiddenAfterResize_data() {
218− return [
219− {tag: "left", startX: greeter.width * 0.95, endX: greeter.width * 0.1},
220− {tag: "right", startX: greeter.width * 0.1, endX: greeter.width * 0.95},
221− ];
222− }
223− function test_hiddenGreeterRemainsHiddenAfterResize(data) {
224− touchFlick(greeter,
225− data.startX, greeter.height / 2, // start pos
226− data.endX, greeter.height / 2); // end pos
227−
228− tryCompare(greeter, "x", data.tag == "left" ? -greeter.width : greeter.width);
229− tryCompare(greeter, "visible", false);
230− tryCompare(greeter, "shown", false);
231− tryCompare(greeter, "showProgress", 0);
232−
233− // flip dimensions to simulate an orientation change
234− greeter.width = greeterLoader.height;
235− greeter.height = greeterLoader.width;
236−
237− // All properties should remain consistent
238− tryCompare(greeter, "x", data.tag == "left" ? -greeter.width : greeter.width);
239− tryCompare(greeter, "visible", false);
240− tryCompare(greeter, "shown", false);
241− tryCompare(greeter, "showProgress", 0);
242− }
243− }
244−}
tests/qmltests/Greeter/tst_WideView.qmlnew file+519
@@ -0,0 +1,519 @@
1+/*
2+ * Copyright 2014 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.0
18+import QtTest 1.0
19+import ".."
20+import "../../../qml/Greeter"
21+import LightDM 0.1 as LightDM
22+import Ubuntu.Components 0.1
23+import Unity.Test 0.1 as UT
24+
25+Item {
26+ id: root
27+ width: units.gu(120)
28+ height: units.gu(80)
29+
30+ Binding {
31+ target: LightDM.Users
32+ property: "mockMode"
33+ value: "full"
34+ }
35+
36+ Row {
37+ anchors.fill: parent
38+ Loader {
39+ id: loader
40+ width: root.width - controls.width
41+ height: parent.height
42+
43+ property bool itemDestroyed: false
44+ sourceComponent: Component {
45+ WideView {
46+ id: view
47+
48+ background: Qt.resolvedUrl("../../../qml/graphics/tablet_background.jpg")
49+ userModel: LightDM.Users
50+ infographicModel: LightDM.Infographic
51+
52+ launcherOffset: parseFloat(launcherOffsetField.text)
53+ currentIndex: parseInt(currentIndexField.text, 10)
54+ delayMinutes: parseInt(delayMinutesField.text, 10)
55+ backgroundTopMargin: parseFloat(backgroundTopMarginField.text)
56+ locked: lockedCheckBox.checked
57+ inputMethod: fakeInputMethod
58+
59+ Component.onDestruction: {
60+ loader.itemDestroyed = true
61+ }
62+
63+ onSelected: {
64+ currentIndexField.text = index;
65+ }
66+
67+ QtObject {
68+ id: fakeInputMethod
69+ property bool visible: fakeKeyboard.visible
70+ property var keyboardRectangle: QtObject {
71+ property real x: fakeKeyboard.x
72+ property real y: fakeKeyboard.y
73+ property real width: fakeKeyboard.width
74+ property real height: fakeKeyboard.height
75+ }
76+ }
77+
78+ Rectangle {
79+ id: fakeKeyboard
80+ color: "green"
81+ opacity: 0.7
82+ anchors.bottom: view.bottom
83+ width: view.width
84+ height: view.height * 0.6
85+ visible: keyboardVisibleCheckBox.checked
86+ Text {
87+ text: "Keyboard Rectangle"
88+ color: "yellow"
89+ font.bold: true
90+ fontSizeMode: Text.Fit
91+ minimumPixelSize: 10; font.pixelSize: 200
92+ verticalAlignment: Text.AlignVCenter
93+ x: (parent.width - width) / 2
94+ y: (parent.height - height) / 2
95+ width: parent.width
96+ height: parent.height
97+ }
98+ }
99+
100+ }
101+ }
102+ }
103+
104+ Rectangle {
105+ id: controls
106+ color: "white"
107+ width: units.gu(40)
108+ height: parent.height
109+
110+ Column {
111+ anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
112+ spacing: units.gu(1)
113+
114+ Row {
115+ Button {
116+ text: "Show Last Chance"
117+ onClicked: loader.item.showLastChance()
118+ }
119+ }
120+ Row {
121+ Button {
122+ text: "Hide"
123+ onClicked: loader.item.hide()
124+ }
125+ }
126+ Row {
127+ Button {
128+ text: "Reset"
129+ onClicked: loader.item.reset()
130+ }
131+ }
132+ Row {
133+ Button {
134+ text: "Show Message"
135+ onClicked: loader.item.showMessage(messageField.text)
136+ }
137+ TextField {
138+ id: messageField
139+ width: units.gu(10)
140+ text: ""
141+ }
142+ }
143+ Row {
144+ Button {
145+ text: "Show Prompt"
146+ onClicked: loader.item.showPrompt(promptField.text, isSecretCheckBox.checked, isDefaultPromptCheckBox.checked)
147+ }
148+ TextField {
149+ id: promptField
150+ width: units.gu(10)
151+ text: ""
152+ }
153+ CheckBox {
154+ id: isSecretCheckBox
155+ }
156+ Label {
157+ text: "secret"
158+ }
159+ CheckBox {
160+ id: isDefaultPromptCheckBox
161+ }
162+ Label {
163+ text: "default"
164+ }
165+ }
166+ Row {
167+ Button {
168+ text: "Authenticated"
169+ onClicked: {
170+ if (successCheckBox.checked) {
171+ loader.item.notifyAuthenticationSucceeded();
172+ } else {
173+ loader.item.notifyAuthenticationFailed();
174+ }
175+ }
176+ }
177+ CheckBox {
178+ id: successCheckBox
179+ }
180+ Label {
181+ text: "success"
182+ }
183+ }
184+ Row {
185+ Button {
186+ text: "Try To Unlock"
187+ onClicked: loader.item.tryToUnlock(toTheRightCheckBox.checked)
188+ }
189+ CheckBox {
190+ id: toTheRightCheckBox
191+ }
192+ Label {
193+ text: "toTheRight"
194+ }
195+ }
196+ Row {
197+ TextField {
198+ id: launcherOffsetField
199+ width: units.gu(10)
200+ text: "0"
201+ }
202+ Label {
203+ text: "launcherOffset"
204+ }
205+ }
206+ Row {
207+ TextField {
208+ id: currentIndexField
209+ width: units.gu(10)
210+ text: "0"
211+ }
212+ Label {
213+ text: "currentIndex"
214+ }
215+ }
216+ Row {
217+ TextField {
218+ id: delayMinutesField
219+ width: units.gu(10)
220+ text: "0"
221+ }
222+ Label {
223+ text: "delayMinutes"
224+ }
225+ }
226+ Row {
227+ TextField {
228+ id: backgroundTopMarginField
229+ width: units.gu(10)
230+ text: "0"
231+ }
232+ Label {
233+ text: "backgroundTopMargin"
234+ }
235+ }
236+ Row {
237+ CheckBox {
238+ id: lockedCheckBox
239+ }
240+ Label {
241+ text: "locked"
242+ }
243+ }
244+ Row {
245+ Label {
246+ text: "selected: " + selectedSpy.count
247+ }
248+ }
249+ Row {
250+ Label {
251+ text: "responded: " + respondedSpy.count
252+ }
253+ }
254+ Row {
255+ Label {
256+ text: "teased: " + teaseSpy.count
257+ }
258+ }
259+ Row {
260+ Label {
261+ text: "emergency: " + emergencySpy.count
262+ }
263+ }
264+ Row {
265+ Button {
266+ text: "Reload View"
267+ onClicked: {
268+ loader.active = false;
269+ loader.active = true;
270+ }
271+ }
272+ }
273+ Row {
274+ CheckBox {
275+ id: keyboardVisibleCheckBox
276+ }
277+ Label {
278+ text: "Keyboard Visible"
279+ }
280+ }
281+ }
282+ }
283+ }
284+
285+ SignalSpy {
286+ id: selectedSpy
287+ target: loader.item
288+ signalName: "selected"
289+ }
290+
291+ SignalSpy {
292+ id: respondedSpy
293+ target: loader.item
294+ signalName: "responded"
295+ }
296+
297+ SignalSpy {
298+ id: teaseSpy
299+ target: loader.item
300+ signalName: "tease"
301+ }
302+
303+ SignalSpy {
304+ id: emergencySpy
305+ target: loader.item
306+ signalName: "emergencyCall"
307+ }
308+
309+ UT.UnityTestCase {
310+ name: "WideView"
311+ when: windowShown
312+
313+ property Item view: loader.status === Loader.Ready ? loader.item : null
314+
315+ function init() {
316+ view.currentIndex = 0; // break binding with text field
317+ selectedSpy.clear();
318+ respondedSpy.clear();
319+ teaseSpy.clear();
320+ emergencySpy.clear();
321+ }
322+
323+ function cleanup() {
324+ keyboardVisibleCheckBox.checked = false;
325+
326+ loader.itemDestroyed = false;
327+ loader.active = false;
328+ tryCompare(loader, "status", Loader.Null);
329+ tryCompare(loader, "item", null);
330+ tryCompare(loader, "itemDestroyed", true);
331+ loader.active = true;
332+ tryCompare(loader, "status", Loader.Ready);
333+ removeTimeConstraintsFromDirectionalDragAreas(loader.item);
334+ }
335+
336+ function getIndexOf(name) {
337+ for (var i = 0; i < LightDM.Users.count; i++) {
338+ if (name === LightDM.Users.data(i, LightDM.UserRoles.NameRole)) {
339+ return i;
340+ }
341+ }
342+ fail("Didn't find name")
343+ return -1;
344+ }
345+
346+ function selectUser(name) {
347+ var i = getIndexOf(name);
348+ view.currentIndex = i;
349+ return i;
350+ }
351+
352+ function swipeAwayCover() {
353+ tryCompare(view, "fullyShown", true);
354+ var touchY = view.height / 2;
355+ touchFlick(view, view.width, touchY, 0, touchY);
356+ var coverPage = findChild(view, "coverPage");
357+ tryCompare(coverPage, "showProgress", 0);
358+ waitForRendering(view);
359+ }
360+
361+ function test_tease_data() {
362+ return [
363+ {tag: "locked", x: 0, offset: 0, count: 0, locked: true},
364+ {tag: "left", x: 0, offset: 0, count: 1, locked: false},
365+ {tag: "leftWithOffsetPass", x: 10, offset: 10, count: 1, locked: false},
366+ {tag: "leftWithOffsetFail", x: 9, offset: 10, count: 0, locked: false},
367+ {tag: "right", x: view.width, offset: 0, count: 1, locked: false},
368+ ]
369+ }
370+ function test_tease(data) {
371+ view.locked = data.locked;
372+ view.dragHandleLeftMargin = data.offset;
373+ tap(view, data.x, 0);
374+ compare(teaseSpy.count, data.count);
375+ }
376+
377+ function test_selected() {
378+ var delegate = findChild(view, "username2");
379+ tap(delegate);
380+ compare(selectedSpy.count, 1);
381+ compare(selectedSpy.signalArguments[0][0], 2);
382+ compare(view.currentIndex, 0); // confirm we didn't change
383+ }
384+
385+ function test_respondedWithPassword() {
386+ view.locked = true;
387+ view.showPrompt("Prompt", true, true);
388+ var passwordInput = findChild(view, "passwordInput");
389+ compare(passwordInput.placeholderText, "Prompt");
390+ compare(passwordInput.echoMode, TextInput.Password);
391+ tap(passwordInput);
392+ typeString("password");
393+ keyClick(Qt.Key_Enter);
394+ compare(respondedSpy.count, 1);
395+ compare(respondedSpy.signalArguments[0][0], "password");
396+ }
397+
398+ function test_respondedWithNonSecret() {
399+ view.locked = true;
400+ view.showPrompt("otp", false, false);
401+ var passwordInput = findChild(view, "passwordInput");
402+ compare(passwordInput.placeholderText, "otp");
403+ compare(passwordInput.echoMode, TextInput.Normal);
404+ tap(passwordInput);
405+ typeString("foo");
406+ keyClick(Qt.Key_Enter);
407+ compare(respondedSpy.count, 1);
408+ compare(respondedSpy.signalArguments[0][0], "foo");
409+ }
410+
411+ function test_respondedWithSwipe() {
412+ swipeAwayCover();
413+ compare(respondedSpy.count, 1);
414+ compare(respondedSpy.signalArguments[0][0], "");
415+ }
416+
417+ function test_fullyShown() {
418+ tryCompare(view, "fullyShown", true);
419+ swipeAwayCover();
420+ tryCompare(view, "fullyShown", false);
421+ }
422+
423+ function test_required() {
424+ tryCompare(view, "required", true);
425+ swipeAwayCover();
426+ tryCompare(view, "required", false);
427+ }
428+
429+ function test_showMessage() {
430+ view.showMessage("Welcome to Unity Greeter");
431+ view.showMessage("<font color=\"#df382c\">This is an error</font>");
432+ view.showMessage("You should have seen three messages and this is a really long message too. wow so long much length");
433+ var infoLabel = findChild(view, "infoLabel");
434+ compare(infoLabel.text, "Welcome to Unity Greeter<br><font color=\"#df382c\">This is an error</font><br>You should have seen three messages and this is a really long message too. wow so long much length");
435+ compare(infoLabel.textFormat, Text.StyledText);
436+ compare(infoLabel.clip, true);
437+ verify(infoLabel.contentWidth > infoLabel.width);
438+ verify(infoLabel.opacity < 1);
439+ tryCompare(infoLabel, "opacity", 1);
440+ }
441+
442+ // Escape is used to reset the authentication, especially if PAM is unresponsive
443+ function test_escape() {
444+ view.currentIndex = 1;
445+ view.locked = true;
446+ view.showPrompt("Prompt", true, true);
447+ var passwordInput = findChild(view, "passwordInput");
448+ tap(passwordInput);
449+ compare(passwordInput.focus, true);
450+ compare(passwordInput.enabled, true);
451+
452+ typeString("password");
453+ keyClick(Qt.Key_Enter);
454+ compare(passwordInput.focus, false);
455+ compare(passwordInput.enabled, false);
456+
457+ compare(selectedSpy.count, 0);
458+ keyClick(Qt.Key_Escape);
459+ compare(selectedSpy.count, 1);
460+ compare(selectedSpy.signalArguments[0][0], 1);
461+
462+ view.reset();
463+ compare(passwordInput.focus, false);
464+ compare(passwordInput.enabled, true);
465+ }
466+
467+ function test_unicode() {
468+ var index = selectUser("unicode");
469+ var label = findChild(view, "username" + index);
470+ tryCompare(label, "text", "가나다라마");
471+ }
472+
473+ function test_longName() {
474+ var index = selectUser("long-name");
475+ var label = findChild(view, "username" + index);
476+ tryCompare(label, "truncated", true);
477+ }
478+
479+ function test_promptless() {
480+ var passwordInput = findChild(view, "passwordInput");
481+
482+ view.locked = true;
483+ compare(passwordInput.placeholderText, "Retry");
484+ tap(passwordInput);
485+ compare(respondedSpy.count, 0);
486+ compare(selectedSpy.count, 1);
487+ compare(selectedSpy.signalArguments[0][0], 0);
488+ selectedSpy.clear();
489+
490+ view.locked = false;
491+ compare(passwordInput.placeholderText, "Tap to unlock");
492+ tap(passwordInput);
493+ compare(selectedSpy.count, 0);
494+ compare(respondedSpy.count, 1);
495+ compare(respondedSpy.signalArguments[0][0], "");
496+ }
497+
498+ function test_loginListNotCoveredByKeyboard() {
499+ var loginList = findChild(view, "loginList");
500+ compare(loginList.height, view.height);
501+
502+ // when the vkb shows up, loginList is moved up to remain fully uncovered
503+
504+ keyboardVisibleCheckBox.checked = true;
505+
506+ tryCompare(loginList, "height", view.height - view.inputMethod.keyboardRectangle.height);
507+ tryCompareFunction( function() {
508+ var loginListRect = loginList.mapToItem(view, 0, 0, loginList.width, loginList.height);
509+ return loginListRect.y + loginListRect.height <= view.inputMethod.keyboardRectangle.y;
510+ }, true);
511+
512+ // once the vkb goes away, loginList goes back to its full height
513+
514+ keyboardVisibleCheckBox.checked = false;
515+
516+ tryCompare(loginList, "height", view.height);
517+ }
518+ }
519+}
tests/qmltests/Tutorial/tst_Tutorial.qml+5−4
@@ -112,9 +112,9 @@ Item {
112112
113113 function init() {
114114 tryCompare(shell, "enabled", true); // enabled by greeter when ready
115− swipeAwayGreeter();
116115 AccountsService.demoEdges = false;
117116 AccountsService.demoEdges = true;
117+ swipeAwayGreeter();
118118 }
119119
120120 function cleanup() {
@@ -151,13 +151,14 @@ Item {
151151 }
152152
153153 function swipeAwayGreeter() {
154− var greeter = findChild(shell, "greeter");
155− tryCompare(greeter, "showProgress", 1);
154+ var coverPage = findChild(shell, "coverPage");
155+ tryCompare(coverPage, "showProgress", 1);
156156
157157 touchFlick(shell, halfWidth, halfHeight, shell.width, halfHeight);
158158
159159 // wait until the animation has finished
160− tryCompare(greeter, "showProgress", 0);
160+ var greeter = findChild(shell, "greeter");
161+ tryCompare(greeter, "required", false);
161162 waitForRendering(greeter);
162163 }
163164
tests/qmltests/Wizard/tst_Wizard.qml+48−5
@@ -73,8 +73,9 @@ Item {
7373 AccountsService.hereEnabled = false;
7474 AccountsService.hereLicensePath = Qt.resolvedUrl("licenses");
7575 i18n.language = "en";
76− MockQOfono.setModems(["sim1"], [false]);
76+ MockQOfono.setModems(["sim1"], [false], []);
7777 MockQOfono.available = true;
78+ MockQOfono.ready = true;
7879 System.wizardEnabled = true;
7980
8081 updateSessionLanguageSpy.clear();
@@ -140,6 +141,19 @@ Item {
140141 tryCompareFunction(function() { return stack.currentPage.objectName; }, name);
141142 tryCompare(stack.currentPage, "opacity", 1.0);
142143 tryCompare(stack.currentPage, "enabled", true);
144+ tryCompare(stack.currentPage, "skipValid", true);
145+ tryCompare(stack.currentPage, "skip", false);
146+ waitForRendering(stack.currentPage);
147+ return stack.currentPage;
148+ }
149+
150+ function verifyPageIsBlocked(name) {
151+ var pages = findChild(wizard, "wizardPages");
152+ var stack = findChild(pages, "pageStack");
153+ // don't simply call tryCompare here, because stack.currentPage will be swapped out itself
154+ tryCompareFunction(function() { return stack.currentPage.objectName; }, name);
155+ tryCompare(stack.currentPage, "enabled", false);
156+ tryCompare(stack.currentPage, "skipValid", false);
143157 waitForRendering(stack.currentPage);
144158 return stack.currentPage;
145159 }
@@ -223,25 +237,54 @@ Item {
223237 }
224238
225239 function test_simNoModemsSkip() {
226− MockQOfono.setModems([], []);
240+ MockQOfono.setModems([], [], []);
227241 goToPage("passwdPage", true);
228242 }
229243
230244 function test_simFirstSkip() {
231− MockQOfono.setModems(["a", "b"], [true, false]);
245+ MockQOfono.setModems(["a", "b"], [true, false], []);
232246 goToPage("passwdPage", true);
233247 }
234248
235249 function test_simSecondSkip() {
236− MockQOfono.setModems(["a", "b"], [false, true]);
250+ MockQOfono.setModems(["a", "b"], [false, true], []);
237251 goToPage("passwdPage", true);
238252 }
239253
240254 function test_simBothSkip() {
241− MockQOfono.setModems(["a", "b"], [true, true]);
255+ MockQOfono.setModems(["a", "b"], [true, true], []);
242256 goToPage("passwdPage", true);
243257 }
244258
259+ function test_simWaitOnManagerAsync() {
260+ MockQOfono.ready = false;
261+ MockQOfono.setModems(["a"], [false], []);
262+
263+ // Go to SIM page, which will be waiting for skip to be valid
264+ var page = goToPage("languagePage");
265+ tap(findChild(page, "forwardButton"));
266+ verifyPageIsBlocked("simPage");
267+
268+ // Now release QOfono from blocking the page
269+ MockQOfono.ready = true;
270+
271+ waitForPage("simPage");
272+ }
273+
274+ function test_simWaitOnCardAsync() {
275+ MockQOfono.setModems(["a"], [false], [false]);
276+
277+ // Go to SIM page, which will be waiting for skip to be valid
278+ var page = goToPage("languagePage");
279+ tap(findChild(page, "forwardButton"));
280+ verifyPageIsBlocked("simPage");
281+
282+ // Now release QOfono from blocking the page
283+ MockQOfono.setModems(["a"], [false], [true]);
284+
285+ waitForPage("simPage");
286+ }
287+
245288 function enterPasscode(passcode) {
246289 for (var i = 0; i < passcode.length; ++i) {
247290 var character = passcode.charAt(i);
tests/qmltests/tst_Shell.qml+65−43
@@ -19,9 +19,11 @@
1919
2020import QtQuick 2.0
2121import QtTest 1.0
22+import AccountsService 0.1
2223import GSettings 1.0
2324import LightDM 0.1 as LightDM
2425import Ubuntu.Components 1.1
26+import Ubuntu.Components.ListItems 1.0 as ListItem
2527import Ubuntu.Telephony 0.1 as Telephony
2628import Unity.Application 0.1
2729import Unity.Connectivity 0.1
@@ -80,7 +82,7 @@ Item {
8082 }
8183
8284 Rectangle {
83− color: "white"
85+ color: "darkgrey"
8486 width: units.gu(30)
8587 height: shellLoader.height
8688
@@ -98,11 +100,23 @@ Item {
98100
99101 var greeter = testCase.findChild(shellLoader.item, "greeter");
100102 if (!greeter.shown) {
101− greeter.show();
103+ LightDM.Greeter.showGreeter();
102104 }
103105 }
104106 }
105107 }
108+ ListItem.ItemSelector {
109+ anchors { left: parent.left; right: parent.right }
110+ activeFocusOnPress: false
111+ text: "LightDM mock mode"
112+ model: ["single", "single-passphrase", "single-pin"]
113+ onSelectedIndexChanged: {
114+ shellLoader.active = false;
115+ LightDM.Greeter.mockMode = model[selectedIndex];
116+ LightDM.Users.mockMode = model[selectedIndex];
117+ shellLoader.active = true;
118+ }
119+ }
106120 }
107121 }
108122 }
@@ -284,11 +298,13 @@ Item {
284298
285299 function test_leftEdgeDrag_data() {
286300 return [
287− {tag: "without launcher", revealLauncher: false, swipeLength: units.gu(27), appHides: true, focusedApp: "dialer-app", launcherHides: true},
288− {tag: "with launcher", revealLauncher: true, swipeLength: units.gu(27), appHides: true, focusedApp: "dialer-app", launcherHides: true},
289− {tag: "small swipe", revealLauncher: false, swipeLength: units.gu(25), appHides: false, focusedApp: "dialer-app", launcherHides: false},
290− {tag: "long swipe", revealLauncher: false, swipeLength: units.gu(27), appHides: true, focusedApp: "dialer-app", launcherHides: true},
291− {tag: "long swipe", revealLauncher: false, swipeLength: units.gu(27), appHides: true, focusedApp: "unity8-dash", launcherHides: false}
301+ {tag: "without launcher", revealLauncher: false, swipeLength: units.gu(27), appHides: true, focusedApp: "dialer-app", launcherHides: true, greeterShown: false},
302+ {tag: "with launcher", revealLauncher: true, swipeLength: units.gu(27), appHides: true, focusedApp: "dialer-app", launcherHides: true, greeterShown: false},
303+ {tag: "small swipe", revealLauncher: false, swipeLength: units.gu(25), appHides: false, focusedApp: "dialer-app", launcherHides: false, greeterShown: false},
304+ {tag: "long swipe", revealLauncher: false, swipeLength: units.gu(27), appHides: true, focusedApp: "dialer-app", launcherHides: true, greeterShown: false},
305+ {tag: "small swipe with greeter", revealLauncher: false, swipeLength: units.gu(25), appHides: false, focusedApp: "dialer-app", launcherHides: false, greeterShown: true},
306+ {tag: "long swipe with greeter", revealLauncher: false, swipeLength: units.gu(27), appHides: true, focusedApp: "dialer-app", launcherHides: true, greeterShown: true},
307+ {tag: "swipe over dash", revealLauncher: false, swipeLength: units.gu(27), appHides: true, focusedApp: "unity8-dash", launcherHides: false, greeterShown: false},
292308 ];
293309 }
294310
@@ -299,6 +315,12 @@ Item {
299315 ApplicationManager.focusApplication(data.focusedApp)
300316 waitUntilApplicationWindowIsFullyVisible();
301317
318+ var greeter = findChild(shell, "greeter");
319+ if (data.greeterShown) {
320+ LightDM.Greeter.showGreeter();
321+ tryCompare(greeter, "fullyShown", true);
322+ }
323+
302324 if (data.revealLauncher) {
303325 dragLauncherIntoView();
304326 }
@@ -306,8 +328,10 @@ Item {
306328 swipeFromLeftEdge(data.swipeLength);
307329 if (data.appHides) {
308330 waitUntilDashIsFocused();
331+ tryCompare(greeter, "shown", false);
309332 } else {
310333 waitUntilApplicationWindowIsFullyVisible();
334+ compare(greeter.fullyShown, data.greeterShown);
311335 }
312336
313337 var launcher = findChild(shell, "launcherPanel");
@@ -335,20 +359,20 @@ Item {
335359 // Suspend while call is active...
336360 callManager.foregroundCall = phoneCall;
337361 Powerd.status = Powerd.Off;
338− tryCompare(greeter, "showProgress", 0);
362+ tryCompare(greeter, "shown", false);
339363
340364 // Now try again after ending call
341365 callManager.foregroundCall = null;
342366 Powerd.status = Powerd.On;
343367 Powerd.status = Powerd.Off;
344− tryCompare(greeter, "showProgress", 1);
368+ tryCompare(greeter, "fullyShown", true);
345369
346370 tryCompare(ApplicationManager, "suspended", true);
347371 compare(mainApp.state, ApplicationInfoInterface.Suspended);
348372
349373 // And wake up
350374 Powerd.status = Powerd.On;
351− tryCompare(greeter, "showProgress", 1);
375+ tryCompare(greeter, "fullyShown", true);
352376
353377 // Swipe away greeter to focus app
354378 swipeAwayGreeter();
@@ -359,14 +383,14 @@ Item {
359383
360384 function swipeAwayGreeter() {
361385 var greeter = findChild(shell, "greeter");
362− tryCompare(greeter, "showProgress", 1);
386+ tryCompare(greeter, "fullyShown", true);
363387
364388 var touchX = shell.width - (shell.edgeSize / 2);
365389 var touchY = shell.height / 2;
366390 touchFlick(shell, touchX, touchY, shell.width * 0.1, touchY);
367391
368392 // wait until the animation has finished
369− tryCompare(greeter, "showProgress", 0);
393+ tryCompare(greeter, "shown", false);
370394 waitForRendering(greeter);
371395 }
372396
@@ -608,12 +632,12 @@ Item {
608632
609633 waitUntilDashIsFocused();
610634
611− greeter.show();
612− tryCompare(greeter, "showProgress", 1);
635+ LightDM.Greeter.showGreeter();
636+ tryCompare(greeter, "fullyShown", true);
613637
614638 // The main point of this test
615639 ApplicationManager.requestFocusApplication("dialer-app");
616− tryCompare(greeter, "showProgress", 0);
640+ tryCompare(greeter, "shown", false);
617641 waitForRendering(greeter);
618642 }
619643
@@ -631,24 +655,22 @@ Item {
631655
632656 function test_showAndHideGreeterDBusCalls() {
633657 var greeter = findChild(shell, "greeter")
634− tryCompare(greeter, "showProgress", 0)
658+ tryCompare(greeter, "shown", false)
635659 waitForRendering(greeter);
636660 LightDM.Greeter.showGreeter()
637661 waitForRendering(greeter)
638− tryCompare(greeter, "showProgress", 1)
662+ tryCompare(greeter, "fullyShown", true)
639663 LightDM.Greeter.hideGreeter()
640− tryCompare(greeter, "showProgress", 0)
664+ tryCompare(greeter, "shown", false)
641665 }
642666
643667 function test_login() {
644− sessionSpy.clear()
645−
646668 var greeter = findChild(shell, "greeter")
647669 waitForRendering(greeter)
648− greeter.show()
649− tryCompare(greeter, "showProgress", 1)
670+ LightDM.Greeter.showGreeter();
671+ tryCompare(greeter, "fullyShown", true)
650672
651− tryCompare(sessionSpy, "count", 0)
673+ sessionSpy.clear();
652674 swipeAwayGreeter()
653675 tryCompare(sessionSpy, "count", 1)
654676 }
@@ -689,7 +711,8 @@ Item {
689711
690712 function test_unlockedProperties() {
691713 // Confirm that various properties have the correct values when unlocked
692− tryCompare(shell, "locked", false)
714+ var greeter = findChild(shell, "greeter");
715+ tryCompare(greeter, "locked", false);
693716
694717 var launcher = findChild(shell, "launcher")
695718 tryCompare(launcher, "available", true)
@@ -702,25 +725,6 @@ Item {
702725 tryCompare(unlockAllModemsSpy, "count", 1)
703726 }
704727
705− function test_leftEdgeDragOnGreeter_data() {
706− return [
707− {tag: "short swipe", targetX: shell.width / 3, unlocked: false},
708− {tag: "long swipe", targetX: shell.width / 3 * 2, unlocked: true}
709− ]
710− }
711−
712− function test_leftEdgeDragOnGreeter(data) {
713− var greeter = findChild(shell, "greeter");
714− greeter.show();
715− tryCompare(greeter, "showProgress", 1);
716−
717− var touchStartX = 2;
718− var touchStartY = shell.height / 2;
719− touchFlick(shell, touchStartX, touchStartY, data.targetX, touchStartY);
720−
721− tryCompare(greeter, "showProgress", data.unlocked ? 0 : 1);
722− }
723−
724728 function test_tapOnRightEdgeReachesApplicationSurface() {
725729 var topmostSpreadDelegate = findChild(shell, "appDelegate0");
726730 var topmostSurface = findChild(topmostSpreadDelegate, "surfaceContainer").surface;
@@ -830,5 +834,23 @@ Item {
830834 var launcherPanel = findChild(shell, "launcherPanel");
831835 tryCompare(launcherPanel, "x", -launcherPanel.width);
832836 }
837+
838+ function test_background_data() {
839+ return [
840+ {tag: "color", accounts: Qt.resolvedUrl("data:image/svg+xml,<svg><rect width='100%' height='100%' fill='#dd4814'/></svg>"), gsettings: "", output: Qt.resolvedUrl("data:image/svg+xml,<svg><rect width='100%' height='100%' fill='#dd4814'/></svg>")},
841+ {tag: "empty", accounts: "", gsettings: "", output: shell.defaultBackground},
842+ {tag: "as-specified", accounts: Qt.resolvedUrl("../data/unity/backgrounds/blue.png"), gsettings: "", output: Qt.resolvedUrl("../data/unity/backgrounds/blue.png")},
843+ {tag: "gs-specified", accounts: "", gsettings: Qt.resolvedUrl("../data/unity/backgrounds/red.png"), output: Qt.resolvedUrl("../data/unity/backgrounds/red.png")},
844+ {tag: "both-specified", accounts: Qt.resolvedUrl("../data/unity/backgrounds/blue.png"), gsettings: Qt.resolvedUrl("../data/unity/backgrounds/red.png"), output: Qt.resolvedUrl("../data/unity/backgrounds/blue.png")},
845+ {tag: "invalid-as", accounts: Qt.resolvedUrl("../data/unity/backgrounds/nope.png"), gsettings: Qt.resolvedUrl("../data/unity/backgrounds/red.png"), output: Qt.resolvedUrl("../data/unity/backgrounds/red.png")},
846+ {tag: "invalid-both", accounts: Qt.resolvedUrl("../data/unity/backgrounds/nope.png"), gsettings: Qt.resolvedUrl("../data/unity/backgrounds/stillnope.png"), output: shell.defaultBackground},
847+ ]
848+ }
849+ function test_background(data) {
850+ AccountsService.backgroundFile = data.accounts;
851+ var backgroundSettings = findInvisibleChild(shell, "backgroundSettings");
852+ backgroundSettings.pictureUri = data.gsettings;
853+ tryCompare(shell, "background", data.output);
854+ }
833855 }
834856}
tests/qmltests/tst_ShellWithPin.qml+97−48
@@ -74,7 +74,6 @@ Item {
7474 Component.onDestruction: {
7575 shellLoader.itemDestroyed = true
7676 }
77− maxFailedLogins: maxRetriesTextField.text
7877 }
7978 }
8079 }
@@ -90,15 +89,7 @@ Item {
9089 Button {
9190 anchors { left: parent.left; right: parent.right }
9291 text: "Show Greeter"
93− onClicked: {
94− if (shellLoader.status !== Loader.Ready)
95− return
96−
97− var greeter = testCase.findChild(shellLoader.item, "greeter")
98− if (!greeter.shown) {
99− greeter.show()
100− }
101− }
92+ onClicked: LightDM.Greeter.showGreeter()
10293 }
10394
10495 Label {
@@ -108,6 +99,10 @@ Item {
10899 TextField {
109100 id: maxRetriesTextField
110101 text: "-1"
102+ onTextChanged: {
103+ var greeter = testCase.findChild(shellLoader.item, "greeter");
104+ greeter.maxFailedLogins = text;
105+ }
111106 }
112107 }
113108 }
@@ -144,11 +139,12 @@ Item {
144139
145140 function init() {
146141 tryCompare(shell, "enabled", true); // will be enabled when greeter is all ready
147− sessionSpy.target = findChild(shell, "greeter")
148− swipeAwayGreeter()
142+ var greeter = findChild(shell, "greeter");
143+ sessionSpy.target = greeter;
144+ swipeAwayGreeter(true);
149145 waitForLockscreen()
150− shell.failedLoginsDelayAttempts = -1
151− maxRetriesTextField.text = "-1"
146+ greeter.failedLoginsDelayAttempts = -1;
147+ greeter.maxFailedLogins = -1;
152148 }
153149
154150 function cleanup() {
@@ -192,17 +188,21 @@ Item {
192188 compare(ApplicationManager.count, 1)
193189 }
194190
195− function swipeAwayGreeter() {
191+ function swipeAwayGreeter(waitForCoverPage) {
196192 var greeter = findChild(shell, "greeter");
197193 waitForRendering(greeter)
198− tryCompare(greeter, "showProgress", 1);
194+ var coverPage = findChild(shell, "coverPage");
195+ tryCompare(coverPage, "showProgress", 1);
199196
200197 var touchX = shell.width - (shell.edgeSize / 2);
201198 var touchY = shell.height / 2;
202199 touchFlick(shell, touchX, touchY, shell.width * 0.1, touchY);
203200
204− // wait until the animation has finished
205− tryCompare(greeter, "showProgress", 0);
201+ if (waitForCoverPage) {
202+ // wait until the animation has finished
203+ var coverPage = findChild(shell, "coverPage");
204+ tryCompare(coverPage, "showProgress", 0);
205+ }
206206 }
207207
208208 function waitForLockscreen() {
@@ -213,19 +213,17 @@ Item {
213213 }
214214
215215 function enterPin(pin) {
216− var inputField = findChild(shell, "pinentryField")
217216 for (var i = 0; i < pin.length; ++i) {
218217 var character = pin.charAt(i)
219218 var button = findChild(shell, "pinPadButton" + character)
219+ tryCompare(button, "enabled", true);
220220 tap(button)
221221 }
222222 }
223223
224224 function confirmLockedApp(app) {
225225 var greeter = findChild(shell, "greeter")
226− var lockscreen = findChild(shell, "lockscreen")
227226 tryCompare(greeter, "shown", false)
228− tryCompare(lockscreen, "shown", false)
229227 tryCompare(greeter, "hasLockedApp", true)
230228 tryCompare(greeter, "lockedApp", app)
231229 tryCompare(LightDM.Greeter, "active", true)
@@ -264,13 +262,16 @@ Item {
264262 AccountsService.demoEdges = true
265263 tryCompare(lockscreen, "shown", false)
266264
267− swipeAwayGreeter()
265+ var greeter = findChild(shell, "greeter");
266+ swipeAwayGreeter(false);
267+ tryCompare(greeter, "shown", false);
268268 tryCompare(sessionSpy, "count", 1)
269269
270270 // Lockscreen is only hidden by the edge demo, so if we turn that
271271 // off and show greeter again, lockscreen should appear
272272 AccountsService.demoEdges = false
273273 LightDM.Greeter.showGreeter()
274+ lockscreen = findChild(shell, "lockscreen");
274275 tryCompare(lockscreen, "shown", true)
275276 }
276277
@@ -288,18 +289,16 @@ Item {
288289
289290 function test_emergencyCall() {
290291 var greeter = findChild(shell, "greeter")
291− var lockscreen = findChild(shell, "lockscreen")
292− var emergencyButton = findChild(lockscreen, "emergencyCallLabel")
293292 var panel = findChild(shell, "panel")
294293 var indicators = findChild(shell, "indicators")
295294 var launcher = findChild(shell, "launcher")
296295 var stage = findChild(shell, "stage")
297296
298− tap(emergencyButton)
297+ tap(findChild(greeter, "emergencyCallLabel"));
299298
300299 tryCompare(greeter, "lockedApp", "dialer-app")
301300 tryCompare(greeter, "hasLockedApp", true)
302− tryCompare(lockscreen, "shown", false)
301+ tryCompare(greeter, "shown", false);
303302 tryCompare(panel, "fullscreenMode", true)
304303 tryCompare(indicators, "available", false)
305304 tryCompare(launcher, "available", false)
@@ -312,7 +311,7 @@ Item {
312311 tryCompare(greeter, "shown", true)
313312 tryCompare(greeter, "lockedApp", "")
314313 tryCompare(greeter, "hasLockedApp", false)
315− tryCompare(lockscreen, "shown", true)
314+ tryCompare(greeter, "fullyShown", true);
316315 tryCompare(panel, "fullscreenMode", false)
317316 tryCompare(indicators, "available", true)
318317 tryCompare(launcher, "available", true)
@@ -320,23 +319,25 @@ Item {
320319 }
321320
322321 function test_emergencyCallCrash() {
323− var lockscreen = findChild(shell, "lockscreen")
324− var emergencyButton = findChild(lockscreen, "emergencyCallLabel")
322+ var greeter = findChild(shell, "greeter");
323+ var emergencyButton = findChild(greeter, "emergencyCallLabel");
325324 tap(emergencyButton)
326325
327− tryCompare(lockscreen, "shown", false)
326+ tryCompare(greeter, "shown", false);
328327 killApps() // kill dialer-app, as if it crashed
329− tryCompare(lockscreen, "shown", true)
328+ tryCompare(greeter, "shown", true);
329+ tryCompare(findChild(greeter, "lockscreen"), "shown", true);
330+ tryCompare(findChild(greeter, "coverPage"), "shown", false);
330331 }
331332
332333 function test_emergencyCallAppLaunch() {
333− var lockscreen = findChild(shell, "lockscreen")
334− var emergencyButton = findChild(lockscreen, "emergencyCallLabel")
334+ var greeter = findChild(shell, "greeter");
335+ var emergencyButton = findChild(greeter, "emergencyCallLabel");
335336 tap(emergencyButton)
336337
337− tryCompare(lockscreen, "shown", false)
338+ tryCompare(greeter, "shown", false);
338339 ApplicationManager.startApplication("gallery-app", ApplicationManager.NoFlag)
339− tryCompare(lockscreen, "shown", true)
340+ tryCompare(greeter, "shown", true);
340341 }
341342
342343 function test_failedLoginsCount() {
@@ -350,7 +351,8 @@ Item {
350351 }
351352
352353 function test_wrongEntries() {
353− shell.failedLoginsDelayAttempts = 3
354+ var greeter = findChild(shell, "greeter");
355+ greeter.failedLoginsDelayAttempts = 3;
354356
355357 var placeHolder = findChild(shell, "wrongNoticeLabel")
356358 tryCompare(placeHolder, "text", "")
@@ -364,7 +366,7 @@ Item {
364366 var lockscreen = findChild(shell, "lockscreen")
365367 tryCompare(lockscreen, "delayMinutes", 0)
366368 enterPin("1111")
367− tryCompare(lockscreen, "delayMinutes", shell.failedLoginsDelayMinutes)
369+ tryCompare(lockscreen, "delayMinutes", greeter.failedLoginsDelayMinutes);
368370 }
369371
370372 function test_factoryReset() {
@@ -410,7 +412,7 @@ Item {
410412 // And when we kill the app, we go back to locked tablet mode
411413 killApps()
412414 var greeter = findChild(shell, "greeter")
413− tryCompare(greeter, "showProgress", 1)
415+ tryCompare(greeter, "fullyShown", true)
414416 tryCompare(shell, "sideStageEnabled", true)
415417 tryCompare(applicationsDisplayLoader, "tabletMode", true)
416418 }
@@ -453,11 +455,11 @@ Item {
453455 // - Should be back in normal dialer
454456 // (we've had a bug where we locked screen in this case)
455457
456− var lockscreen = findChild(shell, "lockscreen");
458+ var greeter = findChild(shell, "greeter");
457459 var panel = findChild(shell, "panel");
458460
459461 enterPin("1234");
460− tryCompare(lockscreen, "shown", false);
462+ tryCompare(greeter, "shown", false);
461463 tryCompare(LightDM.Greeter, "active", false);
462464
463465 ApplicationManager.startApplication("dialer-app", ApplicationManager.NoFlag);
@@ -472,10 +474,32 @@ Item {
472474 ApplicationManager.requestFocusApplication("dialer-app");
473475
474476 tryCompare(ApplicationManager, "focusedApplicationId", "dialer-app");
475− tryCompare(lockscreen, "shown", false);
477+ tryCompare(greeter, "shown", false);
476478 tryCompare(LightDM.Greeter, "active", false);
477479 }
478480
481+ function test_focusRequestedHidesCoverPage() {
482+ LightDM.Greeter.showGreeter();
483+
484+ var app = ApplicationManager.startApplication("gallery-app");
485+ // wait until the app is fully loaded (ie, real surface replaces splash screen)
486+ tryCompareFunction(function() { return app.session !== null && app.session.surface !== null }, true);
487+
488+ // New app hides coverPage?
489+ var greeter = findChild(shell, "greeter");
490+ var coverPage = testCase.findChild(shell, "coverPage");
491+ tryCompare(coverPage, "showProgress", 0);
492+ tryCompare(greeter, "fullyShown", true);
493+
494+ LightDM.Greeter.showGreeter();
495+ tryCompare(coverPage, "showProgress", 1);
496+
497+ // Make sure focusing same app triggers same behavior
498+ ApplicationManager.requestFocusApplication("gallery-app");
499+ tryCompare(coverPage, "showProgress", 0);
500+ tryCompare(greeter, "fullyShown", true);
501+ }
502+
479503 function test_suspend() {
480504 var greeter = findChild(shell, "greeter");
481505
@@ -487,10 +511,10 @@ Item {
487511
488512 // And wake up
489513 Powerd.status = Powerd.On;
490− tryCompare(greeter, "showProgress", 1);
514+ tryCompare(greeter, "fullyShown", true);
491515
492516 // Swipe away greeter to focus app
493− swipeAwayGreeter();
517+ swipeAwayGreeter(true);
494518
495519 // We have a lockscreen, make sure we're still suspended
496520 tryCompare(ApplicationManager, "suspended", true);
@@ -539,9 +563,9 @@ Item {
539563 only after its idle timeout is triggered.
540564 */
541565 function test_shortLeftEdgeSwipeMakesLauncherStayVisible() {
542− var greeter = testCase.findChild(shell, "greeter")
543− greeter.show();
544− tryCompare(greeter, "showProgress", 1);
566+ LightDM.Greeter.showGreeter();
567+ var coverPage = testCase.findChild(shell, "coverPage");
568+ tryCompare(coverPage, "showProgress", 1);
545569
546570 var launcher = testCase.findChild(shell, "launcher")
547571 {
@@ -562,12 +586,12 @@ Item {
562586 tryCompare(launcherPanel, "x", 0);
563587
564588 // Greeter should be displaced
565− tryCompareFunction(function() { return greeter.mapToItem(shell, 0, 0).x > shell.width*0.2; }, true);
589+ tryCompareFunction(function() { return coverPage.mapToItem(shell, 0, 0).x > shell.width*0.2; }, true);
566590
567591 touchRelease(shell, toX, shell.height * 0.5);
568592
569593 // Upon release the greeter should have slid back into full view
570− tryCompareFunction(function() { return greeter.mapToItem(shell, 0, 0).x === 0; }, true);
594+ tryCompareFunction(function() { return coverPage.mapToItem(shell, 0, 0).x === 0; }, true);
571595
572596 // And the launcher should stay fully shown
573597 for (var i = 0; i < 10; ++i) {
@@ -575,5 +599,30 @@ Item {
575599 compare(launcherPanel.x, 0);
576600 }
577601 }
602+
603+ function test_longLeftEdgeDrags() {
604+ var coverPage = findChild(shell, "coverPage");
605+ var lockscreen = findChild(shell, "lockscreen");
606+ var launcher = findChild(shell, "launcherPanel");
607+ ApplicationManager.startApplication("gallery-app");
608+ tryCompare(ApplicationManager, "focusedApplicationId", "gallery-app");
609+
610+ // Show greeter
611+ LightDM.Greeter.showGreeter();
612+ tryCompare(coverPage, "showProgress", 1);
613+
614+ // Swipe cover page away
615+ touchFlick(shell, 2, shell.height / 2, units.gu(27), shell.height / 2);
616+ tryCompare(launcher, "x", -launcher.width);
617+ tryCompare(coverPage, "showProgress", 0);
618+ compare(lockscreen.shown, true);
619+ compare(ApplicationManager.focusedApplicationId, "gallery-app");
620+
621+ // Now attempt a swipe on lockscreen
622+ touchFlick(shell, 2, shell.height / 2, units.gu(27), shell.height / 2);
623+ tryCompare(launcher, "x", 0);
624+ compare(lockscreen.shown, true);
625+ compare(ApplicationManager.focusedApplicationId, "gallery-app");
626+ }
578627 }
579628}
tests/qmltests/tst_TabletShell.qml+50−60
@@ -28,9 +28,10 @@ import Powerd 0.1
2828
2929import "../../qml"
3030
31−Row {
31+Item {
3232 id: root
33− spacing: 0
33+ width: units.gu(120)
34+ height: units.gu(80)
3435
3536 Component.onCompleted: {
3637 // must set the mock mode before loading the Shell
@@ -55,49 +56,52 @@ Row {
5556 }
5657 }
5758
58− Loader {
59− id: shellLoader
59+ Row {
60+ anchors.fill: parent
61+ Loader {
62+ id: shellLoader
6063
61− active: false
62− width: units.gu(100)
63− height: units.gu(80)
64+ active: false
65+ width: units.gu(100)
66+ height: parent.height
6467
65− property bool itemDestroyed: false
66− sourceComponent: Component {
67− Shell {
68− property string indicatorProfile: "phone"
68+ property bool itemDestroyed: false
69+ sourceComponent: Component {
70+ Shell {
71+ property string indicatorProfile: "phone"
6972
70− Component.onDestruction: {
71− shellLoader.itemDestroyed = true
73+ Component.onDestruction: {
74+ shellLoader.itemDestroyed = true
75+ }
7276 }
7377 }
7478 }
75− }
7679
77− Rectangle {
78− color: "white"
79− width: units.gu(20)
80− height: shellLoader.height
81−
82− Column {
83− anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
84− spacing: units.gu(1)
85− Button {
86− text: "Show Greeter"
87− onClicked: {
88− if (shellLoader.status !== Loader.Ready)
89− return
90−
91− var greeter = testCase.findChild(shellLoader.item, "greeter")
92− if (!greeter.shown) {
93− greeter.show()
80+ Rectangle {
81+ color: "white"
82+ width: units.gu(20)
83+ height: parent.height
84+
85+ Column {
86+ anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
87+ spacing: units.gu(1)
88+ Button {
89+ text: "Show Greeter"
90+ onClicked: {
91+ if (shellLoader.status !== Loader.Ready)
92+ return
93+
94+ var greeter = testCase.findChild(shellLoader.item, "greeter")
95+ if (!greeter.shown) {
96+ greeter.show()
97+ }
9498 }
9599 }
96− }
97− Button {
98− text: "Demo edges"
99− onClicked: {
100− AccountsService.demoEdges = true
100+ Button {
101+ text: "Demo edges"
102+ onClicked: {
103+ AccountsService.demoEdges = true;
104+ }
101105 }
102106 }
103107 }
@@ -133,6 +137,8 @@ Row {
133137
134138 function init() {
135139 tryCompare(shell, "enabled", true); // will be enabled when greeter is all ready
140+ var userList = findChild(shell, "userList");
141+ tryCompare(userList, "movingInternally", false);
136142 sessionSpy.clear()
137143 sessionSpy.target = findChild(shell, "greeter")
138144 dashCommunicatorSpy.target = findInvisibleChild(shell, "dashCommunicator")
@@ -188,8 +194,7 @@ Row {
188194 if (userlist.currentIndex > i) {
189195 next = userlist.currentIndex - 1
190196 }
191− var account = findChild(greeter, "username"+next)
192− mouseClick(account, 1, 1)
197+ tap(findChild(greeter, "username"+next));
193198 tryCompare(userlist, "currentIndex", next)
194199 tryCompare(userlist, "movingInternally", false)
195200 }
@@ -197,13 +202,12 @@ Row {
197202
198203 function selectUser(name) {
199204 // Find index of user with the right name
200− var greeter = findChild(shell, "greeter")
201− for (var i = 0; i < greeter.model.count; i++) {
202− if (greeter.model.data(i, LightDM.UserRoles.NameRole) == name) {
205+ for (var i = 0; i < LightDM.Users.count; i++) {
206+ if (LightDM.Users.data(i, LightDM.UserRoles.NameRole) == name) {
203207 break
204208 }
205209 }
206− if (i == greeter.model.count) {
210+ if (i == LightDM.Users.count) {
207211 fail("Didn't find name")
208212 return -1
209213 }
@@ -213,7 +217,7 @@ Row {
213217
214218 function clickPasswordInput(isButton) {
215219 var greeter = findChild(shell, "greeter")
216− tryCompare(greeter, "showProgress", 1)
220+ tryCompare(greeter, "fullyShown", true);
217221
218222 var passwordMouseArea = findChild(shell, "passwordMouseArea")
219223 tryCompare(passwordMouseArea, "enabled", isButton)
@@ -223,9 +227,9 @@ Row {
223227 }
224228
225229 function confirmLoggedIn(loggedIn) {
226− var greeterWrapper = findChild(shell, "greeterWrapper")
227− tryCompare(greeterWrapper, "showProgress", loggedIn ? 0 : 1)
228− tryCompare(sessionSpy, "count", loggedIn ? 1 : 0)
230+ var greeter = findChild(shell, "greeter");
231+ tryCompare(greeter, "shown", loggedIn ? false : true);
232+ verify(loggedIn ? sessionSpy.count > 0 : sessionSpy.count === 0);
229233 }
230234
231235 function swipeFromLeftEdge(swipeLength) {
@@ -234,20 +238,6 @@ Row {
234238 touchFlick(shell, touchStartX, touchStartY, swipeLength, touchStartY)
235239 }
236240
237− function test_noLockscreen() {
238− selectUser("has-password")
239− var lockscreen = findChild(shell, "lockscreen")
240− tryCompare(lockscreen, "shown", false)
241− }
242−
243− function test_showAndHideGreeterDBusCalls() {
244− var greeter = findChild(shell, "greeter")
245− LightDM.Greeter.hideGreeter()
246− tryCompare(greeter, "showProgress", 0)
247− LightDM.Greeter.showGreeter()
248− tryCompare(greeter, "showProgress", 1)
249− }
250−
251241 function test_login_data() {
252242 return [
253243 {tag: "auth error", user: "auth-error", loggedIn: false, password: ""},
tests/utils/modules/Unity/Test/CMakeLists.txt+1
@@ -4,6 +4,7 @@ add_definitions(-DQT_NO_KEYWORDS)
44
55include_directories(
66 ${Qt5Gui_PRIVATE_INCLUDE_DIRS}
7+ ${Qt5Quick_PRIVATE_INCLUDE_DIRS}
78 ${CMAKE_SOURCE_DIR}/libs/UbuntuGestures
89)
910
tests/utils/modules/Unity/Test/Test.qmltypes+1
@@ -21,6 +21,7 @@ Module {
2121 Parameter { type: "string" }
2222 }
2323 Method { name: "touchEvent"; type: "TouchEventSequenceWrapper*" }
24+ Method { name: "ensureTouchRegistryInstalled" }
2425 }
2526 Component {
2627 name: "TouchEventSequenceWrapper"
tests/utils/modules/Unity/Test/TouchEventSequenceWrapper.cpp+10−1
@@ -15,15 +15,24 @@
1515 */
1616
1717#include "TouchEventSequenceWrapper.h"
18−TouchEventSequenceWrapper::TouchEventSequenceWrapper(QTest::QTouchEventSequence eventSequence)
18+#include <private/qquickwindow_p.h>
19+
20+TouchEventSequenceWrapper::TouchEventSequenceWrapper(QTest::QTouchEventSequence eventSequence, QQuickItem *item)
1921 : QObject(0)
2022 , m_eventSequence(eventSequence)
23+ , m_item(item)
2124{
2225}
2326
2427void TouchEventSequenceWrapper::commit(bool processEvents)
2528{
2629 m_eventSequence.commit(processEvents);
30+#if (QT_VERSION >= QT_VERSION_CHECK(5, 4, 0))
31+ if (m_item->window()) {
32+ QQuickWindowPrivate *wp = QQuickWindowPrivate::get(m_item->window());
33+ wp->flushDelayedTouchEvent();
34+ }
35+#endif
2736}
2837
2938void TouchEventSequenceWrapper::move(int touchId, int x, int y)
tests/utils/modules/Unity/Test/TouchEventSequenceWrapper.h+4−1
@@ -21,11 +21,13 @@
2121#include <QtTest/QTest>
2222#include <QtQml/qqml.h>
2323
24+class QQuickItem;
25+
2426class TouchEventSequenceWrapper : public QObject
2527{
2628 Q_OBJECT
2729public:
28− TouchEventSequenceWrapper(QTest::QTouchEventSequence eventSequence);
30+ TouchEventSequenceWrapper(QTest::QTouchEventSequence eventSequence, QQuickItem *item);
2931
3032 Q_INVOKABLE void commit(bool processEvents = true);
3133 Q_INVOKABLE void move(int touchId, int x, int y);
@@ -35,6 +37,7 @@ public:
3537
3638private:
3739 QTest::QTouchEventSequence m_eventSequence;
40+ QQuickItem *m_item;
3841};
3942
4043QML_DECLARE_TYPE(TouchEventSequenceWrapper)
tests/utils/modules/Unity/Test/UnityTestCase.qml+11−11
@@ -221,8 +221,8 @@ TestCase {
221221 }
222222 }
223223
224− function touchEvent() {
225− return UT.Util.touchEvent()
224+ function touchEvent(item) {
225+ return UT.Util.touchEvent(item)
226226 }
227227
228228 // speed is in pixels/second
@@ -255,7 +255,7 @@ TestCase {
255255 if (beginTouch) {
256256 fakeDateTime.currentTimeMs += timeStep
257257
258− var event = touchEvent()
258+ var event = touchEvent(item)
259259 event.press(0 /* touchId */, rootFrom.x, rootFrom.y)
260260 event.commit()
261261 }
@@ -265,19 +265,19 @@ TestCase {
265265 // Avoid any rounding errors by making the last move be at precisely
266266 // the point specified
267267 wait(iterations / speed)
268− var event = touchEvent()
268+ var event = touchEvent(item)
269269 event.move(0 /* touchId */, rootTo.x, rootTo.y)
270270 event.commit()
271271 } else {
272272 wait(iterations / speed)
273− var event = touchEvent()
273+ var event = touchEvent(item)
274274 event.move(0 /* touchId */, rootFrom.x + (i + 1) * diffX, rootFrom.y + (i + 1) * diffY)
275275 event.commit()
276276 }
277277 }
278278 if (endTouch) {
279279 fakeDateTime.currentTimeMs += timeStep
280− var event = touchEvent()
280+ var event = touchEvent(item)
281281 event.release(0 /* touchId */, rootTo.x, rootTo.y)
282282 event.commit()
283283 }
@@ -287,7 +287,7 @@ TestCase {
287287 // Make sure the item is rendered
288288 waitForRendering(item);
289289
290− var event1 = touchEvent();
290+ var event1 = touchEvent(item);
291291 // first finger
292292 event1.press(0, x1Start, y1Start);
293293 event1.commit();
@@ -320,7 +320,7 @@ TestCase {
320320 var root = fetchRootItem(item)
321321 var rootPoint = item.mapToItem(root, x, y)
322322
323− var event = touchEvent()
323+ var event = touchEvent(item)
324324 event.press(0 /* touchId */, rootPoint.x, rootPoint.y)
325325 event.commit()
326326 }
@@ -329,7 +329,7 @@ TestCase {
329329 var root = fetchRootItem(item)
330330 var rootPoint = item.mapToItem(root, x, y)
331331
332− var event = touchEvent()
332+ var event = touchEvent(item)
333333 event.release(0 /* touchId */, rootPoint.x, rootPoint.y)
334334 event.commit()
335335 }
@@ -347,11 +347,11 @@ TestCase {
347347 var root = fetchRootItem(item)
348348 var rootPoint = item.mapToItem(root, x, y)
349349
350− var event = touchEvent()
350+ var event = touchEvent(item)
351351 event.press(0 /* touchId */, rootPoint.x, rootPoint.y)
352352 event.commit()
353353
354− event = touchEvent()
354+ event = touchEvent(item)
355355 event.release(0 /* touchId */, rootPoint.x, rootPoint.y)
356356 event.commit()
357357 }
tests/utils/modules/Unity/Test/testutil.cpp+2−2
@@ -55,14 +55,14 @@ TestUtil::isInstanceOf(QObject *obj, QString name)
5555 return result;
5656}
5757
58−TouchEventSequenceWrapper *TestUtil::touchEvent()
58+TouchEventSequenceWrapper *TestUtil::touchEvent(QQuickItem *item)
5959{
6060 ensureTargetWindow();
6161 ensureTouchDevice();
6262 ensureTouchRegistryInstalled();
6363
6464 return new TouchEventSequenceWrapper(
65− QTest::touchEvent(m_targetWindow, m_touchDevice, /* autoCommit */ false));
65+ QTest::touchEvent(m_targetWindow, m_touchDevice, /* autoCommit */ false), item);
6666}
6767
6868void TestUtil::ensureTargetWindow()
tests/utils/modules/Unity/Test/testutil.h+1−1
@@ -33,7 +33,7 @@ public:
3333 ~TestUtil();
3434
3535 Q_INVOKABLE bool isInstanceOf(QObject*, QString);
36− Q_INVOKABLE TouchEventSequenceWrapper *touchEvent();
36+ Q_INVOKABLE TouchEventSequenceWrapper *touchEvent(QQuickItem *item);
3737 Q_INVOKABLE void ensureTouchRegistryInstalled();
3838
3939private: