[gnome-shell] volume: Clean up stream/slider handling code



commit c9d0e82c52d6033ee70583717d91c3d47f6f7e9d
Author: Jasper St. Pierre <jstpierre mecheye net>
Date:   Wed Dec 19 21:57:26 2012 -0500

    volume: Clean up stream/slider handling code
    
    Rather than using naming schemes and dynamic property lookups as
    a kind of namespace, use what was designed to be used as a namespace:
    a class.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=690539

 js/ui/status/volume.js |  316 +++++++++++++++++++++++++++++-------------------
 1 files changed, 193 insertions(+), 123 deletions(-)
---
diff --git a/js/ui/status/volume.js b/js/ui/status/volume.js
index b1774fc..b08f3d6 100644
--- a/js/ui/status/volume.js
+++ b/js/ui/status/volume.js
@@ -5,6 +5,7 @@ const Lang = imports.lang;
 const Gio = imports.gi.Gio;
 const Gvc = imports.gi.Gvc;
 const St = imports.gi.St;
+const Signals = imports.signals;
 
 const PanelMenu = imports.ui.panelMenu;
 const PopupMenu = imports.ui.popupMenu;
@@ -26,61 +27,134 @@ function getMixerControl() {
     return _mixerControl;
 }
 
-const VolumeMenu = new Lang.Class({
-    Name: 'VolumeMenu',
-    Extends: PopupMenu.PopupMenuSection,
+const StreamSlider = new Lang.Class({
+    Name: 'StreamSlider',
 
-    _init: function(control) {
-        this.parent();
+    _init: function(control, title) {
+        this._control = control;
 
-        this._hasHeadphones = false;
+        this.item = new PopupMenu.PopupMenuSection();
 
-        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));
-        this._control.connect('stream-added', Lang.bind(this, this._maybeShowInput));
-        this._control.connect('stream-removed', Lang.bind(this, this._maybeShowInput));
-        this._volumeMax = this._control.get_vol_max_norm();
+        this._title = new PopupMenu.PopupMenuItem(title, { reactive: false });
+        this._slider = new PopupMenu.PopupSliderMenuItem(0);
+        this._slider.connect('value-changed', Lang.bind(this, this._sliderChanged));
+        this._slider.connect('drag-end', Lang.bind(this, this._notifyVolumeChange));
 
-        this._output = null;
-        this._outputVolumeId = 0;
-        this._outputMutedId = 0;
-        /* Translators: This is the label for audio volume */
-        this._outputTitle = new PopupMenu.PopupMenuItem(_("Volume"), { reactive: false });
-        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.addMenuItem(this._outputTitle);
-        this.addMenuItem(this._outputSlider);
+        this.item.addMenuItem(this._title);
+        this.item.addMenuItem(this._slider);
 
-        this.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
+        this._stream = null;
+        this._shouldShow = true;
+    },
 
-        this._input = null;
-        this._inputVolumeId = 0;
-        this._inputMutedId = 0;
-        this._inputTitle = new PopupMenu.PopupMenuItem(_("Microphone"), { reactive: false });
-        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.addMenuItem(this._inputTitle);
-        this.addMenuItem(this._inputSlider);
+    get stream() {
+        return this._stream;
+    },
 
-        this._onControlStateChanged();
+    set stream(stream) {
+        if (this._stream) {
+            this._disconnectStream(this._stream);
+        }
+
+        this._stream = stream;
+
+        if (this._stream) {
+            this._connectStream(this._stream);
+            this._updateVolume();
+        } else {
+            this.emit('stream-updated');
+        }
+
+        this._updateVisibility();
+    },
+
+    _disconnectStream: function(stream) {
+        stream.disconnect(this._mutedChangedId);
+        this._mutedChangedId = 0;
+        stream.disconnect(this._volumeChangedId);
+        this._volumeChangedId = 0;
+    },
+
+    _connectStream: function(stream) {
+        this._mutedChangedId = stream.connect('notify::is-muted', Lang.bind(this, this._updateVolume));
+        this._volumeChangedId = stream.connect('notify::volume', Lang.bind(this, this._updateVolume));
+    },
+
+    _shouldBeVisible: function() {
+        return this._stream != null;
+    },
+
+    _updateVisibility: function() {
+        let visible = this._shouldBeVisible();
+        this._title.actor.visible = visible;
+        this._slider.actor.visible = visible;
     },
 
     scroll: function(event) {
-        this._outputSlider.scroll(event);
+        this._slider.scroll(event);
     },
 
-    _onControlStateChanged: function() {
-        if (this._control.get_state() == Gvc.MixerControlState.READY) {
-            this._readOutput();
-            this._readInput();
-            this._maybeShowInput();
+    setValue: function(value) {
+        // piggy-back off of sliderChanged
+        this._slider.setValue(value);
+    },
+
+    _sliderChanged: function(slider, value, property) {
+        if (!this._stream)
+            return;
+
+        let volume = value * this._control.get_vol_max_norm();
+        let prevMuted = this._stream.is_muted;
+        if (volume < 1) {
+            this._stream.volume = 0;
+            if (!prevMuted)
+                this._stream.change_is_muted(true);
         } else {
-            this.emit('icon-changed');
+            this._stream.volume = volume;
+            if (prevMuted)
+                this._stream.change_is_muted(false);
         }
+        this._stream.push_volume();
+    },
+
+    _notifyVolumeChange: function() {
+        global.cancel_theme_sound(VOLUME_NOTIFY_ID);
+        global.play_theme_sound(VOLUME_NOTIFY_ID, 'audio-volume-change');
+    },
+
+    _updateVolume: function() {
+        let muted = this._stream.is_muted;
+        this._slider.setValue(muted ? 0 : (this._stream.volume / this._control.get_vol_max_norm()));
+        this.emit('stream-updated');
+    },
+
+    getIcon: function() {
+        if (!this._stream)
+            return null;
+
+        let volume = this._stream.volume;
+        if (this._stream.is_muted || volume <= 0) {
+            return 'audio-volume-muted-symbolic';
+        } else {
+            let n = Math.floor(3 * volume / this._control.get_vol_max_norm()) + 1;
+            if (n < 2)
+                return 'audio-volume-low-symbolic';
+            if (n >= 3)
+                return 'audio-volume-high-symbolic';
+            return 'audio-volume-medium-symbolic';
+        }
+    }
+});
+Signals.addSignalMethods(StreamSlider.prototype);
+
+const OutputStreamSlider = new Lang.Class({
+    Name: 'OutputStreamSlider',
+    Extends: StreamSlider,
+
+    _connectStream: function(stream) {
+        this.parent(stream);
+        this._portChangedId = stream.connect('notify::port', Lang.bind(this, this._portChanged));
+        this._portChanged();
     },
 
     _findHeadphones: function(sink) {
@@ -99,58 +173,41 @@ const VolumeMenu = new Lang.Class({
         return false;
     },
 
-    _portChanged: function() {
-        this._hasHeadphones = this._findHeadphones(this._output);
-        this.emit('headphones-changed', this._hasHeadphones);
+    _disconnectStream: function(stream) {
+        this.parent(stream);
+        stream.disconnect(this._portChangedId);
+        this._portChangedId = 0;
     },
 
-    _readOutput: function() {
-        if (this._outputVolumeId) {
-            this._output.disconnect(this._outputVolumeId);
-            this._output.disconnect(this._outputMutedId);
-            this._output.disconnect(this._outputPortId);
-            this._outputVolumeId = 0;
-            this._outputMutedId = 0;
-            this._outputPortId = 0;
-        }
-        this._output = this._control.get_default_sink();
-        if (this._output) {
-            this._outputMutedId = this._output.connect('notify::is-muted', Lang.bind(this, this._updateVolume, '_output'));
-            this._outputVolumeId = this._output.connect('notify::volume', Lang.bind(this, this._updateVolume, '_output'));
-            this._outputPortId = this._output.connect('notify::port', Lang.bind(this, this._portChanged));
-
-            this._updateVolume(null, null, '_output');
-            this._portChanged();
-        } else {
-            this.hasHeadphones = false;
-            this._outputSlider.setValue(0);
-            this.emit('icon-changed');
+    _portChanged: function() {
+        let hasHeadphones = this._findHeadphones(this._stream);
+        if (hasHeadphones != this._hasHeadphones) {
+            this._hasHeadphones = hasHeadphones;
+            this.emit('headphones-changed', this._hasHeadphones);
         }
+    }
+});
+
+const InputStreamSlider = new Lang.Class({
+    Name: 'InputStreamSlider',
+    Extends: StreamSlider,
+
+    _init: function(control, title) {
+        this.parent(control, title);
+        this._control.connect('stream-added', Lang.bind(this, this._maybeShowInput));
+        this._control.connect('stream-removed', Lang.bind(this, this._maybeShowInput));
     },
 
-    _readInput: function() {
-        if (this._inputVolumeId) {
-            this._input.disconnect(this._inputVolumeId);
-            this._input.disconnect(this._inputMutedId);
-            this._inputVolumeId = 0;
-            this._inputMutedId = 0;
-        }
-        this._input = this._control.get_default_source();
-        if (this._input) {
-            this._inputMutedId = this._input.connect('notify::is-muted', Lang.bind(this, this._updateVolume, '_input'));
-            this._inputVolumeId = this._input.connect('notify::volume', Lang.bind(this, this._updateVolume, '_input'));
-            this._updateVolume(null, null, '_input');
-        } else {
-            this._inputTitle.actor.hide();
-            this._inputSlider.actor.hide();
-        }
+    _connectStream: function(stream) {
+        this.parent(stream);
+        this._maybeShowInput();
     },
 
     _maybeShowInput: function() {
         // only show input widgets if any application is recording audio
         let showInput = false;
         let recordingApps = this._control.get_source_outputs();
-        if (this._input && recordingApps) {
+        if (this._stream && recordingApps) {
             for (let i = 0; i < recordingApps.length; i++) {
                 let outputStream = recordingApps[i];
                 let id = outputStream.get_application_id();
@@ -163,57 +220,70 @@ const VolumeMenu = new Lang.Class({
             }
         }
 
-        this._inputTitle.actor.visible = showInput;
-        this._inputSlider.actor.visible = showInput;
+        this._showInput = showInput;
+        this._updateVisibility();
     },
 
-    _sliderChanged: function(slider, value, property) {
-        if (this[property] == null) {
-            log ('Volume slider changed for %s, but %s does not exist'.format(property, property));
-            return;
-        }
-        let volume = value * this._volumeMax;
-        let prev_muted = this[property].is_muted;
-        if (volume < 1) {
-            this[property].volume = 0;
-            if (!prev_muted)
-                this[property].change_is_muted(true);
-        } else {
-            this[property].volume = volume;
-            if (prev_muted)
-                this[property].change_is_muted(false);
-        }
-        this[property].push_volume();
-    },
+    _shouldBeVisible: function() {
+        return this.parent() && this._showInput;
+    }
+});
 
-    _notifyVolumeChange: function() {
-        global.cancel_theme_sound(VOLUME_NOTIFY_ID);
-        global.play_theme_sound(VOLUME_NOTIFY_ID, 'audio-volume-change');
+const VolumeMenu = new Lang.Class({
+    Name: 'VolumeMenu',
+    Extends: PopupMenu.PopupMenuSection,
+
+    _init: function(control) {
+        this.parent();
+
+        this.hasHeadphones = false;
+
+        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));
+
+        /* Translators: This is the label for audio volume */
+        this._output = new OutputStreamSlider(this._control, _("Volume"));
+        this._output.connect('stream-updated', Lang.bind(this, function() {
+            this.emit('icon-changed');
+        }));
+        this._output.connect('headphones-changed', Lang.bind(this, function(stream, value) {
+            this.emit('headphones-changed', value);
+        }));
+        this.addMenuItem(this._output.item);
+
+        this._input = new InputStreamSlider(this._control, _("Microphone"));
+        this.addMenuItem(this._input.item);
+
+        this.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
+
+        this._onControlStateChanged();
     },
 
-    getIcon: function() {
-        if (!this._output)
-            return null;
+    scroll: function(event) {
+        this._output.scroll(event);
+    },
 
-        let volume = this._output.volume;
-        if (this._output.is_muted || volume <= 0) {
-            return 'audio-volume-muted-symbolic';
+    _onControlStateChanged: function() {
+        if (this._control.get_state() == Gvc.MixerControlState.READY) {
+            this._readInput();
+            this._readOutput();
         } else {
-            let n = Math.floor(3 * volume / this._volumeMax) + 1;
-            if (n < 2)
-                return 'audio-volume-low-symbolic';
-            if (n >= 3)
-                return 'audio-volume-high-symbolic';
-            return 'audio-volume-medium-symbolic';
+            this.emit('icon-changed');
         }
     },
 
-    _updateVolume: function(object, param_spec, property) {
-        let muted = this[property].is_muted;
-        let slider = this[property+'Slider'];
-        slider.setValue(muted ? 0 : (this[property].volume / this._volumeMax));
-        if (property == '_output')
-            this.emit('icon-changed');
+    _readOutput: function() {
+        this._output.stream = this._control.get_default_sink();
+    },
+
+    _readInput: function() {
+        this._input.stream = this._control.get_default_source();
+    },
+
+    getIcon: function() {
+        return this._output.getIcon();
     }
 });
 



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