Merge lp:~ci-train-bot/unity8/unity8-ubuntu-zesty-2272 into lp:unity8

Proposed by Michał Sawicz
Status: Merged
Merged at revision: 2797
Proposed branch: lp:~ci-train-bot/unity8/unity8-ubuntu-zesty-2272
Merge into: lp:unity8
Diff against target: 10072 lines (+3585/-2466)
134 files modified
CMakeLists.txt (+1/-1)
cmake/modules/QmlTest.cmake (+8/-3)
data/com.canonical.Unity8.gschema.xml (+5/-0)
debian/changelog (+51/-0)
debian/control (+1/-1)
debian/rules (+1/-1)
plugins/LightDM/CMakeLists.txt (+1/-1)
plugins/LightDM/DBusGreeterList.cpp (+6/-6)
plugins/LightDM/DBusGreeterList.h (+2/-2)
plugins/LightDM/Greeter.cpp (+142/-28)
plugins/LightDM/Greeter.h (+17/-13)
plugins/LightDM/GreeterPrivate.h (+14/-9)
plugins/LightDM/IntegratedLightDM/liblightdm/Greeter.cpp (+7/-4)
plugins/LightDM/IntegratedLightDM/liblightdm/GreeterPrivate.cpp (+27/-22)
plugins/LightDM/IntegratedLightDM/liblightdm/GreeterPrivate.h (+1/-0)
plugins/LightDM/PromptsModel.cpp (+97/-0)
plugins/LightDM/PromptsModel.h (+70/-0)
plugins/LightDM/SessionsModel.cpp (+1/-2)
plugins/LightDM/SessionsModel.h (+4/-5)
plugins/LightDM/UsersModel.cpp (+171/-9)
plugins/LightDM/UsersModel.h (+6/-8)
plugins/LightDM/plugin.cpp (+12/-4)
plugins/Unity/Launcher/launchermodel.cpp (+0/-1)
plugins/Utils/CMakeLists.txt (+1/-0)
plugins/Utils/appdrawerproxymodel.cpp (+1/-1)
plugins/Utils/plugin.cpp (+3/-1)
plugins/Utils/tabfocusfence.cpp (+72/-0)
plugins/Utils/tabfocusfence.h (+38/-0)
po/unity8.pot (+71/-43)
qml/ApplicationMenus/MenuBar.qml (+14/-0)
qml/ApplicationMenus/MenuPopup.qml (+178/-139)
qml/Components/Dialogs.qml (+42/-3)
qml/Components/KeyboardShortcutsOverlay.qml (+1/-1)
qml/Components/KeymapSwitcher.qml (+15/-7)
qml/Components/Lockscreen.qml (+3/-1)
qml/Components/ModeSwitchWarningDialog.qml (+8/-2)
qml/Components/ShellDialog.qml (+21/-3)
qml/Greeter/Circle.qml (+1/-1)
qml/Greeter/FullLightDMImpl.qml (+1/-0)
qml/Greeter/Greeter.qml (+31/-61)
qml/Greeter/GreeterPrompt.qml (+32/-31)
qml/Greeter/IntegratedLightDMImpl.qml (+1/-0)
qml/Greeter/LightDMService.qml (+4/-4)
qml/Greeter/LoginList.qml (+46/-115)
qml/Greeter/NarrowView.qml (+11/-21)
qml/Greeter/PromptList.qml (+148/-0)
qml/Greeter/WideView.qml (+13/-22)
qml/Launcher/Drawer.qml (+86/-4)
qml/Launcher/DrawerGridView.qml (+3/-1)
qml/Launcher/DrawerListView.qml (+6/-0)
qml/Launcher/Launcher.qml (+11/-8)
qml/Launcher/LauncherDelegate.qml (+8/-9)
qml/Launcher/LauncherPanel.qml (+11/-6)
qml/Launcher/MoreAppsHeader.qml (+12/-5)
qml/Launcher/graphics/launcher-app-focus-ring.svg (+0/-12)
qml/OrientedShell.qml (+2/-1)
qml/Panel/PanelBar.qml (+1/-0)
qml/Panel/PanelMenu.qml (+14/-0)
qml/Shell.qml (+49/-5)
qml/Stage/DecoratedWindow.qml (+39/-46)
qml/Stage/WindowDecoration.qml (+6/-2)
src/MouseTouchAdaptor.cpp (+28/-1)
src/MouseTouchAdaptor.h (+1/-0)
tests/CMakeLists.txt (+5/-3)
tests/autopilot/unity8/fixture_setup.py (+1/-1)
tests/autopilot/unity8/shell/tests/__init__.py (+1/-1)
tests/mocks/AccountsService/AccountsService.cpp (+2/-3)
tests/mocks/AccountsService/AccountsService.h (+3/-6)
tests/mocks/AccountsService/CMakeLists.txt (+3/-2)
tests/mocks/CMakeLists.txt (+2/-1)
tests/mocks/GSettings.1.0/fake_gsettings.cpp (+14/-0)
tests/mocks/GSettings.1.0/fake_gsettings.h (+7/-0)
tests/mocks/LightDM/CMakeLists.txt (+0/-1)
tests/mocks/LightDM/IntegratedLightDM/CMakeLists.txt (+0/-50)
tests/mocks/LightDM/IntegratedLightDM/MockGreeter.cpp (+0/-51)
tests/mocks/LightDM/IntegratedLightDM/MockGreeter.h (+0/-42)
tests/mocks/LightDM/IntegratedLightDM/MockSessionsModel.cpp (+0/-66)
tests/mocks/LightDM/IntegratedLightDM/MockSessionsModel.h (+0/-44)
tests/mocks/LightDM/IntegratedLightDM/MockUsersModel.cpp (+0/-44)
tests/mocks/LightDM/IntegratedLightDM/MockUsersModel.h (+0/-39)
tests/mocks/LightDM/IntegratedLightDM/QLightDM/Greeter (+0/-17)
tests/mocks/LightDM/IntegratedLightDM/QLightDM/SessionsModel (+0/-17)
tests/mocks/LightDM/IntegratedLightDM/QLightDM/UsersModel (+0/-17)
tests/mocks/LightDM/IntegratedLightDM/liblightdm/GreeterPrivate.cpp (+0/-132)
tests/mocks/LightDM/IntegratedLightDM/liblightdm/GreeterPrivate.h (+0/-54)
tests/mocks/LightDM/IntegratedLightDM/liblightdm/SessionsModelPrivate.cpp (+0/-83)
tests/mocks/LightDM/IntegratedLightDM/liblightdm/SessionsModelPrivate.h (+0/-63)
tests/mocks/LightDM/IntegratedLightDM/liblightdm/UsersModelPrivate.cpp (+0/-124)
tests/mocks/LightDM/IntegratedLightDM/liblightdm/UsersModelPrivate.h (+0/-64)
tests/mocks/LightDM/IntegratedLightDM/plugin.cpp (+0/-79)
tests/mocks/LightDM/IntegratedLightDM/plugin.h (+0/-32)
tests/mocks/LightDM/IntegratedLightDM/qmldir (+0/-2)
tests/mocks/LightDMController/CMakeLists.txt (+15/-0)
tests/mocks/LightDMController/plugin.cpp (+35/-0)
tests/mocks/LightDMController/plugin.h (+29/-0)
tests/mocks/LightDMController/qmldir (+2/-0)
tests/mocks/Unity/Application/MirMock.cpp (+13/-0)
tests/mocks/Unity/Application/MirMock.h (+4/-0)
tests/mocks/Unity/Launcher/MockAppDrawerModel.cpp (+0/-1)
tests/mocks/Utils/CMakeLists.txt (+1/-0)
tests/mocks/Utils/plugin.cpp (+3/-1)
tests/mocks/liblightdm/CMakeLists.txt (+8/-11)
tests/mocks/liblightdm/MockController.cpp (+191/-0)
tests/mocks/liblightdm/MockController.h (+106/-0)
tests/mocks/liblightdm/MockGreeter.cpp (+157/-42)
tests/mocks/liblightdm/MockGreeter.h (+9/-22)
tests/mocks/liblightdm/MockSessionsModel.cpp (+47/-49)
tests/mocks/liblightdm/MockSessionsModel.h (+9/-17)
tests/mocks/liblightdm/MockUsersModel.cpp (+118/-20)
tests/mocks/liblightdm/MockUsersModel.h (+8/-16)
tests/plugins/LightDM/IntegratedLightDM/CMakeLists.txt (+37/-9)
tests/plugins/LightDM/IntegratedLightDM/dbus.cpp (+8/-12)
tests/plugins/LightDM/IntegratedLightDM/greeter.qml (+2/-7)
tests/plugins/LightDM/IntegratedLightDM/promptsmodel.cpp (+175/-0)
tests/plugins/LightDM/IntegratedLightDM/sessionsmodel.cpp (+15/-13)
tests/plugins/LightDM/IntegratedLightDM/usersmodel.cpp (+87/-16)
tests/plugins/Utils/CMakeLists.txt (+4/-1)
tests/plugins/Utils/WindowStateStorageTest.cpp (+64/-0)
tests/qmltests/ApplicationMenuDataLoader.qml (+13/-5)
tests/qmltests/ApplicationMenus/tst_MenuBar.qml (+18/-5)
tests/qmltests/ApplicationMenus/tst_MenuPopup.qml (+156/-45)
tests/qmltests/Dash/tst_DashShell.qml (+8/-40)
tests/qmltests/Greeter/TestView.qml (+7/-16)
tests/qmltests/Greeter/tst_Greeter.qml (+63/-111)
tests/qmltests/Greeter/tst_Infographics.qml (+1/-11)
tests/qmltests/Greeter/tst_NarrowView.qml (+20/-43)
tests/qmltests/Greeter/tst_WideView.qml (+125/-121)
tests/qmltests/Launcher/tst_Drawer.qml (+58/-7)
tests/qmltests/Launcher/tst_Launcher.qml (+15/-6)
tests/qmltests/Panel/tst_Panel.qml (+36/-0)
tests/qmltests/Tutorial/tst_Tutorial.qml (+3/-3)
tests/qmltests/tst_OrientedShell.qml (+90/-8)
tests/qmltests/tst_Shell.qml (+87/-81)
tests/qmltests/tst_ShellWithPin.qml (+22/-13)
To merge this branch: bzr merge lp:~ci-train-bot/unity8/unity8-ubuntu-zesty-2272
Reviewer Review Type Date Requested Status
Unity Team Pending
Review via email: mp+315584@code.launchpad.net
To post a comment you must log in.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'CMakeLists.txt'
2--- CMakeLists.txt 2017-01-10 14:46:09 +0000
3+++ CMakeLists.txt 2017-01-25 16:04:08 +0000
4@@ -70,7 +70,7 @@
5 find_package(Qt5Concurrent 5.6 REQUIRED)
6 find_package(Qt5Sql 5.6 REQUIRED)
7
8-pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=23)
9+pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=25)
10 pkg_check_modules(GEONAMES REQUIRED geonames>=0.2)
11 pkg_check_modules(GIO REQUIRED gio-2.0>=2.32)
12 pkg_check_modules(GLIB REQUIRED glib-2.0>=2.32)
13
14=== modified file 'cmake/modules/QmlTest.cmake'
15--- cmake/modules/QmlTest.cmake 2016-06-07 13:25:58 +0000
16+++ cmake/modules/QmlTest.cmake 2017-01-25 16:04:08 +0000
17@@ -264,12 +264,16 @@
18 # installed system.
19
20 function(add_meta_test TARGET_NAME)
21- cmake_parse_arguments(TEST "" "" "DEPENDS" ${ARGN})
22+ cmake_parse_arguments(TEST "SERIAL" "" "DEPENDS" ${ARGN})
23
24 add_custom_target(${TARGET_NAME})
25
26 set(filename "${CMAKE_BINARY_DIR}/tests/scripts/${TARGET_NAME}.sh")
27- file(WRITE "${filename}" "#!/usr/bin/parallel --shebang --no-notice\n\n")
28+ if(TEST_SERIAL)
29+ file(WRITE "${filename}" "#!/bin/sh\n\n")
30+ else()
31+ file(WRITE "${filename}" "#!/usr/bin/parallel --shebang --no-notice\n\n")
32+ endif()
33
34 add_meta_dependencies(${TARGET_NAME} DEPENDS ${TEST_DEPENDS})
35 # else we will write the rest of the script as we add cmake targets
36@@ -307,6 +311,7 @@
37 foreach(ONE_CMD ${TEST_COMMAND})
38 set(script "${script}'${ONE_CMD}' ")
39 endforeach()
40+ set(script "${script}\"\$@\"") # Allow passing arguments if desired
41
42 set(filename "${CMAKE_BINARY_DIR}/tests/scripts/${TARGET_NAME}.sh")
43
44@@ -370,7 +375,7 @@
45 # add depend to the meta test script that we will install on system
46 set(filename "${CMAKE_BINARY_DIR}/tests/scripts/${UPSTREAM_TARGET}.sh")
47 if (EXISTS "${filename}")
48- file(APPEND "${filename}" "${CMAKE_INSTALL_PREFIX}/${SHELL_PRIVATE_LIBDIR}/tests/scripts/${depend}.sh\n")
49+ file(APPEND "${filename}" "${CMAKE_INSTALL_PREFIX}/${SHELL_PRIVATE_LIBDIR}/tests/scripts/${depend}.sh \"\$@\" 2>&1\n")
50 endif()
51 endforeach()
52 endfunction()
53
54=== modified file 'data/com.canonical.Unity8.gschema.xml'
55--- data/com.canonical.Unity8.gschema.xml 2016-11-30 10:32:23 +0000
56+++ data/com.canonical.Unity8.gschema.xml 2017-01-25 16:04:08 +0000
57@@ -53,6 +53,11 @@
58 <summary>Enable or disable the indicator pull down menus</summary>
59 <description>Toggle the availability of the indicator pull down menus</description>
60 </key>
61+ <key type="s" name="appstore-uri">
62+ <default>'scope://com.canonical.scopes.clickstore'</default>
63+ <summary>The uri to the app store</summary>
64+ <description>This will be used whenever the user triggers an action to open the app store.</description>
65+ </key>
66 </schema>
67
68 <schema path="/com/canonical/unity8/greeter/" id="com.canonical.Unity8.Greeter" gettext-domain="unity8">
69
70=== modified file 'debian/changelog'
71--- debian/changelog 2017-01-10 14:48:43 +0000
72+++ debian/changelog 2017-01-25 16:04:08 +0000
73@@ -1,3 +1,54 @@
74+unity8 (8.15+17.04.20170124-0ubuntu1) zesty; urgency=medium
75+
76+ [ Albert Astals Cid ]
77+ * Limit tab-focus travelling on dialogs with a fence
78+ * Restore focus to where it was when our ShellDialogs get unloaded
79+ * Update current session after changing the user
80+ * Add keyboard navigation for Indicators
81+ * a window -> the current window
82+ * There's no spreadDelegate_ anymore
83+
84+ [ Daniel d'Andrada ]
85+ * Simplify DecoratedWindow
86+ * Remove unnecessary warning message
87+
88+ [ Josh Arenson ]
89+ * Add a test for the session chooser icon in the greeter's sessions
90+ list
91+
92+ [ Lukáš Tinkl ]
93+ * Fix keymap not being applied on the shell itself (LP: #1626435)
94+ * Shell dialog improvements (kbd focus, mouse eater)
95+ * Start searching directly as you type, w/o having to first
96+ focus/click the search field.
97+ * Add a test for the real implementation of WindowStateStorage
98+ * Use a four finger gesture to open the drawer, much like in u7
99+
100+ [ Michael Terry ]
101+ * Simplify the lightdm mock to make future greeter improvements easier
102+ to test.
103+ * Add support for guest sessions in unity8-greeter.
104+ * Use a model for PAM prompts, supporting more possible interactions.
105+ * Fix grouping of autopkg output and allow optionally passing
106+ arguments to installed test scripts.
107+ * Add support for LightDM hints for manual logins and hiding normal
108+ users.
109+
110+ [ Michael Zanetti ]
111+ * hint the launcher to indicate a successful size change to the user
112+ (LP: #1646457)
113+ * Improvements for the appdrawer (LP: #1648173)
114+ * Adjust home key to still focus the dash instead of messing with the
115+ drawer
116+ * allow 4 finger simulation with mousetouchadaptor
117+
118+ [ Nick Dedekind ]
119+ * Added Alt+F10 shortcut to open app menus. (LP: #1656896)
120+ * Fixed menu layout width calculations. (LP: #1657050)
121+ * Skip Panel::test_drag_indicator_item_down_shows_menu
122+
123+ -- Michał Sawicz <michal.sawicz@canonical.com> Tue, 24 Jan 2017 07:46:58 +0000
124+
125 unity8 (8.15+17.04.20170110.4-0ubuntu1) zesty; urgency=medium
126
127 [ Albert Astals Cid ]
128
129=== modified file 'debian/control'
130--- debian/control 2017-01-10 14:46:09 +0000
131+++ debian/control 2017-01-25 16:04:08 +0000
132@@ -38,7 +38,7 @@
133 libubuntugestures5-private-dev (>= 1.3.2030),
134 libudev-dev,
135 libudm-common-dev,
136- libunity-api-dev (>= 8.0),
137+ libunity-api-dev (>= 8.1),
138 libusermetricsoutput1-dev,
139 # Need those X11 libs touch emulation from mouse events in manual QML tests on a X11 desktop
140 libx11-dev[!arm64 !armhf],
141
142=== modified file 'debian/rules'
143--- debian/rules 2017-01-03 14:02:23 +0000
144+++ debian/rules 2017-01-25 16:04:08 +0000
145@@ -40,4 +40,4 @@
146 override_dh_shlibdeps:
147 # Some mock libraries link against liblightdm-qt5-3.so which we want to
148 # avoid, since we only really link against our mock one, not the system one.
149- dh_shlibdeps -XlibMockLightDM-qml.so -XlibMockAccountsService-qml.so -Lunity8-private
150+ dh_shlibdeps -XlibMockAccountsService-qml.so -Lunity8-private
151
152=== modified file 'plugins/LightDM/CMakeLists.txt'
153--- plugins/LightDM/CMakeLists.txt 2016-03-02 02:54:30 +0000
154+++ plugins/LightDM/CMakeLists.txt 2017-01-25 16:04:08 +0000
155@@ -7,7 +7,6 @@
156 ${CMAKE_CURRENT_SOURCE_DIR}
157 ${CMAKE_CURRENT_BINARY_DIR}
158 ${CMAKE_SOURCE_DIR}/plugins/Utils
159- ${CMAKE_BINARY_DIR}/tests/mocks/LightDM/IntegratedLightDM
160 ${libunity8-private_SOURCE_DIR}
161 ${LIBUSERMETRICSOUTPUT_INCLUDE_DIRS}
162 )
163@@ -18,6 +17,7 @@
164 DBusGreeterList.cpp
165 Greeter.cpp
166 plugin.cpp
167+ PromptsModel.cpp
168 SessionsModel.cpp
169 UsersModel.cpp
170 )
171
172=== modified file 'plugins/LightDM/DBusGreeterList.cpp'
173--- plugins/LightDM/DBusGreeterList.cpp 2015-10-01 23:54:24 +0000
174+++ plugins/LightDM/DBusGreeterList.cpp 2017-01-25 16:04:08 +0000
175@@ -25,7 +25,7 @@
176 m_greeter(greeter)
177 {
178 connect(m_greeter, &Greeter::authenticationUserChanged, this, &DBusGreeterList::authenticationUserChangedHandler);
179- connect(m_greeter, &Greeter::promptlessChanged, this, &DBusGreeterList::promptlessChangedHandler);
180+ connect(m_greeter, &Greeter::isAuthenticatedChanged, this, &DBusGreeterList::authenticatedChangedHandler);
181 }
182
183 QString DBusGreeterList::GetActiveEntry() const
184@@ -40,16 +40,16 @@
185
186 bool DBusGreeterList::entryIsLocked() const
187 {
188- return !m_greeter->promptless();
189+ return !m_greeter->isAuthenticated();
190 }
191
192-void DBusGreeterList::authenticationUserChangedHandler(const QString &user)
193+void DBusGreeterList::authenticationUserChangedHandler()
194 {
195- notifyPropertyChanged(QStringLiteral("ActiveEntry"), user);
196- Q_EMIT EntrySelected(user);
197+ notifyPropertyChanged(QStringLiteral("ActiveEntry"), m_greeter->authenticationUser());
198+ Q_EMIT EntrySelected(m_greeter->authenticationUser());
199 }
200
201-void DBusGreeterList::promptlessChangedHandler()
202+void DBusGreeterList::authenticatedChangedHandler()
203 {
204 notifyPropertyChanged(QStringLiteral("EntryIsLocked"), entryIsLocked());
205 Q_EMIT entryIsLockedChanged();
206
207=== modified file 'plugins/LightDM/DBusGreeterList.h'
208--- plugins/LightDM/DBusGreeterList.h 2015-09-11 13:38:45 +0000
209+++ plugins/LightDM/DBusGreeterList.h 2017-01-25 16:04:08 +0000
210@@ -47,8 +47,8 @@
211 void entryIsLockedChanged();
212
213 private Q_SLOTS:
214- void authenticationUserChangedHandler(const QString &user);
215- void promptlessChangedHandler();
216+ void authenticationUserChangedHandler();
217+ void authenticatedChangedHandler();
218
219 private:
220 Greeter *m_greeter;
221
222=== modified file 'plugins/LightDM/Greeter.cpp'
223--- plugins/LightDM/Greeter.cpp 2016-07-28 15:34:29 +0000
224+++ plugins/LightDM/Greeter.cpp 2017-01-25 16:04:08 +0000
225@@ -1,5 +1,5 @@
226 /*
227- * Copyright (C) 2013, 2015 Canonical, Ltd.
228+ * Copyright (C) 2013-2017 Canonical, Ltd.
229 *
230 * This program is free software; you can redistribute it and/or modify
231 * it under the terms of the GNU General Public License as published by
232@@ -17,13 +17,16 @@
233
234 #include "Greeter.h"
235 #include "GreeterPrivate.h"
236+#include <QCoreApplication>
237 #include <libintl.h>
238
239+static Greeter *singleton = nullptr;
240+
241 GreeterPrivate::GreeterPrivate(Greeter* parent)
242 : m_greeter(new QLightDM::Greeter(parent)),
243 m_active(false),
244- wasPrompted(false),
245- promptless(false),
246+ responded(false),
247+ everResponded(false),
248 q_ptr(parent)
249 {
250 }
251@@ -41,9 +44,32 @@
252 connect(d->m_greeter, &QLightDM::Greeter::authenticationComplete,
253 this, &Greeter::authenticationCompleteFilter);
254
255+ // Don't get stuck waiting for PAM as we shut down.
256+ connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit,
257+ d->m_greeter, &QLightDM::Greeter::cancelAuthentication);
258+
259 d->m_greeter->connectSync();
260 }
261
262+Greeter::~Greeter()
263+{
264+ singleton = nullptr;
265+}
266+
267+Greeter *Greeter::instance()
268+{
269+ if (!singleton) {
270+ singleton = new Greeter;
271+ }
272+ return singleton;
273+}
274+
275+PromptsModel *Greeter::promptsModel()
276+{
277+ Q_D(Greeter);
278+ return &d->prompts;
279+}
280+
281 bool Greeter::isActive() const
282 {
283 Q_D(const Greeter);
284@@ -68,7 +94,16 @@
285 QString Greeter::authenticationUser() const
286 {
287 Q_D(const Greeter);
288- return d->m_greeter->authenticationUser();
289+ return d->cachedAuthUser;
290+}
291+
292+void Greeter::checkAuthenticationUser()
293+{
294+ Q_D(Greeter);
295+ if (d->cachedAuthUser != d->m_greeter->authenticationUser()) {
296+ d->cachedAuthUser = d->m_greeter->authenticationUser();
297+ Q_EMIT authenticationUserChanged();
298+ }
299 }
300
301 QString Greeter::defaultSessionHint() const
302@@ -77,35 +112,64 @@
303 return d->m_greeter->defaultSessionHint();
304 }
305
306-bool Greeter::promptless() const
307-{
308- Q_D(const Greeter);
309- return d->promptless;
310-}
311-
312 QString Greeter::selectUser() const
313 {
314 Q_D(const Greeter);
315- return d->m_greeter->selectUserHint();
316+ if (hasGuestAccount() && d->m_greeter->selectGuestHint()) {
317+ return QStringLiteral("*guest");
318+ } else {
319+ return d->m_greeter->selectUserHint();
320+ }
321+}
322+
323+bool Greeter::hasGuestAccount() const
324+{
325+ Q_D(const Greeter);
326+ return d->m_greeter->hasGuestAccountHint();
327+}
328+
329+bool Greeter::showManualLoginHint() const
330+{
331+ Q_D(const Greeter);
332+ return d->m_greeter->showManualLoginHint();
333+}
334+
335+bool Greeter::hideUsersHint() const
336+{
337+ Q_D(const Greeter);
338+ return d->m_greeter->hideUsersHint();
339 }
340
341 void Greeter::authenticate(const QString &username)
342 {
343 Q_D(Greeter);
344- d->wasPrompted = false;
345- if (d->promptless) {
346- d->promptless = false;
347- Q_EMIT promptlessChanged();
348- }
349-
350- d->m_greeter->authenticate(username);
351+ d->prompts.clear();
352+ d->responded = false;
353+ d->everResponded = false;
354+
355+ if (authenticationUser() == username) {
356+ d->prompts = d->leftovers;
357+ }
358+ d->leftovers.clear();
359+
360+ if (username == QStringLiteral("*guest")) {
361+ d->m_greeter->authenticateAsGuest();
362+ } else if (username == QStringLiteral("*other")) {
363+ d->m_greeter->authenticate(nullptr);
364+ } else {
365+ d->m_greeter->authenticate(username);
366+ }
367+
368+ Q_EMIT authenticationStarted();
369 Q_EMIT isAuthenticatedChanged();
370- Q_EMIT authenticationUserChanged(username);
371+ checkAuthenticationUser();
372 }
373
374 void Greeter::respond(const QString &response)
375 {
376 Q_D(Greeter);
377+ d->responded = true;
378+ d->everResponded = true;
379 d->m_greeter->respond(response);
380 }
381
382@@ -118,32 +182,82 @@
383 void Greeter::showPromptFilter(const QString &text, QLightDM::Greeter::PromptType type)
384 {
385 Q_D(Greeter);
386- d->wasPrompted = true;
387+
388+ checkAuthenticationUser(); // may have changed in liblightdm
389
390 bool isDefaultPrompt = (text == dgettext("Linux-PAM", "Password: "));
391+ bool isSecret = type == QLightDM::Greeter::PromptTypeSecret;
392+
393+ QString trimmedText;
394+ if (!isDefaultPrompt)
395+ trimmedText = text.trimmed();
396
397 // Strip prompt of any colons at the end
398- QString trimmedText = text.trimmed();
399 if (trimmedText.endsWith(':') || trimmedText.endsWith(QStringLiteral(":"))) {
400 trimmedText.chop(1);
401 }
402
403- Q_EMIT showPrompt(trimmedText, type == QLightDM::Greeter::PromptTypeSecret, isDefaultPrompt);
404+ if (trimmedText == "login") {
405+ // 'login' is provided untranslated by LightDM when asking for a manual
406+ // login username.
407+ trimmedText = gettext("Username");
408+ }
409+
410+ if (d->responded) {
411+ d->prompts.clear();
412+ d->responded = false;
413+ }
414+
415+ d->prompts.append(trimmedText, isSecret ? PromptsModel::Secret : PromptsModel::Question);
416 }
417
418 void Greeter::showMessageFilter(const QString &text, QLightDM::Greeter::MessageType type)
419 {
420- Q_EMIT showMessage(text, type == QLightDM::Greeter::MessageTypeError);
421+ Q_D(Greeter);
422+
423+ checkAuthenticationUser(); // may have changed in liblightdm
424+
425+ bool isError = type == QLightDM::Greeter::MessageTypeError;
426+
427+ if (d->responded) {
428+ d->prompts.clear();
429+ d->responded = false;
430+ }
431+ d->prompts.append(text, isError? PromptsModel::Error : PromptsModel::Message);
432 }
433
434 void Greeter::authenticationCompleteFilter()
435 {
436 Q_D(Greeter);
437- if (!d->wasPrompted) {
438- d->promptless = true;
439- Q_EMIT promptlessChanged();
440- }
441
442 Q_EMIT isAuthenticatedChanged();
443- Q_EMIT authenticationComplete();
444+
445+ bool automatic = !d->everResponded;
446+ bool pamHasLeftoverMessages = !d->prompts.hasPrompt() && d->prompts.rowCount() > 0;
447+
448+ if (!isAuthenticated()) {
449+ if (pamHasLeftoverMessages) {
450+ d->leftovers = d->prompts; // Prefer PAM's messages
451+ } else if (automatic) {
452+ d->leftovers.append(gettext("Failed to authenticate"), PromptsModel::Error);
453+ } else {
454+ d->leftovers.append(gettext("Invalid password, please try again"), PromptsModel::Error);
455+ }
456+ } else if (pamHasLeftoverMessages) {
457+ automatic = true; // treat this successful login as automatic, so user sees message
458+ d->leftovers = d->prompts;
459+ }
460+
461+ if (automatic) {
462+ d->prompts = d->leftovers; // OK, we'll just use these now
463+ d->leftovers.clear();
464+ d->prompts.append(isAuthenticated() ? gettext("Log In") : gettext("Retry"),
465+ PromptsModel::Button);
466+ }
467+
468+ if (isAuthenticated()) {
469+ Q_EMIT loginSuccess(automatic);
470+ } else {
471+ Q_EMIT loginError(automatic);
472+ }
473 }
474
475=== modified file 'plugins/LightDM/Greeter.h'
476--- plugins/LightDM/Greeter.h 2016-07-28 15:34:29 +0000
477+++ plugins/LightDM/Greeter.h 2017-01-25 16:04:08 +0000
478@@ -1,5 +1,5 @@
479 /*
480- * Copyright (C) 2012,2013,2015 Canonical, Ltd.
481+ * Copyright (C) 2012-2016 Canonical, Ltd.
482 *
483 * This program is free software; you can redistribute it and/or modify
484 * it under the terms of the GNU General Public License as published by
485@@ -21,13 +21,13 @@
486 such edits in the future, and by inserting ourselves here, we have more
487 control. */
488
489-#ifndef UNITY_GREETER_H
490-#define UNITY_GREETER_H
491+#pragma once
492
493 #include <QLightDM/Greeter>
494 #include <QtCore/QObject>
495
496 class GreeterPrivate;
497+class PromptsModel;
498
499 class Greeter : public QObject
500 {
501@@ -37,18 +37,22 @@
502 Q_PROPERTY(bool authenticated READ isAuthenticated NOTIFY isAuthenticatedChanged)
503 Q_PROPERTY(QString authenticationUser READ authenticationUser NOTIFY authenticationUserChanged)
504 Q_PROPERTY(QString defaultSession READ defaultSessionHint CONSTANT)
505- Q_PROPERTY(bool promptless READ promptless NOTIFY promptlessChanged)
506 Q_PROPERTY(QString selectUser READ selectUser CONSTANT)
507
508 public:
509- explicit Greeter(QObject* parent=0);
510+ static Greeter *instance();
511+ virtual ~Greeter();
512
513 bool isActive() const;
514 bool isAuthenticated() const;
515 QString authenticationUser() const;
516 QString defaultSessionHint() const;
517- bool promptless() const;
518 QString selectUser() const;
519+ bool hasGuestAccount() const;
520+ bool showManualLoginHint() const;
521+ bool hideUsersHint() const;
522+
523+ PromptsModel *promptsModel();
524
525 public Q_SLOTS:
526 void authenticate(const QString &username=QString());
527@@ -57,21 +61,22 @@
528 void setIsActive(bool isActive);
529
530 Q_SIGNALS:
531- void showMessage(const QString &text, bool isError);
532- void showPrompt(const QString &text, bool isSecret, bool isDefaultPrompt);
533- void authenticationComplete();
534- void authenticationUserChanged(const QString &user);
535+ void authenticationUserChanged();
536 void isActiveChanged();
537 void isAuthenticatedChanged();
538- void promptlessChanged();
539 void showGreeter();
540 void hideGreeter();
541+ void loginError(bool automatic);
542+ void loginSuccess(bool automatic);
543+ void authenticationStarted(); // useful for testing
544
545 // This signal is emitted by external agents like indicators, and the UI
546 // should switch to this user if possible.
547 void requestAuthenticationUser(const QString &user);
548
549 protected:
550+ explicit Greeter(QObject* parent=0);
551+
552 GreeterPrivate * const d_ptr;
553
554 Q_DECLARE_PRIVATE(Greeter)
555@@ -80,6 +85,5 @@
556 void showMessageFilter(const QString &text, QLightDM::Greeter::MessageType type);
557 void showPromptFilter(const QString &text, QLightDM::Greeter::PromptType type);
558 void authenticationCompleteFilter();
559+ void checkAuthenticationUser();
560 };
561-
562-#endif
563
564=== modified file 'plugins/LightDM/GreeterPrivate.h'
565--- plugins/LightDM/GreeterPrivate.h 2015-09-11 13:38:45 +0000
566+++ plugins/LightDM/GreeterPrivate.h 2017-01-25 16:04:08 +0000
567@@ -1,5 +1,5 @@
568 /*
569- * Copyright (C) 2014 Canonical, Ltd.
570+ * Copyright (C) 2015-2017 Canonical, Ltd.
571 *
572 * This program is free software; you can redistribute it and/or modify
573 * it under the terms of the GNU General Public License as published by
574@@ -14,10 +14,14 @@
575 * along with this program. If not, see <http://www.gnu.org/licenses/>.
576 */
577
578-#ifndef UNITY_GREETER_PRIVATE_H
579-#define UNITY_GREETER_PRIVATE_H
580-
581-#include <QLightDM/Greeter>
582+#pragma once
583+
584+#include "PromptsModel.h"
585+#include <QObject>
586+
587+namespace QLightDM {
588+ class Greeter;
589+}
590
591 class GreeterPrivate
592 {
593@@ -26,8 +30,11 @@
594
595 QLightDM::Greeter *m_greeter;
596 bool m_active;
597- bool wasPrompted;
598- bool promptless;
599+ PromptsModel prompts;
600+ PromptsModel leftovers; // prompts to show during next auth for same user
601+ bool responded;
602+ bool everResponded;
603+ QString cachedAuthUser;
604
605 protected:
606 Greeter * const q_ptr;
607@@ -35,5 +42,3 @@
608 private:
609 Q_DECLARE_PUBLIC(Greeter)
610 };
611-
612-#endif // UNITY_GREETER_PRIVATE_H
613
614=== modified file 'plugins/LightDM/IntegratedLightDM/liblightdm/Greeter.cpp'
615--- plugins/LightDM/IntegratedLightDM/liblightdm/Greeter.cpp 2016-07-28 15:34:29 +0000
616+++ plugins/LightDM/IntegratedLightDM/liblightdm/Greeter.cpp 2017-01-25 16:04:08 +0000
617@@ -49,7 +49,7 @@
618
619 bool Greeter::hasGuestAccountHint() const
620 {
621- return true;
622+ return false;
623 }
624
625 QString Greeter::getHint(const QString &name) const
626@@ -70,12 +70,12 @@
627
628 bool Greeter::showManualLoginHint() const
629 {
630- return true;
631+ return false;
632 }
633
634 bool Greeter::showRemoteLoginHint() const
635 {
636- return true;
637+ return false;
638 }
639
640 QString Greeter::selectUserHint() const
641@@ -146,7 +146,10 @@
642 }
643
644 void Greeter::cancelAuthentication()
645-{}
646+{
647+ Q_D(Greeter);
648+ d->cancelAuthentication();
649+}
650
651 void Greeter::setLanguage (const QString &language)
652 {
653
654=== modified file 'plugins/LightDM/IntegratedLightDM/liblightdm/GreeterPrivate.cpp'
655--- plugins/LightDM/IntegratedLightDM/liblightdm/GreeterPrivate.cpp 2015-11-02 17:22:30 +0000
656+++ plugins/LightDM/IntegratedLightDM/liblightdm/GreeterPrivate.cpp 2017-01-25 16:04:08 +0000
657@@ -206,6 +206,28 @@
658 }
659 }
660
661+ void cancelPam()
662+ {
663+ if (pamHandle != nullptr) {
664+ QFuture<int> pamFuture = futureWatcher.future();
665+ pam_handle *handle = pamHandle;
666+ pamHandle = nullptr; // to disable normal finishPam() handling
667+ pamFuture.cancel();
668+
669+ // Note the empty loop, we just want to clear the futures queue.
670+ // Any further prompts from the pam thread will be immediately
671+ // responded to/dismissed in handlePrompt().
672+ while (respond(QString()));
673+
674+ // Now let signal/slot handling happen so the thread can finish
675+ while (!pamFuture.isFinished()) {
676+ QCoreApplication::processEvents();
677+ }
678+
679+ pam_end(handle, PAM_CONV_ERR);
680+ }
681+ }
682+
683 Q_SIGNALS:
684 void showMessage(pam_handle *handle, QString text, QLightDM::Greeter::MessageType type);
685 void showPrompt(pam_handle *handle, QString text, QLightDM::Greeter::PromptType type, QLightDM::GreeterImpl::ResponseFuture response);
686@@ -247,28 +269,6 @@
687 }
688
689 private:
690- void cancelPam()
691- {
692- if (pamHandle != nullptr) {
693- QFuture<int> pamFuture = futureWatcher.future();
694- pam_handle *handle = pamHandle;
695- pamHandle = nullptr; // to disable normal finishPam() handling
696- pamFuture.cancel();
697-
698- // Note the empty loop, we just want to clear the futures queue.
699- // Any further prompts from the pam thread will be immediately
700- // responded to/dismissed in handlePrompt().
701- while (respond(QString()));
702-
703- // Now let signal/slot handling happen so the thread can finish
704- while (!pamFuture.isFinished()) {
705- QCoreApplication::processEvents();
706- }
707-
708- pam_end(handle, PAM_CONV_ERR);
709- }
710- }
711-
712 Greeter *greeter;
713 GreeterPrivate *greeterPrivate;
714 pam_handle* pamHandle;
715@@ -299,6 +299,11 @@
716 m_impl->respond(response);
717 }
718
719+void GreeterPrivate::cancelAuthentication()
720+{
721+ m_impl->cancelPam();
722+}
723+
724 }
725
726 #include "GreeterPrivate.moc"
727
728=== modified file 'plugins/LightDM/IntegratedLightDM/liblightdm/GreeterPrivate.h'
729--- plugins/LightDM/IntegratedLightDM/liblightdm/GreeterPrivate.h 2015-09-11 13:38:45 +0000
730+++ plugins/LightDM/IntegratedLightDM/liblightdm/GreeterPrivate.h 2017-01-25 16:04:08 +0000
731@@ -38,6 +38,7 @@
732
733 void handleAuthenticate();
734 void handleRespond(const QString &response);
735+ void cancelAuthentication();
736
737 protected:
738 GreeterImpl *m_impl; // if the backend needs more private data
739
740=== added file 'plugins/LightDM/PromptsModel.cpp'
741--- plugins/LightDM/PromptsModel.cpp 1970-01-01 00:00:00 +0000
742+++ plugins/LightDM/PromptsModel.cpp 2017-01-25 16:04:08 +0000
743@@ -0,0 +1,97 @@
744+/*
745+ * Copyright (C) 2017 Canonical, Ltd.
746+ *
747+ * This program is free software; you can redistribute it and/or modify
748+ * it under the terms of the GNU General Public License as published by
749+ * the Free Software Foundation; version 3.
750+ *
751+ * This program is distributed in the hope that it will be useful,
752+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
753+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
754+ * GNU General Public License for more details.
755+ *
756+ * You should have received a copy of the GNU General Public License
757+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
758+ *
759+ */
760+
761+#include "PromptsModel.h"
762+
763+PromptsModel::PromptsModel(QObject* parent)
764+ : QAbstractListModel(parent)
765+{
766+ m_roleNames[TypeRole] = "type";
767+ m_roleNames[TextRole] = "text";
768+}
769+
770+PromptsModel& PromptsModel::operator=(const PromptsModel &other)
771+{
772+ beginResetModel();
773+ m_prompts = other.m_prompts;
774+ endResetModel();
775+ Q_EMIT countChanged();
776+ return *this;
777+}
778+
779+int PromptsModel::rowCount(const QModelIndex &parent) const
780+{
781+ if (parent.isValid())
782+ return 0;
783+
784+ return m_prompts.size();
785+}
786+
787+QVariant PromptsModel::data(const QModelIndex &index, int role) const
788+{
789+ if (!index.isValid() || index.column() > 0 || index.row() >= m_prompts.size())
790+ return QVariant();
791+
792+ switch (role) {
793+ case Qt::DisplayRole: // fallthrough
794+ case TextRole: return m_prompts[index.row()].prompt;
795+ case TypeRole: return m_prompts[index.row()].type;
796+ default: return QVariant();
797+ }
798+}
799+
800+QHash<int, QByteArray> PromptsModel::roleNames() const
801+{
802+ return m_roleNames;
803+}
804+
805+void PromptsModel::prepend(const QString &text, PromptType type)
806+{
807+ beginInsertRows(QModelIndex(), 0, 0);
808+ m_prompts.prepend(PromptInfo{text, type});
809+ endInsertRows();
810+
811+ Q_EMIT countChanged();
812+}
813+
814+void PromptsModel::append(const QString &text, PromptType type)
815+{
816+ beginInsertRows(QModelIndex(), m_prompts.size(), m_prompts.size());
817+ m_prompts.append(PromptInfo{text, type});
818+ endInsertRows();
819+
820+ Q_EMIT countChanged();
821+}
822+
823+void PromptsModel::clear()
824+{
825+ beginResetModel();
826+ m_prompts.clear();
827+ endResetModel();
828+
829+ Q_EMIT countChanged();
830+}
831+
832+bool PromptsModel::hasPrompt() const
833+{
834+ Q_FOREACH(const PromptInfo &info, m_prompts) {
835+ if (info.type == PromptType::Secret || info.type == PromptType::Question) {
836+ return true;
837+ }
838+ }
839+ return false;
840+}
841
842=== added file 'plugins/LightDM/PromptsModel.h'
843--- plugins/LightDM/PromptsModel.h 1970-01-01 00:00:00 +0000
844+++ plugins/LightDM/PromptsModel.h 2017-01-25 16:04:08 +0000
845@@ -0,0 +1,70 @@
846+/*
847+ * Copyright (C) 2017 Canonical, Ltd.
848+ *
849+ * This program is free software; you can redistribute it and/or modify
850+ * it under the terms of the GNU General Public License as published by
851+ * the Free Software Foundation; version 3.
852+ *
853+ * This program is distributed in the hope that it will be useful,
854+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
855+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
856+ * GNU General Public License for more details.
857+ *
858+ * You should have received a copy of the GNU General Public License
859+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
860+ *
861+ */
862+
863+#pragma once
864+
865+#include <QAbstractListModel>
866+
867+class PromptsModel : public QAbstractListModel
868+{
869+ Q_OBJECT
870+
871+ Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
872+
873+public:
874+ enum PromptsModelRoles {
875+ TypeRole = Qt::UserRole,
876+ TextRole,
877+ };
878+ Q_ENUM(PromptsModelRoles)
879+
880+ enum PromptType {
881+ Message,
882+ Error,
883+ Secret,
884+ Question,
885+ Button,
886+ };
887+ Q_ENUM(PromptType)
888+
889+ explicit PromptsModel(QObject* parent=0);
890+
891+ PromptsModel& operator=(const PromptsModel &other);
892+
893+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
894+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
895+ QHash<int, QByteArray> roleNames() const override;
896+
897+ Q_INVOKABLE void prepend(const QString &text, PromptType type);
898+ Q_INVOKABLE void append(const QString &text, PromptType type);
899+
900+ void clear();
901+
902+ bool hasPrompt() const;
903+
904+Q_SIGNALS:
905+ void countChanged();
906+
907+private:
908+ struct PromptInfo {
909+ QString prompt;
910+ PromptType type;
911+ };
912+
913+ QList<PromptInfo> m_prompts;
914+ QHash<int, QByteArray> m_roleNames;
915+};
916
917=== modified file 'plugins/LightDM/SessionsModel.cpp'
918--- plugins/LightDM/SessionsModel.cpp 2016-10-07 17:24:55 +0000
919+++ plugins/LightDM/SessionsModel.cpp 2017-01-25 16:04:08 +0000
920@@ -1,5 +1,5 @@
921 /*
922- * Copyright (C) 2015 Canonical, Ltd.
923+ * Copyright (C) 2015-2016 Canonical, Ltd.
924 *
925 * This program is free software; you can redistribute it and/or modify
926 * it under the terms of the GNU General Public License as published by
927@@ -128,7 +128,6 @@
928 }
929
930 setModel(m_model);
931- setSourceModel(m_model);
932 setSortCaseSensitivity(Qt::CaseInsensitive);
933 setSortLocaleAware(true);
934 setSortRole(Qt::DisplayRole);
935
936=== modified file 'plugins/LightDM/SessionsModel.h'
937--- plugins/LightDM/SessionsModel.h 2016-12-23 11:04:53 +0000
938+++ plugins/LightDM/SessionsModel.h 2017-01-25 16:04:08 +0000
939@@ -1,5 +1,5 @@
940 /*
941- * Copyright (C) 2015 Canonical, Ltd.
942+ * Copyright (C) 2015-2016 Canonical, Ltd.
943 *
944 * This program is free software; you can redistribute it and/or modify
945 * it under the terms of the GNU General Public License as published by
946@@ -15,8 +15,7 @@
947 *
948 */
949
950-#ifndef UNITY_SESSIONSMODEL_H
951-#define UNITY_SESSIONSMODEL_H
952+#pragma once
953
954 #include <unitysortfilterproxymodelqml.h>
955 #include <QLightDM/SessionsModel>
956@@ -30,6 +29,7 @@
957
958 Q_PROPERTY(QList<QUrl> iconSearchDirectories READ iconSearchDirectories
959 WRITE setIconSearchDirectories NOTIFY iconSearchDirectoriesChanged)
960+
961 Q_SIGNALS:
962 void iconSearchDirectoriesChanged();
963
964@@ -54,6 +54,7 @@
965 Q_INVOKABLE QUrl iconUrl(const QString sessionName) const;
966
967 void setIconSearchDirectories(const QList<QUrl> searchDirectories);
968+
969 private:
970 QLightDM::SessionsModel* m_model;
971 QHash<int, QByteArray> m_roleNames;
972@@ -63,5 +64,3 @@
973 QUrl("/usr/share/unity-greeter/")};
974
975 };
976-
977-#endif // UNITY_SESSIONSMODEL_H
978
979=== modified file 'plugins/LightDM/UsersModel.cpp'
980--- plugins/LightDM/UsersModel.cpp 2015-10-26 20:15:08 +0000
981+++ plugins/LightDM/UsersModel.cpp 2017-01-25 16:04:08 +0000
982@@ -1,5 +1,5 @@
983 /*
984- * Copyright (C) 2013 Canonical, Ltd.
985+ * Copyright (C) 2013,2015-2016 Canonical, Ltd.
986 *
987 * This program is free software; you can redistribute it and/or modify
988 * it under the terms of the GNU General Public License as published by
989@@ -12,20 +12,22 @@
990 *
991 * You should have received a copy of the GNU General Public License
992 * along with this program. If not, see <http://www.gnu.org/licenses/>.
993- *
994- * Author: Michael Terry <michael.terry@canonical.com>
995 */
996
997+#include "Greeter.h"
998 #include "UsersModel.h"
999+#include <QIdentityProxyModel>
1000 #include <QLightDM/UsersModel>
1001-#include <QtCore/QSortFilterProxyModel>
1002+
1003+#include <libintl.h>
1004
1005 // First, we define an internal class that wraps LightDM's UsersModel. This
1006 // class will modify some of the data coming from LightDM. For example, we
1007-// modify any empty Real Names into just normal Names.
1008+// modify any empty Real Names into just normal Names. We also add optional
1009+// rows, depending on configuration.
1010 // (We can't modify the data directly in UsersModel below because it won't sort
1011 // using the modified data.)
1012-class MangleModel : public QSortFilterProxyModel
1013+class MangleModel : public QIdentityProxyModel
1014 {
1015 Q_OBJECT
1016
1017@@ -33,21 +35,75 @@
1018 explicit MangleModel(QObject* parent=0);
1019
1020 QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
1021+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
1022+ QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
1023+
1024+private:
1025+ struct CustomRow {
1026+ QString name;
1027+ QString realName;
1028+ };
1029+
1030+ int sourceRowCount() const;
1031+
1032+ void updateGuestRow();
1033+ void updateManualRow();
1034+ void updateCustomRows();
1035+
1036+ void addCustomRow(const CustomRow &newRow);
1037+ void removeCustomRow(const QString &rowName);
1038+
1039+ QList<CustomRow> m_customRows;
1040+ bool m_updatingCustomRows;
1041 };
1042
1043 MangleModel::MangleModel(QObject* parent)
1044- : QSortFilterProxyModel(parent)
1045+ : QIdentityProxyModel(parent)
1046+ , m_updatingCustomRows(false)
1047 {
1048 setSourceModel(new QLightDM::UsersModel(this));
1049+
1050+ updateCustomRows();
1051+
1052+ // Would be nice if there were a rowCountChanged signal in the base class.
1053+ // We redo all custom rows on any row count change, because (A) some of
1054+ // custom rows (manual login) use row count information and (B) when
1055+ // testing, we use a modelReset signal as a way to indicate that a custom
1056+ // row has been toggled off or on.
1057+ connect(this, &QIdentityProxyModel::modelReset,
1058+ this, &MangleModel::updateCustomRows);
1059+ connect(this, &QIdentityProxyModel::rowsInserted,
1060+ this, &MangleModel::updateCustomRows);
1061+ connect(this, &QIdentityProxyModel::rowsRemoved,
1062+ this, &MangleModel::updateCustomRows);
1063 }
1064
1065 QVariant MangleModel::data(const QModelIndex &index, int role) const
1066 {
1067- QVariant variantData = QSortFilterProxyModel::data(index, role);
1068+ QVariant variantData;
1069+
1070+ if (index.row() >= rowCount())
1071+ return QVariant();
1072+
1073+ bool isCustomRow = index.row() >= sourceRowCount();
1074+ if (isCustomRow && index.column() == 0) {
1075+ int customIndex = index.row() - sourceRowCount();
1076+ if (role == QLightDM::UsersModel::NameRole) {
1077+ variantData = m_customRows[customIndex].name;
1078+ } else if (role == QLightDM::UsersModel::RealNameRole) {
1079+ variantData = m_customRows[customIndex].realName;
1080+ } else if (role == QLightDM::UsersModel::LoggedInRole) {
1081+ variantData = false;
1082+ } else if (role == QLightDM::UsersModel::SessionRole) {
1083+ variantData = Greeter::instance()->defaultSessionHint();
1084+ }
1085+ } else {
1086+ variantData = QIdentityProxyModel::data(index, role);
1087+ }
1088
1089 // If user's real name is empty, switch to unix name
1090 if (role == QLightDM::UsersModel::RealNameRole && variantData.toString().isEmpty()) {
1091- variantData = QSortFilterProxyModel::data(index, QLightDM::UsersModel::NameRole);
1092+ variantData = data(index, QLightDM::UsersModel::NameRole);
1093 } else if (role == QLightDM::UsersModel::BackgroundPathRole && variantData.toString().startsWith('#')) {
1094 const QString stringData = "data:image/svg+xml,<svg><rect width='100%' height='100%' fill='" + variantData.toString() + "'/></svg>";
1095 variantData = stringData;
1096@@ -56,6 +112,95 @@
1097 return variantData;
1098 }
1099
1100+void MangleModel::addCustomRow(const CustomRow &newRow)
1101+{
1102+ for (int i = 0; i < m_customRows.size(); i++) {
1103+ if (m_customRows[i].name == newRow.name) {
1104+ return; // we don't have custom rows that change content yet
1105+ }
1106+ }
1107+
1108+ beginInsertRows(QModelIndex(), rowCount(), rowCount());
1109+ m_customRows << newRow;
1110+ endInsertRows();
1111+}
1112+
1113+void MangleModel::removeCustomRow(const QString &rowName)
1114+{
1115+ for (int i = 0; i < m_customRows.size(); i++) {
1116+ if (m_customRows[i].name == rowName) {
1117+ int rowNum = sourceRowCount() + i;
1118+ beginRemoveRows(QModelIndex(), rowNum, rowNum);
1119+ m_customRows.removeAt(i);
1120+ endRemoveRows();
1121+ break;
1122+ }
1123+ }
1124+}
1125+
1126+void MangleModel::updateManualRow()
1127+{
1128+ bool hasAnotherEntry = sourceRowCount() > 0;
1129+ for (int i = 0; !hasAnotherEntry && i < m_customRows.size(); i++) {
1130+ if (m_customRows[i].name != QStringLiteral("*other")) {
1131+ hasAnotherEntry = true;
1132+ }
1133+ }
1134+
1135+ // Show manual login if we are asked to OR if no other entry exists
1136+ if (Greeter::instance()->showManualLoginHint() || !hasAnotherEntry)
1137+ addCustomRow({QStringLiteral("*other"), gettext("Login")});
1138+ else
1139+ removeCustomRow(QStringLiteral("*other"));
1140+}
1141+
1142+void MangleModel::updateGuestRow()
1143+{
1144+ if (Greeter::instance()->hasGuestAccount())
1145+ addCustomRow({QStringLiteral("*guest"), gettext("Guest Session")});
1146+ else
1147+ removeCustomRow(QStringLiteral("*guest"));
1148+}
1149+
1150+void MangleModel::updateCustomRows()
1151+{
1152+ // We update when rowCount changes, but we also insert/remove rows here.
1153+ // So guard this function to avoid recursion.
1154+ if (m_updatingCustomRows)
1155+ return;
1156+
1157+ m_updatingCustomRows = true;
1158+ updateGuestRow();
1159+ updateManualRow();
1160+ m_updatingCustomRows = false;
1161+}
1162+
1163+int MangleModel::rowCount(const QModelIndex &parent) const
1164+{
1165+ if (parent.isValid())
1166+ return 0;
1167+ else
1168+ return sourceRowCount() + m_customRows.size();
1169+}
1170+
1171+int MangleModel::sourceRowCount() const
1172+{
1173+ return Greeter::instance()->hideUsersHint() ? 0 : sourceModel()->rowCount();
1174+}
1175+
1176+QModelIndex MangleModel::index(int row, int column, const QModelIndex &parent) const
1177+{
1178+ if (row >= rowCount())
1179+ return QModelIndex();
1180+
1181+ bool isCustomRow = row >= sourceRowCount();
1182+ if (isCustomRow && !parent.isValid()) {
1183+ return createIndex(row, column);
1184+ } else {
1185+ return QIdentityProxyModel::index(row, column, parent);
1186+ }
1187+}
1188+
1189 // **** Now we continue with actual UsersModel class ****
1190
1191 UsersModel::UsersModel(QObject* parent)
1192@@ -68,4 +213,21 @@
1193 sort(0);
1194 }
1195
1196+bool UsersModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const
1197+{
1198+ auto leftName = source_left.data(QLightDM::UsersModel::NameRole);
1199+ auto rightName = source_right.data(QLightDM::UsersModel::NameRole);
1200+
1201+ if (leftName == QStringLiteral("*guest"))
1202+ return false;
1203+ if (rightName == QStringLiteral("*guest"))
1204+ return true;
1205+ if (leftName == QStringLiteral("*other"))
1206+ return false;
1207+ if (rightName == QStringLiteral("*other"))
1208+ return true;
1209+
1210+ return UnitySortFilterProxyModelQML::lessThan(source_left, source_right);
1211+}
1212+
1213 #include "UsersModel.moc"
1214
1215=== modified file 'plugins/LightDM/UsersModel.h'
1216--- plugins/LightDM/UsersModel.h 2015-09-11 13:38:45 +0000
1217+++ plugins/LightDM/UsersModel.h 2017-01-25 16:04:08 +0000
1218@@ -1,5 +1,5 @@
1219 /*
1220- * Copyright (C) 2012,2013 Canonical, Ltd.
1221+ * Copyright (C) 2012-2013,2015-2016 Canonical, Ltd.
1222 *
1223 * This program is free software; you can redistribute it and/or modify
1224 * it under the terms of the GNU General Public License as published by
1225@@ -12,19 +12,16 @@
1226 *
1227 * You should have received a copy of the GNU General Public License
1228 * along with this program. If not, see <http://www.gnu.org/licenses/>.
1229- *
1230- * Authors: Michael Terry <michael.terry@canonical.com>
1231 */
1232
1233 /* This class is a really tiny filter around QLightDM::UsersModel. There are
1234 some operations that we want to edit a bit for the benefit of Qml.
1235 Specifically, we want to sort users according to realName. */
1236
1237-#ifndef UNITY_USERSMODEL_H
1238-#define UNITY_USERSMODEL_H
1239+#pragma once
1240
1241 #include <unitysortfilterproxymodelqml.h>
1242-#include <QtCore/QObject>
1243+#include <QObject>
1244
1245 class UsersModel : public UnitySortFilterProxyModelQML
1246 {
1247@@ -32,6 +29,7 @@
1248
1249 public:
1250 explicit UsersModel(QObject* parent=0);
1251+
1252+protected:
1253+ bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const override;
1254 };
1255-
1256-#endif
1257
1258=== modified file 'plugins/LightDM/plugin.cpp'
1259--- plugins/LightDM/plugin.cpp 2016-06-16 17:23:24 +0000
1260+++ plugins/LightDM/plugin.cpp 2017-01-25 16:04:08 +0000
1261@@ -19,6 +19,7 @@
1262 #include "DBusGreeter.h"
1263 #include "DBusGreeterList.h"
1264 #include "Greeter.h"
1265+#include "PromptsModel.h"
1266 #include "SessionsModel.h"
1267 #include "UsersModel.h"
1268 #include <libusermetricsoutput/ColorTheme.h>
1269@@ -32,16 +33,24 @@
1270
1271 static QObject *greeter_provider(QQmlEngine *engine, QJSEngine *scriptEngine)
1272 {
1273- Q_UNUSED(engine)
1274 Q_UNUSED(scriptEngine)
1275
1276- Greeter *greeter = new Greeter();
1277+ Greeter *greeter = Greeter::instance();
1278 new DBusGreeter(greeter, QStringLiteral("/"));
1279 new DBusGreeterList(greeter, QStringLiteral("/list"));
1280
1281+ engine->setObjectOwnership(greeter, QQmlEngine::CppOwnership);
1282+
1283 return greeter;
1284 }
1285
1286+static QObject *prompts_provider(QQmlEngine *engine, QJSEngine *)
1287+{
1288+ auto model = Greeter::instance()->promptsModel();
1289+ engine->setObjectOwnership(model, QQmlEngine::CppOwnership);
1290+ return model;
1291+}
1292+
1293 static QObject *sessions_provider(QQmlEngine *engine, QJSEngine *scriptEngine)
1294 {
1295 Q_UNUSED(engine)
1296@@ -78,8 +87,7 @@
1297 #error No library defined in LightDM plugin
1298 #endif
1299
1300- qRegisterMetaType<QLightDM::Greeter::MessageType>("QLightDM::Greeter::MessageType");
1301- qRegisterMetaType<QLightDM::Greeter::PromptType>("QLightDM::Greeter::PromptType");
1302+ qmlRegisterSingletonType<PromptsModel>(uri, 0, 1, "Prompts", prompts_provider);
1303
1304 qmlRegisterSingletonType<SessionsModel>(uri, 0, 1, "Sessions", sessions_provider);
1305 qmlRegisterUncreatableType<QLightDM::SessionsModel>(uri, 0, 1, "SessionRoles", QStringLiteral("Type is not instantiable"));
1306
1307=== modified file 'plugins/Unity/Launcher/launchermodel.cpp'
1308--- plugins/Unity/Launcher/launchermodel.cpp 2016-11-01 18:18:56 +0000
1309+++ plugins/Unity/Launcher/launchermodel.cpp 2017-01-25 16:04:08 +0000
1310@@ -214,7 +214,6 @@
1311 void LauncherModel::setUser(const QString &username)
1312 {
1313 Q_UNUSED(username)
1314- qWarning() << "This backend doesn't support multiple users";
1315 }
1316
1317 QString LauncherModel::getUrlForAppId(const QString &appId) const
1318
1319=== modified file 'plugins/Utils/CMakeLists.txt'
1320--- plugins/Utils/CMakeLists.txt 2016-12-07 13:43:25 +0000
1321+++ plugins/Utils/CMakeLists.txt 2017-01-25 16:04:08 +0000
1322@@ -34,6 +34,7 @@
1323 deviceconfigparser.cpp
1324 globalfunctions.cpp
1325 URLDispatcher.cpp
1326+ tabfocusfence.cpp
1327 plugin.cpp
1328 )
1329
1330
1331=== modified file 'plugins/Utils/appdrawerproxymodel.cpp'
1332--- plugins/Utils/appdrawerproxymodel.cpp 2016-12-23 11:04:53 +0000
1333+++ plugins/Utils/appdrawerproxymodel.cpp 2017-01-25 16:04:08 +0000
1334@@ -185,5 +185,5 @@
1335 return adpm->appId(sourceIndex.row());
1336 }
1337 }
1338- return nullptr;
1339+ return QString();
1340 }
1341
1342=== modified file 'plugins/Utils/plugin.cpp'
1343--- plugins/Utils/plugin.cpp 2016-11-01 18:18:56 +0000
1344+++ plugins/Utils/plugin.cpp 2017-01-25 16:04:08 +0000
1345@@ -1,5 +1,5 @@
1346 /*
1347- * Copyright (C) 2012-2015 Canonical, Ltd.
1348+ * Copyright (C) 2012-2017 Canonical, Ltd.
1349 *
1350 * This program is free software; you can redistribute it and/or modify
1351 * it under the terms of the GNU General Public License as published by
1352@@ -40,6 +40,7 @@
1353 #include "globalfunctions.h"
1354 #include "URLDispatcher.h"
1355 #include "appdrawerproxymodel.h"
1356+#include "tabfocusfence.h"
1357
1358 static QObject *createWindowStateStorage(QQmlEngine *engine, QJSEngine *scriptEngine)
1359 {
1360@@ -84,4 +85,5 @@
1361 qmlRegisterSingletonType<GlobalFunctions>(uri, 0, 1, "Functions", createGlobalFunctions);
1362 qmlRegisterType<URLDispatcher>(uri, 0, 1, "URLDispatcher");
1363 qmlRegisterType<AppDrawerProxyModel>(uri, 0, 1, "AppDrawerProxyModel");
1364+ qmlRegisterType<TabFocusFenceItem>(uri, 0, 1, "TabFocusFence");
1365 }
1366
1367=== added file 'plugins/Utils/tabfocusfence.cpp'
1368--- plugins/Utils/tabfocusfence.cpp 1970-01-01 00:00:00 +0000
1369+++ plugins/Utils/tabfocusfence.cpp 2017-01-25 16:04:08 +0000
1370@@ -0,0 +1,72 @@
1371+/*
1372+ * Copyright 2017 Canonical Ltd.
1373+ *
1374+ * This program is free software; you can redistribute it and/or modify
1375+ * it under the terms of the GNU Lesser General Public License as published by
1376+ * the Free Software Foundation; version 3.
1377+ *
1378+ * This program is distributed in the hope that it will be useful,
1379+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1380+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1381+ * GNU Lesser General Public License for more details.
1382+ *
1383+ * You should have received a copy of the GNU Lesser General Public License
1384+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1385+*/
1386+
1387+#include "tabfocusfence.h"
1388+
1389+#include <private/qquickitem_p.h>
1390+
1391+TabFocusFenceItem::TabFocusFenceItem(QQuickItem *parent) : QQuickItem(parent)
1392+{
1393+ QQuickItemPrivate *d = QQuickItemPrivate::get(this);
1394+ d->isTabFence = true;
1395+ setFlag(ItemIsFocusScope);
1396+}
1397+
1398+bool TabFocusFenceItem::focusNext()
1399+{
1400+ QQuickItem * current = scopedFocusItem();
1401+ if (current) {
1402+ QQuickItem * next = current->nextItemInFocusChain(true);
1403+ if (next) {
1404+ next->setFocus(true, Qt::TabFocusReason);
1405+ return true;
1406+ }
1407+ }
1408+ return false;
1409+}
1410+
1411+bool TabFocusFenceItem::focusPrev()
1412+{
1413+ QQuickItem * current = scopedFocusItem();
1414+ if (current) {
1415+ QQuickItem * prev = current->nextItemInFocusChain(false);
1416+ if (prev) {
1417+ prev->setFocus(true, Qt::BacktabFocusReason);
1418+ return true;
1419+ }
1420+ }
1421+ return false;
1422+}
1423+
1424+void TabFocusFenceItem::keyPressEvent(QKeyEvent *event)
1425+{
1426+ // Needed so we eat Tab keys when there's only one item inside the fence
1427+ if (event->key() == Qt::Key_Tab) {
1428+ event->accept();
1429+ } else {
1430+ QQuickItem::keyPressEvent(event);
1431+ }
1432+}
1433+
1434+void TabFocusFenceItem::keyReleaseEvent(QKeyEvent *event)
1435+{
1436+ // Needed so we eat Tab keys when there's only one item inside the fence
1437+ if (event->key() == Qt::Key_Tab) {
1438+ event->accept();
1439+ } else {
1440+ QQuickItem::keyReleaseEvent(event);
1441+ }
1442+}
1443
1444=== added file 'plugins/Utils/tabfocusfence.h'
1445--- plugins/Utils/tabfocusfence.h 1970-01-01 00:00:00 +0000
1446+++ plugins/Utils/tabfocusfence.h 2017-01-25 16:04:08 +0000
1447@@ -0,0 +1,38 @@
1448+/*
1449+ * Copyright 2017 Canonical Ltd.
1450+ *
1451+ * This program is free software; you can redistribute it and/or modify
1452+ * it under the terms of the GNU Lesser General Public License as published by
1453+ * the Free Software Foundation; version 3.
1454+ *
1455+ * This program is distributed in the hope that it will be useful,
1456+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1457+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1458+ * GNU Lesser General Public License for more details.
1459+ *
1460+ * You should have received a copy of the GNU Lesser General Public License
1461+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1462+*/
1463+
1464+#ifndef TABFOCUSFENCE_H
1465+#define TABFOCUSFENCE_H
1466+
1467+#include <QQuickItem>
1468+
1469+// An item that restricts focus Tab travelling
1470+// to its children
1471+class TabFocusFenceItem : public QQuickItem
1472+{
1473+Q_OBJECT
1474+
1475+public:
1476+ TabFocusFenceItem(QQuickItem *parent = nullptr);
1477+
1478+ Q_INVOKABLE bool focusNext();
1479+ Q_INVOKABLE bool focusPrev();
1480+
1481+ void keyPressEvent(QKeyEvent *event) override;
1482+ void keyReleaseEvent(QKeyEvent *event) override;
1483+};
1484+
1485+#endif
1486
1487=== modified file 'po/unity8.pot'
1488--- po/unity8.pot 2017-01-10 14:48:43 +0000
1489+++ po/unity8.pot 2017-01-25 16:04:08 +0000
1490@@ -8,7 +8,7 @@
1491 msgstr ""
1492 "Project-Id-Version: unity8\n"
1493 "Report-Msgid-Bugs-To: \n"
1494-"POT-Creation-Date: 2017-01-10 14:48+0000\n"
1495+"POT-Creation-Date: 2017-01-24 07:46+0000\n"
1496 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
1497 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
1498 "Language-Team: LANGUAGE <LL@li.org>\n"
1499@@ -18,10 +18,38 @@
1500 "Content-Transfer-Encoding: 8bit\n"
1501 "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
1502
1503-#: plugins/LightDM/Greeter.cpp:123
1504+#: plugins/LightDM/Greeter.cpp:188
1505 msgid "Password: "
1506 msgstr ""
1507
1508+#: plugins/LightDM/Greeter.cpp:203
1509+msgid "Username"
1510+msgstr ""
1511+
1512+#: plugins/LightDM/Greeter.cpp:242
1513+msgid "Failed to authenticate"
1514+msgstr ""
1515+
1516+#: plugins/LightDM/Greeter.cpp:244
1517+msgid "Invalid password, please try again"
1518+msgstr ""
1519+
1520+#: plugins/LightDM/Greeter.cpp:254
1521+msgid "Log In"
1522+msgstr ""
1523+
1524+#: plugins/LightDM/Greeter.cpp:254
1525+msgid "Retry"
1526+msgstr ""
1527+
1528+#: plugins/LightDM/UsersModel.cpp:152
1529+msgid "Login"
1530+msgstr ""
1531+
1532+#: plugins/LightDM/UsersModel.cpp:160
1533+msgid "Guest Session"
1534+msgstr ""
1535+
1536 #: plugins/Unity/Launcher/launcheritem.cpp:50
1537 #: plugins/Unity/Launcher/launcheritem.cpp:122
1538 msgid "Pin shortcut"
1539@@ -35,66 +63,66 @@
1540 msgid "Unpin shortcut"
1541 msgstr ""
1542
1543-#: qml/Components/Dialogs.qml:149
1544+#: qml/Components/Dialogs.qml:177
1545 msgctxt "Title: Lock/Log out dialog"
1546 msgid "Log out"
1547 msgstr ""
1548
1549-#: qml/Components/Dialogs.qml:150
1550+#: qml/Components/Dialogs.qml:178
1551 msgid "Are you sure you want to log out?"
1552 msgstr ""
1553
1554-#: qml/Components/Dialogs.qml:152
1555+#: qml/Components/Dialogs.qml:181
1556 msgctxt "Button: Lock the system"
1557 msgid "Lock"
1558 msgstr ""
1559
1560-#: qml/Components/Dialogs.qml:160
1561+#: qml/Components/Dialogs.qml:191
1562 msgctxt "Button: Log out from the system"
1563 msgid "Log Out"
1564 msgstr ""
1565
1566-#: qml/Components/Dialogs.qml:167 qml/Components/Dialogs.qml:225
1567-#: qml/Dash/DashPageHeader.qml:324 qml/Greeter/NarrowView.qml:232
1568+#: qml/Components/Dialogs.qml:199 qml/Components/Dialogs.qml:264
1569+#: qml/Dash/DashPageHeader.qml:324 qml/Greeter/NarrowView.qml:222
1570 #: qml/Wizard/Pages/passcode-confirm.qml:32
1571 #: qml/Wizard/Pages/passcode-set.qml:32
1572 msgid "Cancel"
1573 msgstr ""
1574
1575-#: qml/Components/Dialogs.qml:179
1576+#: qml/Components/Dialogs.qml:211
1577 msgctxt "Title: Reboot dialog"
1578 msgid "Reboot"
1579 msgstr ""
1580
1581-#: qml/Components/Dialogs.qml:180
1582+#: qml/Components/Dialogs.qml:212
1583 msgid "Are you sure you want to reboot?"
1584 msgstr ""
1585
1586-#: qml/Components/Dialogs.qml:182
1587+#: qml/Components/Dialogs.qml:215
1588 msgid "No"
1589 msgstr ""
1590
1591-#: qml/Components/Dialogs.qml:189
1592+#: qml/Components/Dialogs.qml:223
1593 msgid "Yes"
1594 msgstr ""
1595
1596-#: qml/Components/Dialogs.qml:204
1597+#: qml/Components/Dialogs.qml:239
1598 msgctxt "Title: Power off/Restart dialog"
1599 msgid "Power"
1600 msgstr ""
1601
1602-#: qml/Components/Dialogs.qml:205
1603+#: qml/Components/Dialogs.qml:240
1604 msgid ""
1605 "Are you sure you would like\n"
1606 "to power off?"
1607 msgstr ""
1608
1609-#: qml/Components/Dialogs.qml:208
1610+#: qml/Components/Dialogs.qml:244
1611 msgctxt "Button: Power off the system"
1612 msgid "Power off"
1613 msgstr ""
1614
1615-#: qml/Components/Dialogs.qml:217
1616+#: qml/Components/Dialogs.qml:255
1617 msgctxt "Button: Restart the system"
1618 msgid "Restart"
1619 msgstr ""
1620@@ -120,7 +148,7 @@
1621 msgstr ""
1622
1623 #: qml/Components/KeyboardShortcutsOverlay.qml:80
1624-msgid "Takes a screenshot of a window."
1625+msgid "Takes a screenshot of the current window."
1626 msgstr ""
1627
1628 #: qml/Components/KeyboardShortcutsOverlay.qml:88
1629@@ -267,7 +295,7 @@
1630 msgid "Closes the current window."
1631 msgstr ""
1632
1633-#: qml/Components/Lockscreen.qml:212 qml/Greeter/NarrowView.qml:252
1634+#: qml/Components/Lockscreen.qml:212 qml/Greeter/NarrowView.qml:242
1635 msgid "Return to Call"
1636 msgstr ""
1637
1638@@ -275,7 +303,7 @@
1639 msgid "Emergency Call"
1640 msgstr ""
1641
1642-#: qml/Components/Lockscreen.qml:244
1643+#: qml/Components/Lockscreen.qml:245
1644 msgid "OK"
1645 msgstr ""
1646
1647@@ -289,29 +317,29 @@
1648 msgid "%1:%2"
1649 msgstr ""
1650
1651-#: qml/Components/ModeSwitchWarningDialog.qml:32
1652+#: qml/Components/ModeSwitchWarningDialog.qml:33
1653 msgid "Apps may have unsaved data:"
1654 msgstr ""
1655
1656-#: qml/Components/ModeSwitchWarningDialog.qml:57
1657+#: qml/Components/ModeSwitchWarningDialog.qml:60
1658 msgctxt ""
1659 "Re-dock means connect the device again to an external screen/mouse/keyboard"
1660 msgid "Re-dock, save your work and close these apps to continue."
1661 msgstr ""
1662
1663-#: qml/Components/ModeSwitchWarningDialog.qml:63
1664+#: qml/Components/ModeSwitchWarningDialog.qml:67
1665 msgid "Or force close now (unsaved data will be lost)."
1666 msgstr ""
1667
1668-#: qml/Components/ModeSwitchWarningDialog.qml:75
1669+#: qml/Components/ModeSwitchWarningDialog.qml:80
1670 msgid "OK, I will reconnect"
1671 msgstr ""
1672
1673-#: qml/Components/ModeSwitchWarningDialog.qml:76
1674+#: qml/Components/ModeSwitchWarningDialog.qml:81
1675 msgid "Reconnect now!"
1676 msgstr ""
1677
1678-#: qml/Components/ModeSwitchWarningDialog.qml:88
1679+#: qml/Components/ModeSwitchWarningDialog.qml:94
1680 msgid "Close all"
1681 msgstr ""
1682
1683@@ -461,44 +489,44 @@
1684 msgstr[0] ""
1685 msgstr[1] ""
1686
1687-#: qml/Greeter/Greeter.qml:592
1688+#: qml/Greeter/Greeter.qml:561
1689 msgid "Try again"
1690 msgstr ""
1691
1692-#: qml/Greeter/LoginList.qml:70
1693+#: qml/Greeter/Greeter.qml:562
1694+msgid "Enter passphrase to unlock"
1695+msgstr ""
1696+
1697+#: qml/Greeter/Greeter.qml:563
1698+msgid "Enter passcode to unlock"
1699+msgstr ""
1700+
1701+#: qml/Greeter/NarrowView.qml:242
1702+msgid "Emergency"
1703+msgstr ""
1704+
1705+#: qml/Greeter/PromptList.qml:126
1706 msgid "Passphrase"
1707 msgstr ""
1708
1709-#: qml/Greeter/LoginList.qml:71
1710+#: qml/Greeter/PromptList.qml:126
1711 msgid "Passcode"
1712 msgstr ""
1713
1714-#: qml/Greeter/LoginList.qml:98
1715-msgid "Retry"
1716-msgstr ""
1717-
1718-#: qml/Greeter/LoginList.qml:99
1719-msgid "Log In"
1720-msgstr ""
1721-
1722-#: qml/Greeter/NarrowView.qml:252
1723-msgid "Emergency"
1724-msgstr ""
1725-
1726 #: qml/Greeter/SessionsList.qml:122
1727 msgid "Select desktop environment"
1728 msgstr ""
1729
1730-#: qml/Launcher/Drawer.qml:80
1731+#: qml/Launcher/Drawer.qml:92
1732 msgid "Search…"
1733 msgstr ""
1734
1735-#: qml/Launcher/Drawer.qml:101
1736+#: qml/Launcher/Drawer.qml:123
1737 msgctxt "Apps sorted alphabetically"
1738 msgid "A-Z"
1739 msgstr ""
1740
1741-#: qml/Launcher/MoreAppsHeader.qml:32
1742+#: qml/Launcher/MoreAppsHeader.qml:39
1743 msgid "More apps in the store"
1744 msgstr ""
1745
1746
1747=== modified file 'qml/ApplicationMenus/MenuBar.qml'
1748--- qml/ApplicationMenus/MenuBar.qml 2017-01-09 15:26:05 +0000
1749+++ qml/ApplicationMenus/MenuBar.qml 2017-01-25 16:04:08 +0000
1750@@ -57,6 +57,20 @@
1751 onReleased: d.stopSHortcutTimer()
1752 }
1753
1754+ GlobalShortcut {
1755+ shortcut: Qt.AltModifier | Qt.Key_F10
1756+ active: enableKeyFilter && d.currentItem == null
1757+ onTriggered: {
1758+ for (var i = 0; i < rowRepeater.count; i++) {
1759+ var item = rowRepeater.itemAt(i);
1760+ if (item.enabled) {
1761+ item.show();
1762+ break;
1763+ }
1764+ }
1765+ }
1766+ }
1767+
1768 InverseMouseArea {
1769 acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton
1770 anchors.fill: parent
1771
1772=== modified file 'qml/ApplicationMenus/MenuPopup.qml'
1773--- qml/ApplicationMenus/MenuPopup.qml 2017-01-09 15:26:05 +0000
1774+++ qml/ApplicationMenus/MenuPopup.qml 2017-01-25 16:04:08 +0000
1775@@ -26,19 +26,7 @@
1776 objectName: "menu"
1777 backgroundColor: theme.palette.normal.overlay
1778
1779- property alias unityMenuModel: listView.model
1780-
1781- readonly property real __ajustedMinimumHeight: {
1782- if (listView.contentHeight > __minimumHeight) {
1783- return units.gu(30);
1784- }
1785- return Math.max(listView.contentHeight, units.gu(2));
1786- }
1787-
1788- readonly property real __minimumWidth: units.gu(20)
1789- readonly property real __minimumHeight: units.gu(30)
1790- readonly property real __maximumWidth: Screen.width * 0.7
1791- readonly property real __maximumHeight: Screen.height * 0.7
1792+ property alias unityMenuModel: repeater.model
1793
1794 function show() {
1795 visible = true;
1796@@ -63,18 +51,23 @@
1797 d.dismissAll();
1798 }
1799
1800- implicitWidth: container.width
1801- implicitHeight: MathUtils.clamp(listView.contentHeight, __ajustedMinimumHeight, __maximumHeight)
1802+ implicitWidth: focusScope.width
1803+ implicitHeight: focusScope.height
1804
1805 MenuNavigator {
1806 id: d
1807 objectName: "d"
1808- itemView: listView
1809+ itemView: repeater
1810
1811 property Item currentItem: null
1812 property Item hoveredItem: null
1813 readonly property int currentIndex: currentItem ? currentItem.__ownIndex : -1
1814
1815+ property real __minimumWidth: units.gu(20)
1816+ property real __maximumWidth: Screen.width * 0.7
1817+ property real __minimumHeight: units.gu(2)
1818+ property real __maximumHeight: Screen.height * 0.7
1819+
1820 signal dismissAll()
1821
1822 onCurrentItemChanged: {
1823@@ -86,13 +79,26 @@
1824 }
1825
1826 onSelect: {
1827- currentItem = listView.itemAt(index);
1828+ currentItem = repeater.itemAt(index);
1829+ if (currentItem) {
1830+ if (currentItem.y < listView.contentY) {
1831+ listView.contentY = currentItem.y;
1832+ } else if (currentItem.y + currentItem.height > listView.contentY + listView.height) {
1833+ listView.contentY = currentItem.y + currentItem.height - listView.height;
1834+ }
1835+ }
1836 }
1837 }
1838
1839+ MouseArea {
1840+ // Eat events.
1841+ anchors.fill: parent
1842+ }
1843+
1844 Item {
1845 id: focusScope
1846- anchors.fill: parent
1847+ width: container.width
1848+ height: container.height
1849 focus: visible
1850
1851 Keys.onUpPressed: d.selectPrevious(d.currentIndex)
1852@@ -108,17 +114,17 @@
1853 id: container
1854 objectName: "container"
1855
1856- width: listView.contentWidth
1857- height: parent.height
1858+ height: MathUtils.clamp(listView.contentHeight, d.__minimumHeight, d.__maximumHeight)
1859+ width: menuColumn.width
1860 spacing: 0
1861
1862- // FIXME use ListView.header - tried but was flaky with positionViewAtIndex.
1863+ // Header - scroll up
1864 Item {
1865- Layout.fillWidth: true;
1866- Layout.maximumHeight: units.gu(3)
1867- Layout.minimumHeight: units.gu(3)
1868+ Layout.fillWidth: true
1869+ height: units.gu(3)
1870 visible: listView.contentHeight > root.height
1871 enabled: !listView.atYBeginning
1872+ z: 1
1873
1874 Rectangle {
1875 color: enabled ? theme.palette.normal.overlayText :
1876@@ -143,47 +149,35 @@
1877 MouseArea {
1878 anchors.fill: parent
1879 onPressed: {
1880- var index = listView.indexAt(0, listView.contentY);
1881- listView.positionViewAtIndex(index-1, ListView.Beginning);
1882+ var item = menuColumn.childAt(0, listView.contentY);
1883+ if (item) {
1884+ var previousItem = item;
1885+ do {
1886+ previousItem = repeater.itemAt(previousItem.__ownIndex-1);
1887+ if (!previousItem) {
1888+ listView.contentY = 0;
1889+ return;
1890+ }
1891+ } while (previousItem.__isSeparator);
1892+
1893+ listView.contentY = previousItem.y
1894+ }
1895 }
1896 }
1897 }
1898
1899- ListView {
1900+ // Menu Items
1901+ Flickable {
1902 id: listView
1903- objectName: "listView"
1904+ clip: interactive
1905+
1906 Layout.fillHeight: true
1907 Layout.fillWidth: true
1908- contentWidth: MathUtils.clamp(contentItem.childrenRect.width,
1909- __minimumWidth,
1910- __maximumWidth)
1911-
1912- orientation: Qt.Vertical
1913- interactive: contentHeight > height
1914- clip: interactive
1915- highlightFollowsCurrentItem: false
1916-
1917- highlight: Rectangle {
1918- color: "transparent"
1919- border.width: units.dp(1)
1920- border.color: UbuntuColors.orange
1921- z: 1
1922-
1923- width: listView.width
1924- height: d.currentItem ? d.currentItem.height : 0
1925- y: d.currentItem ? d.currentItem.y : 0
1926- visible: d.currentItem
1927- }
1928-
1929- function itemAt(index) {
1930- if (index > count || index < 0) return null;
1931- currentIndex = index;
1932- return currentItem;
1933- }
1934+ contentHeight: menuColumn.height
1935+ interactive: height < contentHeight
1936
1937 MouseArea {
1938- id: menuMouseArea
1939- anchors.fill: listView
1940+ anchors.fill: parent
1941 hoverEnabled: true
1942 propagateComposedEvents: true // propogate events so we send clicks to children.
1943 z: 1 // on top so we override any other hovers
1944@@ -195,7 +189,7 @@
1945
1946 if (!d.hoveredItem || !d.currentItem ||
1947 !d.hoveredItem.contains(Qt.point(pos.x - d.currentItem.x, pos.y - d.currentItem.y))) {
1948- d.hoveredItem = listView.itemAt(listView.indexAt(pos.x, pos.y));
1949+ d.hoveredItem = menuColumn.childAt(pos.x, pos.y)
1950 if (!d.hoveredItem || !d.hoveredItem.enabled)
1951 return false;
1952 d.currentItem = d.hoveredItem;
1953@@ -216,89 +210,123 @@
1954 }
1955 }
1956
1957- delegate: Loader {
1958- id: loader
1959- objectName: root.objectName + "-item" + __ownIndex
1960-
1961- property int __ownIndex: index
1962-
1963- width: root.width
1964- enabled: model.isSeparator ? false : model.sensitive
1965-
1966- sourceComponent: {
1967- if (model.isSeparator) {
1968- return separatorComponent;
1969- }
1970- return menuItemComponent;
1971- }
1972-
1973- property Item popup: null
1974-
1975- Component {
1976- id: menuItemComponent
1977- MenuItem {
1978- id: menuItem
1979- menuData: model
1980- objectName: loader.objectName + "-actionItem"
1981-
1982- action.onTriggered: {
1983- d.currentItem = loader;
1984-
1985- if (hasSubmenu) {
1986- if (!popup) {
1987- var model = root.unityMenuModel.submenu(__ownIndex);
1988- popup = submenuComponent.createObject(focusScope, {
1989- objectName: loader.objectName + "-",
1990- unityMenuModel: model,
1991- x: Qt.binding(function() { return root.width }),
1992- y: Qt.binding(function() { return loader.y })
1993- });
1994- } else if (popup) {
1995- popup.visible = true;
1996- }
1997- popup.retreat.connect(function() {
1998- popup.destroy();
1999- popup = null;
2000- menuItem.forceActiveFocus();
2001- })
2002- } else {
2003- root.unityMenuModel.activate(__ownIndex);
2004- }
2005- }
2006-
2007- Connections {
2008- target: d
2009- onCurrentIndexChanged: {
2010- if (popup && d.currentIndex != __ownIndex) {
2011- popup.visible = false;
2012- }
2013- }
2014- onDismissAll: {
2015- if (popup) {
2016- popup.destroy();
2017- popup = null;
2018- }
2019- }
2020- }
2021- }
2022- }
2023-
2024- Component {
2025- id: separatorComponent
2026- ListItems.ThinDivider {
2027- objectName: loader.objectName + "-separator"
2028- }
2029- }
2030- }
2031- } // ListView
2032-
2033- // FIXME use ListView.footer - tried but was flaky with positionViewAtIndex.
2034+ ColumnLayout {
2035+ id: menuColumn
2036+ spacing: 0
2037+
2038+ width: MathUtils.clamp(implicitWidth, d.__minimumWidth, d.__maximumWidth)
2039+
2040+ Repeater {
2041+ id: repeater
2042+
2043+ Loader {
2044+ id: loader
2045+ objectName: root.objectName + "-item" + __ownIndex
2046+
2047+ property int __ownIndex: index
2048+ property bool __isSeparator: model.isSeparator
2049+
2050+ enabled: __isSeparator ? false : model.sensitive
2051+
2052+ sourceComponent: {
2053+ if (model.isSeparator) {
2054+ return separatorComponent;
2055+ }
2056+ return menuItemComponent;
2057+ }
2058+
2059+ property Item popup: null
2060+
2061+ Layout.fillWidth: true
2062+
2063+ Component {
2064+ id: menuItemComponent
2065+ MenuItem {
2066+ id: menuItem
2067+ menuData: model
2068+ objectName: loader.objectName + "-actionItem"
2069+
2070+ width: MathUtils.clamp(implicitWidth, d.__minimumWidth, d.__maximumWidth)
2071+
2072+ action.onTriggered: {
2073+ d.currentItem = loader;
2074+
2075+ if (hasSubmenu) {
2076+ if (!popup) {
2077+ var model = root.unityMenuModel.submenu(__ownIndex);
2078+ popup = submenuComponent.createObject(focusScope, {
2079+ objectName: loader.objectName + "-",
2080+ unityMenuModel: model,
2081+ x: Qt.binding(function() { return root.width }),
2082+ y: Qt.binding(function() {
2083+ var dummy = listView.contentY; // force a recalc on contentY change.
2084+ return mapToItem(container, 0, y).y;
2085+ })
2086+ });
2087+ } else if (popup) {
2088+ popup.visible = true;
2089+ }
2090+ popup.retreat.connect(function() {
2091+ popup.destroy();
2092+ popup = null;
2093+ menuItem.forceActiveFocus();
2094+ })
2095+ } else {
2096+ root.unityMenuModel.activate(__ownIndex);
2097+ }
2098+ }
2099+
2100+ Connections {
2101+ target: d
2102+ onCurrentIndexChanged: {
2103+ if (popup && d.currentIndex != __ownIndex) {
2104+ popup.visible = false;
2105+ }
2106+ }
2107+ onDismissAll: {
2108+ if (popup) {
2109+ popup.destroy();
2110+ popup = null;
2111+ }
2112+ }
2113+ }
2114+ }
2115+ }
2116+
2117+ Component {
2118+ id: separatorComponent
2119+ ListItems.ThinDivider {
2120+ objectName: loader.objectName + "-separator"
2121+ implicitHeight: units.dp(2)
2122+ }
2123+ }
2124+ }
2125+
2126+ }
2127+ }
2128+
2129+ // Highlight
2130+ Rectangle {
2131+ color: "transparent"
2132+ border.width: units.dp(1)
2133+ border.color: UbuntuColors.orange
2134+ z: 1
2135+
2136+ width: listView.width
2137+ height: d.currentItem ? d.currentItem.height : 0
2138+ y: d.currentItem ? d.currentItem.y : 0
2139+ visible: d.currentItem
2140+ }
2141+
2142+ } // Flickable
2143+
2144+ // Header - scroll down
2145 Item {
2146- Layout.fillWidth: true;
2147- Layout.maximumHeight: units.gu(3)
2148- Layout.minimumHeight: units.gu(3)
2149+ Layout.fillWidth: true
2150+ height: units.gu(3)
2151 visible: listView.contentHeight > root.height
2152 enabled: !listView.atYEnd
2153+ z: 1
2154
2155 Rectangle {
2156 color: enabled ? theme.palette.normal.overlayText :
2157@@ -323,8 +351,19 @@
2158 MouseArea {
2159 anchors.fill: parent
2160 onPressed: {
2161- var index = listView.indexAt(0, listView.contentY);
2162- listView.positionViewAtIndex(index+1, ListView.Beginning);
2163+ var item = menuColumn.childAt(0, listView.contentY + listView.height);
2164+ if (item) {
2165+ var nextItem = item;
2166+ do {
2167+ nextItem = repeater.itemAt(nextItem.__ownIndex+1);
2168+ if (!nextItem) {
2169+ listView.contentY = listView.contentHeight - listView.height;
2170+ return;
2171+ }
2172+ } while (nextItem.__isSeparator);
2173+
2174+ listView.contentY = nextItem.y - listView.height
2175+ }
2176 }
2177 }
2178 }
2179
2180=== modified file 'qml/Components/Dialogs.qml'
2181--- qml/Components/Dialogs.qml 2016-12-20 08:55:23 +0000
2182+++ qml/Components/Dialogs.qml 2017-01-25 16:04:08 +0000
2183@@ -1,5 +1,5 @@
2184 /*
2185- * Copyright (C) 2014-2016 Canonical, Ltd.
2186+ * Copyright (C) 2014-2017 Canonical, Ltd.
2187 *
2188 * This program is free software; you can redistribute it and/or modify
2189 * it under the terms of the GNU General Public License as published by
2190@@ -24,10 +24,13 @@
2191 import Utils 0.1
2192 import "../Greeter"
2193
2194-Item {
2195+MouseArea {
2196 id: root
2197+ acceptedButtons: Qt.AllButtons
2198+ hoverEnabled: true
2199+ onWheel: wheel.accepted = true
2200
2201- readonly property alias hasActiveDialog: dialogLoader.active
2202+ readonly property bool hasActiveDialog: dialogLoader.active || d.modeSwitchWarningPopup
2203
2204 // to be set from outside, useful mostly for testing purposes
2205 property var unitySessionService: DBusUnitySessionService
2206@@ -42,6 +45,7 @@
2207 }
2208 property string usageScenario
2209 property size screenSize: Qt.size(Screen.width, Screen.height)
2210+ property bool hasKeyboard: false
2211
2212 signal powerOffClicked();
2213
2214@@ -120,6 +124,11 @@
2215 onTriggered: LightDMService.greeter.showGreeter()
2216 }
2217
2218+ GlobalShortcut { // lock screen
2219+ shortcut: Qt.MetaModifier|Qt.Key_L
2220+ onTriggered: LightDMService.greeter.showGreeter()
2221+ }
2222+
2223 QtObject {
2224 id: d // private stuff
2225 objectName: "dialogsPrivate"
2226@@ -140,6 +149,25 @@
2227 objectName: "dialogLoader"
2228 anchors.fill: parent
2229 active: false
2230+ onActiveChanged: {
2231+ if (!active) {
2232+ if (previousFocusedItem) {
2233+ previousFocusedItem.forceActiveFocus(Qt.OtherFocusReason);
2234+ previousFocusedItem = undefined;
2235+ }
2236+ previousSourceComponent = undefined;
2237+ sourceComponent = undefined;
2238+ }
2239+ }
2240+ onSourceComponentChanged: {
2241+ if (previousSourceComponent !== sourceComponent) {
2242+ previousSourceComponent = sourceComponent;
2243+ previousFocusedItem = window.activeFocusItem;
2244+ }
2245+ }
2246+
2247+ property var previousSourceComponent: undefined
2248+ property var previousFocusedItem: undefined
2249 }
2250
2251 Component {
2252@@ -149,13 +177,16 @@
2253 title: i18n.ctr("Title: Lock/Log out dialog", "Log out")
2254 text: i18n.tr("Are you sure you want to log out?")
2255 Button {
2256+ width: parent.width
2257 text: i18n.ctr("Button: Lock the system", "Lock")
2258 onClicked: {
2259 LightDMService.greeter.showGreeter()
2260 logoutDialog.hide();
2261 }
2262+ Component.onCompleted: if (root.hasKeyboard) forceActiveFocus(Qt.TabFocusReason)
2263 }
2264 Button {
2265+ width: parent.width
2266 focus: true
2267 text: i18n.ctr("Button: Log out from the system", "Log Out")
2268 onClicked: {
2269@@ -164,6 +195,7 @@
2270 }
2271 }
2272 Button {
2273+ width: parent.width
2274 text: i18n.tr("Cancel")
2275 onClicked: {
2276 logoutDialog.hide();
2277@@ -179,12 +211,14 @@
2278 title: i18n.ctr("Title: Reboot dialog", "Reboot")
2279 text: i18n.tr("Are you sure you want to reboot?")
2280 Button {
2281+ width: parent.width
2282 text: i18n.tr("No")
2283 onClicked: {
2284 rebootDialog.hide();
2285 }
2286 }
2287 Button {
2288+ width: parent.width
2289 focus: true
2290 text: i18n.tr("Yes")
2291 onClicked: {
2292@@ -193,6 +227,7 @@
2293 rebootDialog.hide();
2294 }
2295 color: theme.palette.normal.negative
2296+ Component.onCompleted: if (root.hasKeyboard) forceActiveFocus(Qt.TabFocusReason)
2297 }
2298 }
2299 }
2300@@ -204,6 +239,7 @@
2301 title: i18n.ctr("Title: Power off/Restart dialog", "Power")
2302 text: i18n.tr("Are you sure you would like\nto power off?")
2303 Button {
2304+ width: parent.width
2305 focus: true
2306 text: i18n.ctr("Button: Power off the system", "Power off")
2307 onClicked: {
2308@@ -212,8 +248,10 @@
2309 root.powerOffClicked();
2310 }
2311 color: theme.palette.normal.negative
2312+ Component.onCompleted: if (root.hasKeyboard) forceActiveFocus(Qt.TabFocusReason)
2313 }
2314 Button {
2315+ width: parent.width
2316 text: i18n.ctr("Button: Restart the system", "Restart")
2317 onClicked: {
2318 root.closeAllApps();
2319@@ -222,6 +260,7 @@
2320 }
2321 }
2322 Button {
2323+ width: parent.width
2324 text: i18n.tr("Cancel")
2325 onClicked: {
2326 powerDialog.hide();
2327
2328=== modified file 'qml/Components/KeyboardShortcutsOverlay.qml'
2329--- qml/Components/KeyboardShortcutsOverlay.qml 2016-11-29 10:35:21 +0000
2330+++ qml/Components/KeyboardShortcutsOverlay.qml 2017-01-25 16:04:08 +0000
2331@@ -77,7 +77,7 @@
2332 font.weight: Font.Medium
2333 }
2334 Label {
2335- text: i18n.tr("Takes a screenshot of a window.")
2336+ text: i18n.tr("Takes a screenshot of the current window.")
2337 fontSize: "small"
2338 font.weight: Font.Light
2339 wrapMode: Text.Wrap
2340
2341=== modified file 'qml/Components/KeymapSwitcher.qml'
2342--- qml/Components/KeymapSwitcher.qml 2016-11-30 19:24:02 +0000
2343+++ qml/Components/KeymapSwitcher.qml 2017-01-25 16:04:08 +0000
2344@@ -51,6 +51,9 @@
2345 nextIndex = currentKeymapIndex + 1;
2346 }
2347 currentKeymapIndex = nextIndex;
2348+ if (actionGroup.currentAction.valid) {
2349+ actionGroup.currentAction.updateState(currentKeymapIndex);
2350+ }
2351 }
2352
2353 function previousKeymap() {
2354@@ -60,30 +63,35 @@
2355 prevIndex = currentKeymapIndex - 1;
2356 }
2357 currentKeymapIndex = prevIndex;
2358+ if (actionGroup.currentAction.valid) {
2359+ actionGroup.currentAction.updateState(currentKeymapIndex);
2360+ }
2361 }
2362
2363- property Binding surfaceKeymapBinding: Binding {
2364+ property Binding surfaceKeymapBinding: Binding { // NB: needed mainly for xmir & libertine apps
2365 target: root.focusedSurface
2366 property: "keymap"
2367 value: root.currentKeymap
2368 }
2369
2370+ property Binding unityKeymapBinding: Binding {
2371+ target: Mir
2372+ property: "currentKeymap"
2373+ value: root.currentKeymap
2374+ }
2375+
2376 // indicator
2377 property QDBusActionGroup actionGroup: QDBusActionGroup {
2378 busType: DBus.SessionBus
2379 busName: "com.canonical.indicator.keyboard"
2380 objectPath: "/com/canonical/indicator/keyboard"
2381
2382- property variant currentAction: action("current")
2383- property variant activeAction: action("active")
2384+ property variant currentAction: action("current") // the one that's checked by the indicator
2385+ property variant activeAction: action("active") // the one that we clicked
2386
2387 Component.onCompleted: actionGroup.start();
2388 }
2389
2390- onCurrentKeymapIndexChanged: {
2391- actionGroup.currentAction.updateState(currentKeymapIndex);
2392- }
2393-
2394 readonly property int activeActionState: actionGroup.activeAction.valid ? actionGroup.activeAction.state : -1
2395
2396 onActiveActionStateChanged: {
2397
2398=== modified file 'qml/Components/Lockscreen.qml'
2399--- qml/Components/Lockscreen.qml 2016-08-30 14:06:47 +0000
2400+++ qml/Components/Lockscreen.qml 2017-01-25 16:04:08 +0000
2401@@ -1,5 +1,5 @@
2402 /*
2403- * Copyright (C) 2013 Canonical, Ltd.
2404+ * Copyright (C) 2013-2017 Canonical, Ltd.
2405 *
2406 * This program is free software; you can redistribute it and/or modify
2407 * it under the terms of the GNU General Public License as published by
2408@@ -240,8 +240,10 @@
2409 property var dialogLoader // dummy to satisfy ShellDialog's context dependent prop
2410
2411 Button {
2412+ width: parent.width
2413 objectName: "infoPopupOkButton"
2414 text: i18n.tr("OK")
2415+ focus: true
2416 onClicked: {
2417 PopupUtils.close(dialog)
2418 root.infoPopupConfirmed();
2419
2420=== modified file 'qml/Components/ModeSwitchWarningDialog.qml'
2421--- qml/Components/ModeSwitchWarningDialog.qml 2016-05-17 20:46:51 +0000
2422+++ qml/Components/ModeSwitchWarningDialog.qml 2017-01-25 16:04:08 +0000
2423@@ -1,5 +1,5 @@
2424 /*
2425- * Copyright (C) 2015 Canonical, Ltd.
2426+ * Copyright (C) 2015-2017 Canonical, Ltd.
2427 *
2428 * This program is free software; you can redistribute it and/or modify
2429 * it under the terms of the GNU General Public License as published by
2430@@ -29,6 +29,7 @@
2431 signal forceClose();
2432
2433 Label {
2434+ width: parent.width
2435 text: i18n.tr("Apps may have unsaved data:")
2436 fontSize: "large"
2437 color: "#5D5D5D"
2438@@ -37,6 +38,7 @@
2439 Repeater {
2440 id: appRepeater
2441 RowLayout {
2442+ width: parent.width
2443 spacing: units.gu(2)
2444 Image {
2445 Layout.preferredHeight: units.gu(2)
2446@@ -54,20 +56,23 @@
2447 }
2448
2449 Label {
2450+ width: parent.width
2451 text: i18n.ctr("Re-dock means connect the device again to an external screen/mouse/keyboard", "Re-dock, save your work and close these apps to continue.")
2452 wrapMode: Text.WordWrap
2453 color: "#888888"
2454 }
2455
2456 Label {
2457+ width: parent.width
2458 text: i18n.tr("Or force close now (unsaved data will be lost).")
2459 wrapMode: Text.WordWrap
2460 color: "#888888"
2461 }
2462
2463- ThinDivider {}
2464+ ThinDivider { width: parent.width }
2465
2466 RowLayout {
2467+ width: parent.width
2468 Label {
2469 objectName: "reconnectLabel"
2470 Layout.fillWidth: true
2471@@ -84,6 +89,7 @@
2472 }
2473
2474 Button {
2475+ focus: true
2476 objectName: "forceCloseButton"
2477 text: i18n.tr("Close all")
2478 color: theme.palette.normal.negative
2479
2480=== modified file 'qml/Components/ShellDialog.qml'
2481--- qml/Components/ShellDialog.qml 2016-03-29 03:47:39 +0000
2482+++ qml/Components/ShellDialog.qml 2017-01-25 16:04:08 +0000
2483@@ -1,5 +1,5 @@
2484 /*
2485- * Copyright (C) 2014 Canonical, Ltd.
2486+ * Copyright (C) 2014-2017 Canonical, Ltd.
2487 *
2488 * This program is free software; you can redistribute it and/or modify
2489 * it under the terms of the GNU General Public License as published by
2490@@ -19,6 +19,7 @@
2491 import Ubuntu.Components 1.3
2492 import Ubuntu.Components.Themes 1.3
2493 import Ubuntu.Components.Popups 1.3
2494+import Utils 0.1
2495
2496 /*
2497 A Dialog configured for use as a proper in-scene Dialog
2498@@ -32,10 +33,10 @@
2499 // NB: PopupBase, Dialog's superclass, will check for the existence of this property
2500 property bool reparentToRootItem: false
2501
2502+ default property alias columnContents: column.data
2503+
2504 onVisibleChanged: { if (!visible && dialogLoader) { dialogLoader.active = false; } }
2505
2506- Keys.onEscapePressed: hide()
2507-
2508 focus: true
2509
2510 // FIXME: this is a hack because Dialog subtheming seems broken atm
2511@@ -50,4 +51,21 @@
2512 __foreground.theme = themeHack
2513 show();
2514 }
2515+
2516+ TabFocusFence {
2517+ width: parent.width
2518+ height: column.height
2519+ focus: true
2520+ Column {
2521+ id: column
2522+ width: parent.width
2523+ spacing: units.gu(2)
2524+ }
2525+ Keys.onDownPressed: {
2526+ event.accepted = focusNext();
2527+ }
2528+ Keys.onUpPressed: {
2529+ event.accepted = focusPrev();
2530+ }
2531+ }
2532 }
2533
2534=== modified file 'qml/Greeter/Circle.qml'
2535--- qml/Greeter/Circle.qml 2016-08-30 14:10:12 +0000
2536+++ qml/Greeter/Circle.qml 2017-01-25 16:04:08 +0000
2537@@ -27,7 +27,7 @@
2538 onCenterCircleChanged: requestPaint()
2539
2540 onPaint: {
2541- if (circleScale <= 0) {
2542+ if (circleScale <= 0 || width <= 0 || height <= 0) {
2543 return;
2544 }
2545
2546
2547=== modified file 'qml/Greeter/FullLightDMImpl.qml'
2548--- qml/Greeter/FullLightDMImpl.qml 2015-11-19 21:47:32 +0000
2549+++ qml/Greeter/FullLightDMImpl.qml 2017-01-25 16:04:08 +0000
2550@@ -22,6 +22,7 @@
2551
2552 property var greeter: LightDM.Greeter
2553 property var infographic: LightDM.Infographic
2554+ property var prompts: LightDM.Prompts
2555 property var sessions: LightDM.Sessions
2556 property var sessionRoles: LightDM.SessionRoles
2557 property var users: LightDM.Users
2558
2559=== modified file 'qml/Greeter/Greeter.qml'
2560--- qml/Greeter/Greeter.qml 2016-11-29 00:13:45 +0000
2561+++ qml/Greeter/Greeter.qml 2017-01-25 16:04:08 +0000
2562@@ -74,11 +74,11 @@
2563 forcedUnlock = false;
2564 if (required) {
2565 if (loader.item) {
2566- loader.item.reset(true /* forceShow */);
2567+ loader.item.forceShow();
2568 }
2569 // Normally loader.onLoaded will select a user, but if we're
2570 // already shown, do it manually.
2571- d.selectUser(d.currentIndex, false);
2572+ d.selectUser(d.currentIndex);
2573 }
2574
2575 // Even though we may already be shown, we want to call show() for its
2576@@ -154,7 +154,7 @@
2577 readonly property bool multiUser: LightDMService.users.count > 1
2578 readonly property int selectUserIndex: d.getUserIndex(LightDMService.greeter.selectUser)
2579 property int currentIndex: Math.max(selectUserIndex, 0)
2580- property bool waiting
2581+ readonly property bool waiting: LightDMService.prompts.count == 0 && !root.forcedUnlock
2582 property bool isLockscreen // true when we are locking an active session, rather than first user login
2583 readonly property bool secureFingerprint: isLockscreen &&
2584 AccountsService.failedFingerprintLogins <
2585@@ -189,13 +189,9 @@
2586 return -1;
2587 }
2588
2589- function selectUser(index, reset) {
2590+ function selectUser(index) {
2591 if (index < 0 || index >= LightDMService.users.count)
2592 return;
2593- d.waiting = true;
2594- if (reset) {
2595- loader.item.reset(false /* forceShow */);
2596- }
2597 currentIndex = index;
2598 var user = LightDMService.users.data(index, LightDMService.userRoles.NameRole);
2599 AccountsService.user = user;
2600@@ -206,20 +202,17 @@
2601 function hideView() {
2602 if (loader.item) {
2603 loader.item.enabled = false; // drop OSK and prevent interaction
2604- loader.item.notifyAuthenticationSucceeded(false /* showFakePassword */);
2605 loader.item.hide();
2606 }
2607 }
2608
2609 function login() {
2610- d.waiting = true;
2611 if (LightDMService.greeter.startSessionSync(root.sessionToStart())) {
2612 sessionStarted();
2613 hideView();
2614 } else if (loader.item) {
2615 loader.item.notifyAuthenticationFailed();
2616 }
2617- d.waiting = false;
2618 }
2619
2620 function startUnlock(toTheRight) {
2621@@ -239,27 +232,13 @@
2622 }
2623 }
2624
2625- function showPromptMessage(text, isError) {
2626- // inefficient, but we only rarely deal with messages
2627- var html = text.replace(/&/g, "&amp;")
2628- .replace(/</g, "&lt;")
2629- .replace(/>/g, "&gt;")
2630- .replace(/\n/g, "<br>");
2631- if (isError) {
2632- html = "<font color=\"#df382c\">" + html + "</font>";
2633- }
2634-
2635- if (loader.item) {
2636- loader.item.showMessage(html);
2637- }
2638- }
2639-
2640 function showFingerprintMessage(msg) {
2641+ d.selectUser(d.currentIndex);
2642+ LightDMService.prompts.prepend(msg, LightDMService.prompts.Error);
2643 if (loader.item) {
2644- loader.item.reset(false /* forceShow */);
2645 loader.item.showErrorMessage(msg);
2646+ loader.item.notifyAuthenticationFailed();
2647 }
2648- showPromptMessage(msg, true);
2649 }
2650 }
2651
2652@@ -285,7 +264,6 @@
2653
2654 onRequiredChanged: {
2655 if (required) {
2656- d.waiting = true;
2657 lockedApp = "";
2658 }
2659 }
2660@@ -377,14 +355,14 @@
2661 onLoaded: {
2662 root.lockedApp = "";
2663 item.forceActiveFocus();
2664- d.selectUser(d.currentIndex, true);
2665+ d.selectUser(d.currentIndex);
2666 LightDMService.infographic.readyForDataChange();
2667 }
2668
2669 Connections {
2670 target: loader.item
2671 onSelected: {
2672- d.selectUser(index, true);
2673+ d.selectUser(index);
2674 }
2675 onResponded: {
2676 if (root.locked) {
2677@@ -481,27 +459,15 @@
2678 onShowGreeter: root.forceShow()
2679 onHideGreeter: root.forcedUnlock = true
2680
2681- onShowMessage: d.showPromptMessage(text, isError)
2682-
2683- onShowPrompt: {
2684- if (loader.item) {
2685- loader.item.showPrompt(text, isSecret, isDefaultPrompt);
2686+ onLoginError: {
2687+ if (!loader.item) {
2688+ return;
2689 }
2690
2691- d.waiting = false;
2692- }
2693-
2694- onAuthenticationComplete: {
2695- d.waiting = false;
2696-
2697- if (LightDMService.greeter.authenticated) {
2698- if (!LightDMService.greeter.promptless) {
2699- d.login();
2700- }
2701- } else {
2702- if (!LightDMService.greeter.promptless) {
2703- AccountsService.failedLogins++;
2704- }
2705+ loader.item.notifyAuthenticationFailed();
2706+
2707+ if (!automatic) {
2708+ AccountsService.failedLogins++;
2709
2710 // Check if we should initiate a factory reset
2711 if (maxFailedLogins >= 2) { // require at least a warning
2712@@ -519,14 +485,17 @@
2713 forcedDelayTimer.forceDelay();
2714 }
2715
2716- loader.item.notifyAuthenticationFailed();
2717- if (!LightDMService.greeter.promptless) {
2718- d.selectUser(d.currentIndex, false);
2719- }
2720- }
2721- }
2722-
2723- onRequestAuthenticationUser: d.selectUser(d.getUserIndex(user), true)
2724+ d.selectUser(d.currentIndex);
2725+ }
2726+ }
2727+
2728+ onLoginSuccess: {
2729+ if (!automatic) {
2730+ d.login();
2731+ }
2732+ }
2733+
2734+ onRequestAuthenticationUser: d.selectUser(d.getUserIndex(user))
2735 }
2736
2737 Connections {
2738@@ -589,7 +558,9 @@
2739 if (!d.secureFingerprint) {
2740 d.startUnlock(false /* toTheRight */); // use normal login instead
2741 }
2742- var msg = d.secureFingerprint ? i18n.tr("Try again") : "";
2743+ var msg = d.secureFingerprint ? i18n.tr("Try again") :
2744+ d.alphanumeric ? i18n.tr("Enter passphrase to unlock") :
2745+ i18n.tr("Enter passcode to unlock");
2746 d.showFingerprintMessage(msg);
2747 }
2748
2749@@ -609,8 +580,7 @@
2750 }
2751 console.log("Identified user by fingerprint:", result);
2752 if (loader.item) {
2753- loader.item.enabled = false;
2754- loader.item.notifyAuthenticationSucceeded(true /* showFakePassword */);
2755+ loader.item.showFakePassword();
2756 }
2757 if (root.active)
2758 root.forcedUnlock = true;
2759
2760=== modified file 'qml/Greeter/GreeterPrompt.qml'
2761--- qml/Greeter/GreeterPrompt.qml 2016-09-22 10:33:39 +0000
2762+++ qml/Greeter/GreeterPrompt.qml 2017-01-25 16:04:08 +0000
2763@@ -27,31 +27,25 @@
2764 property bool isAlphanumeric
2765 property string text
2766 property bool isSecret
2767+ property bool interactive: true
2768+ readonly property alias enteredText: passwordInput.text
2769
2770 signal clicked()
2771 signal canceled()
2772- signal responded(string text)
2773-
2774- function reset() {
2775- passwordInput.text = "";
2776- fakeLabel.text = "";
2777- d.enabled = true;
2778- }
2779+ signal accepted()
2780
2781 function showFakePassword() {
2782 // Just a silly hack for looking like 4 pin numbers got entered, if
2783 // a fingerprint was used and we happen to be using a pin. This was
2784 // a request from Design.
2785 if (isSecret && isPrompt && !isAlphanumeric) {
2786- d.enabled = false;
2787- text = "...."; // actual text doesn't matter
2788+ passwordInput.text = "...."; // actual text doesn't matter
2789 }
2790 }
2791
2792 StyledItem {
2793 id: d
2794
2795- property bool enabled: true
2796 readonly property color textColor: passwordInput.enabled ? theme.palette.normal.raisedText
2797 : theme.palette.disabled.raisedText
2798 readonly property color selectedColor: passwordInput.enabled ? theme.palette.normal.raised
2799@@ -60,12 +54,6 @@
2800 : theme.palette.disabled.raisedSecondaryText
2801 readonly property color errorColor: passwordInput.enabled ? theme.palette.normal.negative
2802 : theme.palette.disabled.negative
2803-
2804- onEnabledChanged: {
2805- if (!enabled) {
2806- fakeLabel.text = passwordInput.displayText;
2807- }
2808- }
2809 }
2810
2811 Rectangle {
2812@@ -86,15 +74,17 @@
2813 }
2814 }
2815
2816- Rectangle {
2817+ StyledItem {
2818 id: promptButton
2819 objectName: "promptButton"
2820 anchors.fill: parent
2821 visible: !root.isPrompt
2822+ activeFocusOnTab: true
2823+
2824+ styleName: "FocusShape"
2825
2826 function triggered() {
2827- if (d.enabled) {
2828- d.enabled = false;
2829+ if (root.interactive) {
2830 root.clicked();
2831 }
2832 }
2833@@ -109,6 +99,7 @@
2834 }
2835 }
2836
2837+ Keys.onSpacePressed: triggered();
2838 Keys.onReturnPressed: triggered();
2839 Keys.onEnterPressed: triggered();
2840 MouseArea {
2841@@ -129,6 +120,7 @@
2842 anchors.fill: parent
2843 visible: root.isPrompt
2844 opacity: fakeLabel.visible ? 0 : 1
2845+ activeFocusOnTab: true
2846
2847 validator: RegExpValidator {
2848 regExp: root.isAlphanumeric ? /^.*$/ : /^\d{4}$/
2849@@ -142,15 +134,23 @@
2850
2851 readonly property real frameSpacing: units.gu(0.5)
2852
2853- style: Item {
2854- property color color: d.textColor
2855- property color selectedTextColor: d.selectedColor
2856- property color selectionColor: d.textColor
2857- property color borderColor: "transparent"
2858- property color backgroundColor: "transparent"
2859- property color errorColor: d.errorColor
2860- property real frameSpacing: passwordInput.frameSpacing
2861+ style: StyledItem {
2862 anchors.fill: parent
2863+ styleName: "FocusShape"
2864+
2865+ // Properties needed by TextField
2866+ readonly property color color: d.textColor
2867+ readonly property color selectedTextColor: d.selectedColor
2868+ readonly property color selectionColor: d.textColor
2869+ readonly property color borderColor: "transparent"
2870+ readonly property color backgroundColor: "transparent"
2871+ readonly property color errorColor: d.errorColor
2872+ readonly property real frameSpacing: styledItem.frameSpacing
2873+
2874+ // Properties needed by FocusShape
2875+ readonly property bool enabled: styledItem.enabled
2876+ readonly property bool keyNavigationFocus: styledItem.keyNavigationFocus
2877+ property bool activeFocusOnTab
2878 }
2879
2880 secondaryItem: [
2881@@ -178,9 +178,8 @@
2882 onAccepted: respond()
2883
2884 function respond() {
2885- if (d.enabled) {
2886- d.enabled = false;
2887- root.responded(text);
2888+ if (root.interactive) {
2889+ root.accepted();
2890 }
2891 }
2892
2893@@ -194,6 +193,7 @@
2894 // palette color, whereas we want raisedSecondaryText.
2895 Label {
2896 id: hint
2897+ objectName: "promptHint"
2898 anchors {
2899 left: parent.left
2900 right: parent.right
2901@@ -223,6 +223,7 @@
2902 anchors.leftMargin: passwordInput.frameSpacing * 2
2903 anchors.rightMargin: passwordInput.frameSpacing * 2 + capsIcon.visibleWidth
2904 color: d.drawColor
2905- visible: root.isPrompt && !d.enabled
2906+ text: passwordInput.displayText
2907+ visible: root.isPrompt && !root.interactive
2908 }
2909 }
2910
2911=== modified file 'qml/Greeter/IntegratedLightDMImpl.qml'
2912--- qml/Greeter/IntegratedLightDMImpl.qml 2015-11-19 21:47:32 +0000
2913+++ qml/Greeter/IntegratedLightDMImpl.qml 2017-01-25 16:04:08 +0000
2914@@ -22,6 +22,7 @@
2915
2916 property var greeter: LightDM.Greeter
2917 property var infographic: LightDM.Infographic
2918+ property var prompts: LightDM.Prompts
2919 property var sessions: LightDM.Sessions
2920 property var sessionRoles: LightDM.SessionRoles
2921 property var users: LightDM.Users
2922
2923=== modified file 'qml/Greeter/LightDMService.qml'
2924--- qml/Greeter/LightDMService.qml 2015-11-19 21:47:32 +0000
2925+++ qml/Greeter/LightDMService.qml 2017-01-25 16:04:08 +0000
2926@@ -27,6 +27,7 @@
2927
2928 property var greeter: d.valid ? loader.item.greeter : null
2929 property var infographic: d.valid ? loader.item.infographic : null
2930+ property var prompts: d.valid ? loader.item.prompts : null
2931 property var sessions: d.valid ? loader.item.sessions : null
2932 property var sessionRoles: d.valid ? loader.item.sessionRoles : null
2933 property var users: d.valid ? loader.item.users : null
2934@@ -35,10 +36,9 @@
2935 // This trickery handles cases where applicationArguments aren't provided
2936 // such as during testing
2937 property var fullLightDM: {
2938- if (typeof applicationArguments !== "undefined") {
2939- if (applicationArguments.mode === "greeter") {
2940- return true;
2941- }
2942+ if (typeof applicationArguments === "undefined" ||
2943+ applicationArguments.mode === "greeter") {
2944+ return true;
2945 }
2946 return false;
2947 }
2948
2949=== modified file 'qml/Greeter/LoginList.qml'
2950--- qml/Greeter/LoginList.qml 2016-11-29 00:13:45 +0000
2951+++ qml/Greeter/LoginList.qml 2017-01-25 16:04:08 +0000
2952@@ -25,95 +25,50 @@
2953 focus: true
2954
2955 property alias model: userList.model
2956- property bool alphanumeric: true
2957+ property alias alphanumeric: promptList.alphanumeric
2958 property int currentIndex
2959 property bool locked
2960 property bool waiting
2961 property alias boxVerticalOffset: highlightItem.y
2962
2963- readonly property alias passwordInput: passwordInput
2964 readonly property int numAboveBelow: 4
2965 readonly property int cellHeight: units.gu(5)
2966- readonly property int highlightedHeight: units.gu(15)
2967+ readonly property int highlightedHeight: highlightItem.height
2968 readonly property int moveDuration: UbuntuAnimation.FastDuration
2969- property string selectedSession
2970 property string currentSession
2971 readonly property string currentUser: userList.currentItem.username
2972- property bool wasPrompted: false
2973
2974- signal loginListSessionChanged(string session)
2975 signal responded(string response)
2976 signal selected(int index)
2977 signal sessionChooserButtonClicked()
2978
2979 function tryToUnlock() {
2980- if (wasPrompted) {
2981- passwordInput.forceActiveFocus();
2982- } else {
2983- if (root.locked) {
2984- root.selected(currentIndex);
2985- } else {
2986- root.responded("");
2987- }
2988- }
2989- }
2990-
2991- function showMessage(html) {
2992- if (infoLabel.text === "") {
2993- infoLabel.text = html;
2994- } else {
2995- infoLabel.text += "<br>" + html;
2996- }
2997- }
2998-
2999- function showPrompt(text, isSecret, isDefaultPrompt) {
3000- passwordInput.text = isDefaultPrompt ? alphanumeric ? i18n.tr("Passphrase")
3001- : i18n.tr("Passcode")
3002- : text;
3003- passwordInput.isPrompt = true;
3004- passwordInput.isSecret = isSecret;
3005- passwordInput.reset();
3006- wasPrompted = true;
3007+ promptList.forceActiveFocus();
3008 }
3009
3010 function showError() {
3011 wrongPasswordAnimation.start();
3012- root.resetAuthentication();
3013- }
3014-
3015- function reset() {
3016- root.resetAuthentication();
3017 }
3018
3019 function showFakePassword() {
3020- passwordInput.showFakePassword();
3021- }
3022-
3023- QtObject {
3024- id: d
3025-
3026- function checkIfPromptless() {
3027- if (!waiting && !wasPrompted) {
3028- passwordInput.isPrompt = false;
3029- passwordInput.text = root.locked ? i18n.tr("Retry")
3030- : i18n.tr("Log In")
3031- }
3032- }
3033- }
3034-
3035- onWaitingChanged: d.checkIfPromptless()
3036- onLockedChanged: d.checkIfPromptless()
3037+ promptList.interactive = false;
3038+ promptList.showFakePassword();
3039+ }
3040
3041 theme: ThemeSettings {
3042 name: "Ubuntu.Components.Themes.Ambiance"
3043 }
3044
3045 Keys.onUpPressed: {
3046- selected(currentIndex - 1);
3047+ if (currentIndex > 0) {
3048+ selected(currentIndex - 1);
3049+ }
3050 event.accepted = true;
3051 }
3052 Keys.onDownPressed: {
3053- selected(currentIndex + 1);
3054+ if (currentIndex + 1 < model.count) {
3055+ selected(currentIndex + 1);
3056+ }
3057 event.accepted = true;
3058 }
3059 Keys.onEscapePressed: {
3060@@ -135,7 +90,8 @@
3061 rightMargin: units.gu(2)
3062 }
3063
3064- height: root.highlightedHeight
3065+ height: Math.max(units.gu(15), promptList.height + units.gu(8))
3066+ Behavior on height { NumberAnimation { duration: root.moveDuration; easing.type: Easing.InOutQuad; } }
3067 }
3068
3069 ListView {
3070@@ -153,14 +109,8 @@
3071 interactive: count > 1
3072
3073 readonly property bool movingInternally: moveTimer.running || userList.moving
3074- onMovingInternallyChanged: {
3075- if (!movingInternally) {
3076- root.selected(currentIndex);
3077- }
3078- }
3079
3080 onCurrentIndexChanged: {
3081- root.resetAuthentication();
3082 moveTimer.start();
3083 }
3084
3085@@ -203,7 +153,10 @@
3086 // Add an offset to bottomMargin for any items below the highlight
3087 bottomMargin: -(units.gu(4) + (parent.belowHighlight ? parent.belowOffset : 0))
3088 }
3089- text: realName
3090+ text: userList.currentIndex === index
3091+ && name === "*other"
3092+ && LightDMService.greeter.authenticationUser !== ""
3093+ ? LightDMService.greeter.authenticationUser : realName
3094 color: userList.currentIndex !== index ? theme.palette.normal.raised
3095 : theme.palette.normal.raisedText
3096
3097@@ -309,64 +262,42 @@
3098 }
3099 }
3100
3101- FadingLabel {
3102- id: infoLabel
3103- objectName: "infoLabel"
3104- anchors {
3105- bottom: passwordInput.top
3106- left: highlightItem.left
3107- topMargin: units.gu(1)
3108- bottomMargin: units.gu(1)
3109- leftMargin: units.gu(2)
3110- rightMargin: units.gu(1)
3111- }
3112-
3113- color: theme.palette.normal.raisedText
3114- width: root.width - anchors.leftMargin - anchors.rightMargin
3115- fontSize: "small"
3116- textFormat: Text.StyledText
3117-
3118- opacity: (userList.movingInternally || text == "") ? 0 : 1
3119- Behavior on opacity {
3120- NumberAnimation { duration: 100 }
3121- }
3122- }
3123-
3124- GreeterPrompt {
3125- id: passwordInput
3126- objectName: "passwordInput"
3127+ PromptList {
3128+ id: promptList
3129+ objectName: "promptList"
3130 anchors {
3131 bottom: highlightItem.bottom
3132 horizontalCenter: highlightItem.horizontalCenter
3133 margins: units.gu(2)
3134 }
3135 width: highlightItem.width - anchors.margins * 2
3136- opacity: userList.movingInternally ? 0 : 1
3137-
3138- activeFocusOnTab: true
3139- isAlphanumeric: root.alphanumeric
3140-
3141- onClicked: root.tryToUnlock()
3142- onResponded: root.responded(text)
3143- onCanceled: root.selected(currentIndex)
3144-
3145- Behavior on opacity {
3146- NumberAnimation { duration: 100 }
3147- }
3148-
3149- WrongPasswordAnimation {
3150- id: wrongPasswordAnimation
3151- objectName: "wrongPasswordAnimation"
3152- target: passwordInput
3153+
3154+ onClicked: {
3155+ interactive = false;
3156+ if (root.locked) {
3157+ root.selected(currentIndex);
3158+ } else {
3159+ root.responded("");
3160+ }
3161+ }
3162+ onResponded: {
3163+ interactive = false;
3164+ root.responded(text);
3165+ }
3166+ onCanceled: {
3167+ interactive = false;
3168+ root.selected(currentIndex);
3169+ }
3170+
3171+ Connections {
3172+ target: LightDMService.prompts
3173+ onModelReset: promptList.interactive = true
3174 }
3175 }
3176
3177- function resetAuthentication() {
3178- if (!userList.currentItem) {
3179- return;
3180- }
3181- infoLabel.text = "";
3182- passwordInput.reset();
3183- root.wasPrompted = false;
3184+ WrongPasswordAnimation {
3185+ id: wrongPasswordAnimation
3186+ objectName: "wrongPasswordAnimation"
3187+ target: promptList
3188 }
3189 }
3190
3191=== modified file 'qml/Greeter/NarrowView.qml'
3192--- qml/Greeter/NarrowView.qml 2016-08-30 20:23:15 +0000
3193+++ qml/Greeter/NarrowView.qml 2017-01-25 16:04:08 +0000
3194@@ -48,14 +48,6 @@
3195 signal tease()
3196 signal emergencyCall()
3197
3198- function showMessage(html) {
3199- loginList.showMessage(html);
3200- }
3201-
3202- function showPrompt(text, isSecret, isDefaultPrompt) {
3203- loginList.showPrompt(text, isSecret, isDefaultPrompt);
3204- }
3205-
3206 function showLastChance() {
3207 /* TODO: when we finish support for resetting device after too many
3208 failed logins, we should re-add this popup.
3209@@ -75,10 +67,8 @@
3210 coverPage.hide();
3211 }
3212
3213- function notifyAuthenticationSucceeded(showFakePassword) {
3214- if (showFakePassword) {
3215- loginList.showFakePassword();
3216- }
3217+ function showFakePassword() {
3218+ loginList.showFakePassword();
3219 }
3220
3221 function notifyAuthenticationFailed() {
3222@@ -89,11 +79,8 @@
3223 coverPage.showErrorMessage(msg);
3224 }
3225
3226- function reset(forceShow) {
3227- loginList.reset();
3228- if (forceShow) {
3229- coverPage.show();
3230- }
3231+ function forceShow() {
3232+ coverPage.show();
3233 }
3234
3235 function tryToUnlock(toTheRight) {
3236@@ -120,6 +107,7 @@
3237 objectName: "lockscreen"
3238 anchors.fill: parent
3239 shown: false
3240+ opacity: 0
3241
3242 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
3243 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
3244@@ -195,10 +183,12 @@
3245 onClicked: hide()
3246
3247 onShowProgressChanged: {
3248- if (showProgress === 1) {
3249- loginList.reset();
3250- } else if (showProgress === 0) {
3251- loginList.tryToUnlock();
3252+ if (showProgress === 0) {
3253+ if (lockscreen.shown) {
3254+ loginList.tryToUnlock();
3255+ } else {
3256+ root.responded("");
3257+ }
3258 }
3259 }
3260
3261
3262=== added file 'qml/Greeter/PromptList.qml'
3263--- qml/Greeter/PromptList.qml 1970-01-01 00:00:00 +0000
3264+++ qml/Greeter/PromptList.qml 2017-01-25 16:04:08 +0000
3265@@ -0,0 +1,148 @@
3266+/*
3267+ * Copyright (C) 2017 Canonical, Ltd.
3268+ *
3269+ * This program is free software; you can redistribute it and/or modify
3270+ * it under the terms of the GNU General Public License as published by
3271+ * the Free Software Foundation; version 3.
3272+ *
3273+ * This program is distributed in the hope that it will be useful,
3274+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3275+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3276+ * GNU General Public License for more details.
3277+ *
3278+ * You should have received a copy of the GNU General Public License
3279+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3280+ */
3281+
3282+import QtQuick 2.4
3283+import Ubuntu.Components 1.3
3284+import "../Components"
3285+import "." 0.1
3286+
3287+FocusScope {
3288+ id: root
3289+ height: childrenRect.height
3290+
3291+ property bool alphanumeric: true
3292+ property bool interactive: true
3293+
3294+ signal responded(string text)
3295+ signal clicked()
3296+ signal canceled()
3297+
3298+ function showFakePassword() {
3299+ for (var i = 0; i < repeater.count; i++) {
3300+ var item = repeater.itemAt(i).item;
3301+ if (item.isPrompt) {
3302+ item.showFakePassword();
3303+ }
3304+ }
3305+ }
3306+
3307+ QtObject {
3308+ id: d
3309+
3310+ function sendResponse() {
3311+ for (var i = 0; i < repeater.count; i++) {
3312+ var item = repeater.itemAt(i).item;
3313+ if (item.isPrompt) {
3314+ root.responded(item.enteredText);
3315+ }
3316+ }
3317+ }
3318+ }
3319+
3320+ Column {
3321+ width: parent.width
3322+ spacing: units.gu(0.5)
3323+
3324+ Repeater {
3325+ id: repeater
3326+ model: LightDMService.prompts
3327+
3328+ delegate: Loader {
3329+ id: loader
3330+
3331+ readonly property bool isLabel: model.type == LightDMService.prompts.Message ||
3332+ model.type == LightDMService.prompts.Error
3333+ readonly property var modelData: model
3334+
3335+ sourceComponent: isLabel ? infoLabel : greeterPrompt
3336+
3337+ onLoaded: {
3338+ for (var i = 0; i < repeater.count; i++) {
3339+ var item = repeater.itemAt(i);
3340+ if (item && !item.isLabel) {
3341+ item.focus = true;
3342+ break;
3343+ }
3344+ }
3345+ loader.item.opacity = 1;
3346+ }
3347+
3348+ Binding {
3349+ target: loader.item
3350+ property: "model"
3351+ value: loader.modelData
3352+ }
3353+ }
3354+ }
3355+ }
3356+
3357+ Component {
3358+ id: infoLabel
3359+
3360+ FadingLabel {
3361+ objectName: "infoLabel" + model.index
3362+ width: root.width
3363+
3364+ property var model
3365+ readonly property bool isPrompt: false
3366+
3367+ color: model.type === LightDMService.prompts.Message ? theme.palette.normal.raisedText
3368+ : theme.palette.normal.negative
3369+ fontSize: "small"
3370+ textFormat: Text.PlainText
3371+ text: model.text
3372+
3373+ Behavior on opacity { UbuntuNumberAnimation {} }
3374+ opacity: 0
3375+ }
3376+ }
3377+
3378+ Component {
3379+ id: greeterPrompt
3380+
3381+ GreeterPrompt {
3382+ objectName: "greeterPrompt" + model.index
3383+ width: root.width
3384+
3385+ property var model
3386+
3387+ interactive: root.interactive
3388+ isAlphanumeric: model.text !== "" || root.alphanumeric
3389+ isPrompt: model.type !== LightDMService.prompts.Button
3390+ isSecret: model.type === LightDMService.prompts.Secret
3391+ text: model.text ? model.text : (isAlphanumeric ? i18n.tr("Passphrase") : i18n.tr("Passcode"))
3392+
3393+ onClicked: root.clicked()
3394+ onAccepted: {
3395+ // If there is another GreeterPrompt, focus it.
3396+ for (var i = model.index + 1; i < repeater.count; i++) {
3397+ var item = repeater.itemAt(i).item;
3398+ if (item.isPrompt) {
3399+ item.forceActiveFocus();
3400+ return;
3401+ }
3402+ }
3403+
3404+ // Nope we're the last one; just send our response.
3405+ d.sendResponse();
3406+ }
3407+ onCanceled: root.canceled()
3408+
3409+ Behavior on opacity { UbuntuNumberAnimation {} }
3410+ opacity: 0
3411+ }
3412+ }
3413+}
3414
3415=== modified file 'qml/Greeter/WideView.qml'
3416--- qml/Greeter/WideView.qml 2016-11-29 00:13:45 +0000
3417+++ qml/Greeter/WideView.qml 2017-01-25 16:04:08 +0000
3418@@ -52,16 +52,8 @@
3419 loginList.showError();
3420 }
3421
3422- function reset(forceShow) {
3423- loginList.reset();
3424- }
3425-
3426- function showMessage(html) {
3427- loginList.showMessage(html);
3428- }
3429-
3430- function showPrompt(text, isSecret, isDefaultPrompt) {
3431- loginList.showPrompt(text, isSecret, isDefaultPrompt);
3432+ function forceShow() {
3433+ // Nothing to do, we are always fully shown
3434 }
3435
3436 function tryToUnlock(toTheRight) {
3437@@ -84,10 +76,8 @@
3438 coverPage.hide();
3439 }
3440
3441- function notifyAuthenticationSucceeded(showFakePassword) {
3442- if (showFakePassword) {
3443- loginList.showFakePassword();
3444- }
3445+ function showFakePassword() {
3446+ loginList.showFakePassword();
3447 }
3448
3449 function showLastChance() {
3450@@ -125,8 +115,6 @@
3451 id: loginList
3452 objectName: "loginList"
3453
3454- property int selectedUserIndex: 0
3455-
3456 width: units.gu(40)
3457 anchors {
3458 left: parent.left
3459@@ -141,15 +129,18 @@
3460 Behavior on boxVerticalOffset { UbuntuNumberAnimation {} }
3461
3462 model: root.userModel
3463- currentSession: LightDMService.users.data(selectedUserIndex, LightDMService.userRoles.SessionRole);
3464 onResponded: root.responded(response)
3465- onSelected: {
3466- root.selected(index)
3467- loginList.selectedUserIndex = index;
3468- }
3469+ onSelected: root.selected(index)
3470 onSessionChooserButtonClicked: parent.state = "SessionsList"
3471+ onCurrentIndexChanged: setCurrentSession()
3472
3473 Keys.forwardTo: [sessionChooserLoader.item]
3474+
3475+ Component.onCompleted: setCurrentSession()
3476+
3477+ function setCurrentSession() {
3478+ currentSession = LightDMService.users.data(currentIndex, LightDMService.userRoles.SessionRole);
3479+ }
3480 }
3481
3482 Loader {
3483@@ -177,7 +168,7 @@
3484 onSessionSelected: loginList.currentSession = sessionKey
3485 onShowLoginList: {
3486 coverPage.state = "LoginList"
3487- loginList.passwordInput.forceActiveFocus();
3488+ loginList.tryToUnlock();
3489 }
3490 ignoreUnknownSignals: true
3491 }
3492
3493=== modified file 'qml/Launcher/Drawer.qml'
3494--- qml/Launcher/Drawer.qml 2016-11-28 10:17:22 +0000
3495+++ qml/Launcher/Drawer.qml 2017-01-25 16:04:08 +0000
3496@@ -20,6 +20,7 @@
3497 import Utils 0.1
3498 import "../Components"
3499 import Qt.labs.settings 1.0
3500+import GSettings 1.0
3501
3502 FocusScope {
3503 id: root
3504@@ -43,6 +44,16 @@
3505 searchField.focus = true;
3506 }
3507
3508+ Keys.onPressed: {
3509+ if (event.text.trim() !== "") {
3510+ focusInput();
3511+ searchField.text = event.text;
3512+ }
3513+ // Catch all presses here in case the navigation lets something through
3514+ // We never want to end up in the launcher with focus
3515+ event.accepted = true;
3516+ }
3517+
3518 Settings {
3519 property alias selectedTab: sections.selectedIndex
3520 }
3521@@ -76,9 +87,13 @@
3522
3523 TextField {
3524 id: searchField
3525+ objectName: "searchField"
3526 anchors { left: parent.left; top: parent.top; right: parent.right; margins: units.gu(1) }
3527 placeholderText: i18n.tr("Search…")
3528 focus: true
3529+
3530+ KeyNavigation.down: sections
3531+
3532 onAccepted: {
3533 if (searchField.displayText != "" && listLoader.item && listLoader.item.currentItem) {
3534 root.applicationSelected(listLoader.item.getFirstAppId());
3535@@ -95,7 +110,14 @@
3536
3537 Sections {
3538 id: sections
3539+ objectName: "drawerSections"
3540 width: parent.width
3541+
3542+ KeyNavigation.up: searchField
3543+ KeyNavigation.down: headerFocusScope
3544+ KeyNavigation.backtab: searchField
3545+ KeyNavigation.tab: headerFocusScope
3546+
3547 actions: [
3548 Action {
3549 text: i18n.ctr("Apps sorted alphabetically", "A-Z")
3550@@ -115,9 +137,45 @@
3551 }
3552 }
3553
3554+ FocusScope {
3555+ id: headerFocusScope
3556+ objectName: "headerFocusScope"
3557+ KeyNavigation.up: sections
3558+ KeyNavigation.down: listLoader.item
3559+ KeyNavigation.backtab: sections
3560+ KeyNavigation.tab: listLoader.item
3561+ activeFocusOnTab: true
3562+
3563+ GSettings {
3564+ id: settings
3565+ schema.id: "com.canonical.Unity8"
3566+ }
3567+
3568+ Keys.onPressed: {
3569+ switch (event.key) {
3570+ case Qt.Key_Return:
3571+ case Qt.Key_Enter:
3572+ case Qt.Key_Space:
3573+ trigger();
3574+ event.accepted = true;
3575+ }
3576+ }
3577+
3578+ function trigger() {
3579+ Qt.openUrlExternally(settings.appstoreUri)
3580+ }
3581+ }
3582+
3583 Loader {
3584 id: listLoader
3585- anchors { left: parent.left; top: sectionsContainer.bottom; right: parent.right; bottom: parent.bottom; leftMargin: units.gu(1); rightMargin: units.gu(1) }
3586+ objectName: "drawerListLoader"
3587+ anchors { left: parent.left; top: sectionsContainer.bottom; right: parent.right; bottom: parent.bottom }
3588+
3589+ KeyNavigation.up: headerFocusScope
3590+ KeyNavigation.down: searchField
3591+ KeyNavigation.backtab: headerFocusScope
3592+ KeyNavigation.tab: searchField
3593+
3594 sourceComponent: {
3595 switch (sections.selectedIndex) {
3596 case 0: return aToZComponent;
3597@@ -167,10 +225,13 @@
3598 Component {
3599 id: mostUsedComponent
3600 DrawerListView {
3601+ id: mostUsedListView
3602
3603 header: MoreAppsHeader {
3604 width: parent.width
3605 height: units.gu(6)
3606+ highlighted: headerFocusScope.activeFocus
3607+ onClicked: headerFocusScope.trigger();
3608 }
3609
3610 model: AppDrawerProxyModel {
3611@@ -180,7 +241,8 @@
3612 }
3613
3614 delegate: UbuntuShape {
3615- width: parent.width
3616+ width: parent.width - units.gu(2)
3617+ anchors.horizontalCenter: parent.horizontalCenter
3618 color: "#20ffffff"
3619 aspect: UbuntuShape.Flat
3620 // NOTE: Cannot use gridView.rows here as it would evaluate to 0 at first and only update later,
3621@@ -196,6 +258,9 @@
3622 bottomMargin: units.gu(1)
3623 clip: true
3624
3625+ interactive: true
3626+ focus: index == mostUsedListView.currentIndex
3627+
3628 model: sortProxyModel
3629
3630 delegateWidth: units.gu(8)
3631@@ -209,10 +274,13 @@
3632 Component {
3633 id: aToZComponent
3634 DrawerListView {
3635+ id: aToZListView
3636
3637 header: MoreAppsHeader {
3638 width: parent.width
3639 height: units.gu(6)
3640+ highlighted: headerFocusScope.activeFocus
3641+ onClicked: headerFocusScope.trigger();
3642 }
3643
3644 model: AppDrawerProxyModel {
3645@@ -222,7 +290,8 @@
3646 }
3647
3648 delegate: UbuntuShape {
3649- width: parent.width
3650+ width: parent.width - units.gu(2)
3651+ anchors.horizontalCenter: parent.horizontalCenter
3652 color: "#20ffffff"
3653 aspect: UbuntuShape.Flat
3654
3655@@ -244,7 +313,8 @@
3656 anchors { left: parent.left; top: categoryNameLabel.bottom; right: parent.right; topMargin: units.gu(1) }
3657 height: rows * delegateHeight
3658
3659- interactive: false
3660+ interactive: true
3661+ focus: index == aToZListView.currentIndex
3662
3663 model: AppDrawerProxyModel {
3664 id: categoryModel
3665@@ -263,10 +333,13 @@
3666 Component {
3667 id: drawerDelegateComponent
3668 AbstractButton {
3669+ id: drawerDelegate
3670 width: GridView.view.cellWidth
3671 height: units.gu(10)
3672 objectName: "drawerItem_" + model.appId
3673
3674+ readonly property bool focused: index === GridView.view.currentIndex && GridView.view.activeFocus
3675+
3676 onClicked: root.applicationSelected(model.appId)
3677
3678 Column {
3679@@ -290,6 +363,15 @@
3680 source: model.icon
3681 }
3682 sourceFillMode: UbuntuShape.PreserveAspectCrop
3683+
3684+ StyledItem {
3685+ styleName: "FocusShape"
3686+ anchors.fill: parent
3687+ StyleHints {
3688+ visible: drawerDelegate.focused
3689+ radius: units.gu(2.55)
3690+ }
3691+ }
3692 }
3693
3694 Label {
3695
3696=== modified file 'qml/Launcher/DrawerGridView.qml'
3697--- qml/Launcher/DrawerGridView.qml 2016-11-10 14:39:18 +0000
3698+++ qml/Launcher/DrawerGridView.qml 2017-01-25 16:04:08 +0000
3699@@ -17,7 +17,7 @@
3700 import QtQuick 2.4
3701 import "../Components"
3702
3703-Item {
3704+FocusScope {
3705 id: root
3706
3707 property int delegateWidth: units.gu(10)
3708@@ -25,6 +25,7 @@
3709 property alias delegate: gridView.delegate
3710 property alias model: gridView.model
3711 property alias interactive: gridView.interactive
3712+ property alias currentIndex: gridView.currentIndex
3713
3714 property alias header: gridView.header
3715 property alias topMargin: gridView.topMargin
3716@@ -37,6 +38,7 @@
3717 id: gridView
3718 anchors.fill: parent
3719 leftMargin: spacing
3720+ focus: true
3721
3722 readonly property int overflow: width - (root.columns * root.delegateWidth)
3723 readonly property real spacing: overflow / (root.columns)
3724
3725=== modified file 'qml/Launcher/DrawerListView.qml'
3726--- qml/Launcher/DrawerListView.qml 2016-11-28 14:56:02 +0000
3727+++ qml/Launcher/DrawerListView.qml 2017-01-25 16:04:08 +0000
3728@@ -25,6 +25,12 @@
3729 bottomMargin: units.gu(1)
3730 spacing: units.gu(1)
3731 clip: true
3732+ focus: true
3733+
3734+ onActiveFocusChanged: {
3735+ currentIndex = -1;
3736+ currentIndex = 0;
3737+ }
3738
3739 function getFirstAppId() {
3740 return model.appId(0);
3741
3742=== modified file 'qml/Launcher/Launcher.qml'
3743--- qml/Launcher/Launcher.qml 2016-12-07 13:47:15 +0000
3744+++ qml/Launcher/Launcher.qml 2017-01-25 16:04:08 +0000
3745@@ -74,7 +74,7 @@
3746 } else {
3747 superPressTimer.stop();
3748 superLongPressTimer.stop();
3749- launcher.switchToNextState("");
3750+ switchToNextState("");
3751 panel.shortcutHintsShown = false;
3752 }
3753 }
3754@@ -87,14 +87,14 @@
3755 superPressTimer.stop();
3756 superLongPressTimer.stop();
3757 } else {
3758+ switchToNextState("");
3759+ root.focus = false;
3760 if (panel.highlightIndex == -1) {
3761- showDashHome();
3762+ root.showDashHome();
3763 } else if (panel.highlightIndex >= 0){
3764 launcherApplicationSelected(LauncherModel.get(panel.highlightIndex).appId);
3765 }
3766 panel.highlightIndex = -2;
3767- switchToNextState("");
3768- root.focus = false;
3769 }
3770 }
3771
3772@@ -108,6 +108,10 @@
3773 }
3774 }
3775
3776+ onPanelWidthChanged: {
3777+ hint();
3778+ }
3779+
3780 function hide(flags) {
3781 if ((flags & ignoreHideIfMouseOverLauncher) && Utils.Functions.itemUnderMouse(panel)) {
3782 return;
3783@@ -206,7 +210,7 @@
3784 case Qt.Key_Return:
3785 case Qt.Key_Space:
3786 if (panel.highlightIndex == -1) {
3787- showDashHome();
3788+ root.showDashHome();
3789 } else if (panel.highlightIndex >= 0) {
3790 launcherApplicationSelected(LauncherModel.get(panel.highlightIndex).appId);
3791 }
3792@@ -469,7 +473,7 @@
3793 }
3794 onPassed: {
3795 if (root.drawerEnabled) {
3796- root.switchToNextState("drawer");
3797+ root.openDrawer()
3798 }
3799 }
3800
3801@@ -545,8 +549,7 @@
3802 if (!dragging) {
3803 if (distance > panel.width / 2) {
3804 if (root.drawerEnabled && distance > panel.width * 3 && dragDirection() !== "left") {
3805- root.switchToNextState("drawer");
3806- root.focus = true;
3807+ root.openDrawer(false)
3808 } else {
3809 root.switchToNextState("visible");
3810 }
3811
3812=== modified file 'qml/Launcher/LauncherDelegate.qml'
3813--- qml/Launcher/LauncherDelegate.qml 2016-09-26 11:58:56 +0000
3814+++ qml/Launcher/LauncherDelegate.qml 2017-01-25 16:04:08 +0000
3815@@ -124,15 +124,14 @@
3816 height: parent.itemHeight + units.gu(1)
3817 anchors.centerIn: parent
3818
3819- Image {
3820- objectName: "focusRing"
3821- anchors.centerIn: iconShape
3822- height: width * 15 / 16
3823- width: iconShape.width + units.gu(1)
3824- source: "graphics/launcher-app-focus-ring.svg"
3825- sourceSize.width: width
3826- sourceSize.height: height
3827- visible: root.highlighted
3828+ StyledItem {
3829+ styleName: "FocusShape"
3830+ anchors.fill: iconShape
3831+ activeFocusOnTab: true
3832+ StyleHints {
3833+ visible: root.highlighted
3834+ radius: units.gu(2.55)
3835+ }
3836 }
3837
3838 ProportionalShape {
3839
3840=== modified file 'qml/Launcher/LauncherPanel.qml'
3841--- qml/Launcher/LauncherPanel.qml 2017-01-10 14:44:00 +0000
3842+++ qml/Launcher/LauncherPanel.qml 2017-01-25 16:04:08 +0000
3843@@ -80,6 +80,7 @@
3844 }
3845
3846 Rectangle {
3847+ id: bfb
3848 objectName: "buttonShowDashHome"
3849 width: parent.width
3850 height: width * .9
3851@@ -100,13 +101,15 @@
3852 activeFocusOnPress: false
3853 onClicked: root.showDashHome()
3854 }
3855- Rectangle {
3856- objectName: "bfbFocusHighlight"
3857+
3858+ StyledItem {
3859+ styleName: "FocusShape"
3860 anchors.fill: parent
3861- border.color: "white"
3862- border.width: units.dp(1)
3863- color: "transparent"
3864- visible: parent.highlighted
3865+ anchors.margins: units.gu(.5)
3866+ StyleHints {
3867+ visible: bfb.highlighted
3868+ radius: 0
3869+ }
3870 }
3871 }
3872
3873@@ -788,6 +791,8 @@
3874 quickList.model = launcherListView.model.get(index).quickList;
3875 quickList.appId = launcherListView.model.get(index).appId;
3876 quickList.state = "open";
3877+ root.highlightIndex = index;
3878+ quickList.forceActiveFocus();
3879 }
3880
3881 Item {
3882
3883=== modified file 'qml/Launcher/MoreAppsHeader.qml'
3884--- qml/Launcher/MoreAppsHeader.qml 2016-11-04 10:10:26 +0000
3885+++ qml/Launcher/MoreAppsHeader.qml 2017-01-25 16:04:08 +0000
3886@@ -4,17 +4,24 @@
3887 AbstractButton {
3888 id: root
3889
3890- onClicked: {
3891- // TODO: Make this point to the snappy store as soon as we stop landing to vivid
3892- Qt.openUrlExternally("scope://com.canonical.scopes.clickstore")
3893- }
3894+ property bool highlighted: false
3895
3896 UbuntuShape {
3897- width: parent.width
3898+ width: parent.width - units.gu(2)
3899+ anchors.horizontalCenter: parent.horizontalCenter
3900 height: parent.height - units.gu(1)
3901 color: "#20ffffff"
3902 aspect: UbuntuShape.Flat
3903
3904+ StyledItem {
3905+ styleName: "FocusShape"
3906+ anchors.fill: parent
3907+ activeFocusOnTab: true
3908+ StyleHints {
3909+ visible: root.highlighted
3910+ }
3911+ }
3912+
3913 Row {
3914 anchors.fill: parent
3915 anchors.margins: units.gu(1)
3916
3917=== removed file 'qml/Launcher/graphics/launcher-app-focus-ring.svg'
3918--- qml/Launcher/graphics/launcher-app-focus-ring.svg 2015-12-15 12:53:53 +0000
3919+++ qml/Launcher/graphics/launcher-app-focus-ring.svg 1970-01-01 00:00:00 +0000
3920@@ -1,12 +0,0 @@
3921-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
3922-<svg width="172px" height="163px" viewBox="0 0 172 163" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
3923- <!-- Generator: Sketch 3.4.4 (17249) - http://www.bohemiancoding.com/sketch -->
3924- <title>Shape</title>
3925- <desc>Created with Sketch.</desc>
3926- <defs></defs>
3927- <g id="•-Launcher" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
3928- <g id="Artboard-9" sketch:type="MSArtboardGroup" transform="translate(-163.000000, -1436.000000)" fill="#E95420">
3929- <path d="M221.983432,1440 L221.983432,1440 C195.6127,1440 184.708233,1442.4723 177.107949,1450.10734 C169.476819,1457.77336 167,1468.79245 167,1495.3481 L167,1538.9019 C167,1565.45755 169.476819,1576.47664 177.107949,1584.14266 C184.708233,1591.7777 195.6127,1594.25 221.983432,1594.25 L276.016868,1594.25 C302.387595,1594.25 313.291998,1591.77771 320.892221,1584.14264 C328.523252,1576.47663 331,1565.45769 331,1538.9019 L331,1495.3481 C331,1468.79231 328.523252,1457.77337 320.892221,1450.10736 C313.291998,1442.47229 302.387595,1440 276.016868,1440 L221.983432,1440 Z M221.983432,1436 L276.016868,1436 C302.345315,1436 314.848953,1438.36655 323.727108,1447.2854 C332.633306,1456.23243 335,1468.85167 335,1495.3481 L335,1538.9019 C335,1565.39833 332.633306,1578.01757 323.727108,1586.9646 C314.848953,1595.88345 302.345315,1598.25 276.016868,1598.25 L221.983432,1598.25 C195.654985,1598.25 183.151291,1595.88345 174.273077,1586.96463 C165.366772,1578.0176 163,1565.39822 163,1538.9019 L163,1495.3481 C163,1468.85178 165.366772,1456.2324 174.273077,1447.28537 C183.151291,1438.36655 195.654985,1436 221.983432,1436 L221.983432,1436 Z" id="Shape" sketch:type="MSShapeGroup"></path>
3930- </g>
3931- </g>
3932-</svg>
3933\ No newline at end of file
3934
3935=== modified file 'qml/OrientedShell.qml'
3936--- qml/OrientedShell.qml 2016-12-13 09:56:20 +0000
3937+++ qml/OrientedShell.qml 2017-01-25 16:04:08 +0000
3938@@ -266,7 +266,8 @@
3939 nativeWidth: root.width
3940 nativeHeight: root.height
3941 mode: applicationArguments.mode
3942- hasMouse: miceModel.count + touchPadModel.count > 0
3943+ hasMouse: pointerInputDevices > 0
3944+ hasKeyboard: keyboardsModel.count > 0
3945 // TODO: Factor in if the current screen is a touch screen and if the user wants to
3946 // have multiple keyboards around. For now we only enable one keyboard at a time
3947 // thus hiding it here if there is a physical one around or if we have a second
3948
3949=== modified file 'qml/Panel/PanelBar.qml'
3950--- qml/Panel/PanelBar.qml 2016-09-30 14:27:30 +0000
3951+++ qml/Panel/PanelBar.qml 2017-01-25 16:04:08 +0000
3952@@ -49,6 +49,7 @@
3953 row.resetCurrentItem();
3954 }
3955 row.setCurrentItemIndex(index);
3956+ d.alignIndicators();
3957 }
3958
3959 function addScrollOffset(scrollAmmout) {
3960
3961=== modified file 'qml/Panel/PanelMenu.qml'
3962--- qml/Panel/PanelMenu.qml 2016-12-22 14:55:39 +0000
3963+++ qml/Panel/PanelMenu.qml 2017-01-25 16:04:08 +0000
3964@@ -140,6 +140,19 @@
3965 visible: !root.fullyClosed
3966 }
3967
3968+ Keys.onPressed: {
3969+ if (event.key === Qt.Key_Left) {
3970+ bar.setCurrentItemIndex(bar.currentItemIndex - 1);
3971+ event.accepted = true;
3972+ } else if (event.key === Qt.Key_Right) {
3973+ bar.setCurrentItemIndex(bar.currentItemIndex + 1);
3974+ event.accepted = true;
3975+ } else if (event.key === Qt.Key_Escape) {
3976+ root.hide();
3977+ event.accepted = true;
3978+ }
3979+ }
3980+
3981 PanelBar {
3982 id: bar
3983 objectName: "indicatorsBar"
3984@@ -368,6 +381,7 @@
3985 State {
3986 name: "commit"
3987 extend: "locked"
3988+ PropertyChanges { target: root; focus: true }
3989 PropertyChanges { target: bar; interactive: true }
3990 PropertyChanges {
3991 target: d;
3992
3993=== modified file 'qml/Shell.qml'
3994--- qml/Shell.qml 2017-01-03 12:04:08 +0000
3995+++ qml/Shell.qml 2017-01-25 16:04:08 +0000
3996@@ -68,6 +68,7 @@
3997 stage.updateFocusedAppOrientationAnimated();
3998 }
3999 property bool hasMouse: false
4000+ property bool hasKeyboard: false
4001
4002 // to be read from outside
4003 readonly property int mainAppWindowOrientationAngle: stage.mainAppWindowOrientationAngle
4004@@ -240,7 +241,7 @@
4005 // Ignore when greeter is active, to avoid pocket presses
4006 if (!greeter.active) {
4007 launcher.fadeOut();
4008- shell.showHome();
4009+ ApplicationManager.requestFocusApplication("unity8-dash");
4010 }
4011 }
4012 onTouchBegun: { cursor.opacity = 0; }
4013@@ -319,6 +320,35 @@
4014 panel.applicationMenus.hide();
4015 }
4016 }
4017+
4018+ TouchGestureArea {
4019+ anchors.fill: stage
4020+
4021+ minimumTouchPoints: 4
4022+ maximumTouchPoints: minimumTouchPoints
4023+
4024+ readonly property bool recognisedPress: status == TouchGestureArea.Recognized &&
4025+ touchPoints.length >= minimumTouchPoints &&
4026+ touchPoints.length <= maximumTouchPoints
4027+ property bool wasPressed: false
4028+
4029+ onRecognisedPressChanged: {
4030+ if (recognisedPress) {
4031+ wasPressed = true;
4032+ }
4033+ }
4034+
4035+ onStatusChanged: {
4036+ if (status !== TouchGestureArea.Recognized) {
4037+ if (status === TouchGestureArea.WaitingForTouch) {
4038+ if (wasPressed && !dragging) {
4039+ launcher.openDrawer(true);
4040+ }
4041+ }
4042+ wasPressed = false;
4043+ }
4044+ }
4045+ }
4046 }
4047
4048 InputMethod {
4049@@ -343,6 +373,16 @@
4050 onLoaded: {
4051 item.objectName = "greeter"
4052 }
4053+ property bool openDrawerAfterUnlock: false
4054+ Connections {
4055+ target: greeter
4056+ onActiveChanged: {
4057+ if (!greeter.active && greeterLoader.openDrawerAfterUnlock) {
4058+ launcher.openDrawer(false);
4059+ greeterLoader.openDrawerAfterUnlock = false;
4060+ }
4061+ }
4062+ }
4063 }
4064
4065 Component {
4066@@ -434,9 +474,11 @@
4067 if (shell.mode === "greeter") {
4068 SessionBroadcast.requestHomeShown(AccountsService.user);
4069 } else {
4070- var animate = !LightDMService.greeter.active && !stages.shown;
4071- dash.setCurrentScope(0, animate, false);
4072- ApplicationManager.requestFocusApplication("unity8-dash");
4073+ if (!greeter.active) {
4074+ launcher.openDrawer(false);
4075+ } else {
4076+ greeterLoader.openDrawerAfterUnlock = true;
4077+ }
4078 }
4079 }
4080
4081@@ -512,7 +554,7 @@
4082 lockedVisible: shell.usageScenario == "desktop" && !settings.autohideLauncher && !panel.fullscreenMode
4083 blurSource: greeter.shown ? greeter : stages
4084 topPanelHeight: panel.panelHeight
4085- drawerEnabled: !greeter.shown
4086+ drawerEnabled: !greeter.active
4087
4088 onShowDashHome: showHome()
4089 onLauncherApplicationSelected: {
4090@@ -657,8 +699,10 @@
4091 id: dialogs
4092 objectName: "dialogs"
4093 anchors.fill: parent
4094+ visible: hasActiveDialog
4095 z: overlay.z + 10
4096 usageScenario: shell.usageScenario
4097+ hasKeyboard: shell.hasKeyboard
4098 onPowerOffClicked: {
4099 shutdownFadeOutRectangle.enabled = true;
4100 shutdownFadeOutRectangle.visible = true;
4101
4102=== modified file 'qml/Stage/DecoratedWindow.qml'
4103--- qml/Stage/DecoratedWindow.qml 2016-12-12 11:16:47 +0000
4104+++ qml/Stage/DecoratedWindow.qml 2017-01-25 16:04:08 +0000
4105@@ -196,18 +196,19 @@
4106 ]
4107 }
4108
4109- MouseArea {
4110+ WindowDecoration {
4111+ id: decoration
4112+ closeButtonVisible: root.application.appId !== "unity8-dash"
4113+ objectName: "appWindowDecoration"
4114+
4115 anchors { left: parent.left; top: parent.top; right: parent.right }
4116 height: units.gu(3)
4117
4118+ title: applicationWindow.title
4119+
4120 opacity: root.hasDecoration ? Math.min(1, root.showDecoration) : 0
4121-
4122 Behavior on opacity { UbuntuNumberAnimation { } }
4123
4124- drag.target: Item {}
4125- drag.filterChildren: true
4126- drag.threshold: 0
4127-
4128 onPressed: root.decorationPressed();
4129 onPressedChanged: moveHandler.handlePressedChanged(pressed, pressedButtons, mouseX, mouseY)
4130 onPositionChanged: moveHandler.handlePositionChanged(mouse)
4131@@ -216,47 +217,39 @@
4132 moveHandler.handleReleased();
4133 }
4134
4135- WindowDecoration {
4136- id: decoration
4137- closeButtonVisible: root.application.appId !== "unity8-dash"
4138- objectName: "appWindowDecoration"
4139- anchors.fill: parent
4140- title: applicationWindow.title
4141-
4142- onCloseClicked: root.closeClicked();
4143- onMaximizeClicked: { root.decorationPressed(); root.maximizeClicked(); }
4144- onMaximizeHorizontallyClicked: { root.decorationPressed(); root.maximizeHorizontallyClicked(); }
4145- onMaximizeVerticallyClicked: { root.decorationPressed(); root.maximizeVerticallyClicked(); }
4146- onMinimizeClicked: root.minimizeClicked();
4147-
4148- enableMenus: {
4149- return active &&
4150- surface &&
4151- (PanelState.focusedPersistentSurfaceId === surface.persistentId && !PanelState.decorationsVisible)
4152- }
4153- menu: sharedAppModel.model
4154-
4155- Indicators.SharedUnityMenuModel {
4156- id: sharedAppModel
4157- property var menus: surface ? ApplicationMenuRegistry.getMenusForSurface(surface.persistentId) : []
4158- property var menuService: menus.length > 0 ? menus[0] : undefined
4159-
4160- busName: menuService ? menuService.service : ""
4161- menuObjectPath: menuService && menuService.menuPath ? menuService.menuPath : ""
4162- actions: menuService && menuService.actionPath ? { "unity": menuService.actionPath } : {}
4163- }
4164-
4165- Connections {
4166- target: ApplicationMenuRegistry
4167- onSurfaceMenuRegistered: {
4168- if (surface && surfaceId === surface.persistentId) {
4169- sharedAppModel.menus = Qt.binding(function() { return surface ? ApplicationMenuRegistry.getMenusForSurface(surface.persistentId) : [] });
4170- }
4171+ onCloseClicked: root.closeClicked();
4172+ onMaximizeClicked: { root.decorationPressed(); root.maximizeClicked(); }
4173+ onMaximizeHorizontallyClicked: { root.decorationPressed(); root.maximizeHorizontallyClicked(); }
4174+ onMaximizeVerticallyClicked: { root.decorationPressed(); root.maximizeVerticallyClicked(); }
4175+ onMinimizeClicked: root.minimizeClicked();
4176+
4177+ enableMenus: {
4178+ return active &&
4179+ surface &&
4180+ (PanelState.focusedPersistentSurfaceId === surface.persistentId && !PanelState.decorationsVisible)
4181+ }
4182+ menu: sharedAppModel.model
4183+
4184+ Indicators.SharedUnityMenuModel {
4185+ id: sharedAppModel
4186+ property var menus: surface ? ApplicationMenuRegistry.getMenusForSurface(surface.persistentId) : []
4187+ property var menuService: menus.length > 0 ? menus[0] : undefined
4188+
4189+ busName: menuService ? menuService.service : ""
4190+ menuObjectPath: menuService && menuService.menuPath ? menuService.menuPath : ""
4191+ actions: menuService && menuService.actionPath ? { "unity": menuService.actionPath } : {}
4192+ }
4193+
4194+ Connections {
4195+ target: ApplicationMenuRegistry
4196+ onSurfaceMenuRegistered: {
4197+ if (surface && surfaceId === surface.persistentId) {
4198+ sharedAppModel.menus = Qt.binding(function() { return surface ? ApplicationMenuRegistry.getMenusForSurface(surface.persistentId) : [] });
4199 }
4200- onSurfaceMenuUnregistered: {
4201- if (surface && surfaceId === surface.persistentId) {
4202- sharedAppModel.menus = Qt.binding(function() { return surface ? ApplicationMenuRegistry.getMenusForSurface(surface.persistentId) : [] });
4203- }
4204+ }
4205+ onSurfaceMenuUnregistered: {
4206+ if (surface && surfaceId === surface.persistentId) {
4207+ sharedAppModel.menus = Qt.binding(function() { return surface ? ApplicationMenuRegistry.getMenusForSurface(surface.persistentId) : [] });
4208 }
4209 }
4210 }
4211
4212=== modified file 'qml/Stage/WindowDecoration.qml'
4213--- qml/Stage/WindowDecoration.qml 2016-12-12 11:16:47 +0000
4214+++ qml/Stage/WindowDecoration.qml 2017-01-25 16:04:08 +0000
4215@@ -38,6 +38,10 @@
4216 acceptedButtons: Qt.AllButtons // prevent leaking unhandled mouse events
4217 hoverEnabled: true
4218
4219+ drag.target: Item {}
4220+ drag.filterChildren: true
4221+ drag.threshold: 0
4222+
4223 signal closeClicked()
4224 signal minimizeClicked()
4225 signal maximizeClicked()
4226@@ -64,8 +68,8 @@
4227 (menuBar.showRequested || root.containsMouse)
4228 }
4229
4230- // We dont want touch events to fall through to parent,
4231- // otherwise the containsMouse will not work.
4232+ // We dont want touch events to fall through to parent as it expect some child MouseArea to have them
4233+ // If not some MouseArea in the menu bar, it will be this one.
4234 MouseArea {
4235 anchors.fill: parent
4236 propagateComposedEvents: true
4237
4238=== modified file 'src/MouseTouchAdaptor.cpp'
4239--- src/MouseTouchAdaptor.cpp 2016-03-29 03:47:39 +0000
4240+++ src/MouseTouchAdaptor.cpp 2017-01-25 16:04:08 +0000
4241@@ -72,6 +72,7 @@
4242 MouseTouchAdaptor *g_instance = nullptr;
4243
4244 const Qt::KeyboardModifiers TRI_PRESS_MODIFIER = Qt::ShiftModifier|Qt::ControlModifier|Qt::AltModifier;
4245+const Qt::KeyboardModifiers QUAD_PRESS_MODIFIER = TRI_PRESS_MODIFIER|Qt::MetaModifier;
4246
4247 Qt::MouseButton translateMouseButton(xcb_button_t detail)
4248 {
4249@@ -91,7 +92,7 @@
4250 if (mod & 0x01) qtMod |= Qt::ShiftModifier;
4251 if (mod & 0x04) qtMod |= Qt::ControlModifier;
4252 if (mod & 0x08) qtMod |= Qt::AltModifier;
4253- if (mod & 0x80) qtMod |= Qt::MetaModifier;
4254+ if (mod & 0x40) qtMod |= Qt::MetaModifier;
4255
4256 return qtMod;
4257 }
4258@@ -101,6 +102,7 @@
4259 : QObject(nullptr)
4260 , m_leftButtonIsPressed(false)
4261 , m_triPressModifier(false)
4262+ , m_quadPressModifier(false)
4263 , m_enabled(true)
4264 {
4265 QCoreApplication::instance()->installNativeEventFilter(this);
4266@@ -297,6 +299,13 @@
4267 touchEvent.press(2, windowPos);
4268 m_triPressModifier = true;
4269 }
4270+ if (qtMod == QUAD_PRESS_MODIFIER) {
4271+ touchEvent.press(1, windowPos);
4272+ touchEvent.press(2, windowPos);
4273+ touchEvent.press(3, windowPos);
4274+ m_quadPressModifier = true;
4275+ }
4276+
4277 touchEvent.commit(false /* processEvents */);
4278
4279 m_leftButtonIsPressed = true;
4280@@ -322,10 +331,16 @@
4281 touchEvent.release(1, windowPos);
4282 touchEvent.release(2, windowPos);
4283 }
4284+ if (m_quadPressModifier) {
4285+ touchEvent.release(1, windowPos);
4286+ touchEvent.release(2, windowPos);
4287+ touchEvent.release(3, windowPos);
4288+ }
4289 touchEvent.commit(false /* processEvents */);
4290
4291 m_leftButtonIsPressed = false;
4292 m_triPressModifier = false;
4293+ m_quadPressModifier = false;
4294 return true;
4295 }
4296
4297@@ -354,6 +369,18 @@
4298 m_triPressModifier = false;
4299 }
4300 }
4301+ if (m_quadPressModifier) {
4302+ if (qtMod == QUAD_PRESS_MODIFIER) {
4303+ touchEvent.move(1, windowPos);
4304+ touchEvent.move(2, windowPos);
4305+ touchEvent.move(3, windowPos);
4306+ } else {
4307+ touchEvent.release(1, windowPos);
4308+ touchEvent.release(2, windowPos);
4309+ touchEvent.release(3, windowPos);
4310+ m_quadPressModifier = false;
4311+ }
4312+ }
4313 touchEvent.commit(false /* processEvents */);
4314
4315 return true;
4316
4317=== modified file 'src/MouseTouchAdaptor.h'
4318--- src/MouseTouchAdaptor.h 2016-03-29 03:47:39 +0000
4319+++ src/MouseTouchAdaptor.h 2017-01-25 16:04:08 +0000
4320@@ -59,6 +59,7 @@
4321 QTouchDevice *m_touchDevice;
4322 bool m_leftButtonIsPressed;
4323 bool m_triPressModifier;
4324+ bool m_quadPressModifier;
4325
4326
4327 bool m_enabled;
4328
4329=== modified file 'tests/CMakeLists.txt'
4330--- tests/CMakeLists.txt 2016-12-06 20:16:56 +0000
4331+++ tests/CMakeLists.txt 2017-01-25 16:04:08 +0000
4332@@ -7,8 +7,10 @@
4333 add_meta_test(uitests)
4334 add_meta_test(xvfbuitests)
4335
4336-add_meta_test(alltests DEPENDS unittests uitests)
4337-add_meta_test(xvfballtests DEPENDS unittests xvfbuitests)
4338+# Run our meta-meta tests serially because we don't need to nest
4339+# parallelized tests.
4340+add_meta_test(alltests SERIAL DEPENDS unittests uitests)
4341+add_meta_test(xvfballtests SERIAL DEPENDS unittests xvfbuitests)
4342
4343 # Support libraries and plugins
4344 add_subdirectory(mocks)
4345@@ -42,8 +44,8 @@
4346
4347 set(ld_paths)
4348 list(APPEND ld_paths
4349+ ${UNITY_MOCKPATH}/liblightdm
4350 ${UNITY_MOCKPATH}/libusermetrics
4351- ${UNITY_MOCKPATH}/LightDM/IntegratedLightDM/liblightdm
4352 )
4353
4354 string(REPLACE ";" ":" ld_library_path "${ld_paths}")
4355
4356=== modified file 'tests/autopilot/unity8/fixture_setup.py'
4357--- tests/autopilot/unity8/fixture_setup.py 2015-10-26 20:15:08 +0000
4358+++ tests/autopilot/unity8/fixture_setup.py 2017-01-25 16:04:08 +0000
4359@@ -74,7 +74,7 @@
4360 def _get_lightdm_mock_path(self):
4361 lib_path = get_mocks_library_path()
4362 lightdm_mock_path = os.path.abspath(
4363- os.path.join(lib_path, "LightDM" ,"IntegratedLightDM", "liblightdm")
4364+ os.path.join(lib_path, "liblightdm")
4365 )
4366
4367 if not os.path.exists(lightdm_mock_path):
4368
4369=== modified file 'tests/autopilot/unity8/shell/tests/__init__.py'
4370--- tests/autopilot/unity8/shell/tests/__init__.py 2016-10-25 13:11:19 +0000
4371+++ tests/autopilot/unity8/shell/tests/__init__.py 2017-01-25 16:04:08 +0000
4372@@ -264,7 +264,7 @@
4373 def _get_lightdm_mock_path(self):
4374 lib_path = get_mocks_library_path()
4375 lightdm_mock_path = os.path.abspath(
4376- os.path.join(lib_path, "LightDM" ,"IntegratedLightDM", "liblightdm")
4377+ os.path.join(lib_path, "liblightdm")
4378 )
4379
4380 if not os.path.exists(lightdm_mock_path):
4381
4382=== modified file 'tests/mocks/AccountsService/AccountsService.cpp'
4383--- tests/mocks/AccountsService/AccountsService.cpp 2016-08-04 14:05:54 +0000
4384+++ tests/mocks/AccountsService/AccountsService.cpp 2017-01-25 16:04:08 +0000
4385@@ -15,7 +15,7 @@
4386 */
4387
4388 #include "AccountsService.h"
4389-#include "MockUsersModel.h"
4390+#include "UsersModel.h"
4391
4392 #include <QLightDM/UsersModel>
4393 #include <paths.h>
4394@@ -33,9 +33,8 @@
4395 m_demoEdgesCompleted(),
4396 m_hereEnabled(false),
4397 m_hereLicensePath(""),
4398- m_usersModel(new MockUsersModel(this))
4399+ m_usersModel(new UsersModel(this))
4400 {
4401- m_usersModel->setMockMode("full");
4402 }
4403
4404 QString AccountsService::user() const
4405
4406=== modified file 'tests/mocks/AccountsService/AccountsService.h'
4407--- tests/mocks/AccountsService/AccountsService.h 2016-12-23 11:04:53 +0000
4408+++ tests/mocks/AccountsService/AccountsService.h 2017-01-25 16:04:08 +0000
4409@@ -14,15 +14,14 @@
4410 * along with this program. If not, see <http://www.gnu.org/licenses/>.
4411 */
4412
4413-#ifndef UNITY_MOCK_ACCOUNTSSERVICE_H
4414-#define UNITY_MOCK_ACCOUNTSSERVICE_H
4415+#pragma once
4416
4417 #include <QObject>
4418 #include <QString>
4419 #include <QStringList>
4420 #include <QVariant>
4421
4422-class MockUsersModel;
4423+class UsersModel;
4424
4425 class AccountsService: public QObject
4426 {
4427@@ -165,7 +164,5 @@
4428 QString m_realName;
4429 QStringList m_kbdMap;
4430 QString m_email;
4431- MockUsersModel *m_usersModel;
4432+ UsersModel *m_usersModel;
4433 };
4434-
4435-#endif
4436
4437=== modified file 'tests/mocks/AccountsService/CMakeLists.txt'
4438--- tests/mocks/AccountsService/CMakeLists.txt 2016-12-06 20:16:56 +0000
4439+++ tests/mocks/AccountsService/CMakeLists.txt 2017-01-25 16:04:08 +0000
4440@@ -1,14 +1,15 @@
4441 include_directories(
4442 ${CMAKE_CURRENT_BINARY_DIR}
4443 ${CMAKE_SOURCE_DIR}/plugins/LightDM
4444+ ${CMAKE_SOURCE_DIR}/plugins/LightDM/IntegratedLightDM
4445 ${CMAKE_SOURCE_DIR}/plugins/Utils
4446- ${CMAKE_SOURCE_DIR}/tests/mocks/LightDM/IntegratedLightDM
4447 )
4448
4449 add_library(MockAccountsService-qml MODULE
4450+ ${CMAKE_SOURCE_DIR}/plugins/LightDM/Greeter.cpp
4451+ ${CMAKE_SOURCE_DIR}/plugins/LightDM/PromptsModel.cpp
4452 ${CMAKE_SOURCE_DIR}/plugins/LightDM/UsersModel.cpp
4453 ${CMAKE_SOURCE_DIR}/plugins/Utils/unitysortfilterproxymodelqml.cpp
4454- ${CMAKE_SOURCE_DIR}/tests/mocks/LightDM/IntegratedLightDM/MockUsersModel.cpp
4455 AccountsService.cpp
4456 plugin.cpp
4457 )
4458
4459=== modified file 'tests/mocks/CMakeLists.txt'
4460--- tests/mocks/CMakeLists.txt 2016-12-06 20:16:56 +0000
4461+++ tests/mocks/CMakeLists.txt 2017-01-25 16:04:08 +0000
4462@@ -32,8 +32,9 @@
4463 add_subdirectory(Cursor)
4464 add_subdirectory(GSettings.1.0)
4465 add_subdirectory(indicator-service)
4466+add_subdirectory(liblightdm)
4467 add_subdirectory(libusermetrics)
4468-add_subdirectory(LightDM)
4469+add_subdirectory(LightDMController)
4470 add_subdirectory(Lights)
4471 add_subdirectory(MeeGo)
4472 add_subdirectory(Powerd)
4473
4474=== modified file 'tests/mocks/GSettings.1.0/fake_gsettings.cpp'
4475--- tests/mocks/GSettings.1.0/fake_gsettings.cpp 2016-11-16 05:54:50 +0000
4476+++ tests/mocks/GSettings.1.0/fake_gsettings.cpp 2017-01-25 16:04:08 +0000
4477@@ -29,6 +29,7 @@
4478 , m_edgeDragWidth(2)
4479 , m_enableLauncher(true)
4480 , m_enableIndicatorMenu(true)
4481+ , m_appstoreUri("http://uappexplorer.com")
4482 {
4483 }
4484
4485@@ -173,6 +174,11 @@
4486 }
4487 }
4488
4489+QString GSettingsControllerQml::appstoreUri() const
4490+{
4491+ return m_appstoreUri;
4492+}
4493+
4494 GSettingsSchemaQml::GSettingsSchemaQml(QObject *parent): QObject(parent) {
4495 }
4496
4497@@ -376,6 +382,14 @@
4498 }
4499 }
4500
4501+QVariant GSettingsQml::appstoreUri() const
4502+{
4503+ if (m_valid && m_schema->id() == "com.canonical.Unity8") {
4504+ return GSettingsControllerQml::instance()->appstoreUri();
4505+ }
4506+ return QVariant();
4507+}
4508+
4509 void GSettingsQml::setLifecycleExemptAppids(const QVariant &appIds)
4510 {
4511 if (m_valid && m_schema->id() == "com.canonical.qtmir") {
4512
4513=== modified file 'tests/mocks/GSettings.1.0/fake_gsettings.h'
4514--- tests/mocks/GSettings.1.0/fake_gsettings.h 2016-11-16 05:54:50 +0000
4515+++ tests/mocks/GSettings.1.0/fake_gsettings.h 2017-01-25 16:04:08 +0000
4516@@ -59,6 +59,7 @@
4517 Q_PROPERTY(QVariant edgeDragWidth READ edgeDragWidth WRITE setEdgeDragWidth NOTIFY edgeDragWidthChanged)
4518 Q_PROPERTY(QVariant enableLauncher READ enableLauncher WRITE setEnableLauncher NOTIFY enableLauncherChanged)
4519 Q_PROPERTY(QVariant enableIndicatorMenu READ enableIndicatorMenu WRITE setEnableIndicatorMenu NOTIFY enableIndicatorMenuChanged)
4520+ Q_PROPERTY(QVariant appstoreUri READ appstoreUri NOTIFY appstoreUriChanged)
4521
4522 public:
4523 GSettingsQml(QObject *parent = nullptr);
4524@@ -77,6 +78,7 @@
4525 QVariant edgeDragWidth() const;
4526 QVariant enableLauncher() const;
4527 QVariant enableIndicatorMenu() const;
4528+ QVariant appstoreUri() const;
4529
4530 void setDisableHeight(const QVariant &val);
4531 void setPictureUri(const QVariant &str);
4532@@ -101,6 +103,7 @@
4533 void edgeDragWidthChanged();
4534 void enableLauncherChanged();
4535 void enableIndicatorMenuChanged();
4536+ void appstoreUriChanged();
4537
4538 private:
4539 GSettingsSchemaQml* m_schema;
4540@@ -147,6 +150,8 @@
4541 bool enableIndicatorMenu() const;
4542 Q_INVOKABLE void setEnableIndicatorMenu(bool enableIndicatorMenu);
4543
4544+ QString appstoreUri() const;
4545+
4546 Q_SIGNALS:
4547 void disableHeightChanged();
4548 void pictureUriChanged(const QString&);
4549@@ -158,6 +163,7 @@
4550 void edgeDragWidthChanged(uint edgeDragWidth);
4551 void enableLauncherChanged(bool enableLauncher);
4552 void enableIndicatorMenuChanged(bool enableIndicatorMenu);
4553+ void appstoreUriChanged(const QString &appstoreUri);
4554
4555 private:
4556 GSettingsControllerQml();
4557@@ -172,6 +178,7 @@
4558 uint m_edgeDragWidth;
4559 bool m_enableLauncher;
4560 bool m_enableIndicatorMenu;
4561+ QString m_appstoreUri;
4562
4563 static GSettingsControllerQml* s_controllerInstance;
4564 QList<GSettingsQml *> m_registeredGSettings;
4565
4566=== removed directory 'tests/mocks/LightDM'
4567=== removed file 'tests/mocks/LightDM/CMakeLists.txt'
4568--- tests/mocks/LightDM/CMakeLists.txt 2015-09-25 13:01:00 +0000
4569+++ tests/mocks/LightDM/CMakeLists.txt 1970-01-01 00:00:00 +0000
4570@@ -1,1 +0,0 @@
4571-add_subdirectory(IntegratedLightDM)
4572
4573=== removed directory 'tests/mocks/LightDM/IntegratedLightDM'
4574=== removed file 'tests/mocks/LightDM/IntegratedLightDM/CMakeLists.txt'
4575--- tests/mocks/LightDM/IntegratedLightDM/CMakeLists.txt 2016-07-14 13:04:10 +0000
4576+++ tests/mocks/LightDM/IntegratedLightDM/CMakeLists.txt 1970-01-01 00:00:00 +0000
4577@@ -1,50 +0,0 @@
4578-# This is a copy of the normal LightDM plugin, but instead of statically
4579-# linking in the lightdm bits, this one uses shared libraries so we can swap
4580-# out different sets of users for different tests. When we finally switch to
4581-# actually using the system liblightdm in the normal plugin, this version can
4582-# be deleted.
4583-
4584-add_subdirectory(liblightdm)
4585-
4586-include_directories(
4587- ${CMAKE_CURRENT_SOURCE_DIR}
4588- ${CMAKE_CURRENT_BINARY_DIR}
4589- ${CMAKE_SOURCE_DIR}/plugins/Utils
4590- ${CMAKE_SOURCE_DIR}/plugins/LightDM
4591- ${CMAKE_SOURCE_DIR}/tests/mocks/libusermetrics
4592- ${libunity8-private_SOURCE_DIR}
4593-)
4594-
4595-set(QMLPLUGIN_SRC
4596- ${CMAKE_SOURCE_DIR}/plugins/LightDM/DBusGreeter.cpp
4597- ${CMAKE_SOURCE_DIR}/plugins/LightDM/DBusGreeterList.cpp
4598- ${CMAKE_SOURCE_DIR}/plugins/LightDM/Greeter.cpp
4599- ${CMAKE_SOURCE_DIR}/plugins/LightDM/SessionsModel.cpp
4600- ${CMAKE_SOURCE_DIR}/plugins/LightDM/UsersModel.cpp
4601- ${CMAKE_SOURCE_DIR}/plugins/Utils/unitysortfilterproxymodelqml.cpp
4602- MockGreeter.cpp
4603- MockSessionsModel.cpp
4604- MockUsersModel.cpp
4605- plugin.cpp
4606- )
4607-
4608-add_library(MockLightDM-qml MODULE
4609- ${QMLPLUGIN_SRC}
4610- )
4611-
4612-# We want to link to liblightdm-qt5-3, but we don't want to depend on it being
4613-# installed on the system. So we make sure we link to our full fake version
4614-# At run time, we can point to whichever version we happen to be using via
4615-# LD_LIBRARY_PATH.
4616-target_link_libraries(MockLightDM-qml
4617- MockLightDM
4618- MockUserMetrics
4619- unity8-private
4620- )
4621-
4622-qt5_use_modules(MockLightDM-qml DBus Gui Qml)
4623-
4624-add_unity8_mock(LightDM.IntegratedLightDM 0.1 LightDM/IntegratedLightDM
4625- TARGETS MockLightDM-qml
4626- ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_CURRENT_BINARY_DIR}/liblightdm"
4627-)
4628
4629=== removed file 'tests/mocks/LightDM/IntegratedLightDM/MockGreeter.cpp'
4630--- tests/mocks/LightDM/IntegratedLightDM/MockGreeter.cpp 2016-06-09 21:45:09 +0000
4631+++ tests/mocks/LightDM/IntegratedLightDM/MockGreeter.cpp 1970-01-01 00:00:00 +0000
4632@@ -1,51 +0,0 @@
4633-/*
4634- * Copyright (C) 2014 Canonical, Ltd.
4635- *
4636- * This program is free software; you can redistribute it and/or modify
4637- * it under the terms of the GNU General Public License as published by
4638- * the Free Software Foundation; version 3.
4639- *
4640- * This program is distributed in the hope that it will be useful,
4641- * but WITHOUT ANY WARRANTY; without even the implied warranty of
4642- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4643- * GNU General Public License for more details.
4644- *
4645- * You should have received a copy of the GNU General Public License
4646- * along with this program. If not, see <http://www.gnu.org/licenses/>.
4647- *
4648- */
4649-
4650-#include "MockGreeter.h"
4651-#include <GreeterPrivate.h>
4652-
4653-QString MockGreeter::mockMode() const
4654-{
4655- Q_D(const Greeter);
4656- return d->m_greeter->mockMode();
4657-}
4658-
4659-void MockGreeter::setMockMode(QString mockMode)
4660-{
4661- Q_D(Greeter);
4662-
4663- if (d->m_greeter->mockMode() != mockMode) {
4664- d->m_greeter->setMockMode(mockMode);
4665- Q_EMIT mockModeChanged(mockMode);
4666- }
4667-}
4668-
4669-QString MockGreeter::selectUserHint() const
4670-{
4671- Q_D(const Greeter);
4672- return d->m_greeter->selectUserHint();
4673-}
4674-
4675-void MockGreeter::setSelectUserHint(const QString &selectUserHint)
4676-{
4677- Q_D(Greeter);
4678-
4679- if (d->m_greeter->selectUserHint() != selectUserHint) {
4680- d->m_greeter->setSelectUserHint(selectUserHint);
4681- Q_EMIT selectUserHintChanged();
4682- }
4683-}
4684
4685=== removed file 'tests/mocks/LightDM/IntegratedLightDM/MockGreeter.h'
4686--- tests/mocks/LightDM/IntegratedLightDM/MockGreeter.h 2016-06-09 21:45:09 +0000
4687+++ tests/mocks/LightDM/IntegratedLightDM/MockGreeter.h 1970-01-01 00:00:00 +0000
4688@@ -1,42 +0,0 @@
4689-/*
4690- * Copyright (C) 2014 Canonical, Ltd.
4691- *
4692- * This program is free software; you can redistribute it and/or modify
4693- * it under the terms of the GNU General Public License as published by
4694- * the Free Software Foundation; version 3.
4695- *
4696- * This program is distributed in the hope that it will be useful,
4697- * but WITHOUT ANY WARRANTY; without even the implied warranty of
4698- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4699- * GNU General Public License for more details.
4700- *
4701- * You should have received a copy of the GNU General Public License
4702- * along with this program. If not, see <http://www.gnu.org/licenses/>.
4703- *
4704- */
4705-
4706-// The real, production, Greeter
4707-#include <Greeter.h>
4708-
4709-#ifndef MOCK_UNITY_GREETER_H
4710-#define MOCK_UNITY_GREETER_H
4711-
4712-class MockGreeter : public Greeter {
4713- Q_OBJECT
4714-
4715- Q_PROPERTY(QString mockMode READ mockMode WRITE setMockMode NOTIFY mockModeChanged)
4716- Q_PROPERTY(QString selectUser READ selectUserHint WRITE setSelectUserHint NOTIFY selectUserHintChanged)
4717-
4718-public:
4719- QString mockMode() const;
4720- void setMockMode(QString mockMode);
4721-
4722- QString selectUserHint() const;
4723- void setSelectUserHint(const QString &selectUserHint);
4724-
4725-Q_SIGNALS:
4726- void mockModeChanged(QString mode);
4727- void selectUserHintChanged();
4728-};
4729-
4730-#endif // MOCK_UNITY_GREETER_H
4731
4732=== removed file 'tests/mocks/LightDM/IntegratedLightDM/MockSessionsModel.cpp'
4733--- tests/mocks/LightDM/IntegratedLightDM/MockSessionsModel.cpp 2016-07-13 20:24:24 +0000
4734+++ tests/mocks/LightDM/IntegratedLightDM/MockSessionsModel.cpp 1970-01-01 00:00:00 +0000
4735@@ -1,66 +0,0 @@
4736-/*
4737- * Copyright (C) 2015 Canonical, Ltd.
4738- *
4739- * This program is free software; you can redistribute it and/or modify
4740- * it under the terms of the GNU General Public License as published by
4741- * the Free Software Foundation; version 3.
4742- *
4743- * This program is distributed in the hope that it will be useful,
4744- * but WITHOUT ANY WARRANTY; without even the implied warranty of
4745- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4746- * GNU General Public License for more details.
4747- *
4748- * You should have received a copy of the GNU General Public License
4749- * along with this program. If not, see <http://www.gnu.org/licenses/>.
4750- *
4751- */
4752-
4753-#include "MockSessionsModel.h"
4754-#include <QLightDM/SessionsModel>
4755-
4756-
4757-int MockSessionsModel::numSessions() const
4758-{
4759- QLightDM::SessionsModel* qSessionsModel =
4760- static_cast<QLightDM::SessionsModel*>(sourceModel());
4761-
4762- return qSessionsModel->numSessions();
4763-}
4764-
4765-int MockSessionsModel::numAvailableSessions() const
4766-{
4767- QLightDM::SessionsModel* qSessionsModel =
4768- static_cast<QLightDM::SessionsModel*>(sourceModel());
4769-
4770- return qSessionsModel->numAvailableSessions();
4771-}
4772-
4773-QString MockSessionsModel::testScenario() const
4774-{
4775- QLightDM::SessionsModel* qSessionsModel =
4776- static_cast<QLightDM::SessionsModel*>(sourceModel());
4777-
4778- return qSessionsModel->testScenario();
4779-}
4780-
4781-void MockSessionsModel::setNumSessions(const int numSessions)
4782-{
4783- QLightDM::SessionsModel* qSessionsModel =
4784- static_cast<QLightDM::SessionsModel*>(sourceModel());
4785-
4786- if (qSessionsModel->numSessions() != numSessions) {
4787- qSessionsModel->setNumSessions(numSessions);
4788- Q_EMIT numSessionsChanged();
4789- }
4790-}
4791-
4792-void MockSessionsModel::setTestScenario(const QString testScenario)
4793-{
4794- QLightDM::SessionsModel* qSessionsModel =
4795- static_cast<QLightDM::SessionsModel*>(sourceModel());
4796-
4797- if (qSessionsModel->testScenario() != testScenario) {
4798- qSessionsModel->setTestScenario(testScenario);
4799- Q_EMIT testScenarioChanged();
4800- }
4801-}
4802
4803=== removed file 'tests/mocks/LightDM/IntegratedLightDM/MockSessionsModel.h'
4804--- tests/mocks/LightDM/IntegratedLightDM/MockSessionsModel.h 2016-07-13 20:24:24 +0000
4805+++ tests/mocks/LightDM/IntegratedLightDM/MockSessionsModel.h 1970-01-01 00:00:00 +0000
4806@@ -1,44 +0,0 @@
4807-/*
4808- * Copyright (C) 2015 Canonical, Ltd.
4809- *
4810- * This program is free software; you can redistribute it and/or modify
4811- * it under the terms of the GNU General Public License as published by
4812- * the Free Software Foundation; version 3.
4813- *
4814- * This program is distributed in the hope that it will be useful,
4815- * but WITHOUT ANY WARRANTY; without even the implied warranty of
4816- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4817- * GNU General Public License for more details.
4818- *
4819- * You should have received a copy of the GNU General Public License
4820- * along with this program. If not, see <http://www.gnu.org/licenses/>.
4821- *
4822- */
4823-
4824-#ifndef MOCK_UNITY_SESSIONSMODEL_H
4825-#define MOCK_UNITY_SESSIONSMODEL_H
4826-
4827-#include <SessionsModel.h>
4828-
4829-class MockSessionsModel : public SessionsModel
4830-{
4831- Q_OBJECT
4832-
4833- Q_PROPERTY(int numAvailableSessions READ numAvailableSessions CONSTANT)
4834- Q_PROPERTY(int numSessions READ numSessions WRITE setNumSessions NOTIFY numSessionsChanged)
4835- Q_PROPERTY(QString testScenario READ testScenario WRITE setTestScenario NOTIFY testScenarioChanged)
4836-
4837-public:
4838- int numAvailableSessions() const;
4839- int numSessions() const;
4840- QString testScenario() const;
4841- void setNumSessions(const int numSessions);
4842- void setTestScenario(const QString testScenario);
4843-
4844-Q_SIGNALS:
4845- void numSessionsChanged();
4846- void testScenarioChanged();
4847-
4848-};
4849-
4850-#endif // MOCK_UNITY_SESSIONSMODEL_H
4851
4852=== removed file 'tests/mocks/LightDM/IntegratedLightDM/MockUsersModel.cpp'
4853--- tests/mocks/LightDM/IntegratedLightDM/MockUsersModel.cpp 2016-08-04 14:05:54 +0000
4854+++ tests/mocks/LightDM/IntegratedLightDM/MockUsersModel.cpp 1970-01-01 00:00:00 +0000
4855@@ -1,44 +0,0 @@
4856-/*
4857- * Copyright (C) 2014 Canonical, Ltd.
4858- *
4859- * This program is free software; you can redistribute it and/or modify
4860- * it under the terms of the GNU General Public License as published by
4861- * the Free Software Foundation; version 3.
4862- *
4863- * This program is distributed in the hope that it will be useful,
4864- * but WITHOUT ANY WARRANTY; without even the implied warranty of
4865- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4866- * GNU General Public License for more details.
4867- *
4868- * You should have received a copy of the GNU General Public License
4869- * along with this program. If not, see <http://www.gnu.org/licenses/>.
4870- *
4871- */
4872-
4873-#include "MockUsersModel.h"
4874-#include <QLightDM/UsersModel>
4875-#include <QSortFilterProxyModel>
4876-
4877-MockUsersModel::MockUsersModel(QObject* parent)
4878- : UsersModel(parent)
4879-{
4880-}
4881-
4882-QString MockUsersModel::mockMode() const
4883-{
4884- QLightDM::UsersModel* qUsersModel =
4885- static_cast<QLightDM::UsersModel*>(static_cast<QSortFilterProxyModel*>(sourceModel())->sourceModel());
4886-
4887- return qUsersModel->mockMode();
4888-}
4889-
4890-void MockUsersModel::setMockMode(QString mockMode)
4891-{
4892- QLightDM::UsersModel* qUsersModel =
4893- static_cast<QLightDM::UsersModel*>(static_cast<QSortFilterProxyModel*>(sourceModel())->sourceModel());
4894-
4895- if (qUsersModel->mockMode() != mockMode) {
4896- qUsersModel->setMockMode(mockMode);
4897- Q_EMIT mockModeChanged(mockMode);
4898- }
4899-}
4900
4901=== removed file 'tests/mocks/LightDM/IntegratedLightDM/MockUsersModel.h'
4902--- tests/mocks/LightDM/IntegratedLightDM/MockUsersModel.h 2016-08-04 14:05:54 +0000
4903+++ tests/mocks/LightDM/IntegratedLightDM/MockUsersModel.h 1970-01-01 00:00:00 +0000
4904@@ -1,39 +0,0 @@
4905-/*
4906- * Copyright (C) 2014 Canonical, Ltd.
4907- *
4908- * This program is free software; you can redistribute it and/or modify
4909- * it under the terms of the GNU General Public License as published by
4910- * the Free Software Foundation; version 3.
4911- *
4912- * This program is distributed in the hope that it will be useful,
4913- * but WITHOUT ANY WARRANTY; without even the implied warranty of
4914- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4915- * GNU General Public License for more details.
4916- *
4917- * You should have received a copy of the GNU General Public License
4918- * along with this program. If not, see <http://www.gnu.org/licenses/>.
4919- *
4920- */
4921-
4922-#ifndef MOCK_UNITY_USERSMODEL_H
4923-#define MOCK_UNITY_USERSMODEL_H
4924-
4925-#include <UsersModel.h>
4926-
4927-class MockUsersModel : public UsersModel
4928-{
4929- Q_OBJECT
4930-
4931- Q_PROPERTY(QString mockMode READ mockMode WRITE setMockMode NOTIFY mockModeChanged)
4932-
4933-public:
4934- explicit MockUsersModel(QObject* parent=0);
4935-
4936- QString mockMode() const;
4937- void setMockMode(QString mockMode);
4938-
4939-Q_SIGNALS:
4940- void mockModeChanged(QString mode);
4941-};
4942-
4943-#endif // MOCK_UNITY_USERSMODEL_H
4944
4945=== removed directory 'tests/mocks/LightDM/IntegratedLightDM/QLightDM'
4946=== removed file 'tests/mocks/LightDM/IntegratedLightDM/QLightDM/Greeter'
4947--- tests/mocks/LightDM/IntegratedLightDM/QLightDM/Greeter 2015-01-20 11:50:19 +0000
4948+++ tests/mocks/LightDM/IntegratedLightDM/QLightDM/Greeter 1970-01-01 00:00:00 +0000
4949@@ -1,17 +0,0 @@
4950-/*
4951- * Copyright (C) 2014 Canonical, Ltd.
4952- *
4953- * This program is free software; you can redistribute it and/or modify
4954- * it under the terms of the GNU General Public License as published by
4955- * the Free Software Foundation; version 3.
4956- *
4957- * This program is distributed in the hope that it will be useful,
4958- * but WITHOUT ANY WARRANTY; without even the implied warranty of
4959- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4960- * GNU General Public License for more details.
4961- *
4962- * You should have received a copy of the GNU General Public License
4963- * along with this program. If not, see <http://www.gnu.org/licenses/>.
4964- */
4965-
4966-#include "../liblightdm/Greeter.h"
4967
4968=== removed file 'tests/mocks/LightDM/IntegratedLightDM/QLightDM/SessionsModel'
4969--- tests/mocks/LightDM/IntegratedLightDM/QLightDM/SessionsModel 2015-11-18 03:52:01 +0000
4970+++ tests/mocks/LightDM/IntegratedLightDM/QLightDM/SessionsModel 1970-01-01 00:00:00 +0000
4971@@ -1,17 +0,0 @@
4972-/*
4973- * Copyright (C) 2015 Canonical, Ltd.
4974- *
4975- * This program is free software; you can redistribute it and/or modify
4976- * it under the terms of the GNU General Public License as published by
4977- * the Free Software Foundation; version 3.
4978- *
4979- * This program is distributed in the hope that it will be useful,
4980- * but WITHOUT ANY WARRANTY; without even the implied warranty of
4981- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4982- * GNU General Public License for more details.
4983- *
4984- * You should have received a copy of the GNU General Public License
4985- * along with this program. If not, see <http://www.gnu.org/licenses/>.
4986- */
4987-
4988-#include "../liblightdm/SessionsModel.h"
4989
4990=== removed file 'tests/mocks/LightDM/IntegratedLightDM/QLightDM/UsersModel'
4991--- tests/mocks/LightDM/IntegratedLightDM/QLightDM/UsersModel 2015-01-20 11:50:19 +0000
4992+++ tests/mocks/LightDM/IntegratedLightDM/QLightDM/UsersModel 1970-01-01 00:00:00 +0000
4993@@ -1,17 +0,0 @@
4994-/*
4995- * Copyright (C) 2014 Canonical, Ltd.
4996- *
4997- * This program is free software; you can redistribute it and/or modify
4998- * it under the terms of the GNU General Public License as published by
4999- * the Free Software Foundation; version 3.
5000- *
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches