Unity 8
 All Classes Functions Properties
LoginList.qml
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  */
16 
17 import QtQuick 2.0
18 import Ubuntu.Components 0.1
19 import LightDM 0.1 as LightDM
20 import "../Components"
21 
22 Item {
23  id: root
24 
25  property alias userList: userList
26  property alias model: userList.model
27  property alias currentIndex: userList.currentIndex
28 
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
34 
35  signal selected(int uid)
36  signal unlocked(int uid)
37 
38  Keys.onEscapePressed: root.resetAuthentication()
39 
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  }
54 
55  ListView {
56  id: userList
57  objectName: "userList"
58 
59  anchors.fill: parent
60 
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
66 
67  readonly property bool movingInternally: moveTimer.running || userList.moving
68 
69  onCurrentIndexChanged: {
70  if (LightDM.Greeter.authenticationUser != userList.model.data(currentIndex, LightDM.UserRoles.NameRole)) {
71  root.resetAuthentication();
72  }
73  }
74 
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  }
81 
82  delegate: Item {
83  width: parent.width
84  height: root.cellHeight
85 
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
88 
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  }
106 
107  Label {
108  objectName: "username" + index
109 
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
122 
123  Behavior on anchors.topMargin { NumberAnimation { duration: root.moveDuration; easing.type: Easing.InOutQuad; } }
124  }
125 
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  }
140 
141  Behavior on anchors.topMargin { NumberAnimation { duration: root.moveDuration; easing.type: Easing.InOutQuad; } }
142  }
143  }
144 
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  }
154 
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  }
166 
167  color: "white"
168  width: root.width - anchors.leftMargin - anchors.rightMargin
169  fontSize: "small"
170  textFormat: Text.StyledText
171  clip: true
172 
173  opacity: (userList.movingInternally || text == "") ? 0 : 1
174  Behavior on opacity {
175  NumberAnimation { duration: 100 }
176  }
177  }
178 
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
190 
191  Behavior on opacity {
192  NumberAnimation { duration: 100 }
193  }
194 
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()
204 
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  }
214 
215  WrongPasswordAnimation {
216  id: wrongPasswordAnimation
217  target: passwordInput
218  }
219 
220  Connections {
221  target: Qt.inputMethod
222  onVisibleChanged: {
223  if (!Qt.inputMethod.visible) {
224  passwordInput.focus = false;
225  }
226  }
227  }
228 
229  }
230 
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  }
241 
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  }
254 
255  Connections {
256  target: LightDM.Greeter
257 
258  onShowMessage: {
259  // inefficient, but we only rarely deal with messages
260  text = text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\n/g, "<br>");
261  if (isError)
262  text = "<font color=\"#df382c\">" + text + "</font>"
263  if (infoLabel.text == "")
264  infoLabel.text = text;
265  else
266  infoLabel.text = infoLabel.text + "<br>" + text;
267  }
268 
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  }
278 
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  }
293 
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  }
305 }