2 * Copyright (C) 2013 Canonical, Ltd.
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 import Ubuntu.Components 0.1
19 import LightDM 0.1 as LightDM
20 import "../Components"
21 import "../Components/Flickables" as Flickables
26 property alias userList: userList
27 property alias model: userList.model
28 property alias currentIndex: userList.currentIndex
30 readonly property int numAboveBelow: 4
31 readonly property int cellHeight: units.gu(5)
32 readonly property int highlightedHeight: units.gu(10)
33 readonly property int moveDuration: 200
34 property bool wasPrompted: false
36 signal selected(int uid)
37 signal unlocked(int uid)
39 function tryToUnlock() {
40 if (LightDM.Greeter.promptless) {
41 if (LightDM.Greeter.authenticated) {
42 root.unlocked(userList.currentIndex)
44 root.resetAuthentication()
47 passwordInput.forceActiveFocus()
52 root.resetAuthentication()
55 Keys.onEscapePressed: root.resetAuthentication()
62 verticalCenter: parent.verticalCenter
64 height: root.highlightedHeight
65 color: Qt.rgba(0.1, 0.1, 0.1, 0.4)
66 border.color: Qt.rgba(0.4, 0.4, 0.4, 0.4)
67 border.width: units.dp(1)
74 objectName: "userList"
78 preferredHighlightBegin: userList.height / 2 - root.highlightedHeight / 2
79 preferredHighlightEnd: userList.height / 2 - root.highlightedHeight / 2
80 highlightRangeMode: ListView.StrictlyEnforceRange
81 highlightMoveDuration: root.moveDuration
83 readonly property bool movingInternally: moveTimer.running || userList.moving
85 onCurrentIndexChanged: {
86 if (LightDM.Greeter.authenticationUser != userList.model.data(currentIndex, LightDM.UserRoles.NameRole)) {
87 root.resetAuthentication();
91 onMovingInternallyChanged: {
92 // Only emit the selected signal once we stop moving to avoid frequent background changes
93 if (!movingInternally) {
94 root.selected(userList.currentIndex);
100 height: root.cellHeight
102 readonly property bool belowHighlight: (userList.currentIndex < 0 && index > 0) || (userList.currentIndex >= 0 && index > userList.currentIndex)
103 readonly property int belowOffset: root.highlightedHeight - root.cellHeight
106 // The goal here is to make names less and less opaque as they
107 // leave the highlight area. Can't simply use index, because
108 // that can change quickly if the user clicks at edges of
109 // list. So we use actual pixel distance.
110 var highlightDist = 0;
111 var realY = y - userList.contentY;
113 realY += belowOffset;
114 if (realY + height <= highlightItem.y)
115 highlightDist = realY + height - highlightItem.y;
116 else if (realY >= highlightItem.y + root.highlightedHeight)
117 highlightDist = realY - highlightItem.y - root.highlightedHeight;
120 return 1 - Math.min(1, (Math.abs(highlightDist) + root.cellHeight) / ((root.numAboveBelow + 1) * root.cellHeight))
124 objectName: "username" + index
128 leftMargin: units.gu(2)
130 rightMargin: units.gu(2)
132 // Add an offset to topMargin for any items below the highlight
133 topMargin: units.gu(1) + (parent.belowHighlight ? parent.belowOffset : 0)
137 elide: Text.ElideRight
139 Behavior on anchors.topMargin { NumberAnimation { duration: root.moveDuration; easing.type: Easing.InOutQuad; } }
147 // Add an offset to topMargin for any items below the highlight
148 topMargin: parent.belowHighlight ? parent.belowOffset : 0
150 height: parent.height
151 enabled: userList.currentIndex !== index
154 userList.currentIndex = index;
157 Behavior on anchors.topMargin { NumberAnimation { duration: root.moveDuration; easing.type: Easing.InOutQuad; } }
161 // This is needed because ListView.moving is not true if the ListView
162 // moves because of an internal event (e.g. currentIndex has changed)
167 interval: root.moveDuration
173 objectName: "infoLabel"
175 bottom: passwordInput.top
177 topMargin: units.gu(1)
178 bottomMargin: units.gu(1)
179 leftMargin: units.gu(2)
180 rightMargin: units.gu(1)
184 width: root.width - anchors.leftMargin - anchors.rightMargin
186 textFormat: Text.StyledText
189 opacity: (userList.movingInternally || text == "") ? 0 : 1
190 Behavior on opacity {
191 NumberAnimation { duration: 100 }
197 objectName: "passwordInput"
199 bottom: highlightItem.bottom
200 horizontalCenter: parent.horizontalCenter
203 height: units.gu(4.5)
204 width: parent.width - anchors.margins * 2
205 opacity: userList.movingInternally ? 0 : 1
207 Behavior on opacity {
208 NumberAnimation { duration: 100 }
212 if (text == "") return; // Might be useful anyway, but mainly workaround for LP 1324159
215 root.focus = true; // so that it can handle Escape presses for us
217 LightDM.Greeter.respond(text);
219 Keys.onEscapePressed: root.resetAuthentication()
224 rightMargin: units.gu(2)
225 verticalCenter: parent.verticalCenter
227 visible: LightDM.Greeter.promptless
228 source: "graphics/icon_arrow.png"
231 WrongPasswordAnimation {
232 id: wrongPasswordAnimation
233 target: passwordInput
237 target: Qt.inputMethod
239 if (!Qt.inputMethod.visible) {
240 passwordInput.focus = false;
248 id: passwordMouseArea
249 objectName: "passwordMouseArea"
250 anchors.fill: passwordInput
251 enabled: LightDM.Greeter.promptless
252 onClicked: root.tryToUnlock()
255 function resetAuthentication() {
256 if (!userList.currentItem) {
260 passwordInput.placeholderText = "";
261 passwordInput.text = "";
262 passwordInput.focus = false;
263 passwordInput.enabled = true;
264 root.wasPrompted = false;
265 LightDM.Greeter.authenticate(userList.model.data(currentIndex, LightDM.UserRoles.NameRole));
269 target: LightDM.Greeter
272 // inefficient, but we only rarely deal with messages
273 var html = text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/\n/g, "<br>")
275 html = "<font color=\"#df382c\">" + html + "</font>"
276 if (infoLabel.text == "")
277 infoLabel.text = html
279 infoLabel.text = infoLabel.text + "<br>" + html
283 passwordInput.text = "";
284 passwordInput.placeholderText = text;
285 passwordInput.enabled = true;
286 passwordInput.echoMode = isSecret ? TextInput.Password : TextInput.Normal;
287 if (root.wasPrompted) // stay in text field if second prompt
288 passwordInput.focus = true;
289 root.wasPrompted = true;
292 onAuthenticationComplete: {
293 if (LightDM.Greeter.promptless) {
294 passwordInput.placeholderText = LightDM.Greeter.authenticated ? "Tap to unlock" : "Retry";
297 if (LightDM.Greeter.authenticated) {
298 root.unlocked(userList.currentIndex);
300 wrongPasswordAnimation.start();
301 root.resetAuthentication();
302 passwordInput.focus = true;
304 passwordInput.text = "";
307 onRequestAuthenticationUser: {
308 // Find index for requested user, if it exists
309 for (var i = 0; i < userList.model.count; i++) {
310 if (user == userList.model.data(i, LightDM.UserRoles.NameRole)) {
312 userList.currentIndex = i;