[ease/video] [video] Added inspector for video.



commit 77f99527f3cc7d0d1f5b4d1c7ab4f520c9988222
Author: Nate Stedman <natesm gmail com>
Date:   Fri Jul 30 01:44:26 2010 -0400

    [video] Added inspector for video.
    
    - Video can be automatically played or not
    - Nice video playback controls
    - Video can be muted
    - Different actions to take when a video completes.

 data/ui/inspector-element-video.ui |  118 +++++++++++++++++++++
 ease-core/ease-actor.vala          |   12 ++
 ease-core/ease-element.vala        |    8 ++
 ease-core/ease-slide.vala          |    5 +
 ease-core/ease-theme.vala          |    7 ++
 ease-core/ease-video-actor.vala    |   25 ++++-
 ease-core/ease-video-element.vala  |  203 +++++++++++++++++++++++++++++++++++-
 src/ease-editor-embed.vala         |   41 ++++++--
 src/ease-player.vala               |   13 +++
 9 files changed, 417 insertions(+), 15 deletions(-)
---
diff --git a/data/ui/inspector-element-video.ui b/data/ui/inspector-element-video.ui
new file mode 100644
index 0000000..e6d54f9
--- /dev/null
+++ b/data/ui/inspector-element-video.ui
@@ -0,0 +1,118 @@
+<?xml version="1.0"?>
+<interface>
+  <requires lib="gtk+" version="2.16"/>
+  <!-- interface-naming-policy project-wide -->
+  <object class="GtkAlignment" id="root">
+    <property name="visible">True</property>
+    <property name="top_padding">4</property>
+    <property name="bottom_padding">4</property>
+    <property name="left_padding">4</property>
+    <property name="right_padding">4</property>
+    <child>
+      <object class="GtkVBox" id="root-vbox">
+        <property name="visible">True</property>
+        <property name="orientation">vertical</property>
+        <property name="spacing">9</property>
+        <child>
+          <object class="GtkCheckButton" id="play-auto">
+            <property name="label" translatable="yes">_Automatically start playback</property>
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">False</property>
+            <property name="use_underline">True</property>
+            <property name="draw_indicator">True</property>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">False</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkCheckButton" id="mute">
+            <property name="label" translatable="yes">_Mute Audio</property>
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">False</property>
+            <property name="use_underline">True</property>
+            <property name="draw_indicator">True</property>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">False</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkVBox" id="vbox2">
+            <property name="visible">True</property>
+            <property name="orientation">vertical</property>
+            <property name="spacing">4</property>
+            <child>
+              <object class="GtkHBox" id="hbox1">
+                <property name="visible">True</property>
+                <child>
+                  <object class="GtkLabel" id="label1">
+                    <property name="visible">True</property>
+                    <property name="label" translatable="yes">When video playback ends:</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">False</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <placeholder/>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkComboBox" id="end-action">
+                <property name="height_request">30</property>
+                <property name="visible">True</property>
+                <child>
+                  <object class="GtkCellRendererText" id="cellrenderertext1"/>
+                  <attributes>
+                    <attribute name="text">0</attribute>
+                  </attributes>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">False</property>
+            <property name="position">2</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkButton" id="fill-slide">
+            <property name="label" translatable="yes">_Fill Slide</property>
+            <property name="height_request">30</property>
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">True</property>
+            <property name="use_underline">True</property>
+            <property name="xalign">0</property>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">False</property>
+            <property name="position">3</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+  </object>
+</interface>
diff --git a/ease-core/ease-actor.vala b/ease-core/ease-actor.vala
index a7311e4..cfc4f79 100644
--- a/ease-core/ease-actor.vala
+++ b/ease-core/ease-actor.vala
@@ -88,6 +88,18 @@ public abstract class Ease.Actor : Clutter.Group
 			editor_rect.height = e.height;
 			add_actor(editor_rect);
 		}
+		
+		// update the actor's position when changed in the element
+		e.notify["x"].connect((o, p) => x = element.x);
+		e.notify["y"].connect((o, p) => y = element.y);
+		e.notify["width"].connect((o, p) => {
+			width = element.width;
+			contents.width = element.width;
+		});
+		e.notify["height"].connect((o, p) => {
+			height = element.height;
+			contents.height = element.height;
+		});
 	}
 	
 	/**
diff --git a/ease-core/ease-element.vala b/ease-core/ease-element.vala
index a6f7473..eed4bd3 100644
--- a/ease-core/ease-element.vala
+++ b/ease-core/ease-element.vala
@@ -142,6 +142,14 @@ public abstract class Ease.Element : GLib.Object, UndoSource
 	}
 	
 	/**
+	 * Requests that the presentation be advanced a slide.
+	 */
+	internal void request_advance()
+	{
+		parent.request_advance(this);
+	}
+	
+	/**
 	 * Creates the actual HTML markup for this Element.
 	 *
 	 * @param html The HTML string in its current state.
diff --git a/ease-core/ease-slide.vala b/ease-core/ease-slide.vala
index 702cb37..2ea4b8b 100644
--- a/ease-core/ease-slide.vala
+++ b/ease-core/ease-slide.vala
@@ -120,6 +120,11 @@ public class Ease.Slide : GLib.Object, UndoSource
 	public int count { get { return elements.size; } }
 	
 	/**
+	 * Requests that the player advance past this Slide.
+	 */
+	public signal void request_advance(Element sender);
+	
+	/**
 	 * The next Slide in this Slide's { link Document}.
 	 */
 	public Slide? next
diff --git a/ease-core/ease-theme.vala b/ease-core/ease-theme.vala
index c49062c..8490031 100644
--- a/ease-core/ease-theme.vala
+++ b/ease-core/ease-theme.vala
@@ -109,6 +109,13 @@ public class Ease.Theme : GLib.Object
 	
 	// video properties
 	public const string VIDEO_PLAY_AUTO = "video-play-automatically";
+	public const string VIDEO_MUTE = "video-mute";
+	public const string VIDEO_END_ACTION = "video-end-action";
+	
+	// video end actions
+	public const string VIDEO_END_STOP = "video-end-stop";
+	public const string VIDEO_END_LOOP = "video-end-loop";
+	public const string VIDEO_END_CONTINUE = "video-end-continue";
 	
 	// generic element properties
 	public const string E_IDENTIFIER = "element-identifier";
diff --git a/ease-core/ease-video-actor.vala b/ease-core/ease-video-actor.vala
index 6582831..0b02f7f 100644
--- a/ease-core/ease-video-actor.vala
+++ b/ease-core/ease-video-actor.vala
@@ -60,7 +60,7 @@ public class Ease.VideoActor : Actor, Clutter.Media
 	/**
 	 * Easing for the button fadeout.
 	 */
-	private const int ALPHA_OPACITY = Clutter.AnimationMode.LINEAR;
+	private const Clutter.AnimationMode ALPHA_OPACITY = Clutter.AnimationMode.LINEAR;
 	
 	/**
 	 * Easing for the button scale out.
@@ -105,6 +105,10 @@ public class Ease.VideoActor : Actor, Clutter.Media
 		// play the video if it's in the presentation
 		if (c == ActorContext.PRESENTATION)
 		{
+			// mute the video if requested
+			set_audio_volume(e.mute ? 0 : 1);
+			
+			// if the video should automatically play, play it
 			if (e.play_auto)
 			{
 				video.set_playing(true);
@@ -141,10 +145,27 @@ public class Ease.VideoActor : Actor, Clutter.Media
 					                  "opacity", 255);
 				return true;
 			});
+			
+			// perform the video's end action when requested
+			video.eos.connect((v) => {
+				switch ((element as VideoElement).end_action)
+				{
+					case VideoEndAction.STOP:
+						break;
+					case VideoEndAction.LOOP:
+						set_progress(0);
+						video.set_playing(true);
+						break;
+					case VideoEndAction.CONTINUE:
+						element.request_advance();
+						break;
+				}
+			});
 		}
 		else
 		{
 			// FIXME: toggle playback to get a frame
+			set_audio_volume(0);
 			video.set_playing(true);
 			video.set_playing(false);
 		}
@@ -183,7 +204,7 @@ public class Ease.VideoActor : Actor, Clutter.Media
 		cr.line_to(e.width, 0);
 		cr.line_to(0, e.height);
 		cr.close_path();
-		cr.set_source_rgba(0, 0, 0, 0.8);
+		cr.set_source_rgba(0, 0, 0, 0.7);
 		cr.fill();
 		
 		// create the action button
diff --git a/ease-core/ease-video-element.vala b/ease-core/ease-video-element.vala
index ee35ff6..7abbf4c 100644
--- a/ease-core/ease-video-element.vala
+++ b/ease-core/ease-video-element.vala
@@ -21,11 +21,27 @@
  */
 public class Ease.VideoElement : MediaElement
 {
+	private const string UI_FILE = "inspector-element-video.ui";
+	private bool silence_undo;
+	
 	/**
 	 * If the video should begin playing automatically, or display a play
 	 * button.
 	 */
-	public bool play_auto { get; set; default = false; }
+	internal bool play_auto { get; set; default = false; }
+	
+	/**
+	 * If the video should be muted.
+	 */
+	internal VideoEndAction end_action
+	{
+		get; set; default = VideoEndAction.STOP;
+	}
+	
+	/**
+	 * If the video should be muted.
+	 */
+	internal bool mute { get; set; default = false; }
 	
 	public VideoElement()
 	{
@@ -36,6 +52,9 @@ public class Ease.VideoElement : MediaElement
 	{
 		base.from_json(obj);
 		play_auto = obj.get_string_member(Theme.VIDEO_PLAY_AUTO).to_bool();
+		mute = obj.get_string_member(Theme.VIDEO_MUTE).to_bool();
+		end_action = VideoEndAction.from_string(
+			obj.get_string_member(Theme.VIDEO_END_ACTION));
 	}	
 	
 	public override Actor actor(ActorContext c)
@@ -47,6 +66,8 @@ public class Ease.VideoElement : MediaElement
 	{
 		var obj = base.to_json();
 		obj.set_string_member(Theme.VIDEO_PLAY_AUTO, play_auto.to_string());
+		obj.set_string_member(Theme.VIDEO_MUTE, mute.to_string());
+		obj.set_string_member(Theme.VIDEO_END_ACTION, end_action.to_string());
 		return obj;
 	}
 	
@@ -78,11 +99,114 @@ public class Ease.VideoElement : MediaElement
 		return html;
 	}
 	
+	/**
+	 * { inheritDoc}
+	 */
 	public override Gtk.Widget inspector_widget()
 	{
-		var label = new Gtk.Label("No inspector for videos right now...");
-		label.show();
-		return label;
+		var builder = new Gtk.Builder();
+		try
+		{
+			builder.add_from_file(data_path(Path.build_filename(Temp.UI_DIR,
+				                                                UI_FILE)));
+		}
+		catch (Error e) { error("Error loading UI: %s", e.message); }
+		
+		var m_button = builder.get_object("mute") as Gtk.CheckButton;
+		var a_button = builder.get_object("play-auto") as Gtk.CheckButton;
+		
+		// connect the "fill slide" button
+		(builder.get_object("fill-slide") as Gtk.Button).clicked.connect(() => {
+			// don't create an unneeded undoaction
+			if (width == parent.width && height == parent.height &&
+			    x == 0 && y == 0) return;
+			
+			// create an undo acton
+			var action = new UndoAction(this, "x");
+			action.add(this, "y");
+			action.add(this, "width");
+			action.add(this, "height");
+			
+			// set the position of the element
+			x = 0;
+			y = 0;
+			width = parent.width;
+			height = parent.height;
+			
+			// emit the undoaction
+			undo(action);
+		});
+		
+		// setup mute and autoplay checkboxes
+		m_button.active = mute;
+		a_button.active = play_auto;
+		
+		m_button.toggled.connect(() => {
+			mute = m_button.active;
+			
+			var action = new UndoAction(this, "mute");
+			action.applied.connect((a) => m_button.active = mute);
+			undo(action);
+		});
+		
+		a_button.toggled.connect(() => {
+			play_auto = a_button.active;
+			
+			var action = new UndoAction(this, "play-auto");
+			action.applied.connect((a) => {
+				a_button.active = play_auto;
+			});
+			undo(action);
+		});
+		
+		// setup end action combo box
+		var combo = builder.get_object("end-action") as Gtk.ComboBox;
+		set_combobox(combo);
+		
+		// update the end action when the combo box is changed
+		combo.changed.connect(on_set_end_action);
+		
+		// return the root widget
+		return builder.get_object("root") as Gtk.Widget;
+	}
+	
+	private void on_set_end_action(Gtk.ComboBox sender)
+	{
+		var undo_action = new UndoAction(this, "end-action");
+	
+		undo_action.applied.connect((a) => {
+			silence_undo = true;
+			set_combobox(sender);
+			silence_undo = false;
+		});
+	
+		VideoEndAction action;
+		Gtk.TreeIter itr;
+		sender.model.get_iter_first(out itr);
+		for (int i = 0; i < sender.get_active(); i++)
+		{
+			sender.model.iter_next(ref itr);
+		}
+		sender.model.get(itr, 1, out action);
+		end_action = action;
+		if (!silence_undo) undo(undo_action);
+	}
+	
+	private void set_combobox(Gtk.ComboBox combo)
+	{
+		combo.model = VideoEndAction.list_store();
+		VideoEndAction action;
+		Gtk.TreeIter itr;
+		combo.model.get_iter_first(out itr);
+		do
+		{
+			combo.model.get(itr, 1, out action);
+			if (action == end_action)
+			{
+				combo.set_active_iter(itr);
+				break;
+			}
+		} while (combo.model.iter_next(ref itr));
 	}
 
 	public override void cairo_render(Cairo.Context context) throws Error
@@ -91,3 +215,74 @@ public class Ease.VideoElement : MediaElement
 	}
 }
 
+internal enum Ease.VideoEndAction
+{
+	STOP,
+	LOOP,
+	CONTINUE;
+	
+	/**
+	 * Returns a string representation of this VideoEndAction.
+	 */
+	public string to_string()
+	{
+		switch (this)
+		{
+			case STOP: return Theme.VIDEO_END_STOP;
+			case LOOP: return Theme.VIDEO_END_LOOP;
+			case CONTINUE: return Theme.VIDEO_END_CONTINUE;
+		}
+		return "undefined";
+	}
+	
+	/**
+	 * Creates a VideoEndAction from a string representation.
+	 */
+	public static VideoEndAction from_string(string str)
+	{
+		switch (str)
+		{
+			case Theme.VIDEO_END_STOP: return STOP;
+			case Theme.VIDEO_END_LOOP: return LOOP;
+			case Theme.VIDEO_END_CONTINUE: return CONTINUE;
+		}
+		
+		warning("%s is not a video aend action", str);
+		return STOP;
+	}
+	
+	/**
+	 * Returns a string description of the VideoEndAction
+	 */
+	public string description()
+	{
+		switch (this)
+		{
+			case STOP: return _("Stop playback");
+			case LOOP: return _("Loop");
+			case CONTINUE: return _("Continue to next slide");
+		}
+		return "undefined";
+	}
+	
+	/**
+	 * Creates a ListStore with the first column set as the description
+	 * and the second column set as the VideoEndAction.
+	 */
+	public static Gtk.ListStore list_store()
+	{
+		var store = new Gtk.ListStore(2, typeof(string),
+		                                 typeof(VideoEndAction));
+		Gtk.TreeIter itr;
+		
+		store.append(out itr);
+		store.set(itr, 0, STOP.description(), 1, STOP);
+		store.append(out itr);
+		store.set(itr, 0, LOOP.description(), 1, LOOP);
+		store.append(out itr);
+		store.set(itr, 0, CONTINUE.description(), 1, CONTINUE);
+		
+		return store;
+	}
+}
+
diff --git a/src/ease-editor-embed.vala b/src/ease-editor-embed.vala
index fc4036f..ef30781 100644
--- a/src/ease-editor-embed.vala
+++ b/src/ease-editor-embed.vala
@@ -52,7 +52,20 @@ internal class Ease.EditorEmbed : ScrollableEmbed, UndoSource
 	/**
 	 * The currently selected { link Actor}.
 	 */
-	internal Actor selected { get; private set; }
+	internal Actor selected
+	{
+		get { return selected_priv; }
+		private set
+		{
+			if (selected_priv != null)
+			{
+				selected_priv.notify.disconnect(on_selected_notify);
+			}
+			selected_priv = value;
+			if (value != null) value.notify.connect(on_selected_notify);
+		}
+	}
+	private Actor selected_priv;
 	
 	/**
 	 * If the selected { link Actor} is being edited.
@@ -571,8 +584,6 @@ internal class Ease.EditorEmbed : ScrollableEmbed, UndoSource
 			mouse_x = event.x;
 			mouse_y = event.y;
 			
-			position_selection();
-			
 			selected.element.changed();
 		}
 		return true;
@@ -679,8 +690,6 @@ internal class Ease.EditorEmbed : ScrollableEmbed, UndoSource
 		mouse_x = event.x;
 		mouse_y = event.y;
 		
-		position_selection();
-		
 		selected.element.changed();
 		
 		return true;
@@ -738,7 +747,6 @@ internal class Ease.EditorEmbed : ScrollableEmbed, UndoSource
 				undo(new UndoAction(selected.element, "y"));
 				selected.translate(0, shift ?
 				                      -NUDGE_SHIFT_PIXELS : -NUDGE_PIXELS);
-				position_selection();			
 				selected.element.changed();
 				return true;
 			
@@ -749,7 +757,6 @@ internal class Ease.EditorEmbed : ScrollableEmbed, UndoSource
 				undo(new UndoAction(selected.element, "y"));
 				selected.translate(0, shift ?
 				                      NUDGE_SHIFT_PIXELS : NUDGE_PIXELS);
-				position_selection();			
 				selected.element.changed();
 				return true;
 				
@@ -760,7 +767,6 @@ internal class Ease.EditorEmbed : ScrollableEmbed, UndoSource
 				undo(new UndoAction(selected.element, "x"));
 				selected.translate(shift ?
 				                   -NUDGE_SHIFT_PIXELS : -NUDGE_PIXELS, 0);
-				position_selection();			
 				selected.element.changed();
 				return true;
 			
@@ -771,7 +777,6 @@ internal class Ease.EditorEmbed : ScrollableEmbed, UndoSource
 				undo(new UndoAction(selected.element, "x"));
 				selected.translate(shift ?
 				                   NUDGE_SHIFT_PIXELS : NUDGE_PIXELS, 0);
-				position_selection();			
 				selected.element.changed();
 				return true;
 			
@@ -818,5 +823,23 @@ internal class Ease.EditorEmbed : ScrollableEmbed, UndoSource
 		keys_connected = false;
 		key_press_event.disconnect(on_key_press_event);
 	}
+	
+	/**
+	 * Notifies of changes to the selected actor.
+	 */
+	internal void on_selected_notify(GLib.Object object, GLib.ParamSpec pspec)
+	{
+		if (selection_rectangle == null) return;
+		
+		switch (pspec.name)
+		{
+			case "x":
+			case "y":
+			case "width":
+			case "height":
+				position_selection();
+				break;
+		}
+	}
 }
 
diff --git a/src/ease-player.vala b/src/ease-player.vala
index f917181..b38aa07 100644
--- a/src/ease-player.vala
+++ b/src/ease-player.vala
@@ -244,6 +244,7 @@ internal class Ease.Player : GLib.Object
 		if (slide_index == 0)
 		{
 			create_current_slide(slide);
+			slide.request_advance.connect(on_request_advance);
 			current_slide.stack(container);
 			current_slide.opacity = 0;
 			current_slide.animate(Clutter.AnimationMode.EASE_IN_SINE,
@@ -259,8 +260,10 @@ internal class Ease.Player : GLib.Object
 		// otherwise, animate as usual
 		else
 		{
+			old_slide.slide.request_advance.disconnect(on_request_advance);
 			old_slide = current_slide;
 			create_current_slide(slide);
+			slide.request_advance.connect(on_request_advance);
 			container.add_actor(current_slide);
 			
 			if (old_slide.slide.transition_time > 0)
@@ -322,4 +325,14 @@ internal class Ease.Player : GLib.Object
 			advance_alarm.start();
 		}
 	}
+	
+	/**
+	 * This is requested by video actors that have finished playing. As the
+	 * calling function is on Slide, element plugins should never be aware
+	 * that this functionality exists.
+	 */
+	private void on_request_advance(Element element)
+	{
+		advance();
+	}
 }



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