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