on bbbdccd help sign in
unity8/qml/Greeter/LoginList.qml
302 lines 9.6 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.4
18import Ubuntu.Components 1.3
19import "../Components"
21Item {
22 id: root
24 property alias model: userList.model
25 property bool alphanumeric: true
26 property int currentIndex
27 property bool locked
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 readonly property string currentUser: userList.currentItem.username
34 property bool wasPrompted: false
36 signal selected(int index)
37 signal responded(string response)
39 function tryToUnlock() {
40 if (wasPrompted) {
41 passwordInput.forceActiveFocus();
42 } else {
43 if (root.locked) {
44 root.selected(currentIndex);
45 } else {
46 root.responded("");
47 }
48 }
49 }
51 function showMessage(html) {
52 if (infoLabel.text === "") {
53 infoLabel.text = html;
54 } else {
55 infoLabel.text += "<br>" + html;
56 }
57 }
59 function showPrompt(text, isSecret, isDefaultPrompt) {
60 passwordInput.text = "";
61 passwordInput.promptText = text;
62 passwordInput.enabled = true;
63 passwordInput.echoMode = isSecret ? TextInput.Password : TextInput.Normal;
64 if (wasPrompted) // stay in text field if second prompt
65 passwordInput.focus = true;
66 wasPrompted = true;
67 }
69 function showError() {
70 wrongPasswordAnimation.start();
71 root.resetAuthentication();
72 if (wasPrompted) {
73 passwordInput.focus = true;
74 }
75 }
77 function reset() {
78 root.resetAuthentication();
79 }
81 Keys.onEscapePressed: {
82 selected(currentIndex);
83 }
85 onCurrentIndexChanged: {
86 userList.currentIndex = currentIndex;
87 }
89 Rectangle {
90 id: highlightItem
91 anchors {
92 left: parent.left
93 right: parent.right
94 verticalCenter: parent.verticalCenter
95 }
96 height: root.highlightedHeight
97 color: Qt.rgba(0.1, 0.1, 0.1, 0.4)
98 border.color: Qt.rgba(0.4, 0.4, 0.4, 0.4)
99 border.width: units.dp(1)
100 radius: units.gu(1.5)
101 antialiasing: true
102 }
104 ListView {
105 id: userList
106 objectName: "userList"
108 anchors.fill: parent
110 preferredHighlightBegin: userList.height / 2 - root.highlightedHeight / 2
111 preferredHighlightEnd: userList.height / 2 - root.highlightedHeight / 2
112 highlightRangeMode: ListView.StrictlyEnforceRange
113 highlightMoveDuration: root.moveDuration
114 flickDeceleration: 10000
116 readonly property bool movingInternally: moveTimer.running || userList.moving
117 onMovingInternallyChanged: {
118 if (!movingInternally) {
119 root.selected(currentIndex);
120 }
121 }
123 onCurrentIndexChanged: {
124 root.resetAuthentication();
125 moveTimer.start();
126 }
128 delegate: Item {
129 width: parent.width
130 height: root.cellHeight
132 readonly property bool belowHighlight: (userList.currentIndex < 0 && index > 0) || (userList.currentIndex >= 0 && index > userList.currentIndex)
133 readonly property int belowOffset: root.highlightedHeight - root.cellHeight
134 readonly property string username: name
136 opacity: {
137 // The goal here is to make names less and less opaque as they
138 // leave the highlight area. Can't simply use index, because
139 // that can change quickly if the user clicks at edges of
140 // list. So we use actual pixel distance.
141 var highlightDist = 0;
142 var realY = y - userList.contentY;
143 if (belowHighlight)
144 realY += belowOffset;
145 if (realY + height <= highlightItem.y)
146 highlightDist = realY + height - highlightItem.y;
147 else if (realY >= highlightItem.y + root.highlightedHeight)
148 highlightDist = realY - highlightItem.y - root.highlightedHeight;
149 else
150 return 1;
151 return 1 - Math.min(1, (Math.abs(highlightDist) + root.cellHeight) / ((root.numAboveBelow + 1) * root.cellHeight))
152 }
154 Label {
155 objectName: "username" + index
157 anchors {
158 left: parent.left
159 leftMargin: units.gu(2)
160 right: parent.right
161 rightMargin: units.gu(2)
162 top: parent.top
163 // Add an offset to topMargin for any items below the highlight
164 topMargin: units.gu(1) + (parent.belowHighlight ? parent.belowOffset : 0)
165 }
166 text: realName
167 color: "white"
168 elide: Text.ElideRight
170 Behavior on anchors.topMargin { NumberAnimation { duration: root.moveDuration; easing.type: Easing.InOutQuad; } }
171 }
173 MouseArea {
174 anchors {
175 left: parent.left
176 right: parent.right
177 top: parent.top
178 // Add an offset to topMargin for any items below the highlight
179 topMargin: parent.belowHighlight ? parent.belowOffset : 0
180 }
181 height: parent.height
182 enabled: userList.currentIndex !== index && parent.opacity > 0
183 onClicked: root.selected(index)
185 Behavior on anchors.topMargin { NumberAnimation { duration: root.moveDuration; easing.type: Easing.InOutQuad; } }
186 }
187 }
189 // This is needed because ListView.moving is not true if the ListView
190 // moves because of an internal event (e.g. currentIndex has changed)
191 Timer {
192 id: moveTimer
193 running: false
194 repeat: false
195 interval: root.moveDuration
196 }
197 }
199 Label {
200 id: infoLabel
201 objectName: "infoLabel"
202 anchors {
203 bottom: passwordInput.top
204 left: parent.left
205 topMargin: units.gu(1)
206 bottomMargin: units.gu(1)
207 leftMargin: units.gu(2)
208 rightMargin: units.gu(1)
209 }
211 color: "white"
212 width: root.width - anchors.leftMargin - anchors.rightMargin
213 fontSize: "small"
214 textFormat: Text.StyledText
215 clip: true
217 opacity: (userList.movingInternally || text == "") ? 0 : 1
218 Behavior on opacity {
219 NumberAnimation { duration: 100 }
220 }
221 }
223 TextField {
224 id: passwordInput
225 objectName: "passwordInput"
226 anchors {
227 bottom: highlightItem.bottom
228 horizontalCenter: parent.horizontalCenter
229 margins: units.gu(1)
230 }
231 height: units.gu(4.5)
232 width: parent.width - anchors.margins * 2
233 opacity: userList.movingInternally ? 0 : 1
235 inputMethodHints: root.alphanumeric ? Qt.ImhNone : Qt.ImhDigitsOnly
237 property string promptText
238 placeholderText: root.wasPrompted ? promptText
239 : (root.locked ? i18n.tr("Retry")
240 : i18n.tr("Tap to unlock"))
242 Behavior on opacity {
243 NumberAnimation { duration: 100 }
244 }
246 onAccepted: {
247 if (!enabled)
248 return;
249 root.focus = true; // so that it can handle Escape presses for us
250 enabled = false;
251 root.responded(text);
252 }
253 Keys.onEscapePressed: {
254 root.selected(currentIndex);
255 }
257 Image {
258 anchors {
259 right: parent.right
260 rightMargin: units.gu(2)
261 verticalCenter: parent.verticalCenter
262 }
263 visible: !root.wasPrompted
264 source: "graphics/icon_arrow.png"
265 }
267 WrongPasswordAnimation {
268 id: wrongPasswordAnimation
269 target: passwordInput
270 }
272 Connections {
273 target: Qt.inputMethod
274 onVisibleChanged: {
275 if (!Qt.inputMethod.visible) {
276 passwordInput.focus = false;
277 }
278 }
279 }
281 }
283 MouseArea {
284 id: passwordMouseArea
285 objectName: "passwordMouseArea"
286 anchors.fill: passwordInput
287 enabled: !root.wasPrompted
288 onClicked: root.tryToUnlock()
289 }
291 function resetAuthentication() {
292 if (!userList.currentItem) {
293 return;
294 }
295 infoLabel.text = "";
296 passwordInput.promptText = "";
297 passwordInput.text = "";
298 passwordInput.focus = false;
299 passwordInput.enabled = true;
300 root.wasPrompted = false;
301 }