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