Unity 8
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.4
18 import Ubuntu.Components 1.3
19 import "../Components"
20 
21 Item {
22  id: root
23 
24  property alias model: userList.model
25  property int currentIndex
26  property bool locked
27 
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
34 
35  signal selected(int index)
36  signal responded(string response)
37 
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  }
49 
50  function showMessage(html) {
51  if (infoLabel.text === "") {
52  infoLabel.text = html;
53  } else {
54  infoLabel.text += "<br>" + html;
55  }
56  }
57 
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  }
67 
68  function showError() {
69  wrongPasswordAnimation.start();
70  root.resetAuthentication();
71  if (wasPrompted) {
72  passwordInput.focus = true;
73  }
74  }
75 
76  function reset() {
77  root.resetAuthentication();
78  }
79 
80  Keys.onEscapePressed: {
81  selected(currentIndex);
82  }
83 
84  onCurrentIndexChanged: {
85  userList.currentIndex = currentIndex;
86  }
87 
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  }
102 
103  ListView {
104  id: userList
105  objectName: "userList"
106 
107  anchors.fill: parent
108 
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
114 
115  readonly property bool movingInternally: moveTimer.running || userList.moving
116  onMovingInternallyChanged: {
117  if (!movingInternally) {
118  root.selected(currentIndex);
119  }
120  }
121 
122  onCurrentIndexChanged: {
123  root.resetAuthentication();
124  moveTimer.start();
125  }
126 
127  delegate: Item {
128  width: parent.width
129  height: root.cellHeight
130 
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
134 
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  }
152 
153  Label {
154  objectName: "username" + index
155 
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
168 
169  Behavior on anchors.topMargin { NumberAnimation { duration: root.moveDuration; easing.type: Easing.InOutQuad; } }
170  }
171 
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)
183 
184  Behavior on anchors.topMargin { NumberAnimation { duration: root.moveDuration; easing.type: Easing.InOutQuad; } }
185  }
186  }
187 
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  }
197 
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  }
209 
210  color: "white"
211  width: root.width - anchors.leftMargin - anchors.rightMargin
212  fontSize: "small"
213  textFormat: Text.StyledText
214  clip: true
215 
216  opacity: (userList.movingInternally || text == "") ? 0 : 1
217  Behavior on opacity {
218  NumberAnimation { duration: 100 }
219  }
220  }
221 
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
233 
234  property string promptText
235  placeholderText: root.wasPrompted ? promptText
236  : (root.locked ? i18n.tr("Retry")
237  : i18n.tr("Tap to unlock"))
238 
239  Behavior on opacity {
240  NumberAnimation { duration: 100 }
241  }
242 
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  }
253 
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  }
263 
264  WrongPasswordAnimation {
265  id: wrongPasswordAnimation
266  target: passwordInput
267  }
268 
269  Connections {
270  target: Qt.inputMethod
271  onVisibleChanged: {
272  if (!Qt.inputMethod.visible) {
273  passwordInput.focus = false;
274  }
275  }
276  }
277 
278  }
279 
280  MouseArea {
281  id: passwordMouseArea
282  objectName: "passwordMouseArea"
283  anchors.fill: passwordInput
284  enabled: !root.wasPrompted
285  onClicked: root.tryToUnlock()
286  }
287 
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  }
299 }