[the-board] [things] Add initial implementation of SoundThing
- From: Lucas Rocha <lucasr src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [the-board] [things] Add initial implementation of SoundThing
- Date: Thu, 6 Jan 2011 12:25:36 +0000 (UTC)
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]