Unity 8
 All Classes Functions Properties
Shell.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.0
18 import AccountsService 0.1
19 import GSettings 1.0
20 import Unity.Application 0.1
21 import Ubuntu.Components 0.1
22 import Ubuntu.Gestures 0.1
23 import Unity.Launcher 0.1
24 import LightDM 0.1 as LightDM
25 import Powerd 0.1
26 import SessionBroadcast 0.1
27 import "Dash"
28 import "Greeter"
29 import "Launcher"
30 import "Panel"
31 import "Hud"
32 import "Components"
33 import "Bottombar"
34 import "Notifications"
35 import Unity.Notifications 1.0 as NotificationBackend
36 
37 FocusScope {
38  id: shell
39 
40  // this is only here to select the width / height of the window if not running fullscreen
41  property bool tablet: false
42  width: tablet ? units.gu(160) : applicationArguments.hasGeometry() ? applicationArguments.width() : units.gu(40)
43  height: tablet ? units.gu(100) : applicationArguments.hasGeometry() ? applicationArguments.height() : units.gu(71)
44 
45  property real edgeSize: units.gu(2)
46  property url defaultBackground: Qt.resolvedUrl(shell.width >= units.gu(60) ? "graphics/tablet_background.jpg" : "graphics/phone_background.jpg")
47  property url background
48  readonly property real panelHeight: panel.panelHeight
49 
50  property bool dashShown: dash.shown
51 
52  property bool sideStageEnabled: shell.width >= units.gu(100)
53  readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
54 
55  function activateApplication(appId) {
56  if (ApplicationManager.findApplication(appId)) {
57  ApplicationManager.requestFocusApplication(appId);
58  stages.show(true);
59  if (stages.locked && ApplicationManager.focusedApplicationId == appId) {
60  applicationsDisplayLoader.item.select(appId);
61  }
62  } else {
63  var execFlags = shell.sideStageEnabled ? ApplicationManager.NoFlag : ApplicationManager.ForceMainStage;
64  ApplicationManager.startApplication(appId, execFlags);
65  stages.show(false);
66  }
67  }
68 
69  Binding {
70  target: LauncherModel
71  property: "applicationManager"
72  value: ApplicationManager
73  }
74 
75  Component.onCompleted: {
76  Theme.name = "Ubuntu.Components.Themes.SuruGradient"
77  }
78 
79  GSettings {
80  id: backgroundSettings
81  schema.id: "org.gnome.desktop.background"
82  }
83  property url gSettingsPicture: backgroundSettings.pictureUri != undefined && backgroundSettings.pictureUri.length > 0 ? backgroundSettings.pictureUri : shell.defaultBackground
84  onGSettingsPictureChanged: {
85  shell.background = gSettingsPicture
86  }
87 
88  VolumeControl {
89  id: volumeControl
90  }
91 
92  Keys.onVolumeUpPressed: volumeControl.volumeUp()
93  Keys.onVolumeDownPressed: volumeControl.volumeDown()
94 
95  Item {
96  id: underlayClipper
97  anchors.fill: parent
98  anchors.rightMargin: stages.overlayWidth
99  clip: stages.overlayMode && !stages.painting
100 
101  InputFilterArea {
102  anchors.fill: parent
103  blockInput: parent.clip
104  }
105 
106  Item {
107  id: underlay
108  objectName: "underlay"
109  anchors.fill: parent
110  anchors.rightMargin: -parent.anchors.rightMargin
111 
112  // Whether the underlay is fully covered by opaque UI elements.
113  property bool fullyCovered: panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth
114 
115  // Whether the user should see the topmost application surface (if there's one at all).
116  readonly property bool applicationSurfaceShouldBeSeen: stages.shown && !stages.painting && !stages.overlayMode
117 
118  // NB! Application surfaces are stacked behind the shell one. So they can only be seen by the user
119  // through the translucent parts of the shell surface.
120  visible: !fullyCovered && !applicationSurfaceShouldBeSeen
121 
122  CrossFadeImage {
123  id: backgroundImage
124  objectName: "backgroundImage"
125 
126  anchors.fill: parent
127  source: shell.background
128  fillMode: Image.PreserveAspectCrop
129  }
130 
131  Rectangle {
132  anchors.fill: parent
133  color: "black"
134  opacity: dash.disappearingAnimationProgress
135  }
136 
137  Image {
138  anchors.fill: dash
139  source: shell.width > shell.height ? "Dash/graphics/paper_landscape.png" : "Dash/graphics/paper_portrait.png"
140  fillMode: Image.PreserveAspectCrop
141  horizontalAlignment: Image.AlignRight
142  verticalAlignment: Image.AlignTop
143  }
144 
145  Dash {
146  id: dash
147  objectName: "dash"
148 
149  available: !greeter.shown && !lockscreen.shown
150  hides: [stages, launcher, panel.indicators]
151  shown: disappearingAnimationProgress !== 1.0
152  enabled: disappearingAnimationProgress === 0.0 && edgeDemo.dashEnabled
153 
154  anchors {
155  fill: parent
156  topMargin: panel.panelHeight
157  }
158 
159  contentScale: 1.0 - 0.2 * disappearingAnimationProgress
160  opacity: 1.0 - disappearingAnimationProgress
161  property real disappearingAnimationProgress: {
162  if (greeter.shown) {
163  return greeter.showProgress;
164  } else {
165  if (stages.overlayMode) {
166  return 0;
167  }
168  return stages.showProgress
169  }
170  }
171 
172  // FIXME: only necessary because stages.showProgress and
173  // greeterRevealer.animatedProgress are not animated
174  Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
175  }
176  }
177  }
178 
179  EdgeDragArea {
180  id: stagesDragHandle
181  direction: Direction.Leftwards
182 
183  anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
184  width: shell.edgeSize
185 
186  property real progress: stages.width
187 
188  onTouchXChanged: {
189  if (status == DirectionalDragArea.Recognized) {
190  if (ApplicationManager.count == 0) {
191  progress = Math.max(stages.width - stagesDragHandle.width + touchX, stages.width * .3)
192  } else {
193  progress = stages.width - stagesDragHandle.width + touchX
194  }
195  }
196  }
197 
198  onDraggingChanged: {
199  if (!dragging) {
200  if (ApplicationManager.count > 0 && progress < stages.width - units.gu(10)) {
201  stages.show(true)
202  }
203  stagesDragHandle.progress = stages.width;
204  }
205  }
206  }
207 
208  Item {
209  id: stages
210  objectName: "stages"
211  width: parent.width
212  height: parent.height
213 
214  x: {
215  if (shown) {
216  if (overlayMode || locked) {
217  return 0;
218  }
219  return launcher.progress
220  } else {
221  return stagesDragHandle.progress
222  }
223  }
224 
225  Behavior on x { SmoothedAnimation { velocity: 600; duration: UbuntuAnimation.FastDuration } }
226 
227  property bool shown: false
228 
229  property real showProgress: overlayMode ? 0 : MathUtils.clamp(1 - x / shell.width, 0, 1)
230 
231  property bool fullyShown: x == 0
232  property bool fullyHidden: x == width
233 
234  property bool painting: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.painting : false
235  property bool fullscreen: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.fullscreen : false
236  property bool overlayMode: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.overlayMode : false
237  property int overlayWidth: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.overlayWidth : false
238  property bool locked: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.locked : false
239 
240  function show(focusApp) {
241  shown = true;
242  panel.indicators.hide();
243  edgeDemo.stopDemo();
244  greeter.hide();
245  if (!ApplicationManager.focusedApplicationId && ApplicationManager.count > 0 && focusApp) {
246  ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
247  }
248  }
249 
250  function hide() {
251  shown = false;
252  if (ApplicationManager.focusedApplicationId) {
253  ApplicationManager.unfocusCurrentApplication();
254  }
255  }
256 
257  Connections {
258  target: ApplicationManager
259 
260  onFocusRequested: {
261  stages.show(true);
262  }
263 
264  onFocusedApplicationIdChanged: {
265  if (ApplicationManager.focusedApplicationId.length > 0) {
266  stages.show(false);
267  } else {
268  if (!stages.overlayMode) {
269  stages.hide();
270  }
271  }
272  }
273 
274  onApplicationAdded: {
275  stages.show(false);
276  }
277 
278  onApplicationRemoved: {
279  if (ApplicationManager.focusedApplicationId.length == 0) {
280  stages.hide();
281  }
282  }
283  }
284 
285  Loader {
286  id: applicationsDisplayLoader
287  anchors.fill: parent
288 
289  source: shell.sideStageEnabled ? "Stages/StageWithSideStage.qml" : "Stages/PhoneStage.qml"
290 
291  Binding {
292  target: applicationsDisplayLoader.item
293  property: "moving"
294  value: !stages.fullyShown
295  }
296  Binding {
297  target: applicationsDisplayLoader.item
298  property: "shown"
299  value: stages.shown
300  }
301  Binding {
302  target: applicationsDisplayLoader.item
303  property: "dragAreaWidth"
304  value: shell.edgeSize
305  }
306  }
307  }
308 
309  Lockscreen {
310  id: lockscreen
311  objectName: "lockscreen"
312 
313  readonly property int backgroundTopMargin: -panel.panelHeight
314 
315  hides: [launcher, panel.indicators, hud]
316  shown: false
317  enabled: true
318  showAnimation: StandardAnimation { property: "opacity"; to: 1 }
319  hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
320  y: panel.panelHeight
321  x: required ? 0 : - width
322  width: parent.width
323  height: parent.height - panel.panelHeight
324  background: shell.background
325  pinLength: 4
326 
327  onEntered: LightDM.Greeter.respond(passphrase);
328  onCancel: greeter.show()
329 
330  Component.onCompleted: {
331  if (LightDM.Users.count == 1) {
332  LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
333  }
334  }
335  }
336 
337  Connections {
338  target: LightDM.Greeter
339 
340  onShowPrompt: {
341  if (LightDM.Users.count == 1) {
342  // TODO: There's no better way for now to determine if its a PIN or a passphrase.
343  if (text == "PIN") {
344  lockscreen.alphaNumeric = false
345  } else {
346  lockscreen.alphaNumeric = true
347  }
348  lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
349  lockscreen.show();
350  }
351  }
352 
353  onAuthenticationComplete: {
354  if (LightDM.Greeter.promptless) {
355  return;
356  }
357  if (LightDM.Greeter.authenticated) {
358  lockscreen.hide();
359  } else {
360  lockscreen.clear(true);
361  }
362  }
363  }
364 
365  Greeter {
366  id: greeter
367  objectName: "greeter"
368 
369  available: true
370  hides: [launcher, panel.indicators, hud]
371  shown: true
372 
373  defaultBackground: shell.background
374 
375  y: panel.panelHeight
376  width: parent.width
377  height: parent.height - panel.panelHeight
378 
379  dragHandleWidth: shell.edgeSize
380 
381  onShownChanged: {
382  if (shown) {
383  lockscreen.reset();
384  // If there is only one user, we start authenticating with that one here.
385  // If there are more users, the Greeter will handle that
386  if (LightDM.Users.count == 1) {
387  LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
388  }
389  greeter.forceActiveFocus();
390  }
391  }
392 
393  onUnlocked: greeter.hide()
394  onSelected: {
395  // Update launcher items for new user
396  var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
397  AccountsService.user = user;
398  LauncherModel.setUser(user);
399  }
400 
401  onLeftTeaserPressedChanged: {
402  if (leftTeaserPressed) {
403  launcher.tease();
404  }
405  }
406 
407  Binding {
408  target: ApplicationManager
409  property: "suspended"
410  value: greeter.shown && greeter.showProgress == 1
411  }
412  }
413 
414  InputFilterArea {
415  anchors.fill: parent
416  blockInput: ApplicationManager.focusedApplicationId.length === 0 || greeter.shown || lockscreen.shown || launcher.shown
417  || panel.indicators.shown || hud.shown
418  }
419 
420  Connections {
421  id: powerConnection
422  target: Powerd
423 
424  onDisplayPowerStateChange: {
425  // We ignore any display-off signals when the proximity sensor
426  // is active. This usually indicates something like a phone call.
427  if (status == Powerd.Off && (flags & Powerd.UseProximity) == 0) {
428  greeter.showNow();
429  }
430 
431  // No reason to chew demo CPU when user isn't watching
432  if (status == Powerd.Off) {
433  edgeDemo.paused = true;
434  } else if (status == Powerd.On) {
435  edgeDemo.paused = false;
436  }
437  }
438  }
439 
440  function showHome() {
441  var animate = !greeter.shown && !stages.shown
442  greeter.hide()
443  dash.setCurrentScope("clickscope", animate, false)
444  stages.hide()
445  }
446 
447  function hideIndicatorMenu(delay) {
448  panel.hideIndicatorMenu(delay);
449  }
450 
451  Item {
452  id: overlay
453 
454  anchors.fill: parent
455 
456  Panel {
457  id: panel
458  anchors.fill: parent //because this draws indicator menus
459  indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
460  indicators {
461  hides: [launcher]
462  available: edgeDemo.panelEnabled
463  contentEnabled: edgeDemo.panelContentEnabled
464  }
465  property string focusedAppId: ApplicationManager.focusedApplicationId
466  property var focusedApplication: ApplicationManager.findApplication(focusedAppId)
467  fullscreenMode: focusedApplication && stages.fullscreen && !greeter.shown && !lockscreen.shown
468  searchVisible: !greeter.shown && !lockscreen.shown && dash.shown && dash.searchable
469 
470  InputFilterArea {
471  anchors {
472  top: parent.top
473  left: parent.left
474  right: parent.right
475  }
476  height: (panel.fullscreenMode) ? shell.edgeSize : panel.panelHeight
477  blockInput: true
478  }
479  }
480 
481  Hud {
482  id: hud
483 
484  width: parent.width > units.gu(60) ? units.gu(40) : parent.width
485  height: parent.height
486 
487  available: !greeter.shown && !panel.indicators.shown && !lockscreen.shown && edgeDemo.dashEnabled
488  shown: false
489  showAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: 0; easing.type: Easing.Linear }
490  hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
491 
492  Connections {
493  target: ApplicationManager
494  onFocusedApplicationIdChanged: hud.hide()
495  }
496  }
497 
498  Revealer {
499  id: hudRevealer
500 
501  enabled: hud.shown
502  width: hud.width
503  anchors.left: hud.left
504  height: parent.height
505  target: hud.revealerTarget
506  closedValue: height
507  openedValue: 0
508  direction: Qt.RightToLeft
509  orientation: Qt.Vertical
510  handleSize: hud.handleHeight
511  onCloseClicked: target.hide()
512  }
513 
514  Bottombar {
515  id: bottombar
516  theHud: hud
517  anchors.fill: parent
518  enabled: hud.available
519  applicationIsOnForeground: ApplicationManager.focusedApplicationId
520  }
521 
522  InputFilterArea {
523  blockInput: launcher.shown
524  anchors {
525  top: parent.top
526  bottom: parent.bottom
527  left: parent.left
528  }
529  width: launcher.width
530  }
531 
532  Launcher {
533  id: launcher
534 
535  readonly property bool dashSwipe: progress > 0
536 
537  anchors.top: parent.top
538  anchors.bottom: parent.bottom
539  width: parent.width
540  dragAreaWidth: shell.edgeSize
541  available: (!greeter.shown || greeter.narrowMode) && edgeDemo.launcherEnabled
542 
543  onShowDashHome: {
544  if (edgeDemo.running)
545  return;
546 
547  showHome()
548  }
549  onDash: {
550  if (stages.shown && !stages.overlayMode) {
551  if (!stages.locked) {
552  stages.hide();
553  launcher.hide();
554  }
555  }
556  }
557  onDashSwipeChanged: if (dashSwipe && stages.shown) dash.setCurrentScope("clickscope", false, true)
558  onLauncherApplicationSelected: {
559  if (!edgeDemo.running)
560  shell.activateApplication(appId)
561  }
562  onShownChanged: {
563  if (shown) {
564  panel.indicators.hide()
565  hud.hide()
566  bottombar.hide()
567  }
568  }
569  }
570 
571  Notifications {
572  id: notifications
573 
574  model: NotificationBackend.Model
575  margin: units.gu(1)
576 
577  anchors {
578  top: parent.top
579  right: parent.right
580  bottom: parent.bottom
581  topMargin: panel.panelHeight
582  }
583  states: [
584  State {
585  name: "narrow"
586  when: overlay.width <= units.gu(60)
587  AnchorChanges { target: notifications; anchors.left: parent.left }
588  },
589  State {
590  name: "wide"
591  when: overlay.width > units.gu(60)
592  AnchorChanges { target: notifications; anchors.left: undefined }
593  PropertyChanges { target: notifications; width: units.gu(38) }
594  }
595  ]
596 
597  InputFilterArea {
598  anchors { left: parent.left; right: parent.right }
599  height: parent.contentHeight
600  blockInput: height > 0
601  }
602  }
603  }
604 
605  focus: true
606  onFocusChanged: if (!focus) forceActiveFocus();
607 
608  InputFilterArea {
609  anchors {
610  top: parent.top
611  bottom: parent.bottom
612  left: parent.left
613  }
614  width: shell.edgeSize
615  blockInput: true
616  }
617 
618  InputFilterArea {
619  anchors {
620  top: parent.top
621  bottom: parent.bottom
622  right: parent.right
623  }
624  width: shell.edgeSize
625  blockInput: true
626  }
627 
628  Binding {
629  target: i18n
630  property: "domain"
631  value: "unity8"
632  }
633 
634  OSKController {
635  anchors.topMargin: panel.panelHeight
636  anchors.fill: parent // as needs to know the geometry of the shell
637  }
638 
639  //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
640  MouseArea {
641  anchors.bottom: parent.bottom
642  anchors.left: parent.left
643  anchors.right: parent.right
644  height: ApplicationManager.keyboardVisible ? ApplicationManager.keyboardHeight : 0
645 
646  enabled: ApplicationManager.keyboardVisible
647  }
648 
649  Label {
650  anchors.centerIn: parent
651  visible: ApplicationManager.fake ? ApplicationManager.fake : false
652  text: "EARLY ALPHA\nNOT READY FOR USE"
653  color: "lightgrey"
654  opacity: 0.2
655  font.weight: Font.Black
656  horizontalAlignment: Text.AlignHCenter
657  verticalAlignment: Text.AlignVCenter
658  fontSizeMode: Text.Fit
659  rotation: -45
660  scale: Math.min(parent.width, parent.height) / width
661  }
662 
663  EdgeDemo {
664  id: edgeDemo
665  greeter: greeter
666  launcher: launcher
667  dash: dash
668  indicators: panel.indicators
669  underlay: underlay
670  }
671 
672  Connections {
673  target: SessionBroadcast
674  onShowHome: showHome()
675  }
676 }