[gnome-shell] Add a new lock screen menu to combine volume network and power



commit c1de2788b1714cf11d2bf380dcf9c3681916a01f
Author: Giovanni Campagna <gcampagna src gnome org>
Date:   Sun Aug 26 16:05:46 2012 +0200

    Add a new lock screen menu to combine volume network and power
    
    The design has a combined volume-network-power indicator in the lock
    screen, which when opened shows a volume slider. Implement it by abstracting
    the volume menu into a PopupMenuSection, and by creating three StIcons
    bound to the real ones.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=682540

 js/Makefile.am                 |    1 +
 js/ui/panel.js                 |   12 ++--
 js/ui/panelMenu.js             |    3 +-
 js/ui/sessionMode.js           |   11 +++--
 js/ui/status/lockScreenMenu.js |   62 +++++++++++++++++++++++++
 js/ui/status/network.js        |   30 ++++--------
 js/ui/status/power.js          |   22 +++++----
 js/ui/status/volume.js         |   99 ++++++++++++++++++++++++++++------------
 8 files changed, 172 insertions(+), 68 deletions(-)
---
diff --git a/js/Makefile.am b/js/Makefile.am
index 92e0e59..3300981 100644
--- a/js/Makefile.am
+++ b/js/Makefile.am
@@ -85,6 +85,7 @@ nobase_dist_js_DATA = 	\
 	ui/shellDBus.js		\
 	ui/status/accessibility.js	\
 	ui/status/keyboard.js	\
+	ui/status/lockScreenMenu.js	\
 	ui/status/network.js	\
 	ui/status/power.js	\
 	ui/status/volume.js	\
diff --git a/js/ui/panel.js b/js/ui/panel.js
index 943079f..631a0a1 100644
--- a/js/ui/panel.js
+++ b/js/ui/panel.js
@@ -908,7 +908,7 @@ const Panel = new Lang.Class({
                                                   reactive: true });
         this.actor._delegate = this;
 
-        this._statusArea = {};
+        this.statusArea = {};
 
         Main.overview.connect('shown', Lang.bind(this, function () {
             this.actor.add_style_class_name('in-overview');
@@ -1126,7 +1126,7 @@ const Panel = new Lang.Class({
     },
 
     addToStatusArea: function(role, indicator, position) {
-        if (this._statusArea[role])
+        if (this.statusArea[role])
             throw new Error('Extension point conflict: there is already a status indicator for role ' + role);
 
         if (!(indicator instanceof PanelMenu.Button))
@@ -1138,9 +1138,9 @@ const Panel = new Lang.Class({
         if (indicator.menu)
             this._menus.addMenu(indicator.menu);
 
-        this._statusArea[role] = indicator;
+        this.statusArea[role] = indicator;
         let destroyId = indicator.connect('destroy', Lang.bind(this, function(emitter) {
-            delete this._statusArea[role];
+            delete this.statusArea[role];
             emitter.disconnect(destroyId);
         }));
 
@@ -1155,7 +1155,7 @@ const Panel = new Lang.Class({
         if (this._dateMenu)
             this._dateMenu.setLockedState(locked);
 
-        for (let id in this._statusArea)
-            this._statusArea[id].setLockedState(locked);
+        for (let id in this.statusArea)
+            this.statusArea[id].setLockedState(locked);
     },
 });
diff --git a/js/ui/panelMenu.js b/js/ui/panelMenu.js
index 29007bf..3e48f62 100644
--- a/js/ui/panelMenu.js
+++ b/js/ui/panelMenu.js
@@ -236,7 +236,8 @@ const SystemStatusButton = new Lang.Class({
         this._box = new St.BoxLayout({ style_class: 'panel-status-button-box' });
         this.actor.add_actor(this._box);
 
-        this.setIcon(iconName);
+        if (iconName)
+            this.setIcon(iconName);
     },
 
     addIcon: function(gicon) {
diff --git a/js/ui/sessionMode.js b/js/ui/sessionMode.js
index 1cc4ec3..55cfed7 100644
--- a/js/ui/sessionMode.js
+++ b/js/ui/sessionMode.js
@@ -11,6 +11,7 @@ const STANDARD_STATUS_AREA_SHELL_IMPLEMENTATION = {
     'a11y': imports.ui.status.accessibility.ATIndicator,
     'volume': imports.ui.status.volume.Indicator,
     'battery': imports.ui.status.power.Indicator,
+    'lockScreen': imports.ui.status.lockScreenMenu.Indicator,
     'keyboard': imports.ui.status.keyboard.InputSourceIndicator,
     'userMenu': imports.ui.userMenu.UserMenuButton
 };
@@ -44,12 +45,13 @@ const _modes = {
              statusArea: {
                  order: [
                      'a11y', 'display', 'keyboard',
-                     'volume', 'battery', 'powerMenu'
+                     'volume', 'battery', 'lockScreen', 'powerMenu'
                  ],
                  implementation: {
                      'a11y': imports.ui.status.accessibility.ATIndicator,
                      'volume': imports.ui.status.volume.Indicator,
                      'battery': imports.ui.status.power.Indicator,
+                     'lockScreen': imports.ui.status.lockScreenMenu.Indicator,
                      'keyboard': imports.ui.status.keyboard.InputSourceIndicator,
                      'powerMenu': imports.gdm.powerMenu.PowerMenuButton
                  }
@@ -68,12 +70,13 @@ const _modes = {
                        extraStylesheet: null,
                        statusArea: {
                            order: [
-                               'a11y', 'keyboard', 'volume'
+                               'a11y', 'keyboard', 'volume', 'lockScreen',
                            ],
                            implementation: {
                                'a11y': imports.ui.status.accessibility.ATIndicator,
                                'keyboard': imports.ui.status.keyboard.XKBIndicator,
-                               'volume': imports.ui.status.volume.Indicator
+                               'volume': imports.ui.status.volume.Indicator,
+                               'lockScreen': imports.ui.status.lockScreenMenu.Indicator,
                         }
                 }
            },
@@ -92,7 +95,7 @@ const _modes = {
               statusArea: {
                   order: [
                       'input-method', 'a11y', 'keyboard', 'volume', 'bluetooth',
-                      'network', 'battery', 'userMenu'
+                      'network', 'battery', 'lockScreen', 'userMenu'
                   ],
                   implementation: STANDARD_STATUS_AREA_SHELL_IMPLEMENTATION
               }
diff --git a/js/ui/status/lockScreenMenu.js b/js/ui/status/lockScreenMenu.js
new file mode 100644
index 0000000..6f8b897
--- /dev/null
+++ b/js/ui/status/lockScreenMenu.js
@@ -0,0 +1,62 @@
+// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
+
+const Clutter = imports.gi.Clutter;
+const GObject = imports.gi.GObject;
+const Lang = imports.lang;
+const St = imports.gi.St;
+
+const Main = imports.ui.main;
+const PanelMenu = imports.ui.panelMenu;
+const PopupMenu = imports.ui.popupMenu;
+const VolumeMenu = imports.ui.status.volume;
+
+const Indicator = new Lang.Class({
+    Name: 'LockScreenMenuIndicator',
+    Extends: PanelMenu.SystemStatusButton,
+
+    _init: function() {
+        this.parent(null, _("Volume, network, battery"));
+        this.actor.hide();
+
+        this._volume = Main.panel.statusArea.volume;
+        if (this._volume) {
+            this._volumeIcon = this.addIcon(null);
+            this._volume.mainIcon.bind_property('gicon', this._volumeIcon, 'gicon',
+                                                GObject.BindingFlags.SYNC_CREATE);
+            this._volume.mainIcon.bind_property('visible', this._volumeIcon, 'visible',
+                                                GObject.BindingFlags.SYNC_CREATE);
+
+            this._volumeControl = VolumeMenu.getMixerControl();
+            this._volumeMenu = new VolumeMenu.VolumeMenu(this._volumeControl);
+            this.menu.addMenuItem(this._volumeMenu);
+        }
+
+        this._network = Main.panel.statusArea.network;
+        if (this._network) {
+            this._networkIcon = this.addIcon(null);
+            this._network.mainIcon.bind_property('gicon', this._networkIcon, 'gicon',
+                                                 GObject.BindingFlags.SYNC_CREATE);
+            this._network.mainIcon.bind_property('visible', this._networkIcon, 'visible',
+                                                 GObject.BindingFlags.SYNC_CREATE);
+
+            this._networkSecondaryIcon = this.addIcon(null);
+            this._network.secondaryIcon.bind_property('gicon', this._networkSecondaryIcon, 'gicon',
+                                                      GObject.BindingFlags.SYNC_CREATE);
+            this._network.secondaryIcon.bind_property('visible', this._networkSecondaryIcon, 'visible',
+                                                      GObject.BindingFlags.SYNC_CREATE);
+        }
+
+        this._battery = Main.panel.statusArea.battery;
+        if (this._battery) {
+            this._batteryIcon = this.addIcon(null);
+            this._battery.mainIcon.bind_property('gicon', this._batteryIcon, 'gicon',
+                                                 GObject.BindingFlags.SYNC_CREATE);
+            this._battery.mainIcon.bind_property('visible', this._batteryIcon, 'visible',
+                                                 GObject.BindingFlags.SYNC_CREATE);
+        }
+    },
+
+    setLockedState: function(locked) {
+        this.actor.visible = locked;
+    }
+});
diff --git a/js/ui/status/network.js b/js/ui/status/network.js
index a235bee..72cea77 100644
--- a/js/ui/status/network.js
+++ b/js/ui/status/network.js
@@ -1570,9 +1570,10 @@ const NMApplet = new Lang.Class({
     _init: function() {
         this.parent('network-offline', _('Network'));
 
-        this._secondaryIcon = this.addIcon(new Gio.ThemedIcon({ name: 'network-vpn' }));
-        this._secondaryIcon.hide();
+        this.secondaryIcon = this.addIcon(new Gio.ThemedIcon({ name: 'network-vpn' }));
+        this.secondaryIcon.hide();
 
+        this._isLocked = false;
         this._client = NMClient.Client.new();
 
         this._statusSection = new PopupMenu.PopupMenuSection();
@@ -1681,12 +1682,8 @@ const NMApplet = new Lang.Class({
     },
 
     setLockedState: function(locked) {
-        // FIXME: more design discussion is needed before we can
-        // expose part of this menu
-
-        if (locked)
-            this.menu.close();
-        this.actor.reactive = !locked;
+        this._isLocked = locked;
+        this._syncNMState();
     },
 
     _ensureSource: function() {
@@ -2074,13 +2071,8 @@ const NMApplet = new Lang.Class({
     },
 
     _syncNMState: function() {
-        if (!this._client.manager_running) {
-            log('NetworkManager is not running, hiding...');
-            this.menu.close();
-            this.actor.hide();
-            return;
-        } else
-            this.actor.show();
+        this.mainIcon.visible = this._client.manager_running;
+        this.actor.visible = this.mainIcon.visible && !this._isLocked;
 
         if (!this._client.networking_enabled) {
             this.setIcon('network-offline');
@@ -2192,14 +2184,14 @@ const NMApplet = new Lang.Class({
             // only show a separate icon when we're using a wireless/3g connection
             if (mc._section == NMConnectionCategory.WIRELESS || 
                 mc._section == NMConnectionCategory.WWAN) {
-                this._secondaryIcon.icon_name = vpnIconName;
-                this._secondaryIcon.visible = true;
+                this.secondaryIcon.icon_name = vpnIconName;
+                this.secondaryIcon.show();
             } else {
                 this.setIcon(vpnIconName);
-                this._secondaryIcon.visible = false;
+                this.secondaryIcon.hide();
             }
         } else {
-            this._secondaryIcon.visible = false;
+            this.secondaryIcon.hide();
         }
 
         // cleanup stale signal connections
diff --git a/js/ui/status/power.js b/js/ui/status/power.js
index 1c18b4d..195422f 100644
--- a/js/ui/status/power.js
+++ b/js/ui/status/power.js
@@ -56,6 +56,7 @@ const Indicator = new Lang.Class({
 
         this._proxy = new PowerManagerProxy(Gio.DBus.session, BUS_NAME, OBJECT_PATH);
 
+        this._isLocked = false;
         this._deviceItems = [ ];
         this._hasPrimary = false;
         this._primaryDeviceId = null;
@@ -77,9 +78,8 @@ const Indicator = new Lang.Class({
     },
 
     setLockedState: function(locked) {
-        if (locked)
-            this.menu.close();
-        this.actor.reactive = !locked;
+        this._isLocked = locked;
+        this._syncIcon();
     },
 
     _readPrimaryDevice: function() {
@@ -150,16 +150,20 @@ const Indicator = new Lang.Class({
         }));
     },
 
-    _devicesChanged: function() {
+    _syncIcon: function() {
         let icon = this._proxy.Icon;
-        if (icon) {
+        let hasIcon = (icon != null);
+
+        if (hasIcon) {
             let gicon = Gio.icon_new_for_string(icon);
             this.setGIcon(gicon);
-            this.actor.show();
-        } else {
-            this.menu.close();
-            this.actor.hide();
         }
+        this.mainIcon.visible = hasIcon;
+        this.actor.visible = hasIcon && !this._isLocked;
+    },
+
+    _devicesChanged: function() {
+        this._syncIcon();
         this._readPrimaryDevice();
         this._readOtherDevices();
     }
diff --git a/js/ui/status/volume.js b/js/ui/status/volume.js
index 5507887..ab383dc 100644
--- a/js/ui/status/volume.js
+++ b/js/ui/status/volume.js
@@ -12,14 +12,27 @@ const VOLUME_ADJUSTMENT_STEP = 0.05; /* Volume adjustment step in % */
 
 const VOLUME_NOTIFY_ID = 1;
 
-const Indicator = new Lang.Class({
-    Name: 'VolumeIndicator',
-    Extends: PanelMenu.SystemStatusButton,
+// Each Gvc.MixerControl is a connection to PulseAudio,
+// so it's better to make it a singleton
+let _mixerControl;
+function getMixerControl() {
+    if (_mixerControl)
+        return _mixerControl;
 
-    _init: function() {
-        this.parent('audio-volume-muted', _("Volume"));
+    _mixerControl = new Gvc.MixerControl({ name: 'GNOME Shell Volume Control' });
+    _mixerControl.open();
+
+    return _mixerControl;
+}
 
-        this._control = new Gvc.MixerControl({ name: 'GNOME Shell Volume Control' });
+const VolumeMenu = new Lang.Class({
+    Name: 'VolumeMenu',
+    Extends: PopupMenu.PopupMenuSection,
+
+    _init: function(control) {
+        this.parent();
+
+        this._control = control;
         this._control.connect('state-changed', Lang.bind(this, this._onControlStateChanged));
         this._control.connect('default-sink-changed', Lang.bind(this, this._readOutput));
         this._control.connect('default-source-changed', Lang.bind(this, this._readInput));
@@ -35,10 +48,10 @@ const Indicator = new Lang.Class({
         this._outputSlider = new PopupMenu.PopupSliderMenuItem(0);
         this._outputSlider.connect('value-changed', Lang.bind(this, this._sliderChanged, '_output'));
         this._outputSlider.connect('drag-end', Lang.bind(this, this._notifyVolumeChange));
-        this.menu.addMenuItem(this._outputTitle);
-        this.menu.addMenuItem(this._outputSlider);
+        this.addMenuItem(this._outputTitle);
+        this.addMenuItem(this._outputSlider);
 
-        this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
+        this.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
 
         this._input = null;
         this._inputVolumeId = 0;
@@ -47,22 +60,11 @@ const Indicator = new Lang.Class({
         this._inputSlider = new PopupMenu.PopupSliderMenuItem(0);
         this._inputSlider.connect('value-changed', Lang.bind(this, this._sliderChanged, '_input'));
         this._inputSlider.connect('drag-end', Lang.bind(this, this._notifyVolumeChange));
-        this.menu.addMenuItem(this._inputTitle);
-        this.menu.addMenuItem(this._inputSlider);
-
-        this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
-        this.menu.addSettingsAction(_("Sound Settings"), 'gnome-sound-panel.desktop');
-
-        this.actor.connect('scroll-event', Lang.bind(this, this._onScrollEvent));
-        this._control.open();
-    },
-
-    setLockedState: function(locked) {
-        this.menu.setSettingsVisibility(!locked);
+        this.addMenuItem(this._inputTitle);
+        this.addMenuItem(this._inputSlider);
     },
 
-    _onScrollEvent: function(actor, event) {
-        let direction = event.get_scroll_direction();
+    scroll: function(direction) {
         let currentVolume = this._output.volume;
 
         if (direction == Clutter.ScrollDirection.DOWN) {
@@ -88,9 +90,8 @@ const Indicator = new Lang.Class({
         if (this._control.get_state() == Gvc.MixerControlState.READY) {
             this._readOutput();
             this._readInput();
-            this.actor.show();
         } else {
-            this.actor.hide();
+            this.emit('icon-changed', null);
         }
     },
 
@@ -109,7 +110,7 @@ const Indicator = new Lang.Class({
             this._volumeChanged (null, null, '_output');
         } else {
             this._outputSlider.setValue(0);
-            this.setIcon('audio-volume-muted-symbolic');
+            this.emit('icon-changed', 'audio-volume-muted-symbolic');
         }
     },
 
@@ -196,15 +197,55 @@ const Indicator = new Lang.Class({
         slider.setValue(muted ? 0 : (this[property].volume / this._volumeMax));
         if (property == '_output') {
             if (muted)
-                this.setIcon('audio-volume-muted');
+                this.emit('icon-changed', 'audio-volume-muted');
             else
-                this.setIcon(this._volumeToIcon(this._output.volume));
+                this.emit('icon-changed', this._volumeToIcon(this._output.volume));
         }
     },
 
     _volumeChanged: function(object, param_spec, property) {
         this[property+'Slider'].setValue(this[property].volume / this._volumeMax);
         if (property == '_output' && !this._output.is_muted)
-            this.setIcon(this._volumeToIcon(this._output.volume));
+            this.emit('icon-changed', this._volumeToIcon(this._output.volume));
+    }
+});
+
+const Indicator = new Lang.Class({
+    Name: 'VolumeIndicator',
+    Extends: PanelMenu.SystemStatusButton,
+
+    _init: function() {
+        this.parent('audio-volume-muted', _("Volume"));
+
+        this._isLocked = false;
+
+        this._control = getMixerControl();
+        this._volumeMenu = new VolumeMenu(this._control);
+        this._volumeMenu.connect('icon-changed', Lang.bind(this, function(menu, icon) {
+            this._hasPulseAudio = (icon != null);
+            this.setIcon(icon);
+            this._syncVisibility();
+        }));
+
+        this.menu.addMenuItem(this._volumeMenu);
+
+        this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
+        this.menu.addSettingsAction(_("Sound Settings"), 'gnome-sound-panel.desktop');
+
+        this.actor.connect('scroll-event', Lang.bind(this, this._onScrollEvent));
+    },
+
+    setLockedState: function(locked) {
+        this._isLocked = locked;
+        this._syncVisibility();
+    },
+
+    _syncVisibility: function() {
+        this.actor.visible = this._hasPulseAudio && !this._isLocked;
+        this.mainIcon.visible = this._hasPulseAudio;
+    },
+
+    _onScrollEvent: function(actor, event) {
+        this._volumeMenu.scroll(event.get_scroll_direction());
     }
 });



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]