Reimplement the timeline editing API
authorThibault Saunier <tsaunier@igalia.com>
Fri, 1 Mar 2019 22:32:19 +0000 (19:32 -0300)
committerThibault Saunier <tsaunier@gnome.org>
Fri, 15 Mar 2019 23:51:55 +0000 (23:51 +0000)
This is implemented on top of a Tree that represents the whole timeline.

SourceClips can not fully overlap anymore and the tests have been
updated to take that into account. Some new tests were added to verify
that behaviour in greater details

27 files changed:
ges/ges-auto-transition.c
ges/ges-auto-transition.h
ges/ges-clip.c
ges/ges-clip.h
ges/ges-container.c
ges/ges-group.c
ges/ges-internal.h
ges/ges-layer.c
ges/ges-source-clip.c
ges/ges-timeline-element.c
ges/ges-timeline-tree.c [new file with mode: 0644]
ges/ges-timeline-tree.h [new file with mode: 0644]
ges/ges-timeline.c
ges/ges-track-element.c
ges/ges-uri-clip.c
ges/meson.build
tests/check/ges/asset.c
tests/check/ges/basic.c
tests/check/ges/clip.c
tests/check/ges/group.c
tests/check/ges/layer.c
tests/check/ges/test-utils.h
tests/check/ges/timelineedition.c
tests/check/ges/uriclip.c
tests/check/python/common.py
tests/check/python/test_group.py
tests/check/python/test_timeline.py

index befea90..f28f37a 100644 (file)
@@ -46,6 +46,10 @@ neighbour_changed_cb (GESClip * clip, GParamSpec * arg G_GNUC_UNUSED,
   GESTimelineElement *parent =
       ges_timeline_element_get_toplevel_parent (GES_TIMELINE_ELEMENT (clip));
 
+  if (ELEMENT_FLAG_IS_SET (parent, GES_TIMELINE_ELEMENT_SET_SIMPLE)) {
+    return;
+  }
+
   if (parent) {
     GESTimelineElement *prev_topparent =
         ges_timeline_element_get_toplevel_parent (GES_TIMELINE_ELEMENT
@@ -54,6 +58,11 @@ neighbour_changed_cb (GESClip * clip, GParamSpec * arg G_GNUC_UNUSED,
         ges_timeline_element_get_toplevel_parent (GES_TIMELINE_ELEMENT
         (self->previous_source));
 
+    if (ELEMENT_FLAG_IS_SET (prev_topparent, GES_TIMELINE_ELEMENT_SET_SIMPLE) ||
+        ELEMENT_FLAG_IS_SET (next_topparent, GES_TIMELINE_ELEMENT_SET_SIMPLE)) {
+      return;
+    }
+
     if (parent == prev_topparent && parent == next_topparent) {
       GST_DEBUG_OBJECT (self,
           "Moving all inside the same group, nothing to do");
@@ -192,3 +201,11 @@ ges_auto_transition_new (GESTrackElement * transition,
 
   return self;
 }
+
+void
+ges_auto_transition_update (GESAutoTransition * self)
+{
+  GST_INFO ("Updating info %s",
+      GES_TIMELINE_ELEMENT_NAME (self->transition_clip));
+  neighbour_changed_cb (self->previous_clip, NULL, self);
+}
index a8d80bd..4058b04 100644 (file)
@@ -71,6 +71,7 @@ struct _GESAutoTransition
 
 G_GNUC_INTERNAL GType ges_auto_transition_get_type (void) G_GNUC_CONST;
 
+G_GNUC_INTERNAL void ges_auto_transition_update (GESAutoTransition *self);
 G_GNUC_INTERNAL GESAutoTransition * ges_auto_transition_new (GESTrackElement * transition,
                                              GESTrackElement * previous_source,
                                              GESTrackElement * next_source);
index fab363b..a1637cf 100644 (file)
@@ -175,7 +175,6 @@ static gboolean
 _set_duration (GESTimelineElement * element, GstClockTime duration)
 {
   GList *tmp;
-  GESTimeline *timeline;
 
   GESContainer *container = GES_CONTAINER (element);
 
@@ -183,16 +182,8 @@ _set_duration (GESTimelineElement * element, GstClockTime duration)
   for (tmp = container->children; tmp; tmp = g_list_next (tmp)) {
     GESTimelineElement *child = (GESTimelineElement *) tmp->data;
 
-    if (child != container->initiated_move) {
-      /* Make the snapping happen if in a timeline */
-      timeline = GES_TIMELINE_ELEMENT_TIMELINE (child);
-      if (timeline == NULL
-          || ELEMENT_FLAG_IS_SET (element, GES_CLIP_IS_SPLITTING)
-          || (ges_timeline_trim_object_simple (timeline, child, NULL,
-                  GES_EDGE_END, _START (child) + duration, TRUE) == FALSE)) {
-        _set_duration0 (GES_TIMELINE_ELEMENT (child), duration);
-      }
-    }
+    if (child != container->initiated_move)
+      _set_duration0 (GES_TIMELINE_ELEMENT (child), duration);
   }
   container->children_control_mode = GES_CHILDREN_UPDATE;
 
@@ -628,9 +619,8 @@ static gboolean
 _edit (GESContainer * container, GList * layers,
     gint new_layer_priority, GESEditMode mode, GESEdge edge, guint64 position)
 {
-  GList *tmp;
-  gboolean ret = TRUE;
-  GESLayer *layer;
+  GESTimeline *timeline = GES_TIMELINE_ELEMENT_TIMELINE (container);
+  GESTimelineElement *element = GES_TIMELINE_ELEMENT (container);
 
   if (!G_UNLIKELY (GES_CONTAINER_CHILDREN (container))) {
     GST_WARNING_OBJECT (container, "Trying to edit, but not containing"
@@ -638,31 +628,35 @@ _edit (GESContainer * container, GList * layers,
     return FALSE;
   }
 
-  for (tmp = GES_CONTAINER_CHILDREN (container); tmp; tmp = g_list_next (tmp)) {
-    if (GES_IS_SOURCE (tmp->data) || GES_IS_TRANSITION (tmp->data)) {
-      ret &= ges_track_element_edit (tmp->data, layers, mode, edge, position);
-      break;
-    }
+  if (!timeline) {
+    GST_WARNING_OBJECT (container, "Trying to edit, but not in any"
+        "timeline.");
+    return FALSE;
   }
 
-  /* Moving to layer */
-  if (new_layer_priority == -1) {
-    GST_DEBUG_OBJECT (container, "Not moving new prio %d", new_layer_priority);
-  } else {
-    gint priority_offset;
-
-    layer = GES_CLIP (container)->priv->layer;
-    if (layer == NULL) {
-      GST_WARNING_OBJECT (container, "Not in any layer yet, not moving");
-
+  switch (mode) {
+    case GES_EDIT_MODE_RIPPLE:
+      return timeline_ripple_object (timeline, element,
+          new_layer_priority <
+          0 ? GES_TIMELINE_ELEMENT_LAYER_PRIORITY (container) :
+          new_layer_priority, layers, edge, position);
+    case GES_EDIT_MODE_TRIM:
+      return timeline_trim_object (timeline, element,
+          new_layer_priority <
+          0 ? GES_TIMELINE_ELEMENT_LAYER_PRIORITY (container) :
+          new_layer_priority, layers, edge, position);
+    case GES_EDIT_MODE_NORMAL:
+      return timeline_move_object (timeline, element,
+          new_layer_priority <
+          0 ? GES_TIMELINE_ELEMENT_LAYER_PRIORITY (container) :
+          new_layer_priority, layers, edge, position);
+    case GES_EDIT_MODE_ROLL:
+      return timeline_roll_object (timeline, element, layers, edge, position);
+    case GES_EDIT_MODE_SLIDE:
+      GST_ERROR ("Sliding not implemented.");
       return FALSE;
-    }
-    priority_offset = new_layer_priority - ges_layer_get_priority (layer);
-
-    ret &= timeline_context_to_layer (layer->timeline, priority_offset);
   }
-
-  return ret;
+  return FALSE;
 }
 
 static void
@@ -1013,23 +1007,6 @@ ges_clip_set_layer (GESClip * clip, GESLayer * layer)
 }
 
 /**
- * ges_clip_get_layer_priority:
- * @clip: The clip to get the layer priority from
- *
- * Returns: The priority of the layer @clip is in, -1 if not in a layer.
- */
-guint32
-ges_clip_get_layer_priority (GESClip * clip)
-{
-  g_return_val_if_fail (GES_IS_CLIP (clip), -1);
-
-  if (clip->priv->layer == NULL)
-    return -1;
-
-  return ges_layer_get_priority (clip->priv->layer);
-}
-
-/**
  * ges_clip_set_moving_from_layer:
  * @clip: a #GESClip
  * @is_moving: %TRUE if you want to start moving @clip to another layer
@@ -1088,6 +1065,19 @@ ges_clip_move_to_layer (GESClip * clip, GESLayer * layer)
   g_return_val_if_fail (GES_IS_CLIP (clip), FALSE);
   g_return_val_if_fail (GES_IS_LAYER (layer), FALSE);
 
+  ELEMENT_SET_FLAG (clip, GES_CLIP_IS_MOVING);
+  if (layer->timeline
+      && !timeline_tree_can_move_element (timeline_get_tree (layer->timeline),
+          GES_TIMELINE_ELEMENT (clip),
+          ges_layer_get_priority (layer),
+          GES_TIMELINE_ELEMENT_START (clip),
+          GES_TIMELINE_ELEMENT_DURATION (clip), NULL)) {
+    GST_INFO_OBJECT (layer, "Clip %" GES_FORMAT " can't move to layer %d",
+        GES_ARGS (clip), ges_layer_get_priority (layer));
+    ELEMENT_UNSET_FLAG (clip, GES_CLIP_IS_MOVING);
+    return FALSE;
+  }
+
   current_layer = clip->priv->layer;
 
   if (current_layer == NULL) {
@@ -1099,11 +1089,11 @@ ges_clip_move_to_layer (GESClip * clip, GESLayer * layer)
   GST_DEBUG_OBJECT (clip, "moving to layer %p, priority: %d", layer,
       ges_layer_get_priority (layer));
 
-  ELEMENT_SET_FLAG (clip, GES_CLIP_IS_MOVING);
   gst_object_ref (clip);
   ret = ges_layer_remove_clip (current_layer, clip);
 
   if (!ret) {
+    ELEMENT_UNSET_FLAG (clip, GES_CLIP_IS_MOVING);
     gst_object_unref (clip);
     return FALSE;
   }
@@ -1418,16 +1408,9 @@ ges_clip_split (GESClip * clip, guint64 position)
         position - start + inpoint);
   }
 
-  ELEMENT_SET_FLAG (clip, GES_CLIP_IS_SPLITTING);
+  ELEMENT_SET_FLAG (clip, GES_TIMELINE_ELEMENT_SET_SIMPLE);
   _set_duration0 (GES_TIMELINE_ELEMENT (clip), old_duration);
-  ELEMENT_UNSET_FLAG (clip, GES_CLIP_IS_SPLITTING);
-
-  if (GES_TIMELINE_ELEMENT_TIMELINE (clip)) {
-    for (tmp = GES_CONTAINER_CHILDREN (new_object); tmp; tmp = tmp->next) {
-      timeline_create_transitions (GES_TIMELINE_ELEMENT_TIMELINE (tmp->data),
-          tmp->data);
-    }
-  }
+  ELEMENT_UNSET_FLAG (clip, GES_TIMELINE_ELEMENT_SET_SIMPLE);
 
   return new_object;
 }
@@ -1466,134 +1449,40 @@ ges_clip_get_supported_formats (GESClip * clip)
 gboolean
 _ripple (GESTimelineElement * element, GstClockTime start)
 {
-  gboolean ret = TRUE;
-  GESTimeline *timeline;
-  GESClip *clip = GES_CLIP (element);
-
-  timeline = ges_layer_get_timeline (clip->priv->layer);
-
-  if (timeline == NULL) {
-    GST_DEBUG ("Not in a timeline yet");
-    return FALSE;
-  }
-
-  if (start > _END (element))
-    start = _END (element);
-
-  if (GES_CONTAINER_CHILDREN (element)) {
-    GESTrackElement *track_element =
-        GES_TRACK_ELEMENT (GES_CONTAINER_CHILDREN (element)->data);
-
-    ret = timeline_ripple_object (timeline, track_element, NULL, GES_EDGE_NONE,
-        start);
-  }
-
-  return ret;
+  return ges_container_edit (GES_CONTAINER (element), NULL,
+      ges_timeline_element_get_layer_priority (element),
+      GES_EDIT_MODE_RIPPLE, GES_EDGE_NONE, start);
 }
 
 static gboolean
 _ripple_end (GESTimelineElement * element, GstClockTime end)
 {
-  gboolean ret = TRUE;
-  GESTimeline *timeline;
-  GESClip *clip = GES_CLIP (element);
-
-  timeline = ges_layer_get_timeline (clip->priv->layer);
-
-  if (timeline == NULL) {
-    GST_DEBUG ("Not in a timeline yet");
-    return FALSE;
-  }
-
-  if (GES_CONTAINER_CHILDREN (element)) {
-    GESTrackElement *track_element =
-        GES_TRACK_ELEMENT (GES_CONTAINER_CHILDREN (element)->data);
-
-    ret = timeline_ripple_object (timeline, track_element, NULL, GES_EDGE_END,
-        end);
-  }
-
-  return ret;
+  return ges_container_edit (GES_CONTAINER (element), NULL,
+      ges_timeline_element_get_layer_priority (element),
+      GES_EDIT_MODE_RIPPLE, GES_EDGE_END, end);
 }
 
 gboolean
 _roll_start (GESTimelineElement * element, GstClockTime start)
 {
-  gboolean ret = TRUE;
-  GESTimeline *timeline;
-
-  GESClip *clip = GES_CLIP (element);
-
-  timeline = ges_layer_get_timeline (clip->priv->layer);
-
-  if (timeline == NULL) {
-    GST_DEBUG ("Not in a timeline yet");
-    return FALSE;
-  }
-
-  if (GES_CONTAINER_CHILDREN (element)) {
-    GESTrackElement *track_element =
-        GES_TRACK_ELEMENT (GES_CONTAINER_CHILDREN (element)->data);
-
-    ret = timeline_roll_object (timeline, track_element, NULL, GES_EDGE_START,
-        start);
-  }
-
-  return ret;
+  return ges_container_edit (GES_CONTAINER (element), NULL,
+      ges_timeline_element_get_layer_priority (element),
+      GES_EDIT_MODE_ROLL, GES_EDGE_START, start);
 }
 
 gboolean
 _roll_end (GESTimelineElement * element, GstClockTime end)
 {
-  gboolean ret = TRUE;
-  GESTimeline *timeline;
-
-  GESClip *clip = GES_CLIP (element);
-
-  timeline = ges_layer_get_timeline (clip->priv->layer);
-  if (timeline == NULL) {
-    GST_DEBUG ("Not in a timeline yet");
-    return FALSE;
-  }
-
-
-  if (GES_CONTAINER_CHILDREN (element)) {
-    GESTrackElement *track_element =
-        GES_TRACK_ELEMENT (GES_CONTAINER_CHILDREN (element)->data);
-
-    ret = timeline_roll_object (timeline, track_element,
-        NULL, GES_EDGE_END, end);
-  }
-
-  return ret;
+  return ges_container_edit (GES_CONTAINER (element), NULL,
+      ges_timeline_element_get_layer_priority (element),
+      GES_EDIT_MODE_ROLL, GES_EDGE_END, end);
 }
 
 gboolean
 _trim (GESTimelineElement * element, GstClockTime start)
 {
-  gboolean ret = TRUE;
-  GESTimeline *timeline;
-
-  GESClip *clip = GES_CLIP (element);
-
-  timeline = ges_layer_get_timeline (clip->priv->layer);
-
-  if (timeline == NULL) {
-    GST_DEBUG ("Not in a timeline yet");
-    return FALSE;
-  }
-
-  if (GES_CONTAINER_CHILDREN (element)) {
-    GESTrackElement *track_element =
-        GES_TRACK_ELEMENT (GES_CONTAINER_CHILDREN (element)->data);
-
-    GST_DEBUG_OBJECT (element, "Trimming child: %" GST_PTR_FORMAT,
-        track_element);
-    ret = timeline_trim_object (timeline, track_element, NULL, GES_EDGE_START,
-        start);
-  }
-
-  return ret;
+  return ges_container_edit (GES_CONTAINER (element), NULL, -1,
+      GES_EDIT_MODE_TRIM, GES_EDGE_START, start);
 }
 
 /**
index 848c288..f0bbb1e 100644 (file)
@@ -163,8 +163,6 @@ GES_API
 GESLayer* ges_clip_get_layer              (GESClip *clip);
 GES_API
 gboolean  ges_clip_move_to_layer          (GESClip *clip, GESLayer  *layer);
-GES_API
-guint32 ges_clip_get_layer_priority        (GESClip * clip);
 
 /****************************************************
  *                   Effects                        *
index 034fae2..168b3e8 100644 (file)
@@ -524,10 +524,14 @@ _child_start_changed_cb (GESTimelineElement * child,
 
   GESContainerPrivate *priv = container->priv;
   GESTimelineElement *element = GES_TIMELINE_ELEMENT (container);
+  GESChildrenControlMode pmode = container->children_control_mode;
 
   map = g_hash_table_lookup (priv->mappings, child);
   g_assert (map);
 
+  if (ELEMENT_FLAG_IS_SET (child, GES_TIMELINE_ELEMENT_SET_SIMPLE))
+    container->children_control_mode = GES_CHILDREN_UPDATE_ALL_VALUES;
+
   switch (container->children_control_mode) {
     case GES_CHILDREN_IGNORE_NOTIFIES:
       return;
@@ -540,8 +544,8 @@ _child_start_changed_cb (GESTimelineElement * child,
         _DURATION (container) = _END (container) - start;
         _START (container) = start;
 
-        GST_DEBUG_OBJECT (container, "Child move made us "
-            "move to %" GST_TIME_FORMAT, GST_TIME_ARGS (_START (container)));
+        GST_DEBUG_OBJECT (container, "Child move made us move %" GES_FORMAT,
+            GES_ARGS (container));
 
         g_object_notify (G_OBJECT (container), "start");
       }
@@ -560,6 +564,9 @@ _child_start_changed_cb (GESTimelineElement * child,
     default:
       break;
   }
+
+  if (ELEMENT_FLAG_IS_SET (child, GES_TIMELINE_ELEMENT_SET_SIMPLE))
+    container->children_control_mode = pmode;
 }
 
 static void
@@ -577,7 +584,8 @@ _child_inpoint_changed_cb (GESTimelineElement * child,
   map = g_hash_table_lookup (priv->mappings, child);
   g_assert (map);
 
-  if (container->children_control_mode == GES_CHILDREN_UPDATE_OFFSETS) {
+  if (container->children_control_mode == GES_CHILDREN_UPDATE_OFFSETS
+      || ELEMENT_FLAG_IS_SET (child, GES_TIMELINE_ELEMENT_SET_SIMPLE)) {
     map->inpoint_offset = _START (container) - _START (child);
 
     return;
@@ -599,10 +607,14 @@ _child_duration_changed_cb (GESTimelineElement * child,
   GstClockTime end = 0;
   GESContainerPrivate *priv = container->priv;
   GESTimelineElement *element = GES_TIMELINE_ELEMENT (container);
+  GESChildrenControlMode pmode = container->children_control_mode;
 
   if (container->children_control_mode == GES_CHILDREN_IGNORE_NOTIFIES)
     return;
 
+  if (ELEMENT_FLAG_IS_SET (child, GES_TIMELINE_ELEMENT_SET_SIMPLE))
+    container->children_control_mode = GES_CHILDREN_UPDATE_ALL_VALUES;
+
   map = g_hash_table_lookup (priv->mappings, child);
   g_assert (map);
 
@@ -632,6 +644,9 @@ _child_duration_changed_cb (GESTimelineElement * child,
     default:
       break;
   }
+
+  if (ELEMENT_FLAG_IS_SET (child, GES_TIMELINE_ELEMENT_SET_SIMPLE))
+    container->children_control_mode = pmode;
 }
 
 /****************************************************
index c50a8a4..44c29bd 100644 (file)
@@ -255,11 +255,7 @@ _child_group_priority_changed (GESTimelineElement * child,
 static gboolean
 _trim (GESTimelineElement * group, GstClockTime start)
 {
-  GList *tmp;
-  GstClockTime last_child_end = 0, oldstart = _START (group);
-  GESContainer *container = GES_CONTAINER (group);
   GESTimeline *timeline = GES_TIMELINE_ELEMENT_TIMELINE (group);
-  gboolean ret = TRUE, expending = (start < _START (group));
 
   if (timeline == NULL) {
     GST_DEBUG ("Not in a timeline yet");
@@ -267,40 +263,9 @@ _trim (GESTimelineElement * group, GstClockTime start)
     return FALSE;
   }
 
-  container->children_control_mode = GES_CHILDREN_IGNORE_NOTIFIES;
-  for (tmp = GES_CONTAINER_CHILDREN (group); tmp; tmp = tmp->next) {
-    GESTimelineElement *child = tmp->data;
-
-    if (expending) {
-      /* If the start is bigger, we do not touch it (in case we are expending) */
-      if (_START (child) > oldstart) {
-        GST_DEBUG_OBJECT (child, "Skipping as not at begining of the group");
-        continue;
-      }
-
-      ret &= ges_timeline_element_trim (child, start);
-    } else {
-      if (start > _END (child))
-        ret &= ges_timeline_element_trim (child, _END (child));
-      else if (_START (child) < start && _DURATION (child))
-        ret &= ges_timeline_element_trim (child, start);
-
-    }
-  }
-
-  for (tmp = GES_CONTAINER_CHILDREN (group); tmp; tmp = tmp->next) {
-    if (_DURATION (tmp->data))
-      last_child_end =
-          MAX (GES_TIMELINE_ELEMENT_END (tmp->data), last_child_end);
-  }
-
-  GES_GROUP (group)->priv->setting_value = TRUE;
-  _set_start0 (group, start);
-  _set_duration0 (group, last_child_end - start);
-  GES_GROUP (group)->priv->setting_value = FALSE;
-  container->children_control_mode = GES_CHILDREN_UPDATE;
-
-  return ret;
+  return timeline_tree_trim (timeline_get_tree (timeline), group,
+      0, GST_CLOCK_DIFF (start, _START (group)), GES_EDGE_START,
+      ges_timeline_get_snapping_distance (timeline));
 }
 
 static gboolean
@@ -309,38 +274,44 @@ _set_priority (GESTimelineElement * element, guint32 priority)
   GList *tmp, *layers;
   gint diff = priority - _PRIORITY (element);
   GESContainer *container = GES_CONTAINER (element);
+  GESTimeline *timeline = GES_TIMELINE_ELEMENT_TIMELINE (element);
 
   if (GES_GROUP (element)->priv->setting_value == TRUE)
     return TRUE;
 
   container->children_control_mode = GES_CHILDREN_IGNORE_NOTIFIES;
+
   layers = GES_TIMELINE_ELEMENT_TIMELINE (element) ?
       GES_TIMELINE_ELEMENT_TIMELINE (element)->layers : NULL;
-
   if (layers == NULL) {
     GST_WARNING_OBJECT (element, "Not any layer in the timeline, not doing"
         "anything, timeline: %" GST_PTR_FORMAT,
         GES_TIMELINE_ELEMENT_TIMELINE (element));
 
     return FALSE;
-  } else if (priority + GES_CONTAINER_HEIGHT (container) - 1 >
-      g_list_length (layers)) {
-    GST_WARNING_OBJECT (container, "Trying to move to a layer outside of"
-        "the timeline layers");
-    return FALSE;
   }
 
   for (tmp = GES_CONTAINER_CHILDREN (element); tmp; tmp = tmp->next) {
     GESTimelineElement *child = tmp->data;
 
-    if (child != container->initiated_move) {
+    if (child != container->initiated_move
+        || ELEMENT_FLAG_IS_SET (container, GES_TIMELINE_ELEMENT_SET_SIMPLE)) {
       if (GES_IS_CLIP (child)) {
         guint32 layer_prio = GES_TIMELINE_ELEMENT_LAYER_PRIORITY (child) + diff;
-
-        GST_DEBUG_OBJECT (child, "moving from layer: %i to %i",
-            GES_TIMELINE_ELEMENT_LAYER_PRIORITY (child), layer_prio);
-        ges_clip_move_to_layer (GES_CLIP (child),
-            g_list_nth_data (layers, layer_prio));
+        GESLayer *layer =
+            g_list_nth_data (GES_TIMELINE_GET_LAYERS (timeline), layer_prio);
+
+        if (layer == NULL) {
+          do {
+            layer = ges_timeline_append_layer (timeline);
+          } while (ges_layer_get_priority (layer) < layer_prio);
+        }
+
+        GST_DEBUG ("%" GES_FORMAT "moving from layer: %i to %i",
+            GES_ARGS (child), GES_TIMELINE_ELEMENT_LAYER_PRIORITY (child),
+            layer_prio);
+        ges_clip_move_to_layer (GES_CLIP (child), g_list_nth_data (layers,
+                layer_prio));
       } else if (GES_IS_GROUP (child)) {
         GST_DEBUG_OBJECT (child, "moving from %i to %i",
             _PRIORITY (child), diff + _PRIORITY (child));
@@ -358,24 +329,31 @@ _set_start (GESTimelineElement * element, GstClockTime start)
 {
   GList *tmp;
   gint64 diff = start - _START (element);
+  GESTimeline *timeline;
   GESContainer *container = GES_CONTAINER (element);
+  GESTimelineElement *toplevel =
+      ges_timeline_element_get_toplevel_parent (element);;
 
+  gst_object_unref (toplevel);
   if (GES_GROUP (element)->priv->setting_value == TRUE)
     /* Let GESContainer update itself */
     return GES_TIMELINE_ELEMENT_CLASS (parent_class)->set_start (element,
         start);
 
+  if (ELEMENT_FLAG_IS_SET (element, GES_TIMELINE_ELEMENT_SET_SIMPLE) ||
+      ELEMENT_FLAG_IS_SET (toplevel, GES_TIMELINE_ELEMENT_SET_SIMPLE)) {
+    container->children_control_mode = GES_CHILDREN_IGNORE_NOTIFIES;
+    for (tmp = GES_CONTAINER_CHILDREN (element); tmp; tmp = tmp->next)
+      _set_start0 (tmp->data, _START (tmp->data) + diff);
+    container->children_control_mode = GES_CHILDREN_UPDATE;
 
-  container->children_control_mode = GES_CHILDREN_IGNORE_NOTIFIES;
-  for (tmp = GES_CONTAINER_CHILDREN (element); tmp; tmp = tmp->next) {
-    GESTimelineElement *child = (GESTimelineElement *) tmp->data;
-
-    if (child != container->initiated_move &&
-        (_END (child) > _START (element) || _END (child) > start)) {
-      _set_start0 (child, _START (child) + diff);
-    }
+    return TRUE;
   }
-  container->children_control_mode = GES_CHILDREN_UPDATE;
+
+  timeline = GES_TIMELINE_ELEMENT_TIMELINE (element);
+  if (timeline)
+    return ges_timeline_move_object_simple (timeline, element, NULL,
+        GES_EDGE_NONE, start);
 
   return TRUE;
 }
@@ -399,6 +377,12 @@ _set_duration (GESTimelineElement * element, GstClockTime duration)
     return GES_TIMELINE_ELEMENT_CLASS (parent_class)->set_duration (element,
         duration);
 
+  if (element->timeline
+      && !timeline_tree_can_move_element (timeline_get_tree (element->timeline),
+          element, _PRIORITY (element), element->start, duration, NULL)) {
+    return FALSE;
+  }
+
   if (container->initiated_move == NULL) {
     gboolean expending = (_DURATION (element) < duration);
 
@@ -471,6 +455,7 @@ _child_added (GESContainer * group, GESTimelineElement * child)
   }
 
   priv->setting_value = TRUE;
+  ELEMENT_SET_FLAG (group, GES_TIMELINE_ELEMENT_SET_SIMPLE);
   if (first_child_start != GES_TIMELINE_ELEMENT_START (group)) {
     group->children_control_mode = GES_CHILDREN_IGNORE_NOTIFIES;
     _set_start0 (GES_TIMELINE_ELEMENT (group), first_child_start);
@@ -480,6 +465,7 @@ _child_added (GESContainer * group, GESTimelineElement * child)
     _set_duration0 (GES_TIMELINE_ELEMENT (group),
         last_child_end - first_child_start);
   }
+  ELEMENT_UNSET_FLAG (group, GES_TIMELINE_ELEMENT_SET_SIMPLE);
   priv->setting_value = FALSE;
 
   group->children_control_mode = GES_CHILDREN_UPDATE;
@@ -561,12 +547,14 @@ _child_removed (GESContainer * group, GESTimelineElement * child)
   }
 
   priv->setting_value = TRUE;
+  ELEMENT_SET_FLAG (group, GES_TIMELINE_ELEMENT_SET_SIMPLE);
   first_child_start = GES_TIMELINE_ELEMENT_START (children->data);
   if (first_child_start > GES_TIMELINE_ELEMENT_START (group)) {
     group->children_control_mode = GES_CHILDREN_IGNORE_NOTIFIES;
     _set_start0 (GES_TIMELINE_ELEMENT (group), first_child_start);
     group->children_control_mode = GES_CHILDREN_UPDATE;
   }
+  ELEMENT_UNSET_FLAG (group, GES_TIMELINE_ELEMENT_SET_SIMPLE);
   priv->setting_value = FALSE;
 }
 
index 51286b5..e0c4eda 100644 (file)
@@ -30,6 +30,7 @@
 
 #include "ges-asset.h"
 #include "ges-base-xml-formatter.h"
+#include "ges-timeline-tree.h"
 
 G_BEGIN_DECLS
 
@@ -50,7 +51,9 @@ GST_DEBUG_CATEGORY_EXTERN (_ges_debug);
 #define _DURATION(obj) GES_TIMELINE_ELEMENT_DURATION (obj)
 #define _MAXDURATION(obj) GES_TIMELINE_ELEMENT_MAX_DURATION (obj)
 #define _PRIORITY(obj) GES_TIMELINE_ELEMENT_PRIORITY (obj)
+#ifndef _END
 #define _END(obj) (_START (obj) + _DURATION (obj))
+#endif
 #define _set_start0 ges_timeline_element_set_start
 #define _set_inpoint0 ges_timeline_element_set_inpoint
 #define _set_duration0 ges_timeline_element_set_duration
@@ -60,44 +63,50 @@ GST_DEBUG_CATEGORY_EXTERN (_ges_debug);
     "s<%p>" \
     " [ %" GST_TIME_FORMAT \
     " (%" GST_TIME_FORMAT \
-    ") - %" GST_TIME_FORMAT "]"
+    ") - %" GST_TIME_FORMAT "(%" GST_TIME_FORMAT") layer: %" G_GINT32_FORMAT "] "
 
 #define GES_TIMELINE_ELEMENT_ARGS(element) \
     GES_TIMELINE_ELEMENT_NAME(element), element, \
     GST_TIME_ARGS(GES_TIMELINE_ELEMENT_START(element)), \
     GST_TIME_ARGS(GES_TIMELINE_ELEMENT_INPOINT(element)), \
-    GST_TIME_ARGS(GES_TIMELINE_ELEMENT_DURATION(element))
+    GST_TIME_ARGS(GES_TIMELINE_ELEMENT_DURATION(element)), \
+    GST_TIME_ARGS(GES_TIMELINE_ELEMENT_MAX_DURATION(element)), \
+    GES_TIMELINE_ELEMENT_LAYER_PRIORITY(element)
+
+#define GES_FORMAT GES_TIMELINE_ELEMENT_FORMAT
+#define GES_ARGS GES_TIMELINE_ELEMENT_ARGS
 
 G_GNUC_INTERNAL gboolean
-timeline_ripple_object         (GESTimeline *timeline, GESTrackElement *obj,
-                                    GList * layers, GESEdge edge,
-                                    guint64 position);
+timeline_ripple_object         (GESTimeline *timeline, GESTimelineElement *obj,
+                                gint new_layer_priority,
+                                GList * layers, GESEdge edge,
+                                guint64 position);
 
 G_GNUC_INTERNAL gboolean
 timeline_slide_object          (GESTimeline *timeline, GESTrackElement *obj,
                                     GList * layers, GESEdge edge, guint64 position);
 
 G_GNUC_INTERNAL gboolean
-timeline_roll_object           (GESTimeline *timeline, GESTrackElement *obj,
-                                    GList * layers, GESEdge edge, guint64 position);
+timeline_roll_object           (GESTimeline *timeline, GESTimelineElement *obj,
+                                GList * layers, GESEdge edge, guint64 position);
 
 G_GNUC_INTERNAL gboolean
-timeline_trim_object           (GESTimeline *timeline, GESTrackElement * object,
-                                    GList * layers, GESEdge edge, guint64 position);
+timeline_trim_object           (GESTimeline *timeline, GESTimelineElement * object,
+                                guint32 new_layer_priority, GList * layers, GESEdge edge,
+                                guint64 position);
 G_GNUC_INTERNAL gboolean
 ges_timeline_trim_object_simple (GESTimeline * timeline, GESTimelineElement * obj,
-                                 GList * layers, GESEdge edge, guint64 position, gboolean snapping);
+                                 guint32 new_layer_priority, GList * layers, GESEdge edge,
+                                 guint64 position, gboolean snapping);
 
 G_GNUC_INTERNAL gboolean
 ges_timeline_move_object_simple (GESTimeline * timeline, GESTimelineElement * object,
                                  GList * layers, GESEdge edge, guint64 position);
 
 G_GNUC_INTERNAL gboolean
-timeline_move_object           (GESTimeline *timeline, GESTrackElement * object,
-                                    GList * layers, GESEdge edge, guint64 position);
-
-G_GNUC_INTERNAL gboolean
-timeline_context_to_layer      (GESTimeline *timeline, gint offset);
+timeline_move_object           (GESTimeline *timeline, GESTimelineElement * object,
+                                guint32 new_layer_priority, GList * layers, GESEdge edge,
+                                guint64 position);
 
 G_GNUC_INTERNAL void
 timeline_add_group             (GESTimeline *timeline,
@@ -122,6 +131,14 @@ timeline_remove_element       (GESTimeline *timeline,
                                GESTimelineElement *element);
 
 G_GNUC_INTERNAL
+GNode *
+timeline_get_tree           (GESTimeline *timeline);
+
+G_GNUC_INTERNAL
+void
+timeline_update_transition (GESTimeline *timeline);
+
+G_GNUC_INTERNAL
 void
 timeline_fill_gaps            (GESTimeline *timeline);
 
@@ -396,9 +413,8 @@ G_GNUC_INTERNAL GESVideoTestSource * ges_video_test_source_new (void);
  ****************************************************/
 typedef enum
 {
-  GES_CLIP_IS_SPLITTING = (1 << 0),
-  GES_CLIP_IS_MOVING = (1 << 1),
-  GES_TIMELINE_ELEMENT_SET_SIMPLE = (1 << 2),
+  GES_CLIP_IS_MOVING = (1 << 0),
+  GES_TIMELINE_ELEMENT_SET_SIMPLE = (1 << 1),
 } GESTimelineElementFlags;
 
 G_GNUC_INTERNAL gdouble ges_timeline_element_get_media_duration_factor(GESTimelineElement *self);
@@ -406,9 +422,6 @@ G_GNUC_INTERNAL GESTimelineElement * ges_timeline_element_get_copied_from (GESTi
 G_GNUC_INTERNAL GESTimelineElementFlags ges_timeline_element_flags (GESTimelineElement *self);
 G_GNUC_INTERNAL void                ges_timeline_element_set_flags (GESTimelineElement *self, GESTimelineElementFlags flags);
 
-/* FIXME: Provide a clean way to get layer prio generically */
-G_GNUC_INTERNAL gint32 _layer_priority (GESTimelineElement * element);
-
 #define ELEMENT_FLAGS(obj)             (ges_timeline_element_flags (GES_TIMELINE_ELEMENT(obj)))
 #define ELEMENT_SET_FLAG(obj,flag)     (ges_timeline_element_set_flags(GES_TIMELINE_ELEMENT(obj), (ELEMENT_FLAGS(obj) | (flag))))
 #define ELEMENT_UNSET_FLAG(obj,flag)   (ges_timeline_element_set_flags(GES_TIMELINE_ELEMENT(obj), (ELEMENT_FLAGS(obj) & ~(flag))))
index 204efac..248c1c0 100644 (file)
@@ -666,6 +666,17 @@ ges_layer_add_clip (GESLayer * layer, GESClip * clip)
   /* emit 'clip-added' */
   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));
+    ges_layer_remove_clip_internal (layer, clip, TRUE);
+    return FALSE;
+  }
+
   return TRUE;
 }
 
index 461bb70..755485a 100644 (file)
@@ -49,43 +49,41 @@ G_DEFINE_TYPE_WITH_PRIVATE (GESSourceClip, ges_source_clip, GES_TYPE_CLIP);
 static gboolean
 _set_start (GESTimelineElement * element, GstClockTime start)
 {
-  GList *tmp;
-  GESTimeline *timeline;
-  GESContainer *container = GES_CONTAINER (element);
-  GstClockTime rollback_start = GES_TIMELINE_ELEMENT_START (element);
-
-  GST_DEBUG_OBJECT (element, "Setting children start, (initiated_move: %"
-      GST_PTR_FORMAT ")", container->initiated_move);
-
-  element->start = start;
-  g_object_notify (G_OBJECT (element), "start");
-  container->children_control_mode = GES_CHILDREN_IGNORE_NOTIFIES;
-  for (tmp = container->children; tmp; tmp = g_list_next (tmp)) {
-    GESTimelineElement *child = (GESTimelineElement *) tmp->data;
-
-    if (child != container->initiated_move) {
-      /* Make the snapping happen if in a timeline */
-      timeline = GES_TIMELINE_ELEMENT_TIMELINE (child);
-      if (timeline && !container->initiated_move) {
-        if (!ges_timeline_move_object_simple (timeline, child, NULL,
-                GES_EDGE_NONE, start)) {
-          for (tmp = container->children; tmp; tmp = g_list_next (tmp))
-            ges_timeline_element_set_start (tmp->data, rollback_start);
-
-          element->start = rollback_start;
-          g_object_notify (G_OBJECT (element), "start");
-          container->children_control_mode = GES_CHILDREN_UPDATE;
-          return FALSE;
-        }
-      }
-
-      _set_start0 (GES_TIMELINE_ELEMENT (child), start);
-    }
+  GESTimelineElement *toplevel =
+      ges_timeline_element_get_toplevel_parent (element);
+
+  gst_object_unref (toplevel);
+  if (element->timeline
+      && !ELEMENT_FLAG_IS_SET (element, GES_TIMELINE_ELEMENT_SET_SIMPLE)
+      && !ELEMENT_FLAG_IS_SET (toplevel, GES_TIMELINE_ELEMENT_SET_SIMPLE)) {
+    ges_timeline_move_object_simple (element->timeline, element, NULL,
+        GES_EDGE_NONE, start);
+    return FALSE;
   }
 
-  container->children_control_mode = GES_CHILDREN_UPDATE;
+  return
+      GES_TIMELINE_ELEMENT_CLASS (ges_source_clip_parent_class)->set_start
+      (element, start);
+}
+
+static gboolean
+_set_duration (GESTimelineElement * element, GstClockTime duration)
+{
+  GESTimelineElement *toplevel =
+      ges_timeline_element_get_toplevel_parent (element);
+
+  gst_object_unref (toplevel);
+  if (element->timeline
+      && !ELEMENT_FLAG_IS_SET (element, GES_TIMELINE_ELEMENT_SET_SIMPLE)
+      && !ELEMENT_FLAG_IS_SET (toplevel, GES_TIMELINE_ELEMENT_SET_SIMPLE)) {
+    return !timeline_trim_object (element->timeline, element,
+        GES_TIMELINE_ELEMENT_LAYER_PRIORITY (element), NULL, GES_EDGE_END,
+        element->start + duration);
+  }
 
-  return FALSE;
+  return
+      GES_TIMELINE_ELEMENT_CLASS (ges_source_clip_parent_class)->set_duration
+      (element, duration);
 }
 
 static void
@@ -125,6 +123,7 @@ ges_source_clip_class_init (GESSourceClipClass * klass)
   object_class->finalize = ges_source_clip_finalize;
 
   element_class->set_start = _set_start;
+  element_class->set_duration = _set_duration;
 }
 
 static void
index 040a7f9..39c65b7 100644 (file)
@@ -694,6 +694,9 @@ ges_timeline_element_set_start (GESTimelineElement * self, GstClockTime start)
 
   g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE);
 
+  if (self->start == start)
+    return TRUE;
+
   klass = GES_TIMELINE_ELEMENT_GET_CLASS (self);
 
   GST_DEBUG_OBJECT (self, "current start: %" GST_TIME_FORMAT
diff --git a/ges/ges-timeline-tree.c b/ges/ges-timeline-tree.c
new file mode 100644 (file)
index 0000000..bc9a608
--- /dev/null
@@ -0,0 +1,1256 @@
+/* GStreamer Editing Services
+ * Copyright (C) 2019 Igalia S.L
+ *     Author: 2019 Thibault Saunier <tsaunier@igalia.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "ges-timeline-tree.h"
+#include "ges-internal.h"
+
+GST_DEBUG_CATEGORY_STATIC (tree_debug);
+#undef GST_CAT_DEFAULT
+#define GST_CAT_DEFAULT tree_debug
+
+#define ELEMENT_EDGE_VALUE(e, edge) ((edge == GES_EDGE_END) ? ((GstClockTimeDiff) _END (e)) : ((GstClockTimeDiff) _START (e)))
+typedef struct
+{
+  GstClockTime distance;
+  gboolean on_end_only;
+  gboolean on_start_only;
+
+  GESEdge edge;
+  GESTimelineElement *element;
+
+  GESTimelineElement *moving_element;
+  GESEdge moving_edge;
+  GstClockTimeDiff diff;
+} SnappingData;
+
+/*  *INDENT-OFF* */
+struct _TreeIterationData
+{
+  GNode *root;
+  gboolean res;
+
+  GstClockTimeDiff start_diff;
+  GstClockTimeDiff inpoint_diff;
+  GstClockTimeDiff duration_diff;
+  gint64 priority_diff;
+
+  /* The element we are visiting */
+  GESTimelineElement *element;
+
+  /* All the TrackElement currently moving */
+  GList *movings;
+
+  /* Elements overlaping on the start/end of @element */
+  GESTimelineElement *overlaping_on_start;
+  GESTimelineElement *overlaping_on_end;
+
+  /* Timestamp after which elements will be rippled */
+  GstClockTime ripple_time;
+
+  SnappingData *snapping;
+
+  /* The edge being trimmed or rippled */
+  GESEdge edge;
+  GHashTable *moved_clips;
+
+  GList *neighbours;
+} tree_iteration_data_init = {
+   .root = NULL,
+   .res = TRUE,
+   .start_diff = 0,
+   .inpoint_diff = 0,
+   .duration_diff = 0,
+   .priority_diff = 0,
+   .element = NULL,
+   .movings = NULL,
+   .overlaping_on_start = NULL,
+   .overlaping_on_end = NULL,
+   .ripple_time = GST_CLOCK_TIME_NONE,
+   .snapping = NULL,
+   .edge = GES_EDGE_NONE,
+   .moved_clips = NULL,
+   .neighbours = NULL,
+};
+/*  *INDENT-ON* */
+
+typedef struct _TreeIterationData TreeIterationData;
+
+static void
+clean_iteration_data (TreeIterationData * data)
+{
+  g_list_free (data->neighbours);
+  g_list_free (data->movings);
+  if (data->moved_clips)
+    g_hash_table_unref (data->moved_clips);
+}
+
+void
+timeline_tree_init_debug (void)
+{
+  GST_DEBUG_CATEGORY_INIT (tree_debug, "gestree",
+      GST_DEBUG_FG_YELLOW, "timeline tree");
+}
+
+
+static gboolean
+print_node (GNode * node, gpointer unused_data)
+{
+  if (G_NODE_IS_ROOT (node)) {
+    g_print ("Timeline: %p\n", node->data);
+    return FALSE;
+  }
+
+  g_print ("%*c- %" GES_FORMAT " - layer %" G_GINT32_FORMAT "\n",
+      2 * g_node_depth (node), ' ', GES_ARGS (node->data),
+      ges_timeline_element_get_layer_priority (node->data));
+
+  return FALSE;
+}
+
+void
+timeline_tree_debug (GNode * root)
+{
+  g_node_traverse (root, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
+      (GNodeTraverseFunc) print_node, NULL);
+}
+
+static inline GESTimelineElement *
+get_toplevel_container (gpointer element)
+{
+  GESTimelineElement *ret =
+      ges_timeline_element_get_toplevel_parent ((GESTimelineElement
+          *) (element));
+
+  /*  We own a ref to the elements ourself */
+  gst_object_unref (ret);
+  return ret;
+}
+
+static gboolean
+timeline_tree_can_move_element_internal (GNode * root,
+    GESTimelineElement * element,
+    gint64 priority,
+    GstClockTimeDiff start,
+    GstClockTimeDiff inpoint,
+    GstClockTimeDiff duration,
+    GList * moving_track_elements,
+    GstClockTime ripple_time, SnappingData * snapping, GESEdge edge);
+
+static GNode *
+find_node (GNode * root, gpointer element)
+{
+  return g_node_find (root, G_IN_ORDER, G_TRAVERSE_ALL, element);
+}
+
+static void
+timeline_element_parent_cb (GESTimelineElement * child, GParamSpec * arg
+    G_GNUC_UNUSED, GNode * root)
+{
+  GNode *new_parent_node = NULL, *node = find_node (root, child);
+
+  if (child->parent)
+    new_parent_node = find_node (root, child->parent);
+
+  if (!new_parent_node)
+    new_parent_node = root;
+
+  g_node_unlink (node);
+  g_node_prepend (new_parent_node, node);
+}
+
+void
+timeline_tree_track_element (GNode * root, GESTimelineElement * element)
+{
+  GNode *node;
+  GNode *parent;
+  GESTimelineElement *toplevel;
+
+  if (find_node (root, element)) {
+    return;
+  }
+
+  g_signal_connect (element, "notify::parent",
+      G_CALLBACK (timeline_element_parent_cb), root);
+
+  toplevel = get_toplevel_container (element);
+  if (toplevel == element) {
+    GST_DEBUG ("Tracking toplevel element %" GES_FORMAT, GES_ARGS (element));
+
+    node = g_node_prepend_data (root, element);
+  } else {
+    parent = find_node (root, element->parent);
+    GST_LOG ("%" GES_FORMAT "parent is %" GES_FORMAT, GES_ARGS (element),
+        GES_ARGS (element->parent));
+    g_assert (parent);
+    node = g_node_prepend_data (parent, element);
+  }
+
+  if (GES_IS_CONTAINER (element)) {
+    GList *tmp;
+
+    for (tmp = GES_CONTAINER_CHILDREN (element); tmp; tmp = tmp->next) {
+      GNode *child_node = find_node (root, tmp->data);
+
+      if (child_node) {
+        g_node_unlink (child_node);
+        g_node_prepend (node, child_node);
+      } else {
+        timeline_tree_track_element (root, tmp->data);
+      }
+    }
+  }
+
+  timeline_update_duration (root->data);
+}
+
+void
+timeline_tree_stop_tracking_element (GNode * root, GESTimelineElement * element)
+{
+  GNode *node = find_node (root, element);
+
+  node = find_node (root, element);
+
+  /* Move children to the parent */
+  while (node->children) {
+    GNode *tmp = node->children;
+    g_node_unlink (tmp);
+    g_node_prepend (node->parent, tmp);
+  }
+
+  g_assert (node);
+  GST_DEBUG ("Stop tracking %" GES_FORMAT, GES_ARGS (element));
+  g_signal_handlers_disconnect_by_func (element, timeline_element_parent_cb,
+      root);
+
+  g_node_destroy (node);
+  timeline_update_duration (root->data);
+}
+
+static inline gboolean
+check_can_move_to_layer (GESTimelineElement * element,
+    gint layer_priority_offset)
+{
+  return (((gint64) GES_TIMELINE_ELEMENT_LAYER_PRIORITY (element) -
+          layer_priority_offset) >= 0);
+}
+
+/*  *INDENT-OFF* */
+#define CHECK_AND_SNAP(diff_val,moving_edge_,edge_) \
+if (snapping->distance >= ABS(diff_val) && ABS(diff_val) <= ABS(snapping->diff)) { \
+  snapping->element = element; \
+  snapping->edge = edge_; \
+  snapping->moving_element = moving_elem; \
+  snapping->moving_edge = moving_edge_; \
+  snapping->diff = (diff_val); \
+  GST_LOG("Snapping %" GES_FORMAT "with %" GES_FORMAT " - diff: %" G_GINT64_FORMAT "", GES_ARGS (moving_elem), GES_ARGS(element), (diff_val)); \
+}
+
+static void
+check_snapping (GESTimelineElement * element, GESTimelineElement * moving_elem,
+    SnappingData * snapping, GstClockTime start, GstClockTime end,
+    GstClockTime moving_start, GstClockTime moving_end)
+{
+  GstClockTimeDiff snap_end_end_diff;
+  GstClockTimeDiff snap_end_start_diff;
+
+  if (element == moving_elem)
+    return;
+
+  if (!snapping || (
+      GES_IS_CLIP (element->parent) && element->parent == moving_elem->parent))
+    return;
+
+  snap_end_end_diff = (GstClockTimeDiff) moving_end - (GstClockTimeDiff) end;
+  snap_end_start_diff = (GstClockTimeDiff) moving_end - (GstClockTimeDiff) start;
+
+  GST_DEBUG("Moving [%" G_GINT64_FORMAT "-%" G_GINT64_FORMAT "] element [%" G_GINT64_FORMAT "-%" G_GINT64_FORMAT "]", moving_start, moving_end, start, end);
+  /* Prefer snapping end */
+  if (!snapping->on_start_only) {
+    CHECK_AND_SNAP(snap_end_end_diff, GES_EDGE_END, GES_EDGE_END)
+    else CHECK_AND_SNAP(snap_end_start_diff, GES_EDGE_END, GES_EDGE_START)
+  }
+
+  if (!snapping->on_end_only) {
+    GstClockTimeDiff snap_start_end_diff = GST_CLOCK_DIFF(end, moving_start);
+    GstClockTimeDiff snap_start_start_diff = GST_CLOCK_DIFF(start, moving_start);
+
+    CHECK_AND_SNAP(snap_start_end_diff, GES_EDGE_START, GES_EDGE_END)
+    else CHECK_AND_SNAP(snap_start_start_diff, GES_EDGE_START, GES_EDGE_START)
+  }
+}
+#undef CHECK_AND_SNAP
+/*  *INDENT-ON* */
+
+static gboolean
+check_track_elements_overlaps_and_values (GNode * node,
+    TreeIterationData * data)
+{
+  GESTimelineElement *e = (GESTimelineElement *) node->data;
+  GstClockTimeDiff moving_start, moving_end, start, inpoint, end, duration;
+  gint64 priority = ((gint64) GES_TIMELINE_ELEMENT_LAYER_PRIORITY (e));
+  gint64 moving_priority =
+      (gint64) GES_TIMELINE_ELEMENT_LAYER_PRIORITY (data->element) -
+      data->priority_diff;
+
+  gboolean can_overlap = e != data->element, in_movings, rippling, moving;
+
+  if (!GES_IS_SOURCE (e))
+    return FALSE;
+
+  in_movings = ! !g_list_find (data->movings, e);
+  rippling = e != data->element && !in_movings
+      && (GST_CLOCK_TIME_IS_VALID (data->ripple_time)
+      && e->start >= data->ripple_time);
+  moving = in_movings || rippling || e == data->element;
+
+  start = (GstClockTimeDiff) e->start;
+  inpoint = (GstClockTimeDiff) e->inpoint;
+  end = (GstClockTimeDiff) e->start + e->duration;
+  duration = e->duration;
+  moving_start = GST_CLOCK_DIFF (data->start_diff, data->element->start);
+  moving_end =
+      GST_CLOCK_DIFF (data->duration_diff,
+      moving_start + data->element->duration);
+
+  if (moving) {
+    if (rippling) {
+      if (data->edge == GES_EDGE_END) {
+        /* Moving as rippled from the end of a previous element */
+        start -= data->duration_diff;
+      } else
+        start -= data->start_diff;
+    } else {
+      start -= data->start_diff;
+      if (GES_TIMELINE_ELEMENT_GET_CLASS (e)->set_inpoint)
+        inpoint -= data->inpoint_diff;
+      duration -= data->duration_diff;
+    }
+    end = start + duration;
+    priority -= data->priority_diff;
+
+    GST_DEBUG ("%s %" GES_FORMAT "to [%" G_GINT64_FORMAT "(%"
+        G_GINT64_FORMAT ") - %" G_GINT64_FORMAT "] - layer: %" G_GINT64_FORMAT,
+        rippling ? "Rippling" : "Moving", GES_ARGS (e), start, inpoint,
+        duration, priority);
+  }
+
+  /* Not in the same track */
+  if (ges_track_element_get_track ((GESTrackElement *) node->data) !=
+      ges_track_element_get_track ((GESTrackElement *) data->element)) {
+    GST_LOG ("%" GES_FORMAT " and %" GES_FORMAT
+        " are not in the same track", GES_ARGS (node->data),
+        GES_ARGS (data->element));
+    can_overlap = FALSE;
+  }
+
+  /* Not in the same layer */
+  if (priority != moving_priority) {
+    GST_LOG ("%" GST_PTR_FORMAT " and %" GST_PTR_FORMAT
+        " are not on the same layer (%d != %" G_GINT64_FORMAT ")", node->data,
+        data->element, GES_TIMELINE_ELEMENT_LAYER_PRIORITY (e),
+        moving_priority);
+    can_overlap = FALSE;
+  }
+
+  if (start < 0) {
+    GST_INFO ("%" GES_FORMAT "start would be %" G_GINT64_FORMAT " < 0",
+        GES_ARGS (e), start);
+    goto error;
+  }
+
+  if (duration < 0) {
+    GST_INFO ("%" GES_FORMAT "duration would be %" G_GINT64_FORMAT " < 0",
+        GES_ARGS (e), duration);
+    goto error;
+  }
+
+  if (priority < 0) {
+    GST_INFO ("%" GES_FORMAT "priority would be %" G_GINT64_FORMAT " < 0",
+        GES_ARGS (e), priority);
+    goto error;
+  }
+
+  if (inpoint < 0) {
+    GST_INFO ("%" GES_FORMAT " can't set inpoint %" G_GINT64_FORMAT,
+        GES_ARGS (e), inpoint);
+    goto error;
+  }
+
+  if (inpoint + duration > e->maxduration) {
+    GST_INFO ("%" GES_FORMAT " inpoint + duration %" G_GINT64_FORMAT
+        " > max_duration %" G_GINT64_FORMAT,
+        GES_ARGS (e), inpoint + duration, e->maxduration);
+    goto error;
+  }
+
+  if (!moving)
+    check_snapping (e, data->element, data->snapping, start, end, moving_start,
+        moving_end);
+
+  if (!can_overlap)
+    return FALSE;
+
+  if (start > moving_end || moving_start > end) {
+    /* They do not overlap at all */
+    GST_LOG ("%" GES_FORMAT " and %" GES_FORMAT
+        " do not overlap at all.",
+        GES_ARGS (node->data), GES_ARGS (data->element));
+    return FALSE;
+  }
+
+  if ((moving_start <= start && moving_end >= end) ||
+      (moving_start >= start && moving_end <= end)) {
+    GST_INFO ("Fully overlaped: %s<%p> [%" G_GINT64_FORMAT " - %"
+        G_GINT64_FORMAT "] and %s<%p> [%" G_GINT64_FORMAT " - %" G_GINT64_FORMAT
+        " (%" G_GINT64_FORMAT ")]", e->name, e, start, end, data->element->name,
+        data->element, moving_start, moving_end, data->duration_diff);
+
+    goto error;
+  }
+
+  if (moving_start < end && moving_start > start) {
+    GST_LOG ("Overlap start: %s<%p> [%" G_GINT64_FORMAT "-%" G_GINT64_FORMAT
+        "] and %s<%p> [%" G_GINT64_FORMAT "-%" G_GINT64_FORMAT " (%"
+        G_GINT64_FORMAT ")]", e->name, e, start, end, data->element->name,
+        data->element, moving_start, moving_end, data->duration_diff);
+    if (data->overlaping_on_start) {
+      GST_INFO ("Clip is overlapped by %s and %s at its start",
+          data->overlaping_on_start->name, e->name);
+      goto error;
+    }
+
+    data->overlaping_on_start = node->data;
+  } else if (moving_end > end && end > moving_start) {
+    GST_LOG ("Overlap end: %s<%p> [%" G_GINT64_FORMAT "-%" G_GINT64_FORMAT
+        "] and %s<%p> [%" G_GINT64_FORMAT "-%" G_GINT64_FORMAT " (%"
+        G_GINT64_FORMAT ")]", e->name, e, start, end, data->element->name,
+        data->element, moving_start, moving_end, data->duration_diff);
+
+    if (data->overlaping_on_end) {
+      GST_INFO ("Clip is overlapped by %s and %s at its end",
+          data->overlaping_on_end->name, e->name);
+      goto error;
+    }
+    data->overlaping_on_end = node->data;
+  }
+
+  return FALSE;
+
+error:
+  data->res = FALSE;
+  return TRUE;
+}
+
+static gboolean
+check_can_move_children (GNode * node, TreeIterationData * data)
+{
+  GESTimelineElement *element = node->data;
+  GstClockTimeDiff start = GST_CLOCK_DIFF (data->start_diff, element->start);
+  GstClockTime inpoint = GST_CLOCK_DIFF (data->inpoint_diff, element->inpoint);
+  GstClockTime duration =
+      GST_CLOCK_DIFF (data->duration_diff, element->duration);
+  gint64 priority =
+      (gint64) GES_TIMELINE_ELEMENT_LAYER_PRIORITY (element) -
+      data->priority_diff;
+  if (element == data->element)
+    return FALSE;
+
+  data->res =
+      timeline_tree_can_move_element_internal (data->root, node->data, priority,
+      start, inpoint, duration, data->movings, data->ripple_time,
+      data->snapping, data->edge);
+
+  return !data->res;
+}
+
+static gboolean
+timeline_tree_can_move_element_from_data (GNode * root,
+    TreeIterationData * data)
+{
+  GNode *node = find_node (root, data->element);
+
+  g_assert (node);
+  if (G_NODE_IS_LEAF (node)) {
+    if (GES_IS_SOURCE (node->data)) {
+      g_node_traverse (root, G_IN_ORDER, G_TRAVERSE_LEAVES, -1,
+          (GNodeTraverseFunc) check_track_elements_overlaps_and_values, data);
+
+      return data->res;
+    }
+
+    return TRUE;
+  }
+
+  g_node_traverse (node, G_IN_ORDER, G_TRAVERSE_LEAFS, -1,
+      (GNodeTraverseFunc) check_can_move_children, data);
+
+  return data->res;
+}
+
+static gboolean
+add_element_to_list (GNode * node, GList ** elements)
+{
+  *elements = g_list_prepend (*elements, node->data);
+
+  return FALSE;
+}
+
+static gboolean
+timeline_tree_can_move_element_internal (GNode * root,
+    GESTimelineElement * element, gint64 priority, GstClockTimeDiff start,
+    GstClockTimeDiff inpoint, GstClockTimeDiff duration,
+    GList * moving_track_elements, GstClockTime ripple_time,
+    SnappingData * snapping, GESEdge edge)
+{
+  gboolean res;
+  TreeIterationData data = tree_iteration_data_init;
+
+  data.root = root;
+  data.start_diff = GST_CLOCK_DIFF (start, element->start);
+  data.inpoint_diff = GST_CLOCK_DIFF (inpoint, element->inpoint);
+  data.duration_diff = GST_CLOCK_DIFF (duration, element->duration);
+  data.priority_diff =
+      (gint64) GES_TIMELINE_ELEMENT_LAYER_PRIORITY (element) - priority;
+  data.element = element;
+  data.movings = g_list_copy (moving_track_elements);
+  data.ripple_time = ripple_time;
+  data.snapping = snapping;
+  data.edge = edge;
+
+  if (GES_IS_SOURCE (element))
+    data.priority_diff =
+        GES_TIMELINE_ELEMENT_LAYER_PRIORITY (element) - priority;
+
+  res = timeline_tree_can_move_element_from_data (root, &data);
+  clean_iteration_data (&data);
+
+  return res;
+}
+
+gboolean
+timeline_tree_can_move_element (GNode * root,
+    GESTimelineElement * element, guint32 priority, GstClockTime start,
+    GstClockTime duration, GList * moving_track_elements)
+{
+  GESTimelineElement *toplevel;
+  GstClockTimeDiff start_offset, duration_offset;
+  gint64 priority_diff;
+
+  toplevel = get_toplevel_container (element);
+  if (ELEMENT_FLAG_IS_SET (element, GES_TIMELINE_ELEMENT_SET_SIMPLE) ||
+      ELEMENT_FLAG_IS_SET (toplevel, GES_TIMELINE_ELEMENT_SET_SIMPLE))
+    return TRUE;
+
+  start_offset = GST_CLOCK_DIFF (start, element->start);
+  duration_offset = GST_CLOCK_DIFF (duration, element->duration);
+  priority_diff =
+      (gint64) GES_TIMELINE_ELEMENT_LAYER_PRIORITY (toplevel) -
+      (gint64) priority;
+
+  g_node_traverse (find_node (root, toplevel), G_IN_ORDER,
+      G_TRAVERSE_LEAVES, -1, (GNodeTraverseFunc) add_element_to_list,
+      &moving_track_elements);
+
+  return timeline_tree_can_move_element_internal (root, toplevel,
+      GES_TIMELINE_ELEMENT_LAYER_PRIORITY (toplevel) - priority_diff,
+      GST_CLOCK_DIFF (start_offset, toplevel->start),
+      toplevel->inpoint,
+      GST_CLOCK_DIFF (duration_offset, toplevel->duration),
+      moving_track_elements, GST_CLOCK_TIME_NONE, NULL, GES_EDGE_NONE);
+}
+
+static void
+move_to_new_layer (GESTimelineElement * elem, gint layer_priority_offset)
+{
+  guint32 nprio =
+      GES_TIMELINE_ELEMENT_LAYER_PRIORITY (elem) - layer_priority_offset;
+  GESTimeline *timeline = GES_TIMELINE_ELEMENT_TIMELINE (elem);
+
+  if (!layer_priority_offset)
+    return;
+
+  GST_DEBUG ("%s moving from %" G_GUINT32_FORMAT " to %" G_GUINT32_FORMAT " (%"
+      G_GUINT32_FORMAT ")", elem->name, elem->priority, nprio,
+      layer_priority_offset);
+  if (GES_IS_CLIP (elem)) {
+    GESLayer *layer = ges_timeline_get_layer (timeline, nprio);
+
+    if (layer == NULL) {
+      do {
+        layer = ges_timeline_append_layer (timeline);
+      } while (ges_layer_get_priority (layer) < nprio);
+    } else {
+      gst_object_unref (layer);
+    }
+
+    ges_clip_move_to_layer (GES_CLIP (elem), layer);
+  } else if (GES_IS_GROUP (elem)) {
+    ges_timeline_element_set_priority (elem, nprio);
+  } else {
+    g_assert_not_reached ();
+  }
+}
+
+gboolean
+timeline_tree_ripple (GNode * root, gint64 layer_priority_offset,
+    GstClockTimeDiff offset, GESTimelineElement * rippled_element,
+    GESEdge edge, GstClockTime snapping_distance)
+{
+  GNode *node;
+  GHashTableIter iter;
+  GESTimelineElement *elem;
+  GstClockTimeDiff start, duration;
+  gboolean res = TRUE;
+  GHashTable *to_move = g_hash_table_new (g_direct_hash, g_direct_equal);
+  GList *moving_track_elements = NULL;
+  SnappingData snapping = {
+    .distance = snapping_distance,
+    .on_end_only = edge == GES_EDGE_END,
+    .on_start_only = FALSE,
+    .element = NULL,
+    .edge = GES_EDGE_NONE,
+    .diff = (GstClockTimeDiff) snapping_distance,
+  };
+  gint64 new_layer_priority =
+      ((gint64) GES_TIMELINE_ELEMENT_LAYER_PRIORITY (rippled_element)) -
+      layer_priority_offset;
+  GESTimelineElement *ripple_toplevel =
+      get_toplevel_container (rippled_element);
+  GstClockTimeDiff ripple_time = ELEMENT_EDGE_VALUE (rippled_element, edge);
+
+  if (edge == GES_EDGE_END) {
+    if (ripple_toplevel != rippled_element) {
+      GST_FIXME ("Trying to ripple end %" GES_FORMAT " but in %" GES_FORMAT
+          " we do not know how to do that yet!",
+          GES_ARGS (rippled_element), GES_ARGS (ripple_toplevel));
+      goto error;
+    }
+  } else {
+    g_node_traverse (find_node (root, ripple_toplevel), G_IN_ORDER,
+        G_TRAVERSE_LEAVES, -1, (GNodeTraverseFunc) add_element_to_list,
+        &moving_track_elements);
+  }
+
+  GST_INFO ("Moving %" GES_FORMAT " with offset %" G_GINT64_FORMAT "",
+      GES_ARGS (ripple_toplevel), offset);
+
+  if (edge == GES_EDGE_END) {
+    start = _START (rippled_element);
+    duration = GST_CLOCK_DIFF (offset, _DURATION (rippled_element));
+  } else {
+    start = GST_CLOCK_DIFF (offset, _START (rippled_element));
+    duration = _DURATION (rippled_element);
+  }
+
+  if (!timeline_tree_can_move_element_internal (root, rippled_element,
+          new_layer_priority, start, rippled_element->inpoint, duration, NULL,
+          ripple_time, snapping_distance ? &snapping : NULL, edge)) {
+    goto error;
+  }
+
+  if (snapping_distance) {
+    if (snapping.element) {
+      offset =
+          GST_CLOCK_DIFF (ELEMENT_EDGE_VALUE (snapping.element, snapping.edge),
+          ELEMENT_EDGE_VALUE (snapping.moving_element, snapping.moving_edge));
+
+      if (edge == GES_EDGE_END) {
+        start = _START (rippled_element);
+        duration = GST_CLOCK_DIFF (offset, _DURATION (rippled_element));
+      } else {
+        start = GST_CLOCK_DIFF (offset, _START (rippled_element));
+        duration = _DURATION (rippled_element);
+      }
+
+      GST_INFO ("Snapping on %" GES_FORMAT "%s %" G_GINT64_FORMAT "",
+          GES_ARGS (snapping.element),
+          snapping.edge == GES_EDGE_END ? "end" : "start",
+          ELEMENT_EDGE_VALUE (snapping.element, snapping.edge));
+      if (!timeline_tree_can_move_element_internal (root, rippled_element,
+              new_layer_priority, start, rippled_element->inpoint, duration,
+              NULL, ripple_time, NULL, edge)) {
+        goto error;
+      }
+    }
+
+    ges_timeline_emit_snapping (root->data, rippled_element, snapping.element,
+        snapping.element ? ELEMENT_EDGE_VALUE (snapping.element,
+            snapping.edge) : GST_CLOCK_TIME_NONE);
+  }
+
+  /* Make sure we can ripple all toplevels after the rippled element */
+  for (node = root->children; node; node = node->next) {
+    GESTimelineElement *toplevel = get_toplevel_container (node->data);
+
+    if (GES_TIMELINE_ELEMENT_START (toplevel) < ripple_time
+        && (edge == GES_EDGE_END || toplevel != ripple_toplevel))
+      continue;
+
+    if (!timeline_tree_can_move_element_internal (root, node->data,
+            ((gint64) GES_TIMELINE_ELEMENT_LAYER_PRIORITY (node->data)) -
+            layer_priority_offset,
+            GST_CLOCK_DIFF (offset, _START (node->data)),
+            _INPOINT (node->data),
+            _DURATION (node->data), moving_track_elements, ripple_time, NULL,
+            GES_EDGE_NONE)) {
+      goto error;
+    }
+
+    if (!check_can_move_to_layer (toplevel, layer_priority_offset)) {
+      GST_INFO ("%" GES_FORMAT " would land in a layer with negative priority",
+          GES_ARGS (toplevel));
+      goto error;
+    }
+
+    g_hash_table_add (to_move, toplevel);
+  }
+
+  if (edge == GES_EDGE_END) {
+    if (!check_can_move_to_layer (rippled_element, layer_priority_offset)) {
+      GST_INFO ("%" GES_FORMAT " would land in a layer with negative priority",
+          GES_ARGS (rippled_element));
+
+      goto error;
+    }
+
+    if (duration < 0) {
+      GST_INFO ("Would set duration to  %" G_GINT64_FORMAT " <= 0", duration);
+      goto error;
+    }
+
+    ELEMENT_SET_FLAG (rippled_element, GES_TIMELINE_ELEMENT_SET_SIMPLE);
+    ges_timeline_element_set_duration (rippled_element, duration);
+    ELEMENT_UNSET_FLAG (rippled_element, GES_TIMELINE_ELEMENT_SET_SIMPLE);
+  }
+
+  g_hash_table_iter_init (&iter, to_move);
+  while (g_hash_table_iter_next (&iter, (gpointer *) & elem, NULL)) {
+    GST_LOG ("Moving %" GES_FORMAT " to %" G_GINT64_FORMAT " - layer %"
+        G_GINT64_FORMAT "", GES_ARGS (elem),
+        GES_TIMELINE_ELEMENT_START (elem) - offset,
+        (gint64) GES_TIMELINE_ELEMENT_LAYER_PRIORITY (elem) -
+        layer_priority_offset);
+
+    ELEMENT_SET_FLAG (elem, GES_TIMELINE_ELEMENT_SET_SIMPLE);
+    ges_timeline_element_set_start (elem,
+        GST_CLOCK_DIFF (offset, GES_TIMELINE_ELEMENT_START (elem)));
+    move_to_new_layer (elem, layer_priority_offset);
+    ELEMENT_UNSET_FLAG (elem, GES_TIMELINE_ELEMENT_SET_SIMPLE);
+  }
+
+  ELEMENT_SET_FLAG (rippled_element, GES_TIMELINE_ELEMENT_SET_SIMPLE);
+  if (edge == GES_EDGE_END)
+    move_to_new_layer (rippled_element, layer_priority_offset);
+  ELEMENT_UNSET_FLAG (rippled_element, GES_TIMELINE_ELEMENT_SET_SIMPLE);
+
+  timeline_tree_create_transitions (root, ges_timeline_find_auto_transition);
+  timeline_update_transition (root->data);
+  timeline_update_duration (root->data);
+
+done:
+  g_hash_table_unref (to_move);
+  g_list_free (moving_track_elements);
+  return res;
+
+error:
+  res = FALSE;
+  goto done;
+}
+
+static gboolean
+check_trim_child (GNode * node, TreeIterationData * data)
+{
+  GESTimelineElement *e = node->data;
+  GstClockTimeDiff n_start = GST_CLOCK_DIFF (data->start_diff, e->start);
+  GstClockTimeDiff n_inpoint = GST_CLOCK_DIFF (data->inpoint_diff, e->inpoint);
+  GstClockTimeDiff n_duration = data->edge == GES_EDGE_END ?
+      GST_CLOCK_DIFF (data->duration_diff, e->duration) :
+      GST_CLOCK_DIFF (n_start, (GstClockTimeDiff) e->start + e->duration);
+
+  if (!timeline_tree_can_move_element_internal (data->root, e,
+          (gint64) ges_timeline_element_get_layer_priority (e) -
+          data->priority_diff, n_start, n_inpoint, n_duration, NULL,
+          GST_CLOCK_TIME_NONE, data->snapping, GES_EDGE_NONE))
+    goto error;
+
+  if (GES_IS_CLIP (e->parent))
+    g_hash_table_add (data->moved_clips, e->parent);
+  else if (GES_IS_CLIP (e))
+    g_hash_table_add (data->moved_clips, e);
+
+  return FALSE;
+
+error:
+  data->res = FALSE;
+
+  return TRUE;
+}
+
+static gboolean
+timeline_tree_can_trim_element_internal (GNode * root, TreeIterationData * data)
+{
+  g_node_traverse (find_node (root, data->element), G_IN_ORDER,
+      G_TRAVERSE_LEAVES, -1, (GNodeTraverseFunc) check_trim_child, data);
+
+  return data->res;
+}
+
+static void
+trim_simple (GESTimelineElement * element, GstClockTimeDiff offset,
+    GESEdge edge)
+{
+  ELEMENT_SET_FLAG (element, GES_TIMELINE_ELEMENT_SET_SIMPLE);
+  if (edge == GES_EDGE_END) {
+    ges_timeline_element_set_duration (element, GST_CLOCK_DIFF (offset,
+            element->duration));
+  } else {
+    ges_timeline_element_set_start (element, GST_CLOCK_DIFF (offset,
+            element->start));
+    ges_timeline_element_set_inpoint (element, GST_CLOCK_DIFF (offset,
+            element->inpoint));
+    ges_timeline_element_set_duration (element, element->duration + offset);
+  }
+  GST_LOG ("Trimmed %" GES_FORMAT, GES_ARGS (element));
+  ELEMENT_UNSET_FLAG (element, GES_TIMELINE_ELEMENT_SET_SIMPLE);
+}
+
+#define SET_TRIMMING_DATA(data, _edge, offset) G_STMT_START { \
+  data.edge = (_edge);                                           \
+  data.start_diff = (_edge) == GES_EDGE_END ? 0 : (offset); \
+  data.inpoint_diff = (_edge) == GES_EDGE_END ? 0 : (offset); \
+  data.duration_diff = (_edge) == GES_EDGE_END ? (offset) : -(offset); \
+} G_STMT_END
+
+
+gboolean
+timeline_tree_trim (GNode * root, GESTimelineElement * element,
+    gint64 layer_priority_offset, GstClockTimeDiff offset, GESEdge edge,
+    GstClockTime snapping_distance)
+{
+  GHashTableIter iter;
+  gboolean res = TRUE;
+  GESTimelineElement *elem;
+  gint64 new_layer_priority =
+      ((gint64) GES_TIMELINE_ELEMENT_LAYER_PRIORITY (element)) -
+      layer_priority_offset;
+  SnappingData snapping = {
+    .distance = snapping_distance,
+    .on_end_only = edge == GES_EDGE_END,
+    .on_start_only = edge != GES_EDGE_END,
+    .element = NULL,
+    .edge = GES_EDGE_NONE,
+    .diff = (GstClockTimeDiff) snapping_distance,
+  };
+  TreeIterationData data = tree_iteration_data_init;
+
+  data.root = root;
+  data.element = element;
+  data.priority_diff =
+      (gint64) ges_timeline_element_get_layer_priority (element) -
+      new_layer_priority;
+  data.snapping = snapping_distance ? &snapping : NULL;
+  data.moved_clips = g_hash_table_new (g_direct_hash, g_direct_equal);
+
+  SET_TRIMMING_DATA (data, edge, offset);
+  GST_INFO ("%" GES_FORMAT " trimming %s with offset %" G_GINT64_FORMAT "",
+      GES_ARGS (element), edge == GES_EDGE_END ? "end" : "start", offset);
+  g_node_traverse (find_node (root, element), G_IN_ORDER,
+      G_TRAVERSE_LEAVES, -1, (GNodeTraverseFunc) add_element_to_list,
+      &data.movings);
+
+  if (!timeline_tree_can_trim_element_internal (root, &data)) {
+    GST_INFO ("Can not trim object.");
+    goto error;
+  }
+
+  if (snapping_distance) {
+    if (snapping.element) {
+      offset =
+          GST_CLOCK_DIFF (ELEMENT_EDGE_VALUE (snapping.element, snapping.edge),
+          ELEMENT_EDGE_VALUE (snapping.moving_element, snapping.moving_edge));
+
+      GST_INFO ("Snapping on %" GES_FORMAT "%s %" G_GINT64_FORMAT
+          " -- offset: %" G_GINT64_FORMAT "", GES_ARGS (snapping.element),
+          snapping.edge == GES_EDGE_END ? "end" : "start",
+          ELEMENT_EDGE_VALUE (snapping.element, snapping.edge), offset);
+    }
+
+    ges_timeline_emit_snapping (root->data, element,
+        snapping.element,
+        snapping.element ? ELEMENT_EDGE_VALUE (snapping.element,
+            snapping.edge) : GST_CLOCK_TIME_NONE);
+  }
+
+  g_hash_table_iter_init (&iter, data.moved_clips);
+  while (g_hash_table_iter_next (&iter, (gpointer *) & elem, NULL))
+    trim_simple (elem, offset, edge);
+
+  timeline_tree_create_transitions (root, ges_timeline_find_auto_transition);
+  timeline_update_transition (root->data);
+  timeline_update_duration (root->data);
+
+  return TRUE;
+
+done:
+  clean_iteration_data (&data);
+  return res;
+
+error:
+  res = FALSE;
+  goto done;
+}
+
+gboolean
+timeline_tree_move (GNode * root, GESTimelineElement * element,
+    gint64 layer_priority_offset, GstClockTimeDiff offset, GESEdge edge,
+    GstClockTime snapping_distance)
+{
+  gboolean res = TRUE;
+  GESTimelineElement *toplevel = get_toplevel_container (element);
+  TreeIterationData data = tree_iteration_data_init;
+  SnappingData snapping = {
+    .distance = snapping_distance,
+    .on_end_only = edge == GES_EDGE_END,
+    .on_start_only = edge == GES_EDGE_END,
+    .element = NULL,
+    .edge = GES_EDGE_NONE,
+    .diff = (GstClockTimeDiff) snapping_distance,
+  };
+
+  data.root = root;
+  data.element = edge == GES_EDGE_END ? element : toplevel;
+  data.edge = edge;
+  data.priority_diff = layer_priority_offset;
+  data.snapping = snapping_distance ? &snapping : NULL;
+  data.start_diff = edge == GES_EDGE_END ? 0 : offset;
+  data.duration_diff = edge == GES_EDGE_END ? offset : 0;
+
+  GST_INFO ("%" GES_FORMAT
+      " moving %s with offset %" G_GINT64_FORMAT ", (snaping distance: %"
+      G_GINT64_FORMAT ")", GES_ARGS (element),
+      edge == GES_EDGE_END ? "end" : "start", offset, snapping_distance);
+  g_node_traverse (find_node (root, data.element), G_IN_ORDER,
+      G_TRAVERSE_LEAVES, -1, (GNodeTraverseFunc) add_element_to_list,
+      &data.movings);
+
+  if (!timeline_tree_can_move_element_from_data (root, &data)) {
+    GST_INFO ("Can not move object.");
+    return FALSE;
+  }
+
+  if (snapping_distance) {
+    if (snapping.element) {
+      gint64 noffset =
+          GST_CLOCK_DIFF (ELEMENT_EDGE_VALUE (snapping.element, snapping.edge),
+          ELEMENT_EDGE_VALUE (snapping.moving_element, snapping.moving_edge));
+
+      GST_INFO ("Snapping %" GES_FORMAT " (%s) with %" GES_FORMAT
+          "%s %" G_GINT64_FORMAT " -- offset: %" G_GINT64_FORMAT
+          " (previous offset: %" G_GINT64_FORMAT ")",
+          GES_ARGS (snapping.moving_element),
+          snapping.moving_edge == GES_EDGE_END ? "end" : "start",
+          GES_ARGS (snapping.element),
+          snapping.edge == GES_EDGE_END ? "end" : "start",
+          ELEMENT_EDGE_VALUE (snapping.element, snapping.edge), noffset,
+          offset);
+      offset = noffset;
+      data.start_diff = edge == GES_EDGE_END ? 0 : offset;
+      data.duration_diff = edge == GES_EDGE_END ? offset : 0;
+      data.snapping = NULL;
+      if (!timeline_tree_can_move_element_from_data (root, &data)) {
+        GST_INFO ("Can not move object.");
+        goto error;
+      }
+    }
+
+    ges_timeline_emit_snapping (root->data, element,
+        snapping.element,
+        snapping.element ? ELEMENT_EDGE_VALUE (snapping.element,
+            snapping.edge) : GST_CLOCK_TIME_NONE);
+  }
+
+  if (!check_can_move_to_layer (toplevel, layer_priority_offset)) {
+    GST_INFO ("%" GES_FORMAT " would land in a layer with negative priority",
+        GES_ARGS (toplevel));
+    goto error;
+  }
+
+  ELEMENT_SET_FLAG (toplevel, GES_TIMELINE_ELEMENT_SET_SIMPLE);
+  if (edge == GES_EDGE_END)
+    ges_timeline_element_set_duration (element, GST_CLOCK_DIFF (offset,
+            element->duration));
+  else
+    ges_timeline_element_set_start (toplevel, GST_CLOCK_DIFF (offset,
+            toplevel->start));
+  move_to_new_layer (toplevel, layer_priority_offset);
+  ELEMENT_UNSET_FLAG (toplevel, GES_TIMELINE_ELEMENT_SET_SIMPLE);
+
+  timeline_tree_create_transitions (root, ges_timeline_find_auto_transition);
+  timeline_update_transition (root->data);
+  timeline_update_duration (root->data);
+
+  GST_LOG ("Moved %" GES_FORMAT, GES_ARGS (element));
+
+  return res;
+
+error:
+  return FALSE;
+}
+
+static gboolean
+find_neighbour (GNode * node, TreeIterationData * data)
+{
+  gboolean in_same_track = FALSE;
+  GList *tmp;
+
+  if (!GES_IS_SOURCE (node->data)) {
+    return FALSE;
+  }
+
+
+  for (tmp = GES_CONTAINER_CHILDREN (data->element); tmp; tmp = tmp->next) {
+    if (tmp->data == node->data)
+      return FALSE;
+
+    if (ges_track_element_get_track (node->data) ==
+        ges_track_element_get_track (tmp->data)) {
+      in_same_track = TRUE;
+    }
+  }
+
+  if (!in_same_track) {
+    return FALSE;
+  }
+
+  if (ELEMENT_EDGE_VALUE (node->data,
+          data->edge == GES_EDGE_START ? GES_EDGE_END : GES_EDGE_START) ==
+      ELEMENT_EDGE_VALUE (data->element, data->edge)) {
+    if (!g_list_find (data->neighbours,
+            GES_TIMELINE_ELEMENT_PARENT (node->data)))
+      data->neighbours =
+          g_list_prepend (data->neighbours,
+          GES_TIMELINE_ELEMENT_PARENT (node->data));
+  }
+
+  return FALSE;
+}
+
+gboolean
+timeline_tree_roll (GNode * root, GESTimelineElement * element,
+    GstClockTimeDiff offset, GESEdge edge, GstClockTime snapping_distance)
+{
+  gboolean res = TRUE;
+  GList *tmp;
+  GESEdge neighbour_edge;
+  TreeIterationData data = tree_iteration_data_init;
+  SnappingData snapping = {
+    .distance = snapping_distance,
+    .on_end_only = edge == GES_EDGE_END,
+    .on_start_only = edge == GES_EDGE_END,
+    .element = NULL,
+    .edge = GES_EDGE_NONE,
+    .moving_edge = GES_EDGE_NONE,
+    .diff = (GstClockTimeDiff) snapping_distance,
+  };
+
+  data.root = root;
+  data.element = element;
+  data.edge = edge;
+  data.snapping = snapping_distance ? &snapping : NULL;
+  data.start_diff = edge == GES_EDGE_END ? 0 : offset;
+  data.inpoint_diff = edge == GES_EDGE_END ? 0 : offset;
+  data.duration_diff = edge == GES_EDGE_END ? offset : -offset;
+  data.ripple_time = GST_CLOCK_TIME_NONE;
+  neighbour_edge = data.edge == GES_EDGE_END ? GES_EDGE_START : GES_EDGE_END;
+
+  SET_TRIMMING_DATA (data, edge, offset);
+  g_node_traverse (root, G_PRE_ORDER, G_TRAVERSE_LEAFS, -1,
+      (GNodeTraverseFunc) find_neighbour, &data);
+
+  if (data.neighbours == NULL) {
+    GST_INFO ("%s doesn't have any direct neighbour on edge %s",
+        element->name, ges_edge_name (edge));
+
+    return timeline_tree_trim (root, element, 0, offset, edge,
+        snapping_distance);
+  }
+
+  GST_INFO ("Trimming %" GES_FORMAT " %s to %" G_GINT64_FORMAT "",
+      GES_ARGS (data.element), ges_edge_name (edge), offset);
+
+  if (!timeline_tree_can_move_element_from_data (root, &data))
+    goto error;
+
+
+  if (snapping_distance) {
+    if (snapping.element) {
+      gint64 noffset =
+          GST_CLOCK_DIFF (ELEMENT_EDGE_VALUE (snapping.element, snapping.edge),
+          ELEMENT_EDGE_VALUE (snapping.moving_element, snapping.moving_edge));
+
+      GST_INFO ("Snapping %" GES_FORMAT " (%s) with %" GES_FORMAT
+          "%s %" G_GINT64_FORMAT " -- offset: %" G_GINT64_FORMAT
+          " (previous offset: %" G_GINT64_FORMAT ")",
+          GES_ARGS (snapping.moving_element),
+          snapping.moving_edge == GES_EDGE_END ? "end" : "start",
+          GES_ARGS (snapping.element),
+          snapping.edge == GES_EDGE_END ? "end" : "start",
+          ELEMENT_EDGE_VALUE (snapping.element, snapping.edge), noffset,
+          offset);
+      offset = noffset;
+
+      SET_TRIMMING_DATA (data, edge, offset);
+
+      if (!timeline_tree_can_move_element_from_data (root, &data)) {
+        GST_INFO ("Can not move object.");
+        goto error;
+      }
+    }
+  }
+
+  if (snapping_distance && snapping.element) {
+    ges_timeline_emit_snapping (root->data, element,
+        snapping.element,
+        snapping.element ? ELEMENT_EDGE_VALUE (snapping.element,
+            snapping.edge) : GST_CLOCK_TIME_NONE);
+  }
+
+  data.snapping = NULL;
+  SET_TRIMMING_DATA (data, neighbour_edge, offset);
+  for (tmp = data.neighbours; tmp; tmp = tmp->next) {
+    data.element = tmp->data;
+
+    GST_INFO ("Trimming %" GES_FORMAT " %s to %" G_GINT64_FORMAT "",
+        GES_ARGS (data.element), ges_edge_name (data.edge), offset);
+    if (!timeline_tree_can_move_element_from_data (root, &data)) {
+      GST_INFO ("Can not move object.");
+      goto error;
+    }
+  }
+
+  trim_simple (element, offset, edge);
+  for (tmp = data.neighbours; tmp; tmp = tmp->next)
+    trim_simple (tmp->data, offset, data.edge);
+
+done:
+  timeline_update_duration (root->data);
+  return res;
+
+error:
+  res = FALSE;
+  goto done;
+}
+
+static void
+create_transition_if_needed (GESTimeline * timeline, GESTrackElement * prev,
+    GESTrackElement * next, GESTreeGetAutoTransitionFunc get_auto_transition)
+{
+  GstClockTime duration = _END (prev) - _START (next);
+  GESAutoTransition *trans =
+      get_auto_transition (timeline, prev, next, duration);
+
+  if (!trans) {
+    GESLayer *layer = ges_timeline_get_layer (timeline,
+        GES_TIMELINE_ELEMENT_LAYER_PRIORITY (prev));
+    gst_object_unref (layer);
+
+    GST_INFO ("Creating transition [%" G_GINT64_FORMAT " - %" G_GINT64_FORMAT
+        "]", _START (next), duration);
+    ges_timeline_create_transition (timeline, prev, next, NULL, layer,
+        _START (next), duration);
+  }
+}
+
+static gboolean
+create_transitions (GNode * node,
+    GESTreeGetAutoTransitionFunc get_auto_transition)
+{
+  TreeIterationData data = tree_iteration_data_init;
+  GESTimeline *timeline;
+  GESLayer *layer;
+
+  if (G_NODE_IS_ROOT (node))
+    return FALSE;
+
+  timeline = GES_TIMELINE_ELEMENT_TIMELINE (node->data);
+  data.element = node->data;
+  if (!GES_IS_SOURCE (node->data))
+    return FALSE;
+
+  if (!timeline) {
+    GST_INFO ("%" GES_FORMAT " not in timeline yet", GES_ARGS (node->data));
+
+    return FALSE;
+  }
+
+  layer =
+      ges_timeline_get_layer (timeline,
+      GES_TIMELINE_ELEMENT_LAYER_PRIORITY (node->data));
+  gst_object_unref (layer);
+
+  if (!ges_layer_get_auto_transition (layer))
+    return FALSE;
+
+  g_node_traverse (g_node_get_root (node), G_IN_ORDER, G_TRAVERSE_LEAVES, -1,
+      (GNodeTraverseFunc) check_track_elements_overlaps_and_values, &data);
+
+  if (data.overlaping_on_start)
+    create_transition_if_needed (timeline,
+        GES_TRACK_ELEMENT (data.overlaping_on_start), node->data,
+        get_auto_transition);
+
+  if (data.overlaping_on_end)
+    create_transition_if_needed (timeline, node->data,
+        GES_TRACK_ELEMENT (data.overlaping_on_end), get_auto_transition);
+
+  return FALSE;
+}
+
+void
+timeline_tree_create_transitions (GNode * root,
+    GESTreeGetAutoTransitionFunc get_auto_transition)
+{
+  g_node_traverse (root, G_PRE_ORDER, G_TRAVERSE_LEAFS, -1,
+      (GNodeTraverseFunc) create_transitions, get_auto_transition);
+}
+
+static gboolean
+compute_duration (GNode * node, GstClockTime * duration)
+{
+  *duration = MAX (_END (node->data), *duration);
+
+  return FALSE;
+}
+
+GstClockTime
+timeline_tree_get_duration (GNode * root)
+{
+  GstClockTime duration = 0;
+
+  if (root->children)
+    g_node_traverse (root, G_PRE_ORDER, G_TRAVERSE_LEAFS, -1,
+        (GNodeTraverseFunc) compute_duration, &duration);
+
+  return duration;
+}
diff --git a/ges/ges-timeline-tree.h b/ges/ges-timeline-tree.h
new file mode 100644 (file)
index 0000000..e21c748
--- /dev/null
@@ -0,0 +1,79 @@
+#ifndef __GES_TIMELINE_TREE__
+#define __GES_TIMELINE_TREE__
+
+#include <ges/ges.h>
+#include "ges-auto-transition.h"
+
+void timeline_tree_track_element          (GNode *root,
+                                           GESTimelineElement *element);
+
+void timeline_tree_stop_tracking_element  (GNode *root,
+                                           GESTimelineElement *element);
+
+gboolean timeline_tree_can_move_element   (GNode *root,
+                                           GESTimelineElement *element,
+                                           guint32 priority,
+                                           GstClockTime start,
+                                           GstClockTime duration,
+                                           GList *moving_track_elements);
+
+gboolean timeline_tree_ripple             (GNode *root,
+                                           gint64 layer_priority_offset,
+                                           GstClockTimeDiff offset,
+                                           GESTimelineElement *rippled_element,
+                                           GESEdge moving_edge,
+                                           GstClockTime snapping_distance);
+
+void ges_timeline_emit_snapping           (GESTimeline * timeline,
+                                           GESTimelineElement * elem1,
+                                           GESTimelineElement * elem2,
+                                           GstClockTime snap_time);
+
+gboolean timeline_tree_trim               (GNode *root,
+                                           GESTimelineElement *element,
+                                           gint64 layer_priority_offset,
+                                           GstClockTimeDiff offset,
+                                           GESEdge edge,
+                                           GstClockTime snapping_distance);
+
+
+gboolean timeline_tree_move               (GNode *root,
+                                           GESTimelineElement *element,
+                                           gint64 layer_priority_offset,
+                                           GstClockTimeDiff offset,
+                                           GESEdge edge,
+                                           GstClockTime snapping_distance);
+
+gboolean timeline_tree_roll               (GNode * root,
+                                           GESTimelineElement * element,
+                                           GstClockTimeDiff offset,
+                                           GESEdge edge,
+                                           GstClockTime snapping_distance);
+
+typedef GESAutoTransition *
+(*GESTreeGetAutoTransitionFunc)           (GESTimeline * timeline,
+                                           GESTrackElement * previous,
+                                           GESTrackElement * next,
+                                           GstClockTime transition_duration);
+
+void timeline_tree_create_transitions     (GNode *root,
+                                           GESTreeGetAutoTransitionFunc get_auto_transition);
+
+GstClockTime timeline_tree_get_duration   (GNode *root);
+
+void timeline_tree_debug                  (GNode * root);
+
+GESAutoTransition *
+ges_timeline_create_transition           (GESTimeline * timeline, GESTrackElement * previous,
+                                           GESTrackElement * next, GESClip * transition,
+                                           GESLayer * layer, guint64 start, guint64 duration);
+GESAutoTransition *
+ges_timeline_find_auto_transition         (GESTimeline * timeline, GESTrackElement * prev,
+                                           GESTrackElement * next, GstClockTime transition_duration);
+
+void
+timeline_update_duration                  (GESTimeline * timeline);
+
+void timeline_tree_init_debug             (void);
+
+#endif // __GES_TIMELINE_TREE__
index 62b1210..125da3f 100644 (file)
@@ -4,6 +4,8 @@
  *               2012 Thibault Saunier <tsaunier@gnome.org>
  *               2012 Collabora Ltd.
  *                 Author: Sebastian Dröge <sebastian.droege@collabora.co.uk>
+ *               2019 Igalia S.L
+ *                 Author: Thibault Saunier <tsaunier@igalia.com>
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Library General Public
 #include "ges-project.h"
 #include "ges-container.h"
 #include "ges-timeline.h"
+#include "ges-timeline-tree.h"
 #include "ges-track.h"
 #include "ges-layer.h"
 #include "ges-auto-transition.h"
 #include "ges.h"
 
-typedef struct _MoveContext MoveContext;
 
 static GPtrArray *select_tracks_for_object_default (GESTimeline * timeline,
     GESClip * clip, GESTrackElement * tr_obj, gpointer user_data);
-static inline void init_movecontext (MoveContext * mv_ctx, gboolean first_init);
 static void ges_extractable_interface_init (GESExtractableInterface * iface);
 static void ges_meta_container_interface_init
     (GESMetaContainerInterface * iface);
@@ -86,110 +87,30 @@ GST_DEBUG_CATEGORY_STATIC (ges_timeline_debug);
 
 #define CHECK_THREAD(timeline) g_assert(timeline->priv->valid_thread == g_thread_self())
 
-typedef struct TrackObjIters
-{
-  GSequenceIter *iter_start;
-  GSequenceIter *iter_end;
-  GSequenceIter *iter_obj;
-  GSequenceIter *iter_by_layer;
-
-  GESLayer *layer;
-  GESTrackElement *trackelement;
-} TrackObjIters;
-
-static void
-_destroy_obj_iters (TrackObjIters * iters)
-{
-  g_slice_free (TrackObjIters, iters);
-}
-
-/*  The move context is used for the timeline editing modes functions in order to
- *  + Ripple / Roll /  Slide / Move / Trim
- *
- * The context aims at avoiding to recalculate values/objects on each call of the
- * editing functions.
- */
-struct _MoveContext
-{
-  GESClip *clip;
-  GESEdge edge;
-  GESEditMode mode;
-
-  /* The  start of the moving context */
-  GstClockTime start;
-
-  /* Ripple and Roll Objects */
-  GList *moving_trackelements;
-
-  /* We use it as a set of Clip to move between layers */
-  GHashTable *toplevel_containers;
-  /* Min priority of the objects currently in toplevel_containers */
-  guint min_move_layer;
-  /* Max priority of the objects currently in toplevel_containers */
-  guint max_layer_prio;
-
-  /* Never trim so duration would become < 0 */
-  guint64 max_trim_pos;
-
-  /* Never trim so inpoint + duration would change */
-  guint64 min_trim_pos;
-
-  /* fields to force/avoid new context */
-  /* Set to %TRUE when the track is doing updates of track element
-   * properties so we don't end up always needing new move context */
-  gboolean ignore_needs_ctx;
-  gboolean needs_move_ctx;
-
-  /* Last snapping  properties */
-  GESTrackElement *last_snaped1;
-  GESTrackElement *last_snaped2;
-  GstClockTime *last_snap_ts;
-
-  /* Priority of the layer where we are moving current clip
-   * -1 if not moving any clip to a new layer. */
-  GESLayer *moving_to_layer;
-};
-
 struct _GESTimelinePrivate
 {
+  GNode *tree;
+
   /* The duration of the timeline */
   gint64 duration;
 
   /* The auto-transition of the timeline */
   gboolean auto_transition;
-  /* Use to determine that a edit action should be rolled
-   * back because it leads to a wrong state of the element
-   * position (currently only happens if 3 clips overlap) */
-  gboolean needs_rollback;
-  gboolean rolling_back;
 
   /* Timeline edition modes and snapping management */
   guint64 snapping_distance;
 
-  /* FIXME: Should we offer an API over those fields ?
-   * FIXME: Should other classes than subclasses of Source also
-   * be tracked? */
-
-  /* Snapping fields */
-  GHashTable *by_start;         /* {Source: start} */
-  GHashTable *by_end;           /* {Source: end} */
-  GHashTable *by_object;        /* {timecode: Source} */
-  GHashTable *obj_iters;        /* {Source: TrackObjIters} */
-  GSequence *starts_ends;       /* Sorted list of starts/ends */
-  /* We keep 1 reference to our trackelement here */
-  GSequence *tracksources;      /* Source-s sorted by start/priorities */
-
   GRecMutex dyn_mutex;
   GList *priv_tracks;
-  /* FIXME: We should definitly offer an API over this,
-   * probably through a ges_layer_get_track_elements () method */
-  GHashTable *by_layer;         /* {layer: GSequence of TrackElement by start/priorities} */
 
   /* Avoid sorting layers when we are actually resyncing them ourself */
   gboolean resyncing_layers;
   GList *auto_transitions;
 
-  MoveContext movecontext;
+  /* Last snapping  properties */
+  GstClockTime last_snap_ts;
+  GESTrackElement *last_snaped1;
+  GESTrackElement *last_snaped2;
 
   /* This variable is set to %TRUE when it makes sense to update the transitions,
    * and %FALSE otherwize */
@@ -378,16 +299,6 @@ ges_timeline_dispose (GObject * object)
   g_list_free (priv->groups);
   g_list_free (groups);
 
-  g_hash_table_unref (priv->by_start);
-  g_hash_table_unref (priv->by_end);
-  g_hash_table_unref (priv->by_object);
-  g_hash_table_unref (priv->by_layer);
-  g_hash_table_unref (priv->obj_iters);
-  g_sequence_free (priv->starts_ends);
-  g_sequence_free (priv->tracksources);
-  g_list_free (priv->movecontext.moving_trackelements);
-  g_hash_table_unref (priv->movecontext.toplevel_containers);
-
   g_list_free_full (priv->auto_transitions, gst_object_unref);
 
   g_hash_table_unref (priv->all_elements);
@@ -401,6 +312,7 @@ ges_timeline_finalize (GObject * object)
   GESTimeline *tl = GES_TIMELINE (object);
 
   g_rec_mutex_clear (&tl->priv->dyn_mutex);
+  g_node_destroy (tl->priv->tree);
 
   G_OBJECT_CLASS (ges_timeline_parent_class)->finalize (object);
 }
@@ -486,6 +398,7 @@ ges_timeline_class_init (GESTimelineClass * klass)
 
   GST_DEBUG_CATEGORY_INIT (ges_timeline_debug, "gestimeline",
       GST_DEBUG_FG_YELLOW, "ges timeline");
+  timeline_tree_init_debug ();
 
   parent_class = g_type_class_peek_parent (klass);
 
@@ -666,6 +579,7 @@ ges_timeline_init (GESTimeline * self)
   GESTimelinePrivate *priv = self->priv;
 
   self->priv = ges_timeline_get_instance_private (self);
+  self->priv->tree = g_node_new (self);
 
   priv = self->priv;
   self->layers = NULL;
@@ -676,21 +590,9 @@ ges_timeline_init (GESTimeline * self)
   priv->expected_async_done = 0;
   priv->expected_commited = 0;
 
-  /* Move context initialization */
-  init_movecontext (&self->priv->movecontext, TRUE);
-  priv->movecontext.ignore_needs_ctx = FALSE;
+  self->priv->last_snap_ts = GST_CLOCK_TIME_NONE;
 
   priv->priv_tracks = NULL;
-  priv->by_start = g_hash_table_new (g_direct_hash, g_direct_equal);
-  priv->by_end = g_hash_table_new (g_direct_hash, g_direct_equal);
-  priv->by_object = g_hash_table_new (g_direct_hash, g_direct_equal);
-  priv->by_layer = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL,
-      (GDestroyNotify) g_sequence_free);
-  priv->obj_iters = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL,
-      (GDestroyNotify) _destroy_obj_iters);
-  priv->starts_ends = g_sequence_new (g_free);
-  priv->tracksources = g_sequence_new (gst_object_unref);
-
   priv->needs_transitions_update = TRUE;
 
   priv->all_elements =
@@ -755,64 +657,23 @@ _resync_layers (GESTimeline * timeline)
   timeline->priv->resyncing_layers = FALSE;
 }
 
-static void
+void
 timeline_update_duration (GESTimeline * timeline)
 {
-  GstClockTime *cduration;
-  GSequenceIter *it = g_sequence_get_end_iter (timeline->priv->starts_ends);
-
-  it = g_sequence_iter_prev (it);
-
-  if (g_sequence_iter_is_end (it)) {
-    timeline->priv->duration = 0;
-    g_object_notify_by_pspec (G_OBJECT (timeline), properties[PROP_DURATION]);
-    return;
-  }
+  GstClockTime duration = timeline_tree_get_duration (timeline->priv->tree);
 
-  cduration = g_sequence_get (it);
-
-  if (cduration && timeline->priv->duration != *cduration) {
+  if (timeline->priv->duration != duration) {
     GST_DEBUG ("track duration : %" GST_TIME_FORMAT " current : %"
-        GST_TIME_FORMAT, GST_TIME_ARGS (*cduration),
+        GST_TIME_FORMAT, GST_TIME_ARGS (duration),
         GST_TIME_ARGS (timeline->priv->duration));
 
-    timeline->priv->duration = *cduration;
+    timeline->priv->duration = duration;
 
     g_object_notify_by_pspec (G_OBJECT (timeline), properties[PROP_DURATION]);
   }
 }
 
 static gint
-find_layer_by_prio (GESLayer * a, gpointer pprio)
-{
-  gint prio = GPOINTER_TO_INT (pprio), lprio = ges_layer_get_priority (a);
-
-  if (lprio < prio)
-    return -1;
-  if (lprio > prio)
-    return 1;
-  return 0;
-}
-
-static void
-sort_track_elements (GESTimeline * timeline, TrackObjIters * iters)
-{
-  g_sequence_sort_changed (iters->iter_obj,
-      (GCompareDataFunc) element_start_compare, NULL);
-}
-
-static gint
-compare_uint64 (guint64 * a, guint64 * b, gpointer user_data)
-{
-  if (*a > *b)
-    return 1;
-  else if (*a == *b)
-    return 0;
-  else
-    return -1;
-}
-
-static gint
 custom_find_track (TrackPrivate * tr_priv, GESTrack * track)
 {
   if (tr_priv->track == track)
@@ -820,71 +681,26 @@ custom_find_track (TrackPrivate * tr_priv, GESTrack * track)
   return -1;
 }
 
-static inline void
-sort_starts_ends_end (GESTimeline * timeline, TrackObjIters * iters)
-{
-  GESTimelineElement *obj = GES_TIMELINE_ELEMENT (iters->trackelement);
-  GESTimelinePrivate *priv = timeline->priv;
-  guint64 *end = g_hash_table_lookup (priv->by_end, obj);
-
-  *end = _START (obj) + _DURATION (obj);
-
-  g_sequence_sort_changed (iters->iter_end, (GCompareDataFunc) compare_uint64,
-      NULL);
-  timeline_update_duration (timeline);
-}
-
-static inline void
-sort_starts_ends_start (GESTimeline * timeline, TrackObjIters * iters)
-{
-  GESTimelineElement *obj = GES_TIMELINE_ELEMENT (iters->trackelement);
-  GESTimelinePrivate *priv = timeline->priv;
-  guint64 *start = g_hash_table_lookup (priv->by_start, obj);
-
-  *start = _START (obj);
-
-  g_sequence_sort_changed (iters->iter_start,
-      (GCompareDataFunc) compare_uint64, NULL);
-  timeline_update_duration (timeline);
-}
-
 static void
 _destroy_auto_transition_cb (GESAutoTransition * auto_transition,
     GESTimeline * timeline)
 {
   GESTimelinePrivate *priv = timeline->priv;
-  MoveContext *mv_ctx = &timeline->priv->movecontext;
   GESClip *transition = auto_transition->transition_clip;
   GESLayer *layer = ges_clip_get_layer (transition);
-  GESContainer *toplevel_prev =
-      get_toplevel_container (auto_transition->previous_clip), *toplevel_next =
-      get_toplevel_container (auto_transition->next_clip);
-
-  if (g_hash_table_lookup (mv_ctx->toplevel_containers, toplevel_prev) &&
-      g_hash_table_lookup (mv_ctx->toplevel_containers, toplevel_next)) {
-    GESLayer *nlayer = mv_ctx->moving_to_layer;
-
-    if (!nlayer)
-      return;
-
-    ges_clip_move_to_layer (transition, nlayer);
-
-    return;
-  }
 
   ges_layer_remove_clip (layer, transition);
   g_signal_handlers_disconnect_by_func (auto_transition,
       _destroy_auto_transition_cb, timeline);
 
-
   priv->auto_transitions =
       g_list_remove (priv->auto_transitions, auto_transition);
   gst_object_unref (auto_transition);
 }
 
-static GESAutoTransition *
-create_transition (GESTimeline * timeline, GESTrackElement * previous,
-    GESTrackElement * next, GESClip * transition,
+GESAutoTransition *
+ges_timeline_create_transition (GESTimeline * timeline,
+    GESTrackElement * previous, GESTrackElement * next, GESClip * transition,
     GESLayer * layer, guint64 start, guint64 duration)
 {
   GESAsset *asset;
@@ -915,14 +731,10 @@ create_transition (GESTimeline * timeline, GESTrackElement * previous,
   return auto_transition;
 }
 
-typedef GESAutoTransition *(*GetAutoTransitionFunc) (GESTimeline * timeline,
-    GESLayer * layer, GESTrack * track, GESTrackElement * previous,
-    GESTrackElement * next, GstClockTime transition_duration);
-
-static GESAutoTransition *
-_find_transition_from_auto_transitions (GESTimeline * timeline,
-    GESLayer * layer, GESTrack * track, GESTrackElement * prev,
-    GESTrackElement * next, GstClockTime transition_duration)
+GESAutoTransition *
+ges_timeline_find_auto_transition (GESTimeline * timeline,
+    GESTrackElement * prev, GESTrackElement * next,
+    GstClockTime transition_duration)
 {
   GList *tmp;
 
@@ -934,8 +746,7 @@ _find_transition_from_auto_transitions (GESTimeline * timeline,
     if (auto_trans->previous_source == prev || auto_trans->next_source == next) {
       if (auto_trans->previous_source != prev
           || auto_trans->next_source != next) {
-        timeline->priv->needs_rollback = TRUE;
-        GST_INFO_OBJECT (timeline, "Failed creating auto transition, "
+        GST_ERROR_OBJECT (timeline, "Failed creating auto transition, "
             " trying to have 3 clips overlapping, rolling back");
       }
 
@@ -948,42 +759,27 @@ _find_transition_from_auto_transitions (GESTimeline * timeline,
 
 static GESAutoTransition *
 _create_auto_transition_from_transitions (GESTimeline * timeline,
-    GESLayer * layer, GESTrack * track, GESTrackElement * prev,
-    GESTrackElement * next, GstClockTime transition_duration)
+    GESTrackElement * prev, GESTrackElement * next,
+    GstClockTime transition_duration)
 {
-  GSequenceIter *tmp_iter;
-  GSequence *by_layer_sequence;
-
-  GESTimelinePrivate *priv = timeline->priv;
+  GList *tmp, *elements;
+  GESLayer *layer;
+  guint32 layer_prio = GES_TIMELINE_ELEMENT_LAYER_PRIORITY (prev);
+  GESTrack *track;
   GESAutoTransition *auto_transition =
-      _find_transition_from_auto_transitions (timeline, layer, track, prev,
-      next, transition_duration);
-
-
-  if (auto_transition) {
-    if (timeline->priv->needs_rollback) {
-      GST_WARNING_OBJECT (timeline,
-          "Created an auto transition where we have 3 overlapping clips"
-          " removing it as this case is NOT allowed nor supported");
-      g_signal_emit_by_name (auto_transition, "destroy-me");
-      timeline->priv->needs_rollback = FALSE;
-      return NULL;
-    }
-    return auto_transition;
-  }
+      ges_timeline_find_auto_transition (timeline, prev, next,
+      transition_duration);
 
+  if (auto_transition)
+    return auto_transition;
 
-  /* Try to find a transition that perfectly fits with the one that
-   * should be added at that place
-   * optimize: Use g_sequence_search instead of going over all the
-   * sequence */
-  by_layer_sequence = g_hash_table_lookup (priv->by_layer, layer);
-  for (tmp_iter = g_sequence_get_begin_iter (by_layer_sequence);
-      tmp_iter && !g_sequence_iter_is_end (tmp_iter);
-      tmp_iter = g_sequence_iter_next (tmp_iter)) {
-    GESTrackElement *maybe_transition = g_sequence_get (tmp_iter);
+  layer = ges_timeline_get_layer (timeline, layer_prio);
+  track = ges_track_element_get_track (prev);
+  elements = ges_track_get_elements (track);
+  for (tmp = elements; tmp; tmp = tmp->next) {
+    GESTrackElement *maybe_transition = tmp->data;
 
-    if (ges_track_element_get_track (maybe_transition) != track)
+    if (ges_timeline_element_get_layer_priority (tmp->data) != layer_prio)
       continue;
 
     if (_START (maybe_transition) > _START (next))
@@ -991,880 +787,123 @@ _create_auto_transition_from_transitions (GESTimeline * timeline,
     else if (_START (maybe_transition) != _START (next) ||
         _DURATION (maybe_transition) != transition_duration)
       continue;
-    else if (GES_IS_TRANSITION (maybe_transition))
+    else if (GES_IS_TRANSITION (maybe_transition)) {
       /* Use that transition */
       /* TODO We should make sure that the transition contains only
        * TrackElement-s in @track and if it is not the case properly unlink the
        * object to use it */
-      return create_transition (timeline, prev, next,
+      auto_transition = ges_timeline_create_transition (timeline, prev, next,
           GES_CLIP (GES_TIMELINE_ELEMENT_PARENT (maybe_transition)), layer,
           _START (next), transition_duration);
-  }
-
-  return NULL;
-}
-
-/* Create all transition that do not exist on @layer.
- * @get_auto_transition is called to check if a particular transition exists.
- * If @track is specified, we will create the transitions only for that particular
- * track. */
-static void
-_create_transitions_on_layer (GESTimeline * timeline, GESLayer * layer,
-    GESTrack * track, GESTrackElement * initiating_obj,
-    GetAutoTransitionFunc get_auto_transition)
-{
-  guint32 layer_prio;
-  GSequenceIter *iter;
-  GESAutoTransition *transition;
-  GESContainer *toplevel_next;
-  MoveContext *mv_ctx = &timeline->priv->movecontext;
-  GESTrack *ctrack = track;
-  GList *entered = NULL;        /* List of TrackElement for wich we walk through the
-                                 * "start" but not the "end" in the starts_ends list */
-  GESTimelinePrivate *priv = timeline->priv;
-
-  if (!layer || !ges_layer_get_auto_transition (layer))
-    return;
-
-  layer_prio = ges_layer_get_priority (layer);
-  for (iter = g_sequence_get_begin_iter (priv->starts_ends);
-      iter && !g_sequence_iter_is_end (iter);
-      iter = g_sequence_iter_next (iter)) {
-    GList *tmp;
-    guint *start_or_end = g_sequence_get (iter);
-    GESTrackElement *next = g_hash_table_lookup (timeline->priv->by_object,
-        start_or_end);
-    GESContainer *toplevel =
-        get_toplevel_container (GES_TIMELINE_ELEMENT (next));
-
-    /* Only object that are in that layer and track */
-    if (GES_TIMELINE_ELEMENT_LAYER_PRIORITY (next) != layer_prio ||
-        (track && track != ges_track_element_get_track (next)))
-      continue;
-
-    if (track == NULL)
-      ctrack = ges_track_element_get_track (next);
-
-    if (start_or_end == g_hash_table_lookup (priv->by_end, next)) {
-      if (initiating_obj == next) {
-        /* We passed the objects that initiated the research
-         * we are now done */
-        g_list_free (entered);
-        return;
-      }
-      entered = g_list_remove (entered, next);
-
-      continue;
-    }
-
-    toplevel_next = get_toplevel_container (next);
-    for (tmp = entered; tmp; tmp = tmp->next) {
-      gint64 transition_duration;
-      GESTrackElement *prev = tmp->data;
-      GESContainer *toplevel_prev = get_toplevel_container (prev);
-
-      /* If we are not in the same track, we do not create a transition */
-      if (ctrack != ges_track_element_get_track (prev))
-        continue;
-
-      /* If elements are in the same toplevel element, we do not create a transition */
-      if (get_toplevel_container (GES_TIMELINE_ELEMENT (prev)) == toplevel)
-        continue;
-
-      /* If the element is inside a container we are moving, we do not
-       * create a transition */
-      if (g_hash_table_lookup (mv_ctx->toplevel_containers, toplevel_prev) &&
-          g_hash_table_lookup (mv_ctx->toplevel_containers, toplevel_next))
-        continue;
-
-      transition_duration = (_START (prev) + _DURATION (prev)) - _START (next);
-      if (transition_duration > 0 && transition_duration < _DURATION (prev) &&
-          transition_duration < _DURATION (next)) {
-        transition =
-            get_auto_transition (timeline, layer, ctrack, prev, next,
-            transition_duration);
-        if (!transition)
-          create_transition (timeline, prev, next, NULL, layer,
-              _START (next), transition_duration);
-      }
-    }
-
-    /* And add that object to the entered list so that it we can possibly set
-     * a transition on its end edge */
-    entered = g_list_append (entered, next);
-  }
-}
-
-/* @track_element must be a GESSource */
-void
-timeline_create_transitions (GESTimeline * timeline,
-    GESTrackElement * track_element)
-{
-  GESTrack *track;
-  GList *layer_node;
-
-  GESTimelinePrivate *priv = timeline->priv;
-  MoveContext *mv_ctx = &timeline->priv->movecontext;
-
-  if (!priv->needs_transitions_update)
-    return;
-
-  if (mv_ctx->moving_trackelements &&
-      GES_TIMELINE_ELEMENT_START (track_element) > mv_ctx->start) {
-    GST_DEBUG_OBJECT (timeline, "Not creating transition around %"
-        GES_TIMELINE_ELEMENT_FORMAT " as it is not the first rippled"
-        " element", GES_TIMELINE_ELEMENT_ARGS (track_element));
-    return;
-  }
-
-  track = ges_track_element_get_track (track_element);
-  layer_node = g_list_find_custom (timeline->layers,
-      GINT_TO_POINTER (GES_TIMELINE_ELEMENT_LAYER_PRIORITY (track_element)),
-      (GCompareFunc) find_layer_by_prio);
-
-  _create_transitions_on_layer (timeline,
-      layer_node ? layer_node->data : NULL, track, track_element,
-      _find_transition_from_auto_transitions);
-
-  GST_DEBUG_OBJECT (timeline, "Done updating transitions");
-}
-
-/* Timeline edition functions */
-static inline void
-init_movecontext (MoveContext * mv_ctx, gboolean first_init)
-{
-  if (G_UNLIKELY (first_init))
-    mv_ctx->toplevel_containers =
-        g_hash_table_new (g_direct_hash, g_direct_equal);
-
-  mv_ctx->moving_trackelements = NULL;
-  mv_ctx->start = G_MAXUINT64;
-  mv_ctx->max_trim_pos = G_MAXUINT64;
-  mv_ctx->min_move_layer = G_MAXUINT;
-  mv_ctx->max_layer_prio = 0;
-  mv_ctx->last_snaped1 = NULL;
-  mv_ctx->last_snaped2 = NULL;
-  mv_ctx->last_snap_ts = NULL;
-  mv_ctx->moving_to_layer = NULL;
-}
-
-static inline void
-clean_movecontext (MoveContext * mv_ctx)
-{
-  g_list_free (mv_ctx->moving_trackelements);
-  g_hash_table_remove_all (mv_ctx->toplevel_containers);
-  init_movecontext (mv_ctx, FALSE);
-}
-
-static void
-stop_tracking_track_element (GESTimeline * timeline,
-    GESTrackElement * trackelement)
-{
-  guint64 *start, *end;
-  TrackObjIters *iters;
-  GESTimelinePrivate *priv = timeline->priv;
-
-  iters = g_hash_table_lookup (priv->obj_iters, trackelement);
-  if (G_LIKELY (iters->iter_by_layer)) {
-    g_sequence_remove (iters->iter_by_layer);
-  } else {
-    GST_WARNING_OBJECT (timeline, "TrackElement %p was in no layer",
-        trackelement);
-  }
-
-  if (GES_IS_SOURCE (trackelement)) {
-    start = g_hash_table_lookup (priv->by_start, trackelement);
-    end = g_hash_table_lookup (priv->by_end, trackelement);
-
-    g_hash_table_remove (priv->by_start, trackelement);
-    g_hash_table_remove (priv->by_end, trackelement);
-    g_hash_table_remove (priv->by_object, end);
-    g_hash_table_remove (priv->by_object, start);
-    g_sequence_remove (iters->iter_start);
-    g_sequence_remove (iters->iter_end);
-    g_sequence_remove (iters->iter_obj);
-    timeline_update_duration (timeline);
-  }
-  g_hash_table_remove (priv->obj_iters, trackelement);
-}
-
-static void
-start_tracking_track_element (GESTimeline * timeline,
-    GESTrackElement * trackelement)
-{
-  guint64 *pstart, *pend;
-  GSequence *by_layer_sequence;
-  TrackObjIters *iters;
-  GESTimelinePrivate *priv = timeline->priv;
-
-  guint layer_prio = GES_TIMELINE_ELEMENT_LAYER_PRIORITY (trackelement);
-  GList *layer_node = g_list_find_custom (timeline->layers,
-      GINT_TO_POINTER (layer_prio), (GCompareFunc) find_layer_by_prio);
-  GESLayer *layer = layer_node ? layer_node->data : NULL;
-
-  iters = g_slice_new0 (TrackObjIters);
-
-  /* We add all TrackElement to obj_iters as we always follow them
-   * in the by_layer Sequences */
-  g_hash_table_insert (priv->obj_iters, trackelement, iters);
-
-  /* Track all objects by layer */
-  if (G_UNLIKELY (layer == NULL)) {
-    /* We handle the case where we have TrackElement that are in no layer by not
-     * tracking them
-     *
-     * FIXME: Check if we should rather try to track them in some sort of
-     * dummy layer, or even refuse TrackElements to be added in Tracks if
-     * they land in no layer the timeline controls.
-     */
-    GST_ERROR_OBJECT (timeline, "Adding a TrackElement that lands in no layer "
-        "we are controlling");
-  } else {
-    by_layer_sequence = g_hash_table_lookup (priv->by_layer, layer);
-    iters->iter_by_layer =
-        g_sequence_insert_sorted (by_layer_sequence, trackelement,
-        (GCompareDataFunc) element_start_compare, NULL);
-    iters->layer = layer;
-  }
-
-  if (GES_IS_SOURCE (trackelement)) {
-    /* Track only sources for timeline edition and snapping */
-    pstart = g_malloc (sizeof (guint64));
-    pend = g_malloc (sizeof (guint64));
-    *pstart = _START (trackelement);
-    *pend = *pstart + _DURATION (trackelement);
-
-    iters->iter_start = g_sequence_insert_sorted (priv->starts_ends, pstart,
-        (GCompareDataFunc) compare_uint64, NULL);
-    iters->iter_end = g_sequence_insert_sorted (priv->starts_ends, pend,
-        (GCompareDataFunc) compare_uint64, NULL);
-    iters->iter_obj =
-        g_sequence_insert_sorted (priv->tracksources,
-        gst_object_ref (trackelement), (GCompareDataFunc) element_start_compare,
-        NULL);
-    iters->trackelement = trackelement;
-
-    g_hash_table_insert (priv->by_start, trackelement, pstart);
-    g_hash_table_insert (priv->by_object, pstart, trackelement);
-    g_hash_table_insert (priv->by_end, trackelement, pend);
-    g_hash_table_insert (priv->by_object, pend, trackelement);
-
-    timeline->priv->movecontext.needs_move_ctx = TRUE;
-
-    timeline_update_duration (timeline);
-    timeline_create_transitions (timeline, trackelement);
-  }
-}
-
-static inline void
-ges_timeline_emit_snappig (GESTimeline * timeline, GESTrackElement * obj1,
-    guint64 * timecode)
-{
-  GESTrackElement *obj2;
-  MoveContext *mv_ctx = &timeline->priv->movecontext;
-  GstClockTime snap_time = timecode ? *timecode : 0;
-  GstClockTime last_snap_ts = mv_ctx->last_snap_ts ?
-      *mv_ctx->last_snap_ts : GST_CLOCK_TIME_NONE;
-
-  GST_DEBUG_OBJECT (timeline, "Distance: %" GST_TIME_FORMAT " snapping at %"
-      GST_TIME_FORMAT, GST_TIME_ARGS (timeline->priv->snapping_distance),
-      GST_TIME_ARGS (snap_time));
-
-  if (timecode == NULL) {
-    if (mv_ctx->last_snaped1 != NULL && mv_ctx->last_snaped2 != NULL) {
-      g_signal_emit (timeline, ges_timeline_signals[SNAPING_ENDED], 0,
-          mv_ctx->last_snaped1, mv_ctx->last_snaped2, last_snap_ts);
-
-      /* We then need to recalculate the moving context */
-      timeline->priv->movecontext.needs_move_ctx = TRUE;
-    }
-
-    return;
-  }
-
-  obj2 = g_hash_table_lookup (timeline->priv->by_object, timecode);
-
-  if (last_snap_ts != *timecode) {
-    g_signal_emit (timeline, ges_timeline_signals[SNAPING_ENDED], 0,
-        mv_ctx->last_snaped1, mv_ctx->last_snaped2, (last_snap_ts));
-
-    /* We want the snap start signal to be emited anyway */
-    mv_ctx->last_snap_ts = NULL;
-  }
-
-  if (mv_ctx->last_snap_ts == NULL) {
-
-    mv_ctx->last_snaped1 = obj1;
-    mv_ctx->last_snaped2 = obj2;
-    mv_ctx->last_snap_ts = timecode;
-
-    g_signal_emit (timeline, ges_timeline_signals[SNAPING_STARTED], 0,
-        obj1, obj2, *timecode);
-
-  }
-}
-
-static GstClockTime *
-ges_timeline_snap_position (GESTimeline * timeline,
-    GESTrackElement * trackelement, GstClockTime * current,
-    GstClockTime timecode, gboolean emit)
-{
-  GESTimelinePrivate *priv = timeline->priv;
-  GSequenceIter *iter, *end_iter;
-  GESContainer *container = get_toplevel_container (trackelement);
-  GstClockTime *ret = NULL;
-  GstClockTime smallest_offset = G_MAXUINT64;
-  GstClockTime tmp_pos;
-
-  tmp_pos = timecode - priv->snapping_distance;
-  /* Rippling, not snapping with previous elements */
-  if (priv->movecontext.moving_trackelements)
-    tmp_pos = timecode;
-  iter = g_sequence_search (priv->starts_ends, &tmp_pos,
-      (GCompareDataFunc) compare_uint64, NULL);
-
-  tmp_pos = timecode + priv->snapping_distance;
-  end_iter = g_sequence_search (priv->starts_ends, &tmp_pos,
-      (GCompareDataFunc) compare_uint64, NULL);
-
-  for (; iter != end_iter && !g_sequence_iter_is_end (iter);
-      iter = g_sequence_iter_next (iter)) {
-    GstClockTime *iter_tc = g_sequence_get (iter);
-    GESTrackElement *tmp_trackelement =
-        g_hash_table_lookup (priv->by_object, iter_tc);
-    GESContainer *tmp_container = get_toplevel_container (tmp_trackelement);
-    GstClockTimeDiff diff;
-
-    if (tmp_container == container)
-      continue;
-
-    if (g_hash_table_lookup (priv->movecontext.toplevel_containers,
-            tmp_container))
-      continue;
-
-    if (timecode > *iter_tc)
-      diff = timecode - *iter_tc;
-    else
-      diff = *iter_tc - timecode;
-
-    if (diff > smallest_offset)
-      break;
-
-    smallest_offset = diff;
-    ret = iter_tc;
-  }
-
-  /* We emit the snapping signal only if we snapped with a different value
-   * than the current one */
-  if (emit) {
-    GstClockTime snap_time = ret ? *ret : GST_CLOCK_TIME_NONE;
-
-    if (!timeline->priv->needs_rollback)
-      ges_timeline_emit_snappig (timeline, trackelement, ret);
-    else
-      ges_timeline_emit_snappig (timeline, trackelement, NULL);
-
-    GST_DEBUG_OBJECT (timeline, "Snaping at %" GST_TIME_FORMAT,
-        GST_TIME_ARGS (snap_time));
-  }
-
-  return ret;
-}
-
-static inline GESContainer *
-add_toplevel_container (MoveContext * mv_ctx, GESTrackElement * trackelement)
-{
-  guint layer_prio;
-  GESContainer *toplevel = get_toplevel_container (trackelement);
-
-  /* Avoid recalculating */
-  if (!g_hash_table_lookup (mv_ctx->toplevel_containers, toplevel)) {
-    if (GES_IS_CLIP (toplevel)) {
-
-      layer_prio = ges_clip_get_layer_priority (GES_CLIP (toplevel));
-      if (layer_prio == (guint32) - 1) {
-        GST_WARNING_OBJECT (toplevel, "Not in any layer, can not move"
-            " between layers");
-
-        return toplevel;
-      }
-      mv_ctx->min_move_layer = MIN (mv_ctx->min_move_layer, layer_prio);
-      mv_ctx->max_layer_prio = MAX (mv_ctx->max_layer_prio, layer_prio);
-    } else if GES_IS_GROUP
-      (toplevel) {
-      mv_ctx->min_move_layer = MIN (mv_ctx->min_move_layer,
-          _PRIORITY (toplevel));
-      mv_ctx->max_layer_prio = MAX (mv_ctx->max_layer_prio,
-          _PRIORITY (toplevel) + GES_CONTAINER_HEIGHT (toplevel));
-    } else
-      g_assert_not_reached ();
-
-    mv_ctx->start = MIN (mv_ctx->start, GES_TIMELINE_ELEMENT_START (toplevel));
-    g_hash_table_insert (mv_ctx->toplevel_containers, toplevel, toplevel);
-
-  }
-
-  return toplevel;
-}
-
-static gboolean
-ges_move_context_set_objects (GESTimeline * timeline, GESTrackElement * obj,
-    GESEdge edge)
-{
-  TrackObjIters *iters;
-  GESTrackElement *tmptrackelement;
-  guint64 start, tmpend, moving_point = _START (obj);
-  GSequenceIter *iter, *trackelement_iter, *tmpiter;
-
-  MoveContext *mv_ctx = &timeline->priv->movecontext;
-  iters = g_hash_table_lookup (timeline->priv->obj_iters, obj);
-  trackelement_iter = iters->iter_obj;
-  switch (edge) {
-    case GES_EDGE_START:
-      /* set it properly in the context of "trimming" */
-      mv_ctx->max_trim_pos = 0;
-      mv_ctx->min_trim_pos = 0;
-      start = _START (obj);
-
-      if (g_sequence_iter_is_begin (trackelement_iter))
-        break;
-
-      /* Look for the objects */
-      for (iter = g_sequence_iter_prev (trackelement_iter);
-          iter && !g_sequence_iter_is_end (iter);
-          iter = g_sequence_iter_prev (iter)) {
-
-        tmptrackelement = GES_TRACK_ELEMENT (g_sequence_get (iter));
-        tmpend = _START (tmptrackelement) + _DURATION (tmptrackelement);
-
-        if (tmpend <= start && GES_TIMELINE_ELEMENT_PARENT (tmptrackelement)
-            != GES_TIMELINE_ELEMENT_PARENT (obj)) {
-          mv_ctx->max_trim_pos =
-              MAX (mv_ctx->max_trim_pos, _START (tmptrackelement));
-          mv_ctx->min_trim_pos = MAX (mv_ctx->min_trim_pos,
-              _START (tmptrackelement) - _INPOINT (tmptrackelement));
-          mv_ctx->moving_trackelements =
-              g_list_prepend (mv_ctx->moving_trackelements, tmptrackelement);
-        }
-
-
-        if (g_sequence_iter_is_begin (iter))
-          break;
-      }
-      break;
-
-    case GES_EDGE_END:
-      moving_point = _START (obj) + _DURATION (obj);
-      /* fall-through */
-    case GES_EDGE_NONE:        /* In this case only works for ripple */
-      mv_ctx->max_trim_pos = G_MAXUINT64;
-
-      iter = trackelement_iter;
-      tmpiter = g_sequence_iter_prev (iter);
-
-      /* Make sure to get the first TimelineElement starting at
-       * @moving_point */
-      while (tmpiter && !g_sequence_iter_is_end (tmpiter)) {
-        tmptrackelement = GES_TRACK_ELEMENT (g_sequence_get (iter));
-
-        if (GES_TIMELINE_ELEMENT_START (tmptrackelement) != moving_point)
-          break;
-
-        iter = tmpiter;
-        tmpiter = g_sequence_iter_prev (tmpiter);
-
-        if (g_sequence_iter_is_begin (tmpiter))
-          break;
-      }
-
-      /* Look for folowing objects */
-      for (; iter && !g_sequence_iter_is_end (iter);
-          iter = g_sequence_iter_next (iter)) {
-        tmptrackelement = GES_TRACK_ELEMENT (g_sequence_get (iter));
-
-        if (_START (tmptrackelement) >= moving_point &&
-            GES_TIMELINE_ELEMENT_PARENT (tmptrackelement) !=
-            GES_TIMELINE_ELEMENT_PARENT (obj)) {
-          tmpend = _START (tmptrackelement) + _DURATION (tmptrackelement);
-          mv_ctx->max_trim_pos = MIN (mv_ctx->max_trim_pos, tmpend);
-          mv_ctx->moving_trackelements =
-              g_list_prepend (mv_ctx->moving_trackelements, tmptrackelement);
-        }
-      }
-      break;
-    default:
-      GST_DEBUG ("Edge type %d no supported", edge);
-      return FALSE;
-  }
-
-  return TRUE;
-}
-
-static gboolean
-ges_timeline_set_moving_context (GESTimeline * timeline, GESTrackElement * obj,
-    GESEditMode mode, GESEdge edge, GList * layers)
-{
-  /* A TrackElement that could initiate movement for other object */
-  GESTrackElement *editor_trackelement = NULL;
-  MoveContext *mv_ctx = &timeline->priv->movecontext;
-  GESClip *clip = GES_CLIP (GES_TIMELINE_ELEMENT_PARENT (obj));
-
-  /* Still in the same mv_ctx */
-  if ((mv_ctx->clip == clip && mv_ctx->mode == mode &&
-          mv_ctx->edge == edge && !mv_ctx->needs_move_ctx)) {
-
-    GST_DEBUG ("Keeping the same moving mv_ctx");
-    return TRUE;
-  }
-
-
-  GST_DEBUG_OBJECT (clip,
-      "Changing context:\nold: obj: %p, mode: %d, edge: %d \n"
-      "new: obj: %p, mode: %d, edge: %d ! Has changed %i", mv_ctx->clip,
-      mv_ctx->mode, mv_ctx->edge, clip, mode, edge, mv_ctx->needs_move_ctx);
-
-  /* Make sure snapping context is reset when changing the moving context */
-  ges_timeline_emit_snappig (timeline, NULL, NULL);
-  clean_movecontext (mv_ctx);
-  mv_ctx->edge = edge;
-  mv_ctx->mode = mode;
-  mv_ctx->clip = clip;
-  mv_ctx->needs_move_ctx = FALSE;
-
-  /* We try to find a Source inside the Clip so we can set the
-   * moving context Else we just move the selected one only */
-  if (GES_IS_SOURCE (obj) == FALSE) {
-    GList *tmp;
-
-    for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) {
-      if (GES_IS_SOURCE (tmp->data)) {
-        editor_trackelement = tmp->data;
-        break;
-      }
-    }
-  } else {
-    editor_trackelement = obj;
-  }
-
-  if (editor_trackelement) {
-    switch (mode) {
-      case GES_EDIT_MODE_RIPPLE:
-      case GES_EDIT_MODE_ROLL:
-        if (!(ges_move_context_set_objects (timeline, editor_trackelement,
-                    edge)))
-          return FALSE;
-      default:
-        break;
-    }
-    add_toplevel_container (&timeline->priv->movecontext, editor_trackelement);
-  } else {
-    /* We add the main object to the toplevel_containers set */
-    add_toplevel_container (&timeline->priv->movecontext, obj);
-  }
-
-
-  return TRUE;
-}
-
-static gboolean
-_trim_transition (GESTimeline * timeline, GESLayer * layer,
-    GESTrackElement * element, GESEdge edge, GstClockTime position)
-{
-
-  GList *tmp;
-
-  if (!ges_layer_get_auto_transition (layer))
-    goto fail;
-
-  gst_object_unref (layer);
-  for (tmp = timeline->priv->auto_transitions; tmp; tmp = tmp->next) {
-    GESAutoTransition *auto_transition = tmp->data;
-
-    if (auto_transition->transition == GES_TRACK_ELEMENT (element)) {
-      /* Trimming an auto transition mean trimming its neighboors */
-      if (!auto_transition->positioning) {
-        if (edge == GES_EDGE_END) {
-          ges_container_edit (GES_CONTAINER (auto_transition->previous_clip),
-              NULL, -1, GES_EDIT_MODE_TRIM, GES_EDGE_END, position);
-        } else {
-          ges_container_edit (GES_CONTAINER (auto_transition->next_clip),
-              NULL, -1, GES_EDIT_MODE_TRIM, GES_EDGE_START, position);
-        }
-
-        return TRUE;
-      }
-
-      return FALSE;
-    }
-  }
-
-  return FALSE;
-
-fail:
-  gst_object_unref (layer);
-  return FALSE;
-}
-
-gboolean
-ges_timeline_trim_object_simple (GESTimeline * timeline,
-    GESTimelineElement * element, GList * layers, GESEdge edge,
-    guint64 position, gboolean snapping)
-{
-  guint64 start, inpoint, duration, max_duration, *snapped, *cur;
-  gboolean ret = TRUE;
-  gint64 real_dur;
-  GESTrackElement *track_element;
-
-  if (GES_IS_TRANSITION (element)) {
-    return _trim_transition (timeline,
-        ges_clip_get_layer (GES_CLIP (GES_TIMELINE_ELEMENT_PARENT (element))),
-        GES_TRACK_ELEMENT (element), edge, position);
-  } else if (GES_IS_SOURCE (element) == FALSE) {
-    return FALSE;
-  }
-
-  track_element = GES_TRACK_ELEMENT (element);
-  GST_DEBUG_OBJECT (track_element, "Trimming to %" GST_TIME_FORMAT
-      " %s snaping, edge %i", GST_TIME_ARGS (position),
-      snapping ? "Is" : "Not", edge);
-
-  start = _START (track_element);
-  g_object_get (track_element, "max-duration", &max_duration, NULL);
-
-  switch (edge) {
-    case GES_EDGE_START:
-    {
-      GESTimelineElement *toplevel;
-      GESChildrenControlMode old_mode;
-      gboolean use_inpoint;
-      toplevel = ges_timeline_element_get_toplevel_parent (element);
-
-      if (position < _START (toplevel) && _START (toplevel) < _START (element)) {
-        GST_DEBUG_OBJECT (toplevel, "Not trimming %p as not at begining "
-            "of the container", element);
-
-        gst_object_unref (toplevel);
-        return FALSE;
-      }
-
-      old_mode = GES_CONTAINER (toplevel)->children_control_mode;
-      if (GES_IS_GROUP (toplevel) && old_mode == GES_CHILDREN_UPDATE) {
-        GST_DEBUG_OBJECT (toplevel, "Setting children udpate mode to"
-            " UPDDATE_ALL_VALUES so we can trim without moving the contained");
-        /* The container will update its values itself according to new
-         * values of the children */
-        GES_CONTAINER (toplevel)->children_control_mode =
-            GES_CHILDREN_UPDATE_ALL_VALUES;
-      }
-
-      inpoint = _INPOINT (track_element);
-      duration = _DURATION (track_element);
 
-      if (snapping) {
-        cur = g_hash_table_lookup (timeline->priv->by_start, track_element);
+      break;
+    }
+  }
+  gst_object_unref (layer);
+  g_list_free_full (elements, gst_object_unref);
 
-        snapped = ges_timeline_snap_position (timeline, track_element, cur,
-            position, TRUE);
-        if (snapped)
-          position = *snapped;
-      }
+  return auto_transition;
+}
 
-      /* Calculate new values */
-      position = MIN (position, start + duration);
+void
+ges_timeline_emit_snapping (GESTimeline * timeline, GESTimelineElement * elem1,
+    GESTimelineElement * elem2, GstClockTime snap_time)
+{
+  GESTimelinePrivate *priv = timeline->priv;
+  GstClockTime last_snap_ts = timeline->priv->last_snap_ts;
 
-      use_inpoint =
-          GES_TIMELINE_ELEMENT_GET_CLASS (track_element)->set_inpoint ? TRUE :
-          FALSE;
+  if (!GST_CLOCK_TIME_IS_VALID (snap_time)) {
+    if (priv->last_snaped1 != NULL && priv->last_snaped2 != NULL) {
+      g_signal_emit (timeline, ges_timeline_signals[SNAPING_ENDED], 0,
+          priv->last_snaped1, priv->last_snaped2, last_snap_ts);
+      priv->last_snaped1 = NULL;
+      priv->last_snaped2 = NULL;
+      priv->last_snap_ts = GST_CLOCK_TIME_NONE;
+    }
 
-      if (use_inpoint && inpoint + position < start) {
-        GST_ERROR_OBJECT (timeline, "Track element %s inpoint %" GST_TIME_FORMAT
-            " would be negative,"
-            " not trimming", GES_TIMELINE_ELEMENT_NAME (track_element),
-            GST_TIME_ARGS (inpoint));
-        gst_object_unref (toplevel);
-        return FALSE;
-      }
+    return;
+  }
 
-      inpoint = inpoint + position - start;
-      real_dur = _END (element) - position;
-      if (use_inpoint)
-        duration = CLAMP (real_dur, 0, max_duration > inpoint ?
-            max_duration - inpoint : G_MAXUINT64);
-      else
-        duration = real_dur;
-
-
-      /* If we already are at max duration or duration == 0 do no useless work */
-      if ((duration == _DURATION (track_element) &&
-              _DURATION (track_element) == _MAXDURATION (track_element)) ||
-          (duration == 0 && _DURATION (element) == 0)) {
-        GST_DEBUG_OBJECT (track_element,
-            "Duration already == max_duration, no triming");
-        gst_object_unref (toplevel);
-        return FALSE;
-      }
+  g_assert (elem1 != elem2);
+  if (GES_IS_CLIP (elem1)) {
+    g_assert (GES_CONTAINER_CHILDREN (elem1));
+    elem1 = GES_CONTAINER_CHILDREN (elem1)->data;
+  }
 
-      timeline->priv->needs_transitions_update = FALSE;
-      _set_start0 (GES_TIMELINE_ELEMENT (track_element), position);
-      _set_inpoint0 (GES_TIMELINE_ELEMENT (track_element), inpoint);
-      timeline->priv->needs_transitions_update = TRUE;
+  if (GES_IS_CLIP (elem2)) {
+    g_assert (GES_CONTAINER_CHILDREN (elem2));
+    elem2 = GES_CONTAINER_CHILDREN (elem2)->data;
+  }
 
-      _set_duration0 (GES_TIMELINE_ELEMENT (track_element), duration);
-      if (GES_IS_GROUP (toplevel))
-        GES_CONTAINER (toplevel)->children_control_mode = old_mode;
+  if (last_snap_ts != snap_time) {
+    g_signal_emit (timeline, ges_timeline_signals[SNAPING_ENDED], 0,
+        priv->last_snaped1, priv->last_snaped2, (last_snap_ts));
 
-      gst_object_unref (toplevel);
-      break;
-    }
-    case GES_EDGE_END:
-    {
-      cur = g_hash_table_lookup (timeline->priv->by_end, track_element);
-      snapped = ges_timeline_snap_position (timeline, track_element, cur,
-          position, TRUE);
-      if (snapped)
-        position = *snapped;
-
-      /* Calculate new values */
-      real_dur = position - start;
-      duration = MAX (0, real_dur);
-      duration = MIN (duration, max_duration - _INPOINT (track_element));
-
-      if (duration == 0) {
-        GST_INFO_OBJECT (timeline, "Duration would be 0, not rippling");
-        return FALSE;
-      }
+    /* We want the snap start signal to be emited anyway */
+    timeline->priv->last_snap_ts = GST_CLOCK_TIME_NONE;
+  }
 
-      /* Not moving, avoid overhead */
-      if (duration == _DURATION (track_element)) {
-        GST_DEBUG_OBJECT (track_element, "No change in duration");
-        return TRUE;
-      }
+  if (!GST_CLOCK_TIME_IS_VALID (timeline->priv->last_snap_ts)) {
+    priv->last_snaped1 = (GESTrackElement *) elem1;
+    priv->last_snaped2 = (GESTrackElement *) elem2;
+    timeline->priv->last_snap_ts = snap_time;
 
-      _set_duration0 (GES_TIMELINE_ELEMENT (track_element), duration);
-      break;
-    }
-    default:
-      GST_WARNING ("Can not trim with %i GESEdge", edge);
-      return FALSE;
+    g_signal_emit (timeline, ges_timeline_signals[SNAPING_STARTED], 0,
+        elem1, elem2, snap_time);
   }
 
-  return ret;
 }
 
 gboolean
-timeline_ripple_object (GESTimeline * timeline, GESTrackElement * obj,
-    GList * layers, GESEdge edge, guint64 position)
+ges_timeline_trim_object_simple (GESTimeline * timeline,
+    GESTimelineElement * element, guint32 new_layer_priority,
+    GList * layers, GESEdge edge, guint64 position, gboolean snapping)
 {
-  GList *tmp, *moved_clips = NULL;
-  GESTrackElement *trackelement;
-  GESContainer *container;
-  guint64 duration, new_start, *snapped, *cur;
-  gint64 offset;
 
-  MoveContext *mv_ctx = &timeline->priv->movecontext;
+  return timeline_trim_object (timeline, element, new_layer_priority, layers,
+      edge, position);
+}
 
-  mv_ctx->ignore_needs_ctx = TRUE;
-  timeline->priv->needs_rollback = FALSE;
-  if (!ges_timeline_set_moving_context (timeline, obj, GES_EDIT_MODE_RIPPLE,
-          edge, layers))
-    goto error;
+gboolean
+timeline_ripple_object (GESTimeline * timeline, GESTimelineElement * obj,
+    gint new_layer_priority, GList * layers, GESEdge edge, guint64 position)
+{
+  gboolean res = TRUE;
+  guint64 new_duration;
+  GstClockTimeDiff diff;
 
   switch (edge) {
     case GES_EDGE_NONE:
       GST_DEBUG ("Simply rippling");
+      diff = GST_CLOCK_DIFF (position, _START (obj));
 
-      /* We should be smart here to avoid recalculate transitions when possible */
-      cur = g_hash_table_lookup (timeline->priv->by_end, obj);
-      snapped = ges_timeline_snap_position (timeline, obj, cur, position, TRUE);
-      if (snapped)
-        position = *snapped;
-
-      offset = position - _START (obj);
-
-      for (tmp = mv_ctx->moving_trackelements; tmp; tmp = tmp->next) {
-        trackelement = GES_TRACK_ELEMENT (tmp->data);
-        new_start = _START (trackelement) + offset;
-
-        container = add_toplevel_container (mv_ctx, trackelement);
-        /* Make sure not to move 2 times the same Clip */
-        if (g_list_find (moved_clips, container) == NULL) {
-          _set_start0 (GES_TIMELINE_ELEMENT (trackelement), new_start);
-          moved_clips = g_list_prepend (moved_clips, container);
-        }
-
-      }
-      g_list_free (moved_clips);
-      _set_start0 (GES_TIMELINE_ELEMENT (obj), position);
-
-      moved_clips = NULL;
-      if (timeline->priv->needs_rollback && !timeline->priv->rolling_back) {
-        timeline->priv->rolling_back = TRUE;
-        for (tmp = mv_ctx->moving_trackelements; tmp; tmp = tmp->next) {
-          trackelement = GES_TRACK_ELEMENT (tmp->data);
-          new_start = _START (trackelement) - offset;
-
-          container = add_toplevel_container (mv_ctx, trackelement);
-          /* Make sure not to move 2 times the same Clip */
-          if (g_list_find (moved_clips, container) == NULL) {
-            _set_start0 (GES_TIMELINE_ELEMENT (trackelement), new_start);
-            moved_clips = g_list_prepend (moved_clips, container);
-          }
-
-        }
-        g_list_free (moved_clips);
-        moved_clips = NULL;
-        _set_start0 (GES_TIMELINE_ELEMENT (obj), position - offset);
-
-        ges_timeline_emit_snappig (timeline, obj, NULL);
-        mv_ctx->needs_move_ctx = TRUE;
-        timeline->priv->rolling_back = FALSE;
-
-        goto error;
-      }
+      timeline->priv->needs_transitions_update = FALSE;
+      res = timeline_tree_ripple (timeline->priv->tree,
+          (gint64) GES_TIMELINE_ELEMENT_LAYER_PRIORITY (obj) -
+          (gint64) new_layer_priority, diff, obj,
+          GES_EDGE_NONE, timeline->priv->snapping_distance);
+      timeline->priv->needs_transitions_update = TRUE;
 
       break;
     case GES_EDGE_END:
-      timeline->priv->needs_transitions_update = FALSE;
       GST_DEBUG ("Rippling end");
 
-      cur = g_hash_table_lookup (timeline->priv->by_end, obj);
-      snapped = ges_timeline_snap_position (timeline, obj, cur, position, TRUE);
-      if (snapped)
-        position = *snapped;
-
-      duration = _DURATION (obj);
-
-      if (!ges_timeline_trim_object_simple (timeline,
-              GES_TIMELINE_ELEMENT (obj), NULL, GES_EDGE_END, position,
-              FALSE)) {
-        return FALSE;
-      }
-
-      offset = _DURATION (obj) - duration;
-      for (tmp = mv_ctx->moving_trackelements; tmp; tmp = tmp->next) {
-        trackelement = GES_TRACK_ELEMENT (tmp->data);
-        new_start = _START (trackelement) + offset;
-
-        container = add_toplevel_container (mv_ctx, trackelement);
-        if (GES_IS_GROUP (container))
-          container->children_control_mode = GES_CHILDREN_UPDATE_OFFSETS;
-        /* Make sure not to move 2 times the same Clip */
-        if (g_list_find (moved_clips, container) == NULL) {
-          _set_start0 (GES_TIMELINE_ELEMENT (trackelement), new_start);
-          moved_clips = g_list_prepend (moved_clips, container);
-        }
-        if (GES_IS_GROUP (container))
-          container->children_control_mode = GES_CHILDREN_UPDATE;
-      }
-
-      g_list_free (moved_clips);
+      timeline->priv->needs_transitions_update = FALSE;
+      new_duration =
+          CLAMP (position - obj->start, 0, obj->maxduration - obj->inpoint);
+      res =
+          timeline_tree_ripple (timeline->priv->tree,
+          (gint64) GES_TIMELINE_ELEMENT_LAYER_PRIORITY (obj) -
+          (gint64) new_layer_priority,
+          _DURATION (obj) - new_duration, obj,
+          GES_EDGE_END, timeline->priv->snapping_distance);
       timeline->priv->needs_transitions_update = TRUE;
+
       GST_DEBUG ("Done Rippling end");
       break;
     case GES_EDGE_START:
       GST_INFO ("Ripple start doesn't make sense, trimming instead");
-      timeline->priv->movecontext.needs_move_ctx = TRUE;
-      if (!timeline_trim_object (timeline, obj, layers, edge, position))
+      if (!timeline_trim_object (timeline, obj, -1, layers, edge, position))
         goto error;
       break;
     default:
@@ -1873,12 +912,9 @@ timeline_ripple_object (GESTimeline * timeline, GESTrackElement * obj,
       break;
   }
 
-  mv_ctx->ignore_needs_ctx = FALSE;
-
-  return TRUE;
+  return res;
 
 error:
-  mv_ctx->ignore_needs_ctx = FALSE;
 
   return FALSE;
 }
@@ -1894,352 +930,126 @@ timeline_slide_object (GESTimeline * timeline, GESTrackElement * obj,
   return FALSE;
 }
 
-gboolean
-timeline_trim_object (GESTimeline * timeline, GESTrackElement * object,
-    GList * layers, GESEdge edge, guint64 position)
-{
-  gboolean ret = FALSE;
-  GstClockTime cpos;
-  MoveContext *mv_ctx = &timeline->priv->movecontext;
-
-  mv_ctx->ignore_needs_ctx = TRUE;
-
-  timeline->priv->needs_rollback = FALSE;
-  if (!ges_timeline_set_moving_context (timeline, object, GES_EDIT_MODE_TRIM,
-          edge, layers))
-    goto end;
-
-  switch (edge) {
-    case GES_EDGE_START:
-      cpos = GES_TIMELINE_ELEMENT_START (object);
-      break;
-    case GES_EDGE_END:
-      cpos = GES_TIMELINE_ELEMENT_END (object);
-      break;
-    default:
-      goto end;
-  }
-  ret = ges_timeline_trim_object_simple (timeline,
-      GES_TIMELINE_ELEMENT (object), layers, edge, position, TRUE);
-
-  if (timeline->priv->needs_rollback && !timeline->priv->rolling_back) {
-    timeline->priv->rolling_back = TRUE;
-    ret = FALSE;
-    timeline_trim_object (timeline, object, layers, edge, cpos);
-    ges_timeline_emit_snappig (timeline, object, NULL);
-    timeline->priv->rolling_back = FALSE;
-  }
-
-end:
-  mv_ctx->ignore_needs_ctx = FALSE;
-
-  return ret;
-}
-
-gboolean
-timeline_roll_object (GESTimeline * timeline, GESTrackElement * obj,
-    GList * layers, GESEdge edge, guint64 position)
+static gboolean
+_trim_transition (GESTimeline * timeline, GESTimelineElement * element,
+    GESEdge edge, GstClockTime position)
 {
-  MoveContext *mv_ctx = &timeline->priv->movecontext;
-  guint64 start, duration, end, tmpstart, tmpduration, tmpend, *snapped, *cur;
-  gboolean ret = TRUE;
   GList *tmp;
+  GESLayer *layer = ges_timeline_get_layer (timeline,
+      GES_TIMELINE_ELEMENT_LAYER_PRIORITY (element));
 
-  mv_ctx->ignore_needs_ctx = TRUE;
-
-  GST_DEBUG_OBJECT (obj, "Rolling object to %" GST_TIME_FORMAT,
-      GST_TIME_ARGS (position));
-
-  if (!ges_timeline_set_moving_context (timeline, obj, GES_EDIT_MODE_ROLL,
-          edge, layers))
-    goto error;
-
-  start = _START (obj);
-  duration = _DURATION (obj);
-  end = start + duration;
-
-  timeline->priv->needs_transitions_update = FALSE;
-  switch (edge) {
-    case GES_EDGE_START:
-
-      /* Avoid negative durations */
-      if (position < mv_ctx->max_trim_pos || position > end ||
-          position < mv_ctx->min_trim_pos)
-        goto error;
-
-      cur = g_hash_table_lookup (timeline->priv->by_start, obj);
-      snapped = ges_timeline_snap_position (timeline, obj, cur, position, TRUE);
-      if (snapped)
-        position = *snapped;
-
-      ret &= ges_timeline_trim_object_simple (timeline,
-          GES_TIMELINE_ELEMENT (obj), layers, GES_EDGE_START, position, FALSE);
-
-      if (!ret) {
-        GST_INFO_OBJECT (timeline, "Could not trim %s",
-            GES_TIMELINE_ELEMENT_NAME (obj));
-
-        return FALSE;
-      }
-
-
-      /* In the case we reached max_duration we just make sure to roll
-       * everything to the real new position */
-      position = _START (obj);
-
-      /* Send back changes to the neighbourhood */
-      for (tmp = mv_ctx->moving_trackelements; tmp; tmp = tmp->next) {
-        GESTimelineElement *tmpelement = GES_TIMELINE_ELEMENT (tmp->data);
+  if (!ges_layer_get_auto_transition (layer))
+    goto fail;
 
-        tmpstart = _START (tmpelement);
-        tmpduration = _DURATION (tmpelement);
-        tmpend = tmpstart + tmpduration;
+  gst_object_unref (layer);
+  for (tmp = timeline->priv->auto_transitions; tmp; tmp = tmp->next) {
+    GESAutoTransition *auto_transition = tmp->data;
 
-        /* Check that the object should be resized at this position
-         * even if an error accurs, we keep doing our job */
-        if (tmpend == start) {
-          ret &= ges_timeline_trim_object_simple (timeline, tmpelement, NULL,
-              GES_EDGE_END, position, FALSE);
-          break;
+    if (GES_TIMELINE_ELEMENT (auto_transition->transition) == element ||
+        GES_TIMELINE_ELEMENT (auto_transition->transition_clip) == element) {
+      /* Trimming an auto transition means trimming its neighboors */
+      if (!auto_transition->positioning) {
+        if (edge == GES_EDGE_END) {
+          ges_container_edit (GES_CONTAINER (auto_transition->previous_clip),
+              NULL, -1, GES_EDIT_MODE_TRIM, GES_EDGE_END, position);
+        } else {
+          ges_container_edit (GES_CONTAINER (auto_transition->next_clip),
+              NULL, -1, GES_EDIT_MODE_TRIM, GES_EDGE_START, position);
         }
-      }
-      break;
-    case GES_EDGE_END:
-
-      /* Avoid negative durations */
-      if (position > mv_ctx->max_trim_pos || position < start)
-        goto error;
-
-      end = _START (obj) + _DURATION (obj);
-
-      cur = g_hash_table_lookup (timeline->priv->by_end, obj);
-      snapped = ges_timeline_snap_position (timeline, obj, cur, position, TRUE);
-      if (snapped)
-        position = *snapped;
 
-      ret &= ges_timeline_trim_object_simple (timeline,
-          GES_TIMELINE_ELEMENT (obj), NULL, GES_EDGE_END, position, FALSE);
-
-      if (ret == FALSE) {
-        GST_DEBUG_OBJECT (timeline, "No triming, bailing out");
-        goto done;
+        return TRUE;
       }
 
-      /* In the case we reached max_duration we just make sure to roll
-       * everything to the real new position */
-      position = _START (obj) + _DURATION (obj);
-
-      /* Send back changes to the neighbourhood */
-      for (tmp = mv_ctx->moving_trackelements; tmp; tmp = tmp->next) {
-        GESTimelineElement *tmpelement = GES_TIMELINE_ELEMENT (tmp->data);
-
-        tmpstart = _START (tmpelement);
-        tmpduration = _DURATION (tmpelement);
-        tmpend = tmpstart + tmpduration;
-
-        /* Check that the object should be resized at this position
-         * even if an error accure, we keep doing our job */
-        if (end == tmpstart) {
-          ret &= ges_timeline_trim_object_simple (timeline, tmpelement, NULL,
-              GES_EDGE_START, position, FALSE);
-        }
-      }
-      break;
-    default:
-      GST_DEBUG ("Edge type %i not handled here", edge);
-      break;
+      return FALSE;
+    }
   }
 
-done:
-  timeline->priv->needs_transitions_update = TRUE;
-  mv_ctx->ignore_needs_ctx = FALSE;
-
-  return ret;
-
-error:
-  GST_DEBUG_OBJECT (obj, "Could not roll edge %d to %" GST_TIME_FORMAT,
-      edge, GST_TIME_ARGS (position));
+  return FALSE;
 
-  ret = FALSE;
-  goto done;
+fail:
+  gst_object_unref (layer);
+  return FALSE;
 }
 
+
 gboolean
-timeline_move_object (GESTimeline * timeline, GESTrackElement * object,
-    GList * layers, GESEdge edge, guint64 position)
+timeline_trim_object (GESTimeline * timeline, GESTimelineElement * object,
+    guint32 new_layer_priority, GList * layers, GESEdge edge, guint64 position)
 {
-  if (!ges_timeline_set_moving_context (timeline, object, GES_EDIT_MODE_NORMAL,
-          edge, layers)) {
-    GST_DEBUG_OBJECT (object, "Could not move to %" GST_TIME_FORMAT,
-        GST_TIME_ARGS (position));
-
-    return FALSE;
+  if ((GES_IS_TRANSITION (object) || GES_IS_TRANSITION_CLIP (object)) &&
+      !ELEMENT_FLAG_IS_SET (object, GES_TIMELINE_ELEMENT_SET_SIMPLE)) {
+    return _trim_transition (timeline, object, edge, position);
   }
 
-  return ges_timeline_move_object_simple (timeline,
-      GES_TIMELINE_ELEMENT (object), layers, edge, position);
+  return timeline_tree_trim (timeline->priv->tree,
+      GES_TIMELINE_ELEMENT (object), new_layer_priority > 0 ? (gint64)
+      ges_timeline_element_get_layer_priority (GES_TIMELINE_ELEMENT (object)) -
+      new_layer_priority : 0, edge == GES_EDGE_END ? GST_CLOCK_DIFF (position,
+          _START (object) + _DURATION (object)) : GST_CLOCK_DIFF (position,
+          GES_TIMELINE_ELEMENT_START (object)), edge,
+      timeline->priv->snapping_distance);
 }
 
 gboolean
-ges_timeline_move_object_simple (GESTimeline * timeline,
-    GESTimelineElement * element, GList * layers, GESEdge edge,
-    guint64 position)
+timeline_roll_object (GESTimeline * timeline, GESTimelineElement * element,
+    GList * layers, GESEdge edge, guint64 position)
 {
-  GstClockTime cpos = GES_TIMELINE_ELEMENT_START (element);
-  guint64 *snap_end, *snap_st, *cur, position_offset, off1, off2, top_end;
-  GESTrackElement *track_element;
-  GESContainer *toplevel;
-
-  /* We only work with GESSource-s and we check that we are not already moving
-   * the specified element ourself */
-  if (GES_IS_SOURCE (element) == FALSE) {
-    GST_INFO_OBJECT (element, "Not a source, not moving.");
-    return FALSE;
-  }
-
-  if (g_list_find (timeline->priv->movecontext.moving_trackelements, element)) {
-    GST_DEBUG_OBJECT (element, "Already part of the moving context.");
-    return TRUE;
-  }
-
-  timeline->priv->needs_rollback = FALSE;
-  track_element = GES_TRACK_ELEMENT (element);
-  toplevel = get_toplevel_container (track_element);
-  position_offset = position - _START (track_element);
-
-  top_end = _START (toplevel) + _DURATION (toplevel) + position_offset;
-  cur = g_hash_table_lookup (timeline->priv->by_end, track_element);
-
-  GST_DEBUG_OBJECT (timeline, "Moving %" GST_PTR_FORMAT " to %"
-      GST_TIME_FORMAT " (end %" GST_TIME_FORMAT ")", element,
-      GST_TIME_ARGS (position), GST_TIME_ARGS (top_end));
-
-  snap_end = ges_timeline_snap_position (timeline, track_element, cur, top_end,
-      FALSE);
-  if (snap_end)
-    off1 = top_end > *snap_end ? top_end - *snap_end : *snap_end - top_end;
-  else
-    off1 = G_MAXUINT64;
-
-  cur = g_hash_table_lookup (timeline->priv->by_start, track_element);
-  snap_st =
-      ges_timeline_snap_position (timeline, track_element, cur, position,
-      FALSE);
-  if (snap_st)
-    off2 = position > *snap_st ? position - *snap_st : *snap_st - position;
-  else
-    off2 = G_MAXUINT64;
-
-  /* In the case we could snap on both sides, we snap on the end */
-  if (snap_end && off1 <= off2) {
-    position = position + *snap_end - top_end;
-    ges_timeline_emit_snappig (timeline, track_element, snap_end);
-  } else if (snap_st) {
-    position = position + *snap_st - position;
-    ges_timeline_emit_snappig (timeline, track_element, snap_st);
-  } else
-    ges_timeline_emit_snappig (timeline, track_element, NULL);
-  timeline->priv->needs_rollback = FALSE;
-
-  _set_start0 (GES_TIMELINE_ELEMENT (track_element), position);
-
-  if (timeline->priv->needs_rollback && !timeline->priv->rolling_back) {
-    timeline->priv->needs_rollback = FALSE;
-    timeline->priv->rolling_back = TRUE;
-    ges_timeline_move_object_simple (timeline, element, layers, edge, cpos);
-    ges_timeline_emit_snappig (timeline, track_element, NULL);
-    timeline->priv->rolling_back = FALSE;
-
-    return FALSE;
-  }
-
-  return TRUE;
+  return timeline_tree_roll (timeline->priv->tree,
+      element,
+      (edge == GES_EDGE_END) ?
+      GST_CLOCK_DIFF (position, _END (element)) :
+      GST_CLOCK_DIFF (position, _START (element)),
+      edge, timeline->priv->snapping_distance);
 }
 
 gboolean
-timeline_context_to_layer (GESTimeline * timeline, gint offset)
+timeline_move_object (GESTimeline * timeline, GESTimelineElement * object,
+    guint32 new_layer_priority, GList * layers, GESEdge edge, guint64 position)
 {
-  gboolean ret = TRUE;
-  GHashTableIter iter;
-  GESContainer *key, *value;
-  GESLayer *new_layer;
-  guint prio;
-  MoveContext *mv_ctx = &timeline->priv->movecontext;
-
-  /* Layer's priority is always positive */
-  if (offset == 0)
-    return ret;
-
-  if (offset < 0 && mv_ctx->min_move_layer < -offset)
-    return ret;
-
-  GST_DEBUG ("Moving %d object, offset %d",
-      g_hash_table_size (mv_ctx->toplevel_containers), offset);
-
-  mv_ctx->ignore_needs_ctx = TRUE;
-  timeline->priv->needs_rollback = FALSE;
-  g_hash_table_iter_init (&iter, mv_ctx->toplevel_containers);
-  while (g_hash_table_iter_next (&iter, (gpointer *) & key,
-          (gpointer *) & value)) {
-
-    if (GES_IS_CLIP (value)) {
-      prio = ges_clip_get_layer_priority (GES_CLIP (value));
-
-      /* We know that the layer exists as we created it */
-      new_layer = GES_LAYER (g_list_nth_data (timeline->layers, prio + offset));
-
-      if (new_layer == NULL) {
-        do {
-          new_layer = ges_timeline_append_layer (timeline);
-        } while (ges_layer_get_priority (new_layer) < prio + offset);
-      }
-
-      mv_ctx->moving_to_layer = new_layer;
-      ret &= ges_clip_move_to_layer (GES_CLIP (key), new_layer);
-    } else if (GES_IS_GROUP (value)) {
-      guint32 last_prio = _PRIORITY (value) + offset +
-          GES_CONTAINER_HEIGHT (value) - 1;
-
-      new_layer = GES_LAYER (g_list_nth_data (timeline->layers, last_prio));
-
-      if (new_layer == NULL) {
-        do {
-          new_layer = ges_timeline_append_layer (timeline);
-        } while (ges_layer_get_priority (new_layer) < last_prio);
-      }
-
-      mv_ctx->moving_to_layer = NULL;
-      _set_priority0 (GES_TIMELINE_ELEMENT (value), _PRIORITY (value) + offset);
-    }
-  }
-
-  /* Readjust min_move_layer */
-  mv_ctx->min_move_layer = mv_ctx->min_move_layer + offset;
-  mv_ctx->ignore_needs_ctx = FALSE;
+  gboolean ret = FALSE;
+  GstClockTimeDiff offset = edge == GES_EDGE_END ?
+      GST_CLOCK_DIFF (position, _START (object) + _DURATION (object)) :
+      GST_CLOCK_DIFF (position, GES_TIMELINE_ELEMENT_START (object));
 
-  if (timeline->priv->needs_rollback && !timeline->priv->rolling_back) {
-    ret = FALSE;
-    timeline->priv->rolling_back = TRUE;
-    timeline_context_to_layer (timeline, -offset);
-    timeline->priv->rolling_back = FALSE;
-  }
-  mv_ctx->moving_to_layer = NULL;
+  ret = timeline_tree_move (timeline->priv->tree,
+      GES_TIMELINE_ELEMENT (object), new_layer_priority < 0 ? 0 : (gint64)
+      ges_timeline_element_get_layer_priority (GES_TIMELINE_ELEMENT (object)) -
+      new_layer_priority, offset, edge, timeline->priv->snapping_distance);
 
   return ret;
 }
 
+gboolean
+ges_timeline_move_object_simple (GESTimeline * timeline,
+    GESTimelineElement * element, GList * layers, GESEdge edge,
+    guint64 position)
+{
+  return timeline_move_object (timeline, element,
+      ges_timeline_element_get_layer_priority (element), NULL, edge, position);
+}
+
 void
 timeline_add_group (GESTimeline * timeline, GESGroup * group)
 {
   GST_DEBUG_OBJECT (timeline, "Adding group %" GST_PTR_FORMAT, group);
 
-  timeline->priv->movecontext.needs_move_ctx = TRUE;
   timeline->priv->groups = g_list_prepend (timeline->priv->groups,
       gst_object_ref_sink (group));
 
   ges_timeline_element_set_timeline (GES_TIMELINE_ELEMENT (group), timeline);
 }
 
+void
+timeline_update_transition (GESTimeline * timeline)
+{
+  GList *tmp, *auto_transs;
+
+  auto_transs = g_list_copy (timeline->priv->auto_transitions);
+  for (tmp = auto_transs; tmp; tmp = tmp->next)
+    ges_auto_transition_update (tmp->data);
+  g_list_free (auto_transs);
+}
+
 /**
  * timeline_emit_group_added:
  * @timeline: a #GESTimeline
@@ -2257,7 +1067,6 @@ timeline_emit_group_added (GESTimeline * timeline, GESGroup * group)
  * timeline_emit_group_removed:
  * @timeline: a #GESTimeline
  * @group: group that was removed
- * @array: (element-type GESTimelineElement): children that were removed
  *
  * Emit group-removed signal.
  */
@@ -2276,7 +1085,6 @@ timeline_remove_group (GESTimeline * timeline, GESGroup * group)
 
   timeline->priv->groups = g_list_remove (timeline->priv->groups, group);
 
-  timeline->priv->movecontext.needs_move_ctx = TRUE;
   ges_timeline_element_set_timeline (GES_TIMELINE_ELEMENT (group), NULL);
   gst_object_unref (group);
 }
@@ -2336,10 +1144,8 @@ layer_auto_transition_changed_cb (GESLayer * layer,
 {
   GList *tmp, *clips;
 
-  timeline->priv->needs_rollback = FALSE;
-  _create_transitions_on_layer (timeline, layer, NULL, NULL,
+  timeline_tree_create_transitions (timeline->priv->tree,
       _create_auto_transition_from_transitions);
-
   clips = ges_layer_get_clips (layer);
   for (tmp = clips; tmp; tmp = tmp->next) {
     if (GES_IS_TRANSITION_CLIP (tmp->data)) {
@@ -2489,6 +1295,9 @@ clip_track_element_added_cb (GESClip * clip,
   }
   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
@@ -2521,13 +1330,11 @@ layer_object_added_cb (GESLayer * layer, GESClip * clip, GESTimeline * timeline)
   if (ges_clip_is_moving_from_layer (clip)) {
     GST_DEBUG ("Clip %p moving from one layer to another, not creating "
         "TrackElement", clip);
-    timeline->priv->movecontext.needs_move_ctx = TRUE;
-    _create_transitions_on_layer (timeline, layer, NULL, NULL,
-        _find_transition_from_auto_transitions);
+    timeline_tree_create_transitions (timeline->priv->tree,
+        ges_timeline_find_auto_transition);
     return;
   }
 
-
   add_object_to_tracks (timeline, clip, NULL);
 
   GST_DEBUG ("Making sure that the asset is in our project");
@@ -2603,98 +1410,7 @@ static void
 trackelement_start_changed_cb (GESTrackElement * child,
     GParamSpec * arg G_GNUC_UNUSED, GESTimeline * timeline)
 {
-  GESTimelinePrivate *priv = timeline->priv;
-  TrackObjIters *iters = g_hash_table_lookup (priv->obj_iters, child);
-
-  if (G_LIKELY (iters->iter_by_layer))
-    g_sequence_sort_changed (iters->iter_by_layer,
-        (GCompareDataFunc) element_start_compare, NULL);
-
-  if (GES_IS_SOURCE (child)) {
-    sort_track_elements (timeline, iters);
-    sort_starts_ends_start (timeline, iters);
-    sort_starts_ends_end (timeline, iters);
-
-    /* If the timeline is set to snap objects together, we
-     * are sure that all movement of TrackElement-s are done within
-     * the moving context, so we do not need to recalculate the
-     * move context as often */
-    if (timeline->priv->movecontext.ignore_needs_ctx &&
-        timeline->priv->snapping_distance == 0)
-      timeline->priv->movecontext.needs_move_ctx = TRUE;
-
-    timeline_create_transitions (timeline, child);
-  }
-}
-
-static void
-trackelement_priority_changed_cb (GESTrackElement * child,
-    GParamSpec * arg G_GNUC_UNUSED, GESTimeline * timeline)
-{
-  GESTimelinePrivate *priv = timeline->priv;
-
-  GList *layer_node = g_list_find_custom (timeline->layers,
-      GINT_TO_POINTER (GES_TIMELINE_ELEMENT_LAYER_PRIORITY (child)),
-      (GCompareFunc) find_layer_by_prio);
-  GESLayer *layer = layer_node ? layer_node->data : NULL;
-  TrackObjIters *iters = g_hash_table_lookup (priv->obj_iters,
-      child);
-
-  if (G_UNLIKELY (layer == NULL)) {
-    GST_ERROR_OBJECT (timeline,
-        "Changing a TrackElement prio, which would not "
-        "land in no layer we are controlling");
-    if (iters->iter_by_layer)
-      g_sequence_remove (iters->iter_by_layer);
-    iters->iter_by_layer = NULL;
-    iters->layer = NULL;
-  } else {
-    /* If it moves from layer, properly change it */
-    if (layer != iters->layer) {
-      GSequence *by_layer_sequence =
-          g_hash_table_lookup (priv->by_layer, layer);
-
-      GST_DEBUG_OBJECT (child, "Moved from layer %" GST_PTR_FORMAT
-          "(prio %d) to" " %" GST_PTR_FORMAT " (prio %d)", layer,
-          ges_layer_get_priority (layer), iters->layer,
-          ges_layer_get_priority (iters->layer));
-
-      g_sequence_remove (iters->iter_by_layer);
-      iters->iter_by_layer =
-          g_sequence_insert_sorted (by_layer_sequence, child,
-          (GCompareDataFunc) element_start_compare, NULL);
-      iters->layer = layer;
-    } else {
-      g_sequence_sort_changed (iters->iter_by_layer,
-          (GCompareDataFunc) element_start_compare, NULL);
-    }
-  }
-
-  if (GES_IS_SOURCE (child))
-    sort_track_elements (timeline, iters);
-}
-
-static void
-trackelement_duration_changed_cb (GESTrackElement * child,
-    GParamSpec * arg G_GNUC_UNUSED, GESTimeline * timeline)
-{
-  GESTimelinePrivate *priv = timeline->priv;
-  TrackObjIters *iters = g_hash_table_lookup (priv->obj_iters, child);
-
-  if (GES_IS_SOURCE (child)) {
-    sort_starts_ends_end (timeline, iters);
-
-    /* If the timeline is set to snap objects together, we
-     * are sure that all movement of TrackElement-s are done within
-     * the moving context, so we do not need to recalculate the
-     * move context as often */
-    if (timeline->priv->movecontext.ignore_needs_ctx &&
-        timeline->priv->snapping_distance == 0) {
-      timeline->priv->movecontext.needs_move_ctx = TRUE;
-    }
-
-    timeline_create_transitions (timeline, child);
-  }
+  timeline_update_duration (timeline);
 }
 
 static void
@@ -2704,35 +1420,15 @@ track_element_added_cb (GESTrack * track, GESTrackElement * track_element,
   /* Auto transition should be updated before we receive the signal */
   g_signal_connect_after (GES_TRACK_ELEMENT (track_element), "notify::start",
       G_CALLBACK (trackelement_start_changed_cb), timeline);
-  g_signal_connect_after (GES_TRACK_ELEMENT (track_element),
-      "notify::duration", G_CALLBACK (trackelement_duration_changed_cb),
-      timeline);
-  g_signal_connect_after (GES_TRACK_ELEMENT (track_element),
-      "notify::priority", G_CALLBACK (trackelement_priority_changed_cb),
-      timeline);
-
-  start_tracking_track_element (timeline, track_element);
 }
 
 static void
 track_element_removed_cb (GESTrack * track,
     GESTrackElement * track_element, GESTimeline * timeline)
 {
-
-  if (GES_IS_SOURCE (track_element)) {
-    /* Make sure to reinitialise the moving context next time */
-    timeline->priv->movecontext.needs_move_ctx = TRUE;
-  }
-
   /* Disconnect all signal handlers */
   g_signal_handlers_disconnect_by_func (track_element,
       trackelement_start_changed_cb, timeline);
-  g_signal_handlers_disconnect_by_func (track_element,
-      trackelement_duration_changed_cb, timeline);
-  g_signal_handlers_disconnect_by_func (track_element,
-      trackelement_priority_changed_cb, timeline);
-
-  stop_tracking_track_element (timeline, track_element);
 }
 
 static GstPadProbeReturn
@@ -2825,13 +1521,21 @@ timeline_add_element (GESTimeline * timeline, GESTimelineElement * element)
   g_hash_table_insert (timeline->priv->all_elements,
       ges_timeline_element_get_name (element), gst_object_ref (element));
 
+  timeline_tree_track_element (timeline->priv->tree, element);
+
   return TRUE;
 }
 
 gboolean
 timeline_remove_element (GESTimeline * timeline, GESTimelineElement * element)
 {
-  return g_hash_table_remove (timeline->priv->all_elements, element->name);
+  if (g_hash_table_remove (timeline->priv->all_elements, element->name)) {
+    timeline_tree_stop_tracking_element (timeline->priv->tree, element);
+
+    return TRUE;
+  }
+
+  return FALSE;
 }
 
 void
@@ -2844,6 +1548,12 @@ timeline_fill_gaps (GESTimeline * timeline)
   }
 }
 
+GNode *
+timeline_get_tree (GESTimeline * timeline)
+{
+  return timeline->priv->tree;
+}
+
 /**** API *****/
 /**
  * ges_timeline_new:
@@ -3057,8 +1767,6 @@ ges_timeline_add_layer (GESTimeline * timeline, GESLayer * layer)
   /* Inform the layer that it belongs to a new timeline */
   ges_layer_set_timeline (layer, timeline);
 
-  g_hash_table_insert (timeline->priv->by_layer, layer, g_sequence_new (NULL));
-
   /* 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);
@@ -3081,8 +1789,6 @@ ges_timeline_add_layer (GESTimeline * timeline, GESLayer * layer)
   }
   g_list_free (objects);
 
-  timeline->priv->movecontext.needs_move_ctx = TRUE;
-
   return TRUE;
 }
 
@@ -3134,14 +1840,12 @@ ges_timeline_remove_layer (GESTimeline * timeline, GESLayer * layer)
   g_signal_handlers_disconnect_by_func (layer,
       layer_auto_transition_changed_cb, timeline);
 
-  g_hash_table_remove (timeline->priv->by_layer, layer);
   timeline->layers = g_list_remove (timeline->layers, layer);
   ges_layer_set_timeline (layer, NULL);
 
   g_signal_emit (timeline, ges_timeline_signals[LAYER_REMOVED], 0, layer);
 
   gst_object_unref (layer);
-  timeline->priv->movecontext.needs_move_ctx = TRUE;
 
   return TRUE;
 }
@@ -3451,12 +2155,11 @@ ges_timeline_commit_unlocked (GESTimeline * timeline)
 
   GST_DEBUG_OBJECT (timeline, "commiting changes");
 
+  timeline_tree_create_transitions (timeline->priv->tree,
+      ges_timeline_find_auto_transition);
   for (tmp = timeline->layers; tmp; tmp = tmp->next) {
     GESLayer *layer = tmp->data;
 
-    _create_transitions_on_layer (timeline, layer, NULL, NULL,
-        _find_transition_from_auto_transitions);
-
     /* Ensure clip priorities are correct after an edit */
     ges_layer_resync_priorities (layer);
   }
@@ -3475,9 +2178,6 @@ ges_timeline_commit_unlocked (GESTimeline * timeline)
     }
   }
 
-  /* Make sure we reset the context */
-  timeline->priv->movecontext.needs_move_ctx = TRUE;
-
   return res;
 }
 
@@ -3521,7 +2221,7 @@ ges_timeline_commit (GESTimeline * timeline)
   ret = ges_timeline_commit_unlocked (timeline);
   UNLOCK_DYN (timeline);
 
-  ges_timeline_emit_snappig (timeline, NULL, NULL);
+  ges_timeline_emit_snapping (timeline, NULL, NULL, GST_CLOCK_TIME_NONE);
   return ret;
 }
 
index f744649..ca18bd5 100644 (file)
@@ -1364,16 +1364,21 @@ ges_track_element_edit (GESTrackElement * object,
 
   switch (mode) {
     case GES_EDIT_MODE_NORMAL:
-      return timeline_move_object (timeline, object, layers, edge, position);
+      return timeline_move_object (timeline, GES_TIMELINE_ELEMENT (object), -1,
+          layers, edge, position);
       break;
     case GES_EDIT_MODE_TRIM:
-      return timeline_trim_object (timeline, object, layers, edge, position);
+      return timeline_trim_object (timeline, GES_TIMELINE_ELEMENT (object), -1,
+          layers, edge, position);
       break;
     case GES_EDIT_MODE_RIPPLE:
-      return timeline_ripple_object (timeline, object, layers, edge, position);
+      return timeline_ripple_object (timeline, GES_TIMELINE_ELEMENT (object),
+          GES_TIMELINE_ELEMENT_PRIORITY (object) / LAYER_HEIGHT,
+          layers, edge, position);
       break;
     case GES_EDIT_MODE_ROLL:
-      return timeline_roll_object (timeline, object, layers, edge, position);
+      return timeline_roll_object (timeline, GES_TIMELINE_ELEMENT (object),
+          layers, edge, position);
       break;
     case GES_EDIT_MODE_SLIDE:
       return timeline_slide_object (timeline, object, layers, edge, position);
index 3f8f63c..91ef527 100644 (file)
@@ -276,6 +276,24 @@ extractable_set_asset (GESExtractable * self, GESAsset * asset)
   g_return_val_if_fail (GES_IS_URI_CLIP_ASSET (asset), FALSE);
 
   uri_clip_asset = GES_URI_CLIP_ASSET (asset);
+  if (GST_CLOCK_TIME_IS_VALID (GES_TIMELINE_ELEMENT_DURATION (self)) &&
+      ges_uri_clip_asset_get_duration (uri_clip_asset) <
+      GES_TIMELINE_ELEMENT_INPOINT (self) +
+      GES_TIMELINE_ELEMENT_DURATION (self)) {
+    GST_INFO_OBJECT (self,
+        "Can not set asset to %p as its duration is %" GST_TIME_FORMAT
+        " < to inpoint %" GST_TIME_FORMAT " + %" GST_TIME_FORMAT " = %"
+        GST_TIME_FORMAT, asset,
+        GST_TIME_ARGS (ges_uri_clip_asset_get_duration (uri_clip_asset)),
+        GST_TIME_ARGS (GES_TIMELINE_ELEMENT_INPOINT (self)),
+        GST_TIME_ARGS (GES_TIMELINE_ELEMENT_DURATION (self)),
+        GST_TIME_ARGS (GES_TIMELINE_ELEMENT_INPOINT (self) +
+            GES_TIMELINE_ELEMENT_DURATION (self)));
+
+
+    return FALSE;
+  }
+
   if (GST_CLOCK_TIME_IS_VALID (GES_TIMELINE_ELEMENT_DURATION (clip)) == FALSE)
     _set_duration0 (GES_TIMELINE_ELEMENT (uriclip),
         ges_uri_clip_asset_get_duration (uri_clip_asset));
index ba89315..8b57f61 100644 (file)
@@ -51,6 +51,7 @@ ges_sources = [
     'ges-command-line-formatter.c',
     'ges-auto-transition.c',
     'ges-timeline-element.c',
+    'ges-timeline-tree.c',
     'ges-container.c',
     'ges-effect-asset.c',
     'ges-smart-adder.c',
index da8cd1d..c604c42 100644 (file)
@@ -227,6 +227,9 @@ GST_START_TEST (test_uri_clip_change_asset)
   asset1 = GES_ASSET (ges_uri_clip_asset_request_sync (uri1, NULL));
   fail_unless_equals_int (g_list_length (GES_CONTAINER_CHILDREN (extractable)),
       2);
+  fail_if (ges_extractable_set_asset (extractable, asset1));
+  ges_timeline_element_set_duration (GES_TIMELINE_ELEMENT (extractable),
+      ges_uri_clip_asset_get_duration (GES_URI_CLIP_ASSET (asset1)));
   fail_unless (ges_extractable_set_asset (extractable, asset1));
   fail_unless_equals_int (g_list_length (GES_CONTAINER_CHILDREN (extractable)),
       1);
index 9945256..d37d9c1 100644 (file)
@@ -94,13 +94,13 @@ GST_START_TEST (test_ges_scenario)
   /* There are 3 references:
    * 1 by the clip
    * 1 by the track
-   * 2 by the timeline */
-  ASSERT_OBJECT_REFCOUNT (trackelement, "trackelement", 4);
+   * 1 by the timeline */
+  ASSERT_OBJECT_REFCOUNT (trackelement, "trackelement", 3);
   /* There are 3 references:
    * 1 by the clip
-   * 2 by the timeline
+   * 3 by the timeline
    * 1 by the track */
-  ASSERT_OBJECT_REFCOUNT (trackelement, "trackelement", 4);
+  ASSERT_OBJECT_REFCOUNT (trackelement, "trackelement", 3);
 
   GST_DEBUG ("Remove the Clip from the layer");
 
@@ -225,13 +225,13 @@ GST_START_TEST (test_ges_timeline_add_layer)
   /* There are 3 references:
    * 1 by the clip
    * 1 by the trackelement
-   * 2 by the timeline */
-  ASSERT_OBJECT_REFCOUNT (trackelement, "trackelement", 4);
+   * 1 by the timeline */
+  ASSERT_OBJECT_REFCOUNT (trackelement, "trackelement", 3);
   /* There are 3 references:
    * 1 by the clip
    * 1 by the timeline
-   * 2 by the trackelement */
-  ASSERT_OBJECT_REFCOUNT (trackelement, "trackelement", 4);
+   * 1 by the trackelement */
+  ASSERT_OBJECT_REFCOUNT (trackelement, "trackelement", 3);
 
   trackelements = GES_CONTAINER_CHILDREN (s2);
   trackelement = GES_TRACK_ELEMENT (trackelements->data);
@@ -240,8 +240,8 @@ GST_START_TEST (test_ges_timeline_add_layer)
   /* There are 3 references:
    * 1 by the clip
    * 1 by the timeline
-   * 2 by the trackelement */
-  ASSERT_OBJECT_REFCOUNT (GES_TRACK_ELEMENT (trackelement), "trackelement", 4);
+   * 1 by the trackelement */
+  ASSERT_OBJECT_REFCOUNT (GES_TRACK_ELEMENT (trackelement), "trackelement", 3);
 
   trackelements = GES_CONTAINER_CHILDREN (s3);
   trackelement = GES_TRACK_ELEMENT (trackelements->data);
@@ -251,7 +251,7 @@ GST_START_TEST (test_ges_timeline_add_layer)
    * 1 by the clip
    * 1 by the timeline
    * 2 by the trackelement */
-  ASSERT_OBJECT_REFCOUNT (trackelement, "trackelement", 4);
+  ASSERT_OBJECT_REFCOUNT (trackelement, "trackelement", 3);
 
   /* theoretically this is all we need to do to ensure cleanup */
   gst_object_unref (timeline);
@@ -334,8 +334,8 @@ GST_START_TEST (test_ges_timeline_add_layer_first)
     /* Each object has 3 references:
      * 1 by the clip
      * 1 by the track
-     * 2 by the timeline */
-    ASSERT_OBJECT_REFCOUNT (GES_TRACK_ELEMENT (tmp->data), "trackelement", 4);
+     * 1 by the timeline */
+    ASSERT_OBJECT_REFCOUNT (GES_TRACK_ELEMENT (tmp->data), "trackelement", 3);
   }
 
   trackelements = GES_CONTAINER_CHILDREN (s2);
@@ -344,8 +344,8 @@ GST_START_TEST (test_ges_timeline_add_layer_first)
     /* Each object has 3 references:
      * 1 by the clip
      * 1 by the track
-     * 2 by the timeline */
-    ASSERT_OBJECT_REFCOUNT (GES_TRACK_ELEMENT (tmp->data), "trackelement", 4);
+     * 1 by the timeline */
+    ASSERT_OBJECT_REFCOUNT (GES_TRACK_ELEMENT (tmp->data), "trackelement", 3);
   }
 
   trackelements = GES_CONTAINER_CHILDREN (s3);
@@ -354,8 +354,8 @@ GST_START_TEST (test_ges_timeline_add_layer_first)
     /* Each object has 3 references:
      * 1 by the clip
      * 1 by the track
-     * 2 by the timeline */
-    ASSERT_OBJECT_REFCOUNT (GES_TRACK_ELEMENT (tmp->data), "trackelement", 4);
+     * 1 by the timeline */
+    ASSERT_OBJECT_REFCOUNT (GES_TRACK_ELEMENT (tmp->data), "trackelement", 3);
   }
 
   /* theoretically this is all we need to do to ensure cleanup */
@@ -444,14 +444,14 @@ GST_START_TEST (test_ges_timeline_remove_track)
     /* There are 3 references held:
      * 1 by the clip
      * 1 by the track
-     * 2 by the timeline */
-    ASSERT_OBJECT_REFCOUNT (GES_TRACK_ELEMENT (tmp->data), "trackelement", 4);
+     * 1 by the timeline */
+    ASSERT_OBJECT_REFCOUNT (GES_TRACK_ELEMENT (tmp->data), "trackelement", 3);
   }
   /* There are 3 references held:
    * 1 by the container
    * 1 by the track
-   * 2 by the timeline */
-  ASSERT_OBJECT_REFCOUNT (t1, "trackelement", 4);
+   * 1 by the timeline */
+  ASSERT_OBJECT_REFCOUNT (t1, "trackelement", 3);
 
   trackelements = GES_CONTAINER_CHILDREN (s2);
   fail_unless (trackelements != NULL);
@@ -460,14 +460,14 @@ GST_START_TEST (test_ges_timeline_remove_track)
     /* There are 3 references held:
      * 1 by the clip
      * 1 by the track
-     * 2 by the timeline */
-    ASSERT_OBJECT_REFCOUNT (GES_TRACK_ELEMENT (tmp->data), "trackelement", 4);
+     * 1 by the timeline */
+    ASSERT_OBJECT_REFCOUNT (GES_TRACK_ELEMENT (tmp->data), "trackelement", 3);
   }
   /* There are 3 references held:
    * 1 by the container
    * 1 by the track
-   * 2 by the timeline */
-  ASSERT_OBJECT_REFCOUNT (t2, "t2", 4);
+   * 1 by the timeline */
+  ASSERT_OBJECT_REFCOUNT (t2, "t2", 3);
 
   trackelements = GES_CONTAINER_CHILDREN (s3);
   fail_unless (trackelements != NULL);
@@ -476,21 +476,21 @@ GST_START_TEST (test_ges_timeline_remove_track)
     /* There are 3 references held:
      * 1 by the clip
      * 1 by the track
-     * 2 by the timeline */
-    ASSERT_OBJECT_REFCOUNT (GES_TRACK_ELEMENT (tmp->data), "trackelement", 4);
+     * 1 by the timeline */
+    ASSERT_OBJECT_REFCOUNT (GES_TRACK_ELEMENT (tmp->data), "trackelement", 3);
   }
   /* There are 3 references held:
    * 1 by the container
    * 1 by the track
-   * 2 by the timeline */
-  ASSERT_OBJECT_REFCOUNT (t3, "t3", 4);
+   * 1 by the timeline */
+  ASSERT_OBJECT_REFCOUNT (t3, "t3", 3);
 
   /* remove the track and check that the track elements have been released */
   fail_unless (ges_timeline_remove_track (timeline, track));
 
-  ASSERT_OBJECT_REFCOUNT (t1, "trackelement", 2);
-  ASSERT_OBJECT_REFCOUNT (t2, "trackelement", 2);
-  ASSERT_OBJECT_REFCOUNT (t3, "trackelement", 2);
+  ASSERT_OBJECT_REFCOUNT (t1, "trackelement", 1);
+  ASSERT_OBJECT_REFCOUNT (t2, "trackelement", 1);
+  ASSERT_OBJECT_REFCOUNT (t3, "trackelement", 1);
   ASSERT_OBJECT_REFCOUNT (layer, "1 for the timeline", 1);
   ASSERT_OBJECT_REFCOUNT (timeline, "1 for the us", 1);
   tmp = ges_layer_get_clips (layer);
@@ -614,17 +614,17 @@ GST_START_TEST (test_ges_timeline_multiple_tracks)
     /* There are 3 references held:
      * 1 by the clip
      * 1 by the track
-     * 2 by the timeline */
-    ASSERT_OBJECT_REFCOUNT (GES_TRACK_ELEMENT (tmp->data), "trackelement", 4);
+     * 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
-   * 2 by the timeline
+   * 1 by the timeline
    * 1 added by ourselves above (gst_object_ref (t1)) */
-  ASSERT_OBJECT_REFCOUNT (t1, "trackelement", 5);
+  ASSERT_OBJECT_REFCOUNT (t1, "trackelement", 4);
 
   trackelements = GES_CONTAINER_CHILDREN (s2);
   fail_unless (trackelements != NULL);
@@ -633,17 +633,17 @@ GST_START_TEST (test_ges_timeline_multiple_tracks)
     /* There are 3 references held:
      * 1 by the clip
      * 1 by the track
-     * 2 by the timeline */
-    ASSERT_OBJECT_REFCOUNT (GES_TRACK_ELEMENT (tmp->data), "trackelement", 4);
+     * 1 by the timeline */
+    ASSERT_OBJECT_REFCOUNT (GES_TRACK_ELEMENT (tmp->data), "trackelement", 3);
     fail_unless (ges_track_element_get_track (tmp->data) == track2);
   }
   gst_object_ref (t2);
   /* There are 3 references held:
    * 1 by the container
    * 1 by the track
-   * 2 by the timeline
+   * 1 by the timeline
    * 1 added by ourselves above (gst_object_ref (t2)) */
-  ASSERT_OBJECT_REFCOUNT (t2, "t2", 5);
+  ASSERT_OBJECT_REFCOUNT (t2, "t2", 4);
 
   trackelements = GES_CONTAINER_CHILDREN (s3);
   fail_unless (trackelements != NULL);
@@ -652,17 +652,17 @@ GST_START_TEST (test_ges_timeline_multiple_tracks)
     /* There are 3 references held:
      * 1 by the clip
      * 1 by the track
-     * 2 by the timeline */
-    ASSERT_OBJECT_REFCOUNT (GES_TRACK_ELEMENT (tmp->data), "trackelement", 4);
+     * 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
-   * 2 by the timeline
+   * 1 by the timeline
    * 1 added by ourselves above (gst_object_ref (t3)) */
-  ASSERT_OBJECT_REFCOUNT (t3, "t3", 5);
+  ASSERT_OBJECT_REFCOUNT (t3, "t3", 4);
 
   gst_object_unref (t1);
   gst_object_unref (t2);
index 7bdf773..5068cba 100644 (file)
@@ -365,7 +365,7 @@ GST_START_TEST (test_split_object)
   /* 1 ref for the Clip, 1 ref for the Track and 2 ref for the timeline
    * (1 for the "all_element" hashtable, another for the sequence of TrackElement*/
   ASSERT_OBJECT_REFCOUNT (splittrackelement,
-      "1 ref for the Clip, 1 ref for the Track and 2 ref for the timeline", 4);
+      "1 ref for the Clip, 1 ref for the Track and 1 ref for the timeline", 3);
 
   check_destroyed (G_OBJECT (timeline), G_OBJECT (splitclip), clip,
       splittrackelement, NULL);
@@ -429,7 +429,7 @@ GST_START_TEST (test_clip_group_ungroup)
   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 "
-      "+ 2 for the timeline + 1 in tmp list", 5);
+      "+ 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
@@ -438,7 +438,7 @@ GST_START_TEST (test_clip_group_ungroup)
   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 "
-      "+ 2 for the timeline + 1 in tmp list", 5);
+      "+ 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
@@ -489,7 +489,7 @@ GST_START_TEST (test_clip_group_ungroup)
   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 "
-      "+ 2 for the timeline + 1 in tmp list", 5);
+      "+ 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)) ==
index 84944b0..373eacf 100644 (file)
@@ -165,147 +165,69 @@ GST_START_TEST (test_move_group)
   CHECK_OBJECT_PROPS (clip2, 60, 0, 50);
   CHECK_OBJECT_PROPS (group, 10, 0, 100);
   ASSERT_OBJECT_REFCOUNT (group, "2 ref for the timeline", 2);
-
-  /*
-   *        0           20---Group1---------------110
-   *                    |                          |
-   * layer:             |                          |
-   *                    |                          |
-   *                    |--------------------------|
-   *                    5---------    0------------|
-   * layer1:            | clip1   |    |  clip2    |
-   *                    20--------30   60----------|
-   *                    |--------------------------|
-   */
-  ges_timeline_element_trim (GES_TIMELINE_ELEMENT (group), 20);
-  CHECK_OBJECT_PROPS (clip, 15, 5, 0);
+  fail_if (ges_timeline_element_trim (GES_TIMELINE_ELEMENT (group), 20));
+  CHECK_OBJECT_PROPS (clip, 10, 0, 5);
   CHECK_OBJECT_PROPS (clip1, 20, 5, 10);
   CHECK_OBJECT_PROPS (clip2, 60, 0, 50);
-  CHECK_OBJECT_PROPS (group, 20, 0, 90);
+  CHECK_OBJECT_PROPS (group, 10, 0, 100);
+  ASSERT_OBJECT_REFCOUNT (group, "2 ref for the timeline", 2);
 
-  /*
-   *        0             25---Group1---------------110
-   *                       |                          |
-   * layer:                |                          |
-   *                       |                          |
-   *                       |--------------------------|
-   *                       10------      0------------|
-   * layer1:               | clip1 |      |  clip2    |
-   *                      25------30      60----------|
-   *                       |--------------------------|
-   */
-  ges_timeline_element_trim (GES_TIMELINE_ELEMENT (group), 25);
-  CHECK_OBJECT_PROPS (clip, 15, 5, 0);
-  CHECK_OBJECT_PROPS (clip1, 25, 10, 5);
+  fail_if (ges_timeline_element_trim (GES_TIMELINE_ELEMENT (group), 25));
+  CHECK_OBJECT_PROPS (clip, 10, 0, 5);
+  CHECK_OBJECT_PROPS (clip1, 20, 5, 10);
   CHECK_OBJECT_PROPS (clip2, 60, 0, 50);
-  CHECK_OBJECT_PROPS (group, 25, 0, 85);
+  CHECK_OBJECT_PROPS (group, 10, 0, 100);
   ASSERT_OBJECT_REFCOUNT (group, "2 ref for the timeline", 2);
 
-  /*
-   *        0  10------------Group1------------------110
-   *            |------                               |
-   * layer:     |clip  |                              |
-   *            |-----15                              |
-   *            |-------------------------------------|
-   *            |          10------      0------------|
-   * layer1:    |          | clip1 |      |  clip2    |
-   *            |         25------30      60----------|
-   *            |          |--------------------------|
-   *            |-------------------------------------|
-   */
+  /* Same thing in the end... */
   ges_timeline_element_trim (GES_TIMELINE_ELEMENT (group), 10);
   CHECK_OBJECT_PROPS (clip, 10, 0, 5);
-  CHECK_OBJECT_PROPS (clip1, 25, 10, 5);
+  CHECK_OBJECT_PROPS (clip1, 20, 5, 10);
   CHECK_OBJECT_PROPS (clip2, 60, 0, 50);
   CHECK_OBJECT_PROPS (group, 10, 0, 100);
   ASSERT_OBJECT_REFCOUNT (group, "2 ref for the timeline", 2);
 
   /*
-   *        0             25---Group1---------------110
-   *                       |                          |
-   * layer:         15     |                          |
-   *                 |clip |                          |
-   *                 -     |--------------------------|
-   *                       10------      0------------|
-   * layer1:               | clip1 |      |  clip2    |
-   *                      25------30      60----------|
-   *                       |--------------------------|
+   *        0  12------------Group1---------------110
+   *            2------                            |
+   * layer:     |clip  |                           |
+   *            |-----15                           |
+   *            |----------------------------------|
+   *            |        7---------     2----------|
+   * layer1:    |        | clip1   |    |  clip2   |
+   *            |       22--------30   62----------|
+   *            |----------------------------------|
    */
-  ges_timeline_element_trim (GES_TIMELINE_ELEMENT (group), 25);
-  CHECK_OBJECT_PROPS (clip, 15, 5, 0);
-  CHECK_OBJECT_PROPS (clip1, 25, 10, 5);
-  CHECK_OBJECT_PROPS (clip2, 60, 0, 50);
-  CHECK_OBJECT_PROPS (group, 25, 0, 85);
+  ges_timeline_element_trim (GES_TIMELINE_ELEMENT (group), 12);
+  CHECK_OBJECT_PROPS (clip, 12, 2, 3);
+  CHECK_OBJECT_PROPS (clip1, 22, 7, 8);
+  CHECK_OBJECT_PROPS (clip2, 62, 2, 48);
+  CHECK_OBJECT_PROPS (group, 12, 0, 98);
   ASSERT_OBJECT_REFCOUNT (group, "2 ref for the timeline", 2);
 
-  /*
-   *        0             25---Group1--30
-   *                       |            |
-   * layer:          15    |            |
-   *                 |clip |            |
-   *                  -    |------------
-   *                       15-----------|   60
-   * layer1:               | clip1      |   |clip2
-   *                      25------------|   -
-   *                       |------------|
-   */
+  /* Setting the duration would lead to overlaps */
   ges_timeline_element_set_duration (GES_TIMELINE_ELEMENT (group), 10);
-  CHECK_OBJECT_PROPS (clip, 15, 5, 0);
-  CHECK_OBJECT_PROPS (clip1, 25, 10, 5);
-  CHECK_OBJECT_PROPS (clip2, 60, 0, 0);
-  CHECK_OBJECT_PROPS (group, 25, 0, 5);
-
-  /*
-   *        0             25---Group1---------------125
-   *                       |                          |
-   * layer:        15      |                          |
-   *                |clip  |                          |
-   *                -      |--------------------------|
-   *                       10-------------------------|
-   * layer1:               |  clip1       |  clip2    |
-   *                      25--------------60----------|
-   *                       |--------------------------|
-   */
+  CHECK_OBJECT_PROPS (clip, 12, 2, 3);
+  CHECK_OBJECT_PROPS (clip1, 22, 7, 8);
+  CHECK_OBJECT_PROPS (clip2, 62, 2, 48);
+  CHECK_OBJECT_PROPS (group, 12, 0, 98);
   ges_timeline_element_set_duration (GES_TIMELINE_ELEMENT (group), 100);
-  CHECK_OBJECT_PROPS (clip, 15, 5, 0);
-  CHECK_OBJECT_PROPS (clip1, 25, 10, 100);
-  CHECK_OBJECT_PROPS (clip2, 60, 0, 65);
-  CHECK_OBJECT_PROPS (group, 25, 0, 100);
-  ASSERT_OBJECT_REFCOUNT (group, "2 ref for the timeline", 2);
+  CHECK_OBJECT_PROPS (clip, 12, 2, 3);
+  CHECK_OBJECT_PROPS (clip1, 22, 7, 8);
+  CHECK_OBJECT_PROPS (clip2, 62, 2, 50);
+  CHECK_OBJECT_PROPS (group, 12, 0, 100);
 
-  /*
-   *        0           20---Group1---------------120
-   *                    |                          |
-   * layer:        15   |                          |
-   *               |clip|                          |
-   *               -    |--------------------------|
-   *                    10-------------------------|
-   * layer1:            |  clip1       |  clip2    |
-   *                    20-------------55----------|
-   *                    |--------------------------|
-   */
   ges_timeline_element_set_start (GES_TIMELINE_ELEMENT (group), 20);
-  CHECK_OBJECT_PROPS (clip, 15, 5, 0);
-  CHECK_OBJECT_PROPS (clip1, 20, 10, 100);
-  CHECK_OBJECT_PROPS (clip2, 55, 0, 65);
+  CHECK_OBJECT_PROPS (clip, 20, 2, 3);
+  CHECK_OBJECT_PROPS (clip1, 30, 7, 8);
+  CHECK_OBJECT_PROPS (clip2, 70, 2, 50);
   CHECK_OBJECT_PROPS (group, 20, 0, 100);
 
-  /*
-   *        0      10---Group1---------------120
-   *               |-----15                   |
-   * layer:        | clip|                    |
-   *               |------                    |
-   *               |--------------------------|
-   *               5--------------------------|
-   * layer1:       |  clip1       |  clip2    |
-   *               10-------------55----------|
-   *               |--------------------------|
-   */
-  ges_timeline_element_trim (GES_TIMELINE_ELEMENT (group), 10);
-  CHECK_OBJECT_PROPS (clip, 10, 0, 5);
-  CHECK_OBJECT_PROPS (clip1, 10, 0, 110);
-  CHECK_OBJECT_PROPS (clip2, 55, 0, 65);
-  CHECK_OBJECT_PROPS (group, 10, 0, 110);
+  fail_if (ges_timeline_element_trim (GES_TIMELINE_ELEMENT (group), 10));
+  CHECK_OBJECT_PROPS (clip, 20, 2, 3);
+  CHECK_OBJECT_PROPS (clip1, 30, 7, 8);
+  CHECK_OBJECT_PROPS (clip2, 70, 2, 50);
+  CHECK_OBJECT_PROPS (group, 20, 0, 100);
 
   ASSERT_OBJECT_REFCOUNT (group, "2 ref for the timeline", 2);
   check_destroyed (G_OBJECT (timeline), G_OBJECT (group), NULL);
index cc61de6..8b36fac 100644 (file)
@@ -308,7 +308,7 @@ GST_START_TEST (test_single_layer_automatic_transition)
 {
   GESAsset *asset;
   GESTimeline *timeline;
-  GList *objects, *current;
+  GList *objects;
   GESClip *transition;
   GESLayer *layer;
   GESTimelineElement *src, *src1, *src2;
@@ -379,24 +379,19 @@ GST_START_TEST (test_single_layer_automatic_transition)
   ges_timeline_element_set_start (src, 250);
 
   /*
-   *        500_____transition____1250
-   *    250___________src_________1250
+   *           600_____transition______1500
+   *           600___________src_________1600
    *        500___________src1_________1500
    */
   GST_DEBUG ("Checking src timing values");
-  assert_equals_uint64 (_START (src), 250);
-  assert_equals_uint64 (_DURATION (src), 1250 - 250);
-  assert_equals_uint64 (_START (src1), 500);
-  assert_equals_uint64 (_DURATION (src1), 1500 - 500);
+  CHECK_OBJECT_PROPS (src, 250, 0, 1000);
+  CHECK_OBJECT_PROPS (src1, 500, 0, 1000);
 
   objects = ges_layer_get_clips (layer);
   assert_equals_int (g_list_length (objects), 4);
-  assert_is_type (objects->data, GES_TYPE_TEST_CLIP);
-
   transition = objects->next->data;
   assert_is_type (transition, GES_TYPE_TRANSITION_CLIP);
-  assert_equals_uint64 (_START (transition), 500);
-  assert_equals_uint64 (_DURATION (transition), 750);
+  CHECK_OBJECT_PROPS (transition, 500, 0, 750);
 
   transition = objects->next->next->data;
   assert_is_type (transition, GES_TYPE_TRANSITION_CLIP);
@@ -404,295 +399,65 @@ GST_START_TEST (test_single_layer_automatic_transition)
   assert_equals_uint64 (_DURATION (transition), 750);
   g_list_free_full (objects, gst_object_unref);
 
-  GST_DEBUG ("Moving second source to 250, the transitions should be removed");
-  ges_timeline_element_set_start (src1, 250);
-
-  /* The transition should be removed
-   *    250___________src_________1250
-   *    250___________src1________1250
-   */
-  GST_DEBUG ("Checking src timing values");
-  assert_equals_uint64 (_START (src), 250);
-  assert_equals_uint64 (_DURATION (src), 1250 - 250);
-  assert_equals_uint64 (_START (src1), 250);
-  assert_equals_uint64 (_DURATION (src1), 1250 - 250);
-
-  objects = ges_layer_get_clips (layer);
-  assert_equals_int (g_list_length (objects), 2);
-  g_list_free_full (objects, gst_object_unref);
-
-  GST_DEBUG ("Trimming second source to 500 no transition should be created "
-      "as they have the same end");
-  ges_container_edit (GES_CONTAINER (src1), NULL, -1,
-      GES_EDIT_MODE_TRIM, GES_EDGE_START, 500);
-
-  /*    250___________src_________1250
-   *          500______src1_______1250
-   */
-  GST_DEBUG ("Checking src timing values");
-  assert_equals_uint64 (_START (src), 250);
-  assert_equals_uint64 (_DURATION (src), 1250 - 250);
-  assert_equals_uint64 (_START (src1), 500);
-  assert_equals_uint64 (_DURATION (src1), 1250 - 500);
-
-  objects = ges_layer_get_clips (layer);
-  assert_equals_int (g_list_length (objects), 2);
-  g_list_free_full (objects, gst_object_unref);
-
-  GST_DEBUG ("Trimming second source to 500, no transition should be created");
-  ges_timeline_element_trim (src, 500);
+  fail_if (ges_timeline_element_set_start (src1, 250));
+
+  fail_if (ges_container_edit (GES_CONTAINER (src), NULL, -1,
+          GES_EDIT_MODE_TRIM, GES_EDGE_START, 500));
+  CHECK_OBJECT_PROPS (src, 250, 0, 1000);
+  CHECK_OBJECT_PROPS (src1, 500, 0, 1000);
+  fail_if (ges_timeline_element_trim (src, 500));
+  CHECK_OBJECT_PROPS (src, 250, 0, 1000);
+  CHECK_OBJECT_PROPS (src1, 500, 0, 1000);
+  fail_if (ges_timeline_element_trim (src, 750));
+  CHECK_OBJECT_PROPS (src, 250, 0, 1000);
+  CHECK_OBJECT_PROPS (src1, 500, 0, 1000);
+  fail_if (ges_timeline_element_set_start (src, 500));
+  CHECK_OBJECT_PROPS (src, 250, 0, 1000);
+  CHECK_OBJECT_PROPS (src1, 500, 0, 1000);
 
-  /*        500___________src_________1250
-   *        500___________src1________1250
-   */
-  GST_DEBUG ("Checking src timing values");
-  assert_equals_uint64 (_START (src), 500);
-  assert_equals_uint64 (_DURATION (src), 1250 - 500);
-  assert_equals_uint64 (_START (src1), 500);
-  assert_equals_uint64 (_DURATION (src1), 1250 - 500);
-
-  GST_DEBUG ("Trimming first source to 750, no transition should be created");
-  ges_timeline_element_trim (src, 750);
-
-  /*              750_______src_______1250
-   *        500___________src1________1250
-   */
-  GST_DEBUG ("Checking src timing values");
-  assert_equals_uint64 (_START (src), 750);
-  assert_equals_uint64 (_DURATION (src), 1250 - 750);
-  assert_equals_uint64 (_START (src1), 500);
-  assert_equals_uint64 (_DURATION (src1), 1250 - 500);
-
-  objects = ges_layer_get_clips (layer);
-  assert_equals_int (g_list_length (objects), 2);
-  g_list_free_full (objects, gst_object_unref);
-
-  objects = ges_layer_get_clips (layer);
-  assert_equals_int (g_list_length (objects), 2);
-  g_list_free_full (objects, gst_object_unref);
-
-  GST_DEBUG ("Moving first source to 500, no transition should be created");
-  ges_timeline_element_set_start (src, 500);
-
-  /*        500________src______1000
-   *        500___________src1________1250
+  /*
+   *           600_____transition______1500
+   *           600___________src_________1600
+   *        500___________src1_________1500
    */
-  GST_DEBUG ("Checking src timing values");
-  assert_equals_uint64 (_START (src), 500);
-  assert_equals_uint64 (_DURATION (src), 1000 - 500);
-  assert_equals_uint64 (_START (src1), 500);
-  assert_equals_uint64 (_DURATION (src1), 1250 - 500);
-
-  objects = ges_layer_get_clips (layer);
-  assert_equals_int (g_list_length (objects), 2);
-  g_list_free_full (objects, gst_object_unref);
-
-  objects = ges_layer_get_clips (layer);
-  assert_equals_int (g_list_length (objects), 2);
-  g_list_free_full (objects, gst_object_unref);
-
-  GST_DEBUG ("Moving first source to 600, no transition should be created");
   ges_timeline_element_set_start (src, 600);
-  /*             600____src___1100
-   *        500___________src1________1250
-   */
-  GST_DEBUG ("Checking src timing values");
-  assert_equals_uint64 (_START (src), 600);
-  assert_equals_uint64 (_DURATION (src), 1100 - 600);
-  assert_equals_uint64 (_START (src1), 500);
-  assert_equals_uint64 (_DURATION (src1), 1250 - 500);
-
+  CHECK_OBJECT_PROPS (src, 600, 0, 1000);
+  CHECK_OBJECT_PROPS (src1, 500, 0, 1000);
   objects = ges_layer_get_clips (layer);
-  assert_equals_int (g_list_length (objects), 2);
-  g_list_free_full (objects, gst_object_unref);
-
-  objects = ges_layer_get_clips (layer);
-  assert_equals_int (g_list_length (objects), 2);
+  assert_equals_int (g_list_length (objects), 4);
+  transition = objects->next->data;
+  assert_is_type (transition, GES_TYPE_TRANSITION_CLIP);
+  CHECK_OBJECT_PROPS (transition, 600, 0, 900);
   g_list_free_full (objects, gst_object_unref);
 
   GST_DEBUG ("Adding asset to first layer");
   GST_DEBUG ("Adding clip from 1250 -- 1000 to first layer");
-  src2 =
-      GES_TIMELINE_ELEMENT (ges_layer_add_asset (layer, asset, 1250, 0,
+  fail_if (ges_layer_add_asset (layer, asset, 1250, 0,
           1000, GES_TRACK_TYPE_UNKNOWN));
-  assert_is_type (src2, GES_TYPE_TEST_CLIP);
 
-  /*             600____src___1100
-   *        500___________src1________1250
-   *                                  1250___________src2________2250
+  /*
+   *                                    1500___________src2________2000
+   *                                    1500_trans_1600
+   *           600______________src________________1600
+   *           600_____transition______1500
+   *        500___________src1_________1500
    */
-  assert_equals_uint64 (_START (src), 600);
-  assert_equals_uint64 (_DURATION (src), 1100 - 600);
-  assert_equals_uint64 (_START (src1), 500);
-  assert_equals_uint64 (_DURATION (src1), 1250 - 500);
-  assert_equals_uint64 (_START (src2), 1250);
-  assert_equals_uint64 (_DURATION (src2), 1000);
+  src2 = GES_TIMELINE_ELEMENT (ges_layer_add_asset (layer, asset, 1500, 0,
+          500, GES_TRACK_TYPE_UNKNOWN));
+  assert_is_type (src2, GES_TYPE_TEST_CLIP);
 
+  CHECK_OBJECT_PROPS (src, 600, 0, 1000);
+  CHECK_OBJECT_PROPS (src1, 500, 0, 1000);
+  CHECK_OBJECT_PROPS (src2, 1500, 0, 500);
   objects = ges_layer_get_clips (layer);
-  assert_equals_int (g_list_length (objects), 3);
-  g_list_free_full (objects, gst_object_unref);
-
-  GST_DEBUG
-      ("Changig first source duration to 800 2 transitions should be created");
-  ges_timeline_element_set_duration (src, 800);
-  ges_timeline_commit (timeline);
-  /*             600__________________src_____________1400
-   *        500___________src1________1250
-   *                                  1250___________src2________2250
-   *             600_____trans1_______1250
-   *                                  1250___trans2___1400
-   */
-  GST_DEBUG ("Checking src timing values");
-  assert_equals_uint64 (_START (src), 600);
-  assert_equals_uint64 (_DURATION (src), 1400 - 600);
-  assert_equals_uint64 (_START (src1), 500);
-  assert_equals_uint64 (_DURATION (src1), 1250 - 500);
-
-  current = objects = ges_layer_get_clips (layer);
+  transition = objects->next->next->data;
   assert_equals_int (g_list_length (objects), 7);
-  assert_is_type (objects->data, GES_TYPE_TEST_CLIP);
-  fail_unless (objects->data == src1);
-
-  current = current->next;
-  transition = current->data;
-  assert_is_type (transition, GES_TYPE_TRANSITION_CLIP);
-  assert_equals_uint64 (_START (transition), 600);
-  assert_equals_uint64 (_DURATION (transition), 1250 - 600);
-  ASSERT_OBJECT_REFCOUNT (transition, "layer + timeline + ourself", 3);
-
-  current = current->next;
-  transition = current->data;
-  assert_is_type (transition, GES_TYPE_TRANSITION_CLIP);
-  assert_equals_uint64 (_START (transition), 600);
-  assert_equals_uint64 (_DURATION (transition), 1250 - 600);
-  ASSERT_OBJECT_REFCOUNT (transition, "layer + timeline + ourself", 3);
-
-  current = current->next;
-  fail_unless (current->data == src);
-
-  current = current->next;
-  transition = current->data;
-  assert_is_type (transition, GES_TYPE_TRANSITION_CLIP);
-  assert_equals_uint64 (_START (transition), 1250);
-  assert_equals_uint64 (_DURATION (transition), 1400 - 1250);
-  ASSERT_OBJECT_REFCOUNT (transition, "layer + timeline + ourself", 3);
-
-  current = current->next;
-  transition = current->data;
   assert_is_type (transition, GES_TYPE_TRANSITION_CLIP);
-  assert_equals_uint64 (_START (transition), 1250);
-  assert_equals_uint64 (_DURATION (transition), 1400 - 1250);
-  ASSERT_OBJECT_REFCOUNT (transition, "layer + timeline + ourself", 3);
-
-  current = current->next;
-  fail_unless (current->data == src2);
-  g_list_free_full (objects, gst_object_unref);
-
-  GST_DEBUG ("Back to previous state");
-  /*  Make sure to keep 1 ref so we can check_destroyed afterward */
-  gst_object_ref (transition);
-  ges_timeline_element_set_duration (src, 1100 - 600);
-  /*             600____src___1100
-   *        500___________src1________1250
-   *                                  1250___________src2________2250
-   */
-  assert_equals_uint64 (_START (src), 600);
-  assert_equals_uint64 (_DURATION (src), 1100 - 600);
-  assert_equals_uint64 (_START (src1), 500);
-  assert_equals_uint64 (_DURATION (src1), 1250 - 500);
-  assert_equals_uint64 (_START (src2), 1250);
-  assert_equals_uint64 (_DURATION (src2), 1000);
-
-  /* We check that the transition as actually been freed */
-  check_destroyed (G_OBJECT (transition), NULL, NULL);
-
-  objects = ges_layer_get_clips (layer);
-  assert_equals_int (g_list_length (objects), 3);
-  g_list_free_full (objects, gst_object_unref);
-
-  GST_DEBUG
-      ("Set third clip start to 1100, 1 new transition should be created");
-  ges_timeline_element_set_start (src2, 1100);
-  ges_timeline_commit (timeline);
-  /*             600____src___1100
-   *        500___________src1________1250
-   *                          1100___________src2________2100
-   *                          ^__trans___^
-   */
-  assert_equals_uint64 (_START (src), 600);
-  assert_equals_uint64 (_DURATION (src), 1100 - 600);
-  assert_equals_uint64 (_START (src1), 500);
-  assert_equals_uint64 (_DURATION (src1), 1250 - 500);
-  assert_equals_uint64 (_START (src2), 1100);
-  assert_equals_uint64 (_DURATION (src2), 1000);
-
-  current = objects = ges_layer_get_clips (layer);
-  assert_equals_int (g_list_length (objects), 5);
-  assert_is_type (objects->data, GES_TYPE_TEST_CLIP);
-  fail_unless (current->data == src1);
-
-  current = current->next;
-  fail_unless (current->data == src);
-
-  current = current->next;
-  transition = current->data;
-  assert_is_type (transition, GES_TYPE_TRANSITION_CLIP);
-  assert_equals_uint64 (_START (transition), 1100);
-  assert_equals_uint64 (_DURATION (transition), 1250 - 1100);
-
-  current = current->next;
-  transition = current->data;
-  assert_is_type (transition, GES_TYPE_TRANSITION_CLIP);
-  assert_equals_uint64 (_START (transition), 1100);
-  assert_equals_uint64 (_DURATION (transition), 1250 - 1100);
-
-  current = current->next;
-  fail_unless (current->data == src2);
-  g_list_free_full (objects, gst_object_unref);
-
-  GST_DEBUG ("Check that we can not create 2 transitions at the same place");
-  fail_if (ges_container_edit (GES_CONTAINER (src2), NULL, -1,
-          GES_EDIT_MODE_NORMAL, GES_EDGE_START, 1000));
-
-  /*
-   *        500___________src1________1250
-   *                       1000___________src2________2000
-   *                       ^____trans____^
-   */
-  ges_layer_remove_clip (layer, GES_CLIP (src));
-  fail_unless (ges_container_edit (GES_CONTAINER (src2), NULL, -1,
-          GES_EDIT_MODE_NORMAL, GES_EDGE_START, 1000));
-  assert_equals_uint64 (_START (src1), 500);
-  assert_equals_uint64 (_DURATION (src1), 1250 - 500);
-  assert_equals_uint64 (_START (src2), 1000);
-  assert_equals_uint64 (_DURATION (src2), 1000);
-
-  current = objects = ges_layer_get_clips (layer);
-  current = objects;
-  assert_equals_int (g_list_length (objects), 4);
-  assert_is_type (objects->data, GES_TYPE_TEST_CLIP);
-  transition = objects->next->data;
+  CHECK_OBJECT_PROPS (transition, 600, 0, 900);
+  transition = objects->next->next->next->next->data;
   assert_is_type (transition, GES_TYPE_TRANSITION_CLIP);
-  fail_unless (current->data == src1);
-  g_list_free_full (objects, gst_object_unref);
-
-  /*
-   *        500___________src1________1250
-   *                       ^____trans____^
-   *                       1100___________src2________2000
-   */
-  ges_container_edit (GES_CONTAINER (transition),
-      NULL, -1, GES_EDIT_MODE_TRIM, GES_EDGE_START, 1100);
-  assert_equals_uint64 (_START (src1), 500);
-  assert_equals_uint64 (_DURATION (src1), 1250 - 500);
-  assert_equals_uint64 (_START (src2), 1100);
-  assert_equals_uint64 (_DURATION (src2), 2000 - 1100);
+  CHECK_OBJECT_PROPS (transition, 1500, 0, 100);
 
-  current = objects = ges_layer_get_clips (layer);
-  current = objects;
-  assert_equals_int (g_list_length (objects), 4);
-  assert_is_type (objects->data, GES_TYPE_TEST_CLIP);
-  fail_unless (current->data == src1);
   g_list_free_full (objects, gst_object_unref);
 
   gst_object_unref (timeline);
@@ -1006,7 +771,7 @@ GST_START_TEST (test_multi_layer_automatic_transition)
 
   GST_DEBUG
       ("Moving src to second layer, should remove first transition on first layer");
-  ges_clip_move_to_layer (GES_CLIP (src), layer1);
+  fail_if (ges_clip_move_to_layer (GES_CLIP (src), layer1));
 
   /*        500___________src1_________1500
    *                         1000___________src3_________2000   Layer
@@ -1027,31 +792,8 @@ GST_START_TEST (test_multi_layer_automatic_transition)
 
   GST_DEBUG ("Checking transitions on first layer");
   current = objects = ges_layer_get_clips (layer);
-  assert_equals_int (g_list_length (objects), 4);
-  fail_unless (current->data == src1);
-
-  current = current->next;
-  transition = current->data;
-  assert_is_type (transition, GES_TYPE_TRANSITION_CLIP);
-  assert_equals_uint64 (_START (transition), 1000);
-  assert_equals_uint64 (_DURATION (transition), 500);
-
-  current = current->next;
-  transition = current->data;
-  assert_is_type (transition, GES_TYPE_TRANSITION_CLIP);
-  assert_equals_uint64 (_START (transition), 1000);
-  assert_equals_uint64 (_DURATION (transition), 500);
-
-  current = current->next;
-  fail_unless (current->data == src3);
-  g_list_free_full (objects, gst_object_unref);
-  ASSERT_OBJECT_REFCOUNT (transition, "layer + timeline", 2);
+  assert_equals_int (g_list_length (objects), 7);
 
-  GST_DEBUG ("Checking second layer");
-  current = objects = ges_layer_get_clips (layer1);
-  assert_equals_int (g_list_length (objects), 2);
-  assert_is_type (current->data, GES_TYPE_TEST_CLIP);
-  assert_is_type (current->next->data, GES_TYPE_TEST_CLIP);
   g_list_free_full (objects, gst_object_unref);
   ASSERT_OBJECT_REFCOUNT (transition, "layer + timeline", 2);
 
index 7b8199f..f7382c0 100644 (file)
@@ -83,30 +83,37 @@ G_STMT_START {                                          \
 #define _DURATION(obj) GES_TIMELINE_ELEMENT_DURATION (obj)
 #define _INPOINT(obj) GES_TIMELINE_ELEMENT_INPOINT (obj)
 #define _PRIORITY(obj) GES_TIMELINE_ELEMENT_PRIORITY (obj)
+#ifndef _END
+#define _END(obj) (_START(obj) + _DURATION(obj))
+#endif
 
 #define CHECK_OBJECT_PROPS(obj, start, inpoint, duration) {\
-  assert_equals_uint64 (_START (obj), start);\
-  assert_equals_uint64 (_INPOINT (obj), inpoint);\
-  assert_equals_uint64 (_DURATION (obj), duration);\
+  fail_unless (_START (obj) == start, "%s start is %" GST_TIME_FORMAT " != %" GST_TIME_FORMAT, GES_TIMELINE_ELEMENT_NAME(obj), GST_TIME_ARGS (_START(obj)), GST_TIME_ARGS (start));\
+  fail_unless (_INPOINT (obj) == inpoint, "%s inpoint is %" GST_TIME_FORMAT " != %" GST_TIME_FORMAT, GES_TIMELINE_ELEMENT_NAME(obj), GST_TIME_ARGS (_INPOINT(obj)), GST_TIME_ARGS (inpoint));\
+  fail_unless (_DURATION (obj) == duration, "%s duration is %" GST_TIME_FORMAT " != %" GST_TIME_FORMAT, GES_TIMELINE_ELEMENT_NAME(obj), GST_TIME_ARGS (_DURATION(obj)), GST_TIME_ARGS (duration));\
 }
 
-#define check_layer(clip, layer_prio) {                                        \
-  GESLayer *tmplayer = ges_clip_get_layer ((clip));                            \
-  assert_equals_int (ges_layer_get_priority (tmplayer),  (layer_prio));        \
-  gst_object_unref (tmplayer);                                                 \
+#define check_layer(clip, layer_prio) {                                      \
+  fail_unless (GES_TIMELINE_ELEMENT_LAYER_PRIORITY (clip) ==  (layer_prio),  \
+    "%s in layer %d instead of %d", GES_TIMELINE_ELEMENT_NAME (clip),        \
+    GES_TIMELINE_ELEMENT_LAYER_PRIORITY (clip), layer_prio);                 \
 }
 
 #define GES_TIMELINE_ELEMENT_FORMAT \
     "s<%p>" \
     " [ %" GST_TIME_FORMAT \
     " (%" GST_TIME_FORMAT \
-    ") - %" GST_TIME_FORMAT "]"
+    ") - %" GST_TIME_FORMAT "(%" GST_TIME_FORMAT") layer: %" G_GINT32_FORMAT "] "
 
 #define GES_TIMELINE_ELEMENT_ARGS(element) \
     GES_TIMELINE_ELEMENT_NAME(element), element, \
     GST_TIME_ARGS(GES_TIMELINE_ELEMENT_START(element)), \
     GST_TIME_ARGS(GES_TIMELINE_ELEMENT_INPOINT(element)), \
-    GST_TIME_ARGS(GES_TIMELINE_ELEMENT_DURATION(element))
+    GST_TIME_ARGS(GES_TIMELINE_ELEMENT_DURATION(element)), \
+    GST_TIME_ARGS(GES_TIMELINE_ELEMENT_MAX_DURATION(element)), \
+    GES_TIMELINE_ELEMENT_LAYER_PRIORITY(element)
+#define GES_ARGS GES_TIMELINE_ELEMENT_ARGS
+#define GES_FORMAT GES_TIMELINE_ELEMENT_FORMAT
 
 void print_timeline(GESTimeline *timeline);
 
index 144db17..8625780 100644 (file)
 #define DEEP_CHECK(element, start, inpoint, duration)                          \
 {                                                                              \
   GList *track_elements, *tmp;                                                 \
-                                                                               \
-  assert_equals_uint64 (_START (element), start);                              \
-  assert_equals_uint64 (_INPOINT (element), inpoint);                          \
-  assert_equals_uint64 (_DURATION (element), duration);                        \
+  CHECK_OBJECT_PROPS (element, start, inpoint, duration)                      \
                                                                                \
   track_elements = GES_CONTAINER_CHILDREN (element);                           \
   for (tmp = track_elements; tmp; tmp = tmp->next) {                           \
-    assert_equals_uint64 (_START (tmp->data), start);                          \
-    assert_equals_uint64 (_INPOINT (tmp->data), inpoint);                      \
-    assert_equals_uint64 (_DURATION (tmp->data), duration);                    \
+    CHECK_OBJECT_PROPS (tmp->data, start, inpoint, duration)                      \
   }                                                                            \
 }
 
+#define CHECK_CLIP(element, start, inpoint, duration, layer_prio) \
+{ \
+  DEEP_CHECK(element, start, inpoint, duration);\
+  check_layer (element, layer_prio); \
+}\
+
+
 GST_START_TEST (test_basic_timeline_edition)
 {
   GESAsset *asset;
@@ -288,7 +290,7 @@ GST_START_TEST (test_snapping)
   fail_unless (ges_track_element_get_track (trackelement) == track);
   assert_equals_uint64 (_DURATION (trackelement), 37);
 
-  ASSERT_OBJECT_REFCOUNT (trackelement, "track + timeline + clip", 4);
+  ASSERT_OBJECT_REFCOUNT (trackelement, "track + timeline + clip", 3);
   ASSERT_OBJECT_REFCOUNT (clip, "layer + timeline", 2);
 
   fail_unless (ges_layer_add_clip (layer, GES_CLIP (clip1)));
@@ -299,7 +301,7 @@ GST_START_TEST (test_snapping)
   assert_equals_uint64 (_DURATION (trackelement1), 15);
 
   /* Same ref logic */
-  ASSERT_OBJECT_REFCOUNT (trackelement1, "First trackelement", 4);
+  ASSERT_OBJECT_REFCOUNT (trackelement1, "First trackelement", 3);
   ASSERT_OBJECT_REFCOUNT (clip1, "First clip", 2);
 
   fail_unless (ges_layer_add_clip (layer, GES_CLIP (clip2)));
@@ -310,7 +312,7 @@ GST_START_TEST (test_snapping)
   assert_equals_uint64 (_DURATION (trackelement2), 60);
 
   /* Same ref logic */
-  ASSERT_OBJECT_REFCOUNT (trackelement2, "First trackelement", 4);
+  ASSERT_OBJECT_REFCOUNT (trackelement2, "First trackelement", 3);
   ASSERT_OBJECT_REFCOUNT (clip2, "First clip", 2);
 
   /* Snaping to edge, so no move */
@@ -322,8 +324,7 @@ GST_START_TEST (test_snapping)
   CHECK_OBJECT_PROPS (trackelement2, 62, 0, 60);
 
   /* Snaping to edge, so no move */
-  fail_if (ges_container_edit (clip1, NULL, -1, GES_EDIT_MODE_TRIM,
-          GES_EDGE_END, 27));
+  ges_container_edit (clip1, NULL, -1, GES_EDIT_MODE_TRIM, GES_EDGE_END, 27);
   CHECK_OBJECT_PROPS (trackelement, 25, 0, 37);
   CHECK_OBJECT_PROPS (trackelement1, 20, 0, 5);
   CHECK_OBJECT_PROPS (trackelement2, 62, 0, 60);
@@ -338,144 +339,78 @@ GST_START_TEST (test_snapping)
    */
   g_object_set (timeline, "snapping-distance", (guint64) 0, NULL);
   ges_timeline_element_set_duration (GES_TIMELINE_ELEMENT (clip1), 10);
-  CHECK_OBJECT_PROPS (trackelement, 25, 0, 37);
-  CHECK_OBJECT_PROPS (trackelement1, 20, 0, 10);
-  CHECK_OBJECT_PROPS (trackelement2, 62, 0, 60);
-
-  /**
-   * New timeline(the "layers" are just to help reading diagram, nothing else):
-   * ------------
-   *                    0----------
-   *                    |   clip    |
-   *                    25---------62
-   * inpoints   0----------------------- 10--------
-   *            |       clip1            ||  clip2   |
-   * time      20---------------------- 72 --------122
-   */
-  /* Rolling involves only neighbour that are currently snapping */
-  fail_unless (ges_timeline_element_roll_end (GES_TIMELINE_ELEMENT (clip1),
-          62));
-  fail_unless (ges_timeline_element_roll_end (GES_TIMELINE_ELEMENT (clip1),
+  DEEP_CHECK (clip, 25, 0, 37);
+  DEEP_CHECK (clip1, 20, 0, 10);
+  DEEP_CHECK (clip2, 62, 0, 60);
+
+  /* clip and clip1 would fully overlap ... forbiden */
+  fail_if (ges_timeline_element_roll_end (GES_TIMELINE_ELEMENT (clip1), 62));
+  DEEP_CHECK (clip, 25, 0, 37);
+  DEEP_CHECK (clip1, 20, 0, 10);
+  DEEP_CHECK (clip2, 62, 0, 60);
+  fail_if (ges_timeline_element_roll_end (GES_TIMELINE_ELEMENT (clip1),
           72) == TRUE);
-  CHECK_OBJECT_PROPS (trackelement, 25, 0, 37);
-  CHECK_OBJECT_PROPS (trackelement1, 20, 0, 52);
-  CHECK_OBJECT_PROPS (trackelement2, 72, 10, 50);
+  DEEP_CHECK (clip, 25, 0, 37);
+  DEEP_CHECK (clip1, 20, 0, 10);
+  DEEP_CHECK (clip2, 62, 0, 60);
 
   /**
-   *                    0----------
-   *                    |   clip    |
-   *                    25---------62
-   * inpoints           5--------------- 10--------
-   *                    |     clip1      ||  clip2   |
-   * time               25------------- 72 --------122
+   *                        30-------+0-------------+
+   * inpoints   0-----------5  clip  ||  clip2      |
+   *            |  clip1    |------- 62 -----------122
+   * time      20----------30
    */
   g_object_set (timeline, "snapping-distance", (guint64) 4, NULL);
-  fail_unless (ges_timeline_element_trim (GES_TIMELINE_ELEMENT (clip1),
+  fail_unless (ges_timeline_element_trim (GES_TIMELINE_ELEMENT (clip),
           28) == TRUE);
-  CHECK_OBJECT_PROPS (trackelement, 25, 0, 37);
-  CHECK_OBJECT_PROPS (trackelement1, 25, 5, 47);
-  CHECK_OBJECT_PROPS (trackelement2, 72, 10, 50);
+  DEEP_CHECK (clip, 30, 5, 32);
+  DEEP_CHECK (clip1, 20, 0, 10);
+  DEEP_CHECK (clip2, 62, 0, 60);
 
   /**
-   *                    0----------
-   *                    |   clip    |
-   *                    25---------62
-   * inpoints           5---------- 0---------
-   *                    |  clip1    ||  clip2   |
-   * time               25-------- 62 --------122
+   *                        30-------+0-------------+
+   * inpoints   0-----------5  clip  ||  clip2      |
+   *            |  clip1    |------- 62 -----------122
+   * time      20----------30
    */
+  fail_unless (ges_timeline_element_set_inpoint (GES_TIMELINE_ELEMENT (clip2),
+          5));
   fail_unless (ges_timeline_element_roll_start (GES_TIMELINE_ELEMENT (clip2),
-          59) == TRUE);
-  CHECK_OBJECT_PROPS (trackelement, 25, 0, 37);
-  CHECK_OBJECT_PROPS (trackelement1, 25, 5, 37);
-  CHECK_OBJECT_PROPS (trackelement2, 62, 0, 60);
+          60));
+  DEEP_CHECK (clip, 30, 5, 32);
+  DEEP_CHECK (clip1, 20, 0, 10);
+  DEEP_CHECK (clip2, 62, 5, 60);
 
   /**
-   *                  0----------
-   *                  |   clip  |
-   *                  25--------62
-   * inpoints           5-----------------------+
-   *                    |  clip1    ||  clip2   |
-   * time               30------------]--------122
-   *                                 67
+   *                        30-------+0-------------+
+   * inpoints   0-----------5  clip  ||  clip2      |
+   *            |  clip1    |------- 62 -----------122
+   * time      20----------30
    */
-  ges_container_edit (clip1, NULL, -1, GES_EDIT_MODE_NORMAL, GES_EDGE_NONE, 30);
-  CHECK_OBJECT_PROPS (trackelement, 25, 0, 37);
-  CHECK_OBJECT_PROPS (trackelement1, 30, 5, 37);
-  CHECK_OBJECT_PROPS (trackelement2, 62, 0, 60);
+  /* Moving clip1 to 26 would lead to snapping to 30, and clip1 and clip
+   * would fully overlap */
+  fail_if (ges_container_edit (clip1, NULL, -1, GES_EDIT_MODE_NORMAL,
+          GES_EDGE_NONE, 26) == TRUE);
+  DEEP_CHECK (clip, 30, 5, 32);
+  DEEP_CHECK (clip1, 20, 0, 10);
+  DEEP_CHECK (clip2, 62, 5, 60);
 
    /**
-   * inpoints           0----------5--------------
-   *                    |   clip    ||  clip1    |
-   * time               25----------62----------99
-   *                                            0-----------
-   *                                            |  clip2   |
-   *                                            98--------168
-   * Check that clip1 snaps with the end of clip */
-  fail_unless (ges_timeline_element_ripple (GES_TIMELINE_ELEMENT (clip1),
-          58) == TRUE);
-  CHECK_OBJECT_PROPS (trackelement, 25, 0, 37);
-  CHECK_OBJECT_PROPS (trackelement1, 62, 5, 37);
-  CHECK_OBJECT_PROPS (trackelement2, 94, 0, 60);
-
-  /**
-   * inpoints     0----------- 5------------   0-----------
-   *              |   clip    ||  clip1    |   |  clip2    |
-   * time         25----------62----------99  110--------170
-   */
-  ges_container_edit (clip2, NULL, -1, GES_EDIT_MODE_NORMAL, GES_EDGE_NONE,
-      110);
-  CHECK_OBJECT_PROPS (trackelement, 25, 0, 37);
-  CHECK_OBJECT_PROPS (trackelement1, 62, 5, 37);
-  CHECK_OBJECT_PROPS (trackelement2, 110, 0, 60);
-
-  /**
-   * inpoints     0----------5    5 --------- 0----------
-   *              |   clip    |    |  clip1    ||  clip2    |
-   * time         25---------62   73---------110--------170
-   */
+   *                        30-------+0-------------+
+   * inpoints               5  clip  ||  clip2      |-------------+
+   *                        +------- 62 -----------122  clip1     |
+   * time                                           +------------132 
+   * Check that clip1 snaps with the end of clip2 */
   fail_unless (ges_container_edit (clip1, NULL, -1, GES_EDIT_MODE_NORMAL,
-          GES_EDGE_NONE, 72) == TRUE);
-  CHECK_OBJECT_PROPS (trackelement, 25, 0, 37);
-  CHECK_OBJECT_PROPS (trackelement1, 73, 5, 37);
-  CHECK_OBJECT_PROPS (trackelement2, 110, 0, 60);
-
-  /**
-   * inpoints     0----------5----------     0----------
-   *              |   clip    ||  clip1    |   |  clip2    |
-   * time         25---------62-------- 99  110--------170
-   */
-  fail_unless (ges_container_edit (clip1, NULL, -1, GES_EDIT_MODE_NORMAL,
-          GES_EDGE_NONE, 58) == TRUE);
-  CHECK_OBJECT_PROPS (trackelement, 25, 0, 37);
-  CHECK_OBJECT_PROPS (trackelement1, 62, 5, 37);
-  CHECK_OBJECT_PROPS (trackelement2, 110, 0, 60);
-
-
-  /**
-   * inpoints     0----------5---------- 0----------
-   *              |   clip    ||  clip1   ||  clip2    |
-   * time         25---------62--------110--------170
-   */
-  g_object_set (clip1, "duration", (guint64) 46, NULL);
-  CHECK_OBJECT_PROPS (trackelement, 25, 0, 37);
-  CHECK_OBJECT_PROPS (trackelement1, 62, 5, 48);
-  CHECK_OBJECT_PROPS (trackelement2, 110, 0, 60);
-
-  /**
-   * inpoints     5----------- 0--------- 0----------
-   *              |   clip1    ||  clip2   ||  clip     |
-   * time         62---------110--------170--------207
-   */
-  ges_container_edit (clip, NULL, -1, GES_EDIT_MODE_NORMAL, GES_EDGE_NONE, 168);
-  CHECK_OBJECT_PROPS (trackelement, 170, 0, 37);
-  CHECK_OBJECT_PROPS (trackelement1, 62, 5, 48);
-  CHECK_OBJECT_PROPS (trackelement2, 110, 0, 60);
+          GES_EDGE_NONE, 125) == TRUE);
+  DEEP_CHECK (clip, 30, 5, 32);
+  DEEP_CHECK (clip1, 122, 0, 10);
+  DEEP_CHECK (clip2, 62, 5, 60);
 
   /* Check we didn't lose/screwed any references */
-  ASSERT_OBJECT_REFCOUNT (trackelement, "First trackelement", 4);
-  ASSERT_OBJECT_REFCOUNT (trackelement1, "Second trackelement", 4);
-  ASSERT_OBJECT_REFCOUNT (trackelement2, "Third trackelement", 4);
+  ASSERT_OBJECT_REFCOUNT (trackelement, "First trackelement", 3);
+  ASSERT_OBJECT_REFCOUNT (trackelement1, "Second trackelement", 3);
+  ASSERT_OBJECT_REFCOUNT (trackelement2, "Third trackelement", 3);
   ASSERT_OBJECT_REFCOUNT (clip, "First clip", 2);
   ASSERT_OBJECT_REFCOUNT (clip1, "Second clip", 2);
   ASSERT_OBJECT_REFCOUNT (clip2, "Third clip", 2);
@@ -782,6 +717,15 @@ GST_START_TEST (test_timeline_edition_mode)
   assert_equals_int (ges_layer_get_priority (layer), 2);
   gst_object_unref (layer);
 
+  /* Roll end clip back to 35 */
+  /* Can not move to the first layer as clip2 should move to a layer with priority < 0 */
+  fail_if (ges_container_edit (clip, NULL, 0, GES_EDIT_MODE_RIPPLE,
+          GES_EDGE_END, 52));
+  CHECK_OBJECT_PROPS (trackelement, 32, 5, 3);
+  CHECK_OBJECT_PROPS (trackelement1, 20, 0, 10);
+  CHECK_OBJECT_PROPS (trackelement2, 35, 0, 60);
+  assert_equals_int (GES_TIMELINE_ELEMENT_LAYER_PRIORITY (clip), 2);
+
   /* Ripple clip end to 52
    * New timeline:
    * ------------
@@ -795,8 +739,7 @@ GST_START_TEST (test_timeline_edition_mode)
    *                       32------52
    *
    */
-  /* Can not move to the first layer as clip2 should move to a layer with priority < 0 */
-  fail_unless (ges_container_edit (clip, NULL, 0, GES_EDIT_MODE_RIPPLE,
+  fail_unless (ges_container_edit (clip, NULL, -1, GES_EDIT_MODE_RIPPLE,
           GES_EDGE_END, 52) == TRUE);
   CHECK_OBJECT_PROPS (trackelement, 32, 5, 20);
   CHECK_OBJECT_PROPS (trackelement1, 20, 0, 10);
@@ -817,9 +760,9 @@ GST_START_TEST (test_timeline_edition_mode)
   /* We have 3 references:
    *  track  + timeline  + clip
    */
-  ASSERT_OBJECT_REFCOUNT (trackelement, "First trackelement", 4);
-  ASSERT_OBJECT_REFCOUNT (trackelement1, "Second trackelement", 4);
-  ASSERT_OBJECT_REFCOUNT (trackelement2, "Third trackelement", 4);
+  ASSERT_OBJECT_REFCOUNT (trackelement, "First trackelement", 3);
+  ASSERT_OBJECT_REFCOUNT (trackelement1, "Second trackelement", 3);
+  ASSERT_OBJECT_REFCOUNT (trackelement2, "Third trackelement", 3);
   ASSERT_OBJECT_REFCOUNT (clip, "First clip", 2);
   ASSERT_OBJECT_REFCOUNT (clip1, "Second clip", 2);
   ASSERT_OBJECT_REFCOUNT (clip2, "Third clip", 2);
@@ -904,16 +847,13 @@ GST_START_TEST (test_timeline_edition_mode)
 
   /* Snaping to edge, so no move */
   g_object_set (timeline, "snapping-distance", (guint64) 3, NULL);
-  fail_if (ges_container_edit (clip1, NULL, -1, GES_EDIT_MODE_TRIM,
-          GES_EDGE_END, 27));
+  ges_container_edit (clip1, NULL, -1, GES_EDIT_MODE_TRIM, GES_EDGE_END, 27);
   CHECK_OBJECT_PROPS (trackelement, 25, 0, 37);
   CHECK_OBJECT_PROPS (trackelement1, 20, 0, 5);
   CHECK_OBJECT_PROPS (trackelement2, 62, 0, 60);
 
   /* Snaping to edge, so no move */
-  fail_if (ges_container_edit (clip1, NULL, -1, GES_EDIT_MODE_TRIM,
-          GES_EDGE_END, 27));
-
+  ges_container_edit (clip1, NULL, -1, GES_EDIT_MODE_TRIM, GES_EDGE_END, 27);
   CHECK_OBJECT_PROPS (trackelement, 25, 0, 37);
   CHECK_OBJECT_PROPS (trackelement1, 20, 0, 5);
   CHECK_OBJECT_PROPS (trackelement2, 62, 0, 60);
@@ -939,13 +879,13 @@ GST_START_TEST (test_timeline_edition_mode)
    *                    0----------
    *                    |   clip   |
    *                    25---------62
+   * -------------------------------------------------
    * inpoints   0----------------------- 10--------
    *            |       clip1           ||  clip2  |
    * time      20---------------------- 72 --------122
    */
   /* Rolling involves only neighbours that are currently snapping */
-  fail_unless (ges_container_edit (clip1, NULL, -1, GES_EDIT_MODE_ROLL,
-          GES_EDGE_END, 62) == TRUE);
+  ges_container_edit (clip1, NULL, -1, GES_EDIT_MODE_ROLL, GES_EDGE_END, 62);
   fail_unless (ges_container_edit (clip1, NULL, -1, GES_EDIT_MODE_ROLL,
           GES_EDGE_END, 72) == TRUE);
   CHECK_OBJECT_PROPS (trackelement, 25, 0, 37);
@@ -968,19 +908,8 @@ GST_START_TEST (test_timeline_edition_mode)
   CHECK_OBJECT_PROPS (trackelement1, 25, 5, 47);
   CHECK_OBJECT_PROPS (trackelement2, 72, 10, 50);
 
-  /**
-   *                    0----------
-   *                    |   clip   |
-   *                    25---------62
-   * inpoints           5---------- 0---------
-   *                    |  clip1   ||  clip2  |
-   * time               25-------- 62 --------122
-   */
-  fail_unless (ges_container_edit (clip2, NULL, -1, GES_EDIT_MODE_ROLL,
-          GES_EDGE_START, 59) == TRUE);
-  CHECK_OBJECT_PROPS (trackelement, 25, 0, 37);
-  CHECK_OBJECT_PROPS (trackelement1, 25, 5, 37);
-  CHECK_OBJECT_PROPS (trackelement2, 62, 0, 60);
+  fail_if (ges_container_edit (clip2, NULL, -1, GES_EDIT_MODE_ROLL,
+          GES_EDGE_START, 59));
 
   ges_deinit ();
 }
@@ -1040,131 +969,70 @@ GST_START_TEST (test_groups)
   g_list_free (clips);
 
   fail_unless (GES_IS_GROUP (group));
-  DEEP_CHECK (c, 0, 0, 10);
-  DEEP_CHECK (c1, 10, 0, 10);
-  DEEP_CHECK (c2, 20, 0, 10);
+  CHECK_CLIP (c, 0, 0, 10, 0);
+  CHECK_CLIP (c1, 10, 0, 10, 1);
+  CHECK_CLIP (c2, 20, 0, 10, 1);
   CHECK_OBJECT_PROPS (group, 0, 0, 30);
 
   c3 = ges_layer_add_asset (layer, asset, 30, 0, 20, GES_TRACK_TYPE_UNKNOWN);
   c4 = ges_layer_add_asset (layer1, asset, 40, 0, 20, GES_TRACK_TYPE_UNKNOWN);
   c5 = ges_layer_add_asset (layer2, asset, 50, 0, 20, GES_TRACK_TYPE_UNKNOWN);
 
-  DEEP_CHECK (c3, 30, 0, 20);
-  DEEP_CHECK (c4, 40, 0, 20);
-  DEEP_CHECK (c5, 50, 0, 20);
+  CHECK_CLIP (c3, 30, 0, 20, 0);
+  CHECK_CLIP (c4, 40, 0, 20, 1);
+  CHECK_CLIP (c5, 50, 0, 20, 2);
   check_layer (c, 0);
   check_layer (c1, 1);
   check_layer (c2, 1);
-  check_layer (c3, 0);
-  check_layer (c4, 1);
-  check_layer (c5, 2);
 
   fail_unless (ges_container_edit (GES_CONTAINER (c), NULL, -1,
           GES_EDIT_MODE_RIPPLE, GES_EDGE_NONE, 10) == TRUE);
 
-  DEEP_CHECK (c, 10, 0, 10);
-  DEEP_CHECK (c1, 10, 0, 10);
-  DEEP_CHECK (c2, 30, 0, 10);
-  DEEP_CHECK (c3, 40, 0, 20);
-  DEEP_CHECK (c4, 50, 0, 20);
-  DEEP_CHECK (c5, 60, 0, 20);
-  check_layer (c, 0);
-  check_layer (c1, 1);
-  check_layer (c2, 1);
-  check_layer (c3, 0);
-  check_layer (c4, 1);
-  check_layer (c5, 2);
+  CHECK_CLIP (c, 10, 0, 10, 0);
+  CHECK_CLIP (c1, 20, 0, 10, 1);
+  CHECK_CLIP (c2, 30, 0, 10, 1);
+  CHECK_CLIP (c3, 40, 0, 20, 0);
+  CHECK_CLIP (c4, 50, 0, 20, 1);
+  CHECK_CLIP (c5, 60, 0, 20, 2);
 
   fail_unless (ges_container_edit (GES_CONTAINER (c), NULL, 1,
           GES_EDIT_MODE_RIPPLE, GES_EDGE_NONE, 10) == TRUE);
-  DEEP_CHECK (c, 10, 0, 10);
-  DEEP_CHECK (c1, 10, 0, 10);
-  DEEP_CHECK (c2, 30, 0, 10);
-  DEEP_CHECK (c3, 40, 0, 20);
-  DEEP_CHECK (c4, 50, 0, 20);
-  DEEP_CHECK (c5, 60, 0, 20);
-  check_layer (c, 1);
-  check_layer (c1, 2);
-  check_layer (c2, 2);
-  check_layer (c3, 1);
-  check_layer (c4, 2);
-  check_layer (c5, 3);
-
-  fail_unless (ges_container_edit (GES_CONTAINER (c1), NULL, 2,
+  CHECK_CLIP (c, 10, 0, 10, 1);
+  CHECK_CLIP (c1, 20, 0, 10, 2);
+  CHECK_CLIP (c2, 30, 0, 10, 2);
+  CHECK_CLIP (c3, 40, 0, 20, 1);
+  CHECK_CLIP (c4, 50, 0, 20, 2);
+  CHECK_CLIP (c5, 60, 0, 20, 3);
+
+  fail_if (ges_container_edit (GES_CONTAINER (c1), NULL, 2,
           GES_EDIT_MODE_RIPPLE, GES_EDGE_END, 40) == TRUE);
-  DEEP_CHECK (c, 10, 0, 10);
-  DEEP_CHECK (c1, 10, 0, 30);
-  DEEP_CHECK (c2, 50, 0, 10);
-  DEEP_CHECK (c3, 60, 0, 20);
-  DEEP_CHECK (c4, 70, 0, 20);
-  DEEP_CHECK (c5, 80, 0, 20);
-  check_layer (c, 1);
-  check_layer (c1, 2);
-  check_layer (c2, 2);
-  check_layer (c3, 1);
-  check_layer (c4, 2);
-  check_layer (c5, 3);
-
-  fail_unless (ges_container_edit (GES_CONTAINER (c1), NULL, 2,
+  fail_if (ges_container_edit (GES_CONTAINER (c1), NULL, 2,
           GES_EDIT_MODE_RIPPLE, GES_EDGE_END, 30) == TRUE);
-  DEEP_CHECK (c, 10, 0, 10);
-  DEEP_CHECK (c1, 10, 0, 20);
-  DEEP_CHECK (c2, 40, 0, 10);
-  DEEP_CHECK (c3, 50, 0, 20);
-  DEEP_CHECK (c4, 60, 0, 20);
-  DEEP_CHECK (c5, 70, 0, 20);
-  check_layer (c, 1);
-  check_layer (c1, 2);
-  check_layer (c2, 2);
-  check_layer (c3, 1);
-  check_layer (c4, 2);
-  check_layer (c5, 3);
-
+  CHECK_CLIP (c, 10, 0, 10, 1);
+  CHECK_CLIP (c1, 20, 0, 10, 2);
+  CHECK_CLIP (c2, 30, 0, 10, 2);
+  CHECK_CLIP (c3, 40, 0, 20, 1);
+  CHECK_CLIP (c4, 50, 0, 20, 2);
+  CHECK_CLIP (c5, 60, 0, 20, 3);
   fail_unless (ges_container_edit (GES_CONTAINER (c), NULL, 0,
           GES_EDIT_MODE_RIPPLE, GES_EDGE_NONE, 0) == TRUE);
-  DEEP_CHECK (c, 0, 0, 10);
-  DEEP_CHECK (c1, 10, 0, 20);
-  DEEP_CHECK (c2, 30, 0, 10);
-  DEEP_CHECK (c3, 40, 0, 20);
-  DEEP_CHECK (c4, 50, 0, 20);
-  DEEP_CHECK (c5, 60, 0, 20);
-  check_layer (c, 0);
-  check_layer (c1, 1);
-  check_layer (c2, 1);
-  check_layer (c3, 0);
-  check_layer (c4, 1);
-  check_layer (c5, 2);
-
-  fail_if (ges_container_edit (GES_CONTAINER (c2), NULL, -1,
-          GES_EDIT_MODE_ROLL, GES_EDGE_END, 40) == TRUE);
-  DEEP_CHECK (c, 0, 0, 10);
-  DEEP_CHECK (c1, 10, 0, 20);
-  DEEP_CHECK (c2, 30, 0, 10);
-  DEEP_CHECK (c3, 40, 0, 20);
-  DEEP_CHECK (c4, 50, 0, 20);
-  DEEP_CHECK (c5, 60, 0, 20);
-  check_layer (c, 0);
-  check_layer (c1, 1);
-  check_layer (c2, 1);
-  check_layer (c3, 0);
-  check_layer (c4, 1);
-  check_layer (c5, 2);
+  CHECK_CLIP (c, 0, 0, 10, 0);
+  CHECK_CLIP (c1, 10, 0, 10, 1);
+  CHECK_CLIP (c2, 20, 0, 10, 1);
+  CHECK_CLIP (c3, 30, 0, 20, 0);
+  CHECK_CLIP (c4, 40, 0, 20, 1);
+  CHECK_CLIP (c5, 50, 0, 20, 2);
+  CHECK_OBJECT_PROPS (group, 0, 0, 30);
 
   fail_unless (ges_container_edit (GES_CONTAINER (c), NULL, 0,
           GES_EDIT_MODE_TRIM, GES_EDGE_START, 5) == TRUE);
-  CHECK_OBJECT_PROPS (c, 5, 5, 5);
-  DEEP_CHECK (c1, 10, 0, 20);
-  DEEP_CHECK (c2, 30, 0, 10);
-  DEEP_CHECK (c3, 40, 0, 20);
-  DEEP_CHECK (c4, 50, 0, 20);
-  DEEP_CHECK (c5, 60, 0, 20);
-  CHECK_OBJECT_PROPS (group, 5, 0, 35);
-  check_layer (c, 0);
-  check_layer (c1, 1);
-  check_layer (c2, 1);
-  check_layer (c3, 0);
-  check_layer (c4, 1);
-  check_layer (c5, 2);
+  CHECK_CLIP (c, 5, 5, 5, 0);
+  CHECK_CLIP (c1, 10, 0, 10, 1);
+  CHECK_CLIP (c2, 20, 0, 10, 1);
+  CHECK_CLIP (c3, 30, 0, 20, 0);
+  CHECK_CLIP (c4, 40, 0, 20, 1);
+  CHECK_CLIP (c5, 50, 0, 20, 2);
+  CHECK_OBJECT_PROPS (group, 5, 0, 25);
 
   gst_object_unref (timeline);
   gst_object_unref (asset);
index 2444f8a..3eb9dfe 100644 (file)
@@ -263,7 +263,7 @@ GST_START_TEST (test_filesource_images)
   fail_unless (GES_IS_IMAGE_SOURCE (track_element));
 
   ASSERT_OBJECT_REFCOUNT (track_element, "1 in track, 1 in clip 2 in timeline",
-      4);
+      3);
 
   gst_object_unref (asset);
   gst_object_unref (timeline);
index 0da9927..52d62c8 100644 (file)
@@ -194,7 +194,16 @@ class GESSimpleTimelineTest(GESTest):
 
         return clip
 
-    def assertTimelineTopology(self, topology):
+    def append_clip(self, layer=0):
+        layer = self.timeline.get_layers()[layer]
+        clip = GES.TestClip()
+        clip.props.start = layer.get_duration()
+        clip.props.duration = 10
+        self.assertTrue(layer.add_clip(clip))
+
+        return clip
+
+    def assertTimelineTopology(self, topology, groups=[]):
         res = []
         for layer in self.timeline.get_layers():
             layer_timings = []
@@ -203,6 +212,14 @@ class GESSimpleTimelineTest(GESTest):
                     (type(clip), clip.props.start, clip.props.duration))
 
             res.append(layer_timings)
+        if topology != res:
+            Gst.error(self.timeline_as_str())
+            self.assertEqual(topology, res)
+
+        timeline_groups = self.timeline.get_groups()
+        if groups and timeline_groups:
+            for i, group in enumerate(groups):
+                self.assertEqual(set(group), set(timeline_groups[i].get_children(False)))
+            self.assertEqual(len(timeline_groups), i + 1)
 
-        self.assertEqual(topology, res)
-        return res
\ No newline at end of file
+        return res
index feafd13..eb5a874 100644 (file)
@@ -251,6 +251,8 @@ class TestGroup(common.GESSimpleTimelineTest):
         self.assertEqual(audio_transition.props.duration, 10)
 
     def test_moving_group_snapping_from_the_middle(self):
+        self.track_types = [GES.TrackType.AUDIO]
+        super().setUp()
         snapped_positions = []
         def snapping_started_cb(timeline, first_element, second_element,
                                 position, snapped_positions):
@@ -272,10 +274,88 @@ class TestGroup(common.GESSimpleTimelineTest):
         group = GES.Container.group(clips[1:3])
         self.assertIsNotNone(group)
 
+        self.assertTimelineTopology([
+            [
+                (GES.TestClip, 0, 5),
+                (GES.TestClip, 5, 5),
+                (GES.TestClip, 10, 5),
+                (GES.TestClip, 15, 5),
+            ],
+        ], groups=[clips[1:3]])
+
         self.assertEqual(clips[1].props.start, 5)
         self.assertEqual(clips[2].props.start, 10)
         clips[2].edit([], 0, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 11)
 
-        self.assertEqual(snapped_positions[0], clips[2].start + clips[2].duration)
-        self.assertEqual(clips[1].props.start, 5)
-        self.assertEqual(clips[2].props.start, 10)
+        self.assertEqual(snapped_positions[0], 5)
+        self.assertTimelineTopology([
+            [
+                (GES.TestClip, 0, 5),
+                (GES.TestClip, 5, 5),
+                (GES.TestClip, 10, 5),
+                (GES.TestClip, 15, 5),
+            ],
+        ], groups=[clips[1:3]])
+
+    def test_rippling_with_group(self):
+        self.track_types = [GES.TrackType.AUDIO]
+        super().setUp()
+        for _ in range(4):
+            self.append_clip()
+
+        snapped_positions = []
+        def snapping_started_cb(timeline, first_element, second_element,
+                                position, snapped_positions):
+            snapped_positions.append(position)
+
+        self.timeline.props.snapping_distance = 5
+        self.timeline.connect("snapping-started", snapping_started_cb,
+                              snapped_positions)
+
+        clips = self.layer.get_clips()
+        self.assertEqual(len(clips), 4)
+
+        group_clips = clips[1:3]
+        GES.Container.group(group_clips)
+        self.assertTimelineTopology([
+            [
+                (GES.TestClip, 0, 10),
+                (GES.TestClip, 10, 10),
+                (GES.TestClip, 20, 10),
+                (GES.TestClip, 30, 10),
+            ],
+        ], groups=[group_clips])
+
+        self.assertFalse(clips[2].edit([], 0, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, 5))
+
+        self.assertTimelineTopology([
+            [
+                (GES.TestClip, 0, 10),
+                (GES.TestClip, 10, 10),
+                (GES.TestClip, 20, 10),
+                (GES.TestClip, 30, 10),
+            ],
+        ], groups=[group_clips])
+
+        # Negative start...
+        self.assertFalse(clips[2].edit([], 1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, 1))
+        self.assertTimelineTopology([
+            [
+                (GES.TestClip, 0, 10),
+                (GES.TestClip, 10, 10),
+                (GES.TestClip, 20, 10),
+                (GES.TestClip, 30, 10),
+            ],
+        ], groups=[group_clips])
+
+        self.assertTrue(clips[2].edit([], 1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, 20))
+        self.assertTimelineTopology([
+            [
+                (GES.TestClip, 0, 10),
+            ],
+            [
+                (GES.TestClip, 10, 10),
+                (GES.TestClip, 20, 10),
+                (GES.TestClip, 30, 10),
+            ],
+        ], groups=[group_clips])
\ No newline at end of file
index 6f3b465..67905b0 100644 (file)
@@ -35,7 +35,7 @@ Gst.init(None)
 GES.init()
 
 
-class TestTimeline(unittest.TestCase):
+class TestTimeline(common.GESSimpleTimelineTest):
 
     def test_signals_not_emitted_when_loading(self):
         mainloop = common.create_main_loop()
@@ -63,13 +63,31 @@ class TestTimeline(unittest.TestCase):
         self.assertTrue(loaded_called)
         handle.assert_not_called()
 
+    def test_timeline_duration(self):
+        self.append_clip()
+        self.append_clip()
+        clips = self.layer.get_clips()
 
-class TestSplitting(common.GESSimpleTimelineTest):
-    def setUp(self):
-        self.track_types = [GES.TrackType.AUDIO]
-        super(TestSplitting, self).setUp()
+        self.assertEqual(self.timeline.props.duration, 20)
+        self.layer.remove_clip(clips[1])
+        self.assertEqual(self.timeline.props.duration, 10)
+
+        self.append_clip()
+        self.append_clip()
+        clips = self.layer.get_clips()
+        self.assertEqual(self.timeline.props.duration, 30)
+
+        group = GES.Container.group(clips[1:])
+        self.assertEqual(self.timeline.props.duration, 30)
+
+        group1 = GES.Container.group([])
+        group1.add(group)
+        self.assertEqual(self.timeline.props.duration, 30)
 
     def test_spliting_with_auto_transition_on_the_left(self):
+        self.track_types = [GES.TrackType.AUDIO]
+        super().setUp()
+
         self.timeline.props.auto_transition = True
         clip1 = self.add_clip(0, 0, 100)
         clip2 = self.add_clip(50, 0, 100)
@@ -117,6 +135,102 @@ class TestEditing(common.GESSimpleTimelineTest):
         self.assertEqual(len(self.layer.get_clips()), 1)
         self.assertEqual(len(layer2.get_clips()), 1)
 
+    def activate_snapping(self):
+        self.timeline.set_snapping_distance(5)
+        self.snapped_at = []
+
+        def _snapped_cb(timeline, elem1, elem2, position):
+            self.snapped_at.append(position)
+            Gst.error('%s' % position)
+
+        def _snapped_end_cb(timeline, elem1, elem2, position):
+            if self.snapped_at:  # Ignoring first snap end.
+                self.snapped_at.append(Gst.CLOCK_TIME_NONE)
+                Gst.error('%s' % position)
+
+        self.timeline.connect("snapping-started", _snapped_cb)
+        self.timeline.connect("snapping-ended", _snapped_end_cb)
+
+    def test_snap_start_snap_end(self):
+        clip = self.append_clip()
+        self.append_clip()
+
+        self.activate_snapping()
+        self.assertTimelineTopology([
+            [  # Unique layer
+                (GES.TestClip, 0, 10),
+                (GES.TestClip, 10, 10),
+            ]
+        ])
+
+        clip.props.start = 18
+        self.assertTimelineTopology([
+            [  # Unique layer
+                (GES.TestClip, 10, 10),
+                (GES.TestClip, 20, 10),
+            ]
+        ])
+        self.assertEqual(self.snapped_at, [20])
+
+        clip.props.start = 30
+        self.assertTimelineTopology([
+            [  # Unique layer
+                (GES.TestClip, 10, 10),
+                (GES.TestClip, 30, 10),
+            ]
+        ])
+        self.assertEqual(self.snapped_at, [20, Gst.CLOCK_TIME_NONE])
+
+        clip.props.start = 18
+        self.assertTimelineTopology([
+            [  # Unique layer
+                (GES.TestClip, 10, 10),
+                (GES.TestClip, 20, 10),
+            ]
+        ])
+        self.assertEqual(self.snapped_at, [20, Gst.CLOCK_TIME_NONE,
+            Gst.CLOCK_TIME_NONE, 20])
+        clip.props.start = 19
+        self.assertTimelineTopology([
+            [  # Unique layer
+                (GES.TestClip, 10, 10),
+                (GES.TestClip, 20, 10),
+            ]
+        ])
+        self.assertEqual(self.snapped_at, [20, Gst.CLOCK_TIME_NONE,
+            Gst.CLOCK_TIME_NONE, 20])
+
+    def test_rippling_snaps(self):
+        self.timeline.props.auto_transition = True
+        self.append_clip()
+        clip = self.append_clip()
+
+        self.activate_snapping()
+        self.assertTimelineTopology([
+            [  # Unique layer
+                (GES.TestClip, 0, 10),
+                (GES.TestClip, 10, 10),
+            ]
+        ])
+
+        clip.edit([], 0, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, 15)
+        self.assertEqual(self.snapped_at, [10])
+        self.assertTimelineTopology([
+            [  # Unique layer
+                (GES.TestClip, 0, 10),
+                (GES.TestClip, 10, 10),
+            ]
+        ])
+
+        clip.edit([], 0, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, 20)
+        self.assertEqual(self.snapped_at, [10, Gst.CLOCK_TIME_NONE])
+        self.assertTimelineTopology([
+            [  # Unique layer
+                (GES.TestClip, 0, 10),
+                (GES.TestClip, 20, 10),
+            ]
+        ])
+
     def test_transition_moves_when_rippling_to_another_layer(self):
         self.timeline.props.auto_transition = True
         clip1 = self.add_clip(0, 0, 100)
@@ -160,6 +274,451 @@ class TestEditing(common.GESSimpleTimelineTest):
                    GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, 35 * Gst.SECOND)
         self.assertEqual(len(self.layer.get_clips()), 4)
 
+    def test_trim_transition(self):
+        self.track_types = [GES.TrackType.AUDIO]
+        super().setUp()
+
+        self.timeline.props.auto_transition = True
+        self.add_clip(0, 0, 10)
+        self.add_clip(5, 0, 10)
+        self.assertTimelineTopology([
+            [  # Unique layer
+                (GES.TestClip, 0, 10),
+                (GES.TransitionClip, 5, 5),
+                (GES.TestClip, 5, 10),
+            ]
+        ])
+        transition = self.layer.get_clips()[1]
+        self.assertTrue(transition.edit([], -1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 7))
+
+        self.assertTimelineTopology([
+            [  # Unique layer
+                (GES.TestClip, 0, 10),
+                (GES.TransitionClip, 7, 3),
+                (GES.TestClip, 7, 8),
+            ]
+        ])
+
+    def test_trim_start(self):
+        clip = self.append_clip()
+        self.assertTrue(clip.edit([], -1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 10))
+        self.assertTimelineTopology([
+            [  # Unique layer
+                (GES.TestClip, 10, 10),
+            ]
+        ])
+
+        self.assertFalse(clip.edit([], -1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_NONE, 0))
+        self.assertTimelineTopology([
+            [  # Unique layer
+                (GES.TestClip, 10, 10),
+            ]
+        ])
+
+    def test_ripple_end(self):
+        clip = self.append_clip()
+        clip.set_max_duration(20)
+        self.append_clip().set_max_duration(10)
+        self.append_clip().set_max_duration(10)
+        self.print_timeline()
+        self.assertTrue(clip.edit([], -1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_END, 20))
+        self.assertTimelineTopology([
+            [  # Unique layer
+                (GES.TestClip, 0, 20),
+                (GES.TestClip, 20, 10),
+                (GES.TestClip, 30, 10),
+            ]
+        ])
+
+        self.assertTrue(clip.edit([], -1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_END, 15))
+        self.assertTimelineTopology([
+            [  # Unique layer
+                (GES.TestClip, 0, 15),
+                (GES.TestClip, 15, 10),
+                (GES.TestClip, 25, 10),
+            ]
+        ])
+
+    def test_move_group_full_overlap(self):
+        self.track_types = [GES.TrackType.AUDIO]
+        super().setUp()
+
+        for _ in range(4):
+            self.append_clip()
+        clips = self.layer.get_clips()
+
+        self.assertTrue(clips[0].ripple(20))
+        self.assertTimelineTopology([
+            [
+                (GES.TestClip, 20, 10),
+                (GES.TestClip, 30, 10),
+                (GES.TestClip, 40, 10),
+                (GES.TestClip, 50, 10),
+            ]
+        ])
+        group = GES.Container.group(clips[1:])
+        self.print_timeline()
+        self.assertFalse(group.edit([], -1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 0))
+        self.print_timeline()
+        self.assertTimelineTopology([
+            [
+                (GES.TestClip, 20, 10),
+                (GES.TestClip, 30, 10),
+                (GES.TestClip, 40, 10),
+                (GES.TestClip, 50, 10),
+            ]
+        ])
+
+        self.assertFalse(clips[1].edit([], -1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 0))
+        self.print_timeline()
+        self.assertTimelineTopology([
+            [
+                (GES.TestClip, 20, 10),
+                (GES.TestClip, 30, 10),
+                (GES.TestClip, 40, 10),
+                (GES.TestClip, 50, 10),
+            ]
+        ])
+
+    def test_trim_inside_group(self):
+        self.track_types = [GES.TrackType.AUDIO]
+        super().setUp()
+
+        for _ in range(2):
+            self.append_clip()
+        clips = self.layer.get_clips()
+        group = GES.Container.group(clips)
+        self.assertTimelineTopology([
+            [  # Unique layer
+                (GES.TestClip, 0, 10),
+                (GES.TestClip, 10, 10),
+            ]
+        ])
+        self.assertEqual(group.props.start, 0)
+        self.assertEqual(group.props.duration, 20)
+
+        clips[0].trim(5)
+        self.assertTimelineTopology([
+            [  # Unique layer
+                (GES.TestClip, 5, 5),
+                (GES.TestClip, 10, 10),
+            ]
+        ])
+        self.assertEqual(group.props.start, 5)
+        self.assertEqual(group.props.duration, 15)
+
+    def test_trim_end_past_max_duration(self):
+        clip = self.append_clip()
+        max_duration = clip.props.duration
+        clip.set_max_duration(max_duration)
+        self.assertTrue(clip.edit([], -1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 5))
+        self.assertTimelineTopology([
+            [  # Unique layer
+                (GES.TestClip, 5, 5),
+            ]
+        ])
+
+        self.assertFalse(clip.edit([], -1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, 15))
+        self.assertTimelineTopology([
+            [  # Unique layer
+                (GES.TestClip, 5, 5),
+            ]
+        ])
+
+
+class TestInvalidOverlaps(common.GESSimpleTimelineTest):
+
+    def test_adding_or_moving(self):
+        clip1 = self.add_clip(start=10, in_point=0, duration=3)
+        self.assertIsNotNone(clip1)
+
+        def check_add_move_clip(start, duration):
+            self.timeline.props.auto_transition = True
+            self.layer.props.auto_transition = True
+            clip2 = GES.TestClip()
+            clip2.props.start = start
+            clip2.props.duration = duration
+            self.assertFalse(self.layer.add_clip(clip2))
+            self.assertEqual(len(self.layer.get_clips()), 1)
+
+            # Add the clip at a different position.
+            clip2.props.start = 25
+            self.assertTrue(self.layer.add_clip(clip2))
+            self.assertEqual(clip2.props.start, 25)
+
+            # Try to move the second clip by editing it.
+            self.assertFalse(clip2.edit([], -1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, start))
+            self.assertEqual(clip2.props.start, 25)
+
+            # Try to put it in a group and move the group.
+            clip3 = GES.TestClip()
+            clip3.props.start = 20
+            clip3.props.duration = 1
+            self.assertTrue(self.layer.add_clip(clip3))
+            group = GES.Container.group([clip3, clip2])
+            self.assertTrue(group.props.start, 20)
+            self.assertFalse(group.edit([], -1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, start - 5))
+            self.assertEqual(group.props.start, 20)
+            self.assertEqual(clip3.props.start, 20)
+            self.assertEqual(clip2.props.start, 25)
+
+            for clip in group.ungroup(False):
+                self.assertTrue(self.layer.remove_clip(clip))
+
+        # clip1 contains...
+        check_add_move_clip(start=10, duration=1)
+        check_add_move_clip(start=11, duration=1)
+        check_add_move_clip(start=12, duration=1)
+
+    def test_splitting(self):
+        clip1 = self.add_clip(start=9, in_point=0, duration=3)
+        clip2 = self.add_clip(start=10, in_point=0, duration=4)
+        clip3 = self.add_clip(start=12, in_point=0, duration=3)
+
+        self.assertIsNone(clip1.split(13))
+        self.assertIsNone(clip1.split(8))
+
+        self.assertIsNone(clip3.split(12))
+        self.assertIsNone(clip3.split(15))
+
+    def test_changing_duration(self):
+        clip1 = self.add_clip(start=9, in_point=0, duration=2)
+        clip2 = self.add_clip(start=10, in_point=0, duration=2)
+
+        self.assertFalse(clip1.set_start(10))
+        self.assertFalse(clip1.edit([], -1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, clip2.props.start + clip2.props.duration))
+        self.assertFalse(clip1.ripple_end(clip2.props.start + clip2.props.duration))
+        self.assertFalse(clip1.roll_end(clip2.props.start + clip2.props.duration))
+
+        # clip2's end edge to the left, to decrease its duration.
+        self.assertFalse(clip2.edit([], -1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_END, clip1.props.start + clip1.props.duration))
+        self.assertFalse(clip2.ripple_end(clip1.props.start + clip1.props.duration))
+        self.assertFalse(clip2.roll_end(clip1.props.start + clip1.props.duration))
+
+        # clip2's start edge to the left, to increase its duration.
+        self.assertFalse(clip2.edit([], -1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, clip1.props.start))
+        self.assertFalse(clip2.trim(clip1.props.start))
+
+        # clip1's start edge to the right, to decrease its duration.
+        self.assertFalse(clip1.edit([], -1, GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, clip2.props.start))
+        self.assertFalse(clip1.trim(clip2.props.start))
+
+    def test_rippling_backward(self):
+        self.track_types = [GES.TrackType.AUDIO]
+        super().setUp()
+        self.maxDiff = None
+        for i in range(4):
+            self.append_clip()
+        self.assertTimelineTopology([
+            [  # Unique layer
+                (GES.TestClip, 0, 10),
+                (GES.TestClip, 10, 10),
+                (GES.TestClip, 20, 10),
+                (GES.TestClip, 30, 10),
+            ]
+        ])
+
+        clip = self.layer.get_clips()[2]
+        self.assertFalse(clip.edit([], -1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, clip.props.start - 20))
+        self.assertTimelineTopology([
+            [  # Unique layer
+                (GES.TestClip, 0, 10),
+                (GES.TestClip, 10, 10),
+                (GES.TestClip, 20, 10),
+                (GES.TestClip, 30, 10),
+            ]
+        ])
+        self.assertTrue(clip.edit([], -1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, clip.props.start + 10))
+
+        self.assertTimelineTopology([
+            [  # Unique layer
+                (GES.TestClip, 0, 10),
+                (GES.TestClip, 10, 10),
+                (GES.TestClip, 30, 10),
+                (GES.TestClip, 40, 10),
+            ]
+        ])
+
+        self.assertFalse(clip.edit([], -1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, clip.props.start -20))
+        self.assertTimelineTopology([
+            [  # Unique layer
+                (GES.TestClip, 0, 10),
+                (GES.TestClip, 10, 10),
+                (GES.TestClip, 30, 10),
+                (GES.TestClip, 40, 10),
+            ]
+        ])
+
+    def test_rolling(self):
+        clip1 = self.add_clip(start=9, in_point=0, duration=2)
+        clip2 = self.add_clip(start=10, in_point=0, duration=2)
+        clip3 = self.add_clip(start=11, in_point=0, duration=2)
+
+        # Rolling clip1's end -1 would lead to clip3 to overlap 100% with clip2.
+        self.assertTimelineTopology([
+            [  # Unique layer
+                (GES.TestClip, 9, 2),
+                (GES.TestClip, 10, 2),
+                (GES.TestClip, 11, 2)
+            ]
+        ])
+        self.assertFalse(clip1.edit([], -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_END, clip1.props.start + clip1.props.duration - 1))
+        self.assertFalse(clip1.roll_end(13))
+        self.assertTimelineTopology([
+            [  # Unique layer
+                (GES.TestClip, 9, 2),
+                (GES.TestClip, 10, 2),
+                (GES.TestClip, 11, 2)
+            ]
+        ])
+
+        # Rolling clip3's start +1 would lead to clip1 to overlap 100% with clip2.
+        self.assertFalse(clip3.edit([], -1, GES.EditMode.EDIT_ROLL, GES.Edge.EDGE_START, 12))
+        self.assertTimelineTopology([
+            [  # Unique layer
+                (GES.TestClip, 9, 2),
+                (GES.TestClip, 10, 2),
+                (GES.TestClip, 11, 2)
+            ]
+        ])
+
+    def test_layers(self):
+        self.track_types = [GES.TrackType.AUDIO]
+        super().setUp()
+        self.maxDiff = None
+        self.timeline.append_layer()
+
+        for i in range(2):
+            self.append_clip()
+            self.append_clip(1)
+
+        self.assertTimelineTopology([
+            [
+                (GES.TestClip, 0, 10),
+                (GES.TestClip, 10, 10),
+            ],
+            [
+                (GES.TestClip, 0, 10),
+                (GES.TestClip, 10, 10),
+            ]
+        ])
+
+        clip = self.layer.get_clips()[0]
+        self.assertFalse(clip.edit([], 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 0))
+        self.assertTimelineTopology([
+            [
+                (GES.TestClip, 0, 10),
+                (GES.TestClip, 10, 10),
+            ],
+            [
+                (GES.TestClip, 0, 10),
+                (GES.TestClip, 10, 10),
+            ]
+        ])
+
+    def test_rippling(self):
+        self.timeline.remove_track(self.timeline.get_tracks()[0])
+        clip1 = self.add_clip(start=9, in_point=0, duration=2)
+        clip2 = self.add_clip(start=10, in_point=0, duration=2)
+        clip3 = self.add_clip(start=11, in_point=0, duration=2)
+
+        # Rippling clip2's start -2 would bring clip3 exactly on top of clip1.
+        self.assertFalse(clip2.edit([], -1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, 8))
+        self.assertFalse(clip2.ripple(8))
+
+        # Rippling clip1's end -1 would bring clip3 exactly on top of clip2.
+        self.assertFalse(clip1.edit([], -1, GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_END, 8))
+        self.assertFalse(clip1.ripple_end(8))
+
+    def test_move_group_to_layer(self):
+        self.track_types = [GES.TrackType.AUDIO]
+        super().setUp()
+        self.append_clip()
+        self.append_clip()
+        self.append_clip()
+
+        clips = self.layer.get_clips()
+
+        clips[1].props.start += 2
+        group = GES.Container.group(clips[1:])
+        self.assertTrue(clips[1].edit([], 1, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE,
+            group.props.start))
+
+        self.assertTimelineTopology([
+            [
+                (GES.TestClip, 0, 10),
+            ],
+            [
+                (GES.TestClip, 12, 10),
+                (GES.TestClip, 20, 10),
+            ]
+        ])
+
+        clips[0].props.start = 15
+        self.assertTimelineTopology([
+            [
+                (GES.TestClip, 15, 10),
+            ],
+            [
+                (GES.TestClip, 12, 10),
+                (GES.TestClip, 20, 10),
+            ]
+        ])
+
+        self.assertFalse(clips[1].edit([], 0, GES.EditMode.EDIT_NORMAL,
+            GES.Edge.EDGE_NONE, group.props.start))
+        self.assertTimelineTopology([
+            [
+                (GES.TestClip, 15, 10),
+            ],
+            [
+                (GES.TestClip, 12, 10),
+                (GES.TestClip, 20, 10),
+            ]
+        ])
+
+    def test_move_group_with_overlaping_clips(self):
+        self.track_types = [GES.TrackType.AUDIO]
+        super().setUp()
+        self.append_clip()
+        self.append_clip()
+        self.append_clip()
+
+        self.timeline.props.auto_transition = True
+        clips = self.layer.get_clips()
+
+        clips[1].props.start += 5
+        group = GES.Container.group(clips[1:])
+        self.assertTimelineTopology([
+            [
+                (GES.TestClip, 0, 10),
+                (GES.TestClip, 15, 10),
+                (GES.TransitionClip, 20, 5),
+                (GES.TestClip, 20, 10),
+            ]
+        ])
+
+        clips[0].props.start = 30
+        self.assertTimelineTopology([
+            [
+                (GES.TestClip, 15, 10),
+                (GES.TransitionClip, 20, 5),
+                (GES.TestClip, 20, 10),
+                (GES.TestClip, 30, 10),
+            ]
+        ])
+
+        # the 3 clips would overlap
+        self.assertFalse(clips[1].edit([], 0, GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE, 25))
+        self.assertTimelineTopology([
+            [
+                (GES.TestClip, 15, 10),
+                (GES.TransitionClip, 20, 5),
+                (GES.TestClip, 20, 10),
+                (GES.TestClip, 30, 10),
+            ]
+        ])
+
 
 class TestSnapping(common.GESSimpleTimelineTest):
 
@@ -180,14 +739,77 @@ class TestSnapping(common.GESSimpleTimelineTest):
                    clip2.props.start - 1)
         self.assertEqual(clip2.props.start, split_position)
 
+    def test_trim_snapps_inside_group(self):
+        self.track_types = [GES.TrackType.AUDIO]
+        super().setUp()
+
+        self.timeline.props.auto_transition = True
+        self.timeline.set_snapping_distance(5)
+
+        snaps = []
+        def snapping_started_cb(timeline, element1, element2, dist, self):
+            snaps.append(set([element1, element2]))
+
+        self.timeline.connect('snapping-started', snapping_started_cb, self)
+        clip = self.append_clip()
+        clip1 = self.append_clip()
+
+        self.assertTimelineTopology([
+            [  # Unique layer
+                (GES.TestClip, 0, 10),
+                (GES.TestClip, 10, 10),
+            ]
+        ])
+
+        clip1.edit([], self.layer.get_priority(), GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 15)
+        self.assertTimelineTopology([
+            [  # Unique layer
+                (GES.TestClip, 0, 10),
+                (GES.TestClip, 10, 10),
+            ]
+        ])
+        self.assertEqual(snaps[0], set([clip.get_children(False)[0], clip1.get_children(False)[0]]))
+
+        clip1.edit([], self.layer.get_priority(), GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 16)
+        self.assertTimelineTopology([
+            [  # Unique layer
+                (GES.TestClip, 0, 10),
+                (GES.TestClip, 16, 4),
+            ]
+        ])
+
+    def test_trim_no_snapping_on_same_clip(self):
+        self.timeline.props.auto_transition = True
+        self.timeline.set_snapping_distance(1)
+
+        not_called = []
+        def snapping_started_cb(timeline, element1, element2, dist, self):
+            not_called.append("No snapping should happen")
+
+        self.timeline.connect('snapping-started', snapping_started_cb, self)
+        clip = self.append_clip()
+        clip.edit([], self.layer.get_priority(), GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 5)
+        self.assertEqual(not_called, [])
+        self.assertTimelineTopology([
+            [  # Unique layer
+                (GES.TestClip, 5, 5),
+            ]
+        ])
+
+        clip.edit([], self.layer.get_priority(), GES.EditMode.EDIT_TRIM, GES.Edge.EDGE_START, 4)
+        self.assertEqual(not_called, [])
+        self.assertTimelineTopology([
+            [  # Unique layer
+                (GES.TestClip, 4, 6),
+            ]
+        ])
+
     def test_no_snapping_on_split(self):
         self.timeline.props.auto_transition = True
         self.timeline.set_snapping_distance(1)
 
         not_called = []
         def snapping_started_cb(timeline, element1, element2, dist, self):
-            Gst.error("Here %s %s" % (Gst.TIME_ARGS(element1.props.start + element1.props.duration),
-                Gst.TIME_ARGS(element2.props.start)))
             not_called.append("No snapping should happen")
 
         self.timeline.connect('snapping-started', snapping_started_cb, self)
@@ -296,9 +918,9 @@ class TestTransitions(common.GESSimpleTimelineTest):
             self.assertLess(clip1.props.start + clip1.props.duration, clip2.props.start + clip2.props.duration)
             self.assertEqual(len(clips), 3)
 
-            # Even though 3 clips overlap 1 transition will be created
+            # 3 clips would be overlapping, 1 of them wasn't added!
             clips = layers[1].get_clips()
-            self.assertEqual(len(clips), 4)
+            self.assertEqual(len(clips), 3)
 
 
 class TestPriorities(common.GESSimpleTimelineTest):