GESTimelineObject: Add mapping/offset support [start/priority properties]
authorEdward Hervey <edward.hervey@collabora.co.uk>
Thu, 16 Dec 2010 18:29:14 +0000 (19:29 +0100)
committerEdward Hervey <edward.hervey@collabora.co.uk>
Thu, 16 Dec 2010 18:29:14 +0000 (19:29 +0100)
Allows moving independently (or not) timelineobjects and trackobjects and
have them synchronized with the offsets taken into account.

Right now only the start and priority properties are synchronized. The duration
and in-point properties will require more thoughts.

ges/ges-timeline-object.c
ges/ges-timeline-pipeline.c
tests/check/ges/layer.c
tests/check/ges/timelineobject.c

index f12238e..2d5aca1 100644 (file)
@@ -42,11 +42,41 @@ ges_timeline_object_create_track_objects_func (GESTimelineObject
     * object, GESTrack * track);
 
 static void
-track_object_priority_offset_changed_cb (GESTrackObject * child,
-    GParamSpec * arg G_GNUC_UNUSED, GESTimelineObject * obj);
+track_object_start_changed_cb (GESTrackObject * child,
+    GParamSpec * arg G_GNUC_UNUSED, GESTimelineObject * object);
+static void
+track_object_inpoint_changed_cb (GESTrackObject * child,
+    GParamSpec * arg G_GNUC_UNUSED, GESTimelineObject * object);
+static void
+track_object_duration_changed_cb (GESTrackObject * child,
+    GParamSpec * arg G_GNUC_UNUSED, GESTimelineObject * object);
+static void
+track_object_priority_changed_cb (GESTrackObject * child,
+    GParamSpec * arg G_GNUC_UNUSED, GESTimelineObject * object);
 
 G_DEFINE_ABSTRACT_TYPE (GESTimelineObject, ges_timeline_object, G_TYPE_OBJECT);
 
+/* Mapping of relationship between a TimelineObject and the TrackObjects
+ * it controls
+ *
+ * NOTE : how do we make this public in the future ?
+ */
+typedef struct
+{
+  GESTrackObject *object;
+  gint64 start_offset;
+  gint64 duration_offset;
+  gint64 inpoint_offset;
+  gint32 priority_offset;
+
+  guint start_notifyid;
+  guint duration_notifyid;
+  guint inpoint_notifyid;
+  guint priority_notifyid;
+
+  /* track mapping ?? */
+} ObjectMapping;
+
 struct _GESTimelineObjectPrivate
 {
   /*< public > */
@@ -55,6 +85,13 @@ struct _GESTimelineObjectPrivate
   /*< private > */
   /* A list of TrackObject controlled by this TimelineObject */
   GList *trackobjects;
+
+  /* Set to TRUE when the timelineobject is doing updates of track object
+   * properties so we don't end up in infinite property update loops
+   */
+  gboolean ignore_notifies;
+
+  GList *mappings;
 };
 
 enum
@@ -66,8 +103,11 @@ enum
   PROP_PRIORITY,
   PROP_HEIGHT,
   PROP_LAYER,
+  PROP_LAST
 };
 
+static GParamSpec *properties[PROP_LAST];
+
 static void
 ges_timeline_object_get_property (GObject * object, guint property_id,
     GValue * value, GParamSpec * pspec)
@@ -138,10 +178,10 @@ ges_timeline_object_class_init (GESTimelineObjectClass * klass)
    *
    * The position of the object in the #GESTimelineLayer (in nanoseconds).
    */
+  properties[PROP_START] = g_param_spec_uint64 ("start", "Start",
+      "The position in the container", 0, G_MAXUINT64, 0, G_PARAM_READWRITE);
   g_object_class_install_property (object_class, PROP_START,
-      g_param_spec_uint64 ("start", "Start",
-          "The position in the container", 0, G_MAXUINT64, 0,
-          G_PARAM_READWRITE));
+      properties[PROP_START]);
 
   /**
    * GESTimelineObject:in-point
@@ -152,9 +192,11 @@ ges_timeline_object_class_init (GESTimelineObjectClass * klass)
    * Ex : an in-point of 5 seconds means that the first outputted buffer will
    * be the one located 5 seconds in the controlled resource.
    */
-  g_object_class_install_property (object_class, PROP_INPOINT,
+  properties[PROP_INPOINT] =
       g_param_spec_uint64 ("in-point", "In-point", "The in-point", 0,
-          G_MAXUINT64, 0, G_PARAM_READWRITE));
+      G_MAXUINT64, 0, G_PARAM_READWRITE);
+  g_object_class_install_property (object_class, PROP_INPOINT,
+      properties[PROP_INPOINT]);
 
   /**
    * GESTimelineObject:duration
@@ -162,19 +204,21 @@ ges_timeline_object_class_init (GESTimelineObjectClass * klass)
    * The duration (in nanoseconds) which will be used in the container #GESTrack
    * starting from 'in-point'.
    */
+  properties[PROP_DURATION] =
+      g_param_spec_uint64 ("duration", "Duration", "The duration to use", 0,
+      G_MAXUINT64, GST_CLOCK_TIME_NONE, G_PARAM_READWRITE);
   g_object_class_install_property (object_class, PROP_DURATION,
-      g_param_spec_uint64 ("duration", "Duration", "The duration to use",
-          0, G_MAXUINT64, GST_CLOCK_TIME_NONE, G_PARAM_READWRITE));
+      properties[PROP_DURATION]);
 
   /**
    * GESTimelineObject:priority
    *
    * The layer priority of the timeline object.
    */
-
+  properties[PROP_PRIORITY] = g_param_spec_uint ("priority", "Priority",
+      "The priority of the object", 0, G_MAXUINT, 0, G_PARAM_READWRITE);
   g_object_class_install_property (object_class, PROP_PRIORITY,
-      g_param_spec_uint ("priority", "Priority",
-          "The priority of the object", 0, G_MAXUINT, 0, G_PARAM_READWRITE));
+      properties[PROP_PRIORITY]);
 
   /**
    * GESTimelineObject:height
@@ -306,6 +350,8 @@ gboolean
 ges_timeline_object_add_track_object (GESTimelineObject * object, GESTrackObject
     * trobj)
 {
+  ObjectMapping *mapping;
+
   GST_LOG ("Got a TrackObject : %p , setting the timeline object as its"
       "creator", trobj);
 
@@ -314,6 +360,10 @@ ges_timeline_object_add_track_object (GESTimelineObject * object, GESTrackObject
 
   ges_track_object_set_timeline_object (trobj, object);
 
+  mapping = g_slice_new0 (ObjectMapping);
+  mapping->object = trobj;
+  object->priv->mappings = g_list_append (object->priv->mappings, mapping);
+
   GST_DEBUG ("Adding TrackObject to the list of controlled track objects");
   /* We steal the initial reference */
   object->priv->trackobjects =
@@ -328,8 +378,19 @@ ges_timeline_object_add_track_object (GESTimelineObject * object, GESTrackObject
 
   GST_DEBUG ("Returning trobj:%p", trobj);
 
-  g_signal_connect (G_OBJECT (trobj), "notify::priority-offset", G_CALLBACK
-      (track_object_priority_offset_changed_cb), object);
+  /* Listen to all property changes */
+  mapping->start_notifyid =
+      g_signal_connect (G_OBJECT (trobj), "notify::start",
+      G_CALLBACK (track_object_start_changed_cb), object);
+  mapping->duration_notifyid =
+      g_signal_connect (G_OBJECT (trobj), "notify::duration",
+      G_CALLBACK (track_object_duration_changed_cb), object);
+  mapping->inpoint_notifyid =
+      g_signal_connect (G_OBJECT (trobj), "notify::inpoint",
+      G_CALLBACK (track_object_inpoint_changed_cb), object);
+  mapping->priority_notifyid =
+      g_signal_connect (G_OBJECT (trobj), "notify::priority",
+      G_CALLBACK (track_object_priority_changed_cb), object);
 
   return TRUE;
 }
@@ -338,11 +399,18 @@ ges_timeline_object_add_track_object (GESTimelineObject * object, GESTrackObject
  * ges_timeline_object_release_track_object:
  * @object: a #GESTimelineObject
  * @trackobject: the #GESTrackObject to release
+ *
+ * Release the @trackobject from the control of @object.
+ *
+ * Returns: %TRUE if the @trackobject was properly released, else %FALSE.
  */
 gboolean
 ges_timeline_object_release_track_object (GESTimelineObject * object,
     GESTrackObject * trackobject)
 {
+  GList *tmp;
+  ObjectMapping *mapping = NULL;
+
   GST_DEBUG ("object:%p, trackobject:%p", object, trackobject);
 
   if (!(g_list_find (object->priv->trackobjects, trackobject))) {
@@ -353,6 +421,25 @@ ges_timeline_object_release_track_object (GESTimelineObject * object,
   /* FIXME : Do we need to tell the subclasses ?
    * If so, add a new virtual-method */
 
+  for (tmp = object->priv->mappings; tmp; tmp = tmp->next) {
+    mapping = (ObjectMapping *) tmp->data;
+    if (mapping->object == trackobject)
+      break;
+  }
+
+  if (tmp && mapping) {
+
+    /* Disconnect all notify listeners */
+    g_signal_handler_disconnect (trackobject, mapping->start_notifyid);
+    g_signal_handler_disconnect (trackobject, mapping->duration_notifyid);
+    g_signal_handler_disconnect (trackobject, mapping->inpoint_notifyid);
+    g_signal_handler_disconnect (trackobject, mapping->priority_notifyid);
+
+    g_slice_free (ObjectMapping, mapping);
+
+    object->priv->mappings = g_list_delete_link (object->priv->mappings, tmp);
+  }
+
   object->priv->trackobjects =
       g_list_remove (object->priv->trackobjects, trackobject);
 
@@ -360,6 +447,8 @@ ges_timeline_object_release_track_object (GESTimelineObject * object,
 
   g_object_unref (trackobject);
 
+  /* FIXME : resync properties ? */
+
   return TRUE;
 }
 
@@ -407,6 +496,20 @@ ges_timeline_object_fill_track_object_func (GESTimelineObject * object,
   return FALSE;
 }
 
+static ObjectMapping *
+find_object_mapping (GESTimelineObject * object, GESTrackObject * child)
+{
+  GList *tmp;
+
+  for (tmp = object->priv->mappings; tmp; tmp = tmp->next) {
+    ObjectMapping *map = (ObjectMapping *) tmp->data;
+    if (map->object == child)
+      return map;
+  }
+
+  return NULL;
+}
+
 /**
  * ges_timeline_object_set_start:
  * @object: a #GESTimelineObject
@@ -419,17 +522,28 @@ ges_timeline_object_set_start (GESTimelineObject * object, guint64 start)
 {
   GList *tmp;
   GESTrackObject *tr;
+  ObjectMapping *map;
 
   GST_DEBUG ("object:%p, start:%" GST_TIME_FORMAT,
       object, GST_TIME_ARGS (start));
 
+  object->priv->ignore_notifies = TRUE;
+
   for (tmp = object->priv->trackobjects; tmp; tmp = g_list_next (tmp)) {
     tr = (GESTrackObject *) tmp->data;
-    if (ges_track_object_is_locked (tr))
-      /* call set_start on each trackobject */
-      ges_track_object_set_start (tr, start);
+    map = find_object_mapping (object, tr);
+
+    if (ges_track_object_is_locked (tr)) {
+      /* Move the child... */
+      ges_track_object_set_start (tr, start + map->start_offset);
+    } else {
+      /* ... or update the offset */
+      map->start_offset = start - tr->start;
+    }
   }
 
+  object->priv->ignore_notifies = FALSE;
+
   object->start = start;
 }
 
@@ -500,16 +614,28 @@ ges_timeline_object_set_priority (GESTimelineObject * object, guint priority)
 {
   GList *tmp;
   GESTrackObject *tr;
+  ObjectMapping *map;
+
+  GST_DEBUG ("object:%p, priority:%" GST_TIME_FORMAT,
+      object, GST_TIME_ARGS (priority));
 
-  GST_DEBUG ("object:%p, priority:%d", object, priority);
+  object->priv->ignore_notifies = TRUE;
 
   for (tmp = object->priv->trackobjects; tmp; tmp = g_list_next (tmp)) {
     tr = (GESTrackObject *) tmp->data;
-    if (ges_track_object_is_locked (tr))
-      /* call set_priority on each trackobject */
-      ges_track_object_set_priority (tr, priority);
+    map = find_object_mapping (object, tr);
+
+    if (ges_track_object_is_locked (tr)) {
+      /* Move the child... */
+      ges_track_object_set_priority (tr, priority + map->priority_offset);
+    } else {
+      /* ... or update the offset */
+      map->priority_offset = priority - tr->priority;
+    }
   }
 
+  object->priv->ignore_notifies = FALSE;
+
   object->priority = priority;
 }
 
@@ -605,22 +731,89 @@ ges_timeline_object_get_track_objects (GESTimelineObject * object)
 /*
  * PROPERTY NOTIFICATIONS FROM TRACK OBJECTS
  */
+
 static void
-track_object_priority_offset_changed_cb (GESTrackObject * child,
+track_object_start_changed_cb (GESTrackObject * child,
     GParamSpec * arg G_GNUC_UNUSED, GESTimelineObject * object)
 {
-  guint new, old;
+  ObjectMapping *map;
+
+  if (object->priv->ignore_notifies)
+    return;
+
+  map = find_object_mapping (object, child);
+  if (G_UNLIKELY (map == NULL))
+    /* something massively screwed up if we get this */
+    return;
+
+  if (!ges_track_object_is_locked (child)) {
+    /* Update the internal start_offset */
+    map->start_offset = object->start - child->start;
+  } else {
+    /* Or update the parent start */
+    ges_timeline_object_set_start (object, child->start + map->start_offset);
+  }
+}
 
-  /* all track objects have height 1 */
-  new = ges_track_object_get_priority_offset (child) + 1;
-  old = GES_TIMELINE_OBJECT_HEIGHT (object);
+static void
+track_object_inpoint_changed_cb (GESTrackObject * child,
+    GParamSpec * arg G_GNUC_UNUSED, GESTimelineObject * object)
+{
+  if (object->priv->ignore_notifies)
+    return;
 
-  GST_LOG ("object %p, new=%d, old=%d", object, new, old);
+}
 
-  if (new > old) {
-    object->height = new;
-    GST_LOG ("emitting notify signal");
-    /* FIXME : use g_object_notify_by_pspec */
-    g_object_notify ((GObject *) object, "height");
+static void
+track_object_duration_changed_cb (GESTrackObject * child,
+    GParamSpec * arg G_GNUC_UNUSED, GESTimelineObject * object)
+{
+  if (object->priv->ignore_notifies)
+    return;
+
+}
+
+static void
+track_object_priority_changed_cb (GESTrackObject * child,
+    GParamSpec * arg G_GNUC_UNUSED, GESTimelineObject * object)
+{
+  ObjectMapping *map;
+
+  if (object->priv->ignore_notifies)
+    return;
+
+  map = find_object_mapping (object, child);
+  if (G_UNLIKELY (map == NULL))
+    /* something massively screwed up if we get this */
+    return;
+
+  if (!ges_track_object_is_locked (child)) {
+    GList *tmp;
+    guint32 min_prio = G_MAXUINT32, max_prio = 0;
+
+    /* Update the internal priority_offset */
+    map->priority_offset = object->priority - child->priority;
+
+    /* Go over all childs and check if height has changed */
+    for (tmp = object->priv->trackobjects; tmp; tmp = tmp->next) {
+      GESTrackObject *tmpo = (GESTrackObject *) tmp->data;
+
+      if (tmpo->priority < min_prio)
+        min_prio = tmpo->priority;
+      if (tmpo->priority > max_prio)
+        max_prio = tmpo->priority;
+    }
+
+    /* FIXME : We only grow the height */
+    if (object->height < (max_prio - min_prio + 1)) {
+      object->height = max_prio - min_prio + 1;
+      g_object_notify_by_pspec (G_OBJECT (object), properties[PROP_HEIGHT]);
+    }
+  } else {
+    /* Or update the parent priority */
+    ges_timeline_object_set_priority (object,
+        child->priority + map->priority_offset);
+    /* For the locked situation, we don't need to check the height,
+     * since all object priorities are moving together */
   }
 }
index 690d22c..6835216 100644 (file)
@@ -253,8 +253,8 @@ ges_timeline_pipeline_change_state (GstElement * element,
         ret = GST_STATE_CHANGE_FAILURE;
         goto done;
       }
-      if (self->priv->
-          mode & (TIMELINE_MODE_RENDER | TIMELINE_MODE_SMART_RENDER))
+      if (self->
+          priv->mode & (TIMELINE_MODE_RENDER | TIMELINE_MODE_SMART_RENDER))
         GST_DEBUG ("rendering => Updating pipeline caps");
       if (!ges_timeline_pipeline_update_caps (self)) {
         GST_ERROR_OBJECT (element, "Error setting the caps for rendering");
index 3fa40bf..3b2eaad 100644 (file)
@@ -128,16 +128,6 @@ GST_START_TEST (test_layer_properties)
   gnl_object_check (ges_track_object_get_gnlobject (trackobject), 42, 51, 12,
       51, 0, TRUE);
 
-  /* check priority offsets */
-  assert_equals_int (GES_TIMELINE_OBJECT_HEIGHT (object), 1);
-  g_object_set (trackobject, "priority-offset", (guint32) 3, NULL);
-  gnl_object_check (ges_track_object_get_gnlobject (trackobject), 42, 51, 12,
-      51, 3, TRUE);
-  g_object_set (object, "priority", 5, NULL);
-  gnl_object_check (ges_track_object_get_gnlobject (trackobject), 42, 51, 12,
-      51, 8, TRUE);
-  assert_equals_int (GES_TIMELINE_OBJECT_HEIGHT (object), 4);
-
   g_object_unref (trackobject);
   fail_unless (ges_timeline_layer_remove_object (layer, object));
   fail_unless (ges_timeline_remove_track (timeline, track));
index dacba20..caf9046 100644 (file)
@@ -105,6 +105,15 @@ GST_START_TEST (test_object_properties)
   gnl_object_check (ges_track_object_get_gnlobject (trackobject), 420, 510, 120,
       510, 0, TRUE);
 
+
+  /* This time, we move the trackobject to see if the changes move
+   * along to the parent and the gnonlin object */
+  g_object_set (trackobject, "start", (guint64) 400, NULL);
+  assert_equals_uint64 (GES_TIMELINE_OBJECT_START (object), 400);
+  assert_equals_uint64 (GES_TRACK_OBJECT_START (trackobject), 400);
+  gnl_object_check (ges_track_object_get_gnlobject (trackobject), 400, 510, 120,
+      510, 0, TRUE);
+
   ges_timeline_object_release_track_object (object, trackobject);
 
   g_object_unref (object);
@@ -166,6 +175,17 @@ GST_START_TEST (test_object_properties_unlocked)
   gnl_object_check (ges_track_object_get_gnlobject (trackobject), 42, 51, 12,
       51, 0, TRUE);
 
+  /* When unlocked, moving the GESTrackObject won't move the GESTimelineObject
+   * either */
+  /* This time, we move the trackobject to see if the changes move
+   * along to the parent and the gnonlin object */
+  g_object_set (trackobject, "start", (guint64) 400, NULL);
+  assert_equals_uint64 (GES_TIMELINE_OBJECT_START (object), 420);
+  assert_equals_uint64 (GES_TRACK_OBJECT_START (trackobject), 400);
+  gnl_object_check (ges_track_object_get_gnlobject (trackobject), 400, 51, 12,
+      51, 0, TRUE);
+
+
   ges_timeline_object_release_track_object (object, trackobject);
 
   g_object_unref (object);