Unity 8
LoginList.qml
1 /*
2  * Copyright (C) 2013-2016 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 QtGraphicalEffects 1.0
19 import Ubuntu.Components 1.3
20 import "../Components"
21 
22 StyledItem {
23  id: root
24  focus: true
25 
26  property alias model: userList.model
27  property bool alphanumeric: true
28  property int currentIndex
29  property bool locked
30  property bool waiting
31 
32  readonly property int numAboveBelow: 4
33  readonly property int cellHeight: units.gu(5)
34  readonly property int highlightedHeight: units.gu(15)
35  readonly property int moveDuration: 200
36  readonly property string currentUser: userList.currentItem.username
37  property bool wasPrompted: false
38 
39  signal selected(int index)
40  signal responded(string response)
41 
42  function tryToUnlock() {
43  if (wasPrompted) {
44  passwordInput.forceActiveFocus();
45  } else {
46  if (root.locked) {
47  root.selected(currentIndex);
48  } else {
49  root.responded("");
50  }
51  }
52  }
53 
54  function showMessage(html) {
55  if (infoLabel.text === "") {
56  infoLabel.text = html;
57  } else {
58  infoLabel.text += "<br>" + html;
59  }
60  }
61 
62  function showPrompt(text, isSecret, isDefaultPrompt) {
63  passwordInput.text = isDefaultPrompt ? alphanumeric ? i18n.tr("Passphrase")
64  : i18n.tr("Passcode")
65  : text;
66  passwordInput.isPrompt = true;
67  passwordInput.isSecret = isSecret;
68  passwordInput.reset();
69  wasPrompted = true;
70  }
71 
72  function showError() {
73  wrongPasswordAnimation.start();
74  root.resetAuthentication();
75  }
76 
77  function reset() {
78  root.resetAuthentication();
79  }
80 
81  QtObject {
82  id: d
83 
84  function checkIfPromptless() {
85  if (!waiting && !wasPrompted) {
86  passwordInput.isPrompt = false;
87  passwordInput.text = root.locked ? i18n.tr("Retry")
88  : i18n.tr("Log In")
89  }
90  }
91  }
92 
93  onWaitingChanged: d.checkIfPromptless()
94  onLockedChanged: d.checkIfPromptless()
95 
96  theme: ThemeSettings {
97  name: "Ubuntu.Components.Themes.Ambiance"
98  }
99 
100  Keys.onUpPressed: {
101  selected(currentIndex - 1);
102  event.accepted = true;
103  }
104  Keys.onDownPressed: {
105  selected(currentIndex + 1);
106  event.accepted = true;
107  }
108  Keys.onEscapePressed: {
109  selected(currentIndex);
110  event.accepted = true;
111  }
112 
113  onCurrentIndexChanged: {
114  userList.currentIndex = currentIndex;
115  }
116 
117  BorderImage {
118  anchors {
119  fill: highlightItem
120  topMargin: -units.gu(1)
121  leftMargin: -units.gu(1.5)
122  rightMargin: -units.gu(1.5)
123  bottomMargin: -units.gu(1.5)
124  }
125  source: "../Stages/graphics/dropshadow2gu.sci"
126  opacity: 0.35
127  }
128 
129  UbuntuShape {
130  id: highlightItem
131  anchors {
132  left: parent.left
133  leftMargin: units.gu(2)
134  right: parent.right
135  rightMargin: units.gu(2)
136  verticalCenter: parent.verticalCenter
137  }
138  height: root.highlightedHeight
139  aspect: UbuntuShape.Flat
140  backgroundColor: theme.palette.normal.raised
141  }
142 
143  ListView {
144  id: userList
145  objectName: "userList"
146 
147  anchors.fill: parent
148  anchors.leftMargin: units.gu(2)
149  anchors.rightMargin: units.gu(2)
150 
151  preferredHighlightBegin: userList.height / 2 - root.highlightedHeight / 2
152  preferredHighlightEnd: userList.height / 2 - root.highlightedHeight / 2
153  highlightRangeMode: ListView.StrictlyEnforceRange
154  highlightMoveDuration: root.moveDuration
155  flickDeceleration: 10000
156  interactive: count > 1
157 
158  readonly property bool movingInternally: moveTimer.running || userList.moving
159  onMovingInternallyChanged: {
160  if (!movingInternally) {
161  root.selected(currentIndex);
162  }
163  }
164 
165  onCurrentIndexChanged: {
166  root.resetAuthentication();
167  moveTimer.start();
168  }
169 
170  delegate: Item {
171  width: parent.width
172  height: root.cellHeight
173 
174  readonly property bool belowHighlight: (userList.currentIndex < 0 && index > 0) || (userList.currentIndex >= 0 && index > userList.currentIndex)
175  readonly property int belowOffset: root.highlightedHeight - root.cellHeight
176  readonly property string username: name
177 
178  opacity: {
179  // The goal here is to make names less and less opaque as they
180  // leave the highlight area. Can't simply use index, because
181  // that can change quickly if the user clicks at edges of
182  // list. So we use actual pixel distance.
183  var highlightDist = 0;
184  var realY = y - userList.contentY;
185  if (belowHighlight)
186  realY += belowOffset;
187  if (realY + height <= highlightItem.y)
188  highlightDist = realY + height - highlightItem.y;
189  else if (realY >= highlightItem.y + root.highlightedHeight)
190  highlightDist = realY - highlightItem.y - root.highlightedHeight;
191  else
192  return 1;
193  return 1 - Math.min(1, (Math.abs(highlightDist) + root.cellHeight) / ((root.numAboveBelow + 1) * root.cellHeight))
194  }
195 
196  FadingLabel {
197  objectName: "username" + index
198 
199  anchors {
200  left: parent.left
201  leftMargin: units.gu(2)
202  right: parent.right
203  rightMargin: units.gu(2)
204  bottom: parent.top
205  // Add an offset to bottomMargin for any items below the highlight
206  bottomMargin: -(units.gu(4) + (parent.belowHighlight ? parent.belowOffset : 0))
207  }
208  text: realName
209  color: userList.currentIndex !== index ? theme.palette.normal.raised : theme.palette.normal.raisedText
210 
211  Behavior on anchors.topMargin { NumberAnimation { duration: root.moveDuration; easing.type: Easing.InOutQuad; } }
212  }
213 
214  MouseArea {
215  anchors {
216  left: parent.left
217  right: parent.right
218  top: parent.top
219  // Add an offset to topMargin for any items below the highlight
220  topMargin: parent.belowHighlight ? parent.belowOffset : 0
221  }
222  height: parent.height
223  enabled: userList.currentIndex !== index && parent.opacity > 0
224  onClicked: root.selected(index)
225 
226  Behavior on anchors.topMargin { NumberAnimation { duration: root.moveDuration; easing.type: Easing.InOutQuad; } }
227  }
228  }
229 
230  // This is needed because ListView.moving is not true if the ListView
231  // moves because of an internal event (e.g. currentIndex has changed)
232  Timer {
233  id: moveTimer
234  running: false
235  repeat: false
236  interval: root.moveDuration
237  }
238  }
239 
240  FadingLabel {
241  id: infoLabel
242  objectName: "infoLabel"
243  anchors {
244  bottom: passwordInput.top
245  left: highlightItem.left
246  topMargin: units.gu(1)
247  bottomMargin: units.gu(1)
248  leftMargin: units.gu(2)
249  rightMargin: units.gu(1)
250  }
251 
252  color: theme.palette.normal.raisedText
253  width: root.width - anchors.leftMargin - anchors.rightMargin
254  fontSize: "small"
255  textFormat: Text.StyledText
256 
257  opacity: (userList.movingInternally || text == "") ? 0 : 1
258  Behavior on opacity {
259  NumberAnimation { duration: 100 }
260  }
261  }
262 
263  GreeterPrompt {
264  id: passwordInput
265  objectName: "passwordInput"
266  anchors {
267  bottom: highlightItem.bottom
268  horizontalCenter: highlightItem.horizontalCenter
269  margins: units.gu(2)
270  }
271  width: highlightItem.width - anchors.margins * 2
272  opacity: userList.movingInternally ? 0 : 1
273 
274  isAlphanumeric: root.alphanumeric
275 
276  onClicked: root.tryToUnlock()
277  onResponded: root.responded(text)
278  onCanceled: root.selected(currentIndex)
279 
280  Behavior on opacity {
281  NumberAnimation { duration: 100 }
282  }
283 
284  WrongPasswordAnimation {
285  id: wrongPasswordAnimation
286  target: passwordInput
287  }
288  }
289 
290  function resetAuthentication() {
291  if (!userList.currentItem) {
292  return;
293  }
294  infoLabel.text = "";
295  passwordInput.reset();
296  root.wasPrompted = false;
297  }
298 }