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 import "." 0.1
22 
23 StyledItem {
24  id: root
25  focus: true
26 
27  property alias model: userList.model
28  property alias alphanumeric: promptList.alphanumeric
29  property int currentIndex
30  property bool locked
31  property bool waiting
32  property alias boxVerticalOffset: highlightItem.y
33  property string _realName
34 
35  readonly property int numAboveBelow: 4
36  readonly property int cellHeight: units.gu(5)
37  readonly property int highlightedHeight: highlightItem.height
38  readonly property int moveDuration: UbuntuAnimation.FastDuration
39  property string currentSession // Initially set by LightDM
40  readonly property string currentUser: userList.currentItem.username
41 
42  signal responded(string response)
43  signal selected(int index)
44  signal sessionChooserButtonClicked()
45 
46  function tryToUnlock() {
47  promptList.forceActiveFocus();
48  }
49 
50  function showError() {
51  wrongPasswordAnimation.start();
52  }
53 
54  function showFakePassword() {
55  promptList.interactive = false;
56  promptList.showFakePassword();
57  }
58 
59  theme: ThemeSettings {
60  name: "Ubuntu.Components.Themes.Ambiance"
61  }
62 
63  Keys.onUpPressed: {
64  if (currentIndex > 0) {
65  selected(currentIndex - 1);
66  }
67  event.accepted = true;
68  }
69  Keys.onDownPressed: {
70  if (currentIndex + 1 < model.count) {
71  selected(currentIndex + 1);
72  }
73  event.accepted = true;
74  }
75  Keys.onEscapePressed: {
76  selected(currentIndex);
77  event.accepted = true;
78  }
79 
80  onCurrentIndexChanged: {
81  userList.currentIndex = currentIndex;
82  }
83 
84  LoginAreaContainer {
85  id: highlightItem
86  objectName: "highlightItem"
87  anchors {
88  left: parent.left
89  leftMargin: units.gu(2)
90  right: parent.right
91  rightMargin: units.gu(2)
92  }
93 
94  height: Math.max(units.gu(15), promptList.height + units.gu(8))
95  Behavior on height { NumberAnimation { duration: root.moveDuration; easing.type: Easing.InOutQuad; } }
96 
97  Label {
98  // HACK: Work around https://github.com/ubports/unity8/issues/185
99  text: _realName ? _realName : LightDMService.greeter.authenticationUser
100  visible: userList.count == 1
101  anchors {
102  left: parent.left
103  top: parent.top
104  topMargin: units.gu(2)
105  leftMargin: units.gu(2)
106  }
107  }
108  }
109 
110  ListView {
111  id: userList
112  objectName: "userList"
113 
114  anchors.fill: parent
115  anchors.leftMargin: units.gu(2)
116  anchors.rightMargin: units.gu(2)
117 
118  preferredHighlightBegin: highlightItem.y
119  preferredHighlightEnd: highlightItem.y
120  highlightRangeMode: ListView.StrictlyEnforceRange
121  highlightMoveDuration: root.moveDuration
122  interactive: count > 1
123 
124  readonly property bool movingInternally: moveTimer.running || userList.moving
125 
126  onCurrentIndexChanged: {
127  moveTimer.start();
128  }
129 
130  delegate: Item {
131  width: userList.width
132  height: root.cellHeight
133 
134  readonly property bool belowHighlight: (userList.currentIndex < 0 && index > 0) || (userList.currentIndex >= 0 && index > userList.currentIndex)
135  readonly property int belowOffset: root.highlightedHeight - root.cellHeight
136  readonly property string userSession: session
137  readonly property string username: name
138 
139  opacity: {
140  // The goal here is to make names less and less opaque as they
141  // leave the highlight area. Can't simply use index, because
142  // that can change quickly if the user clicks at edges of
143  // list. So we use actual pixel distance.
144  var highlightDist = 0;
145  var realY = y - userList.contentY;
146  if (belowHighlight)
147  realY += belowOffset;
148  if (realY + height <= highlightItem.y)
149  highlightDist = realY + height - highlightItem.y;
150  else if (realY >= highlightItem.y + root.highlightedHeight)
151  highlightDist = realY - highlightItem.y - root.highlightedHeight;
152  else
153  return 1;
154  return 1 - Math.min(1, (Math.abs(highlightDist) + root.cellHeight) / ((root.numAboveBelow + 1) * root.cellHeight))
155  }
156 
157  FadingLabel {
158  objectName: "username" + index
159  visible: userList.count != 1 // HACK Hide username label until someone sorts out the anchoring with the keyboard-dismiss animation, Work around https://github.com/ubports/unity8/issues/185
160 
161  anchors {
162  left: parent.left
163  leftMargin: units.gu(2)
164  right: parent.right
165  rightMargin: units.gu(2)
166  bottom: parent.top
167  // Add an offset to bottomMargin for any items below the highlight
168  bottomMargin: -(units.gu(4) + (parent.belowHighlight ? parent.belowOffset : 0))
169  }
170  text: userList.currentIndex === index
171  && name === "*other"
172  && LightDMService.greeter.authenticationUser !== ""
173  ? LightDMService.greeter.authenticationUser : realName
174  color: userList.currentIndex !== index ? theme.palette.normal.raised
175  : theme.palette.normal.raisedText
176 
177  Component.onCompleted: _realName = realName
178 
179  Behavior on anchors.topMargin { NumberAnimation { duration: root.moveDuration; easing.type: Easing.InOutQuad; } }
180 
181  Rectangle {
182  id: activeIndicator
183  anchors.horizontalCenter: parent.left
184  anchors.horizontalCenterOffset: -units.gu(1)
185  anchors.verticalCenter: parent.verticalCenter
186  color: userList.currentIndex !== index ? theme.palette.normal.raised
187  : theme.palette.normal.focus
188  visible: userList.count > 1 && loggedIn
189  height: units.gu(0.5)
190  width: height
191  }
192  }
193 
194  MouseArea {
195  anchors {
196  left: parent.left
197  right: parent.right
198  top: parent.top
199  // Add an offset to topMargin for any items below the highlight
200  topMargin: parent.belowHighlight ? parent.belowOffset : 0
201  }
202  height: parent.height
203  enabled: userList.currentIndex !== index && parent.opacity > 0
204  onClicked: root.selected(index)
205 
206  Behavior on anchors.topMargin { NumberAnimation { duration: root.moveDuration; easing.type: Easing.InOutQuad; } }
207  }
208  }
209 
210  // This is needed because ListView.moving is not true if the ListView
211  // moves because of an internal event (e.g. currentIndex has changed)
212  Timer {
213  id: moveTimer
214  running: false
215  repeat: false
216  interval: root.moveDuration
217  }
218  }
219 
220  // Use an AbstractButton due to icon limitations with Button
221  AbstractButton {
222  id: sessionChooser
223  objectName: "sessionChooserButton"
224 
225  readonly property alias icon: badge.source
226 
227  visible: LightDMService.sessions.count > 1 &&
228  !LightDMService.users.data(userList.currentIndex, LightDMService.userRoles.LoggedInRole)
229 
230  height: units.gu(3.5)
231  width: units.gu(3.5)
232 
233  activeFocusOnTab: true
234  anchors {
235  right: highlightItem.right
236  rightMargin: units.gu(2)
237 
238  top: highlightItem.top
239  topMargin: units.gu(1.5)
240  }
241 
242  Rectangle {
243  id: badgeHighlight
244 
245  anchors.fill: parent
246  visible: parent.activeFocus
247  color: "transparent"
248  border.color: theme.palette.normal.focus
249  border.width: units.dp(1)
250  radius: 3
251  }
252 
253  Icon {
254  id: badge
255  anchors.fill: parent
256  anchors.margins: units.dp(3)
257  keyColor: "#ffffff" // icon providers give us white icons
258  color: theme.palette.normal.raisedSecondaryText
259  source: LightDMService.sessions.iconUrl(root.currentSession)
260  }
261 
262  Keys.onReturnPressed: {
263  sessionChooserButtonClicked();
264  event.accepted = true;
265  }
266 
267  onClicked: {
268  sessionChooserButtonClicked();
269  }
270 
271  // Refresh the icon path if looking at different places at runtime
272  // this is mainly for testing
273  Connections {
274  target: LightDMService.sessions
275  onIconSearchDirectoriesChanged: {
276  badge.source = LightDMService.sessions.iconUrl(root.currentSession)
277  }
278  }
279  }
280 
281  PromptList {
282  id: promptList
283  objectName: "promptList"
284  anchors {
285  bottom: highlightItem.bottom
286  horizontalCenter: highlightItem.horizontalCenter
287  margins: units.gu(2)
288  }
289  width: highlightItem.width - anchors.margins * 2
290 
291  onClicked: {
292  interactive = false;
293  if (root.locked) {
294  root.selected(currentIndex);
295  } else {
296  root.responded("");
297  }
298  }
299  onResponded: {
300  interactive = false;
301  root.responded(text);
302  }
303  onCanceled: {
304  interactive = false;
305  root.selected(currentIndex);
306  }
307 
308  Connections {
309  target: LightDMService.prompts
310  onModelReset: promptList.interactive = true
311  }
312  }
313 
314  WrongPasswordAnimation {
315  id: wrongPasswordAnimation
316  objectName: "wrongPasswordAnimation"
317  target: promptList
318  }
319 }