[the-board] [things] Add initial implementation of SoundThing



commit 55a84eb12104901fcfd1d6fcea5e1c8808fa683d
Author: Lucas Rocha <lucasr gnome org>
Date:   Wed Jan 5 00:49:44 2011 +0000

    [things] Add initial implementation of SoundThing
    
    For now, it only supports playback. Voice recording capability coming
    next.

 data/things/Makefile.am                      |   11 +
 data/things/sound/caption-border.png         |  Bin 0 -> 272 bytes
 data/things/sound/cassete-spool.png          |  Bin 0 -> 6139 bytes
 data/things/sound/cassete.png                |  Bin 0 -> 3015 bytes
 data/things/sound/sound-controls-bg.png      |  Bin 0 -> 284 bytes
 data/things/sound/sound-progress-bar-bar.png |  Bin 0 -> 266 bytes
 data/things/sound/sound-progress-bar-bg.png  |  Bin 0 -> 236 bytes
 data/things/sound/style.css                  |   77 ++++
 src/js/ui/things/sound.js                    |  550 ++++++++++++++++++++++++++
 9 files changed, 638 insertions(+), 0 deletions(-)
---
diff --git a/data/things/Makefile.am b/data/things/Makefile.am
index 2b74f1a..9d7b763 100644
--- a/data/things/Makefile.am
+++ b/data/things/Makefile.am
@@ -19,6 +19,17 @@ dist_photo_DATA = \
     photo/style.css \
     photo/tape.png
 
+sounddir = $(thingsdir)/sound
+
+dist_sound_DATA = \
+    sound/caption-border.png \
+    sound/cassete.png \
+    sound/cassete-spool.png \
+    sound/sound-controls-bg.png \
+    sound/sound-progress-bar-bar.png \
+    sound/sound-progress-bar-bg.png \
+    sound/style.css
+
 videodir = $(thingsdir)/video
 
 dist_video_DATA = \
diff --git a/data/things/sound/caption-border.png b/data/things/sound/caption-border.png
new file mode 100644
index 0000000..b3a28f1
Binary files /dev/null and b/data/things/sound/caption-border.png differ
diff --git a/data/things/sound/cassete-spool.png b/data/things/sound/cassete-spool.png
new file mode 100644
index 0000000..dca0d06
Binary files /dev/null and b/data/things/sound/cassete-spool.png differ
diff --git a/data/things/sound/cassete.png b/data/things/sound/cassete.png
new file mode 100644
index 0000000..30ece41
Binary files /dev/null and b/data/things/sound/cassete.png differ
diff --git a/data/things/sound/sound-controls-bg.png b/data/things/sound/sound-controls-bg.png
new file mode 100644
index 0000000..0c66a1f
Binary files /dev/null and b/data/things/sound/sound-controls-bg.png differ
diff --git a/data/things/sound/sound-progress-bar-bar.png b/data/things/sound/sound-progress-bar-bar.png
new file mode 100644
index 0000000..2a36544
Binary files /dev/null and b/data/things/sound/sound-progress-bar-bar.png differ
diff --git a/data/things/sound/sound-progress-bar-bg.png b/data/things/sound/sound-progress-bar-bg.png
new file mode 100644
index 0000000..b5c944f
Binary files /dev/null and b/data/things/sound/sound-progress-bar-bg.png differ
diff --git a/data/things/sound/style.css b/data/things/sound/style.css
new file mode 100644
index 0000000..89b2704
--- /dev/null
+++ b/data/things/sound/style.css
@@ -0,0 +1,77 @@
+TbBox#sound-thing-sound-box {
+    background-color: black;
+}
+
+TbBox#sound-thing-content-box {
+    spacing: 5px;
+    padding: 17px 18px 27px 18px;
+}
+
+TbBox#sound-thing-button-box {
+    background-color: #00000066;
+    padding: 5px;
+    spacing: 10px; 
+}
+
+TbBox#sound-thing-controls-box {
+    border-image: url('sound-controls-bg.png') 8;
+    padding: 2px 7px 1px 5px;
+    spacing: 5px; 
+}
+
+MxButton#sound-thing-play-button {
+    background-color: none;
+    border-image: none;
+    padding: 0px;
+    -mx-icon-name: play;
+    -mx-icon-size: 24;
+}
+
+MxButton#sound-thing-play-button:checked {
+    -mx-icon-name: pause;
+}
+
+TbBox#sound-thing-sound-border-box {
+    border-image: none;
+    background-color: none;
+}
+
+TbBox#sound-thing-caption-box {
+    border-image: url('caption-border.png') 4 4 4 4;
+    padding: 8px 5px 3px 5px;
+}
+
+MxLabel#sound-thing-caption-label {
+    color: black;
+    font-size: 13;
+    font-family: "Action Man";
+}
+
+MxProgressBar#sound-thing-progress-bar {
+  border-image: url('sound-progress-bar-bg.png') 4 4 6 4;
+}
+
+MxProgressBar#sound-thing-progress-bar MxProgressBarFill {
+  border-image: url('sound-progress-bar-bar.png') 3;
+  height: 12;
+}
+
+MxLabel#sound-thing-time-label {
+    color: white;
+    font-size: 15;
+    font-family: "Action Man";
+}
+
+TbBox#sound-thing-time-label-box {
+    padding: 4px 0px 0px 0px;
+}
+
+TbBox#sound-thing-spools-box {
+    spacing: 56px;
+}
+
+MxSpinner#sound-thing-spool {
+    -mx-spinner-image: url('cassete-spool.png');
+    -mx-spinner-animation-duration: 72;
+    -mx-spinner-frames: 8;
+}
diff --git a/src/js/ui/things/sound.js b/src/js/ui/things/sound.js
new file mode 100644
index 0000000..dc3c37c
--- /dev/null
+++ b/src/js/ui/things/sound.js
@@ -0,0 +1,550 @@
+// standard imports
+const Gettext = imports.gettext.domain("the-board");
+const Lang = imports.lang;
+const Mainloop = imports.mainloop;
+const Tweener = imports.tweener.tweener;
+
+// gi imports
+const Tb = imports.gi.Tb;
+const Clutter = imports.gi.Clutter;
+const GLib = imports.gi.GLib;
+const Gtk = imports.gi.Gtk;
+const Mx = imports.gi.Mx;
+const Pango = imports.gi.Pango;
+
+// ui imports
+const Thing = imports.ui.thing;
+const ToolBox = imports.ui.toolBox;
+const Toolbar = imports.ui.toolbar;
+
+// util imports
+const Path = imports.util.path;
+
+const NAME = Gettext.gettext("Sound");
+const STYLE = Path.THINGS_DATA_DIR + "sound/style.css";
+
+const _INITIAL_WIDTH = 233;
+const _INITIAL_HEIGHT = 152;
+
+const _CASSETE_IMAGE = Path.THINGS_DATA_DIR + "sound/cassete.png";
+
+const _SHOW_BUTTON_BOX_TIME = 0.5;
+
+function SoundThing(args) {
+    this._init(args);
+}
+
+SoundThing.prototype = {
+    __proto__: Thing.Thing.prototype,
+
+    _init : function(args) {
+        args = args || {};
+
+        args.content = this;
+        args.canResize = false;
+
+        this._style = new Mx.Style();
+        this._style.load_from_file(STYLE);
+
+        this._createSoundPlayer();
+        this._createSoundBox();
+        this._createCaptionText();
+        this._createSpools();
+
+        Thing.Thing.prototype._init.apply(this, [args]);
+    },
+
+    _createSoundBox : function() {
+        this._soundBox =
+            new Tb.Box({ orientation: Tb.BoxOrientation.HORIZONTAL,
+                         xAlign: Tb.BoxAlignment.FILL,
+                         yAlign: Tb.BoxAlignment.FILL,
+                         name: "sound-thing-sound-box" });
+
+        this._soundBox.set_style(this._style);
+
+        this._createCasseteBg();
+        this._createContentBox();
+    },
+
+    _createSoundPlayer : function() {
+        this._player = new Tb.SoundPlayer();
+
+        this._player.connect("notify::progress",
+                             Lang.bind(this,
+                                       this._onPlayerProgressChanged));
+
+        this._player.connect("notify::state",
+                             Lang.bind(this,
+                                       this._onPlayerStateChanged));
+    },
+
+    _createCasseteBg : function() {
+        this._casseteBg =
+            new Clutter.Texture({ filename: _CASSETE_IMAGE });
+
+        this._soundBox.append(this._casseteBg,
+                              Tb.BoxPackFlags.FIXED);
+
+        this._soundBox.set_fixed_child_align(this._casseteBg,
+                                             Tb.BoxAlignment.FILL,
+                                             Tb.BoxAlignment.FILL);
+    },
+
+    _createContentBox : function() {
+        this._contentBox =
+            new Tb.Box({ orientation: Tb.BoxOrientation.VERTICAL,
+                         xAlign: Tb.BoxAlignment.FILL,
+                         yAlign: Tb.BoxAlignment.FILL,
+                         name: "sound-thing-content-box" });
+
+        this._contentBox.set_style(this._style);
+
+        this._createControlsBox();
+        this._createTimeLabel();
+
+        this._soundBox.append(this._contentBox,
+                                Tb.BoxPackFlags.EXPAND);
+    },
+
+    _createControlsBox : function() {
+        this._controlsBox =
+            new Tb.Box({ orientation: Tb.BoxOrientation.HORIZONTAL,
+                         xAlign: Tb.BoxAlignment.FILL,
+                         yAlign: Tb.BoxAlignment.CENTER,
+                         opacity: 0,
+                         visible: false,
+                         name: "sound-thing-controls-box" });
+
+        this._controlsBox.set_style(this._style);
+
+        this._createPlayButton();
+        this._createProgressBar();
+
+        this._contentBox.append(this._controlsBox,
+                                Tb.BoxPackFlags.FIXED);
+
+        this._contentBox.set_fixed_child_align(this._controlsBox,
+                                               Tb.BoxAlignment.FILL,
+                                               Tb.BoxAlignment.END);
+    },
+
+    _createPlayButton : function() {
+        this._playButton =
+            new Mx.Button({ isToggle: true,
+                            name: "sound-thing-play-button" });
+
+        this._playButton.set_style(this._style);
+
+        this._playButton.connect("notify::toggled",
+                                 Lang.bind(this,
+                                           this._onPlayButtonToggled));
+
+        this._controlsBox.append(this._playButton,
+                                      Tb.BoxPackFlags.NONE);
+    },
+
+    _createProgressBar : function() {
+        this._progressBar =
+            new Mx.ProgressBar({ reactive: true,
+                                 height: 12,
+                                 name: "sound-thing-progress-bar" });
+
+        this._progressBar.set_style(this._style);
+
+        this._updateProgressBar();
+
+        this._progressBar.connect("button-press-event",
+                                  Lang.bind(this,
+                                            this._onProgressBarButtonPressEvent));
+
+        this._controlsBox.append(this._progressBar,
+                                 Tb.BoxPackFlags.EXPAND);
+
+        this._controlsBox.set_child_align(this._progressBar,
+                                          Tb.BoxAlignment.FILL,
+                                          Tb.BoxAlignment.CENTER);
+    },
+
+    _createTimeLabel : function() {
+        this._timeLabel =
+            new Mx.Label({ yAlign: Mx.Align.MIDDLE,
+                           text: "00:12:00",
+                           anchorY: -2,
+                           name: "sound-thing-time-label" });
+
+        this._timeLabel.set_style(this._style);
+
+        this._updateTimeLabel();
+
+        //this._controlsBox.append(this._timeLabel,
+        //                         Tb.BoxPackFlags.END);
+
+        //this._controlsBox.set_child_align(this._timeLabel,
+        //                                  Tb.BoxAlignment.START,
+        //                                  Tb.BoxAlignment.CENTER);
+    },
+
+    _createCaptionText : function() {
+        this._captionBox =
+            new Tb.Box({ orientation: Tb.BoxOrientation.HORIZONTAL,
+                         xAlign: Tb.BoxAlignment.FILL,
+                         yAlign: Tb.BoxAlignment.FILL,
+                         name: "sound-thing-caption-box" });
+
+        this._captionBox.set_style(this._style);
+
+        this._captionLabel =
+            new Mx.Label({ xAlign: Mx.Align.MIDDLE,
+                           name: "sound-thing-caption-label" });
+
+        this._captionLabel.set_style(this._style);
+
+        this._captionLabel.clutterText.maxLength = 25;
+        this._captionLabel.clutterText.lineAlignment = Pango.Alignment.CENTER;
+
+        this._captionLabel.connect("key-press-event",
+                                   Lang.bind(this, this._onCaptionTextKeyPressEvent));
+
+        this._captionLabel.clutterText.connect("text-changed",
+                                               Lang.bind(this,
+                                                         this._onCaptionTextChanged));
+
+        this._captionBox.append(this._captionLabel,
+                                Tb.BoxPackFlags.EXPAND);
+
+        this._contentBox.append(this._captionBox,
+                                Tb.BoxPackFlags.NONE);
+    },
+
+    _createSpools : function() {
+        this._spoolsBox =
+            new Tb.Box({ orientation: Tb.BoxOrientation.HORIZONTAL,
+                         xAlign: Tb.BoxAlignment.FILL,
+                         yAlign: Tb.BoxAlignment.FILL,
+                         name: "sound-thing-spools-box" });
+
+        this._spoolsBox.set_style(this._style);
+
+        for (let i = 0; i < 2; i++) {
+            let spool = new Mx.Spinner({ width: 27,
+                                        height: 27,
+                                        animating: false,
+                                        name: "sound-thing-spool" });
+
+            spool.set_style(this._style);
+
+            this._spoolsBox.append(spool,
+                                   Tb.BoxPackFlags.NONE);
+        }
+
+        this._contentBox.append(this._spoolsBox,
+                                Tb.BoxPackFlags.FIXED);
+
+        this._contentBox.set_fixed_child_align(this._spoolsBox,
+                                               Tb.BoxAlignment.CENTER,
+                                               Tb.BoxAlignment.CENTER);
+    },
+
+    _updateSpinner : function() {
+        // FIXME: show/hide spinner depending on the
+        // loading state of the sound
+    },
+
+    _updateSoundFilename : function(soundFilename, fromState) {
+        if (this._soundFilename == soundFilename) {
+            return;
+        }
+
+        this._soundFilename = soundFilename;
+
+        if (this._soundFilename) {
+            this._updateSpinner();
+
+            // start loading the new sound file
+            this._player.filename = this._soundFilename;
+
+            if (!fromState) {
+                this._playButton.toggled = true;
+            }
+        }
+
+        this._updateControlsVisibility();
+
+        if (!fromState) {
+            this.emit('save');
+        }
+    },
+
+    _showSoundControlsBox : function() {
+        Tweener.addTween(this._controlsBox,
+                         { opacity: 255,
+                           time: _SHOW_BUTTON_BOX_TIME,
+                           onStart: function() {
+                               this.show();
+                           }});
+    },
+
+    _hideSoundControlsBox : function() {
+        Tweener.addTween(this._controlsBox,
+                         { opacity: 0,
+                           time: _SHOW_BUTTON_BOX_TIME,
+                           onComplete: function() {
+                               this.hide();
+                           }});
+    },
+
+    _updateSoundWithFileChooser : function() {
+        let chooser = new Gtk.FileChooserDialog();
+
+        chooser.add_button(Gtk.STOCK_CANCEL,
+                           Gtk.ResponseType.REJECT);
+        chooser.add_button(Gtk.STOCK_OK,
+                           Gtk.ResponseType.ACCEPT);
+
+        let soundFilter = new Gtk.FileFilter();
+
+        //soundFilter.set_name('Sounds');
+        //chooser.add_filter(soundFilter);
+
+        let soundsDir = GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_MUSIC);
+        chooser.set_current_folder(soundsDir);
+
+        chooser.set_transient_for(this.context.gtkWindow);
+
+        let response = chooser.run();
+
+        if (response != Gtk.ResponseType.ACCEPT) {
+            chooser.destroy();
+            return;
+        }
+
+        let filename = chooser.get_filename();
+
+        // Destroy dialog first, then set sound
+        chooser.destroy();
+
+        // we have to restore key focus on stage
+        // because the chooser grabs key focus
+        this._captionLabel.clutterText.grab_key_focus();
+
+        this._updateSoundFilename(filename,
+                                  false /* not from state*/);
+    },
+
+    _updateForSoundLoaded : function() {
+        this._updateSpinner();
+    },
+
+    _updateProgressBar : function() {
+        this._progressBar.progress = this._player.progress;
+    },
+
+    _formatTimeComponent : function(n) {
+        // FIXME: we need a sprinf equivalent to do
+        // proper formatting here.
+        return (n >= 10 ? n : "0" + n);
+    },
+
+    _updateTimeLabel : function() {
+        let currentTime =
+            Math.floor(this._player.duration * this._player.progress);
+
+        let hours = Math.floor(currentTime / 3600);
+        currentTime -= hours * 3600;
+
+        let minutes = Math.floor(currentTime / 60);
+        currentTime -= minutes * 60;
+
+        let seconds = currentTime;
+
+        this._timeLabel.text = this._formatTimeComponent(hours) + ":" +
+                               this._formatTimeComponent(minutes) + ":" +
+                               this._formatTimeComponent(seconds);
+    },
+
+    _updateControlsVisibility : function() {
+        let visible = this._soundFilename &&
+                      (this.hover || this.active);
+
+        if (visible) {
+            this._showSoundControlsBox();
+        } else {
+            this._hideSoundControlsBox();
+        }
+    },
+
+    _setSpoolsAnimating : function(animating) {
+        let spools = this._spoolsBox.get_children();
+
+        for (let i = 0; i < spools.length; i++) {
+            let spool = spools[i];
+            spool.animating = animating;
+        }
+    },
+
+    _onCaptionTextKeyPressEvent : function(o, event) {
+        let key = event.get_key_symbol();
+
+        switch (key) {
+        case Clutter.Return:
+            this._updateSoundWithFileChooser();
+            return true;
+        case Clutter.Escape:
+            this.emit("deactivate");
+            return true;
+        }
+
+        return false;
+    },
+
+    _onCaptionTextChanged : function() {
+        this.emit('save');
+    },
+
+    _onPlayButtonToggled : function() {
+        this._player.playing = this._playButton.toggled;
+        this._setSpoolsAnimating(this._playButton.toggled);
+    },
+
+    _onProgressBarButtonPressEvent : function(progressBar, event) {
+        let [eventX, eventY] = event.get_coords();
+
+        let [transformedX, transformedY] =
+            this._progressBar.get_transformed_position();
+
+        let progress = (eventX - transformedX) / this._progressBar.width;
+        this._player.progress = progress;
+    },
+
+    _onPlayerProgressChanged : function() {
+        this._updateProgressBar();
+        this._updateTimeLabel();
+    },
+
+    _onPlayerStateChanged : function() {
+        switch(this._player.state) {
+        case Tb.SoundPlayerState.IDLE:
+            this._updateForSoundLoaded();
+            break;
+
+        case Tb.SoundPlayerState.DONE:
+            break;
+
+        case Tb.SoundPlayerState.ERROR:
+            // FIXME: show error message in the UI
+            break;
+
+        default:
+            // do nothing
+        }
+
+        this._playButton.toggled = this._player.playing;
+    },
+
+    enter : function() {
+        this._updateControlsVisibility();
+    },
+
+    leave : function() {
+        this._updateControlsVisibility();
+    },
+
+    activate : function() {
+        this._captionLabel.clutterText.editable = true;
+        this._captionLabel.clutterText.grab_key_focus();
+        this._updateControlsVisibility();
+    },
+
+    deactivate : function() {
+        this._captionLabel.clutterText.editable = false;
+        this._updateControlsVisibility();
+    },
+
+    loadState : function(state) {
+        if ('soundFilename' in state) {
+            let fromState = 'width' in state &&
+                            'height' in state;
+
+            this._updateSoundFilename(state.soundFilename,
+                                      fromState);
+        }
+
+        if ('text' in state) {
+            this._captionLabel.text = state.text;
+        }
+    },
+
+    getState : function() {
+        return { soundFilename: this._soundFilename,
+                 text: this._captionLabel.text };
+    },
+
+    doAction : function(actionName, actionArgs) {
+        if (actionName == "chooseFile") {
+            this._updateSoundWithFileChooser();
+        }
+    },
+
+    validateSize : function(width, height) {
+        // minWidth and minHeight always have a valid aspect
+        // ratio once the sound is loaded (see _onSoundLoadFinished)
+        let aspectRatio = this._minWidth / this._minHeight;
+
+        // the point here is to keep aspect ratio while
+        // resize the sound thing
+        if (this._minWidth > this._minHeight) {
+            return [width, width / aspectRatio];
+        } else {
+            return [height * aspectRatio, height];
+        }
+    },
+
+    destroy : function() {
+        if (this._soundBox) {
+            this._soundBox.destroy();
+            delete this._soundBox;
+        }
+    },
+
+    get initialWidth() {
+        return _INITIAL_WIDTH;
+    },
+
+    get initialHeight() {
+        return _INITIAL_HEIGHT;
+    },
+
+    get minWidth() {
+        return _INITIAL_WIDTH;
+    },
+
+    get minHeight() {
+        return _INITIAL_HEIGHT;
+    },
+
+    get contentActor() {
+        return this._soundBox;
+    }
+}
+
+function create(args) {
+    return new SoundThing(args);
+}
+
+function createToolbar(args) {
+    let toolbar =
+        new Toolbar.Toolbar({ title: NAME,
+                              visible: false });
+
+    let toolBox =
+        new ToolBox.ToolBox({ title: Gettext.gettext("Load from"),
+                              isThingToolBox: true });
+
+    toolBox.addButton({ label: Gettext.gettext("File"),
+                        actionName: "chooseFile" });
+
+    toolbar.addToolBox(toolBox);
+
+    return toolbar;
+}



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