on aa5da49 help sign in
unity8/qml/Greeter/LoginList.qml
305 lines 10 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
20import "../Components"
22Item {
23 id: root
25 property alias userList: userList
26 property alias model: userList.model
27 property alias currentIndex: userList.currentIndex
29 readonly property int numAboveBelow: 4
30 readonly property int cellHeight: units.gu(5)
31 readonly property int highlightedHeight: units.gu(10)
32 readonly property int moveDuration: 200
33 property bool wasPrompted: false
35 signal selected(int uid)
36 signal unlocked(int uid)
38 Keys.onEscapePressed: root.resetAuthentication()
40 Rectangle {
41 id: highlightItem
42 anchors {
43 left: parent.left
44 right: parent.right
45 verticalCenter: parent.verticalCenter
46 }
47 height: root.highlightedHeight
48 color: Qt.rgba(0.1, 0.1, 0.1, 0.4)
49 border.color: Qt.rgba(0.4, 0.4, 0.4, 0.4)
50 border.width: units.dp(1)
51 radius: units.gu(1.5)
52 antialiasing: true
53 }
55 ListView {
56 id: userList
57 objectName: "userList"
59 anchors.fill: parent
61 preferredHighlightBegin: userList.height / 2 - root.highlightedHeight / 2
62 preferredHighlightEnd: userList.height / 2 - root.highlightedHeight / 2
63 highlightRangeMode: ListView.StrictlyEnforceRange
64 highlightMoveDuration: root.moveDuration
65 flickDeceleration: 10000
67 readonly property bool movingInternally: moveTimer.running || userList.moving
69 onCurrentIndexChanged: {
70 if (LightDM.Greeter.authenticationUser != userList.model.data(currentIndex, LightDM.UserRoles.NameRole)) {
71 root.resetAuthentication();
72 }
73 }
75 onMovingInternallyChanged: {
76 // Only emit the selected signal once we stop moving to avoid frequent background changes
77 if (!movingInternally) {
78 root.selected(userList.currentIndex);
79 }
80 }
82 delegate: Item {
83 width: parent.width
84 height: root.cellHeight
86 readonly property bool belowHighlight: (userList.currentIndex < 0 && index > 0) || (userList.currentIndex >= 0 && index > userList.currentIndex)
87 readonly property int belowOffset: root.highlightedHeight - root.cellHeight
89 opacity: {
90 // The goal here is to make names less and less opaque as they
91 // leave the highlight area. Can't simply use index, because
92 // that can change quickly if the user clicks at edges of
93 // list. So we use actual pixel distance.
94 var highlightDist = 0;
95 var realY = y - userList.contentY;
96 if (belowHighlight)
97 realY += belowOffset;
98 if (realY + height <= highlightItem.y)
99 highlightDist = realY + height - highlightItem.y;
100 else if (realY >= highlightItem.y + root.highlightedHeight)
101 highlightDist = realY - highlightItem.y - root.highlightedHeight;
102 else
103 return 1;
104 return 1 - Math.min(1, (Math.abs(highlightDist) + root.cellHeight) / ((root.numAboveBelow + 1) * root.cellHeight))
105 }
107 Label {
108 objectName: "username" + index
110 anchors {
111 left: parent.left
112 leftMargin: units.gu(2)
113 right: parent.right
114 rightMargin: units.gu(2)
115 top: parent.top
116 // Add an offset to topMargin for any items below the highlight
117 topMargin: units.gu(1) + (parent.belowHighlight ? parent.belowOffset : 0)
118 }
119 text: realName
120 color: "white"
121 elide: Text.ElideRight
123 Behavior on anchors.topMargin { NumberAnimation { duration: root.moveDuration; easing.type: Easing.InOutQuad; } }
124 }
126 MouseArea {
127 anchors {
128 left: parent.left
129 right: parent.right
130 top: parent.top
131 // Add an offset to topMargin for any items below the highlight
132 topMargin: parent.belowHighlight ? parent.belowOffset : 0
133 }
134 height: parent.height
135 enabled: userList.currentIndex !== index
136 onClicked: {
137 moveTimer.start();
138 userList.currentIndex = index;
139 }
141 Behavior on anchors.topMargin { NumberAnimation { duration: root.moveDuration; easing.type: Easing.InOutQuad; } }
142 }
143 }
145 // This is needed because ListView.moving is not true if the ListView
146 // moves because of an internal event (e.g. currentIndex has changed)
147 Timer {
148 id: moveTimer
149 running: false
150 repeat: false
151 interval: root.moveDuration
152 }
153 }
155 Label {
156 id: infoLabel
157 objectName: "infoLabel"
158 anchors {
159 bottom: passwordInput.top
160 left: parent.left
161 topMargin: units.gu(1)
162 bottomMargin: units.gu(1)
163 leftMargin: units.gu(2)
164 rightMargin: units.gu(1)
165 }
167 color: "white"
168 width: root.width - anchors.leftMargin - anchors.rightMargin
169 fontSize: "small"
170 textFormat: Text.StyledText
171 clip: true
173 opacity: (userList.movingInternally || text == "") ? 0 : 1
174 Behavior on opacity {
175 NumberAnimation { duration: 100 }
176 }
177 }
179 TextField {
180 id: passwordInput
181 objectName: "passwordInput"
182 anchors {
183 bottom: highlightItem.bottom
184 horizontalCenter: parent.horizontalCenter
185 margins: units.gu(1)
186 }
187 height: units.gu(4.5)
188 width: parent.width - anchors.margins * 2
189 opacity: userList.movingInternally ? 0 : 1
191 Behavior on opacity {
192 NumberAnimation { duration: 100 }
193 }
195 onAccepted: {
196 if (text == "") return; // Might be useful anyway, but mainly workaround for LP 1324159
197 if (!enabled)
198 return;
199 root.focus = true; // so that it can handle Escape presses for us
200 enabled = false;
201 LightDM.Greeter.respond(text);
202 }
203 Keys.onEscapePressed: root.resetAuthentication()
205 Image {
206 anchors {
207 right: parent.right
208 rightMargin: units.gu(2)
209 verticalCenter: parent.verticalCenter
210 }
211 visible: LightDM.Greeter.promptless
212 source: "graphics/icon_arrow.png"
213 }
215 WrongPasswordAnimation {
216 id: wrongPasswordAnimation
217 target: passwordInput
218 }
220 Connections {
221 target: Qt.inputMethod
222 onVisibleChanged: {
223 if (!Qt.inputMethod.visible) {
224 passwordInput.focus = false;
225 }
226 }
227 }
229 }
231 MouseArea {
232 anchors.fill: passwordInput
233 enabled: LightDM.Greeter.promptless
234 onClicked: {
235 if (LightDM.Greeter.authenticated)
236 root.unlocked(userList.currentIndex);
237 else
238 root.resetAuthentication();
239 }
240 }
242 function resetAuthentication() {
243 if (!userList.currentItem) {
244 return;
245 }
246 infoLabel.text = "";
247 passwordInput.placeholderText = "";
248 passwordInput.text = "";
249 passwordInput.focus = false;
250 passwordInput.enabled = true;
251 root.wasPrompted = false;
252 LightDM.Greeter.authenticate(userList.model.data(currentIndex, LightDM.UserRoles.NameRole));
253 }
255 Connections {
256 target: LightDM.Greeter
258 onShowMessage: {
259 // inefficient, but we only rarely deal with messages
260 var html = text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\n/g, "<br>")
261 if (isError)
262 html = "<font color=\"#df382c\">" + html + "</font>"
263 if (infoLabel.text == "")
264 infoLabel.text = html
265 else
266 infoLabel.text = infoLabel.text + "<br>" + html
267 }
269 onShowPrompt: {
270 passwordInput.text = "";
271 passwordInput.placeholderText = text;
272 passwordInput.enabled = true;
273 passwordInput.echoMode = isSecret ? TextInput.Password : TextInput.Normal;
274 if (root.wasPrompted) // stay in text field if second prompt
275 passwordInput.focus = true;
276 root.wasPrompted = true;
277 }
279 onAuthenticationComplete: {
280 if (LightDM.Greeter.promptless) {
281 passwordInput.placeholderText = LightDM.Greeter.authenticated ? "Tap to unlock" : "Retry";
282 return;
283 }
284 if (LightDM.Greeter.authenticated) {
285 root.unlocked(userList.currentIndex);
286 } else {
287 wrongPasswordAnimation.start();
288 root.resetAuthentication();
289 passwordInput.focus = true;
290 }
291 passwordInput.text = "";
292 }
294 onRequestAuthenticationUser: {
295 // Find index for requested user, if it exists
296 for (var i = 0; i < userList.model.count; i++) {
297 if (user == userList.model.data(i, LightDM.UserRoles.NameRole)) {
298 moveTimer.start();
299 userList.currentIndex = i;
300 return;
301 }
302 }
303 }
304 }