timeline: re-handle clip children track selection
authorHenry Wilkes <hwilkes@igalia.com>
Mon, 6 Apr 2020 11:09:54 +0000 (12:09 +0100)
committerHenry Wilkes <hwilkes@igalia.com>
Wed, 8 Apr 2020 13:35:28 +0000 (14:35 +0100)
The way a clip's track elements are added to tracks was re-handled. This
doesn't affect the normal usage of a simple audio-video timeline, where
the tracks are added before any clips, but usage for multi-track
timelines has improved. The main changes are:

+ We can now handle a track being selected for more than one track,
  including a full copy of their children properties and bindings.
  (Previously broken.)
+ When a clip is split, we copy the new elements directly into the same
  track, avoiding select-tracks-for-object.
+ When a clip is grouped or ungrouped, we avoid moving the elements to
  or from tracks.
+ Added API to allow users to copy the core elements of a clip directly
  into a track, complementing select-tracks-for-object.
+ Enforced the rule that a clip can only contain one core child in a
  track, and all the non-core children must be added to tracks that
  already contains a core child. This extends the previous condition
  that two sources from the same clip should not be added to the same
  track.
+ Made ges_track_add_element check that the newly added track element
  does not break the configuration rules of the timeline.
+ When adding a track to a timeline, we only use
  select-tracks-for-object to check whether track elements should be
  added to the new track, not existing ones.
+ When removing a track from a timeline, we empty it of all the track
  elements that are controlled by a clip. Thus, we ensure that a clip
  only contains elements that are in the tracks of the same timeline, or
  no track. Similarly, when removing a clip from a timeline.
+ We can now avoid unsupported timeline configurations when a layer is
  added to a timeline, and already contains clips.
+ We can now avoid unsupported timeline configurations when a track is
  added to a timeline, and the timeline already contains clips.

Fixes https://gitlab.freedesktop.org/gstreamer/gst-editing-services/-/issues/84

ges/ges-clip.c
ges/ges-clip.h
ges/ges-internal.h
ges/ges-layer.c
ges/ges-timeline-element.c
ges/ges-timeline.c
ges/ges-track-element.c
ges/ges-track.c
tests/check/ges/basic.c
tests/check/ges/clip.c
tests/check/ges/test-utils.h

index 7e6c295..99767c1 100644 (file)
@@ -113,6 +113,8 @@ struct _GESClipPrivate
   gboolean prevent_max_duration_update;
   gboolean setting_inpoint;
 
+  gboolean allow_any_track;
+
   /* The formats supported by this Clip */
   GESTrackType supportedformats;
 };
@@ -148,8 +150,10 @@ G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GESClip, ges_clip,
  *              Listen to our children              *
  ****************************************************/
 
-#define _IS_CORE_CHILD(child) \
-  (ges_track_element_get_creators (GES_TRACK_ELEMENT (child)) != NULL)
+#define _IS_CORE_CHILD(child) GES_TRACK_ELEMENT_IS_CORE(child)
+
+#define _IS_TOP_EFFECT(child) \
+    (!_IS_CORE_CHILD (child) && GES_IS_BASE_EFFECT (child))
 
 #define _IS_CORE_INTERNAL_SOURCE_CHILD(child) \
   (_IS_CORE_CHILD (child) \
@@ -275,6 +279,85 @@ _child_has_internal_source_changed_cb (GESTimelineElement * child,
   _set_inpoint0 (child, _INPOINT (container));
 }
 
+/****************************************************
+ *              Restrict our children               *
+ ****************************************************/
+
+static gboolean
+_track_contains_core (GESClip * clip, GESTrack * track, gboolean core)
+{
+  GList *tmp;
+  for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) {
+    GESTrackElement *child = tmp->data;
+    if (_IS_CORE_CHILD (child) == core
+        && ges_track_element_get_track (child) == track)
+      return TRUE;
+  }
+  return FALSE;
+}
+
+gboolean
+ges_clip_can_set_track_of_child (GESClip * clip, GESTrackElement * child,
+    GESTrack * track)
+{
+  GESTrack *current_track = ges_track_element_get_track (child);
+
+  if (clip->priv->allow_any_track)
+    return TRUE;
+
+  if (current_track == track)
+    return TRUE;
+
+  if (current_track) {
+    /* can not remove a core element from a track if a non-core one sits
+     * above it */
+    if (_IS_CORE_CHILD (child)
+        && _track_contains_core (clip, current_track, FALSE)) {
+      GST_INFO_OBJECT (clip, "Cannot move the core child %" GES_FORMAT
+          " to the track %" GST_PTR_FORMAT " because it has non-core "
+          "siblings above it in its current track %" GST_PTR_FORMAT,
+          GES_ARGS (child), track, current_track);
+      return FALSE;
+    }
+    /* otherwise can remove */
+  }
+  if (track) {
+    GESTimeline *clip_timeline = GES_TIMELINE_ELEMENT_TIMELINE (clip);
+    const GESTimeline *track_timeline = ges_track_get_timeline (track);
+    if (track_timeline == NULL) {
+      GST_INFO_OBJECT (clip, "Cannot move the child %" GES_FORMAT
+          " to the track %" GST_PTR_FORMAT " because it is not part "
+          "of a timeline", GES_ARGS (child), track);
+      return FALSE;
+    }
+    if (track_timeline != clip_timeline) {
+      GST_INFO_OBJECT (clip, "Cannot move the child %" GES_FORMAT
+          " to the track %" GST_PTR_FORMAT " because its timeline %"
+          GST_PTR_FORMAT " does not match the clip's timeline %"
+          GST_PTR_FORMAT, GES_ARGS (child), track, track_timeline,
+          clip_timeline);
+      return FALSE;
+    }
+    /* one core child per track, and other children (effects) can only be
+     * placed in a track that already has a core child */
+    if (_IS_CORE_CHILD (child)) {
+      if (_track_contains_core (clip, track, TRUE)) {
+        GST_INFO_OBJECT (clip, "Cannot move the core child %" GES_FORMAT
+            " to the track %" GST_PTR_FORMAT " because it contains a "
+            "core sibling", GES_ARGS (child), track);
+        return FALSE;
+      }
+    } else {
+      if (!_track_contains_core (clip, track, TRUE)) {
+        GST_INFO_OBJECT (clip, "Cannot move the non-core child %"
+            GES_FORMAT " to the track %" GST_PTR_FORMAT " because it "
+            " does not contain a core sibling", GES_ARGS (child), track);
+        return FALSE;
+      }
+    }
+  }
+  return TRUE;
+}
 
 /*****************************************************
  *                                                   *
@@ -574,17 +657,18 @@ _add_child (GESContainer * container, GESTimelineElement * element)
 {
   GESClipClass *klass = GES_CLIP_GET_CLASS (GES_CLIP (container));
   guint max_prio, min_prio;
+  GESTrack *track;
   GList *creators;
+  GESTimeline *timeline = GES_TIMELINE_ELEMENT_TIMELINE (container);
   GESClipPrivate *priv = GES_CLIP (container)->priv;
 
   g_return_val_if_fail (GES_IS_TRACK_ELEMENT (element), FALSE);
 
-  if (element->timeline
-      && element->timeline != GES_TIMELINE_ELEMENT_TIMELINE (container)) {
+  if (element->timeline && element->timeline != timeline) {
     GST_WARNING_OBJECT (container, "Cannot add %" GES_FORMAT " as a child "
         "because its timeline is %" GST_PTR_FORMAT " rather than the "
         "clip's timeline %" GST_PTR_FORMAT, GES_ARGS (element),
-        element->timeline, GES_TIMELINE_ELEMENT_TIMELINE (container));
+        element->timeline, timeline);
     return FALSE;
   }
 
@@ -597,12 +681,34 @@ _add_child (GESContainer * container, GESTimelineElement * element)
     return FALSE;
   }
 
+  track = ges_track_element_get_track (GES_TRACK_ELEMENT (element));
+
+  if (track && ges_track_get_timeline (track) != timeline) {
+    /* really, an element in a track should have the same timeline as
+     * the track, so we would have checked this with the
+     * element->timeline check. But technically a user could get around
+     * this, so we double check here. */
+    GST_WARNING_OBJECT (container, "Cannot add %" GES_FORMAT " as a child "
+        "because its track %" GST_PTR_FORMAT " is part of the timeline %"
+        GST_PTR_FORMAT " rather than the clip's timeline %" GST_PTR_FORMAT,
+        GES_ARGS (element), track, ges_track_get_timeline (track), timeline);
+    return FALSE;
+  }
+
   /* NOTE: notifies are currently frozen by ges_container_add */
   _get_priority_range (container, &min_prio, &max_prio);
   if (creators) {
     /* NOTE: Core track elements that are base effects are added like any
-     * other core clip. In particular, they are *not* added to the list of
-     * added effects, so we don not increase nb_effects. */
+     * other core elements. In particular, they are *not* added to the
+     * list of added effects, so we do not increase nb_effects. */
+
+    if (track && !priv->allow_any_track
+        && _track_contains_core (GES_CLIP (container), track, TRUE)) {
+      GST_WARNING_OBJECT (container, "Cannot add the core child %" GES_FORMAT
+          " because it is in the same track %" GST_PTR_FORMAT " as an "
+          "existing core child", GES_ARGS (element), track);
+      return FALSE;
+    }
 
     /* Set the core element to have the same in-point, which we don't
      * apply to effects */
@@ -620,12 +726,20 @@ _add_child (GESContainer * container, GESTimelineElement * element)
 
     /* Always add at the same priority, on top of existing effects */
     _set_priority0 (element, min_prio + priv->nb_effects);
-  } else if (GES_CLIP_CLASS_CAN_ADD_EFFECTS (klass) &&
-      GES_IS_BASE_EFFECT (element)) {
+  } else if (GES_CLIP_CLASS_CAN_ADD_EFFECTS (klass) && _IS_TOP_EFFECT (element)) {
     GList *tmp;
     /* Add the effect at the lowest priority among effects (just after
      * the core elements). Need to shift the core elements up by 1
      * to make room. */
+
+    if (track && !priv->allow_any_track
+        && !_track_contains_core (GES_CLIP (container), track, TRUE)) {
+      GST_WARNING_OBJECT (container, "Cannot add the effect %" GES_FORMAT
+          " because its track %" GST_PTR_FORMAT " does not contain one "
+          "of the clip's core children", GES_ARGS (element), track);
+      return FALSE;
+    }
+
     GST_DEBUG_OBJECT (container, "Adding %ith effect: %" GES_FORMAT
         " Priority %i", priv->nb_effects + 1, GES_ARGS (element),
         min_prio + priv->nb_effects);
@@ -646,7 +760,7 @@ _add_child (GESContainer * container, GESTimelineElement * element)
     /* The height has already changed (increased by 1) */
     _compute_height (container);
   } else {
-    if (GES_IS_BASE_EFFECT (element))
+    if (_IS_TOP_EFFECT (element))
       GST_WARNING_OBJECT (container, "Cannot add the effect %" GES_FORMAT
           " because it is not a core element created by the clip itself "
           "and the %s class does not allow for adding extra effects",
@@ -674,7 +788,7 @@ _remove_child (GESContainer * container, GESTimelineElement * element)
   GESClipPrivate *priv = GES_CLIP (container)->priv;
 
   /* NOTE: notifies are currently frozen by ges_container_add */
-  if (!_IS_CORE_CHILD (element) && GES_IS_BASE_EFFECT (element)) {
+  if (_IS_TOP_EFFECT (element)) {
     GList *tmp;
     GST_DEBUG_OBJECT (container, "Resyncing effects priority.");
 
@@ -701,13 +815,13 @@ _remove_child (GESContainer * container, GESTimelineElement * element)
 static void
 _child_added (GESContainer * container, GESTimelineElement * element)
 {
-  g_signal_connect (G_OBJECT (element), "notify::priority",
+  g_signal_connect (element, "notify::priority",
       G_CALLBACK (_child_priority_changed_cb), container);
-  g_signal_connect (G_OBJECT (element), "notify::in-point",
+  g_signal_connect (element, "notify::in-point",
       G_CALLBACK (_child_inpoint_changed_cb), container);
-  g_signal_connect (G_OBJECT (element), "notify::max-duration",
+  g_signal_connect (element, "notify::max-duration",
       G_CALLBACK (_child_max_duration_changed_cb), container);
-  g_signal_connect (G_OBJECT (element), "notify::has-internal-source",
+  g_signal_connect (element, "notify::has-internal-source",
       G_CALLBACK (_child_has_internal_source_changed_cb), container);
 
   _child_priority_changed_cb (element, NULL, container);
@@ -738,12 +852,24 @@ add_clip_to_list (gpointer key, gpointer clip, GList ** list)
   *list = g_list_prepend (*list, gst_object_ref (clip));
 }
 
+/* NOTE: Since this does not change the track of @child, this should
+ * only be called if it is guaranteed that neither @from_clip nor @to_clip
+ * will not break the track rules:
+ * + no more than one core child per track
+ * + every non-core child must be in the same track as a core child
+ */
 static void
 _transfer_child (GESClip * from_clip, GESClip * to_clip,
     GESTrackElement * child)
 {
+  GESTimeline *timeline = GES_TIMELINE_ELEMENT_TIMELINE (to_clip);
+
   /* We need to bump the refcount to avoid the object to be destroyed */
   gst_object_ref (child);
+
+  /* don't want to change tracks */
+  ges_timeline_set_moving_track_elements (timeline, TRUE);
+
   ges_container_remove (GES_CONTAINER (from_clip),
       GES_TIMELINE_ELEMENT (child));
 
@@ -752,7 +878,11 @@ _transfer_child (GESClip * from_clip, GESClip * to_clip,
     ges_track_element_add_creator (child, to_clip);
   }
 
+  to_clip->priv->allow_any_track = TRUE;
   ges_container_add (GES_CONTAINER (to_clip), GES_TIMELINE_ELEMENT (child));
+  to_clip->priv->allow_any_track = FALSE;
+  ges_timeline_set_moving_track_elements (timeline, FALSE);
+
   gst_object_unref (child);
 }
 
@@ -982,10 +1112,87 @@ _group (GList * containers)
 done:
   if (tracks)
     g_free (tracks);
-
   return ret;
 }
 
+void
+ges_clip_empty_from_track (GESClip * clip, GESTrack * track)
+{
+  GList *tmp;
+  if (track == NULL)
+    return;
+  /* allow us to remove in any order */
+  clip->priv->allow_any_track = TRUE;
+
+  for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) {
+    GESTrackElement *child = tmp->data;
+    if (ges_track_element_get_track (child) == track) {
+      if (!ges_track_remove_element (track, child))
+        GST_ERROR_OBJECT (clip, "Failed to remove child %" GES_FORMAT
+            " from the track %" GST_PTR_FORMAT, GES_ARGS (child), track);
+    }
+  }
+  clip->priv->allow_any_track = FALSE;
+}
+
+static GESTrackElement *
+_copy_track_element_to (GESTrackElement * orig, GESClip * to_clip,
+    GstClockTime position)
+{
+  GESTrackElement *copy;
+  GESTimelineElement *el_copy, *el_orig;
+
+  /* NOTE: we do not deep copy the track element, we instead call
+   * ges_track_element_copy_properties explicitly, which is the
+   * deep_copy for the GESTrackElementClass. */
+  el_orig = GES_TIMELINE_ELEMENT (orig);
+  el_copy = ges_timeline_element_copy (el_orig, FALSE);
+
+  if (el_copy == NULL)
+    return NULL;
+
+  copy = GES_TRACK_ELEMENT (el_copy);
+  ges_track_element_copy_properties (el_orig, el_copy);
+  /* NOTE: control bindings that are not registered in GES are not
+   * handled */
+  ges_track_element_copy_bindings (orig, copy, position);
+
+  if (_IS_CORE_CHILD (orig))
+    ges_track_element_add_creator (copy, to_clip);
+
+  return copy;
+}
+
+static GESTrackElement *
+ges_clip_copy_track_element_into (GESClip * clip, GESTrackElement * orig,
+    GstClockTime position)
+{
+  GESTimeline *timeline = GES_TIMELINE_ELEMENT_TIMELINE (clip);
+  GESTrackElement *copy;
+
+  copy = _copy_track_element_to (orig, clip, position);
+  if (copy == NULL) {
+    GST_ERROR_OBJECT (clip, "Failed to create a copy of the "
+        "element %" GES_FORMAT " for the clip", GES_ARGS (orig));
+    return NULL;
+  }
+
+  gst_object_ref (copy);
+  ges_timeline_set_moving_track_elements (timeline, TRUE);
+  if (!ges_container_add (GES_CONTAINER (clip), GES_TIMELINE_ELEMENT (copy))) {
+    GST_ERROR_OBJECT (clip, "Failed to add the copied child track "
+        "element %" GES_FORMAT " to the clip", GES_ARGS (copy));
+    ges_timeline_set_moving_track_elements (timeline, FALSE);
+    gst_object_unref (copy);
+    return NULL;
+  }
+  ges_timeline_set_moving_track_elements (timeline, FALSE);
+  /* now owned by the clip */
+  gst_object_unref (copy);
+
+  return copy;
+}
+
 static void
 _deep_copy (GESTimelineElement * element, GESTimelineElement * copy)
 {
@@ -993,20 +1200,26 @@ _deep_copy (GESTimelineElement * element, GESTimelineElement * copy)
   GESClip *self = GES_CLIP (element), *ccopy = GES_CLIP (copy);
   GESTrackElement *el, *el_copy;
 
-  if (!self->priv->layer)
-    return;
-
+  /* NOTE: this should only be called on a newly created @copy, so
+   * its copied_track_elements, and copied_layer, should be free to set
+   * without disposing of the previous values */
   for (tmp = GES_CONTAINER_CHILDREN (element); tmp; tmp = tmp->next) {
     el = GES_TRACK_ELEMENT (tmp->data);
-    /* copies the children properties */
-    el_copy = GES_TRACK_ELEMENT (ges_timeline_element_copy (tmp->data, TRUE));
-
-    /* any element created by self, will have its copy considered created
-     * by self's copy */
-    if (_IS_CORE_CHILD (el))
-      ges_track_element_add_creator (el_copy, ccopy);
 
-    ges_track_element_copy_bindings (el, el_copy, GST_CLOCK_TIME_NONE);
+    el_copy = _copy_track_element_to (el, ccopy, GST_CLOCK_TIME_NONE);
+    if (!el_copy) {
+      GST_ERROR_OBJECT (element, "Failed to copy the track element %"
+          GES_FORMAT " for pasting", GES_ARGS (el));
+      continue;
+    }
+    /* owned by copied_track_elements */
+    gst_object_ref_sink (el_copy);
+
+    /* _add_child will add core elements at the lowest priority and new
+     * non-core effects at the lowest effect priority, so we need to add
+     * the highest priority children first to preserve the effect order.
+     * The clip's children are already ordered by highest priority first.
+     * So we order copied_track_elements in the same way */
     ccopy->priv->copied_track_elements =
         g_list_append (ccopy->priv->copied_track_elements, el_copy);
   }
@@ -1022,48 +1235,12 @@ _paste (GESTimelineElement * element, GESTimelineElement * ref,
   GESClip *self = GES_CLIP (element);
   GESClip *nclip = GES_CLIP (ges_timeline_element_copy (element, FALSE));
 
-  if (self->priv->copied_layer)
-    nclip->priv->copied_layer = g_object_ref (self->priv->copied_layer);
   ges_timeline_element_set_start (GES_TIMELINE_ELEMENT (nclip), paste_position);
 
-  for (tmp = self->priv->copied_track_elements; tmp; tmp = tmp->next) {
-    GESTrackElement *new_trackelement, *trackelement =
-        GES_TRACK_ELEMENT (tmp->data);
-
-    /* NOTE: we do not deep copy the track element, we instead call
-     * ges_track_element_copy_properties explicitly, which is the
-     * deep_copy for the GESTrackElementClass. */
-    new_trackelement =
-        GES_TRACK_ELEMENT (ges_timeline_element_copy (GES_TIMELINE_ELEMENT
-            (trackelement), FALSE));
-    if (new_trackelement == NULL) {
-      GST_WARNING_OBJECT (trackelement, "Could not create a copy");
-      continue;
-    }
-
-    if (_IS_CORE_CHILD (trackelement))
-      ges_track_element_add_creator (new_trackelement, nclip);
-
-    gst_object_ref_sink (new_trackelement);
-    if (!ges_container_add (GES_CONTAINER (nclip),
-            GES_TIMELINE_ELEMENT (new_trackelement))) {
-      GST_ERROR_OBJECT (self, "Failed add the copied child track element %"
-          GES_FORMAT " to the copy %" GES_FORMAT,
-          GES_ARGS (new_trackelement), GES_ARGS (nclip));
-      gst_object_unref (new_trackelement);
-      continue;
-    }
+  /* paste in order of priority (highest first) */
+  for (tmp = self->priv->copied_track_elements; tmp; tmp = tmp->next)
+    ges_clip_copy_track_element_into (nclip, tmp->data, GST_CLOCK_TIME_NONE);
 
-    ges_track_element_copy_properties (GES_TIMELINE_ELEMENT (trackelement),
-        GES_TIMELINE_ELEMENT (new_trackelement));
-
-    ges_track_element_copy_bindings (trackelement, new_trackelement,
-        GST_CLOCK_TIME_NONE);
-    gst_object_unref (new_trackelement);
-  }
-
-  /* FIXME: should we bypass the select-tracks-for-object signal when
-   * copying and pasting? */
   if (self->priv->copied_layer) {
     if (!ges_layer_add_clip (self->priv->copied_layer, nclip)) {
       GST_INFO ("%" GES_FORMAT " could not be pasted to %" GST_TIME_FORMAT,
@@ -1071,9 +1248,12 @@ _paste (GESTimelineElement * element, GESTimelineElement * ref,
 
       return NULL;
     }
-
   }
 
+  /* NOTE: self should not be used and be freed after this call, so we can
+   * leave the freeing of copied_layer and copied_track_elements to the
+   * dispose method */
+
   return GES_TIMELINE_ELEMENT (nclip);
 }
 
@@ -1138,7 +1318,7 @@ ges_clip_dispose (GObject * object)
 {
   GESClip *self = GES_CLIP (object);
 
-  g_list_free_full (self->priv->copied_track_elements, g_object_unref);
+  g_list_free_full (self->priv->copied_track_elements, gst_object_unref);
   self->priv->copied_track_elements = NULL;
   g_clear_object (&self->priv->copied_layer);
 
@@ -1218,15 +1398,7 @@ ges_clip_class_init (GESClipClass * klass)
 static void
 ges_clip_init (GESClip * self)
 {
-  GESClipPrivate *priv;
-  priv = self->priv = ges_clip_get_instance_private (self);
-  priv->layer = NULL;
-  priv->nb_effects = 0;
-  priv->prevent_priority_offset_update = FALSE;
-  priv->prevent_resort = FALSE;
-  priv->updating_max_duration = FALSE;
-  priv->prevent_max_duration_update = FALSE;
-  priv->setting_inpoint = FALSE;
+  self->priv = ges_clip_get_instance_private (self);
 }
 
 /**
@@ -1236,9 +1408,6 @@ ges_clip_init (GESClip * self)
  *
  * Creates the core #GESTrackElement of the clip, of the given track type.
  *
- * Note, unlike ges_clip_create_track_elements(), this does not add the
- * created track element to the clip or set their timings.
- *
  * Returns: (transfer floating) (nullable): The element created
  * by @clip, or %NULL if @clip can not provide a track element for the
  * given @type or an error occurred.
@@ -1277,7 +1446,7 @@ ges_clip_create_track_element (GESClip * clip, GESTrackType type)
  * @type: The track-type to create elements for
  *
  * Creates the core #GESTrackElement-s of the clip, of the given track
- * type, and adds them to the clip.
+ * type.
  *
  * Returns: (transfer container) (element-type GESTrackElement): A list of
  * the #GESTrackElement-s created by @clip for the given @type, or %NULL
@@ -1287,14 +1456,15 @@ ges_clip_create_track_element (GESClip * clip, GESTrackType type)
 GList *
 ges_clip_create_track_elements (GESClip * clip, GESTrackType type)
 {
-  /* add_list holds a ref to its elements to keep them alive
-   * result does not */
-  GList *result = NULL, *add_list = NULL, *tmp, *children;
+  GList *tmp, *ret;
   GESClipClass *klass;
-  gboolean readding_effects_only = TRUE;
+  gboolean already_created = FALSE;
 
   g_return_val_if_fail (GES_IS_CLIP (clip), NULL);
 
+  if ((clip->priv->supportedformats & type) == 0)
+    return NULL;
+
   klass = GES_CLIP_GET_CLASS (clip);
 
   if (!(klass->create_track_elements)) {
@@ -1304,71 +1474,24 @@ ges_clip_create_track_elements (GESClip * clip, GESTrackType type)
 
   GST_DEBUG_OBJECT (clip, "Creating TrackElements for type: %s",
       ges_track_type_name (type));
-  children = ges_container_get_children (GES_CONTAINER (clip), TRUE);
-  for (tmp = children; tmp; tmp = tmp->next) {
+  for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) {
     GESTrackElement *child = GES_TRACK_ELEMENT (tmp->data);
 
-    if (!ges_track_element_get_track (child)
+    if (_IS_CORE_CHILD (child)
         && ges_track_element_get_track_type (child) & type) {
-      GST_DEBUG_OBJECT (clip, "Removing for reusage: %" GST_PTR_FORMAT, child);
-      add_list = g_list_append (add_list, gst_object_ref (child));
-      ges_container_remove (GES_CONTAINER (clip), tmp->data);
-      if (_IS_CORE_CHILD (child))
-        readding_effects_only = FALSE;
-    }
-  }
-  g_list_free_full (children, gst_object_unref);
-
-  /* FIXME: we need something smarter to determine whether we should
-   * create the track elements.
-   * Currently, if the clip contains at least one element with a matching
-   * track-type, not in a track and not a GESBaseEffect, we will not
-   * recreate the track elements! But this is not a reliable indicator.
-   *
-   * For example, consider a uri clip that creates two audio track
-   * elements: El_A and El_B. First, we add the clip to a timeline that
-   * only has a single track: Track_A, and we connect to the timeline's
-   * ::select-tracks-for-object signal to only allow El_A to end up in
-   * Track_A. As such, whilst both El_A and El_B are initially created,
-   * El_B will eventually be removed from the clip since it has no track
-   * (see clip_track_element_added_cb in ges-timeline.c). As such, we now
-   * have a clip that only contains El_A.
-   * Next, we remove Track_A from the timeline. El_A will remain a child
-   * of the clip, but now has its track unset.
-   * Next, we add Track_B to the timeline, and we connect to the
-   * timeline's ::select-tracks-for-object signal to only allow El_B to
-   * end up in Track_B.
-   *
-   * However, since the clip contains an audio track element, that is not
-   * an effect and has no track set: El_A. Therefore, the
-   * create_track_elements method below will not be called, so we will not
-   * have an El_B created for Track_B!
-   *
-   * Moreover, even if we did recreate the track elements, we would be
-   * creating El_A again! We could destroy and recreate El_A instead, or
-   * we would need a way to determine exactly which elements need to be
-   * recreated.
-   */
-  if (readding_effects_only) {
-    GList *track_elements = klass->create_track_elements (clip, type);
-    for (tmp = track_elements; tmp; tmp = tmp->next) {
-      gst_object_ref_sink (tmp->data);
-      ges_track_element_add_creator (tmp->data, clip);
+      /* assume the core track elements have all been created if we find
+       * at least one core child with the same type */
+      already_created = TRUE;
+      break;
     }
-    add_list = g_list_concat (track_elements, add_list);
-  }
-
-  for (tmp = add_list; tmp; tmp = tmp->next) {
-    GESTimelineElement *el = GES_TIMELINE_ELEMENT (tmp->data);
-    if (ges_container_add (GES_CONTAINER (clip), el))
-      result = g_list_append (result, el);
-    else
-      GST_ERROR_OBJECT (clip, "Failed add the track element %"
-          GES_FORMAT " to the clip", GES_ARGS (el));
   }
-  g_list_free_full (add_list, gst_object_unref);
+  if (already_created)
+    return NULL;
 
-  return result;
+  ret = klass->create_track_elements (clip, type);
+  for (tmp = ret; tmp; tmp = tmp->next)
+    ges_track_element_add_creator (tmp->data, clip);
+  return ret;
 }
 
 /*
@@ -1605,7 +1728,7 @@ ges_clip_get_top_effects (GESClip * clip)
 
   for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) {
     child = tmp->data;
-    if (GES_IS_BASE_EFFECT (child) && !_IS_CORE_CHILD (child))
+    if (_IS_TOP_EFFECT (child))
       ret = g_list_append (ret, gst_object_ref (child));
   }
 
@@ -1801,14 +1924,20 @@ ges_clip_split (GESClip * clip, guint64 position)
   GESClip *new_object;
   GstClockTime start, inpoint, duration, old_duration, new_duration;
   gdouble media_duration_factor;
+  GESTimelineElement *element;
+  GESTimeline *timeline;
+  GHashTable *track_for_copy;
 
   g_return_val_if_fail (GES_IS_CLIP (clip), NULL);
   g_return_val_if_fail (clip->priv->layer, NULL);
   g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (position), NULL);
 
-  duration = _DURATION (clip);
-  start = _START (clip);
-  inpoint = _INPOINT (clip);
+  element = GES_TIMELINE_ELEMENT (clip);
+  timeline = element->timeline;
+
+  duration = element->duration;
+  start = element->start;
+  inpoint = element->inpoint;
 
   if (position >= start + duration || position <= start) {
     GST_WARNING_OBJECT (clip, "Can not split %" GST_TIME_FORMAT
@@ -1817,9 +1946,9 @@ ges_clip_split (GESClip * clip, guint64 position)
   }
 
   old_duration = position - start;
-  if (!timeline_tree_can_move_element (timeline_get_tree
-          (GES_TIMELINE_ELEMENT_TIMELINE (clip)), GES_TIMELINE_ELEMENT (clip),
-          GES_TIMELINE_ELEMENT_LAYER_PRIORITY (clip),
+  if (timeline && !timeline_tree_can_move_element (timeline_get_tree
+          (timeline), element,
+          ges_timeline_element_get_layer_priority (element),
           start, old_duration, NULL)) {
     GST_WARNING_OBJECT (clip,
         "Can not split %" GES_FORMAT " at %" GST_TIME_FORMAT
@@ -1829,10 +1958,10 @@ ges_clip_split (GESClip * clip, guint64 position)
   }
 
   new_duration = duration + start - position;
-  if (!timeline_tree_can_move_element (timeline_get_tree
-          (GES_TIMELINE_ELEMENT_TIMELINE (clip)), GES_TIMELINE_ELEMENT (clip),
-          GES_TIMELINE_ELEMENT_LAYER_PRIORITY (clip), position, new_duration,
-          NULL)) {
+  if (timeline && !timeline_tree_can_move_element (timeline_get_tree
+          (timeline), element,
+          ges_timeline_element_get_layer_priority (element),
+          position, new_duration, NULL)) {
     GST_WARNING_OBJECT (clip,
         "Can not split %" GES_FORMAT " at %" GST_TIME_FORMAT
         " as timeline would end up in an illegal" " state.", GES_ARGS (clip),
@@ -1844,64 +1973,60 @@ ges_clip_split (GESClip * clip, guint64 position)
       GST_TIME_ARGS (position));
 
   /* Create the new Clip */
-  new_object = GES_CLIP (ges_timeline_element_copy (GES_TIMELINE_ELEMENT (clip),
-          FALSE));
+  new_object = GES_CLIP (ges_timeline_element_copy (element, FALSE));
 
   GST_DEBUG_OBJECT (new_object, "New 'splitted' clip");
   /* Set new timing properties on the Clip */
   media_duration_factor =
-      ges_timeline_element_get_media_duration_factor (GES_TIMELINE_ELEMENT
-      (clip));
+      ges_timeline_element_get_media_duration_factor (element);
   _set_start0 (GES_TIMELINE_ELEMENT (new_object), position);
   _set_inpoint0 (GES_TIMELINE_ELEMENT (new_object),
       inpoint + old_duration * media_duration_factor);
   _set_duration0 (GES_TIMELINE_ELEMENT (new_object), new_duration);
 
-  _DURATION (clip) = old_duration;
-  g_object_notify (G_OBJECT (clip), "duration");
-
   /* We do not want the timeline to create again TrackElement-s */
   ges_clip_set_moving_from_layer (new_object, TRUE);
+  /* adding to the same layer should not fail when moving */
   ges_layer_add_clip (clip->priv->layer, new_object);
   ges_clip_set_moving_from_layer (new_object, FALSE);
 
+  /* split binding before duration changes */
+  track_for_copy = g_hash_table_new_full (NULL, NULL,
+      gst_object_unref, gst_object_unref);
+  /* _add_child will add core elements at the lowest priority and new
+   * non-core effects at the lowest effect priority, so we need to add the
+   * highest priority children first to preserve the effect order. The
+   * clip's children are already ordered by highest priority first. */
   for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) {
-    GESTrackElement *new_trackelement, *trackelement =
-        GES_TRACK_ELEMENT (tmp->data);
-
-    new_trackelement =
-        GES_TRACK_ELEMENT (ges_timeline_element_copy (GES_TIMELINE_ELEMENT
-            (trackelement), FALSE));
-    if (new_trackelement == NULL) {
-      GST_WARNING_OBJECT (trackelement, "Could not create a copy");
-      continue;
-    }
-
-    if (_IS_CORE_CHILD (trackelement))
-      ges_track_element_add_creator (new_trackelement, new_object);
-
-    /* FIXME: in-point for non-core track elements should be shifted by
-     * the split (adding them to the new clip will not set their in-point)
-     * Handle this once generic time effects are supported in splitting */
-    ges_container_add (GES_CONTAINER (new_object),
-        GES_TIMELINE_ELEMENT (new_trackelement));
-    ges_track_element_copy_properties (GES_TIMELINE_ELEMENT (trackelement),
-        GES_TIMELINE_ELEMENT (new_trackelement));
-
+    GESTrackElement *copy, *orig = tmp->data;
+    GESTrack *track = ges_track_element_get_track (orig);
     /* FIXME: is position - start + inpoint always the correct splitting
      * point for control bindings? What coordinate system are control
      * bindings given in? */
-    /* NOTE: control bindings that are not registered in GES are not
-     * handled */
-    ges_track_element_copy_bindings (trackelement, new_trackelement,
+    copy = ges_clip_copy_track_element_into (new_object, orig,
         position - start + inpoint);
+    if (copy && track)
+      g_hash_table_insert (track_for_copy, gst_object_ref (copy),
+          gst_object_ref (track));
   }
 
-  /* FIXME: The below leads to a *second* notify signal for duration */
   ELEMENT_SET_FLAG (clip, GES_TIMELINE_ELEMENT_SET_SIMPLE);
-  _DURATION (clip) = duration;
   _set_duration0 (GES_TIMELINE_ELEMENT (clip), old_duration);
   ELEMENT_UNSET_FLAG (clip, GES_TIMELINE_ELEMENT_SET_SIMPLE);
+  g_object_notify (G_OBJECT (clip), "duration");
+
+  /* add to the track after the duration change so we don't overlap! */
+  for (tmp = GES_CONTAINER_CHILDREN (new_object); tmp; tmp = tmp->next) {
+    GESTrackElement *copy = tmp->data;
+    GESTrack *track = g_hash_table_lookup (track_for_copy, copy);
+    if (track) {
+      new_object->priv->allow_any_track = TRUE;
+      ges_track_add_element (track, copy);
+      new_object->priv->allow_any_track = FALSE;
+    }
+  }
+
+  g_hash_table_unref (track_for_copy);
 
   return new_object;
 }
@@ -2133,3 +2258,117 @@ ges_clip_get_timeline_time_from_source_frame (GESClip * clip,
 
   return GST_CLOCK_DIFF (inpoint_diff, _START (clip));
 }
+
+/**
+ * ges_clip_add_child_to_track:
+ * @clip: A #GESClip
+ * @child: A child of @clip
+ * @track: The track to add @child to
+ * @err: Return location for an error
+ *
+ * Adds the track element child of the clip to a specific track.
+ *
+ * If the given child is already in another track, this will create a copy
+ * of the child, add it to the clip, and add this copy to the track.
+ *
+ * You should only call this whilst a clip is part of a #GESTimeline, and
+ * for tracks that are in the same timeline.
+ *
+ * This method is an alternative to using the
+ * #GESTimeline::select-tracks-for-object signal, but can be used to
+ * complement it when, say, you wish to copy a clip's children from one
+ * track into a new one.
+ *
+ * When the child is a core child, it must be added to a track that does
+ * not already contain another core child of the same clip. If it is not a
+ * core child (an additional effect), then it must be added to a track
+ * that already contains one of the core children of the same clip.
+ *
+ * This method can also fail if the adding the track element to the track
+ * would break a configuration rule of the corresponding #GESTimeline,
+ * such as causing three sources to overlap at a single time, or causing
+ * a source to completely overlap another in the same track.
+ *
+ * Note that @err will not always be set upon failure.
+ *
+ * Returns: (transfer none): The element that was added to @track, either
+ * @child or a copy of child, or %NULL if the element could not be added.
+ */
+GESTrackElement *
+ges_clip_add_child_to_track (GESClip * clip, GESTrackElement * child,
+    GESTrack * track, GError ** err)
+{
+  GESTimeline *timeline;
+  GESTrackElement *el;
+  GESTrack *current_track;
+
+  g_return_val_if_fail (GES_IS_CLIP (clip), NULL);
+  g_return_val_if_fail (GES_IS_TRACK_ELEMENT (child), NULL);
+  g_return_val_if_fail (GES_IS_TRACK (track), NULL);
+  g_return_val_if_fail (!err || !*err, NULL);
+
+  timeline = GES_TIMELINE_ELEMENT_TIMELINE (clip);
+
+  if (!g_list_find (GES_CONTAINER_CHILDREN (clip), child)) {
+    GST_WARNING_OBJECT (clip, "The track element %" GES_FORMAT " is not "
+        "a child of the clip", GES_ARGS (child));
+    return NULL;
+  }
+
+  if (!timeline) {
+    GST_WARNING_OBJECT (clip, "Cannot add children to tracks unless "
+        "the clip is part of a timeline");
+    return NULL;
+  }
+
+  if (timeline != ges_track_get_timeline (track)) {
+    GST_WARNING_OBJECT (clip, "Cannot add the children to the track %"
+        GST_PTR_FORMAT " because its timeline is %" GST_PTR_FORMAT
+        " rather than that of the clip %" GST_PTR_FORMAT,
+        track, ges_track_get_timeline (track), timeline);
+    return NULL;
+  }
+
+  current_track = ges_track_element_get_track (child);
+
+  if (current_track == track) {
+    GST_WARNING_OBJECT (clip, "Child %s" GES_FORMAT " is already in the "
+        "track %" GST_PTR_FORMAT, GES_ARGS (child), track);
+    return NULL;
+  }
+
+  /* copy if the element is already in a track */
+  if (current_track) {
+    el = ges_clip_copy_track_element_into (clip, child, GST_CLOCK_TIME_NONE);
+    if (!el) {
+      GST_ERROR_OBJECT (clip, "Could not add a copy of the track element %"
+          GES_FORMAT " to the clip so cannot add it to the track %"
+          GST_PTR_FORMAT, GES_ARGS (child), track);
+      return NULL;
+    }
+    if (_IS_TOP_EFFECT (child)) {
+      /* add at next lowest priority */
+      ges_clip_set_top_effect_index (clip, GES_BASE_EFFECT (el),
+          ges_clip_get_top_effect_index (clip, GES_BASE_EFFECT (child)) + 1);
+    }
+  } else {
+    el = child;
+  }
+
+  /* FIXME: set error if can not be added to track:
+   * Either breaks the track rules for the clip, or the timeline
+   * configuration rules */
+  if (!ges_track_add_element (track, el)) {
+    GST_WARNING_OBJECT (clip, "Could not add the track element %"
+        GES_FORMAT " to the track %" GST_PTR_FORMAT, GES_ARGS (el), track);
+    if (el != child)
+      ges_container_remove (GES_CONTAINER (clip), GES_TIMELINE_ELEMENT (el));
+    return NULL;
+  }
+
+  if (GES_IS_SOURCE (el))
+    timeline_tree_create_transitions (timeline_get_tree (timeline),
+        ges_timeline_find_auto_transition);
+
+  return el;
+}
index d130bd8..628d756 100644 (file)
@@ -155,6 +155,9 @@ GES_API
 GList *           ges_clip_find_track_elements    (GESClip * clip, GESTrack * track,
                                                    GESTrackType track_type, GType type);
 
+GES_API
+GESTrackElement * ges_clip_add_child_to_track     (GESClip * clip, GESTrackElement * child, GESTrack * track, GError **err);
+
 /****************************************************
  *                     Layer                        *
  ****************************************************/
index f33c940..1d51ea6 100644 (file)
@@ -83,6 +83,9 @@ GstDebugCategory * _ges_debug (void);
 #define GES_FORMAT GES_TIMELINE_ELEMENT_FORMAT
 #define GES_ARGS GES_TIMELINE_ELEMENT_ARGS
 
+#define GES_TRACK_ELEMENT_IS_CORE(child) \
+  (ges_track_element_get_creators (GES_TRACK_ELEMENT (child)) != NULL)
+
 #define SUPRESS_UNUSED_WARNING(a) (void)a
 
 G_GNUC_INTERNAL gboolean
@@ -156,6 +159,14 @@ timeline_create_transitions (GESTimeline * timeline, GESTrackElement * track_ele
 
 G_GNUC_INTERNAL void timeline_get_framerate(GESTimeline *self, gint *fps_n,
                                             gint *fps_d);
+G_GNUC_INTERNAL void
+ges_timeline_set_moving_track_elements (GESTimeline * timeline, gboolean moving);
+
+G_GNUC_INTERNAL gboolean
+ges_timeline_add_clip (GESTimeline * timeline, GESClip * clip);
+
+G_GNUC_INTERNAL void
+ges_timeline_remove_clip (GESTimeline * timeline, GESClip * clip);
 
 G_GNUC_INTERNAL
 void
@@ -406,6 +417,8 @@ G_GNUC_INTERNAL void              ges_clip_set_moving_from_layer  (GESClip *clip
 G_GNUC_INTERNAL GESTrackElement*  ges_clip_create_track_element   (GESClip *clip, GESTrackType type);
 G_GNUC_INTERNAL GList*            ges_clip_create_track_elements  (GESClip *clip, GESTrackType type);
 G_GNUC_INTERNAL gboolean          ges_clip_can_set_inpoint_of_child (GESClip * clip, GESTimelineElement * child, GstClockTime inpoint);
+G_GNUC_INTERNAL gboolean          ges_clip_can_set_track_of_child (GESClip * clip, GESTrackElement * child, GESTrack * tack);
+G_GNUC_INTERNAL void              ges_clip_empty_from_track       (GESClip * clip, GESTrack * track);
 
 /****************************************************
  *              GESLayer                            *
index b5a3a44..05e2065 100644 (file)
@@ -482,6 +482,7 @@ ges_layer_remove_clip_internal (GESLayer * layer, GESClip * clip,
 {
   GESLayer *current_layer;
   GList *tmp;
+  GESTimeline *timeline = layer->timeline;
 
   GST_DEBUG ("layer:%p, clip:%p", layer, clip);
 
@@ -507,8 +508,10 @@ ges_layer_remove_clip_internal (GESLayer * layer, GESClip * clip,
   /* inform the clip it's no longer in a layer */
   ges_clip_set_layer (clip, NULL);
   /* so neither in a timeline */
-  if (layer->timeline)
+  if (timeline) {
+    ges_timeline_remove_clip (timeline, clip);
     ges_timeline_element_set_timeline (GES_TIMELINE_ELEMENT (clip), NULL);
+  }
 
   for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next)
     ges_track_element_set_layer_active (tmp->data, TRUE);
@@ -766,25 +769,17 @@ ges_layer_add_clip (GESLayer * layer, GESClip * clip)
   ges_timeline_element_set_timeline (GES_TIMELINE_ELEMENT (clip),
       layer->timeline);
 
-  /* emit 'clip-added' */
-  /* FIXME: we are emitting the 'clip-added' signal even though we still
-   * might fail. This is because the timeline uses this signal to create
-   * the auto-transitions etc needed for timeline_tree_can_move_element
-   * below, which checks whether the added clip is in a legal position.
-   * However, we should have a way to check that adding a clip will be
-   * legal **before** we actually add it!
-   * A user connecting to 'clip-added' in such a case would receive a
-   * signal saying that the clip was added, but a return value that says
-   * something else! */
+  /* FIXME: ideally we would only emit if we are going to return TRUE.
+   * However, for backward-compatibility, we ensure the "clip-added"
+   * signal is released before the clip's "child-added" signal, which is
+   * invoked by ges_timeline_add_clip */
   g_signal_emit (layer, ges_layer_signals[OBJECT_ADDED], 0, clip);
 
-  if (!ELEMENT_FLAG_IS_SET (clip, GES_CLIP_IS_MOVING) && layer->timeline
-      && !timeline_tree_can_move_element (timeline_get_tree (layer->timeline),
-          GES_TIMELINE_ELEMENT (clip),
-          GES_TIMELINE_ELEMENT_LAYER_PRIORITY (clip),
-          GES_TIMELINE_ELEMENT_START (clip),
-          GES_TIMELINE_ELEMENT_DURATION (clip), NULL)) {
-    GST_INFO_OBJECT (layer, "Clip %" GES_FORMAT, GES_ARGS (clip));
+  if (layer->timeline && !ges_timeline_add_clip (layer->timeline, clip)) {
+    GST_WARNING_OBJECT (layer, "Could not add the clip %" GES_FORMAT
+        " to the timeline %" GST_PTR_FORMAT, GES_ARGS (clip), layer->timeline);
+    /* FIXME: change emit signal to FALSE once we are able to delay the
+     * "clip-added" signal until after ges_timeline_add_clip */
     ges_layer_remove_clip_internal (layer, clip, TRUE);
     return FALSE;
   }
index 81c9529..ade0134 100644 (file)
@@ -409,7 +409,8 @@ ges_timeline_element_class_init (GESTimelineElementClass * klass)
    */
   properties[PROP_TIMELINE] =
       g_param_spec_object ("timeline", "Timeline",
-      "The timeline the object is in", GES_TYPE_TIMELINE, G_PARAM_READWRITE);
+      "The timeline the object is in", GES_TYPE_TIMELINE,
+      G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
 
   /**
    * GESTimelineElement:start:
@@ -980,6 +981,9 @@ ges_timeline_element_set_timeline (GESTimelineElement * self,
 
   GST_DEBUG_OBJECT (self, "set timeline to %" GST_PTR_FORMAT, timeline);
 
+  if (self->timeline == timeline)
+    return TRUE;
+
   if (timeline != NULL && G_UNLIKELY (self->timeline != NULL))
     goto had_timeline;
 
index 9775bce..ef50a9b 100644 (file)
@@ -152,7 +152,8 @@ struct _GESTimelinePrivate
 
   /* While we are creating and adding the TrackElements for a clip, we need to
    * ignore the child-added signal */
-  GESClip *ignore_track_element_added;
+  gboolean track_elements_moving;
+  gboolean track_selection_error;
   GList *groups;
 
   guint stream_start_group_id;
@@ -702,8 +703,12 @@ ges_timeline_class_init (GESTimelineClass * klass)
    * @clip: The clip that @track_element is being added to
    * @track_element: The element being added
    *
-   * This will be emitted whenever a new track element is added to a
-   * clip within the timeline.
+   * This will be emitted whenever the timeline needs to determine which
+   * tracks a clip's children should be added to. The track element will
+   * be added to each of the tracks given in the return. If a track
+   * element is selected to go into multiple tracks, it will be copied
+   * into the additional tracks, under the same clip. Note that the copy
+   * will *not* keep its properties or state in sync with the original.
    *
    * Connect to this signal once if you wish to control which element
    * should be added to which track. Doing so will overwrite the default
@@ -711,16 +716,48 @@ ges_timeline_class_init (GESTimelineClass * klass)
    * #GESTrack:track-type includes the @track_element's
    * #GESTrackElement:track-type.
    *
-   * If you wish to use this, you should add all necessary tracks to the
-   * timeline before adding any clips. In particular, this signal is
-   * **not** re-emitted for the existing clips when a new track is added
-   * to the timeline.
+   * Note that under the default track selection, if a clip would produce
+   * multiple core children of the same #GESTrackType, it will choose
+   * one of the core children arbitrarily to place in the corresponding
+   * tracks, with a warning for the other core children that are not
+   * placed in the track. For example, this would happen for a #GESUriClip
+   * that points to a file that contains multiple audio streams. If you
+   * wish to choose the stream, you could connect to this signal, and use,
+   * say, ges_uri_source_asset_get_stream_info() to choose which core
+   * source to add.
+   *
+   * When a clip is first added to a timeline, its core elements will
+   * be created for the current tracks in the timeline if they have not
+   * already been created. Then this will be emitted for each of these
+   * core children to select which tracks, if any, they should be added
+   * to. It will then be called for any non-core children in the clip.
+   *
+   * In addition, if a new track element is ever added to a clip in a
+   * timeline (and it is not already part of a track) this will be emitted
+   * to select which tracks the element should be added to.
+   *
+   * Finally, as a special case, if a track is added to the timeline
+   * *after* it already contains clips, then it will request the creation
+   * of the clips' core elements of the corresponding type, if they have
+   * not already been created, and this signal will be emitted for each of
+   * these newly created elements. In addition, this will also be released
+   * for all other track elements in the timeline's clips that have not
+   * yet been assigned a track. However, in this final case, the timeline
+   * will only check whether the newly added track appears in the track
+   * list. If it does appear, the track element will be added to the newly
+   * added track. All other tracks in the returned track list are ignored.
+   *
+   * In this latter case, track elements that are already part of a track
+   * will not be asked if they want to be copied into the new track. If
+   * you wish to do this, you can use ges_clip_add_child_to_track().
+   *
+   * Note that the returned #GPtrArray should own a new reference to each
+   * of its contained #GESTrack. The timeline will set the #GDestroyNotify
+   * free function on the #GPtrArray to dereference the elements.
    *
    * Returns: (transfer full) (element-type GESTrack): An array of
-   * #GESTrack-s that @track_element should be added to. If this contains
-   * more than one track, a copy of @track_element will be added to the
-   * other tracks. If this is empty, @track_element will also be removed
-   * from @clip.
+   * #GESTrack-s that @track_element should be added to, or %NULL to
+   * not add the element to any track.
    */
   ges_timeline_signals[SELECT_TRACKS_FOR_OBJECT] =
       g_signal_new ("select-tracks-for-object", G_TYPE_FROM_CLASS (klass),
@@ -1297,12 +1334,26 @@ timeline_remove_group (GESTimeline * timeline, GESGroup * group)
   gst_object_unref (group);
 }
 
+static GESTrackElement *
+_core_in_track (GESTrack * track, GESClip * clip)
+{
+  GList *tmp;
+  for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) {
+    if (GES_TRACK_ELEMENT_IS_CORE (tmp->data)
+        && ges_track_element_get_track (tmp->data) == track) {
+      return tmp->data;
+    }
+  }
+  return NULL;
+}
+
 static GPtrArray *
 select_tracks_for_object_default (GESTimeline * timeline,
     GESClip * clip, GESTrackElement * tr_object, gpointer user_data)
 {
   GPtrArray *result;
   GList *tmp;
+  GESTrackElement *core;
 
   result = g_ptr_array_new ();
 
@@ -1311,6 +1362,24 @@ select_tracks_for_object_default (GESTimeline * timeline,
     GESTrack *track = GES_TRACK (tmp->data);
 
     if ((track->type & ges_track_element_get_track_type (tr_object))) {
+      if (GES_TRACK_ELEMENT_IS_CORE (tr_object)) {
+        core = _core_in_track (track, clip);
+        if (core) {
+          GST_WARNING_OBJECT (timeline, "The clip '%s' contains multiple "
+              "core elements of the same %s track type. The core child "
+              "'%s' has already been chosen arbitrarily for the track %"
+              GST_PTR_FORMAT ", which means that the other core child "
+              "'%s' of the same type can not be added to the track. "
+              "Consider connecting to "
+              "GESTimeline::select-tracks-for-objects to be able to "
+              "specify which core element should land in the track",
+              GES_TIMELINE_ELEMENT_NAME (clip),
+              ges_track_type_name (track->type),
+              GES_TIMELINE_ELEMENT_NAME (core), track,
+              GES_TIMELINE_ELEMENT_NAME (tr_object));
+          continue;
+        }
+      }
       gst_object_ref (track);
       g_ptr_array_add (result, track);
     }
@@ -1320,34 +1389,273 @@ select_tracks_for_object_default (GESTimeline * timeline,
   return result;
 }
 
+static GPtrArray *
+_get_selected_tracks (GESTimeline * timeline, GESClip * clip,
+    GESTrackElement * track_element)
+{
+  guint i, j;
+  GPtrArray *tracks = NULL;
+
+  g_signal_emit (G_OBJECT (timeline),
+      ges_timeline_signals[SELECT_TRACKS_FOR_OBJECT], 0, clip, track_element,
+      &tracks);
+
+  if (tracks == NULL)
+    tracks = g_ptr_array_new ();
+
+  g_ptr_array_set_free_func (tracks, gst_object_unref);
+
+  /* make sure unique */
+  for (i = 0; i < tracks->len;) {
+    GESTrack *track = GES_TRACK (g_ptr_array_index (tracks, i));
+
+    for (j = i + 1; j < tracks->len;) {
+      if (track == g_ptr_array_index (tracks, j)) {
+        GST_WARNING_OBJECT (timeline, "Found the track %" GST_PTR_FORMAT
+            " more than once in the return for select-tracks-for-object "
+            "signal for track element %" GES_FORMAT " in clip %"
+            GES_FORMAT ". Ignoring the extra track", track,
+            GES_ARGS (track_element), GES_ARGS (clip));
+        g_ptr_array_remove_index (tracks, j);
+        /* don't increase index since the next track is in its place */
+        continue;
+      }
+      j++;
+    }
+
+    if (ges_track_get_timeline (track) != timeline) {
+      GST_WARNING_OBJECT (timeline, "The track %" GST_PTR_FORMAT
+          " found in the return for select-tracks-for-object belongs "
+          "to a different timeline %" GST_PTR_FORMAT ". Ignoring this "
+          "track", track, ges_track_get_timeline (track));
+      g_ptr_array_remove_index (tracks, i);
+      /* don't increase index since the next track is in its place */
+      continue;
+    }
+    i++;
+  }
+
+  return tracks;
+}
+
+/* returns TRUE if track element was successfully added to all the
+ * selected tracks */
+static gboolean
+_add_track_element_to_tracks (GESTimeline * timeline, GESClip * clip,
+    GESTrackElement * track_element)
+{
+  guint i;
+  gboolean ret = TRUE;
+  GPtrArray *tracks = _get_selected_tracks (timeline, clip, track_element);
+
+  for (i = 0; i < tracks->len; i++) {
+    GESTrack *track = GES_TRACK (g_ptr_array_index (tracks, i));
+    if (!ges_clip_add_child_to_track (clip, track_element, track, NULL))
+      ret = FALSE;
+  }
+
+  g_ptr_array_unref (tracks);
+
+  return ret;
+}
+
+static gboolean
+_try_add_track_element_to_track (GESTimeline * timeline, GESClip * clip,
+    GESTrackElement * track_element, GESTrack * track)
+{
+  gboolean no_error = TRUE;
+  GPtrArray *tracks = _get_selected_tracks (timeline, clip, track_element);
+
+  /* if we are trying to add the element to a newly added track, then
+   * we only check whether the track list contains the newly added track,
+   * if it does we add the track element to the track, or add a copy if
+   * the track element is already in a track */
+  if (g_ptr_array_find (tracks, track, NULL)) {
+    if (!ges_clip_add_child_to_track (clip, track_element, track, NULL))
+      no_error = FALSE;
+  }
+
+  g_ptr_array_unref (tracks);
+  return no_error;
+}
+
+/* accepts NULL */
+void
+ges_timeline_set_moving_track_elements (GESTimeline * timeline, gboolean moving)
+{
+  if (timeline) {
+    LOCK_DYN (timeline);
+    timeline->priv->track_elements_moving = moving;
+    UNLOCK_DYN (timeline);
+  }
+}
+
 static void
-add_object_to_tracks (GESTimeline * timeline, GESClip * clip, GESTrack * track)
+_set_track_selection_error (GESTimeline * timeline, gboolean error)
 {
-  gint i;
-  GList *tmp, *list;
-  GESTrackType types, visited_type = GES_TRACK_TYPE_UNKNOWN;
+  LOCK_DYN (timeline);
+  timeline->priv->track_selection_error = error;
+  UNLOCK_DYN (timeline);
+}
 
-  GST_DEBUG_OBJECT (timeline, "Creating %" GST_PTR_FORMAT
-      " trackelements and adding them to our tracks", clip);
+static gboolean
+_get_track_selection_error (GESTimeline * timeline)
+{
+  gboolean ret;
+
+  LOCK_DYN (timeline);
+  ret = timeline->priv->track_selection_error;
+  timeline->priv->track_selection_error = FALSE;
+  UNLOCK_DYN (timeline);
+
+  return ret;
+}
+
+static void
+clip_track_element_added_cb (GESClip * clip,
+    GESTrackElement * track_element, GESTimeline * timeline)
+{
+  gboolean error = FALSE;
+
+  if (timeline->priv->track_elements_moving) {
+    GST_DEBUG_OBJECT (timeline, "Ignoring element added: %" GES_FORMAT
+        " in %" GES_FORMAT, GES_ARGS (track_element), GES_ARGS (clip));
+    return;
+  }
+
+  if (ges_track_element_get_track (track_element) != NULL) {
+    GST_DEBUG_OBJECT (timeline, "Not selecting tracks for %" GES_FORMAT
+        " in %" GES_FORMAT " because it already part of the track %"
+        GST_PTR_FORMAT, GES_ARGS (track_element), GES_ARGS (clip),
+        ges_track_element_get_track (track_element));
+    return;
+  }
+
+  if (!_add_track_element_to_tracks (timeline, clip, track_element))
+    error = TRUE;
+
+  if (error)
+    _set_track_selection_error (timeline, TRUE);
+}
+
+static void
+clip_track_element_removed_cb (GESClip * clip,
+    GESTrackElement * track_element, GESTimeline * timeline)
+{
+  GESTrack *track = ges_track_element_get_track (track_element);
+
+  if (timeline->priv->track_elements_moving) {
+    GST_DEBUG_OBJECT (timeline, "Ignoring element removed (%" GST_PTR_FORMAT
+        " in %" GST_PTR_FORMAT, track_element, clip);
+
+    return;
+  }
 
-  types = ges_clip_get_supported_formats (clip);
   if (track) {
-    if ((types & track->type) == 0)
-      return;
-    types = track->type;
+    /* if we have non-core elements in the same track, they should be
+     * removed from them to preserve the rule that a non-core can only be
+     * in the same track as a core element from the same clip */
+    if (GES_TRACK_ELEMENT_IS_CORE (track_element))
+      ges_clip_empty_from_track (clip, track);
+    ges_track_remove_element (track, track_element);
   }
+}
 
-  LOCK_DYN (timeline);
-  for (i = 0, tmp = timeline->tracks; tmp; tmp = tmp->next, i++) {
-    GESTrack *track = GES_TRACK (tmp->data);
-    /* FIXME: visited_type is essentially unused */
-    if (((track->type & types) == 0 || (track->type & visited_type)))
+/* returns TRUE if no errors in adding to tracks */
+static gboolean
+_add_clip_children_to_tracks (GESTimeline * timeline, GESClip * clip,
+    gboolean add_core, GESTrack * new_track, GList * blacklist)
+{
+  GList *tmp, *children;
+  gboolean no_errors = TRUE;
+
+  /* list of children may change if some are copied into tracks */
+  children = ges_container_get_children (GES_CONTAINER (clip), FALSE);
+  for (tmp = children; tmp; tmp = tmp->next) {
+    GESTrackElement *el = tmp->data;
+    if (GES_TRACK_ELEMENT_IS_CORE (el) != add_core)
       continue;
+    if (g_list_find (blacklist, el))
+      continue;
+    if (ges_track_element_get_track (el) == NULL) {
+      gboolean res;
+      if (new_track)
+        res = _try_add_track_element_to_track (timeline, clip, el, new_track);
+      else
+        res = _add_track_element_to_tracks (timeline, clip, el);
+      if (!res)
+        no_errors = FALSE;
+    }
+  }
+  g_list_free_full (children, gst_object_unref);
 
+  return no_errors;
+}
+
+/* returns TRUE if no errors in adding to tracks */
+static gboolean
+add_object_to_tracks (GESTimeline * timeline, GESClip * clip, GESTrack * track)
+{
+  GList *tracks, *tmp, *list, *created, *just_added = NULL;
+  gboolean no_errors = TRUE;
+  /* TODO: extend with GError ** argument, which is accepted by
+   * ges_clip_add_child_to_track */
+
+  GST_DEBUG_OBJECT (timeline, "Creating %" GST_PTR_FORMAT
+      " trackelements and adding them to our tracks", clip);
+
+  LOCK_DYN (timeline);
+  tracks =
+      g_list_copy_deep (timeline->tracks, (GCopyFunc) gst_object_ref, NULL);
+  UNLOCK_DYN (timeline);
+  /* create core elements */
+  for (tmp = tracks; tmp; tmp = tmp->next) {
+    GESTrack *track = GES_TRACK (tmp->data);
     list = ges_clip_create_track_elements (clip, track->type);
-    g_list_free (list);
+    for (created = list; created; created = created->next) {
+      GESTimelineElement *el = created->data;
+
+      gst_object_ref (el);
+
+      /* make track selection be handled by clip_track_element_added_cb
+       * This is needed for backward-compatibility: when adding a clip to
+       * a layer, the track is set for the core elements of the clip
+       * during the child-added signal emission, just before the user's
+       * own connection.
+       * NOTE: for the children that have not just been created, they
+       * are already part of the clip and so child-added will not be
+       * released. And when a child is selected for multiple tracks, their
+       * copy will be added to the clip before the track is selected, so
+       * the track will not be set in the child-added signal */
+      _set_track_selection_error (timeline, FALSE);
+      if (!ges_container_add (GES_CONTAINER (clip), el))
+        GST_ERROR_OBJECT (clip, "Could not add the core element %s "
+            "to the clip", el->name);
+      if (_get_track_selection_error (timeline))
+        no_errors = FALSE;
+
+      gst_object_unref (el);
+    }
+    /* just_added only used for pointer comparison, so safe to include
+     * elements that are now destroyed because they failed to be added to
+     * the clip */
+    just_added = g_list_concat (just_added, list);
   }
-  UNLOCK_DYN (timeline);
+  g_list_free_full (tracks, gst_object_unref);
+
+  /* set the tracks for the other children, with core elements first to
+   * make sure the non-core can be placed above them in the track (a
+   * non-core can not be in a track by itself) */
+  /* include just_added as a blacklist to ensure we do not try the track
+   * selection a second time when track selection returns no tracks */
+  if (!_add_clip_children_to_tracks (timeline, clip, TRUE, track, just_added))
+    no_errors = FALSE;
+  if (!_add_clip_children_to_tracks (timeline, clip, FALSE, track, just_added))
+    no_errors = FALSE;
+
+  g_list_free (just_added);
+
+  return no_errors;
 }
 
 static void
@@ -1392,160 +1700,14 @@ layer_auto_transition_changed_cb (GESLayer * layer,
   g_list_free_full (clips, gst_object_unref);
 }
 
-static void
-clip_track_element_added_cb (GESClip * clip,
-    GESTrackElement * track_element, GESTimeline * timeline)
-{
-  guint i;
-  GESTrack *track;
-  gboolean is_source;
-  GPtrArray *tracks = NULL;
-  GESTrackElement *existing_src = NULL;
-
-  if (timeline->priv->ignore_track_element_added == clip) {
-    GST_DEBUG_OBJECT (timeline, "Ignoring element added (%" GST_PTR_FORMAT
-        " in %" GST_PTR_FORMAT, track_element, clip);
-
-    return;
-  }
-
-  if (ges_track_element_get_track (track_element)) {
-    GST_WARNING_OBJECT (track_element, "Already in a track");
-
-    return;
-  }
-
-  g_signal_emit (G_OBJECT (timeline),
-      ges_timeline_signals[SELECT_TRACKS_FOR_OBJECT], 0, clip, track_element,
-      &tracks);
-  /* FIXME: make sure each track in the list is unique */
-  /* FIXME: make sure each track is part of the same timeline as the clip,
-   * and warn and ignore the track if it isn't */
-
-  if (!tracks || tracks->len == 0) {
-    GST_WARNING_OBJECT (timeline, "Got no Track to add %p (type %s), removing"
-        " from clip (stopping 'child-added' signal emission).",
-        track_element, ges_track_type_name (ges_track_element_get_track_type
-            (track_element)));
-
-    if (tracks)
-      g_ptr_array_unref (tracks);
-
-    g_signal_stop_emission_by_name (clip, "child-added");
-    ges_container_remove (GES_CONTAINER (clip),
-        GES_TIMELINE_ELEMENT (track_element));
-
-    return;
-  }
-
-  /* We add the current element to the first track */
-  track = g_ptr_array_index (tracks, 0);
-
-  is_source = g_type_is_a (G_OBJECT_TYPE (track_element), GES_TYPE_SOURCE);
-  if (is_source)
-    existing_src = ges_clip_find_track_element (clip, track, GES_TYPE_SOURCE);
-
-  if (existing_src == NULL) {
-    if (!ges_track_add_element (track, track_element)) {
-      GST_WARNING_OBJECT (clip, "Failed to add track element to track");
-      ges_container_remove (GES_CONTAINER (clip),
-          GES_TIMELINE_ELEMENT (track_element));
-      /* FIXME: unref all the individual track in tracks */
-      g_ptr_array_unref (tracks);
-      return;
-    }
-  } else {
-    GST_INFO_OBJECT (clip, "Already had a Source Element in %" GST_PTR_FORMAT
-        " of type %s, removing new one. (stopping 'child-added' emission)",
-        track, G_OBJECT_TYPE_NAME (track_element));
-    g_signal_stop_emission_by_name (clip, "child-added");
-    ges_container_remove (GES_CONTAINER (clip),
-        GES_TIMELINE_ELEMENT (track_element));
-  }
-  gst_object_unref (track);
-  g_clear_object (&existing_src);
-
-  /* And create copies to add to other tracks */
-  timeline->priv->ignore_track_element_added = clip;
-  for (i = 1; i < tracks->len; i++) {
-    GESTrack *track;
-    GESTrackElement *track_element_copy;
-
-    track = g_ptr_array_index (tracks, i);
-    if (is_source)
-      existing_src = ges_clip_find_track_element (clip, track, GES_TYPE_SOURCE);
-    if (existing_src == NULL) {
-      ges_container_remove (GES_CONTAINER (clip),
-          GES_TIMELINE_ELEMENT (track_element));
-      gst_object_unref (track);
-      /* FIXME: tracks is needed for the next loop after continue */
-      g_ptr_array_unref (tracks);
-      continue;
-    } else {
-      GST_INFO_OBJECT (clip, "Already had a Source Element in %" GST_PTR_FORMAT
-          " of type %s, removing new one. (stopping 'child-added' emission)",
-          track, G_OBJECT_TYPE_NAME (track_element));
-      g_signal_stop_emission_by_name (clip, "child-added");
-      ges_container_remove (GES_CONTAINER (clip),
-          GES_TIMELINE_ELEMENT (track_element));
-    }
-    /* FIXME: in both cases track_element is removed from the clip! */
-    g_clear_object (&existing_src);
-
-    track_element_copy =
-        GES_TRACK_ELEMENT (ges_timeline_element_copy (GES_TIMELINE_ELEMENT
-            (track_element), TRUE));
-    /* if a core child, mark the copy as core so it can be added */
-    if (ges_track_element_get_creators (track_element))
-      ges_track_element_add_creator (track_element_copy, clip);
-
-    GST_LOG_OBJECT (timeline, "Trying to add %p to track %p",
-        track_element_copy, track);
-
-    if (!ges_container_add (GES_CONTAINER (clip),
-            GES_TIMELINE_ELEMENT (track_element_copy))) {
-      GST_WARNING_OBJECT (clip, "Failed to add track element to clip");
-      gst_object_unref (track_element_copy);
-      /* FIXME: unref **all** the individual track in tracks */
-      g_ptr_array_unref (tracks);
-      return;
-    }
-
-    if (!ges_track_add_element (track, track_element_copy)) {
-      GST_WARNING_OBJECT (clip, "Failed to add track element to track");
-      ges_container_remove (GES_CONTAINER (clip),
-          GES_TIMELINE_ELEMENT (track_element_copy));
-      /* FIXME: should we also stop the child-added and child-removed
-       * emissions? */
-      gst_object_unref (track_element_copy);
-      /* FIXME: unref **all** the individual track in tracks */
-      g_ptr_array_unref (tracks);
-      return;
-    }
-
-    gst_object_unref (track);
-  }
-  timeline->priv->ignore_track_element_added = NULL;
-  g_ptr_array_unref (tracks);
-  if (GES_IS_SOURCE (track_element))
-    timeline_tree_create_transitions (timeline->priv->tree,
-        ges_timeline_find_auto_transition);
-}
-
-static void
-clip_track_element_removed_cb (GESClip * clip,
-    GESTrackElement * track_element, GESTimeline * timeline)
-{
-  GESTrack *track = ges_track_element_get_track (track_element);
-
-  if (track)
-    ges_track_remove_element (track, track_element);
-}
-
-static void
-layer_object_added_cb (GESLayer * layer, GESClip * clip, GESTimeline * timeline)
+/* returns TRUE if selecting of tracks did not error */
+gboolean
+ges_timeline_add_clip (GESTimeline * timeline, GESClip * clip)
 {
   GESProject *project;
+  gboolean ret;
+  /* TODO: extend with GError ** argument, which is accepted by
+   * ges_clip_add_child_to_track */
 
   /* We make sure not to be connected twice */
   g_signal_handlers_disconnect_by_func (clip, clip_track_element_added_cb,
@@ -1564,11 +1726,11 @@ layer_object_added_cb (GESLayer * layer, GESClip * clip, GESTimeline * timeline)
         "TrackElement", clip);
     timeline_tree_create_transitions (timeline->priv->tree,
         ges_timeline_find_auto_transition);
-    return;
+    ret = TRUE;
+  } else {
+    ret = add_object_to_tracks (timeline, clip, NULL);
   }
 
-  add_object_to_tracks (timeline, clip, NULL);
-
   GST_DEBUG ("Making sure that the asset is in our project");
   project =
       GES_PROJECT (ges_extractable_get_asset (GES_EXTRACTABLE (timeline)));
@@ -1576,6 +1738,8 @@ layer_object_added_cb (GESLayer * layer, GESClip * clip, GESTimeline * timeline)
       ges_extractable_get_asset (GES_EXTRACTABLE (clip)));
 
   GST_DEBUG ("Done");
+
+  return ret;
 }
 
 static void
@@ -1589,11 +1753,10 @@ layer_priority_changed_cb (GESLayer * layer,
       sort_layers);
 }
 
-static void
-layer_object_removed_cb (GESLayer * layer, GESClip * clip,
-    GESTimeline * timeline)
+void
+ges_timeline_remove_clip (GESTimeline * timeline, GESClip * clip)
 {
-  GList *trackelements, *tmp;
+  GList *tmp;
 
   if (ges_clip_is_moving_from_layer (clip)) {
     GST_DEBUG ("Clip %p is moving from a layer to another, not doing"
@@ -1601,41 +1764,19 @@ layer_object_removed_cb (GESLayer * layer, GESClip * clip,
     return;
   }
 
-  GST_DEBUG_OBJECT (timeline, "Clip %" GES_FORMAT " removed from layer %p",
-      GES_ARGS (clip), layer);
-
-  /* Go over the clip's track element and figure out which one belongs to
-   * the list of tracks we control */
-
-  trackelements = ges_container_get_children (GES_CONTAINER (clip), FALSE);
-  for (tmp = trackelements; tmp; tmp = tmp->next) {
-    GESTrackElement *track_element = (GESTrackElement *) tmp->data;
-    GESTrack *track = ges_track_element_get_track (track_element);
-
-    if (!track)
-      continue;
-
-    GST_DEBUG_OBJECT (timeline, "Trying to remove TrackElement %p",
-        track_element);
+  GST_DEBUG_OBJECT (timeline, "Clip %" GES_FORMAT " removed from layer",
+      GES_ARGS (clip));
 
-    /* FIXME Check if we should actually check that we control the
-     * track in the new management of TrackElement context */
-    LOCK_DYN (timeline);
-    if (G_LIKELY (g_list_find_custom (timeline->priv->priv_tracks, track,
-                (GCompareFunc) custom_find_track) || track == NULL)) {
-      GST_DEBUG ("Belongs to one of the tracks we control");
+  LOCK_DYN (timeline);
+  for (tmp = timeline->tracks; tmp; tmp = tmp->next)
+    ges_clip_empty_from_track (clip, tmp->data);
+  UNLOCK_DYN (timeline);
 
-      ges_track_remove_element (track, track_element);
-    }
-    UNLOCK_DYN (timeline);
-  }
   g_signal_handlers_disconnect_by_func (clip, clip_track_element_added_cb,
       timeline);
   g_signal_handlers_disconnect_by_func (clip, clip_track_element_removed_cb,
       timeline);
 
-  g_list_free_full (trackelements, gst_object_unref);
-
   GST_DEBUG ("Done");
 }
 
@@ -1971,6 +2112,17 @@ ges_timeline_append_layer (GESTimeline * timeline)
  *
  * Add a layer to the timeline.
  *
+ * If the layer contains #GESClip-s, then this may trigger the creation of
+ * their core track element children for the timeline's tracks, and the
+ * placement of the clip's children in the tracks of the timeline using
+ * #GESTimeline::select-tracks-for-object. Some errors may occur if this
+ * would break one of the configuration rules of the timeline in one of
+ * its tracks. In such cases, some track elements would fail to be added
+ * to their tracks, but this method would still return %TRUE. As such, it
+ * is advised that you only add clips to layers that already part of a
+ * timeline. In such situations, ges_layer_add_clip() is able to fail if
+ * adding the clip would cause such an error.
+ *
  * Deprecated: 1.18: This method requires you to ensure the layer's
  * #GESLayer:priority will be unique to the timeline. Use
  * ges_timeline_append_layer() and ges_timeline_move_layer() instead.
@@ -2025,10 +2177,6 @@ ges_timeline_add_layer (GESTimeline * timeline, GESLayer * layer)
   ges_layer_set_timeline (layer, timeline);
 
   /* Connect to 'clip-added'/'clip-removed' signal from the new layer */
-  g_signal_connect_after (layer, "clip-added",
-      G_CALLBACK (layer_object_added_cb), timeline);
-  g_signal_connect_after (layer, "clip-removed",
-      G_CALLBACK (layer_object_removed_cb), timeline);
   g_signal_connect (layer, "notify::priority",
       G_CALLBACK (layer_priority_changed_cb), timeline);
   g_signal_connect (layer, "notify::auto-transition",
@@ -2041,12 +2189,9 @@ ges_timeline_add_layer (GESTimeline * timeline, GESLayer * layer)
 
   /* add any existing clips to the timeline */
   objects = ges_layer_get_clips (layer);
-  for (tmp = objects; tmp; tmp = tmp->next) {
-    layer_object_added_cb (layer, tmp->data, timeline);
-    gst_object_unref (tmp->data);
-    tmp->data = NULL;
-  }
-  g_list_free (objects);
+  for (tmp = objects; tmp; tmp = tmp->next)
+    ges_timeline_add_clip (timeline, tmp->data);
+  g_list_free_full (objects, gst_object_unref);
 
   return TRUE;
 }
@@ -2080,18 +2225,12 @@ ges_timeline_remove_layer (GESTimeline * timeline, GESLayer * layer)
   /* remove objects from any private data structures */
 
   layer_objects = ges_layer_get_clips (layer);
-  for (tmp = layer_objects; tmp; tmp = tmp->next) {
-    layer_object_removed_cb (layer, GES_CLIP (tmp->data), timeline);
-    gst_object_unref (G_OBJECT (tmp->data));
-    tmp->data = NULL;
-  }
-  g_list_free (layer_objects);
+  for (tmp = layer_objects; tmp; tmp = tmp->next)
+    ges_timeline_remove_clip (timeline, tmp->data);
+  g_list_free_full (layer_objects, gst_object_unref);
 
   /* Disconnect signals */
   GST_DEBUG ("Disconnecting signal callbacks");
-  g_signal_handlers_disconnect_by_func (layer, layer_object_added_cb, timeline);
-  g_signal_handlers_disconnect_by_func (layer, layer_object_removed_cb,
-      timeline);
   g_signal_handlers_disconnect_by_func (layer, layer_priority_changed_cb,
       timeline);
   g_signal_handlers_disconnect_by_func (layer,
@@ -2115,8 +2254,16 @@ ges_timeline_remove_layer (GESTimeline * timeline, GESLayer * layer)
  * @timeline: The #GESTimeline
  * @track: (transfer full): The track to add
  *
- * Add a track to the timeline. Existing #GESClip-s in the timeline will,
- * where appropriate, add their controlled elements to the new track.
+ * Add a track to the timeline.
+ *
+ * If the timeline already contains clips, then this may trigger the
+ * creation of their core track element children for the track, and the
+ * placement of the clip's children in the track of the timeline using
+ * #GESTimeline::select-tracks-for-object. Some errors may occur if this
+ * would break one of the configuration rules for the timeline in the
+ * track. In such cases, some track elements would fail to be added to the
+ * track, but this method would still return %TRUE. As such, it is advised
+ * that you avoid adding tracks to timelines that already contain clips.
  *
  * Returns: %TRUE if @track was properly added.
  */
@@ -2184,13 +2331,10 @@ ges_timeline_add_track (GESTimeline * timeline, GESTrack * track)
     GList *objects, *obj;
     objects = ges_layer_get_clips (tmp->data);
 
-    for (obj = objects; obj; obj = obj->next) {
-      GESClip *clip = obj->data;
+    for (obj = objects; obj; obj = obj->next)
+      add_object_to_tracks (timeline, obj->data, track);
 
-      add_object_to_tracks (timeline, clip, track);
-      gst_object_unref (clip);
-    }
-    g_list_free (objects);
+    g_list_free_full (objects, gst_object_unref);
   }
 
   /* FIXME Check if we should rollback if we can't sync state */
@@ -2240,8 +2384,22 @@ ges_timeline_remove_track (GESTimeline * timeline, GESTrack * track)
   gst_object_unref (tr_priv->pad);
   priv->priv_tracks = g_list_remove (priv->priv_tracks, tr_priv);
   UNLOCK_DYN (timeline);
-  timeline->tracks = g_list_remove (timeline->tracks, track);
 
+  /* empty track of all elements that belong to the timeline's clips */
+  /* elements with no parent can stay in the track, but their timeline
+   * will be set to NULL when the track's timeline is set to NULL */
+
+  for (tmp = timeline->layers; tmp; tmp = tmp->next) {
+    GList *clips, *clip;
+    clips = ges_layer_get_clips (tmp->data);
+
+    for (clip = clips; clip; clip = clip->next)
+      ges_clip_empty_from_track (clip->data, track);
+
+    g_list_free_full (clips, gst_object_unref);
+  }
+
+  timeline->tracks = g_list_remove (timeline->tracks, track);
   ges_track_set_timeline (track, NULL);
 
   /* Remove ghost pad */
index 646a7c0..ec7d178 100644 (file)
@@ -1049,12 +1049,20 @@ ges_track_element_add_children_props (GESTrackElement * self,
 gboolean
 ges_track_element_set_track (GESTrackElement * object, GESTrack * track)
 {
-  gboolean ret = TRUE;
+  GESTimelineElement *parent = GES_TIMELINE_ELEMENT_PARENT (object);
 
   g_return_val_if_fail (object->priv->nleobject, FALSE);
 
   GST_DEBUG_OBJECT (object, "new track: %" GST_PTR_FORMAT, track);
 
+  if (GES_IS_CLIP (parent)
+      && !ges_clip_can_set_track_of_child (GES_CLIP (parent), object, track)) {
+    GST_WARNING_OBJECT (object, "The parent clip %" GES_FORMAT " would "
+        "not allow the track to be set to %" GST_PTR_FORMAT,
+        GES_ARGS (parent), track);
+    return FALSE;
+  }
+
   object->priv->track = track;
 
   if (object->priv->track) {
@@ -1065,7 +1073,7 @@ ges_track_element_set_track (GESTrackElement * object, GESTrack * track)
   }
 
   g_object_notify_by_pspec (G_OBJECT (object), properties[PROP_TRACK]);
-  return ret;
+  return TRUE;
 }
 
 void
index af57dba..d9613d9 100644 (file)
@@ -382,7 +382,8 @@ ges_track_get_composition (GESTrack * track)
  * accessing it
  */
 static gboolean
-remove_object_internal (GESTrack * track, GESTrackElement * object)
+remove_object_internal (GESTrack * track, GESTrackElement * object,
+    gboolean emit)
 {
   GESTrackPrivate *priv;
   GstElement *nleobject;
@@ -392,25 +393,30 @@ remove_object_internal (GESTrack * track, GESTrackElement * object)
   priv = track->priv;
 
   if (G_UNLIKELY (ges_track_element_get_track (object) != track)) {
-    GST_WARNING ("Object belongs to another track");
+    GST_WARNING_OBJECT (track, "Object belongs to another track");
     return FALSE;
   }
 
+  if (!ges_track_element_set_track (object, NULL)) {
+    GST_WARNING_OBJECT (track, "Failed to unset the track for %" GES_FORMAT,
+        GES_ARGS (object));
+    return FALSE;
+  }
+  ges_timeline_element_set_timeline (GES_TIMELINE_ELEMENT (object), NULL);
+
   if ((nleobject = ges_track_element_get_nleobject (object))) {
     GST_DEBUG ("Removing NleObject '%s' from composition '%s'",
         GST_ELEMENT_NAME (nleobject), GST_ELEMENT_NAME (priv->composition));
 
     if (!ges_nle_composition_remove_object (priv->composition, nleobject)) {
-      GST_WARNING ("Failed to remove nleobject from composition");
+      GST_WARNING_OBJECT (track, "Failed to remove nleobject from composition");
       return FALSE;
     }
   }
 
-  ges_track_element_set_track (object, NULL);
-  ges_timeline_element_set_timeline (GES_TIMELINE_ELEMENT (object), NULL);
-
-  g_signal_emit (track, ges_track_signals[TRACK_ELEMENT_REMOVED], 0,
-      GES_TRACK_ELEMENT (object));
+  if (emit)
+    g_signal_emit (track, ges_track_signals[TRACK_ELEMENT_REMOVED], 0,
+        GES_TRACK_ELEMENT (object));
 
   gst_object_unref (object);
 
@@ -420,7 +426,7 @@ remove_object_internal (GESTrack * track, GESTrackElement * object)
 static void
 dispose_trackelements_foreach (GESTrackElement * trackelement, GESTrack * track)
 {
-  remove_object_internal (track, trackelement);
+  remove_object_internal (track, trackelement, TRUE);
 }
 
 /* GstElement virtual methods */
@@ -918,9 +924,19 @@ ges_track_new (GESTrackType type, GstCaps * caps)
 void
 ges_track_set_timeline (GESTrack * track, GESTimeline * timeline)
 {
+  GSequenceIter *it;
+  g_return_if_fail (GES_IS_TRACK (track));
+  g_return_if_fail (timeline == NULL || GES_IS_TIMELINE (timeline));
   GST_DEBUG ("track:%p, timeline:%p", track, timeline);
 
   track->priv->timeline = timeline;
+
+  for (it = g_sequence_get_begin_iter (track->priv->trackelements_by_start);
+      g_sequence_iter_is_end (it) == FALSE; it = g_sequence_iter_next (it)) {
+    GESTimelineElement *trackelement =
+        GES_TIMELINE_ELEMENT (g_sequence_get (it));
+    ges_timeline_element_set_timeline (trackelement, timeline);
+  }
   track_resort_and_fill_gaps (track);
 }
 
@@ -1089,6 +1105,31 @@ notify:
   GST_DEBUG_OBJECT (track, "The track has been set to mixing = %d", mixing);
 }
 
+static gboolean
+remove_element_internal (GESTrack * track, GESTrackElement * object,
+    gboolean emit)
+{
+  GSequenceIter *it;
+  GESTrackPrivate *priv = track->priv;
+
+  GST_DEBUG_OBJECT (track, "Removing %" GST_PTR_FORMAT, object);
+
+  it = g_hash_table_lookup (priv->trackelements_iter, object);
+  g_sequence_remove (it);
+
+  if (remove_object_internal (track, object, emit) == TRUE) {
+    ges_timeline_element_set_timeline (GES_TIMELINE_ELEMENT (object), NULL);
+
+    return TRUE;
+  }
+
+  g_hash_table_insert (track->priv->trackelements_iter, object,
+      g_sequence_insert_sorted (track->priv->trackelements_by_start, object,
+          (GCompareDataFunc) element_start_compare, NULL));
+
+  return FALSE;
+}
+
 /**
  * ges_track_add_element:
  * @track: A #GESTrack
@@ -1097,6 +1138,9 @@ notify:
  * Adds the given track element to the track, which takes ownership of the
  * element.
  *
+ * Note that this can fail if it would break a configuration rule of the
+ * track's #GESTimeline.
+ *
  * Note that a #GESTrackElement can only be added to one track.
  *
  * Returns: %TRUE if @object was successfully added to @track.
@@ -1104,8 +1148,14 @@ notify:
 gboolean
 ges_track_add_element (GESTrack * track, GESTrackElement * object)
 {
+  GESTimeline *timeline;
+  GESTimelineElement *el;
+
   g_return_val_if_fail (GES_IS_TRACK (track), FALSE);
   g_return_val_if_fail (GES_IS_TRACK_ELEMENT (object), FALSE);
+
+  el = GES_TIMELINE_ELEMENT (object);
+
   CHECK_THREAD (track);
 
   GST_DEBUG ("track:%p, object:%p", track, object);
@@ -1117,12 +1167,14 @@ ges_track_add_element (GESTrack * track, GESTrackElement * object)
     return FALSE;
   }
 
-  if (G_UNLIKELY (!ges_track_element_set_track (object, track))) {
-    GST_ERROR ("Couldn't properly add the object to the Track");
+  if (!ges_track_element_set_track (object, track)) {
+    GST_WARNING_OBJECT (track, "Failed to set the track for %" GES_FORMAT,
+        GES_ARGS (object));
     gst_object_ref_sink (object);
     gst_object_unref (object);
     return FALSE;
   }
+  ges_timeline_element_set_timeline (el, NULL);
 
   GST_DEBUG ("Adding object %s to ourself %s",
       GST_OBJECT_NAME (ges_track_element_get_nleobject (object)),
@@ -1131,6 +1183,7 @@ ges_track_add_element (GESTrack * track, GESTrackElement * object)
   if (G_UNLIKELY (!ges_nle_composition_add_object (track->priv->composition,
               ges_track_element_get_nleobject (object)))) {
     GST_WARNING ("Couldn't add object to the NleComposition");
+    ges_track_element_set_track (object, NULL);
     gst_object_ref_sink (object);
     gst_object_unref (object);
     return FALSE;
@@ -1141,8 +1194,20 @@ ges_track_add_element (GESTrack * track, GESTrackElement * object)
       g_sequence_insert_sorted (track->priv->trackelements_by_start, object,
           (GCompareDataFunc) element_start_compare, NULL));
 
-  ges_timeline_element_set_timeline (GES_TIMELINE_ELEMENT (object),
-      track->priv->timeline);
+  timeline = track->priv->timeline;
+  ges_timeline_element_set_timeline (el, timeline);
+  if (timeline
+      && !timeline_tree_can_move_element (timeline_get_tree (timeline), el,
+          GES_TIMELINE_ELEMENT_LAYER_PRIORITY (el), el->start, el->duration,
+          NULL)) {
+    GST_WARNING_OBJECT (track,
+        "Could not add the track element %" GES_FORMAT
+        " to the track because it breaks the timeline " "configuration rules",
+        GES_ARGS (el));
+    remove_element_internal (track, object, FALSE);
+    return FALSE;
+  }
+
   g_signal_emit (track, ges_track_signals[TRACK_ELEMENT_ADDED], 0,
       GES_TRACK_ELEMENT (object));
 
@@ -1188,31 +1253,12 @@ ges_track_get_elements (GESTrack * track)
 gboolean
 ges_track_remove_element (GESTrack * track, GESTrackElement * object)
 {
-  GSequenceIter *it;
-  GESTrackPrivate *priv;
-
   g_return_val_if_fail (GES_IS_TRACK (track), FALSE);
   g_return_val_if_fail (GES_IS_TRACK_ELEMENT (object), FALSE);
-  CHECK_THREAD (track);
-
-  priv = track->priv;
 
-  GST_DEBUG_OBJECT (track, "Removing %" GST_PTR_FORMAT, object);
-
-  it = g_hash_table_lookup (priv->trackelements_iter, object);
-  g_sequence_remove (it);
-
-  if (remove_object_internal (track, object) == TRUE) {
-    ges_timeline_element_set_timeline (GES_TIMELINE_ELEMENT (object), NULL);
-
-    return TRUE;
-  }
-
-  g_hash_table_insert (track->priv->trackelements_iter, object,
-      g_sequence_insert_sorted (track->priv->trackelements_by_start, object,
-          (GCompareDataFunc) element_start_compare, NULL));
+  CHECK_THREAD (track);
 
-  return FALSE;
+  return remove_element_internal (track, object, TRUE);
 }
 
 /**
index d37d9c1..b50f87b 100644 (file)
@@ -54,8 +54,7 @@ GST_START_TEST (test_ges_scenario)
 
   layers = ges_timeline_get_layers (timeline);
   fail_unless (g_list_find (layers, layer) != NULL);
-  g_list_foreach (layers, (GFunc) gst_object_unref, NULL);
-  g_list_free (layers);
+  g_list_free_full (layers, gst_object_unref);
 
   /* Give the Timeline a Track */
   GST_DEBUG ("Create a Track");
@@ -101,6 +100,7 @@ GST_START_TEST (test_ges_scenario)
    * 3 by the timeline
    * 1 by the track */
   ASSERT_OBJECT_REFCOUNT (trackelement, "trackelement", 3);
+  fail_unless (ges_track_element_get_track (trackelement) == track);
 
   GST_DEBUG ("Remove the Clip from the layer");
 
@@ -108,6 +108,10 @@ GST_START_TEST (test_ges_scenario)
   gst_object_ref (source);
   ASSERT_OBJECT_REFCOUNT (layer, "layer", 1);
   fail_unless (ges_layer_remove_clip (layer, GES_CLIP (source)));
+  /* track elements emptied from the track, but stay in clip */
+  fail_unless (GES_TIMELINE_ELEMENT_PARENT (trackelement) ==
+      GES_TIMELINE_ELEMENT (source));
+  fail_unless (ges_track_element_get_track (trackelement) == NULL);
   ASSERT_OBJECT_REFCOUNT (source, "source", 1);
   ASSERT_OBJECT_REFCOUNT (layer, "layer", 1);
   tmp_layer = ges_clip_get_layer (GES_CLIP (source));
@@ -118,7 +122,7 @@ GST_START_TEST (test_ges_scenario)
   /* Remove the track from the timeline */
   gst_object_ref (track);
   fail_unless (ges_timeline_remove_track (timeline, track));
-  fail_unless (ges_track_get_timeline (track) == NULL);
+  assert_num_in_track (track, 0);
 
   tracks = ges_timeline_get_tracks (timeline);
   fail_unless (tracks == NULL);
@@ -149,12 +153,23 @@ GST_END_TEST;
  * and then add it to the timeline.
  */
 
+#define _CREATE_SOURCE(layer, clip, start, duration) \
+{ \
+  GESAsset *asset = ges_asset_request (GES_TYPE_TEST_CLIP, NULL, NULL); \
+  GST_DEBUG ("Creating a source"); \
+  fail_unless (clip = ges_layer_add_asset (layer, asset, start, 0, \
+        duration, GES_TRACK_TYPE_UNKNOWN)); \
+  assert_layer(clip, layer); \
+  ASSERT_OBJECT_REFCOUNT (layer, "layer", 1); \
+  gst_object_unref (asset); \
+}
+
 GST_START_TEST (test_ges_timeline_add_layer)
 {
   GESTimeline *timeline;
-  GESLayer *layer, *tmp_layer;
+  GESLayer *layer;
   GESTrack *track;
-  GESTestClip *s1, *s2, *s3;
+  GESClip *s1, *s2, *s3;
   GList *trackelements, *layers;
   GESTrackElement *trackelement;
 
@@ -180,34 +195,6 @@ GST_START_TEST (test_ges_timeline_add_layer)
   fail_unless (ges_track_get_timeline (track) == timeline);
   fail_unless ((gpointer) GST_ELEMENT_PARENT (track) == (gpointer) timeline);
 
-  /* Create a source and add it to the Layer */
-  GST_DEBUG ("Creating a source");
-  s1 = ges_test_clip_new ();
-  fail_unless (s1 != NULL);
-  fail_unless (ges_layer_add_clip (layer, GES_CLIP (s1)));
-  tmp_layer = ges_clip_get_layer (GES_CLIP (s1));
-  fail_unless (tmp_layer == layer);
-  ASSERT_OBJECT_REFCOUNT (layer, "layer", 2);
-  gst_object_unref (tmp_layer);
-
-  GST_DEBUG ("Creating a source");
-  s2 = ges_test_clip_new ();
-  fail_unless (s2 != NULL);
-  fail_unless (ges_layer_add_clip (layer, GES_CLIP (s2)));
-  tmp_layer = ges_clip_get_layer (GES_CLIP (s2));
-  fail_unless (tmp_layer == layer);
-  ASSERT_OBJECT_REFCOUNT (layer, "layer", 2);
-  gst_object_unref (tmp_layer);
-
-  GST_DEBUG ("Creating a source");
-  s3 = ges_test_clip_new ();
-  fail_unless (s3 != NULL);
-  fail_unless (ges_layer_add_clip (layer, GES_CLIP (s3)));
-  tmp_layer = ges_clip_get_layer (GES_CLIP (s3));
-  fail_unless (tmp_layer == layer);
-  ASSERT_OBJECT_REFCOUNT (layer, "layer", 2);
-  gst_object_unref (tmp_layer);
-
   GST_DEBUG ("Add the layer to the timeline");
   fail_unless (ges_timeline_add_layer (timeline, layer));
   /* The timeline steals our reference to the layer */
@@ -215,8 +202,14 @@ GST_START_TEST (test_ges_timeline_add_layer)
   fail_unless (layer->timeline == timeline);
   layers = ges_timeline_get_layers (timeline);
   fail_unless (g_list_find (layers, layer) != NULL);
-  g_list_foreach (layers, (GFunc) gst_object_unref, NULL);
-  g_list_free (layers);
+  g_list_free_full (layers, gst_object_unref);
+
+  _CREATE_SOURCE (layer, s1, 0, 10);
+  ASSERT_OBJECT_REFCOUNT (layer, "1 for the timeline", 1);
+  _CREATE_SOURCE (layer, s2, 20, 10);
+  ASSERT_OBJECT_REFCOUNT (layer, "1 for the timeline", 1);
+  _CREATE_SOURCE (layer, s3, 40, 10);
+  ASSERT_OBJECT_REFCOUNT (layer, "1 for the timeline", 1);
 
   /* Make sure the associated TrackElements are in the Track */
   trackelements = GES_CONTAINER_CHILDREN (s1);
@@ -266,9 +259,9 @@ GST_END_TEST;
 GST_START_TEST (test_ges_timeline_add_layer_first)
 {
   GESTimeline *timeline;
-  GESLayer *layer, *tmp_layer;
+  GESLayer *layer;
   GESTrack *track;
-  GESTestClip *s1, *s2, *s3;
+  GESClip *s1, *s2, *s3;
   GList *trackelements, *tmp, *layers;
 
   ges_init ();
@@ -286,30 +279,9 @@ GST_START_TEST (test_ges_timeline_add_layer_first)
   track = GES_TRACK (ges_video_track_new ());
   fail_unless (track != NULL);
 
-  /* Create a source and add it to the Layer */
-  GST_DEBUG ("Creating a source");
-  s1 = ges_test_clip_new ();
-  fail_unless (s1 != NULL);
-  fail_unless (ges_layer_add_clip (layer, GES_CLIP (s1)));
-  tmp_layer = ges_clip_get_layer (GES_CLIP (s1));
-  fail_unless (tmp_layer == layer);
-  gst_object_unref (tmp_layer);
-
-  GST_DEBUG ("Creating a source");
-  s2 = ges_test_clip_new ();
-  fail_unless (s2 != NULL);
-  fail_unless (ges_layer_add_clip (layer, GES_CLIP (s2)));
-  tmp_layer = ges_clip_get_layer (GES_CLIP (s2));
-  fail_unless (tmp_layer == layer);
-  gst_object_unref (tmp_layer);
-
-  GST_DEBUG ("Creating a source");
-  s3 = ges_test_clip_new ();
-  fail_unless (s3 != NULL);
-  fail_unless (ges_layer_add_clip (layer, GES_CLIP (s3)));
-  tmp_layer = ges_clip_get_layer (GES_CLIP (s3));
-  fail_unless (tmp_layer == layer);
-  gst_object_unref (tmp_layer);
+  _CREATE_SOURCE (layer, s1, 0, 10);
+  _CREATE_SOURCE (layer, s2, 20, 10);
+  _CREATE_SOURCE (layer, s3, 40, 10);
 
   GST_DEBUG ("Add the layer to the timeline");
   fail_unless (ges_timeline_add_layer (timeline, layer));
@@ -318,8 +290,7 @@ GST_START_TEST (test_ges_timeline_add_layer_first)
   fail_unless (layer->timeline == timeline);
   layers = ges_timeline_get_layers (timeline);
   fail_unless (g_list_find (layers, layer) != NULL);
-  g_list_foreach (layers, (GFunc) gst_object_unref, NULL);
-  g_list_free (layers);
+  g_list_free_full (layers, gst_object_unref);
 
   GST_DEBUG ("Add the track to the timeline");
   fail_unless (ges_timeline_add_track (timeline, track));
@@ -369,9 +340,9 @@ GST_END_TEST;
 GST_START_TEST (test_ges_timeline_remove_track)
 {
   GESTimeline *timeline;
-  GESLayer *layer, *tmp_layer;
+  GESLayer *layer;
   GESTrack *track;
-  GESTestClip *s1, *s2, *s3;
+  GESClip *s1, *s2, *s3;
   GESTrackElement *t1, *t2, *t3;
   GList *trackelements, *tmp, *layers;
 
@@ -390,32 +361,11 @@ GST_START_TEST (test_ges_timeline_remove_track)
   track = GES_TRACK (ges_video_track_new ());
   fail_unless (track != NULL);
 
-  /* Create a source and add it to the Layer */
-  GST_DEBUG ("Creating a source");
-  s1 = ges_test_clip_new ();
-  fail_unless (s1 != NULL);
-  fail_unless (ges_layer_add_clip (layer, GES_CLIP (s1)));
-  tmp_layer = ges_clip_get_layer (GES_CLIP (s1));
-  fail_unless (tmp_layer == layer);
-  gst_object_unref (tmp_layer);
+  _CREATE_SOURCE (layer, s1, 0, 10);
   ASSERT_OBJECT_REFCOUNT (layer, "1 for the timeline", 1);
-
-  GST_DEBUG ("Creating a source");
-  s2 = ges_test_clip_new ();
-  fail_unless (s2 != NULL);
-  fail_unless (ges_layer_add_clip (layer, GES_CLIP (s2)));
-  tmp_layer = ges_clip_get_layer (GES_CLIP (s2));
-  fail_unless (tmp_layer == layer);
-  gst_object_unref (tmp_layer);
+  _CREATE_SOURCE (layer, s2, 20, 10);
   ASSERT_OBJECT_REFCOUNT (layer, "1 for the timeline", 1);
-
-  GST_DEBUG ("Creating a source");
-  s3 = ges_test_clip_new ();
-  fail_unless (s3 != NULL);
-  fail_unless (ges_layer_add_clip (layer, GES_CLIP (s3)));
-  tmp_layer = ges_clip_get_layer (GES_CLIP (s3));
-  fail_unless (tmp_layer == layer);
-  gst_object_unref (tmp_layer);
+  _CREATE_SOURCE (layer, s3, 40, 10);
   ASSERT_OBJECT_REFCOUNT (layer, "1 for the timeline", 1);
 
   GST_DEBUG ("Add the layer to the timeline");
@@ -426,8 +376,7 @@ GST_START_TEST (test_ges_timeline_remove_track)
 
   layers = ges_timeline_get_layers (timeline);
   fail_unless (g_list_find (layers, layer) != NULL);
-  g_list_foreach (layers, (GFunc) gst_object_unref, NULL);
-  g_list_free (layers);
+  g_list_free_full (layers, gst_object_unref);
   ASSERT_OBJECT_REFCOUNT (layer, "1 for the timeline", 1);
 
   GST_DEBUG ("Add the track to the timeline");
@@ -485,8 +434,18 @@ GST_START_TEST (test_ges_timeline_remove_track)
    * 1 by the timeline */
   ASSERT_OBJECT_REFCOUNT (t3, "t3", 3);
 
+  fail_unless (ges_track_element_get_track (t1) == track);
+  fail_unless (ges_track_element_get_track (t2) == track);
+  fail_unless (ges_track_element_get_track (t3) == track);
+
   /* remove the track and check that the track elements have been released */
+  gst_object_ref (track);
   fail_unless (ges_timeline_remove_track (timeline, track));
+  assert_num_in_track (track, 0);
+  gst_object_unref (track);
+  fail_unless (ges_track_element_get_track (t1) == NULL);
+  fail_unless (ges_track_element_get_track (t2) == NULL);
+  fail_unless (ges_track_element_get_track (t3) == NULL);
 
   ASSERT_OBJECT_REFCOUNT (t1, "trackelement", 1);
   ASSERT_OBJECT_REFCOUNT (t2, "trackelement", 1);
@@ -507,22 +466,67 @@ GST_END_TEST;
 
 typedef struct
 {
-  GESTestClip **o1, **o2, **o3;
-  GESTrack **tr1, **tr2;
+  GESClip *clips[4];
+  guint num_calls[4];
+  GESTrackElement *effects[3];
+  GESTrack *tr1, *tr2;
+  guint num_unrecognised;
 } SelectTracksData;
 
 static GPtrArray *
 select_tracks_cb (GESTimeline * timeline, GESClip * clip,
-    GESTrackElement * track_element, SelectTracksData * st_data)
+    GESTrackElement * track_element, SelectTracksData * data)
 {
-  GESTrack *track;
-
   GPtrArray *ret = g_ptr_array_new ();
-  track = (clip == (GESClip *) * st_data->o2) ? *st_data->tr2 : *st_data->tr1;
+  gboolean track1 = FALSE;
+  gboolean track2 = FALSE;
+  guint i;
+  gboolean recognise_clip = FALSE;
+
+  for (i = 0; i < 4; i++) {
+    if (clip == data->clips[i]) {
+      data->num_calls[i]++;
+      recognise_clip = TRUE;
+    }
+  }
 
-  gst_object_ref (track);
+  if (!recognise_clip) {
+    GST_DEBUG_OBJECT (timeline, "unrecognised clip %" GES_FORMAT " for "
+        "track element %" GES_FORMAT, GES_ARGS (clip),
+        GES_ARGS (track_element));
+    data->num_unrecognised++;
+    return ret;
+  }
 
-  g_ptr_array_add (ret, track);
+  if (GES_IS_BASE_EFFECT (track_element)) {
+    if (track_element == data->effects[0]) {
+      track1 = TRUE;
+    } else if (track_element == data->effects[1]) {
+      track1 = TRUE;
+      track2 = TRUE;
+    } else if (track_element == data->effects[2]) {
+      track2 = TRUE;
+    } else {
+      GST_DEBUG_OBJECT (timeline, "unrecognised effect %" GES_FORMAT,
+          GES_ARGS (track_element));
+      data->num_unrecognised++;
+    }
+  } else if (GES_IS_SOURCE (track_element)) {
+    if (clip == data->clips[0] || clip == data->clips[1])
+      track1 = TRUE;
+    if (clip == data->clips[1] || clip == data->clips[2])
+      track2 = TRUE;
+    /* clips[3] has no tracks selected */
+  } else {
+    GST_DEBUG_OBJECT (timeline, "unrecognised track element %" GES_FORMAT,
+        GES_ARGS (track_element));
+    data->num_unrecognised++;
+  }
+
+  if (track1)
+    g_ptr_array_add (ret, gst_object_ref (data->tr1));
+  if (track2)
+    g_ptr_array_add (ret, gst_object_ref (data->tr2));
 
   return ret;
 }
@@ -530,12 +534,14 @@ select_tracks_cb (GESTimeline * timeline, GESClip * clip,
 GST_START_TEST (test_ges_timeline_multiple_tracks)
 {
   GESTimeline *timeline;
-  GESLayer *layer, *tmp_layer;
+  GESLayer *layer;
   GESTrack *track1, *track2;
-  GESTestClip *s1, *s2, *s3;
-  GESTrackElement *t1, *t2, *t3;
+  GESClip *s1, *s2, *s3, *s4;
+  GESTrackElement *e1, *e2, *e3, *el, *el2, *e_copy;
+  gboolean found_e1 = FALSE, found_e2 = FALSE, found_e3 = FALSE;
   GList *trackelements, *tmp, *layers;
-  SelectTracksData st_data = { &s1, &s2, &s3, &track1, &track2 };
+  GstControlSource *ctrl_source;
+  SelectTracksData st_data;
 
   ges_init ();
 
@@ -544,9 +550,6 @@ GST_START_TEST (test_ges_timeline_multiple_tracks)
   timeline = ges_timeline_new ();
   fail_unless (timeline != NULL);
 
-  g_signal_connect (timeline, "select-tracks-for-object",
-      G_CALLBACK (select_tracks_cb), &st_data);
-
   GST_DEBUG ("Create a layer");
   layer = ges_layer_new ();
   fail_unless (layer != NULL);
@@ -570,31 +573,71 @@ GST_START_TEST (test_ges_timeline_multiple_tracks)
   fail_unless (ges_track_get_timeline (track2) == timeline);
   fail_unless ((gpointer) GST_ELEMENT_PARENT (track2) == (gpointer) timeline);
 
-  /* Create a source and add it to the Layer */
-  GST_DEBUG ("Creating a source");
-  s1 = ges_test_clip_new ();
-  fail_unless (s1 != NULL);
-  fail_unless (ges_layer_add_clip (layer, GES_CLIP (s1)));
-  tmp_layer = ges_clip_get_layer (GES_CLIP (s1));
-  fail_unless (tmp_layer == layer);
-  gst_object_unref (tmp_layer);
+  /* adding to the layer before it is part of the timeline does not
+   * trigger track selection */
+  /* s1 and s3 can overlap since they are destined for different tracks */
+  /* s2 will overlap both */
+  /* s4 destined for no track */
+  _CREATE_SOURCE (layer, s1, 0, 10);
+  _CREATE_SOURCE (layer, s2, 5, 10);
+  _CREATE_SOURCE (layer, s3, 0, 10);
+  _CREATE_SOURCE (layer, s4, 0, 20);
+
+  e1 = GES_TRACK_ELEMENT (ges_effect_new ("videobalance"));
+  fail_unless (ges_container_add (GES_CONTAINER (s2),
+          GES_TIMELINE_ELEMENT (e1)));
+  e2 = GES_TRACK_ELEMENT (ges_effect_new ("agingtv ! vertigotv"));
+  fail_unless (ges_container_add (GES_CONTAINER (s2),
+          GES_TIMELINE_ELEMENT (e2)));
+  e3 = GES_TRACK_ELEMENT (ges_effect_new ("alpha"));
+  fail_unless (ges_container_add (GES_CONTAINER (s2),
+          GES_TIMELINE_ELEMENT (e3)));
+  assert_equals_int (0,
+      ges_clip_get_top_effect_index (s2, GES_BASE_EFFECT (e1)));
+  assert_equals_int (1,
+      ges_clip_get_top_effect_index (s2, GES_BASE_EFFECT (e2)));
+  assert_equals_int (2,
+      ges_clip_get_top_effect_index (s2, GES_BASE_EFFECT (e3)));
+
+  assert_num_children (s1, 0);
+  assert_num_children (s2, 3);
+  assert_num_children (s3, 0);
+
+  ges_timeline_element_set_child_properties (GES_TIMELINE_ELEMENT (s2),
+      "scratch-lines", 2, "speed", 50.0, NULL);
+
+  ctrl_source = GST_CONTROL_SOURCE (gst_interpolation_control_source_new ());
+  g_object_set (G_OBJECT (ctrl_source), "mode",
+      GST_INTERPOLATION_MODE_NONE, NULL);
+  fail_unless (gst_timed_value_control_source_set
+      (GST_TIMED_VALUE_CONTROL_SOURCE (ctrl_source), 0, 1.0));
+  fail_unless (gst_timed_value_control_source_set
+      (GST_TIMED_VALUE_CONTROL_SOURCE (ctrl_source), 4, 7.0));
+  fail_unless (gst_timed_value_control_source_set
+      (GST_TIMED_VALUE_CONTROL_SOURCE (ctrl_source), 8, 3.0));
+  fail_unless (ges_track_element_set_control_source (e2, ctrl_source,
+          "scratch-lines", "direct-absolute"));
+  gst_object_unref (ctrl_source);
+
+  st_data.tr1 = track1;
+  st_data.tr2 = track2;
+  st_data.clips[0] = s1;
+  st_data.clips[1] = s2;
+  st_data.clips[2] = s3;
+  st_data.clips[3] = s4;
+  st_data.num_calls[0] = 0;
+  st_data.num_calls[1] = 0;
+  st_data.num_calls[2] = 0;
+  st_data.num_calls[3] = 0;
+  st_data.effects[0] = e1;
+  st_data.effects[1] = e2;
+  st_data.effects[2] = e3;
+  st_data.num_unrecognised = 0;
 
-  GST_DEBUG ("Creating a source");
-  s2 = ges_test_clip_new ();
-  fail_unless (s2 != NULL);
-  fail_unless (ges_layer_add_clip (layer, GES_CLIP (s2)));
-  tmp_layer = ges_clip_get_layer (GES_CLIP (s2));
-  fail_unless (tmp_layer == layer);
-  gst_object_unref (tmp_layer);
-
-  GST_DEBUG ("Creating a source");
-  s3 = ges_test_clip_new ();
-  fail_unless (s3 != NULL);
-  fail_unless (ges_layer_add_clip (layer, GES_CLIP (s3)));
-  tmp_layer = ges_clip_get_layer (GES_CLIP (s3));
-  fail_unless (tmp_layer == layer);
-  gst_object_unref (tmp_layer);
+  g_signal_connect (timeline, "select-tracks-for-object",
+      G_CALLBACK (select_tracks_cb), &st_data);
 
+  /* adding layer to the timeline will trigger track selection, this */
   GST_DEBUG ("Add the layer to the timeline");
   fail_unless (ges_timeline_add_layer (timeline, layer));
   /* The timeline steals our reference to the layer */
@@ -603,70 +646,121 @@ GST_START_TEST (test_ges_timeline_multiple_tracks)
 
   layers = ges_timeline_get_layers (timeline);
   fail_unless (g_list_find (layers, layer) != NULL);
-  g_list_foreach (layers, (GFunc) gst_object_unref, NULL);
-  g_list_free (layers);
+  g_list_free_full (layers, gst_object_unref);
 
-  /* Make sure the associated TrackElements are in the Track */
-  trackelements = GES_CONTAINER_CHILDREN (s1);
-  fail_unless (trackelements != NULL);
-  t1 = GES_TRACK_ELEMENT ((trackelements)->data);
-  for (tmp = trackelements; tmp; tmp = tmp->next) {
-    /* There are 3 references held:
-     * 1 by the clip
-     * 1 by the track
-     * 1 by the timeline */
-    ASSERT_OBJECT_REFCOUNT (GES_TRACK_ELEMENT (tmp->data), "trackelement", 3);
-    fail_unless (ges_track_element_get_track (tmp->data) == track1);
-  }
-  gst_object_ref (t1);
-  /* There are 3 references held:
-   * 1 by the container
-   * 1 by the track
-   * 1 by the timeline
-   * 1 added by ourselves above (gst_object_ref (t1)) */
-  ASSERT_OBJECT_REFCOUNT (t1, "trackelement", 4);
+  assert_equals_int (st_data.num_unrecognised, 0);
 
+  /* Make sure the associated TrackElements are in the Track */
+  assert_num_children (s1, 1);
+  el = GES_CONTAINER_CHILDREN (s1)->data;
+  fail_unless (GES_IS_SOURCE (el));
+  fail_unless (ges_track_element_get_track (el) == track1);
+  ASSERT_OBJECT_REFCOUNT (el, "1 timeline + 1 track + 1 clip", 3);
+  /* called once for source */
+  assert_equals_int (st_data.num_calls[0], 1);
+
+  /* 2 sources + 4 effects */
+  assert_num_children (s2, 6);
   trackelements = GES_CONTAINER_CHILDREN (s2);
-  fail_unless (trackelements != NULL);
-  t2 = GES_TRACK_ELEMENT (trackelements->data);
+  /* sources at the end */
+  el = g_list_nth_data (trackelements, 5);
+  fail_unless (GES_IS_SOURCE (el));
+  el2 = g_list_nth_data (trackelements, 4);
+  fail_unless (GES_IS_SOURCE (el2));
+
+  /* font-desc is originally "", but on setting switches to Normal, so we
+   * set it explicitly */
+  ges_timeline_element_set_child_properties (GES_TIMELINE_ELEMENT (el),
+      "font-desc", "Normal", NULL);
+  assert_equal_children_properties (el, el2);
+  assert_equal_bindings (el, el2);
+
+  assert_equals_int (GES_TIMELINE_ELEMENT_PRIORITY (el),
+      GES_TIMELINE_ELEMENT_PRIORITY (el2));
+
+  /* check one in each track */
+  fail_unless (ges_track_element_get_track (el)
+      != ges_track_element_get_track (el2));
+  fail_unless (ges_track_element_get_track (el) == track1
+      || ges_track_element_get_track (el2) == track1);
+  fail_unless (ges_track_element_get_track (el) == track2
+      || ges_track_element_get_track (el2) == track2);
+
+  /* effects */
+  e_copy = NULL;
   for (tmp = trackelements; tmp; tmp = tmp->next) {
-    /* There are 3 references held:
-     * 1 by the clip
-     * 1 by the track
-     * 1 by the timeline */
-    ASSERT_OBJECT_REFCOUNT (GES_TRACK_ELEMENT (tmp->data), "trackelement", 3);
-    fail_unless (ges_track_element_get_track (tmp->data) == track2);
+    el = tmp->data;
+    ASSERT_OBJECT_REFCOUNT (el, "1 timeline + 1 track + 1 clip", 3);
+    if (GES_IS_BASE_EFFECT (el)) {
+      if (el == e1) {
+        fail_if (found_e1);
+        found_e1 = TRUE;
+      } else if (el == e2) {
+        fail_if (found_e2);
+        found_e2 = TRUE;
+      } else if (el == e3) {
+        fail_if (found_e3);
+        found_e3 = TRUE;
+      } else {
+        fail_if (e_copy);
+        e_copy = el;
+      }
+    }
   }
-  gst_object_ref (t2);
-  /* There are 3 references held:
-   * 1 by the container
-   * 1 by the track
-   * 1 by the timeline
-   * 1 added by ourselves above (gst_object_ref (t2)) */
-  ASSERT_OBJECT_REFCOUNT (t2, "t2", 4);
+  fail_unless (found_e1);
+  fail_unless (found_e2);
+  fail_unless (found_e3);
+  fail_unless (e_copy);
+
+  fail_unless (ges_track_element_get_track (e1) == track1);
+  fail_unless (ges_track_element_get_track (e3) == track2);
+
+  assert_equal_children_properties (e2, e_copy);
+  assert_equal_bindings (e2, e_copy);
+
+  /* check one in each track */
+  fail_unless (ges_track_element_get_track (e2)
+      != ges_track_element_get_track (e_copy));
+  fail_unless (ges_track_element_get_track (e2) == track1
+      || ges_track_element_get_track (e_copy) == track1);
+  fail_unless (ges_track_element_get_track (e2) == track2
+      || ges_track_element_get_track (e_copy) == track2);
+
+  /* e2 copy placed next to e2 in top effect list */
+  assert_equals_int (0,
+      ges_clip_get_top_effect_index (s2, GES_BASE_EFFECT (e1)));
+  assert_equals_int (1,
+      ges_clip_get_top_effect_index (s2, GES_BASE_EFFECT (e2)));
+  assert_equals_int (2,
+      ges_clip_get_top_effect_index (s2, GES_BASE_EFFECT (e_copy)));
+  assert_equals_int (3,
+      ges_clip_get_top_effect_index (s2, GES_BASE_EFFECT (e3)));
+
+  /* called 4 times: 1 for source, and 1 for each effect (3) */
+  assert_equals_int (st_data.num_calls[1], 4);
+
+  assert_num_children (s3, 1);
+  el = GES_CONTAINER_CHILDREN (s3)->data;
+  fail_unless (GES_IS_SOURCE (el));
+  fail_unless (ges_track_element_get_track (el) == track2);
+  ASSERT_OBJECT_REFCOUNT (el, "1 timeline + 1 track + 1 clip", 3);
+  /* called once for source */
+  assert_equals_int (st_data.num_calls[2], 1);
+
+  /* one child but no track */
+  assert_num_children (s4, 1);
+  el = GES_CONTAINER_CHILDREN (s4)->data;
+  fail_unless (GES_IS_SOURCE (el));
+  fail_unless (ges_track_element_get_track (el) == NULL);
+  ASSERT_OBJECT_REFCOUNT (el, "1 clip", 1);
+  /* called once for source (where no track was selected) */
+  assert_equals_int (st_data.num_calls[0], 1);
+
+  /* 2 sources + 2 effects */
+  assert_num_in_track (track1, 4);
+  assert_num_in_track (track2, 4);
 
-  trackelements = GES_CONTAINER_CHILDREN (s3);
-  fail_unless (trackelements != NULL);
-  t3 = GES_TRACK_ELEMENT (trackelements->data);
-  for (tmp = trackelements; tmp; tmp = tmp->next) {
-    /* There are 3 references held:
-     * 1 by the clip
-     * 1 by the track
-     * 1 by the timeline */
-    ASSERT_OBJECT_REFCOUNT (GES_TRACK_ELEMENT (tmp->data), "trackelement", 3);
-    fail_unless (ges_track_element_get_track (tmp->data) == track1);
-  }
-  gst_object_ref (t3);
-  /* There are 3 references held:
-   * 1 by the container
-   * 1 by the track
-   * 1 by the timeline
-   * 1 added by ourselves above (gst_object_ref (t3)) */
-  ASSERT_OBJECT_REFCOUNT (t3, "t3", 4);
 
-  gst_object_unref (t1);
-  gst_object_unref (t2);
-  gst_object_unref (t3);
 
   gst_object_unref (timeline);
 
index c231b11..f07ac11 100644 (file)
@@ -54,7 +54,7 @@ GST_START_TEST (test_object_properties)
 
   ges_layer_add_clip (layer, GES_CLIP (clip));
   ges_timeline_commit (timeline);
-  assert_equals_int (g_list_length (GES_CONTAINER_CHILDREN (clip)), 1);
+  assert_num_children (clip, 1);
   trackelement = GES_CONTAINER_CHILDREN (clip)->data;
   fail_unless (trackelement != NULL);
   fail_unless (GES_TIMELINE_ELEMENT_PARENT (trackelement) ==
@@ -133,7 +133,7 @@ GST_START_TEST (test_split_direct_bindings)
   g_object_unref (asset);
 
   CHECK_OBJECT_PROPS (clip, 0 * GST_SECOND, 10 * GST_SECOND, 10 * GST_SECOND);
-  assert_equals_int (g_list_length (GES_CONTAINER_CHILDREN (clip)), 1);
+  assert_num_children (clip, 1);
   check_layer (clip, 0);
 
   source = gst_interpolation_control_source_new ();
@@ -231,7 +231,7 @@ GST_START_TEST (test_split_direct_absolute_bindings)
   g_object_unref (asset);
 
   CHECK_OBJECT_PROPS (clip, 0 * GST_SECOND, 10 * GST_SECOND, 10 * GST_SECOND);
-  assert_equals_int (g_list_length (GES_CONTAINER_CHILDREN (clip)), 1);
+  assert_num_children (clip, 1);
   check_layer (clip, 0);
 
   source = gst_interpolation_control_source_new ();
@@ -301,14 +301,37 @@ GST_START_TEST (test_split_direct_absolute_bindings)
 
 GST_END_TEST;
 
+static GPtrArray *
+_select_none (GESTimeline * timeline, GESClip * clip,
+    GESTrackElement * track_element, guint * called_p)
+{
+  (*called_p)++;
+  return NULL;
+}
+
+static GPtrArray *
+_select_track (GESTimeline * timeline, GESClip * clip,
+    GESTrackElement * track_element, GESTrack ** track_p)
+{
+  GPtrArray *tracks = g_ptr_array_new ();
+  fail_unless (track_p);
+  fail_unless (*track_p);
+  g_ptr_array_insert (tracks, -1, gst_object_ref (*track_p));
+  *track_p = NULL;
+  return tracks;
+}
 
 GST_START_TEST (test_split_object)
 {
   GESTimeline *timeline;
+  GESTrack *track1, *track2, *effect_track;
   GESLayer *layer;
   GESClip *clip, *splitclip;
   GList *splittrackelements;
-  GESTrackElement *trackelement, *splittrackelement;
+  GESTrackElement *trackelement1, *trackelement2, *effect1, *effect2,
+      *splittrackelement;
+  guint32 priority1, priority2, effect_priority1, effect_priority2;
+  guint selection_called = 0;
 
   ges_init ();
 
@@ -327,58 +350,151 @@ GST_START_TEST (test_split_object)
   g_object_set (clip, "start", (guint64) 42, "duration", (guint64) 50,
       "in-point", (guint64) 12, NULL);
   ASSERT_OBJECT_REFCOUNT (timeline, "timeline", 1);
-  assert_equals_uint64 (_START (clip), 42);
-  assert_equals_uint64 (_DURATION (clip), 50);
-  assert_equals_uint64 (_INPOINT (clip), 12);
+  CHECK_OBJECT_PROPS (clip, 42, 12, 50);
 
   ges_layer_add_clip (layer, GES_CLIP (clip));
   ges_timeline_commit (timeline);
-  assert_equals_int (g_list_length (GES_CONTAINER_CHILDREN (clip)), 2);
-  trackelement = GES_CONTAINER_CHILDREN (clip)->data;
-  fail_unless (trackelement != NULL);
-  fail_unless (GES_TIMELINE_ELEMENT_PARENT (trackelement) ==
+  assert_num_children (clip, 2);
+  trackelement1 = GES_CONTAINER_CHILDREN (clip)->data;
+  fail_unless (trackelement1 != NULL);
+  fail_unless (GES_TIMELINE_ELEMENT_PARENT (trackelement1) ==
+      GES_TIMELINE_ELEMENT (clip));
+  trackelement2 = GES_CONTAINER_CHILDREN (clip)->next->data;
+  fail_unless (trackelement2 != NULL);
+  fail_unless (GES_TIMELINE_ELEMENT_PARENT (trackelement2) ==
       GES_TIMELINE_ELEMENT (clip));
 
+  effect1 = GES_TRACK_ELEMENT (ges_effect_new ("agingtv"));
+  ges_container_add (GES_CONTAINER (clip), GES_TIMELINE_ELEMENT (effect1));
+
+  effect2 = GES_TRACK_ELEMENT (ges_effect_new ("vertigotv"));
+  ges_container_add (GES_CONTAINER (clip), GES_TIMELINE_ELEMENT (effect2));
+
   /* Check that trackelement has the same properties */
-  assert_equals_uint64 (_START (trackelement), 42);
-  assert_equals_uint64 (_DURATION (trackelement), 50);
-  assert_equals_uint64 (_INPOINT (trackelement), 12);
+  CHECK_OBJECT_PROPS (trackelement1, 42, 12, 50);
+  CHECK_OBJECT_PROPS (trackelement2, 42, 12, 50);
+  CHECK_OBJECT_PROPS (effect1, 42, 0, 50);
+  CHECK_OBJECT_PROPS (effect2, 42, 0, 50);
 
   /* And let's also check that it propagated correctly to GNonLin */
-  nle_object_check (ges_track_element_get_nleobject (trackelement), 42, 50, 12,
-      50, MIN_NLE_PRIO + TRANSITIONS_HEIGHT, TRUE);
+  nle_object_check (ges_track_element_get_nleobject (trackelement1), 42, 50, 12,
+      50, MIN_NLE_PRIO + TRANSITIONS_HEIGHT + 2, TRUE);
+  nle_object_check (ges_track_element_get_nleobject (trackelement2), 42, 50, 12,
+      50, MIN_NLE_PRIO + TRANSITIONS_HEIGHT + 2, TRUE);
+
+  track1 = ges_track_element_get_track (trackelement1);
+  fail_unless (track1);
+  track2 = ges_track_element_get_track (trackelement2);
+  fail_unless (track2);
+  fail_unless (track1 != track2);
+  effect_track = ges_track_element_get_track (effect1);
+  fail_unless (effect_track);
+  fail_unless (ges_track_element_get_track (effect2) == effect_track);
+
+  priority1 = GES_TIMELINE_ELEMENT_PRIORITY (trackelement1);
+  priority2 = GES_TIMELINE_ELEMENT_PRIORITY (trackelement2);
+  effect_priority1 = GES_TIMELINE_ELEMENT_PRIORITY (effect1);
+  effect_priority2 = GES_TIMELINE_ELEMENT_PRIORITY (effect2);
+
+  fail_unless (priority1 == priority2);
+  fail_unless (priority1 > effect_priority2);
+  fail_unless (effect_priority2 > effect_priority1);
+
+  ges_timeline_element_set_child_properties (GES_TIMELINE_ELEMENT (clip),
+      "font-desc", "Normal", "posx", 30, "posy", 50, "alpha", 0.1,
+      "freq", 449.0, "scratch-lines", 2, "zoom-speed", 1.05, NULL);
+
+  /* splitting should avoid track selection */
+  g_signal_connect (timeline, "select-tracks-for-object",
+      G_CALLBACK (_select_none), &selection_called);
 
   splitclip = ges_clip_split (clip, 67);
   fail_unless (GES_IS_CLIP (splitclip));
+  fail_unless (splitclip != clip);
 
-  assert_equals_uint64 (_START (clip), 42);
-  assert_equals_uint64 (_DURATION (clip), 25);
-  assert_equals_uint64 (_INPOINT (clip), 12);
+  fail_if (selection_called);
+
+  CHECK_OBJECT_PROPS (clip, 42, 12, 25);
+  CHECK_OBJECT_PROPS (trackelement1, 42, 12, 25);
+  CHECK_OBJECT_PROPS (trackelement1, 42, 12, 25);
+  CHECK_OBJECT_PROPS (effect1, 42, 0, 25);
+  CHECK_OBJECT_PROPS (effect2, 42, 0, 25);
+
+  CHECK_OBJECT_PROPS (splitclip, 67, 37, 25);
 
-  assert_equals_uint64 (_START (splitclip), 67);
-  assert_equals_uint64 (_DURATION (splitclip), 25);
-  assert_equals_uint64 (_INPOINT (splitclip), 37);
+  assert_equal_children_properties (splitclip, clip);
 
   splittrackelements = GES_CONTAINER_CHILDREN (splitclip);
-  fail_unless_equals_int (g_list_length (splittrackelements), 2);
+  fail_unless_equals_int (g_list_length (splittrackelements), 4);
 
+  /* first is the effects */
   splittrackelement = GES_TRACK_ELEMENT (splittrackelements->data);
   fail_unless (GES_IS_TRACK_ELEMENT (splittrackelement));
-  assert_equals_uint64 (_START (splittrackelement), 67);
-  assert_equals_uint64 (_DURATION (splittrackelement), 25);
-  assert_equals_uint64 (_INPOINT (splittrackelement), 37);
+  CHECK_OBJECT_PROPS (splittrackelement, 67, 0, 25);
 
-  fail_unless (splittrackelement != trackelement);
-  fail_unless (splitclip != clip);
+  assert_equal_children_properties (splittrackelement, effect1);
+  fail_unless (ges_track_element_get_track (splittrackelement) == effect_track);
+  fail_unless (ges_track_element_get_track (effect1) == effect_track);
+  /* +3 priority from layer */
+  assert_equals_int (GES_TIMELINE_ELEMENT_PRIORITY (splittrackelement),
+      effect_priority1 + 3);
+  fail_unless (GES_TIMELINE_ELEMENT_PRIORITY (effect1) == effect_priority1);
+
+  fail_unless (splittrackelement != trackelement1);
+  fail_unless (splittrackelement != trackelement2);
+  fail_unless (splittrackelement != effect1);
+  fail_unless (splittrackelement != effect2);
 
   splittrackelement = GES_TRACK_ELEMENT (splittrackelements->next->data);
   fail_unless (GES_IS_TRACK_ELEMENT (splittrackelement));
-  assert_equals_uint64 (_START (splittrackelement), 67);
-  assert_equals_uint64 (_DURATION (splittrackelement), 25);
-  assert_equals_uint64 (_INPOINT (splittrackelement), 37);
+  CHECK_OBJECT_PROPS (splittrackelement, 67, 0, 25);
 
-  fail_unless (splittrackelement != trackelement);
-  fail_unless (splitclip != clip);
+  assert_equal_children_properties (splittrackelement, effect2);
+  fail_unless (ges_track_element_get_track (splittrackelement) == effect_track);
+  fail_unless (ges_track_element_get_track (effect2) == effect_track);
+  assert_equals_int (GES_TIMELINE_ELEMENT_PRIORITY (splittrackelement),
+      effect_priority2 + 3);
+  fail_unless (GES_TIMELINE_ELEMENT_PRIORITY (effect2) == effect_priority2);
+
+  fail_unless (splittrackelement != trackelement1);
+  fail_unless (splittrackelement != trackelement2);
+  fail_unless (splittrackelement != effect1);
+  fail_unless (splittrackelement != effect2);
+
+  splittrackelement = GES_TRACK_ELEMENT (splittrackelements->next->next->data);
+  fail_unless (GES_IS_TRACK_ELEMENT (splittrackelement));
+  CHECK_OBJECT_PROPS (splittrackelement, 67, 37, 25);
+
+  /* core elements have swapped order in the clip, this is ok since they
+   * share the same priority */
+  assert_equal_children_properties (splittrackelement, trackelement2);
+  fail_unless (ges_track_element_get_track (splittrackelement) == track2);
+  fail_unless (ges_track_element_get_track (trackelement2) == track2);
+  assert_equals_int (GES_TIMELINE_ELEMENT_PRIORITY (splittrackelement),
+      priority2 + 3);
+  fail_unless (GES_TIMELINE_ELEMENT_PRIORITY (trackelement2) == priority2);
+
+  fail_unless (splittrackelement != trackelement1);
+  fail_unless (splittrackelement != trackelement2);
+  fail_unless (splittrackelement != effect1);
+  fail_unless (splittrackelement != effect2);
+
+  splittrackelement =
+      GES_TRACK_ELEMENT (splittrackelements->next->next->next->data);
+  fail_unless (GES_IS_TRACK_ELEMENT (splittrackelement));
+  CHECK_OBJECT_PROPS (splittrackelement, 67, 37, 25);
+
+  assert_equal_children_properties (splittrackelement, trackelement1);
+  fail_unless (ges_track_element_get_track (splittrackelement) == track1);
+  fail_unless (ges_track_element_get_track (trackelement1) == track1);
+  assert_equals_int (GES_TIMELINE_ELEMENT_PRIORITY (splittrackelement),
+      priority1 + 3);
+  fail_unless (GES_TIMELINE_ELEMENT_PRIORITY (trackelement1) == priority2);
+
+  fail_unless (splittrackelement != trackelement1);
+  fail_unless (splittrackelement != trackelement2);
+  fail_unless (splittrackelement != effect1);
+  fail_unless (splittrackelement != effect2);
 
   /* We own the only ref */
   ASSERT_OBJECT_REFCOUNT (splitclip, "1 ref for us + 1 for the timeline", 2);
@@ -395,15 +511,51 @@ GST_START_TEST (test_split_object)
 
 GST_END_TEST;
 
+#define _assert_higher_priority(el, higher) \
+{ \
+  if (higher) { \
+    guint32 el_prio = GES_TIMELINE_ELEMENT_PRIORITY (el); \
+    guint32 higher_prio = GES_TIMELINE_ELEMENT_PRIORITY (higher); \
+    fail_unless (el_prio > higher_prio, "%s does not have a higher " \
+        "priority than %s (%u vs %u)", GES_TIMELINE_ELEMENT_NAME (el), \
+        GES_TIMELINE_ELEMENT_NAME (higher), el_prio, higher_prio); \
+  } \
+}
+
+#define _assert_regroup_fails(clip_list) \
+{ \
+  GESContainer *regrouped = ges_container_group (clip_list); \
+  fail_unless (GES_IS_GROUP (regrouped)); \
+  assert_equals_int (g_list_length (regrouped->children), \
+      g_list_length (clip_list)); \
+  g_list_free_full (ges_container_ungroup (regrouped, FALSE), \
+      gst_object_unref); \
+}
+
 GST_START_TEST (test_clip_group_ungroup)
 {
   GESAsset *asset;
   GESTimeline *timeline;
-  GESClip *clip, *clip2;
+  GESClip *clip, *video_clip, *audio_clip;
+  GESTrackElement *el;
   GList *containers, *tmp;
   GESLayer *layer;
   GESContainer *regrouped_clip;
   GESTrack *audio_track, *video_track;
+  guint selection_called = 0;
+  struct
+  {
+    GESTrackElement *element;
+    GESTrackElement *higher_priority;
+  } audio_els[2];
+  struct
+  {
+    GESTrackElement *element;
+    GESTrackElement *higher_priority;
+  } video_els[3];
+  guint i, j;
+  const gchar *name;
+  GESTrackType type;
 
   ges_init ();
 
@@ -420,101 +572,192 @@ GST_START_TEST (test_clip_group_ungroup)
   assert_is_type (asset, GES_TYPE_ASSET);
 
   clip = ges_layer_add_asset (layer, asset, 0, 0, 10, GES_TRACK_TYPE_UNKNOWN);
-  ASSERT_OBJECT_REFCOUNT (clip, "1 layer + 1 timeline.all_elements", 2);
-  assert_equals_uint64 (_START (clip), 0);
-  assert_equals_uint64 (_INPOINT (clip), 0);
-  assert_equals_uint64 (_DURATION (clip), 10);
-  assert_equals_int (g_list_length (GES_CONTAINER_CHILDREN (clip)), 2);
+  ASSERT_OBJECT_REFCOUNT (clip, "1 layer + 1 timeline.all_els", 2);
+  assert_num_children (clip, 2);
+  CHECK_OBJECT_PROPS (clip, 0, 0, 10);
+
+  el = GES_TRACK_ELEMENT (ges_effect_new ("audioecho"));
+  ges_track_element_set_track_type (el, GES_TRACK_TYPE_AUDIO);
+  fail_unless (ges_container_add (GES_CONTAINER (clip),
+          GES_TIMELINE_ELEMENT (el)));
+
+  el = GES_TRACK_ELEMENT (ges_effect_new ("agingtv"));
+  ges_track_element_set_track_type (el, GES_TRACK_TYPE_VIDEO);
+  fail_unless (ges_container_add (GES_CONTAINER (clip),
+          GES_TIMELINE_ELEMENT (el)));
+
+  el = GES_TRACK_ELEMENT (ges_effect_new ("videobalance"));
+  ges_track_element_set_track_type (el, GES_TRACK_TYPE_VIDEO);
+  fail_unless (ges_container_add (GES_CONTAINER (clip),
+          GES_TIMELINE_ELEMENT (el)));
+
+  assert_num_children (clip, 5);
+  CHECK_OBJECT_PROPS (clip, 0, 0, 10);
+
+  i = j = 0;
+  for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) {
+    el = tmp->data;
+    type = ges_track_element_get_track_type (el);
+    if (type == GES_TRACK_TYPE_AUDIO) {
+      fail_unless (i < G_N_ELEMENTS (audio_els));
+      audio_els[i].element = el;
+      fail_unless (ges_track_element_get_track (el) == audio_track,
+          "%s not in audio track", GES_TIMELINE_ELEMENT_NAME (el));
+      if (i == 0)
+        audio_els[i].higher_priority = NULL;
+      else
+        audio_els[i].higher_priority = audio_els[i - 1].element;
+      _assert_higher_priority (el, audio_els[i].higher_priority);
+      i++;
+    }
+    if (type == GES_TRACK_TYPE_VIDEO) {
+      fail_unless (j < G_N_ELEMENTS (video_els));
+      video_els[j].element = el;
+      fail_unless (ges_track_element_get_track (el) == video_track,
+          "%s not in video track", GES_TIMELINE_ELEMENT_NAME (el));
+      if (j == 0)
+        video_els[j].higher_priority = NULL;
+      else
+        video_els[j].higher_priority = video_els[j - 1].element;
+      _assert_higher_priority (el, video_els[j].higher_priority);
+      j++;
+    }
+  }
+  fail_unless (i == G_N_ELEMENTS (audio_els));
+  fail_unless (j == G_N_ELEMENTS (video_els));
+  assert_num_in_track (audio_track, 2);
+  assert_num_in_track (video_track, 3);
+
+  /* group and ungroup should avoid track selection */
+  g_signal_connect (timeline, "select-tracks-for-object",
+      G_CALLBACK (_select_none), &selection_called);
 
   containers = ges_container_ungroup (GES_CONTAINER (clip), FALSE);
+
+  fail_if (selection_called);
+
+  video_clip = NULL;
+  audio_clip = NULL;
+
   assert_equals_int (g_list_length (containers), 2);
-  fail_unless (clip == containers->data);
-  assert_equals_int (g_list_length (GES_CONTAINER_CHILDREN (clip)), 1);
-  assert_equals_uint64 (_START (clip), 0);
-  assert_equals_uint64 (_INPOINT (clip), 0);
-  assert_equals_uint64 (_DURATION (clip), 10);
-  ASSERT_OBJECT_REFCOUNT (clip, "1 for the layer + 1 for the timeline + "
+
+  type = ges_clip_get_supported_formats (containers->data);
+  if (type == GES_TRACK_TYPE_VIDEO)
+    video_clip = containers->data;
+  if (type == GES_TRACK_TYPE_AUDIO)
+    audio_clip = containers->data;
+
+  type = ges_clip_get_supported_formats (containers->next->data);
+  if (type == GES_TRACK_TYPE_VIDEO)
+    video_clip = containers->next->data;
+  if (type == GES_TRACK_TYPE_AUDIO)
+    audio_clip = containers->next->data;
+
+  fail_unless (video_clip);
+  fail_unless (audio_clip);
+  fail_unless (video_clip == clip || audio_clip == clip);
+
+  assert_layer (video_clip, layer);
+  assert_num_children (video_clip, 3);
+  fail_unless (GES_TIMELINE_ELEMENT_TIMELINE (video_clip) == timeline);
+  CHECK_OBJECT_PROPS (video_clip, 0, 0, 10);
+  ASSERT_OBJECT_REFCOUNT (video_clip, "1 for the layer + 1 for the timeline + "
       "1 in containers list", 3);
 
-  clip2 = containers->next->data;
-  fail_if (clip2 == clip);
-  fail_unless (GES_TIMELINE_ELEMENT_TIMELINE (clip2) != NULL);
-  assert_equals_int (g_list_length (GES_CONTAINER_CHILDREN (clip2)), 1);
-  assert_equals_uint64 (_START (clip2), 0);
-  assert_equals_uint64 (_INPOINT (clip2), 0);
-  assert_equals_uint64 (_DURATION (clip2), 10);
-  ASSERT_OBJECT_REFCOUNT (clip2, "1 for the layer + 1 for the timeline +"
-      " 1 in containers list", 3);
-
-  tmp = ges_track_get_elements (audio_track);
-  assert_equals_int (g_list_length (tmp), 1);
-  ASSERT_OBJECT_REFCOUNT (tmp->data, "1 for the track + 1 for the container "
-      "+ 1 for the timeline + 1 in tmp list", 4);
-  assert_equals_int (ges_track_element_get_track_type (tmp->data),
-      GES_TRACK_TYPE_AUDIO);
-  assert_equals_int (ges_clip_get_supported_formats (GES_CLIP
-          (ges_timeline_element_get_parent (tmp->data))), GES_TRACK_TYPE_AUDIO);
-  g_list_free_full (tmp, gst_object_unref);
-  tmp = ges_track_get_elements (video_track);
-  assert_equals_int (g_list_length (tmp), 1);
-  ASSERT_OBJECT_REFCOUNT (tmp->data, "1 for the track + 1 for the container "
-      "+ 1 for the timeline + 1 in tmp list", 4);
-  assert_equals_int (ges_track_element_get_track_type (tmp->data),
-      GES_TRACK_TYPE_VIDEO);
-  assert_equals_int (ges_clip_get_supported_formats (GES_CLIP
-          (ges_timeline_element_get_parent (tmp->data))), GES_TRACK_TYPE_VIDEO);
-  g_list_free_full (tmp, gst_object_unref);
-
-  ges_timeline_element_set_start (GES_TIMELINE_ELEMENT (clip), 10);
-  assert_equals_int (g_list_length (GES_CONTAINER_CHILDREN (clip)), 1);
-  assert_equals_uint64 (_START (clip), 10);
-  assert_equals_uint64 (_INPOINT (clip), 0);
-  assert_equals_uint64 (_DURATION (clip), 10);
-  assert_equals_int (g_list_length (GES_CONTAINER_CHILDREN (clip2)), 1);
-  assert_equals_uint64 (_START (clip2), 0);
-  assert_equals_uint64 (_INPOINT (clip2), 0);
-  assert_equals_uint64 (_DURATION (clip2), 10);
+  assert_layer (audio_clip, layer);
+  assert_num_children (audio_clip, 2);
+  fail_unless (GES_TIMELINE_ELEMENT_TIMELINE (audio_clip) == timeline);
+  CHECK_OBJECT_PROPS (audio_clip, 0, 0, 10);
+  ASSERT_OBJECT_REFCOUNT (audio_clip, "1 for the layer + 1 for the timeline + "
+      "1 in containers list", 3);
 
-  regrouped_clip = ges_container_group (containers);
-  fail_unless (GES_IS_GROUP (regrouped_clip));
-  assert_equals_int (g_list_length (GES_CONTAINER_CHILDREN (regrouped_clip)),
-      2);
-  tmp = ges_container_ungroup (regrouped_clip, FALSE);
-  g_list_free_full (tmp, gst_object_unref);
+  for (i = 0; i < G_N_ELEMENTS (audio_els); i++) {
+    el = audio_els[i].element;
+    name = GES_TIMELINE_ELEMENT_NAME (el);
+    fail_unless (ges_track_element_get_track (el) == audio_track,
+        "%s not in audio track", name);
+    fail_unless (GES_TIMELINE_ELEMENT_PARENT (el) ==
+        GES_TIMELINE_ELEMENT (audio_clip), "%s not in the audio clip", name);
+    ASSERT_OBJECT_REFCOUNT (el,
+        "1 for the track + 1 for the container " "+ 1 for the timeline", 3);
+    _assert_higher_priority (el, audio_els[i].higher_priority);
+  }
+  for (i = 0; i < G_N_ELEMENTS (video_els); i++) {
+    el = video_els[i].element;
+    name = GES_TIMELINE_ELEMENT_NAME (el);
+    fail_unless (ges_track_element_get_track (el) == video_track,
+        "%s not in video track", name);
+    fail_unless (GES_TIMELINE_ELEMENT_PARENT (el) ==
+        GES_TIMELINE_ELEMENT (video_clip), "%s not in the video clip", name);
+    ASSERT_OBJECT_REFCOUNT (el,
+        "1 for the track + 1 for the container " "+ 1 for the timeline", 3);
+    _assert_higher_priority (el, video_els[i].higher_priority);
+  }
+  assert_num_in_track (audio_track, 2);
+  assert_num_in_track (video_track, 3);
+
+  ges_timeline_element_set_start (GES_TIMELINE_ELEMENT (video_clip), 10);
+  CHECK_OBJECT_PROPS (video_clip, 10, 0, 10);
+  CHECK_OBJECT_PROPS (audio_clip, 0, 0, 10);
+
+  _assert_regroup_fails (containers);
+
+  ges_timeline_element_set_start (GES_TIMELINE_ELEMENT (video_clip), 0);
+  ges_timeline_element_set_inpoint (GES_TIMELINE_ELEMENT (video_clip), 10);
+  CHECK_OBJECT_PROPS (video_clip, 0, 10, 10);
+  CHECK_OBJECT_PROPS (audio_clip, 0, 0, 10);
+
+  _assert_regroup_fails (containers);
+
+  ges_timeline_element_set_inpoint (GES_TIMELINE_ELEMENT (video_clip), 0);
+  ges_timeline_element_set_duration (GES_TIMELINE_ELEMENT (video_clip), 15);
+  CHECK_OBJECT_PROPS (video_clip, 0, 0, 15);
+  CHECK_OBJECT_PROPS (audio_clip, 0, 0, 10);
+
+  _assert_regroup_fails (containers);
+
+  ges_timeline_element_set_duration (GES_TIMELINE_ELEMENT (video_clip), 10);
+  CHECK_OBJECT_PROPS (video_clip, 0, 0, 10);
+  CHECK_OBJECT_PROPS (audio_clip, 0, 0, 10);
 
-  ges_timeline_element_set_start (GES_TIMELINE_ELEMENT (clip), 0);
   regrouped_clip = ges_container_group (containers);
+
+  fail_if (selection_called);
+
   assert_is_type (regrouped_clip, GES_TYPE_CLIP);
-  assert_equals_int (g_list_length (GES_CONTAINER_CHILDREN (regrouped_clip)),
-      2);
+  assert_num_children (regrouped_clip, 5);
   assert_equals_int (ges_clip_get_supported_formats (GES_CLIP (regrouped_clip)),
       GES_TRACK_TYPE_VIDEO | GES_TRACK_TYPE_AUDIO);
   g_list_free_full (containers, gst_object_unref);
 
-  GST_DEBUG ("Check clips in the layer");
-  tmp = ges_layer_get_clips (layer);
-  assert_equals_int (g_list_length (tmp), 1);
-  g_list_free_full (tmp, gst_object_unref);
-
-  GST_DEBUG ("Check TrackElement in audio track");
-  tmp = ges_track_get_elements (audio_track);
-  assert_equals_int (g_list_length (tmp), 1);
-  assert_equals_int (ges_track_element_get_track_type (tmp->data),
-      GES_TRACK_TYPE_AUDIO);
-  fail_unless (GES_CONTAINER (ges_timeline_element_get_parent (tmp->data)) ==
-      regrouped_clip);
-  g_list_free_full (tmp, gst_object_unref);
-
-  GST_DEBUG ("Check TrackElement in video track");
-  tmp = ges_track_get_elements (video_track);
-  assert_equals_int (g_list_length (tmp), 1);
-  ASSERT_OBJECT_REFCOUNT (tmp->data, "1 for the track + 1 for the container "
-      "+ 1 for the timeline + 1 in tmp list", 4);
-  assert_equals_int (ges_track_element_get_track_type (tmp->data),
-      GES_TRACK_TYPE_VIDEO);
-  fail_unless (GES_CONTAINER (ges_timeline_element_get_parent (tmp->data)) ==
-      regrouped_clip);
-  g_list_free_full (tmp, gst_object_unref);
+  assert_layer (regrouped_clip, layer);
+
+  for (i = 0; i < G_N_ELEMENTS (audio_els); i++) {
+    el = audio_els[i].element;
+    name = GES_TIMELINE_ELEMENT_NAME (el);
+    fail_unless (ges_track_element_get_track (el) == audio_track,
+        "%s not in audio track", name);
+    fail_unless (GES_TIMELINE_ELEMENT_PARENT (el) ==
+        GES_TIMELINE_ELEMENT (regrouped_clip), "%s not in the regrouped clip",
+        name);
+    ASSERT_OBJECT_REFCOUNT (el,
+        "1 for the track + 1 for the container " "+ 1 for the timeline", 3);
+    _assert_higher_priority (el, audio_els[i].higher_priority);
+  }
+  for (i = 0; i < G_N_ELEMENTS (video_els); i++) {
+    el = video_els[i].element;
+    name = GES_TIMELINE_ELEMENT_NAME (el);
+    fail_unless (ges_track_element_get_track (el) == video_track,
+        "%s not in video track", name);
+    fail_unless (GES_TIMELINE_ELEMENT_PARENT (el) ==
+        GES_TIMELINE_ELEMENT (regrouped_clip), "%s not in the regrouped clip",
+        name);
+    ASSERT_OBJECT_REFCOUNT (el,
+        "1 for the track + 1 for the container " "+ 1 for the timeline", 3);
+    _assert_higher_priority (el, video_els[i].higher_priority);
+  }
+  assert_num_in_track (audio_track, 2);
+  assert_num_in_track (video_track, 3);
 
   gst_object_unref (timeline);
 
@@ -523,12 +766,324 @@ GST_START_TEST (test_clip_group_ungroup)
 
 GST_END_TEST;
 
+GST_START_TEST (test_adding_children_to_track)
+{
+  GESTimeline *timeline;
+  GESLayer *layer;
+  GESTrack *track1, *track2;
+  GESClip *clip, *clip2;
+  GESAsset *asset;
+  GESTrackElement *source, *effect, *effect2, *added, *added2, *added3;
+  GstControlSource *ctrl_source;
+  guint selection_called = 0;
+
+  ges_init ();
+
+  timeline = ges_timeline_new ();
+  ges_timeline_set_auto_transition (timeline, TRUE);
+  track1 = GES_TRACK (ges_video_track_new ());
+  track2 = GES_TRACK (ges_video_track_new ());
+
+
+  /* only add two for now */
+  fail_unless (ges_timeline_add_track (timeline, track1));
+
+  layer = ges_timeline_append_layer (timeline);
+
+  asset = ges_asset_request (GES_TYPE_TEST_CLIP, NULL, NULL);
+
+  clip = ges_layer_add_asset (layer, asset, 0, 0, 10, GES_TRACK_TYPE_UNKNOWN);
+  fail_unless (clip);
+  assert_num_children (clip, 1);
+  assert_num_in_track (track1, 1);
+  assert_num_in_track (track2, 0);
+  source = GES_CONTAINER_CHILDREN (clip)->data;
+  fail_unless (ges_track_element_get_track (source) == track1);
+
+  effect = GES_TRACK_ELEMENT (ges_effect_new ("agingtv"));
+  fail_unless (ges_container_add (GES_CONTAINER (clip),
+          GES_TIMELINE_ELEMENT (effect)));
+  effect2 = GES_TRACK_ELEMENT (ges_effect_new ("vertigotv"));
+  fail_unless (ges_container_add (GES_CONTAINER (clip),
+          GES_TIMELINE_ELEMENT (effect2)));
+  assert_num_children (clip, 3);
+  assert_num_in_track (track1, 3);
+  assert_num_in_track (track2, 0);
+  fail_unless (ges_track_element_get_track (effect) == track1);
+  fail_unless (ges_track_element_get_track (effect2) == track1);
+
+  ges_timeline_element_set_child_properties (GES_TIMELINE_ELEMENT (clip),
+      "font-desc", "Normal", "posx", 30, "posy", 50, "alpha", 0.1,
+      "freq", 449.0, "scratch-lines", 2, NULL);
+
+  ctrl_source = GST_CONTROL_SOURCE (gst_interpolation_control_source_new ());
+  g_object_set (G_OBJECT (ctrl_source), "mode",
+      GST_INTERPOLATION_MODE_CUBIC, NULL);
+  fail_unless (gst_timed_value_control_source_set
+      (GST_TIMED_VALUE_CONTROL_SOURCE (ctrl_source), 0, 20.0));
+  fail_unless (gst_timed_value_control_source_set
+      (GST_TIMED_VALUE_CONTROL_SOURCE (ctrl_source), 5, 45.0));
+  fail_unless (ges_track_element_set_control_source (source, ctrl_source,
+          "posx", "direct-absolute"));
+  gst_object_unref (ctrl_source);
+
+  ctrl_source = GST_CONTROL_SOURCE (gst_interpolation_control_source_new ());
+  g_object_set (G_OBJECT (ctrl_source), "mode",
+      GST_INTERPOLATION_MODE_LINEAR, NULL);
+  fail_unless (gst_timed_value_control_source_set
+      (GST_TIMED_VALUE_CONTROL_SOURCE (ctrl_source), 2, 0.1));
+  fail_unless (gst_timed_value_control_source_set
+      (GST_TIMED_VALUE_CONTROL_SOURCE (ctrl_source), 5, 0.7));
+  fail_unless (gst_timed_value_control_source_set
+      (GST_TIMED_VALUE_CONTROL_SOURCE (ctrl_source), 8, 0.3));
+  fail_unless (ges_track_element_set_control_source (source, ctrl_source,
+          "alpha", "direct"));
+  gst_object_unref (ctrl_source);
+
+  ctrl_source = GST_CONTROL_SOURCE (gst_interpolation_control_source_new ());
+  g_object_set (G_OBJECT (ctrl_source), "mode",
+      GST_INTERPOLATION_MODE_NONE, NULL);
+  fail_unless (gst_timed_value_control_source_set
+      (GST_TIMED_VALUE_CONTROL_SOURCE (ctrl_source), 0, 1.0));
+  fail_unless (gst_timed_value_control_source_set
+      (GST_TIMED_VALUE_CONTROL_SOURCE (ctrl_source), 4, 7.0));
+  fail_unless (gst_timed_value_control_source_set
+      (GST_TIMED_VALUE_CONTROL_SOURCE (ctrl_source), 8, 3.0));
+  fail_unless (ges_track_element_set_control_source (effect, ctrl_source,
+          "scratch-lines", "direct-absolute"));
+  gst_object_unref (ctrl_source);
+
+  /* can't add to a track that does not belong to the timeline */
+  fail_if (ges_clip_add_child_to_track (clip, source, track2, NULL));
+  assert_num_children (clip, 3);
+  fail_unless (ges_track_element_get_track (source) == track1);
+  assert_num_in_track (track1, 3);
+  assert_num_in_track (track2, 0);
+
+  /* can't add the clip to a track that already contains our source */
+  fail_if (ges_clip_add_child_to_track (clip, source, track1, NULL));
+  assert_num_children (clip, 3);
+  fail_unless (ges_track_element_get_track (source) == track1);
+  assert_num_in_track (track1, 3);
+  assert_num_in_track (track2, 0);
+
+  /* can't remove a core element from its track whilst a non-core sits
+   * above it */
+  fail_if (ges_track_remove_element (track1, source));
+  assert_num_children (clip, 3);
+  fail_unless (ges_track_element_get_track (source) == track1);
+  assert_num_in_track (track1, 3);
+  assert_num_in_track (track2, 0);
+
+  /* can not add to the same track as it is currently in */
+  fail_if (ges_clip_add_child_to_track (clip, effect, track1, NULL));
+  fail_unless (ges_track_element_get_track (effect) == track1);
+  assert_num_in_track (track1, 3);
+  assert_num_in_track (track2, 0);
+
+  /* adding another video track, select-tracks-for-object will do nothing
+   * since no each track element is already part of a track */
+  fail_unless (ges_timeline_add_track (timeline, track2));
+  assert_num_children (clip, 3);
+  assert_num_in_track (track1, 3);
+  assert_num_in_track (track2, 0);
+
+  /* can not add effect to a track that does not contain a core child */
+  fail_if (ges_clip_add_child_to_track (clip, effect, track2, NULL));
+  assert_num_children (clip, 3);
+  assert_num_in_track (track1, 3);
+  assert_num_in_track (track2, 0);
+
+  /* can add core */
+
+  added = ges_clip_add_child_to_track (clip, source, track2, NULL);
+  fail_unless (added);
+  assert_num_children (clip, 4);
+  fail_unless (added != source);
+  fail_unless (ges_track_element_get_track (source) == track1);
+  fail_unless (ges_track_element_get_track (added) == track2);
+  assert_num_in_track (track1, 3);
+  assert_num_in_track (track2, 1);
+
+  assert_equal_children_properties (added, source);
+  assert_equal_bindings (added, source);
+
+  /* can now add non-core */
+  assert_equals_int (0,
+      ges_clip_get_top_effect_index (clip, GES_BASE_EFFECT (effect)));
+  assert_equals_int (1,
+      ges_clip_get_top_effect_index (clip, GES_BASE_EFFECT (effect2)));
+
+  added2 = ges_clip_add_child_to_track (clip, effect, track2, NULL);
+  fail_unless (added2);
+  assert_num_children (clip, 5);
+  fail_unless (added2 != effect);
+  fail_unless (ges_track_element_get_track (effect) == track1);
+  fail_unless (ges_track_element_get_track (added2) == track2);
+  assert_num_in_track (track1, 3);
+  assert_num_in_track (track2, 2);
+
+  assert_equal_children_properties (added2, effect);
+  assert_equal_bindings (added2, effect);
+
+  assert_equals_int (0,
+      ges_clip_get_top_effect_index (clip, GES_BASE_EFFECT (effect)));
+  assert_equals_int (1,
+      ges_clip_get_top_effect_index (clip, GES_BASE_EFFECT (added2)));
+  assert_equals_int (2,
+      ges_clip_get_top_effect_index (clip, GES_BASE_EFFECT (effect2)));
+
+  added3 = ges_clip_add_child_to_track (clip, effect2, track2, NULL);
+  fail_unless (added3);
+  assert_num_children (clip, 6);
+  fail_unless (added3 != effect2);
+  fail_unless (ges_track_element_get_track (effect2) == track1);
+  fail_unless (ges_track_element_get_track (added3) == track2);
+  assert_num_in_track (track1, 3);
+  assert_num_in_track (track2, 3);
+
+  assert_equal_children_properties (added3, effect2);
+  assert_equal_bindings (added3, effect2);
+
+  /* priorities within new track match that in previous track! */
+  assert_equals_int (0,
+      ges_clip_get_top_effect_index (clip, GES_BASE_EFFECT (effect)));
+  assert_equals_int (1,
+      ges_clip_get_top_effect_index (clip, GES_BASE_EFFECT (added2)));
+  assert_equals_int (2,
+      ges_clip_get_top_effect_index (clip, GES_BASE_EFFECT (effect2)));
+  assert_equals_int (3,
+      ges_clip_get_top_effect_index (clip, GES_BASE_EFFECT (added3)));
+
+  /* removing core from the container, empties the non-core from their
+   * tracks */
+  gst_object_ref (added);
+  fail_unless (ges_container_remove (GES_CONTAINER (clip),
+          GES_TIMELINE_ELEMENT (added)));
+  assert_num_children (clip, 5);
+  fail_unless (ges_track_element_get_track (source) == track1);
+  fail_if (ges_track_element_get_track (added));
+  fail_if (ges_track_element_get_track (added2));
+  fail_unless (GES_TIMELINE_ELEMENT_PARENT (added) == NULL);
+  fail_unless (GES_TIMELINE_ELEMENT_PARENT (added2) ==
+      GES_TIMELINE_ELEMENT (clip));
+  assert_num_in_track (track1, 3);
+  assert_num_in_track (track2, 0);
+  gst_object_unref (added);
+
+  fail_unless (ges_container_remove (GES_CONTAINER (clip),
+          GES_TIMELINE_ELEMENT (added2)));
+  fail_unless (ges_container_remove (GES_CONTAINER (clip),
+          GES_TIMELINE_ELEMENT (added3)));
+  assert_num_children (clip, 3);
+  assert_num_in_track (track1, 3);
+  assert_num_in_track (track2, 0);
+
+  /* remove from layer empties all children from the tracks */
+  gst_object_ref (clip);
+
+  fail_unless (ges_layer_remove_clip (layer, clip));
+  assert_num_children (clip, 3);
+  fail_if (ges_track_element_get_track (source));
+  fail_if (ges_track_element_get_track (effect));
+  assert_num_in_track (track1, 0);
+  assert_num_in_track (track2, 0);
+
+  /* add different sources to the layer */
+  fail_unless (ges_layer_add_asset (layer, asset, 0, 0, 10,
+          GES_TRACK_TYPE_UNKNOWN));
+  fail_unless (ges_layer_add_asset (layer, asset, 20, 0, 10,
+          GES_TRACK_TYPE_UNKNOWN));
+  fail_unless (clip2 = ges_layer_add_asset (layer, asset, 25, 0, 10,
+          GES_TRACK_TYPE_UNKNOWN));
+  assert_num_children (clip2, 2);
+  /* 3 sources + 1 transition */
+  assert_num_in_track (track1, 4);
+  assert_num_in_track (track2, 4);
+
+  /* removing the track from the timeline empties it of track elements */
+  gst_object_ref (track2);
+  fail_unless (ges_timeline_remove_track (timeline, track2));
+  /* but children remain in the clips */
+  assert_num_children (clip2, 2);
+  assert_num_in_track (track1, 4);
+  assert_num_in_track (track2, 0);
+  gst_object_unref (track2);
+
+  /* add clip back in, but don't select any tracks */
+  g_signal_connect (timeline, "select-tracks-for-object",
+      G_CALLBACK (_select_none), &selection_called);
+
+  /* can add the clip to the layer, despite a source existing between
+   * 0 and 10 because the clip will not fill any track */
+  /* NOTE: normally this would be useless because it would not trigger
+   * the creation of any core children. But clip currently still has
+   * its core children */
+  fail_unless (ges_layer_add_clip (layer, clip));
+  gst_object_unref (clip);
+
+  /* one call for each child */
+  assert_equals_int (selection_called, 3);
+
+  fail_if (ges_track_element_get_track (source));
+  fail_if (ges_track_element_get_track (effect));
+  assert_num_children (clip, 3);
+  assert_num_in_track (track1, 4);
+
+  /* can not add the source to the track because it would overlap another
+   * source */
+  fail_if (ges_clip_add_child_to_track (clip, source, track1, NULL));
+  assert_num_children (clip, 3);
+  assert_num_in_track (track1, 4);
+
+  /* can not add source at time 23 because it would result in three
+   * overlapping sources in the track */
+  fail_unless (ges_timeline_element_set_start (GES_TIMELINE_ELEMENT (clip),
+          23));
+  fail_if (ges_clip_add_child_to_track (clip, source, track1, NULL));
+  assert_num_children (clip, 3);
+  assert_num_in_track (track1, 4);
+
+  /* can add at 5, with overlap */
+  fail_unless (ges_timeline_element_set_start (GES_TIMELINE_ELEMENT (clip), 5));
+  added = ges_clip_add_child_to_track (clip, source, track1, NULL);
+  /* added is the source since it was not already in a track */
+  fail_unless (added == source);
+  assert_num_children (clip, 3);
+  /* 4 sources + 2 transitions */
+  assert_num_in_track (track1, 6);
+
+  /* also add effect */
+  added = ges_clip_add_child_to_track (clip, effect, track1, NULL);
+  /* added is the source since it was not already in a track */
+  fail_unless (added == effect);
+  assert_num_children (clip, 3);
+  assert_num_in_track (track1, 7);
+
+  added = ges_clip_add_child_to_track (clip, effect2, track1, NULL);
+  /* added is the source since it was not already in a track */
+  fail_unless (added == effect2);
+  assert_num_children (clip, 3);
+  assert_num_in_track (track1, 8);
+
+  assert_equals_int (0,
+      ges_clip_get_top_effect_index (clip, GES_BASE_EFFECT (effect)));
+  assert_equals_int (1,
+      ges_clip_get_top_effect_index (clip, GES_BASE_EFFECT (effect2)));
+
+  gst_object_unref (timeline);
+
+  ges_deinit ();
+}
+
+GST_END_TEST;
 
 static void
 child_removed_cb (GESClip * clip, GESTimelineElement * effect,
     gboolean * called)
 {
-  ASSERT_OBJECT_REFCOUNT (effect, "1 keeping alive ref + emission ref", 2);
+  ASSERT_OBJECT_REFCOUNT (effect, "1 test ref + 1 keeping alive ref + "
+      "emission ref", 3);
   *called = TRUE;
 }
 
@@ -537,30 +1092,52 @@ GST_START_TEST (test_clip_refcount_remove_child)
   GESClip *clip;
   GESTrack *track;
   gboolean called;
-  GESTrackElement *effect;
+  GESTrackElement *effect, *source;
+  GESTimeline *timeline;
+  GESLayer *layer;
 
   ges_init ();
 
-  clip = GES_CLIP (ges_test_clip_new ());
+  timeline = ges_timeline_new ();
   track = GES_TRACK (ges_audio_track_new ());
-  effect = GES_TRACK_ELEMENT (ges_effect_new ("identity"));
+  fail_unless (ges_timeline_add_track (timeline, track));
+
+  layer = ges_timeline_append_layer (timeline);
+  clip = GES_CLIP (ges_test_clip_new ());
+  fail_unless (ges_layer_add_clip (layer, clip));
+
+  assert_num_children (clip, 1);
+  assert_num_in_track (track, 1);
 
+  source = GES_CONTAINER_CHILDREN (clip)->data;
+  ASSERT_OBJECT_REFCOUNT (source, "1 for the container + 1 for the track"
+      " + 1 timeline", 3);
+
+  effect = GES_TRACK_ELEMENT (ges_effect_new ("identity"));
   fail_unless (ges_track_add_element (track, effect));
+  assert_num_in_track (track, 2);
+  ASSERT_OBJECT_REFCOUNT (effect, "1 for the track + 1 timeline", 2);
+
   fail_unless (ges_container_add (GES_CONTAINER (clip),
           GES_TIMELINE_ELEMENT (effect)));
-  ASSERT_OBJECT_REFCOUNT (effect, "1 for the container + 1 for the track", 2);
+  assert_num_children (clip, 2);
+  ASSERT_OBJECT_REFCOUNT (effect, "1 for the container + 1 for the track"
+      " + 1 timeline", 3);
 
   fail_unless (ges_track_remove_element (track, effect));
   ASSERT_OBJECT_REFCOUNT (effect, "1 for the container", 1);
 
   g_signal_connect (clip, "child-removed", G_CALLBACK (child_removed_cb),
       &called);
+  gst_object_ref (effect);
   fail_unless (ges_container_remove (GES_CONTAINER (clip),
           GES_TIMELINE_ELEMENT (effect)));
   fail_unless (called == TRUE);
+  ASSERT_OBJECT_REFCOUNT (effect, "1 test ref", 1);
+  gst_object_unref (effect);
 
-  check_destroyed (G_OBJECT (track), NULL, NULL);
-  check_destroyed (G_OBJECT (clip), NULL, NULL);
+  check_destroyed (G_OBJECT (timeline), G_OBJECT (track),
+      G_OBJECT (layer), G_OBJECT (clip), G_OBJECT (source), NULL);
 
   ges_deinit ();
 }
@@ -572,13 +1149,14 @@ GST_START_TEST (test_clip_find_track_element)
   GESClip *clip;
   GList *foundelements;
   GESTimeline *timeline;
+  GESLayer *layer;
   GESTrack *track, *track1, *track2;
+  guint selection_called = 0;
 
-  GESTrackElement *effect, *effect1, *effect2, *foundelem;
+  GESTrackElement *effect, *effect1, *effect2, *foundelem, *video_source;
 
   ges_init ();
 
-  clip = GES_CLIP (ges_test_clip_new ());
   track = GES_TRACK (ges_audio_track_new ());
   track1 = GES_TRACK (ges_audio_track_new ());
   track2 = GES_TRACK (ges_video_track_new ());
@@ -588,11 +1166,18 @@ GST_START_TEST (test_clip_find_track_element)
   fail_unless (ges_timeline_add_track (timeline, track1));
   fail_unless (ges_timeline_add_track (timeline, track2));
 
-  /* need to register the clip with the timeline */
-  /* FIXME: we should make the clip part of a layer, but the current
-   * default select-tracks-for-object signal is broken for multiple
-   * tracks. In fact, we should be using this signal in this test */
-  ges_timeline_element_set_timeline (GES_TIMELINE_ELEMENT (clip), timeline);
+  layer = ges_timeline_append_layer (timeline);
+  clip = GES_CLIP (ges_test_clip_new ());
+
+  /* should have a source in every track */
+  fail_unless (ges_layer_add_clip (layer, clip));
+  assert_num_children (clip, 3);
+  assert_num_in_track (track, 1);
+  assert_num_in_track (track1, 1);
+  assert_num_in_track (track2, 1);
+
+  g_signal_connect (timeline, "select-tracks-for-object",
+      G_CALLBACK (_select_none), &selection_called);
 
   effect = GES_TRACK_ELEMENT (ges_effect_new ("identity"));
   fail_unless (ges_track_add_element (track, effect));
@@ -609,29 +1194,111 @@ GST_START_TEST (test_clip_find_track_element)
   fail_unless (ges_container_add (GES_CONTAINER (clip),
           GES_TIMELINE_ELEMENT (effect2)));
 
-  foundelem = ges_clip_find_track_element (clip, track, G_TYPE_NONE);
+  fail_if (selection_called);
+  assert_num_children (clip, 6);
+  assert_num_in_track (track, 2);
+  assert_num_in_track (track1, 2);
+  assert_num_in_track (track2, 2);
+
+  foundelem = ges_clip_find_track_element (clip, track, GES_TYPE_EFFECT);
   fail_unless (foundelem == effect);
   gst_object_unref (foundelem);
 
-  foundelem = ges_clip_find_track_element (clip, NULL, GES_TYPE_SOURCE);
+  foundelem = ges_clip_find_track_element (clip, track1, GES_TYPE_EFFECT);
+  fail_unless (foundelem == effect1);
+  gst_object_unref (foundelem);
+
+  foundelem = ges_clip_find_track_element (clip, track2, GES_TYPE_EFFECT);
+  fail_unless (foundelem == effect2);
+  gst_object_unref (foundelem);
+
+  foundelem = ges_clip_find_track_element (clip, NULL, GES_TYPE_TRANSITION);
+  fail_unless (foundelem == NULL);
+
+  foundelem = ges_clip_find_track_element (clip, track, GES_TYPE_TRANSITION);
+  fail_unless (foundelem == NULL);
+
+  foundelem = ges_clip_find_track_element (clip, track1, GES_TYPE_TRANSITION);
+  fail_unless (foundelem == NULL);
+
+  foundelem = ges_clip_find_track_element (clip, track2, GES_TYPE_TRANSITION);
   fail_unless (foundelem == NULL);
 
+  foundelem = ges_clip_find_track_element (clip, track, GES_TYPE_SOURCE);
+  fail_unless (GES_IS_AUDIO_TEST_SOURCE (foundelem));
+  gst_object_unref (foundelem);
+
+  foundelem = ges_clip_find_track_element (clip, track1, GES_TYPE_SOURCE);
+  fail_unless (GES_IS_AUDIO_TEST_SOURCE (foundelem));
+  gst_object_unref (foundelem);
+
+  foundelem = ges_clip_find_track_element (clip, track2, GES_TYPE_SOURCE);
+  fail_unless (GES_IS_VIDEO_TEST_SOURCE (foundelem));
+  gst_object_unref (foundelem);
+
+  video_source = ges_clip_find_track_element (clip, NULL,
+      GES_TYPE_VIDEO_TEST_SOURCE);
+  fail_unless (foundelem == video_source);
+  gst_object_unref (video_source);
+
+
   foundelements = ges_clip_find_track_elements (clip, NULL,
       GES_TRACK_TYPE_AUDIO, G_TYPE_NONE);
-  fail_unless_equals_int (g_list_length (foundelements), 2);
+  fail_unless_equals_int (g_list_length (foundelements), 4);
   g_list_free_full (foundelements, gst_object_unref);
 
   foundelements = ges_clip_find_track_elements (clip, NULL,
       GES_TRACK_TYPE_VIDEO, G_TYPE_NONE);
+  fail_unless_equals_int (g_list_length (foundelements), 2);
+  g_list_free_full (foundelements, gst_object_unref);
+
+  foundelements = ges_clip_find_track_elements (clip, NULL,
+      GES_TRACK_TYPE_UNKNOWN, GES_TYPE_SOURCE);
+  fail_unless_equals_int (g_list_length (foundelements), 3);
+  fail_unless (g_list_find (foundelements, video_source));
+  g_list_free_full (foundelements, gst_object_unref);
+
+  foundelements = ges_clip_find_track_elements (clip, NULL,
+      GES_TRACK_TYPE_UNKNOWN, GES_TYPE_EFFECT);
+  fail_unless_equals_int (g_list_length (foundelements), 3);
+  fail_unless (g_list_find (foundelements, effect));
+  fail_unless (g_list_find (foundelements, effect1));
+  fail_unless (g_list_find (foundelements, effect2));
+  g_list_free_full (foundelements, gst_object_unref);
+
+  foundelements = ges_clip_find_track_elements (clip, NULL,
+      GES_TRACK_TYPE_VIDEO, GES_TYPE_SOURCE);
+  fail_unless_equals_int (g_list_length (foundelements), 1);
+  fail_unless (foundelements->data == video_source);
+  g_list_free_full (foundelements, gst_object_unref);
+
+  foundelements = ges_clip_find_track_elements (clip, track2,
+      GES_TRACK_TYPE_UNKNOWN, GES_TYPE_SOURCE);
+  fail_unless_equals_int (g_list_length (foundelements), 1);
+  fail_unless (foundelements->data == video_source);
+  g_list_free_full (foundelements, gst_object_unref);
+
+  foundelements = ges_clip_find_track_elements (clip, track2,
+      GES_TRACK_TYPE_UNKNOWN, G_TYPE_NONE);
+  fail_unless_equals_int (g_list_length (foundelements), 2);
+  fail_unless (g_list_find (foundelements, effect2));
+  fail_unless (g_list_find (foundelements, video_source));
+  g_list_free_full (foundelements, gst_object_unref);
+
+  foundelements = ges_clip_find_track_elements (clip, track1,
+      GES_TRACK_TYPE_UNKNOWN, GES_TYPE_EFFECT);
   fail_unless_equals_int (g_list_length (foundelements), 1);
+  fail_unless (foundelements->data == effect1);
   g_list_free_full (foundelements, gst_object_unref);
 
+  /* NOTE: search in *either* track or track type
+   * TODO 2.0: this should be an AND condition, rather than OR */
   foundelements = ges_clip_find_track_elements (clip, track,
       GES_TRACK_TYPE_VIDEO, G_TYPE_NONE);
-  fail_unless_equals_int (g_list_length (foundelements), 2);
-  fail_unless (g_list_find (foundelements, effect2) != NULL,
-      "In the video track");
-  fail_unless (g_list_find (foundelements, effect2) != NULL, "In 'track'");
+  fail_unless_equals_int (g_list_length (foundelements), 4);
+  fail_unless (g_list_find (foundelements, effect));
+  fail_unless (g_list_find (foundelements, effect2));
+  fail_unless (g_list_find (foundelements, video_source));
   g_list_free_full (foundelements, gst_object_unref);
 
   gst_object_unref (timeline);
@@ -1736,9 +2403,10 @@ _el_with_child_prop (GESTimelineElement * clip, GObject * prop_child,
     if (ges_timeline_element_lookup_child (tmp->data, prop->name,
             &found_child, &found_prop)) {
       if (found_child == prop_child && found_prop == prop) {
+        g_param_spec_unref (found_prop);
+        g_object_unref (found_child);
         child = tmp->data;
-        /* break early, but still free */
-        tmp->next = NULL;
+        break;
       }
       g_param_spec_unref (found_prop);
       g_object_unref (found_child);
@@ -1802,13 +2470,12 @@ GST_START_TEST (test_copy_paste_children_properties)
   GESLayer *layer;
   GESTimelineElement *clip, *copy, *pasted, *track_el, *pasted_el;
   GObject *sub_child, *pasted_sub_child;
-  GParamSpec **orig_props, **pasted_props, **track_el_props, **pasted_el_props;
-  guint num_orig_props, num_pasted_props, num_track_el_props,
-      num_pasted_el_props;
+  GParamSpec **orig_props;
+  guint num_orig_props;
   GParamSpec *prop, *found_prop;
   GValue val = G_VALUE_INIT;
-  GSList *timed_vals;
   GstControlSource *source;
+  GSList *timed_vals;
 
   ges_init ();
 
@@ -1824,24 +2491,20 @@ GST_START_TEST (test_copy_paste_children_properties)
       ges_timeline_element_list_children_properties (clip, &num_orig_props);
   fail_unless (num_orig_props);
 
+  /* font-desc is originally "", but on setting switches to Normal, so we
+   * set it explicitly */
+  ges_timeline_element_set_child_properties (clip, "font-desc", "Normal",
+      "posx", 30, "posy", 50, "alpha", 0.1, "freq", 449.0, NULL);
+
   /* focus on one property */
   fail_unless (ges_timeline_element_lookup_child (clip, "posx",
           &sub_child, &prop));
-  g_value_init (&val, G_TYPE_INT);
-  g_value_set_int (&val, 30);
-  fail_unless (ges_timeline_element_set_child_property (clip, "posx", &val));
-  g_value_unset (&val);
-
   _assert_int_val_child_prop (clip, val, 30, prop, "posx");
 
   /* find the track element where the child property comes from */
   fail_unless (track_el = _el_with_child_prop (clip, sub_child, prop));
   _assert_int_val_child_prop (track_el, val, 30, prop, "posx");
 
-  track_el_props =
-      ges_timeline_element_list_children_properties (track_el,
-      &num_track_el_props);
-
   /* set a control binding */
   timed_vals = g_slist_prepend (NULL, _new_timed_value (200, 5));
   timed_vals = g_slist_prepend (timed_vals, _new_timed_value (40, 50));
@@ -1855,6 +2518,7 @@ GST_START_TEST (test_copy_paste_children_properties)
 
   fail_unless (ges_track_element_set_control_source (GES_TRACK_ELEMENT
           (track_el), source, "posx", "direct-absolute"));
+
   g_object_unref (source);
 
   /* check the control binding */
@@ -1866,13 +2530,10 @@ GST_START_TEST (test_copy_paste_children_properties)
   fail_unless (pasted = ges_timeline_element_paste (copy, 30));
 
   gst_object_unref (copy);
+  gst_object_unref (pasted);
 
   /* test that the new clip has the same child properties */
-  pasted_props =
-      ges_timeline_element_list_children_properties (pasted, &num_pasted_props);
-
-  assert_property_list_match (pasted_props, num_pasted_props,
-      orig_props, num_orig_props);
+  assert_equal_children_properties (clip, pasted);
 
   /* get the details for the copied 'prop' property */
   fail_unless (ges_timeline_element_lookup_child (pasted,
@@ -1888,25 +2549,18 @@ GST_START_TEST (test_copy_paste_children_properties)
       _el_with_child_prop (pasted, pasted_sub_child, prop));
   _assert_int_val_child_prop (pasted_el, val, 30, prop, "posx");
 
-  pasted_el_props =
-      ges_timeline_element_list_children_properties (pasted_el,
-      &num_pasted_el_props);
-
-  assert_property_list_match (pasted_el_props, num_pasted_el_props,
-      track_el_props, num_track_el_props);
+  assert_equal_children_properties (track_el, pasted_el);
 
   /* check the control binding on the pasted element */
   _assert_binding (pasted_el, "posx", pasted_sub_child, timed_vals,
       GST_INTERPOLATION_MODE_CUBIC);
 
+  assert_equal_bindings (pasted_el, track_el);
 
   /* free */
   g_slist_free_full (timed_vals, g_free);
 
-  free_children_properties (pasted_props, num_pasted_props);
   free_children_properties (orig_props, num_orig_props);
-  free_children_properties (pasted_el_props, num_pasted_el_props);
-  free_children_properties (track_el_props, num_track_el_props);
 
   g_param_spec_unref (prop);
   g_object_unref (pasted_sub_child);
@@ -1933,6 +2587,7 @@ ges_suite (void)
   tcase_add_test (tc_chain, test_split_direct_bindings);
   tcase_add_test (tc_chain, test_split_direct_absolute_bindings);
   tcase_add_test (tc_chain, test_clip_group_ungroup);
+  tcase_add_test (tc_chain, test_adding_children_to_track);
   tcase_add_test (tc_chain, test_clip_refcount_remove_child);
   tcase_add_test (tc_chain, test_clip_find_track_element);
   tcase_add_test (tc_chain, test_effects_priorities);
index 1dc7057..8208361 100644 (file)
@@ -106,6 +106,23 @@ G_STMT_START {                                          \
       GST_TIME_ARGS (_MAX_DURATION(obj)), GST_TIME_ARGS (max_duration)); \
 }
 
+#define assert_num_in_track(track, val) \
+{ \
+  GList *tmp = ges_track_get_elements (track); \
+  guint length = g_list_length (tmp); \
+  fail_unless (length == val, "Track %" GST_PTR_FORMAT \
+      " contains %u track elements, rather than %u", track, length, val); \
+  g_list_free_full (tmp, gst_object_unref); \
+}
+
+#define assert_num_children(clip, cmp) \
+{ \
+  guint num_children = g_list_length (GES_CONTAINER_CHILDREN (clip)); \
+  fail_unless (cmp == num_children, \
+      "clip %s contains %u children rather than %u", \
+      GES_TIMELINE_ELEMENT_NAME (clip), num_children, cmp); \
+}
+
 /* assert that the time property (start, duration or in-point) is the
  * same as @cmp for the clip and all its children */
 #define assert_clip_children_time_val(clip, property, cmp) \
@@ -142,6 +159,27 @@ G_STMT_START {                                          \
     GES_TIMELINE_ELEMENT_LAYER_PRIORITY (clip), layer_prio);                 \
 }
 
+#define assert_layer(clip, layer) \
+{ \
+  GESLayer *tmp_layer = ges_clip_get_layer (GES_CLIP (clip)); \
+  fail_unless (tmp_layer == GES_LAYER (layer), "clip %s belongs to " \
+      "layer %u (timeline %" GST_PTR_FORMAT ") rather than layer %u " \
+      "(timeline %" GST_PTR_FORMAT ")", \
+      tmp_layer ? ges_layer_get_priority (tmp_layer) : 0, \
+      tmp_layer ? tmp_layer->timeline : NULL, \
+      layer ? ges_layer_get_priority (GES_LAYER (layer)) : 0, \
+      layer ? GES_LAYER (layer)->timeline : NULL); \
+  if (tmp_layer) \
+    gst_object_unref (tmp_layer); \
+  if (layer) { \
+    GList *layer_clips = ges_layer_get_clips (GES_LAYER (layer)); \
+    fail_unless (g_list_find (layer_clips, clip), "clip %s not found " \
+        "in layer %u (timeline %" GST_PTR_FORMAT ")", \
+        ges_layer_get_priority (GES_LAYER (layer)), layer->timeline); \
+    g_list_free_full (layer_clips, gst_object_unref); \
+  } \
+}
+
 /* test that the two property lists contain the same properties the same
  * number of times */
 #define assert_property_list_match(list1, len1, list2, len2) \
@@ -188,5 +226,144 @@ G_STMT_START {                                          \
     g_free (count_list1); \
   }
 
+#define assert_equal_children_properties(el1, el2) \
+{ \
+  guint i, num1, num2; \
+  const gchar *name1 = GES_TIMELINE_ELEMENT_NAME (el1); \
+  const gchar *name2 = GES_TIMELINE_ELEMENT_NAME (el2); \
+  GParamSpec **el_props1 = ges_timeline_element_list_children_properties ( \
+      GES_TIMELINE_ELEMENT (el1), &num1); \
+  GParamSpec **el_props2 = ges_timeline_element_list_children_properties ( \
+      GES_TIMELINE_ELEMENT (el2), &num2); \
+  assert_property_list_match (el_props1, num1, el_props2, num2); \
+  \
+  for (i = 0; i < num1; i++) { \
+    gchar *ser1, *ser2; \
+    GParamSpec *prop = el_props1[i]; \
+    GValue val1 = G_VALUE_INIT, val2 = G_VALUE_INIT; \
+    /* name property can be different */ \
+    if (g_strcmp0 (prop->name, "name") == 0) \
+      continue; \
+    if (g_strcmp0 (prop->name, "parent") == 0) \
+      continue; \
+    g_value_init (&val1, prop->value_type); \
+    g_value_init (&val2, prop->value_type); \
+    ges_timeline_element_get_child_property_by_pspec ( \
+        GES_TIMELINE_ELEMENT (el1), prop, &val1); \
+    ges_timeline_element_get_child_property_by_pspec ( \
+        GES_TIMELINE_ELEMENT (el2), prop, &val2); \
+    ser1 = gst_value_serialize (&val1); \
+    ser2 = gst_value_serialize (&val2); \
+    fail_unless (gst_value_compare (&val1, &val2) == GST_VALUE_EQUAL, \
+        "Child property '%s' for %s does not match that for %s (%s vs %s)", \
+        prop->name, name1, name2, ser1, ser2); \
+    g_free (ser1); \
+    g_free (ser2); \
+    g_value_unset (&val1); \
+    g_value_unset (&val2); \
+  } \
+  free_children_properties (el_props1, num1); \
+  free_children_properties (el_props2, num2); \
+}
+
+#define assert_equal_bindings(el1, el2) \
+{ \
+  guint i, num1, num2; \
+  const gchar *name1 = GES_TIMELINE_ELEMENT_NAME (el1); \
+  const gchar *name2 = GES_TIMELINE_ELEMENT_NAME (el2); \
+  GParamSpec **props1 = ges_timeline_element_list_children_properties ( \
+      GES_TIMELINE_ELEMENT (el1), &num1); \
+  GParamSpec **props2 = ges_timeline_element_list_children_properties ( \
+      GES_TIMELINE_ELEMENT (el2), &num2); \
+  assert_property_list_match (props1, num1, props2, num2); \
+  \
+  for (i = 0; i < num1; i++) { \
+    const gchar *prop = props1[i]->name; \
+    GList *tmp1, *tmp2; \
+    GList *timed_vals1, *timed_vals2; \
+    GObject *object1, *object2; \
+    gboolean abs1, abs2; \
+    GstControlSource *source1, *source2; \
+    GstInterpolationMode mode1, mode2; \
+    GstControlBinding *binding1, *binding2; \
+    guint j; \
+    \
+    binding1 = ges_track_element_get_control_binding ( \
+        GES_TRACK_ELEMENT (el1), prop); \
+    binding2 = ges_track_element_get_control_binding ( \
+        GES_TRACK_ELEMENT (el2), prop); \
+    if (binding1 == NULL) { \
+      fail_unless (binding2 == NULL, "%s has a binding for property " \
+          " '%s', whilst %s does not", name2, prop, name1); \
+      continue; \
+    } \
+    if (binding2 == NULL) { \
+      fail_unless (binding1 == NULL, "%s has a binding for property " \
+          "'%s', whilst %s does not", name1, prop, name2); \
+      continue; \
+    } \
+    \
+    fail_unless (G_OBJECT_TYPE (binding1) == GST_TYPE_DIRECT_CONTROL_BINDING, \
+        "%s binding for property '%s' is not a direct control binding, " \
+        "so cannot be handled", prop, name1); \
+    fail_unless (G_OBJECT_TYPE (binding2) == GST_TYPE_DIRECT_CONTROL_BINDING, \
+        "%s binding for property '%s' is not a direct control binding, " \
+        "so cannot be handled", prop, name2); \
+    \
+    g_object_get (G_OBJECT (binding1), "control-source", &source1, \
+        "absolute", &abs1, "object", &object1, NULL); \
+    g_object_get (G_OBJECT (binding2), "control-source", &source2, \
+        "absolute", &abs2, "object", &object2, NULL); \
+    \
+    fail_unless (G_OBJECT_TYPE (object1) == G_OBJECT_TYPE (object2), \
+        "The child object for property '%s' for %s and %s correspond " \
+        "to different object types (%s vs %s)", prop, name1, name2, \
+        G_OBJECT_TYPE_NAME (object1), G_OBJECT_TYPE_NAME (object2)); \
+    gst_object_unref (object1); \
+    gst_object_unref (object2); \
+    \
+    fail_unless (abs1 == abs2, "control biding for property '%s' " \
+        " is %s absolute for %s, but %s absolute for %s", prop, \
+        abs1 ? "" : "not", name1, abs2 ? "" : "not", name2); \
+    \
+    fail_unless (GST_IS_INTERPOLATION_CONTROL_SOURCE (source1), \
+        "%s does not have an interpolation control source for " \
+        "property '%s', so cannot be handled", name1, prop); \
+    fail_unless (GST_IS_INTERPOLATION_CONTROL_SOURCE (source2), \
+        "%s does not have an interpolation control source for " \
+        "property '%s', so cannot be handled", name2, prop); \
+    g_object_get (G_OBJECT (source1), "mode", &mode1, NULL); \
+    g_object_get (G_OBJECT (source2), "mode", &mode2, NULL); \
+    fail_unless (mode1 == mode2, "control source for property '%s' " \
+        "has different modes for %s and %s (%i vs %i)", prop, \
+        name1, name2, mode1, mode2); \
+    \
+    timed_vals1 = gst_timed_value_control_source_get_all ( \
+      GST_TIMED_VALUE_CONTROL_SOURCE (source1)); \
+    timed_vals2 = gst_timed_value_control_source_get_all ( \
+      GST_TIMED_VALUE_CONTROL_SOURCE (source2)); \
+    \
+    for (j = 0, tmp1 = timed_vals1, tmp2 = timed_vals2; tmp1 && tmp2; \
+        j++, tmp1 = tmp1->next, tmp2 = tmp2->next) { \
+      GstTimedValue *val1 = tmp1->data, *val2 = tmp2->data; \
+      fail_unless (val1->timestamp == val2->timestamp && \
+          val1->value == val2->value, "The %uth timed value for property " \
+          "'%s' is different for %s and %s: (%" G_GUINT64_FORMAT ": %g) vs " \
+          "(%" G_GUINT64_FORMAT ": %g)", j, prop, name1, name2, \
+          val1->timestamp, val1->value, val2->timestamp, val2->value); \
+    } \
+    fail_unless (tmp1 == NULL, "Found too many timed values for " \
+        "property '%s' for %s", prop, name1); \
+    fail_unless (tmp2 == NULL, "Found too many timed values for " \
+        "property '%s' for %s", prop, name2); \
+    \
+    g_list_free (timed_vals1); \
+    g_list_free (timed_vals2); \
+    gst_object_unref (source1); \
+    gst_object_unref (source2); \
+  } \
+  free_children_properties (props1, num1); \
+  free_children_properties (props2, num2); \
+}
 
 void print_timeline(GESTimeline *timeline);