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