on 60de064 help sign in
unity8/Greeter/LoginList.qml
321 lines 11 kB history raw
1/*
2 * Copyright (C) 2013 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
17import QtQuick 2.0
18import Ubuntu.Components 0.1
19import LightDM 0.1 as LightDM
21Item {
22 id: root
24 property alias userList: userList
25 property alias model: userList.model
26 property alias currentIndex: userList.currentIndex
28 readonly property int numAboveBelow: 4
29 readonly property int cellHeight: units.gu(5)
30 readonly property int highlightedHeight: units.gu(10)
31 readonly property int moveDuration: 200
32 property bool wasPrompted: false
34 signal selected(int uid)
35 signal unlocked(int uid)
37 Keys.onEscapePressed: root.resetAuthentication()
39 Rectangle {
40 id: highlightItem
41 anchors {
42 left: parent.left
43 right: parent.right
44 verticalCenter: parent.verticalCenter
45 }
46 height: root.highlightedHeight
47 color: Qt.rgba(0.1, 0.1, 0.1, 0.4)
48 border.color: Qt.rgba(0.4, 0.4, 0.4, 0.4)
49 border.width: units.dp(1)
50 radius: units.gu(1.5)
51 antialiasing: true
52 }
54 ListView {
55 id: userList
56 objectName: "userList"
58 anchors.fill: parent
60 preferredHighlightBegin: userList.height / 2 - root.highlightedHeight / 2
61 preferredHighlightEnd: userList.height / 2 - root.highlightedHeight / 2
62 highlightRangeMode: ListView.StrictlyEnforceRange
63 highlightMoveDuration: root.moveDuration
64 flickDeceleration: 10000
66 readonly property bool movingInternally: moveTimer.running || userList.moving
68 onCountChanged: {
69 // Start authentication when we start up
70 if (!LightDM.Greeter.authenticationUser && userList.currentItem) {
71 root.selected(userList.currentIndex);
72 root.resetAuthentication();
73 }
74 }
76 onCurrentIndexChanged: {
77 if (LightDM.Greeter.authenticationUser != userList.model.data(currentIndex, LightDM.UserRoles.NameRole)) {
78 root.resetAuthentication();
79 }
80 }
82 onMovingInternallyChanged: {
83 // Only emit the selected signal once we stop moving to avoid frequent background changes
84 if (!movingInternally) {
85 root.selected(userList.currentIndex);
86 }
87 }
89 delegate: Item {
90 width: parent.width
91 height: root.cellHeight
93 readonly property bool belowHighlight: (userList.currentIndex < 0 && index > 0) || (userList.currentIndex >= 0 && index > userList.currentIndex)
94 readonly property int belowOffset: root.highlightedHeight - root.cellHeight
96 opacity: {
97 // The goal here is to make names less and less opaque as they
98 // leave the highlight area. Can't simply use index, because
99 // that can change quickly if the user clicks at edges of
100 // list. So we use actual pixel distance.
101 var highlightDist = 0;
102 var realY = y - userList.contentY;
103 if (belowHighlight)
104 realY += belowOffset;
105 if (realY + height <= highlightItem.y)
106 highlightDist = realY + height - highlightItem.y;
107 else if (realY >= highlightItem.y + root.highlightedHeight)
108 highlightDist = realY - highlightItem.y - root.highlightedHeight;
109 else
110 return 1;
111 return 1 - Math.min(1, (Math.abs(highlightDist) + root.cellHeight) / ((root.numAboveBelow + 1) * root.cellHeight))
112 }
114 Label {
115 objectName: "username" + index
117 anchors {
118 left: parent.left
119 leftMargin: units.gu(2)
120 right: parent.right
121 rightMargin: units.gu(2)
122 top: parent.top
123 // Add an offset to topMargin for any items below the highlight
124 topMargin: units.gu(1) + (parent.belowHighlight ? parent.belowOffset : 0)
125 }
126 text: realName
127 color: "white"
128 elide: Text.ElideRight
130 Behavior on anchors.topMargin { NumberAnimation { duration: root.moveDuration; easing.type: Easing.InOutQuad; } }
131 }
133 MouseArea {
134 anchors {
135 left: parent.left
136 right: parent.right
137 top: parent.top
138 // Add an offset to topMargin for any items below the highlight
139 topMargin: parent.belowHighlight ? parent.belowOffset : 0
140 }
141 height: parent.height
142 enabled: userList.currentIndex !== index
143 onClicked: {
144 moveTimer.start();
145 userList.currentIndex = index;
146 }
148 Behavior on anchors.topMargin { NumberAnimation { duration: root.moveDuration; easing.type: Easing.InOutQuad; } }
149 }
150 }
152 // This is needed because ListView.moving is not true if the ListView
153 // moves because of an internal event (e.g. currentIndex has changed)
154 Timer {
155 id: moveTimer
156 running: false
157 repeat: false
158 interval: root.moveDuration
159 }
160 }
162 Label {
163 id: infoLabel
164 objectName: "infoLabel"
165 anchors {
166 bottom: passwordInput.top
167 left: parent.left
168 topMargin: units.gu(1)
169 bottomMargin: units.gu(1)
170 leftMargin: units.gu(2)
171 rightMargin: units.gu(1)
172 }
174 color: "white"
175 width: root.width - anchors.leftMargin - anchors.rightMargin
176 fontSize: "small"
177 textFormat: Text.StyledText
178 clip: true
180 opacity: (userList.movingInternally || text == "") ? 0 : 1
181 Behavior on opacity {
182 NumberAnimation { duration: 100 }
183 }
184 }
186 TextField {
187 id: passwordInput
188 objectName: "passwordInput"
189 anchors {
190 bottom: highlightItem.bottom
191 horizontalCenter: parent.horizontalCenter
192 margins: units.gu(1)
193 }
194 height: units.gu(4.5)
195 width: parent.width - anchors.margins * 2
196 opacity: userList.movingInternally ? 0 : 1
198 Behavior on opacity {
199 NumberAnimation { duration: 100 }
200 }
202 onAccepted: {
203 if (!enabled)
204 return;
205 root.focus = true; // so that it can handle Escape presses for us
206 enabled = false;
207 LightDM.Greeter.respond(text);
208 }
209 Keys.onEscapePressed: root.resetAuthentication()
211 Image {
212 anchors {
213 right: parent.right
214 rightMargin: units.gu(2)
215 verticalCenter: parent.verticalCenter
216 }
217 visible: LightDM.Greeter.promptless
218 source: "graphics/icon_arrow.png"
219 }
221 SequentialAnimation {
222 id: wrongPasswordAnimation
223 NumberAnimation {
224 target: passwordInput
225 property: "anchors.horizontalCenterOffset"
226 duration: 50
227 easing.type: Easing.InQuad
228 to: units.gu(2)
229 }
230 NumberAnimation {
231 target: passwordInput
232 property: "anchors.horizontalCenterOffset"
233 duration: 100
234 easing.type: Easing.InOutQuad
235 to: -units.gu(2)
236 }
237 NumberAnimation {
238 target: passwordInput
239 easing.type: Easing.OutElastic
240 properties: "anchors.horizontalCenterOffset"
241 to: 0
242 duration: 400
243 easing.overshoot: units.gu(2)
244 }
245 }
247 Connections {
248 target: Qt.inputMethod
249 onVisibleChanged: {
250 if (!Qt.inputMethod.visible) {
251 passwordInput.focus = false;
252 }
253 }
254 }
256 }
258 MouseArea {
259 anchors.fill: passwordInput
260 enabled: LightDM.Greeter.promptless
261 onClicked: {
262 if (LightDM.Greeter.authenticated)
263 root.unlocked(userList.currentIndex);
264 else
265 root.resetAuthentication();
266 }
267 }
269 function resetAuthentication() {
270 if (!userList.currentItem) {
271 return;
272 }
273 infoLabel.text = "";
274 passwordInput.placeholderText = "";
275 passwordInput.text = "";
276 passwordInput.focus = false;
277 passwordInput.enabled = true;
278 root.wasPrompted = false;
279 LightDM.Greeter.authenticate(userList.model.data(currentIndex, LightDM.UserRoles.NameRole));
280 }
282 Connections {
283 target: LightDM.Greeter
285 onShowMessage: {
286 // inefficient, but we only rarely deal with messages
287 text = text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\n/g, "<br>");
288 if (isError)
289 text = "<font color=\"#df382c\">" + text + "</font>"
290 if (infoLabel.text == "")
291 infoLabel.text = text;
292 else
293 infoLabel.text = infoLabel.text + "<br>" + text;
294 }
296 onShowPrompt: {
297 passwordInput.text = "";
298 passwordInput.placeholderText = text;
299 passwordInput.enabled = true;
300 passwordInput.echoMode = isSecret ? TextInput.Password : TextInput.Normal;
301 if (root.wasPrompted) // stay in text field if second prompt
302 passwordInput.focus = true;
303 root.wasPrompted = true;
304 }
306 onAuthenticationComplete: {
307 if (LightDM.Greeter.promptless) {
308 passwordInput.placeholderText = LightDM.Greeter.authenticated ? "Tap to unlock" : "Retry";
309 return;
310 }
311 if (LightDM.Greeter.authenticated) {
312 root.unlocked(userList.currentIndex);
313 } else {
314 wrongPasswordAnimation.start();
315 root.resetAuthentication();
316 passwordInput.focus = true;
317 }
318 passwordInput.text = "";
319 }
320 }