timeline: Track TrackObject-s by layer
authorThibault Saunier <thibault.saunier@collabora.com>
Thu, 10 Jan 2013 14:39:46 +0000 (11:39 -0300)
committerThibault Saunier <thibault.saunier@collabora.com>
Thu, 10 Jan 2013 14:50:02 +0000 (11:50 -0300)
ges/ges-timeline.c

index 2ef5370..154919c 100644 (file)
@@ -68,6 +68,10 @@ typedef struct TrackObjIters
   GSequenceIter *iter_start;
   GSequenceIter *iter_end;
   GSequenceIter *iter_obj;
+  GSequenceIter *iter_by_layer;
+
+  GESTimelineLayer *layer;
+  GESTrackObject *tckobj;
 } TrackObjIters;
 
 static void
@@ -135,6 +139,9 @@ struct _GESTimelinePrivate
   GSequence *tracksources;      /* TrackSource-s sorted by start/priorities */
 
   GList *priv_tracks;
+  /* FIXME: We should definitly offer an API over this,
+   * probably through a ges_timeline_layer_get_track_objects () method */
+  GHashTable *by_layer;         /* {layer: GSequence of TrackObject by start/priorities} */
 
   MoveContext movecontext;
 };
@@ -518,6 +525,8 @@ ges_timeline_init (GESTimeline * self)
   priv->by_start = g_hash_table_new (g_direct_hash, g_direct_equal);
   priv->by_end = g_hash_table_new (g_direct_hash, g_direct_equal);
   priv->by_object = g_hash_table_new (g_direct_hash, g_direct_equal);
+  priv->by_layer = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL,
+      (GDestroyNotify) g_sequence_free);
   priv->obj_iters = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL,
       (GDestroyNotify) _destroy_obj_iters);
   priv->starts_ends = g_sequence_new (g_free);
@@ -572,11 +581,22 @@ timeline_update_duration (GESTimeline * timeline)
   }
 }
 
-static inline void
-sort_track_objects (GESTimeline * timeline, GESTrackObject * obj)
+static gint
+find_layer_by_prio (GESTimelineLayer * a, gpointer pprio)
 {
-  TrackObjIters *iters = g_hash_table_lookup (timeline->priv->obj_iters, obj);
+  gint prio = GPOINTER_TO_INT (pprio), lprio =
+      ges_timeline_layer_get_priority (a);
+
+  if (lprio < prio)
+    return -1;
+  if (lprio > prio)
+    return 1;
+  return 0;
+}
 
+static void
+sort_track_objects (GESTimeline * timeline, TrackObjIters * iters)
+{
   g_sequence_sort_changed (iters->iter_obj,
       (GCompareDataFunc) track_object_start_compare, NULL);
 }
@@ -601,11 +621,11 @@ custom_find_track (TrackPrivate * tr_priv, GESTrack * track)
 }
 
 static inline void
-sort_starts_ends_end (GESTimeline * timeline, GESTrackObject * obj)
+sort_starts_ends_end (GESTimeline * timeline, TrackObjIters * iters)
 {
+  GESTrackObject *obj = iters->tckobj;
   GESTimelinePrivate *priv = timeline->priv;
   guint64 *end = g_hash_table_lookup (priv->by_end, obj);
-  TrackObjIters *iters = g_hash_table_lookup (priv->obj_iters, obj);
 
   *end = obj->start + obj->duration;
 
@@ -615,11 +635,11 @@ sort_starts_ends_end (GESTimeline * timeline, GESTrackObject * obj)
 }
 
 static inline void
-sort_starts_ends_start (GESTimeline * timeline, GESTrackObject * obj)
+sort_starts_ends_start (GESTimeline * timeline, TrackObjIters * iters)
 {
+  GESTrackObject *obj = iters->tckobj;
   GESTimelinePrivate *priv = timeline->priv;
   guint64 *start = g_hash_table_lookup (priv->by_start, obj);
-  TrackObjIters *iters = g_hash_table_lookup (priv->obj_iters, obj);
 
   *start = obj->start;
 
@@ -651,58 +671,97 @@ clean_movecontext (MoveContext * mv_ctx)
 }
 
 static void
-stop_tracking_for_snapping (GESTimeline * timeline, GESTrackObject * tckobj)
+stop_tracking_track_object (GESTimeline * timeline, GESTrackObject * tckobj)
 {
   guint64 *start, *end;
-  GESTimelinePrivate *priv = timeline->priv;
   TrackObjIters *iters;
+  GESTimelinePrivate *priv = timeline->priv;
 
-
-  start = g_hash_table_lookup (priv->by_start, tckobj);
-  end = g_hash_table_lookup (priv->by_end, tckobj);
   iters = g_hash_table_lookup (priv->obj_iters, tckobj);
+  if (G_LIKELY (iters->iter_by_layer)) {
+    g_sequence_remove (iters->iter_by_layer);
+  } else {
+    GST_WARNING_OBJECT (timeline, "TrackObject %p was in no layer", tckobj);
+  }
 
-  g_hash_table_remove (priv->by_start, tckobj);
-  g_hash_table_remove (priv->by_end, tckobj);
-  g_hash_table_remove (priv->by_object, end);
-  g_hash_table_remove (priv->by_object, start);
-  g_sequence_remove (iters->iter_start);
-  g_sequence_remove (iters->iter_end);
-  g_sequence_remove (iters->iter_obj);
+  if (GES_IS_TRACK_SOURCE (tckobj)) {
+    start = g_hash_table_lookup (priv->by_start, tckobj);
+    end = g_hash_table_lookup (priv->by_end, tckobj);
+
+    g_hash_table_remove (priv->by_start, tckobj);
+    g_hash_table_remove (priv->by_end, tckobj);
+    g_hash_table_remove (priv->by_object, end);
+    g_hash_table_remove (priv->by_object, start);
+    g_sequence_remove (iters->iter_start);
+    g_sequence_remove (iters->iter_end);
+    g_sequence_remove (iters->iter_obj);
+    timeline_update_duration (timeline);
+  }
   g_hash_table_remove (priv->obj_iters, tckobj);
-  timeline_update_duration (timeline);
 }
 
 static void
-start_tracking_track_obj (GESTimeline * timeline, GESTrackObject * tckobj)
+start_tracking_track_object (GESTimeline * timeline, GESTrackObject * tckobj)
 {
-  TrackObjIters *iters;
   guint64 *pstart, *pend;
+  GSequence *by_layer_sequence;
+  TrackObjIters *iters;
   GESTimelinePrivate *priv = timeline->priv;
 
-  pstart = g_malloc (sizeof (guint64));
-  pend = g_malloc (sizeof (guint64));
-  *pstart = tckobj->start;
-  *pend = *pstart + tckobj->duration;
+  guint layer_prio = tckobj->priority / LAYER_HEIGHT;
+  GList *layer_node = g_list_find_custom (timeline->layers,
+      GINT_TO_POINTER (layer_prio), (GCompareFunc) find_layer_by_prio);
+  GESTimelineLayer *layer = layer_node ? layer_node->data : NULL;
 
-  iters = g_slice_new (TrackObjIters);
-  iters->iter_start = g_sequence_insert_sorted (priv->starts_ends, pstart,
-      (GCompareDataFunc) compare_uint64, NULL);
-  iters->iter_end = g_sequence_insert_sorted (priv->starts_ends, pend,
-      (GCompareDataFunc) compare_uint64, NULL);
-  iters->iter_obj =
-      g_sequence_insert_sorted (priv->tracksources, g_object_ref (tckobj),
-      (GCompareDataFunc) track_object_start_compare, NULL);
+  iters = g_slice_new0 (TrackObjIters);
 
-  g_hash_table_insert (priv->by_start, tckobj, pstart);
-  g_hash_table_insert (priv->by_object, pstart, tckobj);
-  g_hash_table_insert (priv->by_end, tckobj, pend);
-  g_hash_table_insert (priv->by_object, pend, tckobj);
+  /* We add all TrackObject to obj_iters as we always follow them
+   * in the by_layer Sequences */
   g_hash_table_insert (priv->obj_iters, tckobj, iters);
 
-  timeline->priv->movecontext.needs_move_ctx = TRUE;
+  /* Track all objects by layer */
+  if (G_UNLIKELY (layer == NULL)) {
+    /* We handle the case where we have TrackObject that are in no layer by not
+     * tracking them
+     *
+     * FIXME: Check if we should rather try to track them in some sort of
+     * dummy layer, or even refuse TrackObjects to be added in Tracks if
+     * they land in no layer the timeline controls.
+     */
+    GST_ERROR_OBJECT (timeline, "Adding a TrackObject that lands in no layer "
+        "we are controlling");
+  } else {
+    by_layer_sequence = g_hash_table_lookup (priv->by_layer, layer);
+    iters->iter_by_layer = g_sequence_insert_sorted (by_layer_sequence, tckobj,
+        (GCompareDataFunc) track_object_start_compare, NULL);
+    iters->layer = layer;
+  }
 
-  timeline_update_duration (timeline);
+  if (GES_IS_TRACK_SOURCE (tckobj)) {
+    /* Track only sources for timeline edition and snapping */
+    pstart = g_malloc (sizeof (guint64));
+    pend = g_malloc (sizeof (guint64));
+    *pstart = tckobj->start;
+    *pend = *pstart + tckobj->duration;
+
+    iters->iter_start = g_sequence_insert_sorted (priv->starts_ends, pstart,
+        (GCompareDataFunc) compare_uint64, NULL);
+    iters->iter_end = g_sequence_insert_sorted (priv->starts_ends, pend,
+        (GCompareDataFunc) compare_uint64, NULL);
+    iters->iter_obj =
+        g_sequence_insert_sorted (priv->tracksources, g_object_ref (tckobj),
+        (GCompareDataFunc) track_object_start_compare, NULL);
+    iters->tckobj = tckobj;
+
+    g_hash_table_insert (priv->by_start, tckobj, pstart);
+    g_hash_table_insert (priv->by_object, pstart, tckobj);
+    g_hash_table_insert (priv->by_end, tckobj, pend);
+    g_hash_table_insert (priv->by_object, pend, tckobj);
+
+    timeline->priv->movecontext.needs_move_ctx = TRUE;
+
+    timeline_update_duration (timeline);
+  }
 }
 
 static inline void
@@ -885,7 +944,7 @@ ges_move_context_set_objects (GESTimeline * timeline, GESTrackObject * obj,
   tckobj_iter = iters->iter_obj;
   switch (edge) {
     case GES_EDGE_START:
-      /* set it properly int the context of "trimming" */
+      /* set it properly in the context of "trimming" */
       mv_ctx->max_trim_pos = 0;
       start = obj->start;
 
@@ -1611,64 +1670,123 @@ static void
 trackobj_start_changed_cb (GESTrackObject * child,
     GParamSpec * arg G_GNUC_UNUSED, GESTimeline * timeline)
 {
-  sort_track_objects (timeline, child);
-  sort_starts_ends_start (timeline, child);
-  sort_starts_ends_end (timeline, child);
+  GESTimelinePrivate *priv = timeline->priv;
+  TrackObjIters *iters = g_hash_table_lookup (priv->obj_iters, child);
+
+  if (G_LIKELY (iters->iter_by_layer))
+    g_sequence_sort_changed (iters->iter_by_layer,
+        (GCompareDataFunc) track_object_start_compare, NULL);
+
+  if (GES_IS_TRACK_SOURCE (child)) {
+    sort_track_objects (timeline, iters);
+    sort_starts_ends_start (timeline, iters);
+    sort_starts_ends_end (timeline, iters);
+
+    /* If the timeline is set to snap objects together, we
+     * are sure that all movement of TrackObject-s are done within
+     * the moving context, so we do not need to recalculate the
+     * move context as often */
+    if (timeline->priv->movecontext.ignore_needs_ctx &&
+        timeline->priv->snapping_distance == 0)
+      timeline->priv->movecontext.needs_move_ctx = TRUE;
 
-  /* If the timeline is set to snap objects together, we
-   * are sure that all movement of TrackObject-s are done within
-   * the moving context, so we do not need to recalculate the
-   * move context as often */
-  if (timeline->priv->movecontext.ignore_needs_ctx &&
-      timeline->priv->snapping_distance == 0)
-    timeline->priv->movecontext.needs_move_ctx = TRUE;
+  }
+}
+
+static void
+trackobj_priority_changed_cb (GESTrackObject * child,
+    GParamSpec * arg G_GNUC_UNUSED, GESTimeline * timeline)
+{
+  GESTimelinePrivate *priv = timeline->priv;
+
+  GList *layer_node = g_list_find_custom (timeline->layers,
+      GINT_TO_POINTER (child->priority / LAYER_HEIGHT),
+      (GCompareFunc) find_layer_by_prio);
+  GESTimelineLayer *layer = layer_node ? layer_node->data : NULL;
+  TrackObjIters *iters = g_hash_table_lookup (priv->obj_iters,
+      child);
+
+  if (G_UNLIKELY (layer == NULL)) {
+    GST_ERROR_OBJECT (timeline,
+        "Changing a TrackObject prio, which would not "
+        "land in no layer we are controlling");
+    g_sequence_remove (iters->iter_by_layer);
+    iters->iter_by_layer = NULL;
+    iters->layer = NULL;
+  } else {
+    /* If it moves from layer, properly change it */
+    if (layer != iters->layer) {
+      GSequence *by_layer_sequence =
+          g_hash_table_lookup (priv->by_layer, layer);
+
+      g_sequence_remove (iters->iter_by_layer);
+      iters->iter_by_layer =
+          g_sequence_insert_sorted (by_layer_sequence, child,
+          (GCompareDataFunc) track_object_start_compare, NULL);
+      iters->layer = layer;
+    } else {
+      g_sequence_sort_changed (iters->iter_by_layer,
+          (GCompareDataFunc) track_object_start_compare, NULL);
+    }
+  }
+
+  if (GES_IS_TRACK_SOURCE (child))
+    sort_track_objects (timeline, iters);
 }
 
 static void
 trackobj_duration_changed_cb (GESTrackObject * child,
     GParamSpec * arg G_GNUC_UNUSED, GESTimeline * timeline)
 {
-  sort_starts_ends_end (timeline, child);
+  GESTimelinePrivate *priv = timeline->priv;
+  TrackObjIters *iters = g_hash_table_lookup (priv->obj_iters, child);
 
-  /* If the timeline is set to snap objects together, we
-   * are sure that all movement of TrackObject-s are done within
-   * the moving context, so we do not need to recalculate the
-   * move context as often */
-  if (timeline->priv->movecontext.ignore_needs_ctx &&
-      timeline->priv->snapping_distance == 0)
-    timeline->priv->movecontext.needs_move_ctx = TRUE;
+  if (GES_IS_TRACK_SOURCE (child)) {
+    sort_starts_ends_end (timeline, iters);
+
+    /* If the timeline is set to snap objects together, we
+     * are sure that all movement of TrackObject-s are done within
+     * the moving context, so we do not need to recalculate the
+     * move context as often */
+    if (timeline->priv->movecontext.ignore_needs_ctx &&
+        timeline->priv->snapping_distance == 0) {
+      timeline->priv->movecontext.needs_move_ctx = TRUE;
+    }
+  }
 }
 
 static void
 track_object_added_cb (GESTrack * track, GESTrackObject * object,
     GESTimeline * timeline)
 {
-  /* We only work with sources */
-  if (GES_IS_TRACK_SOURCE (object)) {
-    start_tracking_track_obj (timeline, object);
+  /* Auto transition should be updated before we receive the signal */
+  g_signal_connect_after (GES_TRACK_OBJECT (object), "notify::start",
+      G_CALLBACK (trackobj_start_changed_cb), timeline);
+  g_signal_connect_after (GES_TRACK_OBJECT (object), "notify::duration",
+      G_CALLBACK (trackobj_duration_changed_cb), timeline);
+  g_signal_connect_after (GES_TRACK_OBJECT (object), "notify::priority",
+      G_CALLBACK (trackobj_priority_changed_cb), timeline);
 
-    g_signal_connect (GES_TRACK_OBJECT (object), "notify::start",
-        G_CALLBACK (trackobj_start_changed_cb), timeline);
-    g_signal_connect (GES_TRACK_OBJECT (object), "notify::duration",
-        G_CALLBACK (trackobj_duration_changed_cb), timeline);
-  }
+  start_tracking_track_object (timeline, object);
 }
 
 static void
 track_object_removed_cb (GESTrack * track, GESTrackObject * object,
     GESTimeline * timeline)
 {
-  /* We only work with sources */
+
   if (GES_IS_TRACK_SOURCE (object)) {
-    g_signal_handlers_disconnect_by_func (object, trackobj_start_changed_cb,
-        NULL);
-    g_signal_handlers_disconnect_by_func (object, trackobj_duration_changed_cb,
-        NULL);
 
     /* Make sure to reinitialise the moving context next time */
     timeline->priv->movecontext.needs_move_ctx = TRUE;
-    stop_tracking_for_snapping (timeline, object);
   }
+
+  /* Disconnect all signal handlers */
+  g_signal_handlers_disconnect_by_func (object, trackobj_duration_changed_cb,
+      NULL);
+  g_signal_handlers_disconnect_by_func (object, trackobj_priority_changed_cb,
+      NULL);
+  stop_tracking_track_object (timeline, object);
 }
 
 static void
@@ -1903,6 +2021,8 @@ ges_timeline_add_layer (GESTimeline * timeline, GESTimelineLayer * layer)
   /* Inform the layer that it belongs to a new timeline */
   ges_timeline_layer_set_timeline (layer, timeline);
 
+  g_hash_table_insert (timeline->priv->by_layer, layer, g_sequence_new (NULL));
+
   /* Connect to 'object-added'/'object-removed' signal from the new layer */
   g_signal_connect (layer, "object-added", G_CALLBACK (layer_object_added_cb),
       timeline);
@@ -1966,6 +2086,7 @@ ges_timeline_remove_layer (GESTimeline * timeline, GESTimelineLayer * layer)
   g_signal_handlers_disconnect_by_func (layer, layer_object_removed_cb,
       timeline);
 
+  g_hash_table_remove (timeline->priv->by_layer, layer);
   timeline->layers = g_list_remove (timeline->layers, layer);
 
   ges_timeline_layer_set_timeline (layer, NULL);